update db str

This commit is contained in:
2026-03-05 12:43:44 +05:30
parent eabf295f2e
commit 6e184dc590
27 changed files with 2780 additions and 146 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,105 +1,181 @@
"""Journal entry routes"""
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, HTTPException, Query
from db import get_database
from models import JournalEntryCreate, JournalEntryUpdate
from datetime import datetime
from typing import List
from models import JournalEntryCreate, JournalEntryUpdate, JournalEntry, EntriesListResponse, PaginationMeta
from datetime import datetime, timedelta
from typing import List, Optional
from bson import ObjectId
from utils import format_ist_timestamp
router = APIRouter()
def _format_entry(entry: dict) -> dict:
"""Helper to format entry document for API response."""
return {
"id": str(entry["_id"]),
"userId": str(entry["userId"]),
"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(),
"encryption": entry.get("encryption", {
"encrypted": False,
"iv": None,
"algorithm": None
})
}
@router.post("/{user_id}", response_model=dict)
async def create_entry(user_id: str, entry_data: JournalEntryCreate):
"""Create a new journal entry"""
"""
Create a new journal entry.
entryDate: The logical journal date for this entry (defaults to today UTC).
createdAt: Database write timestamp.
"""
db = get_database()
try:
user_oid = ObjectId(user_id)
# Verify user exists
user = db.users.find_one({"_id": user_oid})
if not user:
raise HTTPException(status_code=404, detail="User not found")
now = datetime.utcnow()
entry_date = entry_data.entryDate or now.replace(hour=0, minute=0, second=0, microsecond=0)
entry_doc = {
"userId": user_id,
"userId": user_oid,
"title": entry_data.title,
"content": entry_data.content,
"mood": entry_data.mood,
"tags": entry_data.tags or [],
"isPublic": entry_data.isPublic,
"createdAt": datetime.utcnow(),
"updatedAt": datetime.utcnow()
"isPublic": entry_data.isPublic or False,
"entryDate": entry_date, # Logical journal date
"createdAt": now,
"updatedAt": now,
"encryption": entry_data.encryption.model_dump() if entry_data.encryption else {
"encrypted": False,
"iv": None,
"algorithm": None
}
}
result = db.entries.insert_one(entry_doc)
entry_doc["id"] = str(result.inserted_id)
return {
"id": entry_doc["id"],
"id": str(result.inserted_id),
"userId": user_id,
"message": "Entry created successfully"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Failed to create entry: {str(e)}")
@router.get("/{user_id}")
async def get_user_entries(user_id: str, limit: int = 50, skip: int = 0):
"""Get all entries for a user (paginated, most recent first)"""
async def get_user_entries(
user_id: str,
limit: int = Query(50, ge=1, le=100),
skip: int = Query(0, ge=0)
):
"""
Get paginated entries for a user (most recent first).
Supports pagination via skip and limit.
"""
db = get_database()
try:
user_oid = ObjectId(user_id)
# 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_id}
{"userId": user_oid}
).sort("createdAt", -1).skip(skip).limit(limit)
)
for entry in entries:
entry["id"] = str(entry["_id"])
del entry["_id"]
total = db.entries.count_documents({"userId": user_id})
# 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": entries,
"total": total,
"skip": skip,
"limit": limit
"entries": formatted_entries,
"pagination": {
"total": total,
"limit": limit,
"skip": skip,
"hasMore": has_more
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Failed to fetch entries: {str(e)}")
@router.get("/{user_id}/{entry_id}")
async def get_entry(user_id: str, entry_id: str):
"""Get a specific entry"""
"""Get a specific entry by ID."""
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
entry = db.entries.find_one({
"_id": ObjectId(entry_id),
"userId": user_id
"_id": entry_oid,
"userId": user_oid
})
if not entry:
raise HTTPException(status_code=404, detail="Entry not found")
entry["id"] = str(entry["_id"])
del entry["_id"]
return entry
return _format_entry(entry)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid ID format")
raise HTTPException(status_code=500, detail=f"Failed to fetch entry: {str(e)}")
@router.put("/{user_id}/{entry_id}")
async def update_entry(user_id: str, entry_id: str, entry_data: JournalEntryUpdate):
"""Update a journal entry"""
"""Update a journal entry."""
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
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": ObjectId(entry_id),
"userId": user_id
"_id": entry_oid,
"userId": user_oid
},
{"$set": update_data}
)
@@ -107,20 +183,27 @@ async def update_entry(user_id: str, entry_id: str, entry_data: JournalEntryUpda
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="Entry not found")
return {"message": "Entry updated successfully"}
# Fetch and return updated entry
entry = db.entries.find_one({"_id": entry_oid})
return _format_entry(entry)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid ID format")
raise HTTPException(status_code=500, detail=f"Failed to update entry: {str(e)}")
@router.delete("/{user_id}/{entry_id}")
async def delete_entry(user_id: str, entry_id: str):
"""Delete a journal entry"""
"""Delete a journal entry."""
db = get_database()
try:
user_oid = ObjectId(user_id)
entry_oid = ObjectId(entry_id)
result = db.entries.delete_one({
"_id": ObjectId(entry_id),
"userId": user_id
"_id": entry_oid,
"userId": user_oid
})
if result.deleted_count == 0:
@@ -128,38 +211,116 @@ async def delete_entry(user_id: str, entry_id: str):
return {"message": "Entry deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid ID format")
raise HTTPException(status_code=500, detail=f"Failed to delete entry: {str(e)}")
@router.get("/{user_id}/date/{date_str}")
@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)"""
"""
Get entries for a specific date (format: YYYY-MM-DD).
Matches entries by entryDate field.
"""
db = get_database()
try:
from datetime import datetime as dt
user_oid = ObjectId(user_id)
# Parse date
target_date = dt.strptime(date_str, "%Y-%m-%d")
next_date = dt.fromtimestamp(target_date.timestamp() + 86400)
target_date = datetime.strptime(date_str, "%Y-%m-%d")
next_date = target_date + timedelta(days=1)
entries = list(
db.entries.find({
"userId": user_id,
"createdAt": {
"userId": user_oid,
"entryDate": {
"$gte": target_date,
"$lt": next_date
}
}).sort("createdAt", -1)
)
for entry in entries:
entry["id"] = str(entry["_id"])
del entry["_id"]
formatted_entries = [_format_entry(entry) for entry in entries]
return {"entries": entries, "date": date_str}
return {
"entries": formatted_entries,
"date": date_str,
"count": len(formatted_entries)
}
except ValueError:
raise HTTPException(
status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Failed to fetch entries: {str(e)}")
@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
"""
db = get_database()
try:
user_oid = ObjectId(user_id)
if not (1 <= month <= 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)
entries = list(
db.entries.find({
"userId": user_oid,
"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,
"year": year,
"month": month,
"count": len(formatted_entries)
}
except ValueError:
raise HTTPException(status_code=400, detail="Invalid year or month")
except Exception as e:
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Failed to fetch entries: {str(e)}")
@router.post("/convert-timestamp/utc-to-ist")
async def convert_utc_to_ist(data: dict):
"""Convert UTC ISO timestamp to IST (Indian Standard Time)."""
try:
utc_timestamp = data.get("timestamp")
if not utc_timestamp:
raise HTTPException(
status_code=400, detail="Missing 'timestamp' field")
ist_timestamp = format_ist_timestamp(utc_timestamp)
return {
"utc": utc_timestamp,
"ist": ist_timestamp
}
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)}")

