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):