from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.orm import Session from app.core.security import utc_now from app.db.base import get_db from app.models import Notification, NotificationPreference from app.schemas import NotificationPreferencePatch from app.services.auth import CurrentContext, get_current_context from app.services.dashboard import calendar_items, file_items, home_dashboard, local_actions_for_context from app.services.serializers import profile_dict router = APIRouter(prefix="/api", tags=["home"]) DEFAULT_PREFERENCES = { "direct_mentions": "immediate", "event_changes": "immediate", "urgent_announcements": "immediate", "tasks_assigned": "immediate", "discussions": "digest", "new_files": "digest", "general_chatter": "digest", "reactions": "muted", "off_topic": "muted", } @router.get("/home") def home(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: dashboard = home_dashboard(db, ctx) return { "profile": profile_dict(ctx.home_profile, ctx.member), "sections": dashboard["sections"], "connections": dashboard["connections"], } @router.get("/home/actions") def home_actions(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: return {"actions": home_dashboard(db, ctx)["sections"]["needs_me"]} @router.get("/home/calendar") def home_calendar(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: return {"events": calendar_items(db, ctx)} @router.get("/home/files") def home_files(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: return {"files": file_items(db, ctx)} @router.get("/home/official-updates") def official_updates(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: return {"official_updates": home_dashboard(db, ctx)["sections"]["official_updates"]} @router.get("/home/catch-up") def catch_up(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: return {"catch_up": home_dashboard(db, ctx)["sections"]["catch_up"]} def _preference_owner(ctx: CurrentContext) -> tuple[str | None, str | None]: if ctx.home_profile: return ctx.home_profile.id, None if ctx.member: return None, ctx.member.id return None, None @router.get("/me/notification-preferences") def get_notification_preferences(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: home_profile_id, member_id = _preference_owner(ctx) rows = db.scalars( select(NotificationPreference).where( NotificationPreference.home_profile_id == home_profile_id, NotificationPreference.member_id == member_id, ) ).all() result = dict(DEFAULT_PREFERENCES) for row in rows: result[row.category] = row.delivery return { "headline": "Mute the noise, not the group.", "preferences": result, "groups": { "Immediate": ["direct_mentions", "event_changes", "urgent_announcements", "tasks_assigned"], "Quiet / digest": ["discussions", "new_files", "general_chatter"], "Mute": ["reactions", "off_topic"], }, } @router.patch("/me/notification-preferences") def patch_notification_preferences( payload: NotificationPreferencePatch, ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db), ) -> dict: home_profile_id, member_id = _preference_owner(ctx) if home_profile_id is None and member_id is None: raise HTTPException(status_code=401, detail={"error": {"code": "not_authenticated", "message": "Open an invite first.", "details": {}}}) for category, delivery in payload.preferences.items(): row = db.scalar( select(NotificationPreference).where( NotificationPreference.home_profile_id == home_profile_id, NotificationPreference.member_id == member_id, NotificationPreference.category == category, ) ) if row: row.delivery = delivery row.enabled = delivery != "muted" row.updated_at = utc_now() else: db.add( NotificationPreference( home_profile_id=home_profile_id, member_id=member_id, category=category, delivery=delivery, enabled=delivery != "muted", ) ) db.commit() return get_notification_preferences(ctx, db) @router.get("/me/notifications") def notifications(ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: query = select(Notification) if ctx.home_profile: query = query.where(Notification.home_profile_id == ctx.home_profile.id) elif ctx.member: query = query.where(Notification.member_id == ctx.member.id) rows = db.scalars(query.order_by(Notification.created_at.desc()).limit(50)).all() return { "notifications": [ { "id": item.id, "title": item.title, "body": item.body, "category": item.category, "read_at": item.read_at.isoformat() if item.read_at else None, "created_at": item.created_at.isoformat(), } for item in rows ] } @router.patch("/me/notifications/{notification_id}/read") def mark_notification_read(notification_id: str, ctx: CurrentContext = Depends(get_current_context), db: Session = Depends(get_db)) -> dict: query = select(Notification).where(Notification.id == notification_id) if ctx.home_profile: query = query.where(Notification.home_profile_id == ctx.home_profile.id) elif ctx.member: query = query.where(Notification.member_id == ctx.member.id) notification = db.scalar(query) if not notification: raise HTTPException(status_code=404, detail={"error": {"code": "not_found", "message": "Notification not found.", "details": {}}}) notification.read_at = utc_now() db.commit() return {"ok": True}