Skip to content

feat: add user display_name (#875)#957

Open
maxachis wants to merge 2 commits intodevfrom
feat/875-user-display-names
Open

feat: add user display_name (#875)#957
maxachis wants to merge 2 commits intodevfrom
feat/875-user-display-names

Conversation

@maxachis
Copy link
Copy Markdown
Contributor

@maxachis maxachis commented May 2, 2026

Closes #875.

Summary

  • Adds a public, case-insensitive-unique display_name field to users.
  • Settable at signup, on user creation, and updatable any time by the user via PATCH /user/{user_id} (also accessible to admins with USER_CREATE_UPDATE).
  • Validates length (3-30 chars) and charset (letters, numbers, -, _) with descriptive error messages, and surfaces a clean conflict message on duplicates.
  • Defaults to the user's numeric id (left-padded to 3 chars, e.g. user 7 -> "007") when none is provided. Padding ensures existing users with id < 100 still satisfy the 3-char minimum.
  • Included in both GET /user/{user_id} (v2 + v3) responses.

Migration

alembic/versions/c4a3f8e1d75b_add_user_display_name.py:

  • Adds users.display_name VARCHAR(30) NOT NULL, backfills existing rows with LPAD(id::text, 3, '0'), and creates a unique functional index on LOWER(display_name) (CITEXT-equivalent without the extension).
  • Adds nullable pending_users.display_name so the chosen handle can be carried through the email-validation step.

Tests added (tests/integration/user/display_name/test_display_name.py)

  • test_signup_with_display_name_persists_it
  • test_signup_without_display_name_defaults_to_padded_user_id
  • test_signup_rejects_display_name_too_short
  • test_signup_rejects_display_name_too_long
  • test_signup_rejects_display_name_with_invalid_characters
  • test_signup_rejects_duplicate_display_name_case_insensitive
  • test_patch_updates_display_name
  • test_patch_rejects_display_name_too_short
  • test_patch_rejects_display_name_with_invalid_characters
  • test_patch_rejects_duplicate_display_name_case_insensitive
  • test_patch_allows_setting_same_display_name_idempotently
  • test_user_profile_response_includes_display_name

Follow-ups deferred (per the issue)

  • Storing past display names (history table).
  • Rate-limiting display-name changes to once per 7 days.

Test plan

  • uv run pytest tests/integration/user tests/integration/auth tests/integration/oauth (47 passed)
  • uv run pytest tests (282 passed)
  • uv run ruff check .
  • uv run basedpyright --level error (0 errors)
  • Migration up/down/up cycle verified locally

maxachis and others added 2 commits May 2, 2026 08:52
Adds a public, case-insensitive-unique `display_name` to users.
Settable at signup, on user creation, and updatable any time via
PATCH /user/{user_id}. Defaults to the user's id (left-padded to 3
chars) when none is provided. Validates length (3-30) and charset
(letters, numbers, hyphens, underscores) with descriptive errors.

Includes Alembic migration that backfills existing users and adds a
case-insensitive unique index on LOWER(display_name).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous "__pending__" placeholder collided on the LOWER(display_name)
unique index when two signups without a display_name ran concurrently,
surfacing as a misleading DuplicateDisplayNameError. Use a per-insert
random suffix instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <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