from fastapi import APIRouter, Depends, HTTPException, Form, UploadFile, File, Request import db import schemas import auth_utils import config import os import uuid import shutil from services.global_manager import global_manager from dependencies import require_admin from services.audit_service import audit_service router = APIRouter(tags=["portfolio"]) @router.get("/portfolio") async def get_public_portfolio(): query = """ SELECT p.id, p.file_path, COALESCE(o.material_name, 'Showcase') as material_name, p.order_id FROM order_photos p LEFT JOIN orders o ON p.order_id = o.id WHERE p.is_public = TRUE AND (o.id IS NULL OR o.allow_portfolio = TRUE) ORDER BY p.created_at DESC """ return db.execute_query(query) @router.get("/admin/all-photos") async def admin_get_all_photos(admin: dict = Depends(require_admin)): query = """ SELECT p.id, p.file_path, p.is_public, p.order_id, o.allow_portfolio, o.first_name, o.last_name, COALESCE(o.material_name, 'Manual') as material_name FROM order_photos p JOIN orders o ON p.order_id = o.id ORDER BY p.created_at DESC """ return db.execute_query(query) @router.post("/admin/orders/{order_id}/photos") async def admin_upload_order_photo( request: Request, order_id: int, is_public: bool = Form(False), file: UploadFile = File(...), admin: dict = Depends(require_admin) ): order = db.execute_query("SELECT allow_portfolio FROM orders WHERE id = %s", (order_id,)) if not order: raise HTTPException(status_code=404, detail="Order not found") if is_public and not order[0]['allow_portfolio']: raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage") if not file.filename: raise HTTPException(status_code=400, detail="Invalid file") unique_filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}" disk_path = os.path.join(config.UPLOAD_DIR, unique_filename) db_file_path = f"uploads/{unique_filename}" with open(disk_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) query = "INSERT INTO order_photos (order_id, file_path, is_public) VALUES (%s, %s, %s)" photo_id = db.execute_commit(query, (order_id, db_file_path, is_public)) await audit_service.log( user_id=admin['id'], action="upload_order_photo", target_type="order", target_id=order_id, details={"photo_id": photo_id, "is_public": is_public}, request=request ) # NOTIFY USER VIA WEBSOCKET order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,)) if order_info: await global_manager.notify_order_update(order_info[0]['user_id'], order_id) return {"id": photo_id, "file_path": db_file_path, "is_public": is_public} @router.patch("/admin/photos/{photo_id}") async def admin_update_photo_status(request: Request, photo_id: int, data: schemas.PhotoUpdate, admin: dict = Depends(require_admin)): query = "SELECT p.*, o.allow_portfolio FROM order_photos p JOIN orders o ON p.order_id = o.id WHERE p.id = %s" photo_data = db.execute_query(query, (photo_id,)) if not photo_data: raise HTTPException(status_code=404, detail="Photo not found") if data.is_public and not photo_data[0]['allow_portfolio']: raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage") db.execute_commit("UPDATE order_photos SET is_public = %s WHERE id = %s", (data.is_public, photo_id)) await audit_service.log( user_id=admin['id'], action="update_photo_visibility", target_type="photo", target_id=photo_id, details={"is_public": data.is_public}, request=request ) # NOTIFY USER VIA WEBSOCKET order_id = photo_data[0]['order_id'] order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,)) if order_info: await global_manager.notify_order_update(order_info[0]['user_id'], order_id) return {"id": photo_id, "is_public": data.is_public} @router.delete("/admin/photos/{photo_id}") async def admin_delete_photo(request: Request, photo_id: int, admin: dict = Depends(require_admin)): photo = db.execute_query("SELECT file_path, order_id FROM order_photos WHERE id = %s", (photo_id,)) if not photo: raise HTTPException(status_code=404, detail="Photo not found") order_id = photo[0]['order_id'] try: path = os.path.join(config.BASE_DIR, photo[0]['file_path']) if os.path.exists(path): os.remove(path) except Exception as e: print(f"Error deleting photo file: {e}") db.execute_commit("DELETE FROM order_photos WHERE id = %s", (photo_id,)) await audit_service.log( user_id=admin['id'], action="delete_photo", target_type="photo", target_id=photo_id, details={"order_id": order_id}, request=request ) # NOTIFY USER VIA WEBSOCKET order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,)) if order_info: await global_manager.notify_order_update(order_info[0]['user_id'], order_id) return {"id": photo_id, "status": "deleted"}