Skip to content

API — goal-completions router

backend/src/routers/goal_completions.py (56 lines). APIRouter(prefix="/goal_completions", tags=["goals"]) (goal_completions.py:19).

Method Path Auth Request DTO Response Success Errors
POST /goal_completions/ JWT + owned goal GoalCompletionRequest (defined in-router, extra="forbid") CheckInResult (backend/src/schemas/checkin.py) 200 404 goal_not_found (missing / orphaned / not yours — enumeration-safe), 400 (future completed_on, from the service)

The request DTO (goal_completions.py:22-32):

class GoalCompletionRequest(BaseModel):
    """Payload for recording a goal completion or miss; rejects unknown fields."""

    model_config = ConfigDict(extra="forbid")

    goal_id: int
    did_complete: bool = True
    # Calendar day the check-in is for, in the user's timezone. Omit to log
    # today; supply a past ``YYYY-MM-DD`` to backfill a missed day. A future
    # date is rejected by the route.
    completed_on: date | None = None

Behavior (goal_completions.py:35-56): resolves (goal, habit) through the enumeration-safe ownership helper (backend/src/dependencies/ownership.py:90-115), builds a CheckInContext with the per-request-cached user timezone (backend/src/dependencies/timezone.py:30-40), and delegates to services.checkin.record_goal_completion. Recording is idempotent on the same (user, goal, local day) — backed by the DB unique index over (goal_id, user_id, local_day) (data-model/habits-goals) — and the service layer is shared deliberately: "the journal accept flow (#818) records through the identical path" (goal_completions.py:42-48). did_complete=false persists a real zero-unit row (the additive-parity rule documented in domain/habit-stats). The response carries updated streak and milestone data.


Grounded in adepthood@fbc529d, 2026-07-31.