Skip to content

feat(api): let users read and set their preferred language - #582

Open
abdulrafey1 wants to merge 3 commits into
refey/feat/lang-03-user-columnfrom
refey/feat/lang-04-api
Open

feat(api): let users read and set their preferred language#582
abdulrafey1 wants to merge 3 commits into
refey/feat/lang-03-user-columnfrom
refey/feat/lang-04-api

Conversation

@abdulrafey1

Copy link
Copy Markdown
Contributor

What

Exposes the preferred-language setting over the API so clients can list the supported languages and set or clear a user's choice.

Changes

  • feat(api): add authenticated GET /api/v1/languages, returning the supported languages and the platform default
  • feat(api): add PATCH /api/v1/user/me to set or clear the preference, and add language to the user schema
  • chore(frontend): regenerate the API client types

How to Test

  1. uv run pytest tests/api/v1/test_language.py tests/api/v1/test_user.py -q — 16 tests pass.
  2. curl http://localhost:7727/api/v1/languages -H "Authorization: Bearer $TOKEN" returns the three languages and "default": "en"; without the header it returns 401.
  3. curl -X PATCH .../api/v1/user/me -d '{"language":"es"}' returns 200; GET /api/v1/user/me then reports "language": "es".
  4. -d '{"language":"en-US"}' returns 422 and leaves the column untouched — matching is exact and case-sensitive.
  5. -d '{"language":null}' clears the preference back to null.

Notes

No migration. GET /me returns the raw stored value, null included, so a client can tell "never chose" from "chose English"; resolving to the default is resolve_language's job.

language is a required field on the request body, so an explicit null clears the preference while omitting the field is a 422 rather than a silent no-op.

Generated content does not consult this preference yet — that arrives with prompt injection in a later change.

This description was written with the assistance of an LLM (Claude).

abdulrafey1 and others added 3 commits August 7, 2026 23:29
The frontend picker and the static-UI translation layer both need this list;
serving it keeps the allowlist defined once instead of duplicated client-side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PATCH /user/me validates the tag against the supported-language allowlist in the
request model, so an unsupported value is a 422 before any domain logic runs. GET
returns the raw column, null included, so the frontend can tell a user who never
chose from one who chose English.

get_current_user takes Depends(get_async_session); FastAPI caches that dependency
per request, so current_user is already attached to the same session the route
body uses. PATCH mutates it directly instead of re-fetching or refreshing after
commit (open_session defaults to expire_on_commit=False, so the in-memory value
never goes stale). The test double for get_current_user takes the same dependency
for the same reason, so a value one request writes is visible to a later request
on the same client. That rewrote _override_current_user — the shared helper the
three pre-existing is_admin tests also call — from a detached make_transient
snapshot to a live select(User) on the request session; those three tests are
themselves left unmodified and still pass.

Also covers the PATCH auth gate (401 unauthenticated) and that a PATCH only ever
writes to the caller's own row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
openapi.json is a gitignored build artifact and is not committed; only the
generated TypeScript client changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant