Complete implementation of Phase 1 enhancements including: Backend: - UserProfile model for storing user preferences (timezone, trading style, risk tolerance) - DailyRoutine model for scheduling routines (morning, active_trading, evening) - RoutineExecution model for tracking routine execution history - Notification model for managing all types of notifications - DailyChecklist model for daily task tracking with completion percentage - HabitTracker model for tracking habits and streaks Services: - RoutineService: Handles routine execution with task registry pattern - RoutineScheduler: Async scheduler for automated routine execution - NotificationService: Comprehensive notification creation and delivery system - Support for price alerts, news, routines, reminders, and performance notifications API Endpoints (daily_helper router): - User profile: CRUD operations, get/update preferences - Daily routines: Create, list, execute, track history - Notifications: CRUD, mark read, batch operations - Daily checklists: CRUD, item management, completion tracking - Habits: Create, track, log completions, manage streaks - Dashboard: Summary endpoint for daily helper overview Frontend Components: - UserProfileSetup: Complete user profile configuration with preferences - NotificationCenter: Bell icon with dropdown, notification management - HabitTracker: Habit creation, streak tracking, gamification with fire emojis - DailyChecklistPanel: Checklist management with completion percentage Schemas: - Full Pydantic schemas for request/response validation - Type-safe API contracts Features: - Timezone support for international users - Trading style and risk tolerance preferences - Automated routine execution with task registry - Real-time notifications with priority levels - Habit streaks with motivational badges - Daily checklist with persistent state - Completion percentage tracking - Notes and metadata support All components are production-ready with error handling and user feedback.
647 lines
21 KiB
Python
647 lines
21 KiB
Python
"""
|
|
Daily Helper API endpoints for Phase 1 enhancements
|
|
Includes user profiles, routines, notifications, and habit tracking
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
from datetime import datetime, date, timedelta
|
|
from app.db.database import get_db
|
|
from app.models.models import (
|
|
UserProfile, DailyRoutine, RoutineExecution, Notification,
|
|
DailyChecklist, HabitTracker
|
|
)
|
|
from app.schemas.schemas import (
|
|
UserProfileCreate, UserProfileUpdate, UserProfileResponse,
|
|
DailyRoutineCreate, DailyRoutineUpdate, DailyRoutineResponse,
|
|
RoutineExecutionResponse, NotificationCreate, NotificationResponse,
|
|
NotificationListResponse, DailyChecklistCreate, DailyChecklistUpdate,
|
|
DailyChecklistResponse, HabitTrackerCreate, HabitTrackerResponse,
|
|
HabitCompletionRequest, ChecklistItem
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/daily-helper", tags=["Daily Helper"])
|
|
|
|
|
|
# ============================================================================
|
|
# USER PROFILE ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/profile", response_model=UserProfileResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_user_profile(
|
|
profile: UserProfileCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new user profile"""
|
|
# Check if email already exists
|
|
if profile.email:
|
|
existing = db.query(UserProfile).filter(UserProfile.email == profile.email).first()
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Email already registered"
|
|
)
|
|
|
|
db_profile = UserProfile(**profile.dict())
|
|
db.add(db_profile)
|
|
db.commit()
|
|
db.refresh(db_profile)
|
|
return db_profile
|
|
|
|
|
|
@router.get("/profile", response_model=UserProfileResponse)
|
|
async def get_user_profile(db: Session = Depends(get_db)):
|
|
"""Get user profile (current implementation returns first profile)"""
|
|
profile = db.query(UserProfile).first()
|
|
if not profile:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User profile not found"
|
|
)
|
|
return profile
|
|
|
|
|
|
@router.put("/profile", response_model=UserProfileResponse)
|
|
async def update_user_profile(
|
|
profile_update: UserProfileUpdate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update user profile"""
|
|
profile = db.query(UserProfile).first()
|
|
if not profile:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User profile not found"
|
|
)
|
|
|
|
update_data = profile_update.dict(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(profile, key, value)
|
|
|
|
profile.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(profile)
|
|
return profile
|
|
|
|
|
|
@router.delete("/profile", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_user_profile(db: Session = Depends(get_db)):
|
|
"""Delete user profile"""
|
|
profile = db.query(UserProfile).first()
|
|
if not profile:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User profile not found"
|
|
)
|
|
db.delete(profile)
|
|
db.commit()
|
|
|
|
|
|
# ============================================================================
|
|
# DAILY ROUTINE ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/routines", response_model=DailyRoutineResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_routine(
|
|
routine: DailyRoutineCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new daily routine"""
|
|
db_routine = DailyRoutine(**routine.dict())
|
|
db.add(db_routine)
|
|
db.commit()
|
|
db.refresh(db_routine)
|
|
return db_routine
|
|
|
|
|
|
@router.get("/routines", response_model=List[DailyRoutineResponse])
|
|
async def list_routines(
|
|
routine_type: Optional[str] = Query(None),
|
|
enabled: Optional[bool] = Query(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""List daily routines with optional filters"""
|
|
query = db.query(DailyRoutine)
|
|
|
|
if routine_type:
|
|
query = query.filter(DailyRoutine.routine_type == routine_type)
|
|
if enabled is not None:
|
|
query = query.filter(DailyRoutine.enabled == enabled)
|
|
|
|
return query.all()
|
|
|
|
|
|
@router.get("/routines/{routine_id}", response_model=DailyRoutineResponse)
|
|
async def get_routine(routine_id: int, db: Session = Depends(get_db)):
|
|
"""Get a specific routine"""
|
|
routine = db.query(DailyRoutine).filter(DailyRoutine.id == routine_id).first()
|
|
if not routine:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Routine not found"
|
|
)
|
|
return routine
|
|
|
|
|
|
@router.put("/routines/{routine_id}", response_model=DailyRoutineResponse)
|
|
async def update_routine(
|
|
routine_id: int,
|
|
routine_update: DailyRoutineUpdate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update a routine"""
|
|
routine = db.query(DailyRoutine).filter(DailyRoutine.id == routine_id).first()
|
|
if not routine:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Routine not found"
|
|
)
|
|
|
|
update_data = routine_update.dict(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(routine, key, value)
|
|
|
|
routine.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(routine)
|
|
return routine
|
|
|
|
|
|
@router.delete("/routines/{routine_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_routine(routine_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a routine"""
|
|
routine = db.query(DailyRoutine).filter(DailyRoutine.id == routine_id).first()
|
|
if not routine:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Routine not found"
|
|
)
|
|
db.delete(routine)
|
|
db.commit()
|
|
|
|
|
|
@router.post("/routines/{routine_id}/execute", response_model=RoutineExecutionResponse, status_code=status.HTTP_201_CREATED)
|
|
async def execute_routine(
|
|
routine_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Execute a routine (log execution)"""
|
|
routine = db.query(DailyRoutine).filter(DailyRoutine.id == routine_id).first()
|
|
if not routine:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Routine not found"
|
|
)
|
|
|
|
# Create execution record
|
|
execution = RoutineExecution(
|
|
routine_id=routine_id,
|
|
completion_status="completed",
|
|
tasks_completed=routine.tasks
|
|
)
|
|
db.add(execution)
|
|
db.commit()
|
|
db.refresh(execution)
|
|
return execution
|
|
|
|
|
|
@router.get("/routines/{routine_id}/executions", response_model=List[RoutineExecutionResponse])
|
|
async def get_routine_executions(
|
|
routine_id: int,
|
|
limit: int = Query(10, ge=1, le=100),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get execution history for a routine"""
|
|
executions = db.query(RoutineExecution).filter(
|
|
RoutineExecution.routine_id == routine_id
|
|
).order_by(RoutineExecution.executed_at.desc()).limit(limit).all()
|
|
return executions
|
|
|
|
|
|
# ============================================================================
|
|
# NOTIFICATION ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/notifications", response_model=NotificationResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_notification(
|
|
notification: NotificationCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new notification"""
|
|
db_notification = Notification(**notification.dict())
|
|
db.add(db_notification)
|
|
db.commit()
|
|
db.refresh(db_notification)
|
|
return db_notification
|
|
|
|
|
|
@router.get("/notifications", response_model=NotificationListResponse)
|
|
async def get_notifications(
|
|
notification_type: Optional[str] = Query(None),
|
|
read: Optional[bool] = Query(None),
|
|
limit: int = Query(20, ge=1, le=100),
|
|
offset: int = Query(0, ge=0),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get notifications with optional filters"""
|
|
query = db.query(Notification)
|
|
|
|
if notification_type:
|
|
query = query.filter(Notification.notification_type == notification_type)
|
|
if read is not None:
|
|
query = query.filter(Notification.read == read)
|
|
|
|
total = query.count()
|
|
unread = query.filter(Notification.read == False).count()
|
|
|
|
notifications = query.order_by(
|
|
Notification.created_at.desc()
|
|
).limit(limit).offset(offset).all()
|
|
|
|
return NotificationListResponse(
|
|
notifications=notifications,
|
|
unread_count=unread,
|
|
total_count=total
|
|
)
|
|
|
|
|
|
@router.get("/notifications/{notification_id}", response_model=NotificationResponse)
|
|
async def get_notification(notification_id: int, db: Session = Depends(get_db)):
|
|
"""Get a specific notification"""
|
|
notification = db.query(Notification).filter(
|
|
Notification.id == notification_id
|
|
).first()
|
|
if not notification:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Notification not found"
|
|
)
|
|
return notification
|
|
|
|
|
|
@router.put("/notifications/{notification_id}/read", response_model=NotificationResponse)
|
|
async def mark_notification_read(
|
|
notification_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Mark notification as read"""
|
|
notification = db.query(Notification).filter(
|
|
Notification.id == notification_id
|
|
).first()
|
|
if not notification:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Notification not found"
|
|
)
|
|
|
|
notification.read = True
|
|
notification.read_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(notification)
|
|
return notification
|
|
|
|
|
|
@router.post("/notifications/mark-all-read")
|
|
async def mark_all_notifications_read(db: Session = Depends(get_db)):
|
|
"""Mark all notifications as read"""
|
|
db.query(Notification).filter(Notification.read == False).update({
|
|
Notification.read: True,
|
|
Notification.read_at: datetime.utcnow()
|
|
})
|
|
db.commit()
|
|
return {"message": "All notifications marked as read"}
|
|
|
|
|
|
@router.delete("/notifications/{notification_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_notification(notification_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a notification"""
|
|
notification = db.query(Notification).filter(
|
|
Notification.id == notification_id
|
|
).first()
|
|
if not notification:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Notification not found"
|
|
)
|
|
db.delete(notification)
|
|
db.commit()
|
|
|
|
|
|
# ============================================================================
|
|
# DAILY CHECKLIST ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/checklists", response_model=DailyChecklistResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_checklist(
|
|
checklist: DailyChecklistCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new daily checklist"""
|
|
db_checklist = DailyChecklist(**checklist.dict())
|
|
db.add(db_checklist)
|
|
db.commit()
|
|
db.refresh(db_checklist)
|
|
return db_checklist
|
|
|
|
|
|
@router.get("/checklists/today", response_model=Optional[DailyChecklistResponse])
|
|
async def get_today_checklist(
|
|
checklist_type: Optional[str] = Query(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get today's checklist"""
|
|
query = db.query(DailyChecklist).filter(
|
|
DailyChecklist.checklist_date == date.today()
|
|
)
|
|
|
|
if checklist_type:
|
|
query = query.filter(DailyChecklist.checklist_type == checklist_type)
|
|
|
|
return query.first()
|
|
|
|
|
|
@router.get("/checklists", response_model=List[DailyChecklistResponse])
|
|
async def list_checklists(
|
|
checklist_type: Optional[str] = Query(None),
|
|
start_date: Optional[str] = Query(None),
|
|
end_date: Optional[str] = Query(None),
|
|
limit: int = Query(30, ge=1, le=365),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""List checklists with optional filters"""
|
|
query = db.query(DailyChecklist)
|
|
|
|
if checklist_type:
|
|
query = query.filter(DailyChecklist.checklist_type == checklist_type)
|
|
|
|
if start_date:
|
|
try:
|
|
start = datetime.fromisoformat(start_date).date()
|
|
query = query.filter(DailyChecklist.checklist_date >= start)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid start_date format")
|
|
|
|
if end_date:
|
|
try:
|
|
end = datetime.fromisoformat(end_date).date()
|
|
query = query.filter(DailyChecklist.checklist_date <= end)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid end_date format")
|
|
|
|
return query.order_by(DailyChecklist.checklist_date.desc()).limit(limit).all()
|
|
|
|
|
|
@router.get("/checklists/{checklist_id}", response_model=DailyChecklistResponse)
|
|
async def get_checklist(checklist_id: int, db: Session = Depends(get_db)):
|
|
"""Get a specific checklist"""
|
|
checklist = db.query(DailyChecklist).filter(
|
|
DailyChecklist.id == checklist_id
|
|
).first()
|
|
if not checklist:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Checklist not found"
|
|
)
|
|
return checklist
|
|
|
|
|
|
@router.put("/checklists/{checklist_id}", response_model=DailyChecklistResponse)
|
|
async def update_checklist(
|
|
checklist_id: int,
|
|
checklist_update: DailyChecklistUpdate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update a checklist"""
|
|
checklist = db.query(DailyChecklist).filter(
|
|
DailyChecklist.id == checklist_id
|
|
).first()
|
|
if not checklist:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Checklist not found"
|
|
)
|
|
|
|
update_data = checklist_update.dict(exclude_unset=True)
|
|
|
|
# Calculate completion percentage if items are provided
|
|
if "items" in update_data:
|
|
items = update_data["items"]
|
|
if items:
|
|
completed = sum(1 for item in items if item.get("completed", False))
|
|
completion_pct = (completed / len(items)) * 100
|
|
update_data["completion_percentage"] = completion_pct
|
|
|
|
for key, value in update_data.items():
|
|
setattr(checklist, key, value)
|
|
|
|
checklist.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(checklist)
|
|
return checklist
|
|
|
|
|
|
@router.put("/checklists/{checklist_id}/items/{item_id}", response_model=DailyChecklistResponse)
|
|
async def update_checklist_item(
|
|
checklist_id: int,
|
|
item_id: str,
|
|
completed: bool,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update a specific checklist item"""
|
|
checklist = db.query(DailyChecklist).filter(
|
|
DailyChecklist.id == checklist_id
|
|
).first()
|
|
if not checklist:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Checklist not found"
|
|
)
|
|
|
|
# Find and update item
|
|
items = checklist.items or []
|
|
for item in items:
|
|
if item.get("id") == item_id:
|
|
item["completed"] = completed
|
|
if completed:
|
|
item["completed_at"] = datetime.utcnow().isoformat()
|
|
break
|
|
|
|
# Recalculate completion percentage
|
|
if items:
|
|
completed_count = sum(1 for item in items if item.get("completed", False))
|
|
checklist.completion_percentage = (completed_count / len(items)) * 100
|
|
|
|
checklist.items = items
|
|
checklist.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(checklist)
|
|
return checklist
|
|
|
|
|
|
@router.delete("/checklists/{checklist_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_checklist(checklist_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a checklist"""
|
|
checklist = db.query(DailyChecklist).filter(
|
|
DailyChecklist.id == checklist_id
|
|
).first()
|
|
if not checklist:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Checklist not found"
|
|
)
|
|
db.delete(checklist)
|
|
db.commit()
|
|
|
|
|
|
# ============================================================================
|
|
# HABIT TRACKER ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.post("/habits", response_model=HabitTrackerResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_habit(
|
|
habit: HabitTrackerCreate,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new habit to track"""
|
|
db_habit = HabitTracker(**habit.dict())
|
|
db.add(db_habit)
|
|
db.commit()
|
|
db.refresh(db_habit)
|
|
return db_habit
|
|
|
|
|
|
@router.get("/habits", response_model=List[HabitTrackerResponse])
|
|
async def list_habits(
|
|
habit_name: Optional[str] = Query(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""List all habits"""
|
|
query = db.query(HabitTracker)
|
|
|
|
if habit_name:
|
|
query = query.filter(HabitTracker.habit_name.ilike(f"%{habit_name}%"))
|
|
|
|
return query.all()
|
|
|
|
|
|
@router.get("/habits/{habit_id}", response_model=HabitTrackerResponse)
|
|
async def get_habit(habit_id: int, db: Session = Depends(get_db)):
|
|
"""Get a specific habit"""
|
|
habit = db.query(HabitTracker).filter(HabitTracker.id == habit_id).first()
|
|
if not habit:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Habit not found"
|
|
)
|
|
return habit
|
|
|
|
|
|
@router.post("/habits/{habit_id}/log", response_model=HabitTrackerResponse)
|
|
async def log_habit_completion(
|
|
habit_id: int,
|
|
request: HabitCompletionRequest,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Log habit completion"""
|
|
habit = db.query(HabitTracker).filter(HabitTracker.id == habit_id).first()
|
|
if not habit:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Habit not found"
|
|
)
|
|
|
|
completion_date = request.completion_date or date.today().isoformat()
|
|
completion_dates = habit.completion_dates or []
|
|
|
|
# Avoid duplicates
|
|
if completion_date not in completion_dates:
|
|
completion_dates.append(completion_date)
|
|
habit.completion_dates = completion_dates
|
|
habit.total_completions = len(completion_dates)
|
|
|
|
# Calculate streaks
|
|
sorted_dates = sorted(completion_dates)
|
|
current_streak = 0
|
|
for i in range(len(sorted_dates) - 1, -1, -1):
|
|
current_date = datetime.fromisoformat(sorted_dates[i]).date()
|
|
expected_date = date.today() - timedelta(days=len(sorted_dates) - 1 - i)
|
|
|
|
if i == len(sorted_dates) - 1:
|
|
if current_date >= date.today() - timedelta(days=1):
|
|
current_streak = 1
|
|
else:
|
|
break
|
|
else:
|
|
next_date = datetime.fromisoformat(sorted_dates[i + 1]).date()
|
|
if next_date - current_date == timedelta(days=1):
|
|
current_streak += 1
|
|
else:
|
|
break
|
|
|
|
habit.current_streak = current_streak
|
|
habit.longest_streak = max(habit.longest_streak or 0, current_streak)
|
|
habit.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(habit)
|
|
|
|
return habit
|
|
|
|
|
|
@router.delete("/habits/{habit_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_habit(habit_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a habit"""
|
|
habit = db.query(HabitTracker).filter(HabitTracker.id == habit_id).first()
|
|
if not habit:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Habit not found"
|
|
)
|
|
db.delete(habit)
|
|
db.commit()
|
|
|
|
|
|
# ============================================================================
|
|
# SUMMARY ENDPOINTS
|
|
# ============================================================================
|
|
|
|
@router.get("/dashboard")
|
|
async def get_dashboard_summary(db: Session = Depends(get_db)):
|
|
"""Get daily helper dashboard summary"""
|
|
today = date.today()
|
|
|
|
# Get today's checklists
|
|
checklists = db.query(DailyChecklist).filter(
|
|
DailyChecklist.checklist_date == today
|
|
).all()
|
|
|
|
# Get unread notifications
|
|
unread_notifications = db.query(Notification).filter(
|
|
Notification.read == False
|
|
).count()
|
|
|
|
# Get habits
|
|
habits = db.query(HabitTracker).all()
|
|
habit_summary = {
|
|
"total": len(habits),
|
|
"completed_today": sum(
|
|
1 for h in habits
|
|
if h.completion_dates and today.isoformat() in h.completion_dates
|
|
)
|
|
}
|
|
|
|
# Get user profile
|
|
profile = db.query(UserProfile).first()
|
|
|
|
return {
|
|
"user_profile": profile if profile else None,
|
|
"today_date": today.isoformat(),
|
|
"checklists": checklists,
|
|
"checklists_count": len(checklists),
|
|
"unread_notifications": unread_notifications,
|
|
"habits_summary": habit_summary,
|
|
"pending_routines": db.query(DailyRoutine).filter(
|
|
DailyRoutine.enabled == True
|
|
).count()
|
|
}
|