View File

@@ -1,10 +1,11 @@
"""User management routes"""
from fastapi import APIRouter, HTTPException, Header
from pymongo.errors import DuplicateKeyError
from fastapi import APIRouter, HTTPException
from pymongo.errors import DuplicateKeyError, WriteError
from db import get_database
from models import UserCreate, UserUpdate, User
from datetime import datetime
from typing import Optional, List
from typing import Optional
from bson import ObjectId
router = APIRouter()
@@ -12,56 +13,107 @@ router = APIRouter()
@router.post("/register", response_model=dict)
async def register_user(user_data: UserCreate):
"""
Register a new user (called after Firebase Google Auth)
Stores user profile in MongoDB
Register or get user (idempotent).
Uses upsert pattern to ensure one user per email.
If user already exists, returns existing user.
Called after Firebase Google Auth on frontend.
"""
db = get_database()
try:
user_doc = {
"email": user_data.email,
"displayName": user_data.displayName or user_data.email.split("@")[0],
"photoURL": user_data.photoURL,
"createdAt": datetime.utcnow(),
"updatedAt": datetime.utcnow(),
"theme": "light"
}
# Upsert: Update if exists, insert if not
result = db.users.update_one(
{"email": user_data.email},
{
"$setOnInsert": {
"email": user_data.email,
"displayName": user_data.displayName or user_data.email.split("@")[0],
"photoURL": user_data.photoURL,
"theme": "light",
"createdAt": datetime.utcnow()
},
"$set": {
"updatedAt": datetime.utcnow()
}
},
upsert=True
)
result = db.users.insert_one(user_doc)
user_doc["id"] = str(result.inserted_id)
# 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")
return {
"id": user_doc["id"],
"email": user_doc["email"],
"displayName": user_doc["displayName"],
"message": "User registered successfully"
"id": str(user["_id"]),
"email": user["email"],
"displayName": user["displayName"],
"photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"),
"createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat(),
"message": "User registered successfully" if result.upserted_id else "User already exists"
}
except DuplicateKeyError:
raise HTTPException(status_code=400, detail="User already exists")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}")
@router.get("/by-email/{email}", response_model=dict)
async def get_user_by_email(email: str):
"""Get user profile by email (called after Firebase Auth)"""
"""Get user profile by email (called after Firebase Auth)."""
db = get_database()
user = db.users.find_one({"email": email})
if not user:
raise HTTPException(status_code=404, detail="User not found")
user["id"] = str(user["_id"])
return user
@router.put("/update/{user_id}", response_model=dict)
async def update_user(user_id: str, user_data: UserUpdate):
"""Update user profile"""
db = get_database()
from bson import ObjectId
try:
user = db.users.find_one({"email": email})
if not user:
raise HTTPException(status_code=404, detail="User not found")
return {
"id": str(user["_id"]),
"email": user["email"],
"displayName": user.get("displayName"),
"photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"),
"createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat()
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch user: {str(e)}")
@router.get("/{user_id}", response_model=dict)
async def get_user_by_id(user_id: str):
"""Get user profile by ID."""
db = get_database()
try:
user = db.users.find_one({"_id": ObjectId(user_id)})
if not user:
raise HTTPException(status_code=404, detail="User not found")
return {
"id": str(user["_id"]),
"email": user["email"],
"displayName": user.get("displayName"),
"photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"),
"createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat()
}
except Exception as e:
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Failed to fetch user: {str(e)}")
@router.put("/{user_id}", response_model=dict)
async def update_user(user_id: str, user_data: UserUpdate):
"""Update user profile."""
db = get_database()
try:
# Prepare update data (exclude None values)
update_data = user_data.model_dump(exclude_unset=True)
update_data["updatedAt"] = datetime.utcnow()
@@ -73,20 +125,47 @@ async def update_user(user_id: str, user_data: UserUpdate):
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="User not found")
return {"message": "User updated successfully"}
# Fetch and return updated user
user = db.users.find_one({"_id": ObjectId(user_id)})
return {
"id": str(user["_id"]),
"email": user["email"],
"displayName": user.get("displayName"),
"photoURL": user.get("photoURL"),
"theme": user.get("theme", "light"),
"createdAt": user["createdAt"].isoformat(),
"updatedAt": user["updatedAt"].isoformat(),
"message": "User updated successfully"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Update failed: {str(e)}")
@router.delete("/{user_id}")
async def delete_user(user_id: str):
"""Delete user account and all associated data"""
"""Delete user account and all associated data."""
db = get_database()
from bson import ObjectId
try:
# Delete user
db.users.delete_one({"_id": ObjectId(user_id)})
user_result = db.users.delete_one({"_id": ObjectId(user_id)})
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": ObjectId(user_id)})
return {
"message": "User deleted successfully",
"user_deleted": user_result.deleted_count,
"entries_deleted": entry_result.deleted_count
}
except Exception as e:
if "invalid ObjectId" in str(e).lower():
raise HTTPException(status_code=400, detail="Invalid user ID format")
raise HTTPException(status_code=500, detail=f"Deletion failed: {str(e)}")
# Delete all entries by user
db.entries.delete_many({"userId": user_id})