172 lines
5.1 KiB
Python
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": "提交成功"}
|