77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
"""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)
|