from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi.responses import FileResponse from sqlalchemy.orm import Session from ..database import get_db from ..models import MapObject, ObjectMedia from ..schemas import MapObjectCreate, MapObjectRead, MapObjectUpdate, ObjectMediaRead from ..storage import ( ALLOWED_CONTENT_TYPES, MAX_FILE_SIZE, build_stored_name, is_allowed_content_type, media_file_path, remove_media_file, ) router = APIRouter(tags=["objects"]) def media_to_read(media: ObjectMedia) -> ObjectMediaRead: return ObjectMediaRead( id=media.id, object_id=media.object_id, original_name=media.original_name, content_type=media.content_type, size=media.size, created_at=media.created_at, url=f"/api/media/{media.id}/file", ) @router.get("/api/health") def health(): return {"status": "ok"} @router.get("/api/objects", response_model=list[MapObjectRead]) def list_objects(db: Session = Depends(get_db)): return db.query(MapObject).order_by(MapObject.id).all() @router.get("/api/objects/{object_id}", response_model=MapObjectRead) def get_object(object_id: int, db: Session = Depends(get_db)): obj = db.query(MapObject).filter(MapObject.id == object_id).first() if not obj: raise HTTPException(status_code=404, detail="Объект не найден") return obj @router.post("/api/objects", response_model=MapObjectRead, status_code=201) def create_object(payload: MapObjectCreate, db: Session = Depends(get_db)): data = payload.model_dump() created_at = data.pop("created_at", None) obj = MapObject(**data) if created_at is not None: obj.created_at = created_at db.add(obj) db.commit() db.refresh(obj) return obj @router.patch("/api/objects/{object_id}", response_model=MapObjectRead) def update_object( object_id: int, payload: MapObjectUpdate, db: Session = Depends(get_db), ): obj = db.query(MapObject).filter(MapObject.id == object_id).first() if not obj: raise HTTPException(status_code=404, detail="Объект не найден") for field, value in payload.model_dump(exclude_unset=True).items(): setattr(obj, field, value) db.commit() db.refresh(obj) return obj @router.delete("/api/objects/{object_id}", status_code=204) def delete_object(object_id: int, db: Session = Depends(get_db)): obj = db.query(MapObject).filter(MapObject.id == object_id).first() if not obj: raise HTTPException(status_code=404, detail="Объект не найден") for media in obj.media: remove_media_file(media.stored_name) db.delete(obj) db.commit() @router.get("/api/objects/{object_id}/media", response_model=list[ObjectMediaRead]) def list_object_media(object_id: int, db: Session = Depends(get_db)): obj = db.query(MapObject).filter(MapObject.id == object_id).first() if not obj: raise HTTPException(status_code=404, detail="Объект не найден") media_items = ( db.query(ObjectMedia) .filter(ObjectMedia.object_id == object_id) .order_by(ObjectMedia.id) .all() ) return [media_to_read(item) for item in media_items] @router.post("/api/objects/{object_id}/media", response_model=ObjectMediaRead, status_code=201) async def upload_object_media( object_id: int, file: UploadFile = File(...), db: Session = Depends(get_db), ): obj = db.query(MapObject).filter(MapObject.id == object_id).first() if not obj: raise HTTPException(status_code=404, detail="Объект не найден") content_type = file.content_type or "application/octet-stream" if not is_allowed_content_type(content_type): raise HTTPException( status_code=400, detail=f"Неподдерживаемый тип файла. Разрешены: {', '.join(sorted(ALLOWED_CONTENT_TYPES))}", ) content = await file.read() if len(content) > MAX_FILE_SIZE: raise HTTPException(status_code=400, detail="Файл слишком большой (макс. 20 МБ)") original_name = file.filename or "file" stored_name = build_stored_name(original_name) path = media_file_path(stored_name) path.write_bytes(content) media = ObjectMedia( object_id=object_id, stored_name=stored_name, original_name=original_name, content_type=content_type, size=len(content), ) db.add(media) db.commit() db.refresh(media) return media_to_read(media) @router.get("/api/media/{media_id}/file") def get_media_file(media_id: int, db: Session = Depends(get_db)): media = db.query(ObjectMedia).filter(ObjectMedia.id == media_id).first() if not media: raise HTTPException(status_code=404, detail="Медиафайл не найден") path = media_file_path(media.stored_name) if not path.exists(): raise HTTPException(status_code=404, detail="Файл не найден на диске") return FileResponse(path, media_type=media.content_type, filename=media.original_name) @router.delete("/api/media/{media_id}", status_code=204) def delete_media(media_id: int, db: Session = Depends(get_db)): media = db.query(ObjectMedia).filter(ObjectMedia.id == media_id).first() if not media: raise HTTPException(status_code=404, detail="Медиафайл не найден") remove_media_file(media.stored_name) db.delete(media) db.commit()