diff --git a/frontend/src/api/people.ts b/frontend/src/api/people.ts index 1b6c0e1..31cd658 100644 --- a/frontend/src/api/people.ts +++ b/frontend/src/api/people.ts @@ -28,7 +28,7 @@ export interface PersonCreateRequest { last_name: string middle_name?: string maiden_name?: string - date_of_birth: string + date_of_birth?: string | null } export interface PersonUpdateRequest { diff --git a/frontend/src/pages/Identify.tsx b/frontend/src/pages/Identify.tsx index 6e5a546..39024e0 100644 --- a/frontend/src/pages/Identify.tsx +++ b/frontend/src/pages/Identify.tsx @@ -82,8 +82,10 @@ export default function Identify() { const restorationCompleteRef = useRef(false) const canIdentify = useMemo(() => { - return Boolean((personId && currentFace) || (firstName && lastName && dob && currentFace)) - }, [personId, firstName, lastName, dob, currentFace]) + if (!currentFace) return false + if (personId) return true + return Boolean(firstName.trim() && lastName.trim()) + }, [personId, firstName, lastName, currentFace]) const loadFaces = async (clearState: boolean = false, ignorePhotoIds: boolean = false) => { setLoadingFaces(true) @@ -589,6 +591,11 @@ export default function Identify() { const handleIdentify = async () => { if (!currentFace) return setBusy(true) + const trimmedFirstName = firstName.trim() + const trimmedLastName = lastName.trim() + const trimmedMiddleName = middleName.trim() + const trimmedMaidenName = maidenName.trim() + const trimmedDob = dob.trim() const additional = Object.entries(selectedSimilar) .filter(([, v]) => v) .map(([k]) => Number(k)) @@ -597,11 +604,17 @@ export default function Identify() { if (personId) { payload.person_id = personId } else { - payload.first_name = firstName - payload.last_name = lastName - payload.middle_name = middleName || undefined - payload.maiden_name = maidenName || undefined - payload.date_of_birth = dob + payload.first_name = trimmedFirstName + payload.last_name = trimmedLastName + if (trimmedMiddleName) { + payload.middle_name = trimmedMiddleName + } + if (trimmedMaidenName) { + payload.maiden_name = trimmedMaidenName + } + if (trimmedDob) { + payload.date_of_birth = trimmedDob + } } await facesApi.identify(currentFace.id, payload) // Optimistic: remove identified faces from list @@ -948,7 +961,7 @@ export default function Identify() { disabled={!!personId} />
- + { setDob(e.target.value) setPersonId(undefined) // Clear person selection when typing diff --git a/scripts/drop_all_tables_web.py b/scripts/drop_all_tables_web.py index 26d82d1..8dd2bac 100644 --- a/scripts/drop_all_tables_web.py +++ b/scripts/drop_all_tables_web.py @@ -11,17 +11,39 @@ from sqlalchemy import inspect from src.web.db.session import engine, get_database_url from src.web.db.models import Base +# Ordered list ensures foreign-key dependents drop first +TARGET_TABLES = [ + "photo_favorites", + "phototaglinkage", + "person_encodings", + "faces", + "tags", + "photos", + "people", +] + def drop_all_tables(): """Drop all tables from the database.""" db_url = get_database_url() print(f"Connecting to database: {db_url}") - # Drop all tables - print("\nDropping all tables...") - Base.metadata.drop_all(bind=engine) + inspector = inspect(engine) + existing_tables = set(inspector.get_table_names()) - print("✅ All tables dropped successfully!") + print("\nDropping selected tables...") + for table_name in TARGET_TABLES: + if table_name not in Base.metadata.tables: + print(f" ⚠️ Table '{table_name}' not found in metadata, skipping.") + continue + if table_name not in existing_tables: + print(f" ℹ️ Table '{table_name}' does not exist in database, skipping.") + continue + table = Base.metadata.tables[table_name] + print(f" 🗑️ Dropping '{table_name}'...") + table.drop(bind=engine, checkfirst=True) + + print("✅ Selected tables dropped successfully!") print("\nYou can now recreate tables using:") print(" python scripts/recreate_tables_web.py") diff --git a/src/web/api/faces.py b/src/web/api/faces.py index a5517f7..f68acf3 100644 --- a/src/web/api/faces.py +++ b/src/web/api/faces.py @@ -290,16 +290,20 @@ def identify_face( raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="person_id not found") else: # Validate required fields for creation - if not (request.first_name and request.last_name and request.date_of_birth): + first_name = (request.first_name or "").strip() + last_name = (request.last_name or "").strip() + middle_name = request.middle_name.strip() if request.middle_name else None + maiden_name = request.maiden_name.strip() if request.maiden_name else None + if not (first_name and last_name): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail="first_name, last_name and date_of_birth are required to create a person", + detail="first_name and last_name are required to create a person", ) person = Person( - first_name=request.first_name, - last_name=request.last_name, - middle_name=request.middle_name, - maiden_name=request.maiden_name, + first_name=first_name, + last_name=last_name, + middle_name=middle_name, + maiden_name=maiden_name, date_of_birth=request.date_of_birth, ) db.add(person) diff --git a/src/web/api/pending_identifications.py b/src/web/api/pending_identifications.py index 5c34dd9..810aa10 100644 --- a/src/web/api/pending_identifications.py +++ b/src/web/api/pending_identifications.py @@ -304,41 +304,32 @@ def approve_deny_pending_identifications( # Check if person already exists (by name and DOB) # Match the unique constraint: first_name, last_name, middle_name, maiden_name, date_of_birth - person = None + # Build query with proper None handling + query = main_db.query(Person).filter( + Person.first_name == row.first_name, + Person.last_name == row.last_name, + ) + # Handle optional fields - use IS NULL for None values + if row.middle_name: + query = query.filter(Person.middle_name == row.middle_name) + else: + query = query.filter(Person.middle_name.is_(None)) + + if row.maiden_name: + query = query.filter(Person.maiden_name == row.maiden_name) + else: + query = query.filter(Person.maiden_name.is_(None)) + if row.date_of_birth: - # Build query with proper None handling - query = main_db.query(Person).filter( - Person.first_name == row.first_name, - Person.last_name == row.last_name, - Person.date_of_birth == row.date_of_birth - ) - # Handle optional fields - use IS NULL for None values - if row.middle_name: - query = query.filter(Person.middle_name == row.middle_name) - else: - query = query.filter(Person.middle_name.is_(None)) - - if row.maiden_name: - query = query.filter(Person.maiden_name == row.maiden_name) - else: - query = query.filter(Person.maiden_name.is_(None)) - - person = query.first() + query = query.filter(Person.date_of_birth == row.date_of_birth) + else: + query = query.filter(Person.date_of_birth.is_(None)) + + person = query.first() # Create person if doesn't exist created_person = False if not person: - if not row.date_of_birth: - errors.append(f"Pending identification {decision.id} missing date_of_birth (required for person creation)") - auth_db.execute(text(""" - UPDATE pending_identifications - SET status = 'denied', updated_at = :updated_at - WHERE id = :id - """), {"id": decision.id, "updated_at": datetime.utcnow()}) - auth_db.commit() - denied_count += 1 - continue - person = Person( first_name=row.first_name, last_name=row.last_name, diff --git a/src/web/api/people.py b/src/web/api/people.py index e282b59..7bca72c 100644 --- a/src/web/api/people.py +++ b/src/web/api/people.py @@ -95,11 +95,15 @@ def list_people_with_faces( @router.post("", response_model=PersonResponse, status_code=status.HTTP_201_CREATED) def create_person(request: PersonCreateRequest, db: Session = Depends(get_db)) -> PersonResponse: """Create a new person.""" + first_name = request.first_name.strip() + last_name = request.last_name.strip() + middle_name = request.middle_name.strip() if request.middle_name else None + maiden_name = request.maiden_name.strip() if request.maiden_name else None person = Person( - first_name=request.first_name, - last_name=request.last_name, - middle_name=request.middle_name, - maiden_name=request.maiden_name, + first_name=first_name, + last_name=last_name, + middle_name=middle_name, + maiden_name=maiden_name, date_of_birth=request.date_of_birth, ) db.add(person) diff --git a/src/web/schemas/people.py b/src/web/schemas/people.py index 8de4bd9..2087ff1 100644 --- a/src/web/schemas/people.py +++ b/src/web/schemas/people.py @@ -30,7 +30,7 @@ class PersonCreateRequest(BaseModel): last_name: str = Field(..., min_length=1) middle_name: Optional[str] = None maiden_name: Optional[str] = None - date_of_birth: date + date_of_birth: Optional[date] = None class PeopleListResponse(BaseModel):