Spaces:
Runtime error
Runtime error
| """ | |
| api/database.py — Endpoints quản lý database face (danh sách & xóa theo tên) | |
| GET /api/faces — Danh sách khuôn mặt (RESTful) | |
| GET /api/db — Legacy alias | |
| DELETE /api/db/<filename> — Legacy: xóa theo tên file (backward compat) | |
| GET /api/unknown-faces — Danh sách khuôn mặt chưa nhận diện | |
| """ | |
| import logging | |
| import os | |
| from flask import Blueprint, jsonify | |
| 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.database") | |
| bp = Blueprint("database", __name__, url_prefix="/api") | |
| def list_faces(): | |
| """ | |
| GET /api/faces | |
| Trả về danh sách khuôn mặt đã đăng ký từ Supabase + MongoDB. | |
| """ | |
| try: | |
| persons = pg_storage.list_persons_with_faces() | |
| faces = [ | |
| { | |
| "filename": f"{p['full_name']}.jpg", | |
| "name": p["full_name"], | |
| "person_id": p["person_id"], | |
| "status": p.get("status", "active"), | |
| "total_appearances": p.get("total_appearances", 0), | |
| "last_seen_at": p.get("last_seen_at"), | |
| "url": f"/api/face-crops/{p['face_crop_mongo_id']}" if p.get("face_crop_mongo_id") else None, | |
| } | |
| for p in persons | |
| ] | |
| return jsonify({ | |
| "total": len(faces), | |
| "faces": faces, | |
| }), 200 | |
| except Exception as e: | |
| logger.error(f"[list_faces] Error: {e}", exc_info=True) | |
| return jsonify({"error": { | |
| "code": "FETCH_FAILED", | |
| "message": f"Lỗi khi lấy danh sách khuôn mặt: {e}", | |
| "status": 500, | |
| }}), 500 | |
| def list_faces_legacy(): | |
| """ | |
| Legacy alias GET /api/db (backward compatibility với frontend). | |
| Response format giữ nguyên để frontend không cần sửa. | |
| """ | |
| try: | |
| persons = pg_storage.list_persons_with_faces() | |
| faces = [ | |
| { | |
| "filename": f"{p['full_name']}.jpg", | |
| "name": p["full_name"], | |
| "url": f"/api/face-crops/{p['face_crop_mongo_id']}" if p.get("face_crop_mongo_id") else "", | |
| } | |
| for p in persons | |
| ] | |
| return jsonify({ | |
| "total": len(faces), | |
| "faces": faces, | |
| }), 200 | |
| except Exception as e: | |
| logger.error(f"[list_faces_legacy] Error: {e}", exc_info=True) | |
| return jsonify({"detail": f"Lỗi khi lấy danh sách khuôn mặt: {e}"}), 500 | |
| def delete_face_legacy(filename: str): | |
| """ | |
| Legacy DELETE /api/db/<filename> — Xóa theo tên file (backward compat với frontend). | |
| """ | |
| name = os.path.splitext(filename)[0] | |
| try: | |
| success, mongo_ids = pg_storage.delete_person_by_name(name) | |
| if not success: | |
| return jsonify({"detail": "Không tìm thấy người này trong database."}), 404 | |
| for mid in mongo_ids: | |
| mongo_storage.delete_face_crop(mid) | |
| # Xóa khỏi RAM cache (theo tên) | |
| to_remove = [ | |
| pid for pid, n in zip(face_analyzer.known_person_ids, face_analyzer.known_names) | |
| if n == name | |
| ] | |
| for pid in to_remove: | |
| face_analyzer.remove_from_cache(pid) | |
| return jsonify({ | |
| "success": True, | |
| "message": f"Đã xóa thành công {name} khỏi database.", | |
| "total_faces": face_analyzer.total, | |
| }), 200 | |
| except Exception as e: | |
| logger.error(f"[delete_face_legacy] Error: {e}", exc_info=True) | |
| return jsonify({"detail": f"Lỗi khi xóa khuôn mặt: {e}"}), 500 | |
| def list_unknown_faces(): | |
| """ | |
| GET /api/unknown-faces | |
| Trả về danh sách khuôn mặt chưa nhận diện từ MongoDB. | |
| """ | |
| try: | |
| unknowns = mongo_storage.list_unknown_faces(limit=50) | |
| return jsonify({ | |
| "total": len(unknowns), | |
| "unknown_faces": unknowns, | |
| }), 200 | |
| except Exception as e: | |
| logger.error(f"[list_unknown_faces] Error: {e}", exc_info=True) | |
| return jsonify({"error": { | |
| "code": "FETCH_FAILED", | |
| "message": f"Lỗi khi lấy danh sách unknown faces: {e}", | |
| "status": 500, | |
| }}), 500 | |