""" api/persons.py — Endpoints quản lý thông tin Person GET /api/persons — Danh sách tất cả persons có embedding PUT /api/persons/ — Cập nhật thông tin cá nhân DELETE /api/persons/ — Xóa person (cascade: visits, embeddings, logs, MongoDB) DELETE /api/db/ — Legacy alias xóa theo tên file (backward compat) """ import logging from flask import Blueprint, jsonify, request from face_model.analyzer import face_analyzer from storage.mongo_storage import mongo_storage from storage.pg_storage import pg_storage logger = logging.getLogger("api.persons") bp = Blueprint("persons", __name__, url_prefix="/api") @bp.get("/persons") def list_persons(): """ GET /api/persons Trả về danh sách tất cả persons có face_embedding active. """ try: persons = pg_storage.list_persons_with_faces() for p in persons: crop_id = p.get("face_crop_mongo_id", "") p["face_url"] = f"/api/face-crops/{crop_id}" if crop_id else None return jsonify({ "total": len(persons), "persons": persons, }), 200 except Exception as e: logger.error(f"[list_persons] Error: {e}", exc_info=True) return jsonify({"error": { "code": "FETCH_FAILED", "message": f"Không thể lấy danh sách persons: {e}", "status": 500, }}), 500 @bp.put("/persons/") def update_person(person_id: str): """ PUT /api/persons/ Body JSON: { full_name, date_of_birth?, phone_number?, address? } """ body = request.get_json(silent=True) if not body or "full_name" not in body: return jsonify({"error": { "code": "VALIDATION_ERROR", "message": "Thiếu trường bắt buộc: full_name.", "status": 400, }}), 400 full_name = body["full_name"].strip() date_of_birth = body.get("date_of_birth") phone_number = body.get("phone_number") address = body.get("address") if not full_name: return jsonify({"error": { "code": "VALIDATION_ERROR", "message": "full_name không được để trống.", "status": 400, }}), 400 try: updated = pg_storage.update_person_info( person_id=person_id, full_name=full_name, date_of_birth=date_of_birth, phone_number=phone_number, address=address, ) if not updated: return jsonify({"error": { "code": "NOT_FOUND", "message": f"Không tìm thấy person với ID: {person_id}", "status": 404, }}), 404 # Cập nhật tên trong RAM cache face_analyzer.update_name_in_cache(person_id, full_name) return jsonify({ "success": True, "person_id": person_id, "full_name": full_name, "message": f"Đã cập nhật thông tin cho {full_name}.", }), 200 except Exception as e: logger.error(f"[update_person] Error: {e}", exc_info=True) return jsonify({"error": { "code": "UPDATE_FAILED", "message": f"Không thể cập nhật thông tin: {e}", "status": 500, }}), 500 @bp.delete("/persons/") def delete_person(person_id: str): """ DELETE /api/persons/ Xóa person và toàn bộ dữ liệu liên quan (cascade). """ try: success, mongo_ids = pg_storage.delete_person(person_id) if not success: return jsonify({"error": { "code": "NOT_FOUND", "message": f"Không tìm thấy person với ID: {person_id}", "status": 404, }}), 404 # Xóa ảnh crop trên MongoDB for mid in mongo_ids: mongo_storage.delete_face_crop(mid) # Xóa khỏi RAM cache face_analyzer.remove_from_cache(person_id) return jsonify({ "success": True, "person_id": person_id, "message": "Đã xóa person thành công.", "total_faces": face_analyzer.total, }), 200 except Exception as e: logger.error(f"[delete_person] Error: {e}", exc_info=True) return jsonify({"error": { "code": "DELETE_FAILED", "message": f"Không thể xóa person: {e}", "status": 500, }}), 500 # ── Legacy endpoints ─────────────────────────────────────────────────────── @bp.put("/person/") def update_person_legacy(person_id: str): """Legacy alias → PUT /api/persons/""" return update_person(person_id)