/security-auditor skills changes done

This commit is contained in:
2026-04-24 12:58:46 +05:30
parent 7bee838bb0
commit 373adc776f
6 changed files with 241 additions and 293 deletions

47
backend/auth.py Normal file
View File

@@ -0,0 +1,47 @@
"""Firebase token verification and ownership helpers."""
import logging
from fastapi import HTTPException, Header
from firebase_admin import auth as firebase_auth
import firebase_admin
from bson import ObjectId
from bson.errors import InvalidId
log = logging.getLogger(__name__)
async def get_current_user(authorization: str = Header(..., alias="Authorization")) -> dict:
"""FastAPI dependency: verifies Firebase ID token and returns decoded payload."""
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization header")
token = authorization[len("Bearer "):]
if not firebase_admin._apps:
raise HTTPException(status_code=503, detail="Authentication service unavailable")
try:
return firebase_auth.verify_id_token(token)
except firebase_auth.ExpiredIdTokenError:
raise HTTPException(status_code=401, detail="Token expired")
except Exception:
raise HTTPException(status_code=401, detail="Invalid token")
def verify_user_access(user_id: str, db, token: dict) -> dict:
"""
Fetch user by ObjectId and confirm the token owner matches.
Returns the user document. Raises 400/404/403 on failure.
"""
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
if user.get("email") != token.get("email"):
raise HTTPException(status_code=403, detail="Access denied")
return user

View File

@@ -52,7 +52,7 @@ app.add_middleware(
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
allow_headers=["Authorization", "Content-Type"],
)
# Include routers

View File

