Three-pane webmail client. - Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP sync, threading, search, drafts/outbox with undo, attachments, 2FA. - Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane UI with rich-text composer, light/dark themes, resizable panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
"""Recipient autocomplete from known_recipients (SPEC §9.1, §4).
|
|
|
|
Prefix match on email or name, ranked by ``times_used`` decayed by recency. The
|
|
composer merges these with plugin capability providers client-side (M3).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.db import get_session
|
|
from app.identity.deps import CurrentUser
|
|
from app.models import KnownRecipient
|
|
|
|
router = APIRouter(prefix="/recipients", tags=["compose"])
|
|
|
|
DbSession = Annotated[AsyncSession, Depends(get_session)]
|
|
|
|
|
|
class Suggestion(BaseModel):
|
|
name: str
|
|
email: str
|
|
|
|
|
|
@router.get("/suggest", response_model=list[Suggestion])
|
|
async def suggest(
|
|
user: CurrentUser, db: DbSession, q: str = Query("", max_length=200)
|
|
) -> list[Suggestion]:
|
|
term = q.strip()
|
|
if not term:
|
|
return []
|
|
like = f"{term}%"
|
|
# Recency-decayed score: times_used / (1 + age_in_days). Ranks frequent and
|
|
# recent correspondents ahead of stale ones (SPEC §9.1).
|
|
age_days = func.extract("epoch", func.now() - KnownRecipient.last_used_at) / 86400.0
|
|
score = KnownRecipient.times_used / (1.0 + age_days)
|
|
stmt = (
|
|
select(KnownRecipient.name, KnownRecipient.email)
|
|
.where(
|
|
KnownRecipient.user_id == user.id,
|
|
KnownRecipient.email.ilike(like) | KnownRecipient.name.ilike(like),
|
|
)
|
|
.order_by(score.desc())
|
|
.limit(10)
|
|
)
|
|
rows = (await db.execute(stmt)).all()
|
|
return [Suggestion(name=row.name, email=row.email) for row in rows]
|