"""Snooze endpoints: hide a conversation from the Inbox until a set time.""" from __future__ import annotations import datetime as dt import uuid from typing import Annotated from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app import snooze from app.api.errors import raise_api_error from app.db import get_session from app.events import get_event_bus from app.identity.deps import CurrentUser, require_csrf from app.models import Thread router = APIRouter(prefix="/threads", tags=["mail"], dependencies=[Depends(require_csrf)]) DbSession = Annotated[AsyncSession, Depends(get_session)] class SnoozeIn(BaseModel): until: dt.datetime class SnoozeOut(BaseModel): thread_id: uuid.UUID snoozed_until: dt.datetime | None async def _thread(db: AsyncSession, user_id: uuid.UUID, thread_id: uuid.UUID) -> Thread: thread = await db.scalar( select(Thread).where(Thread.id == thread_id, Thread.user_id == user_id) ) if thread is None: raise_api_error(404, "not_found", "thread not found") return thread async def _announce(user_id: uuid.UUID, thread_id: uuid.UUID) -> None: await get_event_bus().publish( "thread.updated", {"user_id": str(user_id), "thread_id": str(thread_id)} ) @router.post("/{thread_id}/snooze", response_model=SnoozeOut) async def snooze_thread( thread_id: uuid.UUID, body: SnoozeIn, user: CurrentUser, db: DbSession ) -> SnoozeOut: until = body.until if until.tzinfo is None: raise_api_error(400, "invalid_time", "the time needs a time zone") now = dt.datetime.now(dt.UTC) if until <= now: raise_api_error(400, "invalid_time", "pick a time in the future") if until > now + snooze.MAX_SNOOZE: raise_api_error(400, "invalid_time", "snooze for at most a year") thread = await _thread(db, user.id, thread_id) snooze.snooze(thread, until, now) await db.commit() await _announce(user.id, thread_id) return SnoozeOut(thread_id=thread_id, snoozed_until=thread.snoozed_until) @router.post("/{thread_id}/unsnooze", response_model=SnoozeOut) async def unsnooze_thread(thread_id: uuid.UUID, user: CurrentUser, db: DbSession) -> SnoozeOut: thread = await _thread(db, user.id, thread_id) if thread.snoozed_until is not None: snooze.wake(thread, dt.datetime.now(dt.UTC)) await db.commit() await _announce(user.id, thread_id) return SnoozeOut(thread_id=thread_id, snoozed_until=None)