portfolio.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. from fastapi import APIRouter, Depends, HTTPException, Form, UploadFile, File
  2. import db
  3. import schemas
  4. import auth_utils
  5. import config
  6. import os
  7. import uuid
  8. import shutil
  9. from services.global_manager import global_manager
  10. router = APIRouter(tags=["portfolio"])
  11. @router.get("/portfolio")
  12. async def get_public_portfolio():
  13. query = """
  14. SELECT p.id, p.file_path, COALESCE(o.material_name, 'Showcase') as material_name, p.order_id
  15. FROM order_photos p
  16. LEFT JOIN orders o ON p.order_id = o.id
  17. WHERE p.is_public = TRUE AND (o.id IS NULL OR o.allow_portfolio = TRUE)
  18. ORDER BY p.created_at DESC
  19. """
  20. return db.execute_query(query)
  21. @router.get("/admin/all-photos")
  22. async def admin_get_all_photos(token: str = Depends(auth_utils.oauth2_scheme)):
  23. payload = auth_utils.decode_token(token)
  24. if not payload or payload.get("role") != 'admin':
  25. raise HTTPException(status_code=403, detail="Admin role required")
  26. query = """
  27. SELECT p.id, p.file_path, p.is_public, p.order_id, o.allow_portfolio,
  28. o.first_name, o.last_name, COALESCE(o.material_name, 'Manual') as material_name
  29. FROM order_photos p
  30. JOIN orders o ON p.order_id = o.id
  31. ORDER BY p.created_at DESC
  32. """
  33. return db.execute_query(query)
  34. @router.post("/admin/orders/{order_id}/photos")
  35. async def admin_upload_order_photo(
  36. order_id: int,
  37. is_public: bool = Form(False),
  38. file: UploadFile = File(...),
  39. token: str = Depends(auth_utils.oauth2_scheme)
  40. ):
  41. payload = auth_utils.decode_token(token)
  42. if not payload or payload.get("role") != 'admin':
  43. raise HTTPException(status_code=403, detail="Admin role required")
  44. order = db.execute_query("SELECT allow_portfolio FROM orders WHERE id = %s", (order_id,))
  45. if not order: raise HTTPException(status_code=404, detail="Order not found")
  46. if is_public and not order[0]['allow_portfolio']:
  47. raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage")
  48. if not file.filename: raise HTTPException(status_code=400, detail="Invalid file")
  49. unique_filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
  50. disk_path = os.path.join(config.UPLOAD_DIR, unique_filename)
  51. db_file_path = f"uploads/{unique_filename}"
  52. with open(disk_path, "wb") as buffer:
  53. shutil.copyfileobj(file.file, buffer)
  54. query = "INSERT INTO order_photos (order_id, file_path, is_public) VALUES (%s, %s, %s)"
  55. photo_id = db.execute_commit(query, (order_id, db_file_path, is_public))
  56. # NOTIFY USER VIA WEBSOCKET
  57. order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  58. if order_info:
  59. await global_manager.notify_order_update(order_info[0]['user_id'], order_id)
  60. return {"id": photo_id, "file_path": db_file_path, "is_public": is_public}
  61. @router.patch("/admin/photos/{photo_id}")
  62. async def admin_update_photo_status(photo_id: int, data: schemas.PhotoUpdate, token: str = Depends(auth_utils.oauth2_scheme)):
  63. payload = auth_utils.decode_token(token)
  64. if not payload or payload.get("role") != 'admin':
  65. raise HTTPException(status_code=403, detail="Admin role required")
  66. query = "SELECT p.*, o.allow_portfolio FROM order_photos p JOIN orders o ON p.order_id = o.id WHERE p.id = %s"
  67. photo_data = db.execute_query(query, (photo_id,))
  68. if not photo_data: raise HTTPException(status_code=404, detail="Photo not found")
  69. if data.is_public and not photo_data[0]['allow_portfolio']:
  70. raise HTTPException(status_code=400, detail="Cannot make public: User did not consent to portfolio usage")
  71. db.execute_commit("UPDATE order_photos SET is_public = %s WHERE id = %s", (data.is_public, photo_id))
  72. # NOTIFY USER VIA WEBSOCKET
  73. order_id = photo_data[0]['order_id']
  74. order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  75. if order_info:
  76. await global_manager.notify_order_update(order_info[0]['user_id'], order_id)
  77. return {"id": photo_id, "is_public": data.is_public}
  78. @router.delete("/admin/photos/{photo_id}")
  79. async def admin_delete_photo(photo_id: int, token: str = Depends(auth_utils.oauth2_scheme)):
  80. payload = auth_utils.decode_token(token)
  81. if not payload or payload.get("role") != 'admin':
  82. raise HTTPException(status_code=403, detail="Admin role required")
  83. photo = db.execute_query("SELECT file_path, order_id FROM order_photos WHERE id = %s", (photo_id,))
  84. if not photo:
  85. raise HTTPException(status_code=404, detail="Photo not found")
  86. order_id = photo[0]['order_id']
  87. try:
  88. path = os.path.join(config.BASE_DIR, photo[0]['file_path'])
  89. if os.path.exists(path):
  90. os.remove(path)
  91. except Exception as e:
  92. print(f"Error deleting photo file: {e}")
  93. db.execute_commit("DELETE FROM order_photos WHERE id = %s", (photo_id,))
  94. # NOTIFY USER VIA WEBSOCKET
  95. order_info = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  96. if order_info:
  97. await global_manager.notify_order_update(order_info[0]['user_id'], order_id)
  98. return {"id": photo_id, "status": "deleted"}