from pathlib import Path from uuid import uuid4 from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from sqlalchemy.orm import Session from app.auth import get_current_user_api, hash_password, verify_password from app.config import UPLOAD_DIR from app.database import get_db from app.models import ApiKey, Postcard, Profile, User from app.schemas import ( ApiKeyOut, LoginRequest, PostcardCreate, PostcardOut, PostcardUpdate, ProfileCreate, ProfileOut, RegisterRequest, ) router = APIRouter(prefix="/api", tags=["api"]) # ---------- Auth ---------- @router.post("/register", response_model=dict) def api_register(body: RegisterRequest, db: Session = Depends(get_db)): if db.query(User).filter(User.username == body.username).first(): raise HTTPException(400, "Username already exists") user = User(username=body.username, password_hash=hash_password(body.password)) db.add(user) db.commit() return {"message": "ok"} @router.post("/login", response_model=dict) def api_login(body: LoginRequest, db: Session = Depends(get_db)): user = db.query(User).filter(User.username == body.username).first() if not user or not verify_password(body.password, user.password_hash): raise HTTPException(401, "Invalid credentials") api_key = ApiKey(user_id=user.id, key=ApiKey.generate_key()) db.add(api_key) db.commit() return {"api_key": api_key.key} # ---------- Profile ---------- def _get_profile(profile_id: int, user: User, db: Session) -> Profile: p = db.query(Profile).filter(Profile.id == profile_id, Profile.user_id == user.id).first() if not p: raise HTTPException(404, "Profile not found") return p @router.get("/profiles", response_model=list[ProfileOut]) def api_list_profiles(user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): return db.query(Profile).filter(Profile.user_id == user.id).all() @router.post("/profiles", response_model=ProfileOut, status_code=201) def api_create_profile(body: ProfileCreate, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): p = Profile(user_id=user.id, nickname=body.nickname) db.add(p) db.commit() db.refresh(p) return p @router.put("/profiles/{profile_id}", response_model=ProfileOut) def api_update_profile(profile_id: int, body: ProfileCreate, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): p = _get_profile(profile_id, user, db) p.nickname = body.nickname db.commit() db.refresh(p) return p @router.delete("/profiles/{profile_id}") def api_delete_profile(profile_id: int, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): p = _get_profile(profile_id, user, db) db.delete(p) db.commit() return {"message": "ok"} # ---------- Postcard ---------- def _get_postcard(postcard_id: int, user: User, db: Session) -> Postcard: pc = ( db.query(Postcard) .join(Profile) .filter(Postcard.id == postcard_id, Profile.user_id == user.id) .first() ) if not pc: raise HTTPException(404, "Postcard not found") return pc @router.get("/profiles/{profile_id}/postcards", response_model=list[PostcardOut]) def api_list_postcards( profile_id: int, status: str | None = None, country: str | None = None, user: User = Depends(get_current_user_api), db: Session = Depends(get_db), ): _get_profile(profile_id, user, db) q = db.query(Postcard).filter(Postcard.profile_id == profile_id) if status: q = q.filter(Postcard.status == status) if country: q = q.filter(Postcard.country.ilike(f"%{country}%")) return q.order_by(Postcard.created_at.desc()).all() @router.post("/profiles/{profile_id}/postcards", response_model=PostcardOut, status_code=201) def api_create_postcard( profile_id: int, body: PostcardCreate, user: User = Depends(get_current_user_api), db: Session = Depends(get_db), ): _get_profile(profile_id, user, db) pc = Postcard(profile_id=profile_id, **body.model_dump()) db.add(pc) db.commit() db.refresh(pc) return pc @router.get("/postcards/{postcard_id}", response_model=PostcardOut) def api_get_postcard(postcard_id: int, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): return _get_postcard(postcard_id, user, db) @router.put("/postcards/{postcard_id}", response_model=PostcardOut) def api_update_postcard( postcard_id: int, body: PostcardUpdate, user: User = Depends(get_current_user_api), db: Session = Depends(get_db), ): pc = _get_postcard(postcard_id, user, db) for k, v in body.model_dump(exclude_unset=True).items(): setattr(pc, k, v) db.commit() db.refresh(pc) return pc @router.delete("/postcards/{postcard_id}") def api_delete_postcard(postcard_id: int, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): pc = _get_postcard(postcard_id, user, db) db.delete(pc) db.commit() return {"message": "ok"} # ---------- Image Upload ---------- @router.post("/postcards/{postcard_id}/image/{side}", response_model=PostcardOut) async def api_upload_image( postcard_id: int, side: str, file: UploadFile = File(...), user: User = Depends(get_current_user_api), db: Session = Depends(get_db), ): if side not in ("front", "back"): raise HTTPException(400, "side must be 'front' or 'back'") pc = _get_postcard(postcard_id, user, db) ext = Path(file.filename or "upload.jpg").suffix or ".jpg" filename = f"{uuid4().hex}{ext}" dest = UPLOAD_DIR / filename content = await file.read() dest.write_bytes(content) setattr(pc, f"image_{side}", f"/uploads/{filename}") db.commit() db.refresh(pc) return pc # ---------- API Key ---------- @router.get("/api-keys", response_model=list[ApiKeyOut]) def api_list_keys(user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): return db.query(ApiKey).filter(ApiKey.user_id == user.id).all() @router.post("/api-keys", response_model=ApiKeyOut, status_code=201) def api_create_key(user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): ak = ApiKey(user_id=user.id, key=ApiKey.generate_key()) db.add(ak) db.commit() db.refresh(ak) return ak @router.delete("/api-keys/{key_id}") def api_delete_key(key_id: int, user: User = Depends(get_current_user_api), db: Session = Depends(get_db)): ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first() if not ak: raise HTTPException(404, "API key not found") db.delete(ak) db.commit() return {"message": "ok"}