@@ -1,13 +1,13 @@
"""Journal entry routes"""
from fastapi import APIRouter, HTTPException, Query
import logging
from fastapi import APIRouter, HTTPException, Query, Depends
from db import get_database
from models import JournalEntryCreate, JournalEntryUpdate, JournalEntry, EntriesListResponse, PaginationMeta
from models import JournalEntryCreate, JournalEntryUpdate
from datetime import datetime, timedelta
from typing import List, Optional
from bson import ObjectId
from bson.errors import InvalidId
from auth import get_current_user, verify_user_access
from utils import format_ist_timestamp
log = logging.getLogger(__name__)
router = APIRouter()
@@ -16,21 +16,20 @@ def _format_entry(entry: dict) -> dict:
return {
"id": str(entry["_id"]),
"userId": str(entry["userId"]),
"title": entry.get("title"), # None if encrypted
"content": entry.get("content"), # None if encrypted
"title": entry.get("title"),
"content": entry.get("content"),
"mood": entry.get("mood"),
"tags": entry.get("tags", []),
"isPublic": entry.get("isPublic", False),
"entryDate": entry.get("entryDate", entry.get("createdAt")).isoformat() if entry.get("entryDate") or entry.get("createdAt") else None,
"createdAt": entry["createdAt"].isoformat(),
"updatedAt": entry["updatedAt"].isoformat(),
# Full encryption metadata including ciphertext and nonce
"encryption": entry.get("encryption")
}
@router.post("/{user_id}", response_model=dict)
async def create_entry(user_id: str, entry_data: JournalEntryCreate):
async def create_entry(user_id: str, entry_data: JournalEntryCreate, token: dict = Depends(get_current_user)):
"""
Create a new journal entry.
@@ -38,33 +37,18 @@ async def create_entry(user_id: str, entry_data: JournalEntryCreate):
- Send encryption metadata with ciphertext and nonce
- Omit title and content (they're encrypted in ciphertext)
For unencrypted entries (deprecated):
- Send title and content directly
entryDate: The logical journal date for this entry (defaults to today UTC).
createdAt: Database write timestamp.
Server stores only: encrypted ciphertext, nonce, and metadata.
Server never sees plaintext.
"""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
try:
# Verify user exists
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
now = datetime.utcnow()
entry_date = entry_data.entryDate or now.replace(
hour=0, minute=0, second=0, microsecond=0)
entry_date = entry_data.entryDate or now.replace(hour=0, minute=0, second=0, microsecond=0)
# Validate encryption metadata if present
if entry_data.encryption:
if not entry_data.encryption.ciphertext or not entry_data.encryption.nonce:
raise HTTPException(
@@ -74,12 +58,12 @@ async def create_entry(user_id: str, entry_data: JournalEntryCreate):
entry_doc = {
"userId": user_oid,
"title": entry_data.title, # None if encrypted
"content": entry_data.content, # None if encrypted
"title": entry_data.title,
"content": entry_data.content,
"mood": entry_data.mood,
"tags": entry_data.tags or [],
"isPublic": entry_data.isPublic or False,
"entryDate": entry_date, # Logical journal date
"entryDate": entry_date,
"createdAt": now,
"updatedAt": now,
"encryption": entry_data.encryption.model_dump() if entry_data.encryption else None
@@ -94,48 +78,29 @@ async def create_entry(user_id: str, entry_data: JournalEntryCreate):
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to create entry: {str(e)}")
except Exception:
log.exception("Failed to create entry")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}")
async def get_user_entries(
user_id: str,
limit: int = Query(50, ge=1, le=100),
skip: int = Query(0, ge=0)
skip: int = Query(0, ge=0),
token: dict = Depends(get_current_user)
):
"""
Get paginated entries for a user (most recent first).
Supports pagination via skip and limit.
"""
"""Get paginated entries for a user (most recent first)."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
# Verify user exists
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Get entries
entries = list(
db.entries.find(
{"userId": user_oid}
).sort("createdAt", -1).skip(skip).limit(limit)
db.entries.find({"userId": user_oid}).sort("createdAt", -1).skip(skip).limit(limit)
)
# Format entries
formatted_entries = [_format_entry(entry) for entry in entries]
# Get total count
total = db.entries.count_documents({"userId": user_oid})
has_more = (skip + limit) < total
return {
"entries": formatted_entries,
@@ -143,101 +108,95 @@ async def get_user_entries(
"total": total,
"limit": limit,
"skip": skip,
"hasMore": has_more
"hasMore": (skip + limit) < total
}
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch entries: {str(e)}")
except Exception:
log.exception("Failed to fetch entries")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}/{entry_id}")
async def get_entry(user_id: str, entry_id: str):
async def get_entry(user_id: str, entry_id: str, token: dict = Depends(get_current_user)):
"""Get a specific entry by ID."""
from bson import ObjectId
from bson.errors import InvalidId
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid ID format")
try:
entry = db.entries.find_one({
"_id": entry_oid,
"userId": user_oid
})
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid entry ID format")
entry = db.entries.find_one({"_id": entry_oid, "userId": user_oid})
if not entry:
raise HTTPException(status_code=404, detail="Entry not found")
return _format_entry(entry)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch entry: {str(e)}")
except Exception:
log.exception("Failed to fetch entry")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{user_id}/{entry_id}")
async def update_entry(user_id: str, entry_id: str, entry_data: JournalEntryUpdate):
async def update_entry(user_id: str, entry_id: str, entry_data: JournalEntryUpdate, token: dict = Depends(get_current_user)):
"""Update a journal entry."""
from bson import ObjectId
from bson.errors import InvalidId
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid entry ID format")
try:
update_data = entry_data.model_dump(exclude_unset=True)
update_data["updatedAt"] = datetime.utcnow()
# If entryDate provided in update data, ensure it's a datetime
if "entryDate" in update_data and isinstance(update_data["entryDate"], str):
update_data["entryDate"] = datetime.fromisoformat(
update_data["entryDate"].replace("Z", "+00:00"))
result = db.entries.update_one(
{
"_id": entry_oid,
"userId": user_oid
},
{"_id": entry_oid, "userId": user_oid},
{"$set": update_data}
)
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="Entry not found")
# Fetch and return updated entry
entry = db.entries.find_one({"_id": entry_oid})
return _format_entry(entry)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to update entry: {str(e)}")
except Exception:
log.exception("Failed to update entry")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/{user_id}/{entry_id}")
async def delete_entry(user_id: str, entry_id: str):
async def delete_entry(user_id: str, entry_id: str, token: dict = Depends(get_current_user)):
"""Delete a journal entry."""
from bson import ObjectId
from bson.errors import InvalidId
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
entry_oid = ObjectId(entry_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid entry ID format")
try:
result = db.entries.delete_one({
"_id": entry_oid,
"userId": user_oid
})
result = db.entries.delete_one({"_id": entry_oid, "userId": user_oid})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="Entry not found")
@@ -245,108 +204,83 @@ async def delete_entry(user_id: str, entry_id: str):
return {"message": "Entry deleted successfully"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to delete entry: {str(e)}")
except Exception:
log.exception("Failed to delete entry")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}/by-date/{date_str}")
async def get_entries_by_date(user_id: str, date_str: str):
"""
Get entries for a specific date (format: YYYY-MM-DD).
Matches entries by entryDate field.
"""
async def get_entries_by_date(user_id: str, date_str: str, token: dict = Depends(get_current_user)):
"""Get entries for a specific date (format: YYYY-MM-DD)."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
target_date = datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
try:
# Parse date
target_date = datetime.strptime(date_str, "%Y-%m-%d")
next_date = target_date + timedelta(days=1)
entries = list(
db.entries.find({
"userId": user_oid,
"entryDate": {
"$gte": target_date,
"$lt": next_date
}
"entryDate": {"$gte": target_date, "$lt": next_date}
}).sort("createdAt", -1)
)
formatted_entries = [_format_entry(entry) for entry in entries]
return {
"entries": formatted_entries,
"entries": [_format_entry(e) for e in entries],
"date": date_str,
"count": len(formatted_entries)
"count": len(entries)
}
except ValueError:
raise HTTPException(
status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch entries: {str(e)}")
except Exception:
log.exception("Failed to fetch entries by date")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}/by-month/{year}/{month}")
async def get_entries_by_month(user_id: str, year: int, month: int, limit: int = Query(100, ge=1, le=500)):
"""
Get entries for a specific month (for calendar view).
Query format: GET /api/entries/{user_id}/by-month/{year}/{month}?limit=100
"""
async def get_entries_by_month(
user_id: str,
year: int,
month: int,
limit: int = Query(100, ge=1, le=500),
token: dict = Depends(get_current_user)
):
"""Get entries for a specific month (for calendar view)."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
if not (1 <= month <= 12):
raise HTTPException(
status_code=400, detail="Month must be between 1 and 12")
raise HTTPException(status_code=400, detail="Month must be between 1 and 12")
# Calculate date range
start_date = datetime(year, month, 1)
if month == 12:
end_date = datetime(year + 1, 1, 1)
else:
end_date = datetime(year, month + 1, 1)
end_date = datetime(year + 1, 1, 1) if month == 12 else datetime(year, month + 1, 1)
entries = list(
db.entries.find({
"userId": user_oid,
"entryDate": {
"$gte": start_date,
"$lt": end_date
}
"entryDate": {"$gte": start_date, "$lt": end_date}
}).sort("entryDate", -1).limit(limit)
)
formatted_entries = [_format_entry(entry) for entry in entries]
return {
"entries": formatted_entries,
"entries": [_format_entry(e) for e in entries],
"year": year,
"month": month,
"count": len(formatted_entries)
"count": len(entries)
}
except ValueError:
raise HTTPException(status_code=400, detail="Invalid year or month")
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch entries: {str(e)}")
except Exception:
log.exception("Failed to fetch entries by month")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("/convert-timestamp/utc-to-ist")
@@ -355,18 +289,14 @@ async def convert_utc_to_ist(data: dict):
try:
utc_timestamp = data.get("timestamp")
if not utc_timestamp:
raise HTTPException(
status_code=400, detail="Missing 'timestamp' field")
raise HTTPException(status_code=400, detail="Missing 'timestamp' field")
ist_timestamp = format_ist_timestamp(utc_timestamp)
return {
"utc": utc_timestamp,
"ist": ist_timestamp
}
return {"utc": utc_timestamp, "ist": ist_timestamp}
except HTTPException:
raise
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Conversion failed: {str(e)}")
except Exception:
log.exception("Timestamp conversion failed")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@@ -1,12 +1,13 @@
"""Notification routes — FCM token registration and reminder settings."""
from fastapi import APIRouter, HTTPException
import logging
from fastapi import APIRouter, HTTPException, Depends
from db import get_database
from pydantic import BaseModel
from typing import Optional
from bson import ObjectId
from bson.errors import InvalidId
from datetime import datetime
from auth import get_current_user, verify_user_access
log = logging.getLogger(__name__)
router = APIRouter()
@@ -22,57 +23,52 @@ class ReminderSettingsRequest(BaseModel):
@router.post("/fcm-token", response_model=dict)
async def register_fcm_token(body: FcmTokenRequest):
async def register_fcm_token(body: FcmTokenRequest, token: dict = Depends(get_current_user)):
"""
Register (or refresh) an FCM device token for a user.
Stores unique tokens per user — duplicate tokens are ignored.
"""
db = get_database()
try:
user_oid = ObjectId(body.userId)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID")
user = verify_user_access(body.userId, db, token)
user_oid = user["_id"]
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
# Add token to set (avoid duplicates)
db.users.update_one(
{"_id": user_oid},
{
"$addToSet": {"fcmTokens": body.fcmToken},
"$set": {"updatedAt": datetime.utcnow()},
}
)
return {"message": "FCM token registered"}
db.users.update_one(
{"_id": user_oid},
{
"$addToSet": {"fcmTokens": body.fcmToken},
"$set": {"updatedAt": datetime.utcnow()},
}
)
return {"message": "FCM token registered"}
except HTTPException:
raise
except Exception:
log.exception("Failed to register FCM token")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/reminder/{user_id}", response_model=dict)
async def update_reminder(user_id: str, settings: ReminderSettingsRequest):
"""
Save or update daily reminder settings for a user.
"""
async def update_reminder(user_id: str, settings: ReminderSettingsRequest, token: dict = Depends(get_current_user)):
"""Save or update daily reminder settings for a user."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
reminder_update: dict = {"reminder.enabled": settings.enabled}
if settings.time is not None:
reminder_update["reminder.time"] = settings.time
if settings.timezone is not None:
reminder_update["reminder.timezone"] = settings.timezone
reminder_update: dict = {"reminder.enabled": settings.enabled}
if settings.time is not None:
reminder_update["reminder.time"] = settings.time
if settings.timezone is not None:
reminder_update["reminder.timezone"] = settings.timezone
db.users.update_one(
{"_id": user_oid},
{"$set": {**reminder_update, "updatedAt": datetime.utcnow()}}
)
return {"message": "Reminder settings updated"}
db.users.update_one(
{"_id": user_oid},
{"$set": {**reminder_update, "updatedAt": datetime.utcnow()}}
)
return {"message": "Reminder settings updated"}
except HTTPException:
raise
except Exception:
log.exception("Failed to update reminder settings")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@@ -1,17 +1,17 @@
"""User management routes"""
from fastapi import APIRouter, HTTPException
import logging
from fastapi import APIRouter, HTTPException, Depends
from db import get_database
from models import UserCreate, UserUpdate, User
from models import UserCreate, UserUpdate
from datetime import datetime
from typing import Optional
from bson import ObjectId
from bson.errors import InvalidId
from auth import get_current_user, verify_user_access
log = logging.getLogger(__name__)
router = APIRouter()
@router.post("/register", response_model=dict)
async def register_user(user_data: UserCreate):
async def register_user(user_data: UserCreate, token: dict = Depends(get_current_user)):
"""
Register or get user (idempotent).
@@ -19,10 +19,11 @@ async def register_user(user_data: UserCreate):
If user already exists, returns existing user.
Called after Firebase Google Auth on frontend.
"""
db = get_database()
if user_data.email != token.get("email"):
raise HTTPException(status_code=403, detail="Access denied")
db = get_database()
try:
# Upsert: Update if exists, insert if not
result = db.users.update_one(
{"email": user_data.email},
{
@@ -40,11 +41,9 @@ async def register_user(user_data: UserCreate):
upsert=True
)
# Fetch the user (either newly created or existing)
user = db.users.find_one({"email": user_data.email})
if not user:
raise HTTPException(
status_code=500, detail="Failed to retrieve user after upsert")
raise HTTPException(status_code=500, detail="Failed to retrieve user after upsert")
return {
"id": str(user["_id"]),
@@ -62,15 +61,17 @@ async def register_user(user_data: UserCreate):
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Registration failed: {str(e)}")
log.exception("Registration failed")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/by-email/{email}", response_model=dict)
async def get_user_by_email(email: str):
async def get_user_by_email(email: str, token: dict = Depends(get_current_user)):
"""Get user profile by email (called after Firebase Auth)."""
db = get_database()
if email != token.get("email"):
raise HTTPException(status_code=403, detail="Access denied")
db = get_database()
try:
user = db.users.find_one({"email": email})
if not user:
@@ -91,26 +92,17 @@ async def get_user_by_email(email: str):
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch user: {str(e)}")
except Exception:
log.exception("Failed to fetch user by email")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}", response_model=dict)
async def get_user_by_id(user_id: str):
async def get_user_by_id(user_id: str, token: dict = Depends(get_current_user)):
"""Get user profile by ID."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
try:
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
user = verify_user_access(user_id, db, token)
return {
"id": str(user["_id"]),
"email": user["email"],
@@ -124,72 +116,54 @@ async def get_user_by_id(user_id: str):
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch user: {str(e)}")
except Exception:
log.exception("Failed to fetch user by ID")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{user_id}", response_model=dict)
async def update_user(user_id: str, user_data: UserUpdate):
async def update_user(user_id: str, user_data: UserUpdate, token: dict = Depends(get_current_user)):
"""Update user profile."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
# Prepare update data (exclude None values)
update_data = user_data.model_dump(exclude_unset=True)
update_data["updatedAt"] = datetime.utcnow()
result = db.users.update_one(
{"_id": user_oid},
{"$set": update_data}
)
db.users.update_one({"_id": user_oid}, {"$set": update_data})
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="User not found")
# Fetch and return updated user
user = db.users.find_one({"_id": user_oid})
updated = db.users.find_one({"_id": user_oid})
return {
"id": str(user["_id"]),
"email": user["email"],
"displayName": user.get("displayName"),
"photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"),
"backgroundImage": user.get("backgroundImage"),
"backgroundImages": user.get("backgroundImages", []),
"tutorial": user.get("tutorial"),
"createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat(),
"id": str(updated["_id"]),
"email": updated["email"],
"displayName": updated.get("displayName"),
"photoURL": updated.get("photoURL"),
"theme": updated.get("theme", "light"),
"backgroundImage": updated.get("backgroundImage"),
"backgroundImages": updated.get("backgroundImages", []),
"tutorial": updated.get("tutorial"),
"createdAt": updated["createdAt"].isoformat(),
"updatedAt": updated["updatedAt"].isoformat(),
"message": "User updated successfully"
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Update failed: {str(e)}")
except Exception:
log.exception("User update failed")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/{user_id}")
async def delete_user(user_id: str):
async def delete_user(user_id: str, token: dict = Depends(get_current_user)):
"""Delete user account and all associated data."""
db = get_database()
try:
user_oid = ObjectId(user_id)
except InvalidId:
raise HTTPException(status_code=400, detail="Invalid user ID format")
user = verify_user_access(user_id, db, token)
user_oid = user["_id"]
try:
# Delete user
user_result = db.users.delete_one({"_id": user_oid})
if user_result.deleted_count == 0:
raise HTTPException(status_code=404, detail="User not found")
# Delete all user's entries
entry_result = db.entries.delete_many({"userId": user_oid})
return {
@@ -199,6 +173,6 @@ async def delete_user(user_id: str):
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Deletion failed: {str(e)}")
except Exception:
log.exception("User deletion failed")
raise HTTPException(status_code=500, detail="Internal server error")