Skip to content

feat: add user display_name (#875)#957

Merged
maxachis merged 2 commits into
devfrom
feat/875-user-display-names
May 10, 2026
Merged

feat: add user display_name (#875)#957
maxachis merged 2 commits into
devfrom
feat/875-user-display-names

Conversation

@maxachis

@maxachis maxachis commented May 2, 2026

Copy link
Copy Markdown
Contributor

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>
@maxachis
maxachis merged commit e754762 into dev May 10, 2026
5 checks passed
@maxachis
maxachis deleted the feat/875-user-display-names branch May 10, 2026 15:11
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