init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,155 @@
|
||||
"""The timer endpoints. Thin: every decision lives in `services.pomodoro`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from app.api.deps import DbSession, Settings
|
||||
from app.api.schemas import (
|
||||
AbandonRequest,
|
||||
AttachTaskRequest,
|
||||
CompleteTimerRequest,
|
||||
ExtendRequest,
|
||||
StartTimerRequest,
|
||||
)
|
||||
from app.services import pomodoro, report, stats
|
||||
from app.services.notify import count_sent, notify, record_sent
|
||||
from app.timeutil import local_date
|
||||
|
||||
router = APIRouter(prefix="/api/timer", tags=["timer"])
|
||||
|
||||
|
||||
@router.get("/state")
|
||||
def get_state(session: DbSession, settings: Settings) -> dict:
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/start")
|
||||
def start(body: StartTimerRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.start(
|
||||
session,
|
||||
settings,
|
||||
kind=body.kind,
|
||||
minutes=body.minutes,
|
||||
task_id=body.task_id,
|
||||
note=body.note,
|
||||
)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/pause")
|
||||
def pause(session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.pause(session, settings)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/resume")
|
||||
def resume(session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.resume(session)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/complete")
|
||||
def complete(body: CompleteTimerRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
finished, follow_on = pomodoro.complete(session, settings, auto_next=body.auto_next)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
celebration = _maybe_celebrate(session, settings) if finished.is_work else None
|
||||
state = pomodoro.state(session, settings)
|
||||
state["finished"] = pomodoro.session_dict(finished)
|
||||
state["auto_started"] = pomodoro.session_dict(follow_on)
|
||||
state["celebration"] = celebration
|
||||
return state
|
||||
|
||||
|
||||
@router.post("/abandon")
|
||||
def abandon(body: AbandonRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.abandon(session, settings, reason=body.reason)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/interrupt")
|
||||
def interrupt(session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.add_interruption(session)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/extend")
|
||||
def extend(body: ExtendRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.extend(session, body.minutes)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.post("/task")
|
||||
def attach_task(body: AttachTaskRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
pomodoro.attach_task(session, body.task_id)
|
||||
except pomodoro.TimerError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
return pomodoro.state(session, settings)
|
||||
|
||||
|
||||
@router.get("/sessions")
|
||||
def sessions(
|
||||
session: DbSession,
|
||||
settings: Settings,
|
||||
day: date | None = Query(default=None, description="Local date; defaults to today."),
|
||||
) -> dict:
|
||||
target = day or local_date(settings.timezone)
|
||||
rows = pomodoro.sessions_for_day(session, settings, target)
|
||||
return {
|
||||
"date": target.isoformat(),
|
||||
"sessions": [pomodoro.session_dict(row) for row in rows],
|
||||
"metrics": stats.day_metrics(session, settings, target),
|
||||
}
|
||||
|
||||
|
||||
def _maybe_celebrate(session, settings) -> str | None:
|
||||
"""Cheer in Telegram every N pomodoros — the cheapest dopamine there is."""
|
||||
every = settings.notifications.telegram.celebrate_every_n_pomodoros
|
||||
if not (settings.notifications.telegram.enabled and every):
|
||||
return None
|
||||
|
||||
done = pomodoro.completed_work_today(session, settings)
|
||||
if done == 0 or done % every != 0:
|
||||
return None
|
||||
|
||||
today = local_date(settings.timezone)
|
||||
kind = f"celebrate:{done}"
|
||||
if count_sent(session, kind, today):
|
||||
return None
|
||||
|
||||
ru = settings.notifications.report.language == "ru"
|
||||
goal = settings.pomodoro.daily_goal_pomodoros
|
||||
bar = report.progress_bar(done, goal)
|
||||
text = (
|
||||
f"🎉 <b>{done}</b> помидорок готово!\n{bar} {done}/{goal}\nДержи темп."
|
||||
if ru
|
||||
else f"🎉 <b>{done}</b> pomodoros done!\n{bar} {done}/{goal}\nKeep the rhythm."
|
||||
)
|
||||
deliveries = notify(settings, text)
|
||||
if any(delivery.ok for delivery in deliveries):
|
||||
record_sent(session, kind, today, {"pomodoros": done})
|
||||
return text
|
||||
Reference in new issue
Block a user