3.7 KiB
TODO (but later) - Tasks to get round to
The following are suggested improvements to the system which require more thought or triage. Do NOT start work on these.
User onboarding flow
4. Add onboarding_complete state to the user or derive it.
After verifying their email, the user still needs to add at least one LearnableLanguage before the app can serve them content. There are two options: (a) add a boolean onboarding_complete column to users that is set when the first language is added, or (b) derive it at runtime (len(learnable_languages) > 0). Option (b) is simpler and avoids a migration, but it makes the concept implicit. Either way, the BFF /user_profile response must expose this state so the frontend knows which screen to show.
5. Protected routes must enforce both is_email_verified and onboarding completion.
Currently verify_token in app/auth.py only checks the JWT signature and expiry. Routes like /vocab, /flashcards, and /bff/articles should not be reachable by a user who has not verified their email or not completed onboarding. Add a require_onboarding_complete dependency (similar to the existing require_admin) that does a lightweight DB lookup and returns 403 with a structured error body ({"code": "EMAIL_NOT_VERIFIED"} / {"code": "ONBOARDING_INCOMPLETE"}) so the frontend can route appropriately.
6. Add a POST /auth/resend-verification endpoint.
Users frequently miss or lose the initial verification email. Without a resend endpoint they are permanently locked out if the first email is lost. This endpoint should be unauthenticated (the user has no token yet), accept {"email": "..."}, and always return 200 regardless of whether the email exists (to avoid user enumeration). Rate-limit it tightly (e.g. 3 requests per hour per IP).
7. Add password strength validation at registration.
POST /auth/register currently accepts any non-empty string. Add a minimum length (12 characters is a reasonable baseline) at the Pydantic model layer in app/routers/auth.py. This is one line using a @field_validator and is much cheaper to add now than after users have weak passwords in the DB.
8. Rate-limit the register and login endpoints.
Neither endpoint has any rate limiting. login in particular is vulnerable to credential-stuffing. Add slowapi (already commonly paired with FastAPI) and apply per-IP limits: e.g. 5 requests/minute on login, 10 requests/hour on register.
Enqueued or stashed articles
Just because a TranslatedArticle exists doesn't mean it should be accessible to every language learner learning that language.
One article could be accessible to many language learners.
Not every language learner could "earn" access to that article at the same time.
E.g. consider that we ran a three-piece non-fiction evergreen set of articles on the history of the croissant, for French learners.
We might have several versions of this series, one at each proficiency level - and a learner who has both B1, B2 selected shouldn't receive both.
To model this we might have, a data structure that looks like:
// This is extremely speculative, purely illustrative
{
"user_id": "alice-user-uuid",
"article_id": "croissant-piece-part-2-uuid",
"available_after": "2026-04-01:00:00Z"
}
We may want to release the first piece to the user no the day they sign up, then a further piece in the 24h after that.
Similarly, we may generate news summarise a day ahead, to be released the following morning at 7am. That data might look like:
{
"article_id": "mondays-french-briefing-uuid",
"available_after": "2026-04-01T07:00Z",
"for_language": "fr",
"for_proficiencies": ["B1"]
}