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

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)}")