feat: game lobby service

This commit is contained in:
Ilia Denisov
2026-04-25 23:20:55 +02:00
committed by GitHub
parent 32dc29359a
commit 48b0056b49
336 changed files with 57074 additions and 1418 deletions
+139 -28
View File
@@ -217,9 +217,10 @@ paths:
operationId: updateMyProfile
summary: Update self-service profile fields
description: |
`race_name` uniqueness is enforced through a canonical reservation
policy that is case-insensitive, rejects the frozen anti-fraud
confusable pairs, and preserves the original stored casing.
Accepts only `display_name`. Validation delegates to
`pkg/util/string.go:ValidateTypeName`; an empty value is accepted
and resets any stored display name. `user_name` is immutable and is
not returned in the request body.
parameters:
- $ref: "#/components/parameters/UserIDPath"
requestBody:
@@ -387,21 +388,21 @@ paths:
$ref: "#/components/responses/InternalError"
"503":
$ref: "#/components/responses/ServiceUnavailableError"
/api/v1/internal/user-lookups/by-race-name:
/api/v1/internal/user-lookups/by-user-name:
post:
tags:
- AdminUsers
operationId: getUserByRaceName
summary: Read one user by exact race name
operationId: getUserByUserName
summary: Read one user by exact user name
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserLookupByRaceNameRequest"
$ref: "#/components/schemas/UserLookupByUserNameRequest"
responses:
"200":
description: Exact user lookup result for the supplied `race_name`.
description: Exact user lookup result for the supplied `user_name`.
content:
application/json:
schema:
@@ -482,6 +483,24 @@ paths:
description: Filter by the derived game-join eligibility marker.
schema:
type: boolean
- name: user_name
in: query
description: Filter by exact `user_name`.
schema:
$ref: "#/components/schemas/UserName"
- name: display_name
in: query
description: Filter by `display_name`. Combined with `display_name_match`.
schema:
$ref: "#/components/schemas/DisplayName"
- name: display_name_match
in: query
description: Match mode for `display_name`; defaults to `exact`.
schema:
type: string
enum:
- exact
- prefix
responses:
"200":
description: Deterministically ordered page of users.
@@ -728,6 +747,46 @@ paths:
$ref: "#/components/responses/InternalError"
"503":
$ref: "#/components/responses/ServiceUnavailableError"
/api/v1/internal/users/{user_id}/delete:
post:
tags:
- AdminUsers
operationId: deleteUser
summary: Soft-delete one regular-user account
description: |
Soft-deletes the account identified by `user_id`. The account record is
preserved for audit with a `deleted_at` timestamp. Subsequent external
auth, self-service, admin-read, and lobby-eligibility operations
addressing the same `user_id` return `404 subject_not_found`.
The command is idempotent per `user_id`: calling it after the account
is already soft-deleted returns `404 subject_not_found` and does not
re-emit the `user.lifecycle.deleted` event.
parameters:
- $ref: "#/components/parameters/UserIDPath"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DeleteUserRequest"
responses:
"200":
description: Soft-delete applied successfully.
content:
application/json:
schema:
$ref: "#/components/schemas/DeleteUserResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/SubjectNotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"500":
$ref: "#/components/responses/InternalError"
"503":
$ref: "#/components/responses/ServiceUnavailableError"
components:
parameters:
UserIDPath:
@@ -766,14 +825,24 @@ components:
trimmed value as the exact stored and lookup value. The service does
not lowercase or otherwise canonicalize e-mail before storage or exact
lookup.
RaceName:
UserName:
type: string
description: |
Stored race name preserving the user-selected casing after successful
uniqueness checks. Uniqueness is enforced against a canonical
reservation key rather than exact string equality only.
minLength: 1
Immutable auto-generated platform handle in `player-<suffix>` form.
The suffix is eight characters drawn from a confusable-free
alphanumeric alphabet. Assigned once at account creation and never
changes thereafter.
pattern: "^player-[a-z0-9]{8}$"
minLength: 15
maxLength: 64
DisplayName:
type: string
description: |
Optional free-text user-facing label. Validated by
`pkg/util/string.go:ValidateTypeName`. Empty values are accepted and
represent no display name. Uniqueness is not enforced.
minLength: 0
maxLength: 30
LanguageTag:
type: string
description: |
@@ -827,6 +896,7 @@ components:
- private_game_manage_block
- game_join_block
- profile_update_block
- permanent_block
LimitCode:
type: string
description: |
@@ -837,6 +907,7 @@ components:
- max_owned_private_games
- max_pending_public_applications
- max_active_game_memberships
- max_registered_race_names
ActorRef:
type: object
additionalProperties: false
@@ -926,8 +997,9 @@ components:
description: |
Present for `existing` and `created`. A `created` outcome returns
the durable newly materialized `user_id` created together with an
initial generated `player-<shortid>` race name and free
entitlement snapshot.
initial auto-generated `user_name` handle and the free
entitlement snapshot. `display_name` defaults to empty for new
accounts.
block_reason_code:
type: string
description: Present only for `outcome=blocked`.
@@ -1077,7 +1149,7 @@ components:
required:
- user_id
- email
- race_name
- user_name
- preferred_language
- time_zone
- entitlement
@@ -1090,8 +1162,10 @@ components:
$ref: "#/components/schemas/UserID"
email:
$ref: "#/components/schemas/Email"
race_name:
$ref: "#/components/schemas/RaceName"
user_name:
$ref: "#/components/schemas/UserName"
display_name:
$ref: "#/components/schemas/DisplayName"
preferred_language:
$ref: "#/components/schemas/LanguageTag"
time_zone:
@@ -1114,6 +1188,15 @@ components:
updated_at:
type: string
format: date-time
deleted_at:
type: string
format: date-time
description: |
Soft-delete timestamp. Present only when the account has been
soft-deleted by a trusted `DeleteUser` command. External reads by
stable `user_id` return `404 subject_not_found` for such accounts;
admin listings exclude them unless explicitly asked via the
`deleted` filter.
GetMyAccountResponse:
type: object
additionalProperties: false
@@ -1126,14 +1209,15 @@ components:
type: object
additionalProperties: false
description: |
The current implementation accepts only `race_name` here. Attempts to
mutate `email` or `declared_country` are rejected as `400
invalid_request` through strict unknown-field handling.
Accepts only `display_name`. An empty value is accepted and resets
any stored display name. Any other field (including the legacy
`race_name` payload) is rejected as `400 invalid_request` through
strict unknown-field handling.
required:
- race_name
- display_name
properties:
race_name:
$ref: "#/components/schemas/RaceName"
display_name:
$ref: "#/components/schemas/DisplayName"
UpdateMySettingsRequest:
type: object
additionalProperties: false
@@ -1238,14 +1322,14 @@ components:
properties:
email:
$ref: "#/components/schemas/Email"
UserLookupByRaceNameRequest:
UserLookupByUserNameRequest:
type: object
additionalProperties: false
required:
- race_name
- user_name
properties:
race_name:
$ref: "#/components/schemas/RaceName"
user_name:
$ref: "#/components/schemas/UserName"
UserLookupResponse:
type: object
additionalProperties: false
@@ -1451,6 +1535,33 @@ components:
type: array
items:
$ref: "#/components/schemas/ActiveLimit"
DeleteUserRequest:
type: object
additionalProperties: false
description: |
Soft-delete command payload. The caller is expected to be
`Admin Service`. `actor.type` must be non-empty; `actor.id` is
optional.
required:
- reason_code
- actor
properties:
reason_code:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
DeleteUserResponse:
type: object
additionalProperties: false
required:
- user_id
- deleted_at
properties:
user_id:
$ref: "#/components/schemas/UserID"
deleted_at:
type: string
format: date-time
ErrorResponse:
type: object
additionalProperties: false