From 90571e221b16cbe250bb1abd64c46b5975430974 Mon Sep 17 00:00:00 2001 From: barneyjackson <1398471+barneyjackson@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:51:51 +0000 Subject: [PATCH] docs(py): automated update of leadr-oss docs bundle (v0.8.8) --- docs/api/.api-version | 2 +- docs/api/http-api/authentication.md | 4 + docs/api/http-api/index.md | 4 +- docs/api/http-api/scores.md | 2 +- docs/api/openapi.json | 6 +- docs/api/reference/accounts.md | 6 +- docs/api/reference/auth.md | 112 +++++++++++++++++++++++++++- docs/api/reference/boards.md | 10 +-- docs/api/reference/common.md | 52 +++++++++++++ docs/api/reference/config.md | 15 ++++ docs/api/reference/scores.md | 4 +- 11 files changed, 198 insertions(+), 19 deletions(-) diff --git a/docs/api/.api-version b/docs/api/.api-version index 060b616..90d9dc1 100644 --- a/docs/api/.api-version +++ b/docs/api/.api-version @@ -1 +1 @@ -v0.8.7 +v0.8.8 diff --git a/docs/api/http-api/authentication.md b/docs/api/http-api/authentication.md index 6054d9a..32aaa2b 100644 --- a/docs/api/http-api/authentication.md +++ b/docs/api/http-api/authentication.md @@ -154,9 +154,13 @@ the device record and creates a new identity session. No authentication is required to call this endpoint (it IS the authentication). +The _geo parameter triggers GeoIP lookup for this endpoint. Geo data is +available for future use via _geo.timezone, _geo.country, _geo.city. + Args: session_request: Session start request with game_id and fingerprint identity_service: IdentityService dependency (handles device and identity creation) + _geo: GeoIP information extracted from client IP address (available for future use) Returns: StartSessionResponse with identity info and access tokens diff --git a/docs/api/http-api/index.md b/docs/api/http-api/index.md index b093fba..5eb8a2c 100644 --- a/docs/api/http-api/index.md +++ b/docs/api/http-api/index.md @@ -1,8 +1,8 @@ --- -title: LEADR Open-Source Edition - Admin & Client API v0.8.6 +title: LEADR Open-Source Edition - Admin & Client API v0.8.7 --- -# LEADR Open-Source Edition - Admin & Client API v0.8.6 +# LEADR Open-Source Edition - Admin & Client API v0.8.7 > Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. diff --git a/docs/api/http-api/scores.md b/docs/api/http-api/scores.md index 7fe7211..041c2e3 100644 --- a/docs/api/http-api/scores.md +++ b/docs/api/http-api/scores.md @@ -459,7 +459,7 @@ are automatically derived from the authenticated session. Args: score_request: Score creation details including board_id, player_name, and value. - request: FastAPI request object for accessing geo data. + geo: GeoIP information extracted from client IP address. service: Injected score service dependency. board_service: Injected board service for board lookup. background_tasks: FastAPI background tasks for async metadata updates. diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 0e57a5f..b3ef2b0 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "LEADR Open-Source Edition - Admin & Client API", "description": "LEADR Open-Source Edition is the free cross-platform leaderboard backend for game devs", - "version": "0.8.6" + "version": "0.8.7" }, "paths": { "/v1/accounts": { @@ -7321,7 +7321,7 @@ "Scores" ], "summary": "Create Score Client", - "description": "Create a new score (Client API).\n\nCreates a new score submission for a board. All IDs (account_id, game_id, identity_id)\nare automatically derived from the authenticated session.\n\nArgs:\n score_request: Score creation details including board_id, player_name, and value.\n request: FastAPI request object for accessing geo data.\n service: Injected score service dependency.\n board_service: Injected board service for board lookup.\n background_tasks: FastAPI background tasks for async metadata updates.\n auth: Client authentication context with device and identity info.\n pre_create_hook: Hook called before score creation (for quota checks).\n post_create_hook: Hook called after successful score creation.\n\nReturns:\n ScoreClientResponse with the created score (excludes device_id).\n\nRaises:\n 404: Board not found.\n 400: Validation failed (board doesn't belong to account, or game doesn't\n match board's game).\n 403: Score rejected by anti-cheat (rate limit exceeded).", + "description": "Create a new score (Client API).\n\nCreates a new score submission for a board. All IDs (account_id, game_id, identity_id)\nare automatically derived from the authenticated session.\n\nArgs:\n score_request: Score creation details including board_id, player_name, and value.\n geo: GeoIP information extracted from client IP address.\n service: Injected score service dependency.\n board_service: Injected board service for board lookup.\n background_tasks: FastAPI background tasks for async metadata updates.\n auth: Client authentication context with device and identity info.\n pre_create_hook: Hook called before score creation (for quota checks).\n post_create_hook: Hook called after successful score creation.\n\nReturns:\n ScoreClientResponse with the created score (excludes device_id).\n\nRaises:\n 404: Board not found.\n 400: Validation failed (board doesn't belong to account, or game doesn't\n match board's game).\n 403: Score rejected by anti-cheat (rate limit exceeded).", "operationId": "create_score_client_v1_client_scores_post", "parameters": [ { @@ -7985,7 +7985,7 @@ "Authentication" ], "summary": "Start Session", - "description": "Start a new identity session for a game client.\n\nThis endpoint authenticates game clients and provides JWT access tokens.\nIt is idempotent - calling multiple times for the same fingerprint updates\nthe device record and creates a new identity session.\n\nNo authentication is required to call this endpoint (it IS the authentication).\n\nArgs:\n session_request: Session start request with game_id and fingerprint\n identity_service: IdentityService dependency (handles device and identity creation)\n\nReturns:\n StartSessionResponse with identity info and access tokens\n\nRaises:\n 404: Game not found\n 422: Invalid request (missing required fields, invalid UUID format)", + "description": "Start a new identity session for a game client.\n\nThis endpoint authenticates game clients and provides JWT access tokens.\nIt is idempotent - calling multiple times for the same fingerprint updates\nthe device record and creates a new identity session.\n\nNo authentication is required to call this endpoint (it IS the authentication).\n\nThe _geo parameter triggers GeoIP lookup for this endpoint. Geo data is\navailable for future use via _geo.timezone, _geo.country, _geo.city.\n\nArgs:\n session_request: Session start request with game_id and fingerprint\n identity_service: IdentityService dependency (handles device and identity creation)\n _geo: GeoIP information extracted from client IP address (available for future use)\n\nReturns:\n StartSessionResponse with identity info and access tokens\n\nRaises:\n 404: Game not found\n 422: Invalid request (missing required fields, invalid UUID format)", "operationId": "start_session_v1_client_sessions_post", "requestBody": { "content": { diff --git a/docs/api/reference/accounts.md b/docs/api/reference/accounts.md index 1977503..03f1018 100644 --- a/docs/api/reference/accounts.md +++ b/docs/api/reference/accounts.md @@ -76,7 +76,7 @@ slug: Mapped[str] = mapped_column(String, nullable=False, unique=True, index=Tru ####### `leadr.accounts.adapters.orm.AccountORM.status` ```python -status: Mapped[AccountStatusEnum] = mapped_column(Enum(AccountStatusEnum, name='account_status', native_enum=True, values_callable=(lambda x: [(e.value) for e in x])), nullable=False, default=(AccountStatusEnum.ACTIVE), server_default='active') +status: Mapped[AccountStatusEnum] = mapped_column(Enum(AccountStatusEnum, name='account_status', native_enum=True, values_callable=(lambda x: [(e.value) for e in x])), nullable=False, default=(AccountStatusEnum.ACTIVE), server_default='active', index=True) ``` ####### `leadr.accounts.adapters.orm.AccountORM.updated_at` @@ -182,7 +182,7 @@ id: Mapped[uuid_pk] ####### `leadr.accounts.adapters.orm.UserORM.is_owner` ```python -is_owner: Mapped[bool] = mapped_column(nullable=False, default=False, server_default='false') +is_owner: Mapped[bool] = mapped_column(nullable=False, default=False, server_default='false', index=True) ``` ####### `leadr.accounts.adapters.orm.UserORM.status` @@ -194,7 +194,7 @@ status: Mapped[UserStatusEnum] = mapped_column(Enum(UserStatusEnum, name='user_s ####### `leadr.accounts.adapters.orm.UserORM.super_admin` ```python -super_admin: Mapped[bool] = mapped_column(nullable=False, default=False, server_default='false') +super_admin: Mapped[bool] = mapped_column(nullable=False, default=False, server_default='false', index=True) ``` ####### `leadr.accounts.adapters.orm.UserORM.updated_at` diff --git a/docs/api/reference/auth.md b/docs/api/reference/auth.md index 81176e9..a8ea0bf 100644 --- a/docs/api/reference/auth.md +++ b/docs/api/reference/auth.md @@ -1220,7 +1220,7 @@ No authentication is required (the refresh token itself is the credential). ###### `leadr.auth.api.client_routes.start_session` ```python -start_session(session_request, identity_service) +start_session(session_request, identity_service, _geo) ``` Start a new identity session for a game client. @@ -1231,10 +1231,14 @@ the device record and creates a new identity session. No authentication is required to call this endpoint (it IS the authentication). +The \_geo parameter triggers GeoIP lookup for this endpoint. Geo data is +available for future use via \_geo.timezone, \_geo.country, \_geo.city. + **Parameters:** - **session_request** ([StartSessionRequest](#leadr.auth.api.client_schemas.StartSessionRequest)) – Session start request with game_id and fingerprint - **identity_service** ([IdentityServiceDep](./auth.md#leadr.auth.services.dependencies.IdentityServiceDep)) – IdentityService dependency (handles device and identity creation) +- **\_geo** ([GeoInfoDep](./common.md#leadr.common.dependencies.GeoInfoDep)) – GeoIP information extracted from client IP address (available for future use) **Returns:** @@ -4097,10 +4101,13 @@ and repository layer. - [**list_all**](#leadr.auth.services.api_key_service.APIKeyService.list_all) – List all non-deleted entities. - [**list_api_keys**](#leadr.auth.services.api_key_service.APIKeyService.list_api_keys) – List API keys for an account with optional filters and pagination. - [**record_usage**](#leadr.auth.services.api_key_service.APIKeyService.record_usage) – Record that an API key was used at a specific time. +- [**record_usage_async**](#leadr.auth.services.api_key_service.APIKeyService.record_usage_async) – Record API key usage asynchronously (for background tasks). - [**revoke_api_key**](#leadr.auth.services.api_key_service.APIKeyService.revoke_api_key) – Revoke an API key, preventing further use. +- [**should_update_usage**](#leadr.auth.services.api_key_service.APIKeyService.should_update_usage) – Check if last_used_at needs updating based on configurable threshold. - [**soft_delete**](#leadr.auth.services.api_key_service.APIKeyService.soft_delete) – Soft-delete an entity and return it before deletion. - [**update_api_key_status**](#leadr.auth.services.api_key_service.APIKeyService.update_api_key_status) – Update the status of an API key. - [**validate_api_key**](#leadr.auth.services.api_key_service.APIKeyService.validate_api_key) – Validate an API key and return the domain entity if valid. +- [**validate_api_key_with_user**](#leadr.auth.services.api_key_service.APIKeyService.validate_api_key_with_user) – Validate an API key and return both the key and associated user. **Attributes:** @@ -4343,6 +4350,21 @@ also be called explicitly if needed. - [EntityNotFoundError](./common.md#leadr.common.domain.exceptions.EntityNotFoundError) – If the key doesn't exist. +####### `leadr.auth.services.api_key_service.APIKeyService.record_usage_async` + +```python +record_usage_async(key_id) +``` + +Record API key usage asynchronously (for background tasks). + +This method is designed to be called as a background task. It silently +handles missing keys to avoid errors in background processing. + +**Parameters:** + +- **key_id** ([APIKeyID](./common.md#leadr.common.domain.ids.APIKeyID)) – The ID of the API key that was used. + ####### `leadr.auth.services.api_key_service.APIKeyService.repository` ```python @@ -4369,6 +4391,27 @@ Revoke an API key, preventing further use. - [EntityNotFoundError](./common.md#leadr.common.domain.exceptions.EntityNotFoundError) – If the key doesn't exist. +####### `leadr.auth.services.api_key_service.APIKeyService.should_update_usage` + +```python +should_update_usage(api_key) +``` + +Check if last_used_at needs updating based on configurable threshold. + +Returns True if: + +- last_used_at is None (never used before), or +- last_used_at is older than API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS + +**Parameters:** + +- **api_key** ([APIKey](#leadr.auth.domain.api_key.APIKey)) – The API key to check. + +**Returns:** + +- [bool](#bool) – True if usage should be updated, False otherwise. + ####### `leadr.auth.services.api_key_service.APIKeyService.soft_delete` ```python @@ -4427,7 +4470,10 @@ Performs the following checks: 1. Verifies the hash matches 1. Checks if key is active (not revoked) 1. Checks if key is not expired -1. Records usage timestamp if valid + +Note: This method does NOT update last_used_at. The caller should check +should_update_usage() and schedule record_usage_async() via background task +if needed. **Parameters:** @@ -4448,6 +4494,48 @@ Performs the following checks: +####### `leadr.auth.services.api_key_service.APIKeyService.validate_api_key_with_user` + +```python +validate_api_key_with_user(plain_key) +``` + +Validate an API key and return both the key and associated user. + +This method performs a single JOIN query to retrieve both the API key +and user in one database round-trip, reducing auth latency. + +Performs the following checks: + +1. Extracts prefix and looks up key + user in database via JOIN +1. Verifies the hash matches +1. Checks if key is active (not revoked) +1. Checks if key is not expired + +Note: This method does NOT update last_used_at. The caller should check +should_update_usage() and schedule record_usage_async() via background task +if needed. + +**Parameters:** + +- **plain_key** ([str](#str)) – The plain API key string to validate. + +**Returns:** + +- [tuple](#tuple)\[[APIKey](#leadr.auth.domain.api_key.APIKey), [User](./accounts.md#leadr.accounts.domain.user.User)\] | None – A tuple of (APIKey, User) if valid, None otherwise. + +
+Example + +> > > result = await service.validate_api_key_with_user("ldr_abc123...") +> > > if result: +> > > ... api_key, user = result +> > > ... print(f"Valid key for user {user.email}") +> > > ... else: +> > > ... print("Invalid or expired key") + +
+ ##### `leadr.auth.services.dependencies` Auth service dependency injection factories. @@ -5715,6 +5803,7 @@ API Key repository for managing API key persistence. - [**filter**](./auth.md#leadr.auth.services.repositories.APIKeyRepository.filter) – Filter API keys by account and optional criteria with pagination. - [**get_by_id**](#leadr.auth.services.repositories.APIKeyRepository.get_by_id) – Get an entity by its ID. - [**get_by_prefix**](#leadr.auth.services.repositories.APIKeyRepository.get_by_prefix) – Get API key by prefix, returns None if not found or soft-deleted. +- [**get_by_prefix_with_user**](#leadr.auth.services.repositories.APIKeyRepository.get_by_prefix_with_user) – Get API key and associated user in a single query. - [**update**](./auth.md#leadr.auth.services.repositories.APIKeyRepository.update) – Update an existing entity in the database. **Attributes:** @@ -5827,6 +5916,25 @@ get_by_prefix(key_prefix) Get API key by prefix, returns None if not found or soft-deleted. +####### `leadr.auth.services.repositories.APIKeyRepository.get_by_prefix_with_user` + +```python +get_by_prefix_with_user(key_prefix) +``` + +Get API key and associated user in a single query. + +This method performs a JOIN between api_keys and users tables to retrieve +both entities in one database round-trip, reducing auth latency. + +**Parameters:** + +- **key_prefix** ([str](#str)) – The API key prefix to search for. + +**Returns:** + +- [tuple](#tuple)\[[APIKey](#leadr.auth.domain.api_key.APIKey), [User](./accounts.md#leadr.accounts.domain.user.User)\] | None – A tuple of (APIKey, User) if found and neither soft-deleted, None otherwise. + ####### `leadr.auth.services.repositories.APIKeyRepository.session` ```python diff --git a/docs/api/reference/boards.md b/docs/api/reference/boards.md index 3b07877..1e86dcf 100644 --- a/docs/api/reference/boards.md +++ b/docs/api/reference/boards.md @@ -96,7 +96,7 @@ created_at: Mapped[timestamp] ####### `leadr.boards.adapters.orm.BoardORM.created_from_template_id` ```python -created_from_template_id: Mapped[UUID | None] = mapped_column(nullable=True, default=None) +created_from_template_id: Mapped[UUID | None] = mapped_column(nullable=True, default=None, index=True) ``` ####### `leadr.boards.adapters.orm.BoardORM.deleted_at` @@ -144,13 +144,13 @@ id: Mapped[uuid_pk] ####### `leadr.boards.adapters.orm.BoardORM.is_active` ```python -is_active: Mapped[bool] = mapped_column(Boolean, nullable=False) +is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, index=True) ``` ####### `leadr.boards.adapters.orm.BoardORM.is_published` ```python -is_published: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=(sa.text('true'))) +is_published: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=(sa.text('true')), index=True) ``` ####### `leadr.boards.adapters.orm.BoardORM.keep_strategy` @@ -607,7 +607,7 @@ id: Mapped[uuid_pk] ####### `leadr.boards.adapters.orm.BoardTemplateORM.is_active` ```python -is_active: Mapped[bool] = mapped_column(Boolean, nullable=False) +is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, index=True) ``` ####### `leadr.boards.adapters.orm.BoardTemplateORM.is_published` @@ -637,7 +637,7 @@ name_template: Mapped[str | None] = mapped_column(String, nullable=True, default ####### `leadr.boards.adapters.orm.BoardTemplateORM.next_run_at` ```python -next_run_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) +next_run_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) ``` ####### `leadr.boards.adapters.orm.BoardTemplateORM.repeat_interval` diff --git a/docs/api/reference/common.md b/docs/api/reference/common.md index ab1bab8..89ef96d 100644 --- a/docs/api/reference/common.md +++ b/docs/api/reference/common.md @@ -858,9 +858,15 @@ handles cleanup and rollback on exceptions. Shared FastAPI dependencies for the application. +**Functions:** + +- [**get_geo_info**](#leadr.common.dependencies.get_geo_info) – FastAPI dependency to get GeoIP info for the request. + **Attributes:** - [**DatabaseSession**](./common.md#leadr.common.dependencies.DatabaseSession) – +- [**GeoInfoDep**](./common.md#leadr.common.dependencies.GeoInfoDep) – +- [**logger**](./common.md#leadr.common.dependencies.logger) – ##### `leadr.common.dependencies.DatabaseSession` @@ -868,6 +874,52 @@ Shared FastAPI dependencies for the application. DatabaseSession = Annotated[AsyncSession, Depends(get_db)] ``` +##### `leadr.common.dependencies.GeoInfoDep` + +```python +GeoInfoDep = Annotated[GeoInfo, Depends(get_geo_info)] +``` + +##### `leadr.common.dependencies.get_geo_info` + +```python +get_geo_info(request) +``` + +FastAPI dependency to get GeoIP info for the request. + +This dependency performs GeoIP lookup for the client's IP address. It's +designed to be used on specific endpoints (like score submissions) rather +than globally as middleware. + +The dependency gracefully handles failures - if GeoIP lookup fails for any +reason, it returns a GeoInfo with all None fields. + +**Parameters:** + +- **request** ([Request](#fastapi.Request)) – The incoming FastAPI request. + +**Returns:** + +- [GeoInfo](./common.md#leadr.common.geoip.GeoInfo) – GeoInfo with timezone, country, and city (all may be None). + +
+Example + +@router.post("/scores") +async def submit_score(geo: GeoInfoDep): +timezone = geo.timezone +country = geo.country +city = geo.city + +
+ +##### `leadr.common.dependencies.logger` + +```python +logger = logging.getLogger(__name__) +``` + #### `leadr.common.domain` **Modules:** diff --git a/docs/api/reference/config.md b/docs/api/reference/config.md index a354b2c..1524762 100644 --- a/docs/api/reference/config.md +++ b/docs/api/reference/config.md @@ -67,6 +67,7 @@ field names (case-sensitive). - [**ANTICHEAT_RATE_LIMIT_TIER_C**](#leadr.config.CommonSettings.ANTICHEAT_RATE_LIMIT_TIER_C) ([int](#int)) – - [**ANTICHEAT_VELOCITY_THRESHOLD_SECONDS**](#leadr.config.CommonSettings.ANTICHEAT_VELOCITY_THRESHOLD_SECONDS) ([float](#float)) – - [**API_KEY_SECRET**](#leadr.config.CommonSettings.API_KEY_SECRET) ([str](#str)) – +- [**API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS**](#leadr.config.CommonSettings.API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS) ([int](#int)) – - [**API_PREFIX**](#leadr.config.CommonSettings.API_PREFIX) ([str](#str)) – - [**APP**](./config.md#leadr.config.CommonSettings.APP) ([str](#str)) – - [**BACKGROUND_TASK_EXPIRE_INTERVAL**](#leadr.config.CommonSettings.BACKGROUND_TASK_EXPIRE_INTERVAL) ([int](#int)) – @@ -196,6 +197,12 @@ ANTICHEAT_VELOCITY_THRESHOLD_SECONDS: float = Field(default=2.0, description='Mi API_KEY_SECRET: str = Field(default='your-super-secret-api-key-pepper-change-in-production', description='Secret pepper for API key hashing. MUST be changed in production.') ``` +##### `leadr.config.CommonSettings.API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS` + +```python +API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS: int = Field(default=300, description='Only update API key last_used_at if older than this (default: 5 minutes)') +``` + ##### `leadr.config.CommonSettings.API_PREFIX` ```python @@ -577,6 +584,7 @@ This is the default settings class used when ENV != 'TEST'. - [**ANTICHEAT_RATE_LIMIT_TIER_C**](#leadr.config.Settings.ANTICHEAT_RATE_LIMIT_TIER_C) ([int](#int)) – - [**ANTICHEAT_VELOCITY_THRESHOLD_SECONDS**](#leadr.config.Settings.ANTICHEAT_VELOCITY_THRESHOLD_SECONDS) ([float](#float)) – - [**API_KEY_SECRET**](#leadr.config.Settings.API_KEY_SECRET) ([str](#str)) – +- [**API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS**](#leadr.config.Settings.API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS) ([int](#int)) – - [**API_PREFIX**](#leadr.config.Settings.API_PREFIX) ([str](#str)) – - [**APP**](#leadr.config.Settings.APP) ([str](#str)) – - [**BACKGROUND_TASK_EXPIRE_INTERVAL**](#leadr.config.Settings.BACKGROUND_TASK_EXPIRE_INTERVAL) ([int](#int)) – @@ -664,6 +672,7 @@ Test-specific overrides can be added here. - [**ANTICHEAT_RATE_LIMIT_TIER_C**](#leadr.config.TestSettings.ANTICHEAT_RATE_LIMIT_TIER_C) ([int](#int)) – - [**ANTICHEAT_VELOCITY_THRESHOLD_SECONDS**](#leadr.config.TestSettings.ANTICHEAT_VELOCITY_THRESHOLD_SECONDS) ([float](#float)) – - [**API_KEY_SECRET**](#leadr.config.TestSettings.API_KEY_SECRET) ([str](#str)) – +- [**API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS**](#leadr.config.TestSettings.API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS) ([int](#int)) – - [**API_PREFIX**](#leadr.config.TestSettings.API_PREFIX) ([str](#str)) – - [**APP**](./config.md#leadr.config.TestSettings.APP) ([str](#str)) – - [**BACKGROUND_TASK_EXPIRE_INTERVAL**](#leadr.config.TestSettings.BACKGROUND_TASK_EXPIRE_INTERVAL) ([int](#int)) – @@ -793,6 +802,12 @@ ANTICHEAT_VELOCITY_THRESHOLD_SECONDS: float = Field(default=2.0, description='Mi API_KEY_SECRET: str = Field(default='your-super-secret-api-key-pepper-change-in-production', description='Secret pepper for API key hashing. MUST be changed in production.') ``` +##### `leadr.config.TestSettings.API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS` + +```python +API_KEY_USAGE_UPDATE_THRESHOLD_SECONDS: int = Field(default=300, description='Only update API key last_used_at if older than this (default: 5 minutes)') +``` + ##### `leadr.config.TestSettings.API_PREFIX` ```python diff --git a/docs/api/reference/scores.md b/docs/api/reference/scores.md index 2d15185..0460b3c 100644 --- a/docs/api/reference/scores.md +++ b/docs/api/reference/scores.md @@ -995,7 +995,7 @@ client_router = APIRouter() ###### `leadr.scores.api.score_routes.create_score_client` ```python -create_score_client(score_request, request, service, board_service, background_tasks, auth, identity_service, pre_create_hook, post_create_hook) +create_score_client(score_request, geo, service, board_service, background_tasks, auth, identity_service, pre_create_hook, post_create_hook) ``` Create a new score (Client API). @@ -1006,7 +1006,7 @@ are automatically derived from the authenticated session. **Parameters:** - **score_request** ([ScoreClientCreateRequest](#leadr.scores.api.score_schemas.ScoreClientCreateRequest)) – Score creation details including board_id, player_name, and value. -- **request** ([Request](#fastapi.Request)) – FastAPI request object for accessing geo data. +- **geo** ([GeoInfoDep](./common.md#leadr.common.dependencies.GeoInfoDep)) – GeoIP information extracted from client IP address. - **service** ([ScoreServiceDep](./scores.md#leadr.scores.services.dependencies.ScoreServiceDep)) – Injected score service dependency. - **board_service** ([BoardServiceDep](./boards.md#leadr.boards.services.dependencies.BoardServiceDep)) – Injected board service for board lookup. - **background_tasks** ([BackgroundTasks](#fastapi.BackgroundTasks)) – FastAPI background tasks for async metadata updates.