Files

172 lines
5.1 KiB
Python

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.core.deps import require_member
from app.db import get_db
from app.models.content import AdminFile, read_upload_limit
from app.models.expert import create_question, member_question, member_questions
from app.models.member import get_member_by_id
from app.models.uploaded_case import create_uploaded_case
from app.services.storage import (
make_object_key,
private_object_url,
remove_stored_object,
safe_filename,
upload_object,
)
router = APIRouter(dependencies=[Depends(require_member)])
class QuestionWrite(BaseModel):
title: str = ""
body: str = ""
attachmentFileIds: list[int] = Field(default_factory=list)
def _member_id(principal: dict) -> int:
try:
return int(principal["sub"])
except (TypeError, ValueError, KeyError) as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="登录已失效") from exc
def _active_member(db: Session, principal: dict):
member = get_member_by_id(db, _member_id(principal))
if member is None or member.status != "active":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="登录已失效")
return member
def _content_type(value: str | None) -> str:
raw = (value or "").split(";", 1)[0].strip().lower()
return raw.replace("\r", "").replace("\n", "")[:128]
def _read_limited(upload: UploadFile, limit: int) -> bytes:
chunks = bytearray()
while True:
piece = upload.file.read(65536)
if not piece:
break
if len(chunks) + len(piece) > limit:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="文件超过大小上限")
chunks.extend(piece)
return bytes(chunks)
@router.get("/me")
def member_me(
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
) -> dict:
member = _active_member(db, principal)
return {
"id": member.id,
"email": member.email,
"tokenType": principal["typ"],
}
@router.post("/files")
def upload_private_file(
file: UploadFile = File(...),
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
):
member = _active_member(db, principal)
limit = read_upload_limit(db)
if limit is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="未配置上传大小上限")
data = _read_limited(file, limit)
filename = safe_filename(file.filename)
content_type = _content_type(file.content_type)
object_key = make_object_key(filename)
upload_object("private", object_key, data, content_type)
row = AdminFile(
bucket="private",
object_key=object_key,
filename=filename,
content_type=content_type,
size=len(data),
created_by=member.id,
)
db.add(row)
try:
db.commit()
db.refresh(row)
except Exception:
db.rollback()
try:
remove_stored_object("private", object_key)
except HTTPException:
pass
raise
url, expires_at = private_object_url(row.object_key)
return {
"id": row.id,
"bucket": row.bucket,
"filename": row.filename,
"contentType": row.content_type,
"size": row.size,
"url": url,
"expiresAt": expires_at.isoformat(),
}
@router.get("/expert-questions")
def list_my_questions(
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
) -> dict:
member = _active_member(db, principal)
return member_questions(db, member.id)
@router.get("/expert-questions/{question_id}")
def read_my_question(
question_id: int,
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
) -> dict:
member = _active_member(db, principal)
body = member_question(db, member.id, question_id)
if body is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="提问不存在")
return body
@router.post("/expert-questions")
def ask_question(
body: QuestionWrite,
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
) -> dict:
member = _active_member(db, principal)
try:
question_id = create_question(
db,
member_id=member.id,
title=body.title,
body=body.body,
attachment_file_ids=body.attachmentFileIds,
)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
return {"id": question_id}
@router.post("/uploaded-cases")
def submit_uploaded_case(
body: dict,
principal: dict = Depends(require_member),
db: Session = Depends(get_db),
) -> dict:
member = _active_member(db, principal)
try:
row = create_uploaded_case(db, member, body)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
return {"id": row.id, "message": "提交成功"}