diff --git a/public/partner-products.openapi.json b/public/partner-products.openapi.json
index f76ceb58..e18403b2 100644
--- a/public/partner-products.openapi.json
+++ b/public/partner-products.openapi.json
@@ -23,7 +23,7 @@
"/api/v1/shops/{shopId}/products": {
"put": {
"summary": "Batch upsert products (Partner API)",
- "description": "Creates new products or updates existing ones for a shop in a single batch call,\nusing API key authentication. This endpoint is intended for partner shops \u2014 shops\nthat have been granted partner status and have an API key configured. It does **not**\nuse Cognito JWT authentication.\n\nThe request body is an array of `PutProductData` objects. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue as an upsert command.\nWhen the queued command is later ingested:\n- **Existing product** \u2014 only `state` and `price` are updated (other fields are ignored).\n- **New product** \u2014 a full product is created using all provided fields.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all upserts were\naccepted for asynchronous processing.\n",
+ "description": "Creates new products or updates existing ones for a shop in a single batch call,\nusing bearer authentication. This endpoint is intended for partner shops and accepts:\n- a Cognito bearer token for the partner user linked to the shop, or\n- an Aura Historia access token owned by that partner user.\n\nAura Historia access tokens on this endpoint must include the `products:write` scope.\n\nThe request body is an array of `PutProductData` objects. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue as an upsert command.\nWhen the queued command is later ingested:\n- **New product** \u2014 a full product is created using all provided fields. Omitting\n `title`, `url`, or `state` causes the backend to fall back to an empty title,\n a placeholder URL, and `LISTED` respectively.\n- **Existing product** \u2014 the backend applies `price`, `priceEstimateMin`,\n `priceEstimateMax`, `state`, `url`, `images`, `auctionStart`, and `auctionEnd`.\n `title`, `description`, `sellerName`, `structuredAddress`, and `geoAddress`\n are ignored on the update path.\n- On the update path, omitting or sending `null` for `price`, `priceEstimateMin`,\n `priceEstimateMax`, `url`, `auctionStart`, or `auctionEnd` leaves the stored value\n unchanged.\n- On the update path, `images` always replaces the stored image set; omitting\n `images` or sending `null` is treated as an empty list and therefore clears all\n stored images.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all upserts were\naccepted for asynchronous processing.\n",
"operationId": "putPartnerProducts",
"tags": [
"Products"
@@ -43,7 +43,10 @@
],
"security": [
{
- "PartnerApiKeyAuth": []
+ "BearerAuth": []
+ },
+ {
+ "AccessTokenAuth": []
}
],
"requestBody": {
@@ -84,11 +87,34 @@
]
},
"update_existing": {
- "summary": "Update an existing product's state",
+ "summary": "Update an existing product while leaving the current price unchanged",
"value": [
{
"shopsProductId": "baroque-violin-001",
- "state": "SOLD"
+ "state": "SOLD",
+ "priceEstimateMin": {
+ "currency": "EUR",
+ "amount": 4200
+ },
+ "priceEstimateMax": {
+ "currency": "EUR",
+ "amount": 4800
+ },
+ "url": "https://my-shop.com/products/baroque-violin?status=sold",
+ "images": [
+ "https://my-shop.com/images/violin-1.jpg",
+ "https://my-shop.com/images/violin-detail.jpg"
+ ],
+ "auctionEnd": "2025-05-10T12:30:00Z"
+ }
+ ]
+ },
+ "clear_images": {
+ "summary": "Clear an existing product's images",
+ "value": [
+ {
+ "shopsProductId": "baroque-violin-001",
+ "images": null
}
]
}
@@ -159,32 +185,31 @@
}
},
"401": {
- "description": "Unauthorized \u2014 the `x-api-key` header is missing, malformed, or the API key does not\nmatch the key stored for the shop.\n",
+ "description": "Unauthorized \u2014 the bearer token is missing, invalid, expired, or the referenced\nAura Historia access token no longer exists.\n",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
},
"examples": {
- "missing_header": {
- "summary": "x-api-key header is missing or empty",
+ "missing_authorization": {
+ "summary": "Authorization header is missing",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "BAD_HEADER_VALUE",
+ "error": "UNAUTHORIZED",
"source": {
- "field": "x-api-key",
+ "field": "Authorization",
"sourceType": "header"
- },
- "detail": "Missing or empty 'x-api-key' header."
+ }
}
},
- "api_key_mismatch": {
- "summary": "API key does not match the shop's stored key",
+ "access_token_not_found": {
+ "summary": "Aura Historia access token is unknown or expired",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "PARTNER_SHOP_API_KEY_MISMATCH"
+ "error": "ACCESS_TOKEN_NOT_FOUND"
}
}
}
@@ -192,7 +217,7 @@
}
},
"403": {
- "description": "Forbidden \u2014 the shop exists but has not been granted partner status",
+ "description": "Forbidden \u2014 the authenticated caller is not allowed to ingest products for this shop",
"content": {
"application/problem+json": {
"schema": {
@@ -255,7 +280,7 @@
},
"post": {
"summary": "Batch create products (Partner API)",
- "description": "Creates one or more products for a shop using API key authentication.\nThis endpoint is intended for partner shops \u2014 shops that have been granted partner status\nand have an API key configured. It does **not** use Cognito JWT authentication.\n\nThe request body is an array of `PostProductData` objects. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all products were\naccepted for asynchronous processing.\n",
+ "description": "Creates one or more products for a shop using bearer authentication.\nThis endpoint is intended for partner shops and accepts:\n- a Cognito bearer token for the partner user linked to the shop, or\n- an Aura Historia access token owned by that partner user.\n\nAura Historia access tokens on this endpoint must include the `products:write` scope.\n\nThe request body is an array of `PostProductData` objects. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all products were\naccepted for asynchronous processing.\n",
"operationId": "postPartnerProducts",
"tags": [
"Products"
@@ -275,7 +300,10 @@
],
"security": [
{
- "PartnerApiKeyAuth": []
+ "BearerAuth": []
+ },
+ {
+ "AccessTokenAuth": []
}
],
"requestBody": {
@@ -378,32 +406,31 @@
}
},
"401": {
- "description": "Unauthorized \u2014 the `x-api-key` header is missing, malformed, or the API key does not\nmatch the key stored for the shop.\n",
+ "description": "Unauthorized \u2014 the bearer token is missing, invalid, expired, or the referenced\nAura Historia access token no longer exists.\n",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
},
"examples": {
- "missing_header": {
- "summary": "x-api-key header is missing or empty",
+ "missing_authorization": {
+ "summary": "Authorization header is missing",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "BAD_HEADER_VALUE",
+ "error": "UNAUTHORIZED",
"source": {
- "field": "x-api-key",
+ "field": "Authorization",
"sourceType": "header"
- },
- "detail": "Missing or empty 'x-api-key' header."
+ }
}
},
- "api_key_mismatch": {
- "summary": "API key does not match the shop's stored key",
+ "access_token_not_found": {
+ "summary": "Aura Historia access token is unknown or expired",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "PARTNER_SHOP_API_KEY_MISMATCH"
+ "error": "ACCESS_TOKEN_NOT_FOUND"
}
}
}
@@ -411,7 +438,7 @@
}
},
"403": {
- "description": "Forbidden \u2014 the shop exists but has not been granted partner status",
+ "description": "Forbidden \u2014 the authenticated caller is not allowed to ingest products for this shop",
"content": {
"application/problem+json": {
"schema": {
@@ -474,7 +501,7 @@
},
"patch": {
"summary": "Batch update products (Partner API)",
- "description": "Updates one or more existing products for a shop using API key authentication.\nThis endpoint is intended for partner shops \u2014 shops that have been granted partner status\nand have an API key configured. It does **not** use Cognito JWT authentication.\n\nThe request body is an array of `PatchProductData` objects. Only the fields provided in\neach entry are updated; omitted optional fields are left unchanged. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all updates were\naccepted for asynchronous processing. Because persistence happens asynchronously, acceptance\ndoes not guarantee that the referenced product currently exists or that the update will\nlater succeed.\n",
+ "description": "Updates one or more existing products for a shop using bearer authentication.\nThis endpoint is intended for partner shops and accepts:\n- a Cognito bearer token for the partner user linked to the shop, or\n- an Aura Historia access token owned by that partner user.\n\nAura Historia access tokens on this endpoint must include the `products:write` scope.\n\nThe request body is an array of `PatchProductData` objects. Only the fields provided in\neach entry are updated; omitted optional fields are left unchanged. Each entry is forwarded\nindividually to the asynchronous partner-product ingestion queue.\n\nThe response returns HTTP 202 with an array containing only the `shopsProductId` values\nthat failed to be forwarded to the queue. An empty array indicates that all updates were\naccepted for asynchronous processing. Because persistence happens asynchronously, acceptance\ndoes not guarantee that the referenced product currently exists or that the update will\nlater succeed.\n",
"operationId": "patchPartnerProducts",
"tags": [
"Products"
@@ -494,7 +521,10 @@
],
"security": [
{
- "PartnerApiKeyAuth": []
+ "BearerAuth": []
+ },
+ {
+ "AccessTokenAuth": []
}
],
"requestBody": {
@@ -588,32 +618,31 @@
}
},
"401": {
- "description": "Unauthorized \u2014 the `x-api-key` header is missing, malformed, or the API key does not\nmatch the key stored for the shop.\n",
+ "description": "Unauthorized \u2014 the bearer token is missing, invalid, expired, or the referenced\nAura Historia access token no longer exists.\n",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
},
"examples": {
- "missing_header": {
- "summary": "x-api-key header is missing or empty",
+ "missing_authorization": {
+ "summary": "Authorization header is missing",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "BAD_HEADER_VALUE",
+ "error": "UNAUTHORIZED",
"source": {
- "field": "x-api-key",
+ "field": "Authorization",
"sourceType": "header"
- },
- "detail": "Missing or empty 'x-api-key' header."
+ }
}
},
- "api_key_mismatch": {
- "summary": "API key does not match the shop's stored key",
+ "access_token_not_found": {
+ "summary": "Aura Historia access token is unknown or expired",
"value": {
"status": 401,
"title": "Unauthorized",
- "error": "PARTNER_SHOP_API_KEY_MISMATCH"
+ "error": "ACCESS_TOKEN_NOT_FOUND"
}
}
}
@@ -621,7 +650,7 @@
}
},
"403": {
- "description": "Forbidden \u2014 the shop exists but has not been granted partner status",
+ "description": "Forbidden \u2014 the authenticated caller is not allowed to ingest products for this shop",
"content": {
"application/problem+json": {
"schema": {
@@ -690,13 +719,13 @@
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
- "description": "Cognito JWT token.\nThe token is used for authenticating and authorizing users as well as personalizing responses.\n"
+ "description": "Cognito JWT bearer token.\nThis scheme is used for authenticated user routes and for partner routes that accept Cognito user tokens.\n"
},
- "PartnerApiKeyAuth": {
- "type": "apiKey",
- "in": "header",
- "name": "x-api-key",
- "description": "API key for partner shops. Issued to shops that have been granted partner status.\nThe key must be provided in the `x-api-key` request header.\nThis scheme is separate from user authentication (Cognito JWT) and is used\nby partner shops for programmatic product ingestion, WooCommerce webhook delivery,\nand selected partner-authorized shop operations.\n"
+ "AccessTokenAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "opaque",
+ "description": "Aura Historia bearer access token created through `/api/v1/me/access-tokens`.\nTokens are sent as standard bearer authorization headers.\nSelected partner routes accept this scheme instead of the removed partner API-key flow.\n"
}
},
"schemas": {
@@ -3311,17 +3340,381 @@
}
}
},
- "PartnerShopApiKeyResponse": {
+ "AccessTokenScopeData": {
+ "type": "string",
+ "enum": [
+ "shops:manage",
+ "products:write"
+ ],
+ "description": "Aura Historia access-token scope string.\n- `shops:manage`: reserved for shop-management flows\n- `products:write`: required for partner batch product ingestion\n",
+ "example": "products:write"
+ },
+ "AccessTokenTypeData": {
+ "type": "string",
+ "enum": [
+ "BEARER"
+ ],
+ "description": "Token type returned for Aura Historia access tokens.",
+ "example": "BEARER"
+ },
+ "GetAccessTokenData": {
+ "type": "object",
+ "description": "Access-token read model.\n`POST /api/v1/me/access-tokens` returns the plaintext bearer token once;\nlist/get/update endpoints return the masked display value instead.\n",
+ "required": [
+ "accessTokenId",
+ "name",
+ "token",
+ "tokenType",
+ "created",
+ "updated"
+ ],
+ "properties": {
+ "accessTokenId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier of the access token.",
+ "example": "01970f22-2bf0-7000-8000-000000000001"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 128,
+ "description": "User-defined display name of the access token.",
+ "example": "Partner product sync"
+ },
+ "scope": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "uniqueItems": true,
+ "description": "Granted access-token scopes. Omitted when the token has no scopes.",
+ "example": [
+ "products:write"
+ ]
+ },
+ "token": {
+ "type": "string",
+ "description": "Plaintext or masked bearer token value.\nCreate responses return the full token once; subsequent reads return a masked value such as `aurahistoria_abcdefghijk_****`.\n",
+ "example": "aurahistoria_abcdefghijk_****"
+ },
+ "tokenType": {
+ "$ref": "#/components/schemas/AccessTokenTypeData"
+ },
+ "expiresAt": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "Optional absolute expiration timestamp in RFC 3339 format.",
+ "example": "2026-06-30T00:00:00Z"
+ },
+ "expiresIn": {
+ "type": "integer",
+ "minimum": 0,
+ "nullable": true,
+ "description": "Optional non-negative number of seconds remaining until expiry.",
+ "example": 2851200
+ },
+ "created": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Timestamp when the access token was created.",
+ "example": "2026-05-28T06:30:00Z"
+ },
+ "updated": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Timestamp when the access token metadata was last updated.",
+ "example": "2026-05-28T07:00:00Z"
+ }
+ }
+ },
+ "PostAccessTokenData": {
+ "type": "object",
+ "description": "Request body for creating an Aura Historia access token.",
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 128,
+ "description": "User-defined display name of the new access token.",
+ "example": "Partner product sync"
+ },
+ "scope": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "uniqueItems": true,
+ "description": "Optional scopes granted to the access token. Defaults to an empty set.",
+ "example": [
+ "products:write"
+ ]
+ },
+ "expiresAt": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "Optional expiration timestamp in RFC 3339 format.",
+ "example": "2026-06-30T00:00:00Z"
+ }
+ }
+ },
+ "PatchAccessTokenData": {
+ "type": "object",
+ "description": "Request body for updating access-token metadata.\nOptional fields that are omitted or set to `null` leave the stored value unchanged.\n",
+ "required": [
+ "accessTokenId"
+ ],
+ "properties": {
+ "accessTokenId": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Unique identifier of the access token to update.",
+ "example": "01970f22-2bf0-7000-8000-000000000001"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 128,
+ "nullable": true,
+ "description": "Optional replacement display name.",
+ "example": "Renamed partner sync token"
+ },
+ "scope": {
+ "type": "array",
+ "nullable": true,
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "uniqueItems": true,
+ "description": "Optional replacement scope set.",
+ "example": [
+ "products:write"
+ ]
+ },
+ "expiresAt": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "description": "Optional replacement expiration timestamp in RFC 3339 format.",
+ "example": "2026-07-31T00:00:00Z"
+ }
+ }
+ },
+ "OAuthClientMetadataRequestData": {
+ "type": "object",
+ "description": "Request body for creating OAuth client metadata.\n`redirect_uris` must contain at least one HTTPS redirect URI.\n",
+ "required": [
+ "client_name",
+ "redirect_uris"
+ ],
+ "properties": {
+ "client_name": {
+ "type": "string",
+ "description": "Display name of the OAuth client.",
+ "example": "Acceptance OAuth client"
+ },
+ "redirect_uris": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true,
+ "description": "Registered HTTPS redirect URIs for the OAuth client.",
+ "items": {
+ "type": "string",
+ "format": "uri"
+ },
+ "example": [
+ "https://client.example/callback"
+ ]
+ },
+ "scope": {
+ "type": "array",
+ "uniqueItems": true,
+ "description": "Optional allowed scopes for tokens issued to this OAuth client.",
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "example": [
+ "products:write"
+ ]
+ }
+ }
+ },
+ "OAuthClientMetadataPatchData": {
+ "type": "object",
+ "description": "Request body for updating OAuth client metadata.\nOmitted or `null` optional properties leave the stored value unchanged.\n",
+ "properties": {
+ "client_name": {
+ "type": "string",
+ "nullable": true,
+ "description": "Optional replacement display name for the OAuth client.",
+ "example": "Updated acceptance OAuth client"
+ },
+ "redirect_uris": {
+ "type": "array",
+ "nullable": true,
+ "minItems": 1,
+ "uniqueItems": true,
+ "description": "Optional replacement set of registered HTTPS redirect URIs.",
+ "items": {
+ "type": "string",
+ "format": "uri"
+ },
+ "example": [
+ "https://client.example/updated"
+ ]
+ },
+ "scope": {
+ "type": "array",
+ "nullable": true,
+ "uniqueItems": true,
+ "description": "Optional replacement set of allowed scopes for the OAuth client.",
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "example": [
+ "shops:manage"
+ ]
+ }
+ }
+ },
+ "OAuthClientMetadataResponseData": {
"type": "object",
- "description": "Response body returned after creating or overwriting a partner shop API key.",
+ "description": "OAuth client metadata read model.\n`client_secret` contains the plaintext secret only in the create response.\nList/get/update responses return a masked secret display value instead.\n",
"required": [
- "apiKey"
+ "client_id",
+ "client_secret",
+ "client_name",
+ "redirect_uris",
+ "scope",
+ "client_id_issued_at"
],
"properties": {
- "apiKey": {
+ "client_id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "UUIDv7 identifier of the OAuth client.",
+ "example": "01970f22-2bf0-7000-8000-000000000010"
+ },
+ "client_secret": {
+ "type": "string",
+ "description": "OAuth client secret.\nCreate responses return the plaintext secret once; later reads return a masked value such as `aurahistoria_oauth_client_secret_abcdefghijk_****`.\n",
+ "example": "aurahistoria_oauth_client_secret_abcdefghijk_****"
+ },
+ "client_name": {
"type": "string",
- "description": "Plaintext partner shop API key.\nReturned only when the key is created; subsequent verification uses the `x-api-key` request header.\n",
- "example": "aurahistoria_abcdefghijk_abcdefghijklmnopqrstuvwxyz1234567"
+ "description": "Display name of the OAuth client.",
+ "example": "Acceptance OAuth client"
+ },
+ "redirect_uris": {
+ "type": "array",
+ "uniqueItems": true,
+ "description": "Registered redirect URIs for the OAuth client.",
+ "items": {
+ "type": "string",
+ "format": "uri"
+ },
+ "example": [
+ "https://client.example/callback"
+ ]
+ },
+ "scope": {
+ "type": "array",
+ "uniqueItems": true,
+ "description": "Allowed scopes for tokens issued to this OAuth client.",
+ "items": {
+ "$ref": "#/components/schemas/AccessTokenScopeData"
+ },
+ "example": [
+ "products:write"
+ ]
+ },
+ "client_id_issued_at": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Unix timestamp (seconds) when the OAuth client was created and its client ID was issued.",
+ "example": 1748539200
+ }
+ }
+ },
+ "OAuthTokenResponseData": {
+ "type": "object",
+ "description": "Token response returned by `POST /api/v1/oauth/token` after a successful authorization code\nexchange (RFC 6749 \u00a75.1). The `access_token` is an Aura Historia bearer access token.\n`expires_in` is omitted (`null`) for non-expiring tokens.\n",
+ "required": [
+ "access_token",
+ "token_type",
+ "scope"
+ ],
+ "properties": {
+ "access_token": {
+ "type": "string",
+ "description": "The issued Aura Historia access token (plaintext bearer value).",
+ "example": "aurahistoria_abcdefghijk_verylongtokenvalue"
+ },
+ "token_type": {
+ "$ref": "#/components/schemas/AccessTokenTypeData"
+ },
+ "expires_in": {
+ "type": "integer",
+ "format": "int64",
+ "minimum": 0,
+ "nullable": true,
+ "description": "Seconds until the access token expires. `null` when the token does not expire.",
+ "example": null
+ },
+ "scope": {
+ "type": "string",
+ "description": "Space-separated list of scopes granted to the access token.",
+ "example": "products:write"
+ }
+ }
+ },
+ "OAuthIntrospectionResponseData": {
+ "type": "object",
+ "description": "Introspection response returned by `POST /api/v1/oauth/introspect` (RFC 7662).\nWhen `active` is `false` all other fields are omitted.\nWhen `active` is `true` the available token metadata is populated.\n",
+ "required": [
+ "active"
+ ],
+ "properties": {
+ "active": {
+ "type": "boolean",
+ "description": "Whether the token is currently active (not expired, not revoked, and known to this server).",
+ "example": true
+ },
+ "scope": {
+ "type": "string",
+ "description": "Space-separated list of scopes granted to the token. Present only when `active` is `true`.",
+ "example": "products:write"
+ },
+ "client_id": {
+ "type": "string",
+ "format": "uuid",
+ "description": "UUIDv7 OAuth client identifier that issued the token. Present only when `active` is `true` and the token was issued via the OAuth flow.",
+ "example": "01970f22-2bf0-7000-8000-000000000010"
+ },
+ "sub": {
+ "type": "string",
+ "format": "uuid",
+ "description": "Subject \u2014 the Aura Historia user ID who authorized the token. Present only when `active` is `true`.",
+ "example": "0196580c-e4ca-723f-a7e0-1a73588380f1"
+ },
+ "token_type": {
+ "type": "string",
+ "description": "Token type. Always `\"Bearer\"` when `active` is `true`.",
+ "example": "Bearer"
+ },
+ "exp": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Unix timestamp (seconds) when the token expires. Present only when `active` is `true` and the token has an expiry.",
+ "example": 1780000000
+ },
+ "iat": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Unix timestamp (seconds) when the token was issued. Present only when `active` is `true` and the issue time is known.",
+ "example": 1748400000
}
}
},
@@ -4652,6 +5045,13 @@
"description": "Display name of the shop referenced by the partner application.",
"example": "Tech Store"
},
+ "image": {
+ "type": "string",
+ "format": "uri",
+ "nullable": true,
+ "description": "Optional shop logo URL included with partner-application notifications when the related\napplication or linked existing shop has an image configured.\nAbsent when no shop logo URL is available.\n",
+ "example": "https://tech-store.example/logo.png"
+ },
"partnerApplicationPayload": {
"$ref": "#/components/schemas/PartnerApplicationPayloadData"
}
@@ -4772,7 +5172,7 @@
},
"PostProductData": {
"type": "object",
- "description": "Data for creating a single product via the partner batch-create endpoint.\nwhen omitted from the request.\n",
+ "description": "Data for creating a single product via the partner batch-create endpoint.\n",
"required": [
"shopsProductId",
"title",
@@ -4977,7 +5377,7 @@
},
"PutProductData": {
"type": "object",
- "description": "Data for upserting a single product via the partner batch-upsert endpoint.\nOnly `shopsProductId` is required. All other fields are optional.\n\nDuring later asynchronous ingestion:\n- If the product **does not yet exist**, it is created using all provided fields.\n Omitting `title`, `url`, or `state` will result in placeholder defaults being applied\n internally (empty title, a placeholder URL, and `LISTED` state respectively).\n- If the product **already exists**, only `state` and `price` are updated; all other\n fields are ignored for the update path.\n\nto `UNKNOWN` when omitted.\n",
+ "description": "Data for upserting a single product via the partner batch-upsert endpoint.\nOnly `shopsProductId` is required. All other fields are optional.\n\nDuring later asynchronous ingestion:\n- If the product **does not yet exist**, it is created using all provided fields.\n Omitting `title`, `url`, or `state` will result in placeholder defaults being applied\n internally (empty title, a placeholder URL, and `LISTED` state respectively).\n- If the product **already exists**, `price`, `priceEstimateMin`,\n `priceEstimateMax`, `state`, `url`, `images`, `auctionStart`, and `auctionEnd`\n participate in the update path.\n- On the update path, omitting or sending `null` for `price`, `priceEstimateMin`,\n `priceEstimateMax`, `url`, `auctionStart`, or `auctionEnd` leaves the stored value\n unchanged.\n- On the update path, omitting `images` or sending `null` is treated as an empty list\n and therefore clears all stored images.\n",
"required": [
"shopsProductId"
],
@@ -5012,7 +5412,7 @@
}
],
"nullable": true,
- "description": "Optional asking price for the product. Applied on both create and update paths."
+ "description": "Optional asking price for the product. Applied on create and on update when a non-null value is provided. Omit or send `null` to leave the current stored price unchanged."
},
"priceEstimateMin": {
"allOf": [
@@ -5021,7 +5421,7 @@
}
],
"nullable": true,
- "description": "Optional lower bound of the estimated price range. Used only when creating a new product."
+ "description": "Optional lower bound of the estimated price range. Applied on both create and update paths. Omit or send `null` to leave the current stored lower bound unchanged."
},
"priceEstimateMax": {
"allOf": [
@@ -5030,7 +5430,7 @@
}
],
"nullable": true,
- "description": "Optional upper bound of the estimated price range. Used only when creating a new product."
+ "description": "Optional upper bound of the estimated price range. Applied on both create and update paths. Omit or send `null` to leave the current stored upper bound unchanged."
},
"state": {
"allOf": [
@@ -5045,7 +5445,7 @@
"type": "string",
"format": "uri",
"nullable": true,
- "description": "URL to the product on the shop's website. Used only when creating a new product.",
+ "description": "URL to the product on the shop's website. Applied on both create and update paths. Omit or send `null` to leave the current stored URL unchanged.",
"example": "https://my-shop.com/products/baroque-violin"
},
"images": {
@@ -5055,7 +5455,7 @@
"format": "uri"
},
"nullable": true,
- "description": "List of image URLs for the product. Used only when creating a new product.",
+ "description": "List of image URLs for the product. Applied on both create and update paths. On update, the provided array replaces the stored image set; omitting `images` or sending `null` is treated as an empty list and therefore clears all stored images.",
"example": [
"https://my-shop.com/images/violin-1.jpg",
"https://my-shop.com/images/violin-2.jpg"
@@ -5065,14 +5465,14 @@
"type": "string",
"format": "date-time",
"nullable": true,
- "description": "RFC3339 timestamp of when the auction for this product starts.\nOnly relevant for auction-house shop types. Used only when creating a new product.\n",
+ "description": "RFC3339 timestamp of when the auction for this product starts.\nOnly relevant for auction-house shop types. Applied on both create and update paths.\nOmit or send `null` to leave the current stored start timestamp unchanged.\n",
"example": "2025-05-01T12:00:00Z"
},
"auctionEnd": {
"type": "string",
"format": "date-time",
"nullable": true,
- "description": "RFC3339 timestamp of when the auction for this product ends.\nOnly relevant for auction-house shop types. Used only when creating a new product.\n",
+ "description": "RFC3339 timestamp of when the auction for this product ends.\nOnly relevant for auction-house shop types. Applied on both create and update paths.\nOmit or send `null` to leave the current stored end timestamp unchanged.\n",
"example": "2025-05-10T12:00:00Z"
},
"sellerName": {
diff --git a/sonar-project.properties b/sonar-project.properties
index df47518c..6e52ccc4 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,4 +1,4 @@
-sonar.projectKey=blitzfilter_blitzfilter-frontend
+sonar.projectKey=aura-historia_webapp
sonar.organization=aura-historia
sonar.host.url=https://sonarcloud.io
diff --git a/src/client/@tanstack/react-query.gen.ts b/src/client/@tanstack/react-query.gen.ts
index aef3335c..d91d2df8 100644
--- a/src/client/@tanstack/react-query.gen.ts
+++ b/src/client/@tanstack/react-query.gen.ts
@@ -3,15 +3,18 @@
import { queryOptions, type UseMutationOptions } from '@tanstack/react-query';
import { client } from '../client.gen';
-import { addWatchlistProduct, adminDeleteUser, adminGetPartnerApplication, adminGetPartnerApplications, adminGetUser, adminPatchPartnerApplication, adminPatchUser, adminPostPartnerApplicationDecision, adminSearchUsers, complexSearchProducts, createUserSearchFilter, deleteAllNotifications, deleteNotification, deletePartnerApplication, deleteUser, deleteUserSearchFilter, deleteWatchlistProduct, getCategories, getCategoryById, getNotifications, getPartnerApplication, getPartnerApplications, getPartnerShops, getPeriodById, getPeriods, getProduct, getProductBySlug, getProductHistory, getSearchFilterLiveProducts, getSearchFilterMatches, getShopById, getShopBySlug, getSimilarProducts, getUserAccount, getUserSearchFilter, getUserSearchFilters, getWatchlistProducts, type Options, patchAllNotifications, patchNotification, patchPartnerApplication, patchPartnerProducts, patchShopById, patchWatchlistProduct, postBillingCheckout, postBillingManage, postBillingPortal, postPartnerApplication, postPartnerProducts, postShop, postWoocommerceWebhook, putNewsletterSubscription, putPartnerProducts, putShopApiKey, searchCategories, searchPeriods, searchShops, simpleSearchProducts, simpleSearchShops, updateSearchFilterMatchFeedback, updateUserAccount, updateUserSearchFilter } from '../sdk.gen';
-import type { AddWatchlistProductData, AddWatchlistProductError, AddWatchlistProductResponse, AdminDeleteUserData, AdminDeleteUserError, AdminDeleteUserResponse, AdminGetPartnerApplicationData, AdminGetPartnerApplicationError, AdminGetPartnerApplicationResponse, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsError, AdminGetPartnerApplicationsResponse, AdminGetUserData, AdminGetUserError, AdminGetUserResponse, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationError, AdminPatchPartnerApplicationResponse, AdminPatchUserData, AdminPatchUserError, AdminPatchUserResponse, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionError, AdminPostPartnerApplicationDecisionResponse, AdminSearchUsersData, AdminSearchUsersError, AdminSearchUsersResponse, ComplexSearchProductsData, ComplexSearchProductsError, ComplexSearchProductsResponse, CreateUserSearchFilterData, CreateUserSearchFilterError, CreateUserSearchFilterResponse, DeleteAllNotificationsData, DeleteAllNotificationsError, DeleteAllNotificationsResponse, DeleteNotificationData, DeleteNotificationError, DeleteNotificationResponse, DeletePartnerApplicationData, DeletePartnerApplicationError, DeletePartnerApplicationResponse, DeleteUserData, DeleteUserError, DeleteUserResponse, DeleteUserSearchFilterData, DeleteUserSearchFilterError, DeleteUserSearchFilterResponse, DeleteWatchlistProductData, DeleteWatchlistProductError, DeleteWatchlistProductResponse, GetCategoriesData, GetCategoriesError, GetCategoriesResponse, GetCategoryByIdData, GetCategoryByIdError, GetCategoryByIdResponse, GetNotificationsData, GetNotificationsError, GetNotificationsResponse, GetPartnerApplicationData, GetPartnerApplicationError, GetPartnerApplicationResponse, GetPartnerApplicationsData, GetPartnerApplicationsError, GetPartnerApplicationsResponse, GetPartnerShopsData, GetPartnerShopsError, GetPartnerShopsResponse, GetPeriodByIdData, GetPeriodByIdError, GetPeriodByIdResponse, GetPeriodsData, GetPeriodsError, GetPeriodsResponse, GetProductBySlugData, GetProductBySlugError, GetProductBySlugResponse, GetProductData2, GetProductError, GetProductHistoryData, GetProductHistoryError, GetProductHistoryResponse, GetProductResponse, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsError, GetSearchFilterLiveProductsResponse, GetSearchFilterMatchesData, GetSearchFilterMatchesError, GetSearchFilterMatchesResponse, GetShopByIdData, GetShopByIdError, GetShopByIdResponse, GetShopBySlugData, GetShopBySlugError, GetShopBySlugResponse, GetSimilarProductsData, GetSimilarProductsError, GetSimilarProductsResponse, GetUserAccountData2, GetUserAccountError, GetUserAccountResponse, GetUserSearchFilterData, GetUserSearchFilterError, GetUserSearchFilterResponse, GetUserSearchFiltersData, GetUserSearchFiltersError, GetUserSearchFiltersResponse, GetWatchlistProductsData, GetWatchlistProductsError, GetWatchlistProductsResponse, PatchAllNotificationsData, PatchAllNotificationsError, PatchAllNotificationsResponse, PatchNotificationData2, PatchNotificationError, PatchNotificationResponse, PatchPartnerApplicationData, PatchPartnerApplicationError, PatchPartnerApplicationResponse, PatchPartnerProductsData, PatchPartnerProductsError, PatchPartnerProductsResponse, PatchShopByIdData, PatchShopByIdError, PatchShopByIdResponse, PatchWatchlistProductData, PatchWatchlistProductError, PatchWatchlistProductResponse, PostBillingCheckoutData2, PostBillingCheckoutError, PostBillingCheckoutResponse, PostBillingManageData, PostBillingManageError, PostBillingManageResponse, PostBillingPortalData, PostBillingPortalError, PostBillingPortalResponse, PostPartnerApplicationData, PostPartnerApplicationError, PostPartnerApplicationResponse, PostPartnerProductsData, PostPartnerProductsError, PostPartnerProductsResponse, PostShopData2, PostShopError, PostShopResponse, PostWoocommerceWebhookData, PostWoocommerceWebhookError, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionError, PutNewsletterSubscriptionResponse, PutPartnerProductsData, PutPartnerProductsError, PutPartnerProductsResponse, PutShopApiKeyData, PutShopApiKeyError, PutShopApiKeyResponse, SearchCategoriesData, SearchCategoriesError, SearchCategoriesResponse, SearchPeriodsData, SearchPeriodsError, SearchPeriodsResponse, SearchShopsData, SearchShopsError, SearchShopsResponse, SimpleSearchProductsData, SimpleSearchProductsError, SimpleSearchProductsResponse, SimpleSearchShopsData, SimpleSearchShopsError, SimpleSearchShopsResponse, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackError, UpdateSearchFilterMatchFeedbackResponse, UpdateUserAccountData, UpdateUserAccountError, UpdateUserAccountResponse, UpdateUserSearchFilterData, UpdateUserSearchFilterError, UpdateUserSearchFilterResponse } from '../types.gen';
+import { addWatchlistProduct, adminDeleteUser, adminGetPartnerApplication, adminGetPartnerApplications, adminGetUser, adminPatchPartnerApplication, adminPatchUser, adminPostPartnerApplicationDecision, adminSearchUsers, complexSearchProducts, createUserSearchFilter, deleteAllNotifications, deleteMyAccessToken, deleteNotification, deleteOAuthClient, deletePartnerApplication, deleteUser, deleteUserSearchFilter, deleteWatchlistProduct, getCategories, getCategoryById, getMyAccessToken, getMyAccessTokens, getMyPartnerShops, getNotifications, getOAuthClient, getOAuthClients, getPartnerApplication, getPartnerApplications, getPeriodById, getPeriods, getProduct, getProductBySlug, getProductHistory, getSearchFilterLiveProducts, getSearchFilterMatches, getShopById, getShopBySlug, getSimilarProducts, getUserAccount, getUserSearchFilter, getUserSearchFilters, getWatchlistProducts, oauthAuthorize, oauthIntrospect, oauthRevoke, oauthToken, type Options, patchAllNotifications, patchMyAccessToken, patchNotification, patchOAuthClient, patchPartnerApplication, patchPartnerProducts, patchShopById, patchWatchlistProduct, postBillingCheckout, postBillingManage, postBillingPortal, postMyAccessToken, postOAuthClient, postPartnerApplication, postPartnerProducts, postShop, postWoocommerceWebhook, putNewsletterSubscription, putPartnerProducts, searchCategories, searchPeriods, searchShops, simpleSearchProducts, simpleSearchShops, updateSearchFilterMatchFeedback, updateUserAccount, updateUserSearchFilter } from '../sdk.gen';
+import type { AddWatchlistProductData, AddWatchlistProductError, AddWatchlistProductResponse, AdminDeleteUserData, AdminDeleteUserError, AdminDeleteUserResponse, AdminGetPartnerApplicationData, AdminGetPartnerApplicationError, AdminGetPartnerApplicationResponse, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsError, AdminGetPartnerApplicationsResponse, AdminGetUserData, AdminGetUserError, AdminGetUserResponse, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationError, AdminPatchPartnerApplicationResponse, AdminPatchUserData, AdminPatchUserError, AdminPatchUserResponse, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionError, AdminPostPartnerApplicationDecisionResponse, AdminSearchUsersData, AdminSearchUsersError, AdminSearchUsersResponse, ComplexSearchProductsData, ComplexSearchProductsError, ComplexSearchProductsResponse, CreateUserSearchFilterData, CreateUserSearchFilterError, CreateUserSearchFilterResponse, DeleteAllNotificationsData, DeleteAllNotificationsError, DeleteAllNotificationsResponse, DeleteMyAccessTokenData, DeleteMyAccessTokenError, DeleteMyAccessTokenResponse, DeleteNotificationData, DeleteNotificationError, DeleteNotificationResponse, DeleteOAuthClientData, DeleteOAuthClientError, DeleteOAuthClientResponse, DeletePartnerApplicationData, DeletePartnerApplicationError, DeletePartnerApplicationResponse, DeleteUserData, DeleteUserError, DeleteUserResponse, DeleteUserSearchFilterData, DeleteUserSearchFilterError, DeleteUserSearchFilterResponse, DeleteWatchlistProductData, DeleteWatchlistProductError, DeleteWatchlistProductResponse, GetCategoriesData, GetCategoriesError, GetCategoriesResponse, GetCategoryByIdData, GetCategoryByIdError, GetCategoryByIdResponse, GetMyAccessTokenData, GetMyAccessTokenError, GetMyAccessTokenResponse, GetMyAccessTokensData, GetMyAccessTokensError, GetMyAccessTokensResponse, GetMyPartnerShopsData, GetMyPartnerShopsError, GetMyPartnerShopsResponse, GetNotificationsData, GetNotificationsError, GetNotificationsResponse, GetOAuthClientData, GetOAuthClientError, GetOAuthClientResponse, GetOAuthClientsData, GetOAuthClientsError, GetOAuthClientsResponse, GetPartnerApplicationData, GetPartnerApplicationError, GetPartnerApplicationResponse, GetPartnerApplicationsData, GetPartnerApplicationsError, GetPartnerApplicationsResponse, GetPeriodByIdData, GetPeriodByIdError, GetPeriodByIdResponse, GetPeriodsData, GetPeriodsError, GetPeriodsResponse, GetProductBySlugData, GetProductBySlugError, GetProductBySlugResponse, GetProductData2, GetProductError, GetProductHistoryData, GetProductHistoryError, GetProductHistoryResponse, GetProductResponse, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsError, GetSearchFilterLiveProductsResponse, GetSearchFilterMatchesData, GetSearchFilterMatchesError, GetSearchFilterMatchesResponse, GetShopByIdData, GetShopByIdError, GetShopByIdResponse, GetShopBySlugData, GetShopBySlugError, GetShopBySlugResponse, GetSimilarProductsData, GetSimilarProductsError, GetSimilarProductsResponse, GetUserAccountData2, GetUserAccountError, GetUserAccountResponse, GetUserSearchFilterData, GetUserSearchFilterError, GetUserSearchFilterResponse, GetUserSearchFiltersData, GetUserSearchFiltersError, GetUserSearchFiltersResponse, GetWatchlistProductsData, GetWatchlistProductsError, GetWatchlistProductsResponse, OauthAuthorizeData, OauthAuthorizeError, OauthIntrospectData, OauthIntrospectError, OauthIntrospectResponse, OauthRevokeData, OauthRevokeError, OauthTokenData, OauthTokenError, OauthTokenResponse, PatchAllNotificationsData, PatchAllNotificationsError, PatchAllNotificationsResponse, PatchMyAccessTokenData, PatchMyAccessTokenError, PatchMyAccessTokenResponse, PatchNotificationData2, PatchNotificationError, PatchNotificationResponse, PatchOAuthClientData, PatchOAuthClientError, PatchOAuthClientResponse, PatchPartnerApplicationData, PatchPartnerApplicationError, PatchPartnerApplicationResponse, PatchPartnerProductsData, PatchPartnerProductsError, PatchPartnerProductsResponse, PatchShopByIdData, PatchShopByIdError, PatchShopByIdResponse, PatchWatchlistProductData, PatchWatchlistProductError, PatchWatchlistProductResponse, PostBillingCheckoutData2, PostBillingCheckoutError, PostBillingCheckoutResponse, PostBillingManageData, PostBillingManageError, PostBillingManageResponse, PostBillingPortalData, PostBillingPortalError, PostBillingPortalResponse, PostMyAccessTokenData, PostMyAccessTokenError, PostMyAccessTokenResponse, PostOAuthClientData, PostOAuthClientError, PostOAuthClientResponse, PostPartnerApplicationData, PostPartnerApplicationError, PostPartnerApplicationResponse, PostPartnerProductsData, PostPartnerProductsError, PostPartnerProductsResponse, PostShopData2, PostShopError, PostShopResponse, PostWoocommerceWebhookData, PostWoocommerceWebhookError, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionError, PutNewsletterSubscriptionResponse, PutPartnerProductsData, PutPartnerProductsError, PutPartnerProductsResponse, SearchCategoriesData, SearchCategoriesError, SearchCategoriesResponse, SearchPeriodsData, SearchPeriodsError, SearchPeriodsResponse, SearchShopsData, SearchShopsError, SearchShopsResponse, SimpleSearchProductsData, SimpleSearchProductsError, SimpleSearchProductsResponse, SimpleSearchShopsData, SimpleSearchShopsError, SimpleSearchShopsResponse, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackError, UpdateSearchFilterMatchFeedbackResponse, UpdateUserAccountData, UpdateUserAccountError, UpdateUserAccountResponse, UpdateUserSearchFilterData, UpdateUserSearchFilterError, UpdateUserSearchFilterResponse } from '../types.gen';
/**
* Batch update products (Partner API)
*
- * Updates one or more existing products for a shop using API key authentication.
- * This endpoint is intended for partner shops — shops that have been granted partner status
- * and have an API key configured. It does **not** use Cognito JWT authentication.
+ * Updates one or more existing products for a shop using bearer authentication.
+ * This endpoint is intended for partner shops and accepts:
+ * - a Cognito bearer token for the partner user linked to the shop, or
+ * - an Aura Historia access token owned by that partner user.
+ *
+ * Aura Historia access tokens on this endpoint must include the `products:write` scope.
*
* The request body is an array of `PatchProductData` objects. Only the fields provided in
* each entry are updated; omitted optional fields are left unchanged. Each entry is forwarded
@@ -41,9 +44,12 @@ export const patchPartnerProductsMutation = (options?: Partial) => createQueryKey('getMyAccessTokens', options);
+
+/**
+ * List Aura Historia access tokens
+ *
+ * Lists the authenticated user's non-expired Aura Historia access tokens.
+ * The `token` field is masked on this endpoint and does not reveal the plaintext bearer token again.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getMyAccessTokensOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await getMyAccessTokens({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: getMyAccessTokensQueryKey(options)
+});
+
+/**
+ * Update an Aura Historia access token
+ *
+ * Updates metadata for one access token owned by the authenticated user.
+ * Omitted or `null` optional properties leave the existing value unchanged.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const patchMyAccessTokenMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await patchMyAccessToken({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Create an Aura Historia access token
+ *
+ * Creates a new Aura Historia access token for the authenticated user.
+ * The plaintext bearer token is returned only in this create response; later reads return a masked token value.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const postMyAccessTokenMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await postMyAccessToken({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Delete an Aura Historia access token
+ *
+ * Deletes one access token owned by the authenticated user.
+ * The token to delete is identified by the required `{accessTokenId}` path parameter.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const deleteMyAccessTokenMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await deleteMyAccessToken({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const getMyAccessTokenQueryKey = (options: Options) => createQueryKey('getMyAccessToken', options);
+
+/**
+ * Get one Aura Historia access token
+ *
+ * Retrieves one non-expired Aura Historia access token owned by the authenticated user.
+ * The `token` field is masked on this endpoint and does not reveal the plaintext bearer token again.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getMyAccessTokenOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await getMyAccessToken({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: getMyAccessTokenQueryKey(options)
+});
+
/**
* Create Stripe checkout session
*
@@ -1052,6 +1185,10 @@ export const simpleSearchShopsQueryKey = (options?: Options) => create
* Retrieves detailed information about a specific shop by its shop ID (UUID).
* Returns complete shop metadata including name, domains, image, address/contact metadata, and timestamps.
*
+ * Authentication is optional on this endpoint:
+ * - unauthenticated requests return cacheable shared responses
+ * - authenticated Cognito JWT or Aura Historia access-token requests return `Cache-Control: no-store`
+ *
*/
export const getShopByIdOptions = (options: Options) => queryOptions>({
queryFn: async ({ queryKey, signal }) => {
@@ -1130,7 +1271,7 @@ export const getShopByIdOptions = (options: Options) => queryOp
*
* Requires either:
* - a valid Cognito JWT for the partner user assigned to the shop or for an `ADMIN`, or
- * - the partner shop's `x-api-key` when no Cognito identity is present.
+ * - a valid Aura Historia access token owned by the partner user assigned to the shop.
*
* The request body uses `PatchShopData`. Only fields present in the JSON body are applied;
* omitted or `null` fields are left unchanged. The HTTP request body itself must not be absent
@@ -1151,33 +1292,6 @@ export const patchShopByIdMutation = (options?: Partial>): UseMutationOptions> => {
- const mutationOptions: UseMutationOptions> = {
- mutationFn: async (fnOptions) => {
- const { data } = await putShopApiKey({
- ...options,
- ...fnOptions,
- throwOnError: true
- });
- return data;
- }
- };
- return mutationOptions;
-};
-
export const getShopBySlugQueryKey = (options: Options) => createQueryKey('getShopBySlug', options);
/**
@@ -1186,6 +1300,10 @@ export const getShopBySlugQueryKey = (options: Options) => cr
* Retrieves detailed information about a specific shop by its human-readable slug identifier.
* Returns complete shop metadata including name, domains, image, address/contact metadata, and timestamps.
*
+ * Authentication is optional on this endpoint:
+ * - unauthenticated requests return cacheable shared responses
+ * - authenticated Cognito JWT or Aura Historia access-token requests return `Cache-Control: no-store`
+ *
* **Human-Readable Identifiers**: This endpoint uses slug-based identifiers which are human-readable
* kebab-case strings derived from the shop name (e.g., "tech-store-premium" or "christies").
*
@@ -1393,23 +1511,20 @@ export const searchPeriodsMutation = (options?: Partial) => createQueryKey('getPartnerShops', options);
+export const getMyPartnerShopsQueryKey = (options?: Options) => createQueryKey('getMyPartnerShops', options);
/**
- * List shops for a partner user
+ * List the authenticated user's partner shops
*
- * Returns all shops whose `partner_user_id` matches the requested `partnerId`.
+ * Returns all shops currently linked to the authenticated user's `partnerShops` set.
+ * Requires a valid Cognito JWT and always resolves shops for the current user only.
*
- * Requires a valid Cognito JWT. The caller may request:
- * - their own shops (`partnerId` equals the JWT `sub`), or
- * - another partner's shops when the caller has the `ADMIN` role.
- *
- * Returns an empty array when the partner currently has no shops.
+ * Returns an empty array when the authenticated user currently has no linked partner shops.
*
*/
-export const getPartnerShopsOptions = (options: Options) => queryOptions>({
+export const getMyPartnerShopsOptions = (options?: Options) => queryOptions>({
queryFn: async ({ queryKey, signal }) => {
- const { data } = await getPartnerShops({
+ const { data } = await getMyPartnerShops({
...options,
...queryKey[0],
signal,
@@ -1417,7 +1532,7 @@ export const getPartnerShopsOptions = (options: Options) =>
});
return data;
},
- queryKey: getPartnerShopsQueryKey(options)
+ queryKey: getMyPartnerShopsQueryKey(options)
});
export const getPartnerApplicationsQueryKey = (options?: Options) => createQueryKey('getPartnerApplications', options);
@@ -1634,3 +1749,220 @@ export const adminPostPartnerApplicationDecisionMutation = (options?: Partial) => createQueryKey('oauthAuthorize', options);
+
+/**
+ * OAuth2 authorization endpoint
+ *
+ * Starts the OAuth2 authorization code flow (RFC 6749 §4.1).
+ * The authenticated user (resource owner) grants the requesting OAuth client access to their
+ * Aura Historia account. PKCE (RFC 7636) with `S256` is required for all requests.
+ *
+ * On success the endpoint redirects (302) to the `redirect_uri` appending `code` and, if
+ * supplied, `state` as query parameters. The single-use authorization code must be exchanged
+ * for an access token via `POST /api/v1/oauth/token` before it expires.
+ *
+ */
+export const oauthAuthorizeOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await oauthAuthorize({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: oauthAuthorizeQueryKey(options)
+});
+
+/**
+ * OAuth2 token endpoint
+ *
+ * Exchanges a single-use authorization code for an Aura Historia access token (RFC 6749 §4.1.3).
+ * The request body must be `application/x-www-form-urlencoded`. Client authentication is
+ * performed via `client_id` / `client_secret` form fields. PKCE code verification (RFC 7636)
+ * is mandatory: `code_verifier` must produce the `code_challenge` supplied to the authorize
+ * endpoint.
+ *
+ * The issued access token is non-expiring by default (same lifetime as tokens created via
+ * `POST /api/v1/me/access-tokens`).
+ *
+ */
+export const oauthTokenMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await oauthToken({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * OAuth2 token revocation endpoint
+ *
+ * Revokes an Aura Historia access token that was issued through the OAuth authorization code
+ * flow (RFC 7009). The request body must be `application/x-www-form-urlencoded`. Client
+ * authentication is performed via `client_id` / `client_secret` form fields.
+ *
+ * After revocation the token is immediately inactive and introspection will return
+ * `active: false`. Attempting to revoke an unknown or already-revoked token is not an error.
+ *
+ */
+export const oauthRevokeMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await oauthRevoke({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * OAuth2 token introspection endpoint
+ *
+ * Returns metadata about an Aura Historia access token (RFC 7662). The request body must be
+ * `application/x-www-form-urlencoded`. Client authentication is performed via `client_id` /
+ * `client_secret` form fields.
+ *
+ * If the token is unknown, expired, or revoked, `active` is `false` and all other fields are
+ * omitted. Otherwise `active` is `true` and the available token metadata is populated.
+ *
+ */
+export const oauthIntrospectMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await oauthIntrospect({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const getOAuthClientsQueryKey = (options?: Options) => createQueryKey('getOAuthClients', options);
+
+/**
+ * List OAuth client metadata
+ *
+ * Lists registered OAuth client metadata records.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getOAuthClientsOptions = (options?: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await getOAuthClients({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: getOAuthClientsQueryKey(options)
+});
+
+/**
+ * Create OAuth client metadata
+ *
+ * Creates a new OAuth client metadata record.
+ * The plaintext `client_secret` is returned only in this create response; later reads return
+ * a masked secret display value.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const postOAuthClientMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await postOAuthClient({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+/**
+ * Delete OAuth client metadata
+ *
+ * Deletes one OAuth client metadata record.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const deleteOAuthClientMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await deleteOAuthClient({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
+
+export const getOAuthClientQueryKey = (options: Options) => createQueryKey('getOAuthClient', options);
+
+/**
+ * Get OAuth client metadata
+ *
+ * Retrieves one OAuth client metadata record.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getOAuthClientOptions = (options: Options) => queryOptions>({
+ queryFn: async ({ queryKey, signal }) => {
+ const { data } = await getOAuthClient({
+ ...options,
+ ...queryKey[0],
+ signal,
+ throwOnError: true
+ });
+ return data;
+ },
+ queryKey: getOAuthClientQueryKey(options)
+});
+
+/**
+ * Update OAuth client metadata
+ *
+ * Updates one OAuth client metadata record.
+ * Omitted or `null` optional properties leave the existing value unchanged.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const patchOAuthClientMutation = (options?: Partial>): UseMutationOptions> => {
+ const mutationOptions: UseMutationOptions> = {
+ mutationFn: async (fnOptions) => {
+ const { data } = await patchOAuthClient({
+ ...options,
+ ...fnOptions,
+ throwOnError: true
+ });
+ return data;
+ }
+ };
+ return mutationOptions;
+};
diff --git a/src/client/index.ts b/src/client/index.ts
index 864a1853..fd4fc9bc 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -1,4 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
-export { addWatchlistProduct, adminDeleteUser, adminGetPartnerApplication, adminGetPartnerApplications, adminGetUser, adminPatchPartnerApplication, adminPatchUser, adminPostPartnerApplicationDecision, adminSearchUsers, complexSearchProducts, createUserSearchFilter, deleteAllNotifications, deleteNotification, deletePartnerApplication, deleteUser, deleteUserSearchFilter, deleteWatchlistProduct, getCategories, getCategoryById, getNotifications, getPartnerApplication, getPartnerApplications, getPartnerShops, getPeriodById, getPeriods, getProduct, getProductBySlug, getProductHistory, getSearchFilterLiveProducts, getSearchFilterMatches, getShopById, getShopBySlug, getSimilarProducts, getUserAccount, getUserSearchFilter, getUserSearchFilters, getWatchlistProducts, type Options, patchAllNotifications, patchNotification, patchPartnerApplication, patchPartnerProducts, patchShopById, patchWatchlistProduct, postBillingCheckout, postBillingManage, postBillingPortal, postPartnerApplication, postPartnerProducts, postShop, postWoocommerceWebhook, putNewsletterSubscription, putPartnerProducts, putShopApiKey, searchCategories, searchPeriods, searchShops, simpleSearchProducts, simpleSearchShops, updateSearchFilterMatchFeedback, updateUserAccount, updateUserSearchFilter } from './sdk.gen';
-export type { AddWatchlistProductData, AddWatchlistProductError, AddWatchlistProductErrors, AddWatchlistProductResponse, AddWatchlistProductResponses, AdminDeleteUserData, AdminDeleteUserError, AdminDeleteUserErrors, AdminDeleteUserResponse, AdminDeleteUserResponses, AdminGetPartnerApplicationData, AdminGetPartnerApplicationError, AdminGetPartnerApplicationErrors, AdminGetPartnerApplicationResponse, AdminGetPartnerApplicationResponses, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsError, AdminGetPartnerApplicationsErrors, AdminGetPartnerApplicationsResponse, AdminGetPartnerApplicationsResponses, AdminGetUserData, AdminGetUserError, AdminGetUserErrors, AdminGetUserResponse, AdminGetUserResponses, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationError, AdminPatchPartnerApplicationErrors, AdminPatchPartnerApplicationResponse, AdminPatchPartnerApplicationResponses, AdminPatchPartnerShopApplicationData, AdminPatchUserData, AdminPatchUserError, AdminPatchUserErrors, AdminPatchUserResponse, AdminPatchUserResponses, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionError, AdminPostPartnerApplicationDecisionErrors, AdminPostPartnerApplicationDecisionResponse, AdminPostPartnerApplicationDecisionResponses, AdminSearchUsersData, AdminSearchUsersError, AdminSearchUsersErrors, AdminSearchUsersResponse, AdminSearchUsersResponses, ApiError, ApiErrorSource, ApprovedPartnerApplicationPayloadData, AuctionData, BillingCycleData, BillingPlanData, BillingSessionUrlData, CategorySearchData, ClientOptions, ComplexSearchProductsData, ComplexSearchProductsError, ComplexSearchProductsErrors, ComplexSearchProductsResponse, ComplexSearchProductsResponses, ContinentData, CountryCodeData, CreateUserSearchFilterData, CreateUserSearchFilterError, CreateUserSearchFilterErrors, CreateUserSearchFilterResponse, CreateUserSearchFilterResponses, CurrencyData, DeleteAllNotificationsData, DeleteAllNotificationsError, DeleteAllNotificationsErrors, DeleteAllNotificationsResponse, DeleteAllNotificationsResponses, DeleteNotificationData, DeleteNotificationError, DeleteNotificationErrors, DeleteNotificationResponse, DeleteNotificationResponses, DeletePartnerApplicationData, DeletePartnerApplicationError, DeletePartnerApplicationErrors, DeletePartnerApplicationResponse, DeletePartnerApplicationResponses, DeleteUserData, DeleteUserError, DeleteUserErrors, DeleteUserResponse, DeleteUserResponses, DeleteUserSearchFilterData, DeleteUserSearchFilterError, DeleteUserSearchFilterErrors, DeleteUserSearchFilterResponse, DeleteUserSearchFilterResponses, DeleteWatchlistProductData, DeleteWatchlistProductError, DeleteWatchlistProductErrors, DeleteWatchlistProductResponse, DeleteWatchlistProductResponses, DistanceData, DistanceUnitData, ExecutionStateData, GeoAddressData, GeoDistanceQueryData, GetCategoriesData, GetCategoriesError, GetCategoriesErrors, GetCategoriesResponse, GetCategoriesResponses, GetCategoryByIdData, GetCategoryByIdError, GetCategoryByIdErrors, GetCategoryByIdResponse, GetCategoryByIdResponses, GetCategoryData, GetCategorySummaryData, GetNotificationData, GetNotificationsData, GetNotificationsError, GetNotificationsErrors, GetNotificationsResponse, GetNotificationsResponses, GetPartnerApplicationData, GetPartnerApplicationError, GetPartnerApplicationErrors, GetPartnerApplicationResponse, GetPartnerApplicationResponses, GetPartnerApplicationsData, GetPartnerApplicationsError, GetPartnerApplicationsErrors, GetPartnerApplicationsResponse, GetPartnerApplicationsResponses, GetPartnerShopApplicationData, GetPartnerShopApplicationPayloadData, GetPartnerShopsData, GetPartnerShopsError, GetPartnerShopsErrors, GetPartnerShopsResponse, GetPartnerShopsResponses, GetPeriodByIdData, GetPeriodByIdError, GetPeriodByIdErrors, GetPeriodByIdResponse, GetPeriodByIdResponses, GetPeriodData, GetPeriodsData, GetPeriodsError, GetPeriodsErrors, GetPeriodsResponse, GetPeriodsResponses, GetPeriodSummaryData, GetProductBySlugData, GetProductBySlugError, GetProductBySlugErrors, GetProductBySlugResponse, GetProductBySlugResponses, GetProductData, GetProductData2, GetProductError, GetProductErrors, GetProductEventData, GetProductHistoryData, GetProductHistoryError, GetProductHistoryErrors, GetProductHistoryResponse, GetProductHistoryResponses, GetProductResponse, GetProductResponses, GetProductSummaryData, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsError, GetSearchFilterLiveProductsErrors, GetSearchFilterLiveProductsResponse, GetSearchFilterLiveProductsResponses, GetSearchFilterMatchesData, GetSearchFilterMatchesError, GetSearchFilterMatchesErrors, GetSearchFilterMatchesResponse, GetSearchFilterMatchesResponses, GetShopByIdData, GetShopByIdError, GetShopByIdErrors, GetShopByIdResponse, GetShopByIdResponses, GetShopBySlugData, GetShopBySlugError, GetShopBySlugErrors, GetShopBySlugResponse, GetShopBySlugResponses, GetShopData, GetSimilarProductsData, GetSimilarProductsError, GetSimilarProductsErrors, GetSimilarProductsResponse, GetSimilarProductsResponses, GetUserAccountData, GetUserAccountData2, GetUserAccountError, GetUserAccountErrors, GetUserAccountResponse, GetUserAccountResponses, GetUserSearchFilterData, GetUserSearchFilterError, GetUserSearchFilterErrors, GetUserSearchFilterResponse, GetUserSearchFilterResponses, GetUserSearchFiltersData, GetUserSearchFiltersError, GetUserSearchFiltersErrors, GetUserSearchFiltersResponse, GetUserSearchFiltersResponses, GetWatchlistProductsData, GetWatchlistProductsError, GetWatchlistProductsErrors, GetWatchlistProductsResponse, GetWatchlistProductsResponses, LanguageData, LocalizedTextData, NotificationCollectionData, NotificationPayloadData, NotificationUserStateData, PartnerApplicationNotificationPayloadData, PartnerApplicationPayloadData, PartnerProductEnqueueFailuresResponse, PartnerShopApiKeyResponse, PartnerShopApplicationDecisionData, PartnerShopApplicationStateData, PatchAdminUserData, PatchAllNotificationsData, PatchAllNotificationsError, PatchAllNotificationsErrors, PatchAllNotificationsResponse, PatchAllNotificationsResponses, PatchNotificationData, PatchNotificationData2, PatchNotificationError, PatchNotificationErrors, PatchNotificationResponse, PatchNotificationResponses, PatchPartnerApplicationData, PatchPartnerApplicationError, PatchPartnerApplicationErrors, PatchPartnerApplicationResponse, PatchPartnerApplicationResponses, PatchPartnerProductsData, PatchPartnerProductsError, PatchPartnerProductsErrors, PatchPartnerProductsResponse, PatchPartnerProductsResponses, PatchPartnerShopApplicationData, PatchProductData, PatchProductSearchData, PatchResourceStateData, PatchShopByIdData, PatchShopByIdError, PatchShopByIdErrors, PatchShopByIdResponse, PatchShopByIdResponses, PatchShopData, PatchShopDataWritable, PatchUserAccountData, PatchUserSearchFilterData, PatchUserSearchFilterMatchData, PatchWatchlistProductData, PatchWatchlistProductError, PatchWatchlistProductErrors, PatchWatchlistProductResponse, PatchWatchlistProductResponses, PeriodSearchData, PersonalizedGetProductData, PersonalizedGetProductSummaryData, PersonalizedProductSearchResultData, PostBillingCheckoutData, PostBillingCheckoutData2, PostBillingCheckoutError, PostBillingCheckoutErrors, PostBillingCheckoutResponse, PostBillingCheckoutResponses, PostBillingManageData, PostBillingManageError, PostBillingManageErrors, PostBillingManageResponse, PostBillingManageResponses, PostBillingPortalData, PostBillingPortalError, PostBillingPortalErrors, PostBillingPortalResponse, PostBillingPortalResponses, PostPartnerApplicationData, PostPartnerApplicationError, PostPartnerApplicationErrors, PostPartnerApplicationResponse, PostPartnerApplicationResponses, PostPartnerProductsData, PostPartnerProductsError, PostPartnerProductsErrors, PostPartnerProductsResponse, PostPartnerProductsResponses, PostPartnerShopApplicationDecisionData, PostPartnerShopApplicationPayloadData, PostProductData, PostShopData, PostShopData2, PostShopDataWritable, PostShopError, PostShopErrors, PostShopResponse, PostShopResponses, PostUserSearchFilterData, PostWoocommerceWebhookData, PostWoocommerceWebhookError, PostWoocommerceWebhookErrors, PostWoocommerceWebhookResponses, PriceChangeWatchlistPayloadData, PriceData, PriceEstimateData, PricingData, ProductCreatedEventPayloadData, ProductEventAuctionTimeChangedPayloadData, ProductEventEstimatePriceChangedPayloadData, ProductEventImagesChangedPayloadData, ProductEventPayloadData, ProductEventPriceChangedPayloadData, ProductEventStateChangedPayloadData, ProductEventTypeData, ProductEventUrlChangedPayloadData, ProductImageData, ProductKeyData, ProductSearchData, ProductStateData, ProductUserStateData, ProhibitedContentData, ProhibitedContentUserStateData, PutNewsletterSubscriptionData, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionError, PutNewsletterSubscriptionErrors, PutNewsletterSubscriptionResponse, PutNewsletterSubscriptionResponses, PutPartnerProductsData, PutPartnerProductsError, PutPartnerProductsErrors, PutPartnerProductsResponse, PutPartnerProductsResponses, PutProductData, PutShopApiKeyData, PutShopApiKeyError, PutShopApiKeyErrors, PutShopApiKeyResponse, PutShopApiKeyResponses, RangeQueryDateTime, RangeQueryInt32, RangeQueryUInt64, RejectedPartnerApplicationPayloadData, ResourceStateData, SearchCategoriesData, SearchCategoriesError, SearchCategoriesErrors, SearchCategoriesResponse, SearchCategoriesResponses, SearchFilterMatchProductCollectionData, SearchFilterNotificationPayloadData, SearchFilterPayloadData, SearchFilterProductMatchData, SearchFilterUserStateData, SearchPeriodsData, SearchPeriodsError, SearchPeriodsErrors, SearchPeriodsResponse, SearchPeriodsResponses, SearchShopsData, SearchShopsError, SearchShopsErrors, SearchShopsResponse, SearchShopsResponses, ShopPartnerStatusData, ShopSearchData, ShopSearchResultData, ShopTypeData, SimpleSearchProductsData, SimpleSearchProductsError, SimpleSearchProductsErrors, SimpleSearchProductsResponse, SimpleSearchProductsResponses, SimpleSearchShopsData, SimpleSearchShopsError, SimpleSearchShopsErrors, SimpleSearchShopsResponse, SimpleSearchShopsResponses, SortCategoryFieldData, SortPeriodFieldData, SortProductFieldData, SortSearchFilterMatchFieldData, SortShopFieldData, SortUserFieldData, SortUserSearchFilterFieldData, SortWatchlistProductFieldData, StateChangeWatchlistPayloadData, StructuredAddressData, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackError, UpdateSearchFilterMatchFeedbackErrors, UpdateSearchFilterMatchFeedbackResponse, UpdateSearchFilterMatchFeedbackResponses, UpdateUserAccountData, UpdateUserAccountError, UpdateUserAccountErrors, UpdateUserAccountResponse, UpdateUserAccountResponses, UpdateUserSearchFilterData, UpdateUserSearchFilterError, UpdateUserSearchFilterErrors, UpdateUserSearchFilterResponse, UpdateUserSearchFilterResponses, UserCollectionData, UserRoleData, UserSearchData, UserSearchFilterCollectionData, UserSearchFilterData, UserTierData, WatchlistCollectionData, WatchlistNotificationPayloadData, WatchlistPayloadData, WatchlistProductPatch, WatchlistUserStateData, WoocommerceProductWebhookDeleteData, WoocommerceProductWebhookImageData, WoocommerceProductWebhookUpsertData } from './types.gen';
+export { addWatchlistProduct, adminDeleteUser, adminGetPartnerApplication, adminGetPartnerApplications, adminGetUser, adminPatchPartnerApplication, adminPatchUser, adminPostPartnerApplicationDecision, adminSearchUsers, complexSearchProducts, createUserSearchFilter, deleteAllNotifications, deleteMyAccessToken, deleteNotification, deleteOAuthClient, deletePartnerApplication, deleteUser, deleteUserSearchFilter, deleteWatchlistProduct, getCategories, getCategoryById, getMyAccessToken, getMyAccessTokens, getMyPartnerShops, getNotifications, getOAuthClient, getOAuthClients, getPartnerApplication, getPartnerApplications, getPeriodById, getPeriods, getProduct, getProductBySlug, getProductHistory, getSearchFilterLiveProducts, getSearchFilterMatches, getShopById, getShopBySlug, getSimilarProducts, getUserAccount, getUserSearchFilter, getUserSearchFilters, getWatchlistProducts, oauthAuthorize, oauthIntrospect, oauthRevoke, oauthToken, type Options, patchAllNotifications, patchMyAccessToken, patchNotification, patchOAuthClient, patchPartnerApplication, patchPartnerProducts, patchShopById, patchWatchlistProduct, postBillingCheckout, postBillingManage, postBillingPortal, postMyAccessToken, postOAuthClient, postPartnerApplication, postPartnerProducts, postShop, postWoocommerceWebhook, putNewsletterSubscription, putPartnerProducts, searchCategories, searchPeriods, searchShops, simpleSearchProducts, simpleSearchShops, updateSearchFilterMatchFeedback, updateUserAccount, updateUserSearchFilter } from './sdk.gen';
+export type { AccessTokenScopeData, AccessTokenTypeData, AddWatchlistProductData, AddWatchlistProductError, AddWatchlistProductErrors, AddWatchlistProductResponse, AddWatchlistProductResponses, AdminDeleteUserData, AdminDeleteUserError, AdminDeleteUserErrors, AdminDeleteUserResponse, AdminDeleteUserResponses, AdminGetPartnerApplicationData, AdminGetPartnerApplicationError, AdminGetPartnerApplicationErrors, AdminGetPartnerApplicationResponse, AdminGetPartnerApplicationResponses, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsError, AdminGetPartnerApplicationsErrors, AdminGetPartnerApplicationsResponse, AdminGetPartnerApplicationsResponses, AdminGetUserData, AdminGetUserError, AdminGetUserErrors, AdminGetUserResponse, AdminGetUserResponses, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationError, AdminPatchPartnerApplicationErrors, AdminPatchPartnerApplicationResponse, AdminPatchPartnerApplicationResponses, AdminPatchPartnerShopApplicationData, AdminPatchUserData, AdminPatchUserError, AdminPatchUserErrors, AdminPatchUserResponse, AdminPatchUserResponses, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionError, AdminPostPartnerApplicationDecisionErrors, AdminPostPartnerApplicationDecisionResponse, AdminPostPartnerApplicationDecisionResponses, AdminSearchUsersData, AdminSearchUsersError, AdminSearchUsersErrors, AdminSearchUsersResponse, AdminSearchUsersResponses, ApiError, ApiErrorSource, ApprovedPartnerApplicationPayloadData, AuctionData, BillingCycleData, BillingPlanData, BillingSessionUrlData, CategorySearchData, ClientOptions, ComplexSearchProductsData, ComplexSearchProductsError, ComplexSearchProductsErrors, ComplexSearchProductsResponse, ComplexSearchProductsResponses, ContinentData, CountryCodeData, CreateUserSearchFilterData, CreateUserSearchFilterError, CreateUserSearchFilterErrors, CreateUserSearchFilterResponse, CreateUserSearchFilterResponses, CurrencyData, DeleteAllNotificationsData, DeleteAllNotificationsError, DeleteAllNotificationsErrors, DeleteAllNotificationsResponse, DeleteAllNotificationsResponses, DeleteMyAccessTokenData, DeleteMyAccessTokenError, DeleteMyAccessTokenErrors, DeleteMyAccessTokenResponse, DeleteMyAccessTokenResponses, DeleteNotificationData, DeleteNotificationError, DeleteNotificationErrors, DeleteNotificationResponse, DeleteNotificationResponses, DeleteOAuthClientData, DeleteOAuthClientError, DeleteOAuthClientErrors, DeleteOAuthClientResponse, DeleteOAuthClientResponses, DeletePartnerApplicationData, DeletePartnerApplicationError, DeletePartnerApplicationErrors, DeletePartnerApplicationResponse, DeletePartnerApplicationResponses, DeleteUserData, DeleteUserError, DeleteUserErrors, DeleteUserResponse, DeleteUserResponses, DeleteUserSearchFilterData, DeleteUserSearchFilterError, DeleteUserSearchFilterErrors, DeleteUserSearchFilterResponse, DeleteUserSearchFilterResponses, DeleteWatchlistProductData, DeleteWatchlistProductError, DeleteWatchlistProductErrors, DeleteWatchlistProductResponse, DeleteWatchlistProductResponses, DistanceData, DistanceUnitData, ExecutionStateData, GeoAddressData, GeoDistanceQueryData, GetAccessTokenData, GetCategoriesData, GetCategoriesError, GetCategoriesErrors, GetCategoriesResponse, GetCategoriesResponses, GetCategoryByIdData, GetCategoryByIdError, GetCategoryByIdErrors, GetCategoryByIdResponse, GetCategoryByIdResponses, GetCategoryData, GetCategorySummaryData, GetMyAccessTokenData, GetMyAccessTokenError, GetMyAccessTokenErrors, GetMyAccessTokenResponse, GetMyAccessTokenResponses, GetMyAccessTokensData, GetMyAccessTokensError, GetMyAccessTokensErrors, GetMyAccessTokensResponse, GetMyAccessTokensResponses, GetMyPartnerShopsData, GetMyPartnerShopsError, GetMyPartnerShopsErrors, GetMyPartnerShopsResponse, GetMyPartnerShopsResponses, GetNotificationData, GetNotificationsData, GetNotificationsError, GetNotificationsErrors, GetNotificationsResponse, GetNotificationsResponses, GetOAuthClientData, GetOAuthClientError, GetOAuthClientErrors, GetOAuthClientResponse, GetOAuthClientResponses, GetOAuthClientsData, GetOAuthClientsError, GetOAuthClientsErrors, GetOAuthClientsResponse, GetOAuthClientsResponses, GetPartnerApplicationData, GetPartnerApplicationError, GetPartnerApplicationErrors, GetPartnerApplicationResponse, GetPartnerApplicationResponses, GetPartnerApplicationsData, GetPartnerApplicationsError, GetPartnerApplicationsErrors, GetPartnerApplicationsResponse, GetPartnerApplicationsResponses, GetPartnerShopApplicationData, GetPartnerShopApplicationPayloadData, GetPeriodByIdData, GetPeriodByIdError, GetPeriodByIdErrors, GetPeriodByIdResponse, GetPeriodByIdResponses, GetPeriodData, GetPeriodsData, GetPeriodsError, GetPeriodsErrors, GetPeriodsResponse, GetPeriodsResponses, GetPeriodSummaryData, GetProductBySlugData, GetProductBySlugError, GetProductBySlugErrors, GetProductBySlugResponse, GetProductBySlugResponses, GetProductData, GetProductData2, GetProductError, GetProductErrors, GetProductEventData, GetProductHistoryData, GetProductHistoryError, GetProductHistoryErrors, GetProductHistoryResponse, GetProductHistoryResponses, GetProductResponse, GetProductResponses, GetProductSummaryData, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsError, GetSearchFilterLiveProductsErrors, GetSearchFilterLiveProductsResponse, GetSearchFilterLiveProductsResponses, GetSearchFilterMatchesData, GetSearchFilterMatchesError, GetSearchFilterMatchesErrors, GetSearchFilterMatchesResponse, GetSearchFilterMatchesResponses, GetShopByIdData, GetShopByIdError, GetShopByIdErrors, GetShopByIdResponse, GetShopByIdResponses, GetShopBySlugData, GetShopBySlugError, GetShopBySlugErrors, GetShopBySlugResponse, GetShopBySlugResponses, GetShopData, GetSimilarProductsData, GetSimilarProductsError, GetSimilarProductsErrors, GetSimilarProductsResponse, GetSimilarProductsResponses, GetUserAccountData, GetUserAccountData2, GetUserAccountError, GetUserAccountErrors, GetUserAccountResponse, GetUserAccountResponses, GetUserSearchFilterData, GetUserSearchFilterError, GetUserSearchFilterErrors, GetUserSearchFilterResponse, GetUserSearchFilterResponses, GetUserSearchFiltersData, GetUserSearchFiltersError, GetUserSearchFiltersErrors, GetUserSearchFiltersResponse, GetUserSearchFiltersResponses, GetWatchlistProductsData, GetWatchlistProductsError, GetWatchlistProductsErrors, GetWatchlistProductsResponse, GetWatchlistProductsResponses, LanguageData, LocalizedTextData, NotificationCollectionData, NotificationPayloadData, NotificationUserStateData, OauthAuthorizeData, OauthAuthorizeError, OauthAuthorizeErrors, OAuthClientMetadataPatchData, OAuthClientMetadataRequestData, OAuthClientMetadataResponseData, OauthIntrospectData, OauthIntrospectError, OauthIntrospectErrors, OAuthIntrospectionResponseData, OauthIntrospectResponse, OauthIntrospectResponses, OauthRevokeData, OauthRevokeError, OauthRevokeErrors, OauthRevokeResponses, OauthTokenData, OauthTokenError, OauthTokenErrors, OauthTokenResponse, OAuthTokenResponseData, OauthTokenResponses, PartnerApplicationNotificationPayloadData, PartnerApplicationPayloadData, PartnerProductEnqueueFailuresResponse, PartnerShopApplicationDecisionData, PartnerShopApplicationStateData, PatchAccessTokenData, PatchAdminUserData, PatchAllNotificationsData, PatchAllNotificationsError, PatchAllNotificationsErrors, PatchAllNotificationsResponse, PatchAllNotificationsResponses, PatchMyAccessTokenData, PatchMyAccessTokenError, PatchMyAccessTokenErrors, PatchMyAccessTokenResponse, PatchMyAccessTokenResponses, PatchNotificationData, PatchNotificationData2, PatchNotificationError, PatchNotificationErrors, PatchNotificationResponse, PatchNotificationResponses, PatchOAuthClientData, PatchOAuthClientError, PatchOAuthClientErrors, PatchOAuthClientResponse, PatchOAuthClientResponses, PatchPartnerApplicationData, PatchPartnerApplicationError, PatchPartnerApplicationErrors, PatchPartnerApplicationResponse, PatchPartnerApplicationResponses, PatchPartnerProductsData, PatchPartnerProductsError, PatchPartnerProductsErrors, PatchPartnerProductsResponse, PatchPartnerProductsResponses, PatchPartnerShopApplicationData, PatchProductData, PatchProductSearchData, PatchResourceStateData, PatchShopByIdData, PatchShopByIdError, PatchShopByIdErrors, PatchShopByIdResponse, PatchShopByIdResponses, PatchShopData, PatchShopDataWritable, PatchUserAccountData, PatchUserSearchFilterData, PatchUserSearchFilterMatchData, PatchWatchlistProductData, PatchWatchlistProductError, PatchWatchlistProductErrors, PatchWatchlistProductResponse, PatchWatchlistProductResponses, PeriodSearchData, PersonalizedGetProductData, PersonalizedGetProductSummaryData, PersonalizedProductSearchResultData, PostAccessTokenData, PostBillingCheckoutData, PostBillingCheckoutData2, PostBillingCheckoutError, PostBillingCheckoutErrors, PostBillingCheckoutResponse, PostBillingCheckoutResponses, PostBillingManageData, PostBillingManageError, PostBillingManageErrors, PostBillingManageResponse, PostBillingManageResponses, PostBillingPortalData, PostBillingPortalError, PostBillingPortalErrors, PostBillingPortalResponse, PostBillingPortalResponses, PostMyAccessTokenData, PostMyAccessTokenError, PostMyAccessTokenErrors, PostMyAccessTokenResponse, PostMyAccessTokenResponses, PostOAuthClientData, PostOAuthClientError, PostOAuthClientErrors, PostOAuthClientResponse, PostOAuthClientResponses, PostPartnerApplicationData, PostPartnerApplicationError, PostPartnerApplicationErrors, PostPartnerApplicationResponse, PostPartnerApplicationResponses, PostPartnerProductsData, PostPartnerProductsError, PostPartnerProductsErrors, PostPartnerProductsResponse, PostPartnerProductsResponses, PostPartnerShopApplicationDecisionData, PostPartnerShopApplicationPayloadData, PostProductData, PostShopData, PostShopData2, PostShopDataWritable, PostShopError, PostShopErrors, PostShopResponse, PostShopResponses, PostUserSearchFilterData, PostWoocommerceWebhookData, PostWoocommerceWebhookError, PostWoocommerceWebhookErrors, PostWoocommerceWebhookResponses, PriceChangeWatchlistPayloadData, PriceData, PriceEstimateData, PricingData, ProductCreatedEventPayloadData, ProductEventAuctionTimeChangedPayloadData, ProductEventEstimatePriceChangedPayloadData, ProductEventImagesChangedPayloadData, ProductEventPayloadData, ProductEventPriceChangedPayloadData, ProductEventStateChangedPayloadData, ProductEventTypeData, ProductEventUrlChangedPayloadData, ProductImageData, ProductKeyData, ProductSearchData, ProductStateData, ProductUserStateData, ProhibitedContentData, ProhibitedContentUserStateData, PutNewsletterSubscriptionData, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionError, PutNewsletterSubscriptionErrors, PutNewsletterSubscriptionResponse, PutNewsletterSubscriptionResponses, PutPartnerProductsData, PutPartnerProductsError, PutPartnerProductsErrors, PutPartnerProductsResponse, PutPartnerProductsResponses, PutProductData, RangeQueryDateTime, RangeQueryInt32, RangeQueryUInt64, RejectedPartnerApplicationPayloadData, ResourceStateData, SearchCategoriesData, SearchCategoriesError, SearchCategoriesErrors, SearchCategoriesResponse, SearchCategoriesResponses, SearchFilterMatchProductCollectionData, SearchFilterNotificationPayloadData, SearchFilterPayloadData, SearchFilterProductMatchData, SearchFilterUserStateData, SearchPeriodsData, SearchPeriodsError, SearchPeriodsErrors, SearchPeriodsResponse, SearchPeriodsResponses, SearchShopsData, SearchShopsError, SearchShopsErrors, SearchShopsResponse, SearchShopsResponses, ShopPartnerStatusData, ShopSearchData, ShopSearchResultData, ShopTypeData, SimpleSearchProductsData, SimpleSearchProductsError, SimpleSearchProductsErrors, SimpleSearchProductsResponse, SimpleSearchProductsResponses, SimpleSearchShopsData, SimpleSearchShopsError, SimpleSearchShopsErrors, SimpleSearchShopsResponse, SimpleSearchShopsResponses, SortCategoryFieldData, SortPeriodFieldData, SortProductFieldData, SortSearchFilterMatchFieldData, SortShopFieldData, SortUserFieldData, SortUserSearchFilterFieldData, SortWatchlistProductFieldData, StateChangeWatchlistPayloadData, StructuredAddressData, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackError, UpdateSearchFilterMatchFeedbackErrors, UpdateSearchFilterMatchFeedbackResponse, UpdateSearchFilterMatchFeedbackResponses, UpdateUserAccountData, UpdateUserAccountError, UpdateUserAccountErrors, UpdateUserAccountResponse, UpdateUserAccountResponses, UpdateUserSearchFilterData, UpdateUserSearchFilterError, UpdateUserSearchFilterErrors, UpdateUserSearchFilterResponse, UpdateUserSearchFilterResponses, UserCollectionData, UserRoleData, UserSearchData, UserSearchFilterCollectionData, UserSearchFilterData, UserTierData, WatchlistCollectionData, WatchlistNotificationPayloadData, WatchlistPayloadData, WatchlistProductPatch, WatchlistUserStateData, WoocommerceProductWebhookDeleteData, WoocommerceProductWebhookImageData, WoocommerceProductWebhookUpsertData } from './types.gen';
diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts
index 2a6bb87c..b910e24c 100644
--- a/src/client/sdk.gen.ts
+++ b/src/client/sdk.gen.ts
@@ -1,8 +1,8 @@
// This file is auto-generated by @hey-api/openapi-ts
-import type { Client, Options as Options2, TDataShape } from './client';
+import { type Client, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client';
import { client } from './client.gen';
-import type { AddWatchlistProductData, AddWatchlistProductErrors, AddWatchlistProductResponses, AdminDeleteUserData, AdminDeleteUserErrors, AdminDeleteUserResponses, AdminGetPartnerApplicationData, AdminGetPartnerApplicationErrors, AdminGetPartnerApplicationResponses, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsErrors, AdminGetPartnerApplicationsResponses, AdminGetUserData, AdminGetUserErrors, AdminGetUserResponses, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationErrors, AdminPatchPartnerApplicationResponses, AdminPatchUserData, AdminPatchUserErrors, AdminPatchUserResponses, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionErrors, AdminPostPartnerApplicationDecisionResponses, AdminSearchUsersData, AdminSearchUsersErrors, AdminSearchUsersResponses, ComplexSearchProductsData, ComplexSearchProductsErrors, ComplexSearchProductsResponses, CreateUserSearchFilterData, CreateUserSearchFilterErrors, CreateUserSearchFilterResponses, DeleteAllNotificationsData, DeleteAllNotificationsErrors, DeleteAllNotificationsResponses, DeleteNotificationData, DeleteNotificationErrors, DeleteNotificationResponses, DeletePartnerApplicationData, DeletePartnerApplicationErrors, DeletePartnerApplicationResponses, DeleteUserData, DeleteUserErrors, DeleteUserResponses, DeleteUserSearchFilterData, DeleteUserSearchFilterErrors, DeleteUserSearchFilterResponses, DeleteWatchlistProductData, DeleteWatchlistProductErrors, DeleteWatchlistProductResponses, GetCategoriesData, GetCategoriesErrors, GetCategoriesResponses, GetCategoryByIdData, GetCategoryByIdErrors, GetCategoryByIdResponses, GetNotificationsData, GetNotificationsErrors, GetNotificationsResponses, GetPartnerApplicationData, GetPartnerApplicationErrors, GetPartnerApplicationResponses, GetPartnerApplicationsData, GetPartnerApplicationsErrors, GetPartnerApplicationsResponses, GetPartnerShopsData, GetPartnerShopsErrors, GetPartnerShopsResponses, GetPeriodByIdData, GetPeriodByIdErrors, GetPeriodByIdResponses, GetPeriodsData, GetPeriodsErrors, GetPeriodsResponses, GetProductBySlugData, GetProductBySlugErrors, GetProductBySlugResponses, GetProductData2, GetProductErrors, GetProductHistoryData, GetProductHistoryErrors, GetProductHistoryResponses, GetProductResponses, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsErrors, GetSearchFilterLiveProductsResponses, GetSearchFilterMatchesData, GetSearchFilterMatchesErrors, GetSearchFilterMatchesResponses, GetShopByIdData, GetShopByIdErrors, GetShopByIdResponses, GetShopBySlugData, GetShopBySlugErrors, GetShopBySlugResponses, GetSimilarProductsData, GetSimilarProductsErrors, GetSimilarProductsResponses, GetUserAccountData2, GetUserAccountErrors, GetUserAccountResponses, GetUserSearchFilterData, GetUserSearchFilterErrors, GetUserSearchFilterResponses, GetUserSearchFiltersData, GetUserSearchFiltersErrors, GetUserSearchFiltersResponses, GetWatchlistProductsData, GetWatchlistProductsErrors, GetWatchlistProductsResponses, PatchAllNotificationsData, PatchAllNotificationsErrors, PatchAllNotificationsResponses, PatchNotificationData2, PatchNotificationErrors, PatchNotificationResponses, PatchPartnerApplicationData, PatchPartnerApplicationErrors, PatchPartnerApplicationResponses, PatchPartnerProductsData, PatchPartnerProductsErrors, PatchPartnerProductsResponses, PatchShopByIdData, PatchShopByIdErrors, PatchShopByIdResponses, PatchWatchlistProductData, PatchWatchlistProductErrors, PatchWatchlistProductResponses, PostBillingCheckoutData2, PostBillingCheckoutErrors, PostBillingCheckoutResponses, PostBillingManageData, PostBillingManageErrors, PostBillingManageResponses, PostBillingPortalData, PostBillingPortalErrors, PostBillingPortalResponses, PostPartnerApplicationData, PostPartnerApplicationErrors, PostPartnerApplicationResponses, PostPartnerProductsData, PostPartnerProductsErrors, PostPartnerProductsResponses, PostShopData2, PostShopErrors, PostShopResponses, PostWoocommerceWebhookData, PostWoocommerceWebhookErrors, PostWoocommerceWebhookResponses, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionErrors, PutNewsletterSubscriptionResponses, PutPartnerProductsData, PutPartnerProductsErrors, PutPartnerProductsResponses, PutShopApiKeyData, PutShopApiKeyErrors, PutShopApiKeyResponses, SearchCategoriesData, SearchCategoriesErrors, SearchCategoriesResponses, SearchPeriodsData, SearchPeriodsErrors, SearchPeriodsResponses, SearchShopsData, SearchShopsErrors, SearchShopsResponses, SimpleSearchProductsData, SimpleSearchProductsErrors, SimpleSearchProductsResponses, SimpleSearchShopsData, SimpleSearchShopsErrors, SimpleSearchShopsResponses, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackErrors, UpdateSearchFilterMatchFeedbackResponses, UpdateUserAccountData, UpdateUserAccountErrors, UpdateUserAccountResponses, UpdateUserSearchFilterData, UpdateUserSearchFilterErrors, UpdateUserSearchFilterResponses } from './types.gen';
+import type { AddWatchlistProductData, AddWatchlistProductErrors, AddWatchlistProductResponses, AdminDeleteUserData, AdminDeleteUserErrors, AdminDeleteUserResponses, AdminGetPartnerApplicationData, AdminGetPartnerApplicationErrors, AdminGetPartnerApplicationResponses, AdminGetPartnerApplicationsData, AdminGetPartnerApplicationsErrors, AdminGetPartnerApplicationsResponses, AdminGetUserData, AdminGetUserErrors, AdminGetUserResponses, AdminPatchPartnerApplicationData, AdminPatchPartnerApplicationErrors, AdminPatchPartnerApplicationResponses, AdminPatchUserData, AdminPatchUserErrors, AdminPatchUserResponses, AdminPostPartnerApplicationDecisionData, AdminPostPartnerApplicationDecisionErrors, AdminPostPartnerApplicationDecisionResponses, AdminSearchUsersData, AdminSearchUsersErrors, AdminSearchUsersResponses, ComplexSearchProductsData, ComplexSearchProductsErrors, ComplexSearchProductsResponses, CreateUserSearchFilterData, CreateUserSearchFilterErrors, CreateUserSearchFilterResponses, DeleteAllNotificationsData, DeleteAllNotificationsErrors, DeleteAllNotificationsResponses, DeleteMyAccessTokenData, DeleteMyAccessTokenErrors, DeleteMyAccessTokenResponses, DeleteNotificationData, DeleteNotificationErrors, DeleteNotificationResponses, DeleteOAuthClientData, DeleteOAuthClientErrors, DeleteOAuthClientResponses, DeletePartnerApplicationData, DeletePartnerApplicationErrors, DeletePartnerApplicationResponses, DeleteUserData, DeleteUserErrors, DeleteUserResponses, DeleteUserSearchFilterData, DeleteUserSearchFilterErrors, DeleteUserSearchFilterResponses, DeleteWatchlistProductData, DeleteWatchlistProductErrors, DeleteWatchlistProductResponses, GetCategoriesData, GetCategoriesErrors, GetCategoriesResponses, GetCategoryByIdData, GetCategoryByIdErrors, GetCategoryByIdResponses, GetMyAccessTokenData, GetMyAccessTokenErrors, GetMyAccessTokenResponses, GetMyAccessTokensData, GetMyAccessTokensErrors, GetMyAccessTokensResponses, GetMyPartnerShopsData, GetMyPartnerShopsErrors, GetMyPartnerShopsResponses, GetNotificationsData, GetNotificationsErrors, GetNotificationsResponses, GetOAuthClientData, GetOAuthClientErrors, GetOAuthClientResponses, GetOAuthClientsData, GetOAuthClientsErrors, GetOAuthClientsResponses, GetPartnerApplicationData, GetPartnerApplicationErrors, GetPartnerApplicationResponses, GetPartnerApplicationsData, GetPartnerApplicationsErrors, GetPartnerApplicationsResponses, GetPeriodByIdData, GetPeriodByIdErrors, GetPeriodByIdResponses, GetPeriodsData, GetPeriodsErrors, GetPeriodsResponses, GetProductBySlugData, GetProductBySlugErrors, GetProductBySlugResponses, GetProductData2, GetProductErrors, GetProductHistoryData, GetProductHistoryErrors, GetProductHistoryResponses, GetProductResponses, GetSearchFilterLiveProductsData, GetSearchFilterLiveProductsErrors, GetSearchFilterLiveProductsResponses, GetSearchFilterMatchesData, GetSearchFilterMatchesErrors, GetSearchFilterMatchesResponses, GetShopByIdData, GetShopByIdErrors, GetShopByIdResponses, GetShopBySlugData, GetShopBySlugErrors, GetShopBySlugResponses, GetSimilarProductsData, GetSimilarProductsErrors, GetSimilarProductsResponses, GetUserAccountData2, GetUserAccountErrors, GetUserAccountResponses, GetUserSearchFilterData, GetUserSearchFilterErrors, GetUserSearchFilterResponses, GetUserSearchFiltersData, GetUserSearchFiltersErrors, GetUserSearchFiltersResponses, GetWatchlistProductsData, GetWatchlistProductsErrors, GetWatchlistProductsResponses, OauthAuthorizeData, OauthAuthorizeErrors, OauthIntrospectData, OauthIntrospectErrors, OauthIntrospectResponses, OauthRevokeData, OauthRevokeErrors, OauthRevokeResponses, OauthTokenData, OauthTokenErrors, OauthTokenResponses, PatchAllNotificationsData, PatchAllNotificationsErrors, PatchAllNotificationsResponses, PatchMyAccessTokenData, PatchMyAccessTokenErrors, PatchMyAccessTokenResponses, PatchNotificationData2, PatchNotificationErrors, PatchNotificationResponses, PatchOAuthClientData, PatchOAuthClientErrors, PatchOAuthClientResponses, PatchPartnerApplicationData, PatchPartnerApplicationErrors, PatchPartnerApplicationResponses, PatchPartnerProductsData, PatchPartnerProductsErrors, PatchPartnerProductsResponses, PatchShopByIdData, PatchShopByIdErrors, PatchShopByIdResponses, PatchWatchlistProductData, PatchWatchlistProductErrors, PatchWatchlistProductResponses, PostBillingCheckoutData2, PostBillingCheckoutErrors, PostBillingCheckoutResponses, PostBillingManageData, PostBillingManageErrors, PostBillingManageResponses, PostBillingPortalData, PostBillingPortalErrors, PostBillingPortalResponses, PostMyAccessTokenData, PostMyAccessTokenErrors, PostMyAccessTokenResponses, PostOAuthClientData, PostOAuthClientErrors, PostOAuthClientResponses, PostPartnerApplicationData, PostPartnerApplicationErrors, PostPartnerApplicationResponses, PostPartnerProductsData, PostPartnerProductsErrors, PostPartnerProductsResponses, PostShopData2, PostShopErrors, PostShopResponses, PostWoocommerceWebhookData, PostWoocommerceWebhookErrors, PostWoocommerceWebhookResponses, PutNewsletterSubscriptionData2, PutNewsletterSubscriptionErrors, PutNewsletterSubscriptionResponses, PutPartnerProductsData, PutPartnerProductsErrors, PutPartnerProductsResponses, SearchCategoriesData, SearchCategoriesErrors, SearchCategoriesResponses, SearchPeriodsData, SearchPeriodsErrors, SearchPeriodsResponses, SearchShopsData, SearchShopsErrors, SearchShopsResponses, SimpleSearchProductsData, SimpleSearchProductsErrors, SimpleSearchProductsResponses, SimpleSearchShopsData, SimpleSearchShopsErrors, SimpleSearchShopsResponses, UpdateSearchFilterMatchFeedbackData, UpdateSearchFilterMatchFeedbackErrors, UpdateSearchFilterMatchFeedbackResponses, UpdateUserAccountData, UpdateUserAccountErrors, UpdateUserAccountResponses, UpdateUserSearchFilterData, UpdateUserSearchFilterErrors, UpdateUserSearchFilterResponses } from './types.gen';
export type Options = Options2 & {
/**
@@ -21,9 +21,12 @@ export type Options(options: Options) => (options.client ?? client).patch({
- security: [{ name: 'x-api-key', type: 'apiKey' }],
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
url: '/api/v1/shops/{shopId}/products',
...options,
headers: {
@@ -49,9 +52,12 @@ export const patchPartnerProducts = (optio
/**
* Batch create products (Partner API)
*
- * Creates one or more products for a shop using API key authentication.
- * This endpoint is intended for partner shops — shops that have been granted partner status
- * and have an API key configured. It does **not** use Cognito JWT authentication.
+ * Creates one or more products for a shop using bearer authentication.
+ * This endpoint is intended for partner shops and accepts:
+ * - a Cognito bearer token for the partner user linked to the shop, or
+ * - an Aura Historia access token owned by that partner user.
+ *
+ * Aura Historia access tokens on this endpoint must include the `products:write` scope.
*
* The request body is an array of `PostProductData` objects. Each entry is forwarded
* individually to the asynchronous partner-product ingestion queue.
@@ -62,7 +68,7 @@ export const patchPartnerProducts = (optio
*
*/
export const postPartnerProducts = (options: Options) => (options.client ?? client).post({
- security: [{ name: 'x-api-key', type: 'apiKey' }],
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
url: '/api/v1/shops/{shopId}/products',
...options,
headers: {
@@ -75,15 +81,28 @@ export const postPartnerProducts = (option
* Batch upsert products (Partner API)
*
* Creates new products or updates existing ones for a shop in a single batch call,
- * using API key authentication. This endpoint is intended for partner shops — shops
- * that have been granted partner status and have an API key configured. It does **not**
- * use Cognito JWT authentication.
+ * using bearer authentication. This endpoint is intended for partner shops and accepts:
+ * - a Cognito bearer token for the partner user linked to the shop, or
+ * - an Aura Historia access token owned by that partner user.
+ *
+ * Aura Historia access tokens on this endpoint must include the `products:write` scope.
*
* The request body is an array of `PutProductData` objects. Each entry is forwarded
* individually to the asynchronous partner-product ingestion queue as an upsert command.
* When the queued command is later ingested:
- * - **Existing product** — only `state` and `price` are updated (other fields are ignored).
- * - **New product** — a full product is created using all provided fields.
+ * - **New product** — a full product is created using all provided fields. Omitting
+ * `title`, `url`, or `state` causes the backend to fall back to an empty title,
+ * a placeholder URL, and `LISTED` respectively.
+ * - **Existing product** — the backend applies `price`, `priceEstimateMin`,
+ * `priceEstimateMax`, `state`, `url`, `images`, `auctionStart`, and `auctionEnd`.
+ * `title`, `description`, `sellerName`, `structuredAddress`, and `geoAddress`
+ * are ignored on the update path.
+ * - On the update path, omitting or sending `null` for `price`, `priceEstimateMin`,
+ * `priceEstimateMax`, `url`, `auctionStart`, or `auctionEnd` leaves the stored value
+ * unchanged.
+ * - On the update path, `images` always replaces the stored image set; omitting
+ * `images` or sending `null` is treated as an empty list and therefore clears all
+ * stored images.
*
* The response returns HTTP 202 with an array containing only the `shopsProductId` values
* that failed to be forwarded to the queue. An empty array indicates that all upserts were
@@ -91,7 +110,7 @@ export const postPartnerProducts = (option
*
*/
export const putPartnerProducts = (options: Options) => (options.client ?? client).put({
- security: [{ name: 'x-api-key', type: 'apiKey' }],
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
url: '/api/v1/shops/{shopId}/products',
...options,
headers: {
@@ -103,11 +122,13 @@ export const putPartnerProducts = (options
/**
* Ingest a WooCommerce product webhook (Partner API)
*
- * Accepts a single WooCommerce product webhook event for a partner shop using API key authentication.
- * This endpoint is intended for partner shops and does **not** use Cognito JWT authentication.
+ * Accepts a single WooCommerce product webhook event for a partner shop using bearer authentication.
+ * This endpoint is intended for partner shops and accepts:
+ * - a Cognito bearer token for the partner user linked to the shop, or
+ * - an Aura Historia access token owned by that partner user.
*
* The caller must provide:
- * - the partner shop API key in the `x-api-key` header,
+ * - the bearer token in the `Authorization` header,
* - the WooCommerce topic in `x-wc-webhook-topic`, and
* - the base64-encoded HMAC-SHA256 signature of the raw request body in `x-wc-webhook-signature`.
*
@@ -121,7 +142,7 @@ export const putPartnerProducts = (options
*
*/
export const postWoocommerceWebhook = (options: Options) => (options.client ?? client).post({
- security: [{ name: 'x-api-key', type: 'apiKey' }],
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
url: '/api/v1/webhooks/woocommerce/{shopId}',
...options,
headers: {
@@ -572,6 +593,84 @@ export const updateUserAccount = (options:
}
});
+/**
+ * List Aura Historia access tokens
+ *
+ * Lists the authenticated user's non-expired Aura Historia access tokens.
+ * The `token` field is masked on this endpoint and does not reveal the plaintext bearer token again.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getMyAccessTokens = (options?: Options) => (options?.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/me/access-tokens',
+ ...options
+});
+
+/**
+ * Update an Aura Historia access token
+ *
+ * Updates metadata for one access token owned by the authenticated user.
+ * Omitted or `null` optional properties leave the existing value unchanged.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const patchMyAccessToken = (options: Options) => (options.client ?? client).patch({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/me/access-tokens',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
+/**
+ * Create an Aura Historia access token
+ *
+ * Creates a new Aura Historia access token for the authenticated user.
+ * The plaintext bearer token is returned only in this create response; later reads return a masked token value.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const postMyAccessToken = (options: Options) => (options.client ?? client).post({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/me/access-tokens',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
+/**
+ * Delete an Aura Historia access token
+ *
+ * Deletes one access token owned by the authenticated user.
+ * The token to delete is identified by the required `{accessTokenId}` path parameter.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const deleteMyAccessToken = (options: Options) => (options.client ?? client).delete({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/me/access-tokens/{accessTokenId}',
+ ...options
+});
+
+/**
+ * Get one Aura Historia access token
+ *
+ * Retrieves one non-expired Aura Historia access token owned by the authenticated user.
+ * The `token` field is masked on this endpoint and does not reveal the plaintext bearer token again.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getMyAccessToken = (options: Options) => (options.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/me/access-tokens/{accessTokenId}',
+ ...options
+});
+
/**
* Create Stripe checkout session
*
@@ -775,6 +874,10 @@ export const putNewsletterSubscription = (
* Performs shop search using query parameters instead of a JSON request body.
* This is the cache-friendly equivalent of `POST /api/v1/shops/search`.
*
+ * Authentication is optional on this endpoint:
+ * - unauthenticated requests return cacheable shared responses
+ * - authenticated Cognito JWT or Aura Historia access-token requests return `Cache-Control: no-store`
+ *
* All optional filters from `ShopSearchData` are supported as query parameters
* using the same field names:
* - `shopNameQuery`
@@ -786,7 +889,11 @@ export const putNewsletterSubscription = (
* - `updated[min]`, `updated[max]`
*
*/
-export const simpleSearchShops = (options?: Options) => (options?.client ?? client).get({ url: '/api/v1/shops', ...options });
+export const simpleSearchShops = (options?: Options) => (options?.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/shops',
+ ...options
+});
/**
* Create a shop
@@ -815,8 +922,16 @@ export const postShop = (options: Options<
* Retrieves detailed information about a specific shop by its shop ID (UUID).
* Returns complete shop metadata including name, domains, image, address/contact metadata, and timestamps.
*
+ * Authentication is optional on this endpoint:
+ * - unauthenticated requests return cacheable shared responses
+ * - authenticated Cognito JWT or Aura Historia access-token requests return `Cache-Control: no-store`
+ *
*/
-export const getShopById = (options: Options) => (options.client ?? client).get({ url: '/api/v1/shops/{shopId}', ...options });
+export const getShopById = (options: Options) => (options.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/shops/{shopId}',
+ ...options
+});
/**
* Update shop details
@@ -825,7 +940,7 @@ export const getShopById = (options: Optio
*
* Requires either:
* - a valid Cognito JWT for the partner user assigned to the shop or for an `ADMIN`, or
- * - the partner shop's `x-api-key` when no Cognito identity is present.
+ * - a valid Aura Historia access token owned by the partner user assigned to the shop.
*
* The request body uses `PatchShopData`. Only fields present in the JSON body are applied;
* omitted or `null` fields are left unchanged. The HTTP request body itself must not be absent
@@ -833,7 +948,7 @@ export const getShopById = (options: Optio
*
*/
export const patchShopById = (options: Options) => (options.client ?? client).patch({
- security: [{ scheme: 'bearer', type: 'http' }, { name: 'x-api-key', type: 'apiKey' }],
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
url: '/api/v1/shops/{shopId}',
...options,
headers: {
@@ -842,36 +957,25 @@ export const patchShopById = (options: Opt
}
});
-/**
- * Create or overwrite a partner shop API key
- *
- * Creates a new partner API key for the specified shop and returns the plaintext key in the response.
- * If a key already exists for the shop, it is overwritten.
- *
- * Requires a valid Cognito JWT. The caller must either:
- * - be the partner user assigned to the shop, or
- * - have the `ADMIN` role.
- *
- * The returned key is only available in this response. The backend stores only a hash.
- *
- */
-export const putShopApiKey = (options: Options) => (options.client ?? client).put({
- security: [{ scheme: 'bearer', type: 'http' }],
- url: '/api/v1/shops/{shopId}/api-key',
- ...options
-});
-
/**
* Get shop details by slug
*
* Retrieves detailed information about a specific shop by its human-readable slug identifier.
* Returns complete shop metadata including name, domains, image, address/contact metadata, and timestamps.
*
+ * Authentication is optional on this endpoint:
+ * - unauthenticated requests return cacheable shared responses
+ * - authenticated Cognito JWT or Aura Historia access-token requests return `Cache-Control: no-store`
+ *
* **Human-Readable Identifiers**: This endpoint uses slug-based identifiers which are human-readable
* kebab-case strings derived from the shop name (e.g., "tech-store-premium" or "christies").
*
*/
-export const getShopBySlug = (options: Options) => (options.client ?? client).get({ url: '/api/v1/by-slug/shops/{shopSlugId}', ...options });
+export const getShopBySlug = (options: Options) => (options.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }, { scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/by-slug/shops/{shopSlugId}',
+ ...options
+});
/**
* Search shops
@@ -997,20 +1101,17 @@ export const searchPeriods = (options: Opt
});
/**
- * List shops for a partner user
- *
- * Returns all shops whose `partner_user_id` matches the requested `partnerId`.
+ * List the authenticated user's partner shops
*
- * Requires a valid Cognito JWT. The caller may request:
- * - their own shops (`partnerId` equals the JWT `sub`), or
- * - another partner's shops when the caller has the `ADMIN` role.
+ * Returns all shops currently linked to the authenticated user's `partnerShops` set.
+ * Requires a valid Cognito JWT and always resolves shops for the current user only.
*
- * Returns an empty array when the partner currently has no shops.
+ * Returns an empty array when the authenticated user currently has no linked partner shops.
*
*/
-export const getPartnerShops = (options: Options) => (options.client ?? client).get({
+export const getMyPartnerShops = (options?: Options) => (options?.client ?? client).get({
security: [{ scheme: 'bearer', type: 'http' }],
- url: '/api/v1/partner/{partnerId}/shops',
+ url: '/api/v1/me/partner-shops',
...options
});
@@ -1168,3 +1269,165 @@ export const adminPostPartnerApplicationDecision = (options: Options) => (options.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/authorize',
+ ...options
+});
+
+/**
+ * OAuth2 token endpoint
+ *
+ * Exchanges a single-use authorization code for an Aura Historia access token (RFC 6749 §4.1.3).
+ * The request body must be `application/x-www-form-urlencoded`. Client authentication is
+ * performed via `client_id` / `client_secret` form fields. PKCE code verification (RFC 7636)
+ * is mandatory: `code_verifier` must produce the `code_challenge` supplied to the authorize
+ * endpoint.
+ *
+ * The issued access token is non-expiring by default (same lifetime as tokens created via
+ * `POST /api/v1/me/access-tokens`).
+ *
+ */
+export const oauthToken = (options: Options) => (options.client ?? client).post({
+ ...urlSearchParamsBodySerializer,
+ url: '/api/v1/oauth/token',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ ...options.headers
+ }
+});
+
+/**
+ * OAuth2 token revocation endpoint
+ *
+ * Revokes an Aura Historia access token that was issued through the OAuth authorization code
+ * flow (RFC 7009). The request body must be `application/x-www-form-urlencoded`. Client
+ * authentication is performed via `client_id` / `client_secret` form fields.
+ *
+ * After revocation the token is immediately inactive and introspection will return
+ * `active: false`. Attempting to revoke an unknown or already-revoked token is not an error.
+ *
+ */
+export const oauthRevoke = (options: Options) => (options.client ?? client).post({
+ ...urlSearchParamsBodySerializer,
+ url: '/api/v1/oauth/revoke',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ ...options.headers
+ }
+});
+
+/**
+ * OAuth2 token introspection endpoint
+ *
+ * Returns metadata about an Aura Historia access token (RFC 7662). The request body must be
+ * `application/x-www-form-urlencoded`. Client authentication is performed via `client_id` /
+ * `client_secret` form fields.
+ *
+ * If the token is unknown, expired, or revoked, `active` is `false` and all other fields are
+ * omitted. Otherwise `active` is `true` and the available token metadata is populated.
+ *
+ */
+export const oauthIntrospect = (options: Options) => (options.client ?? client).post({
+ ...urlSearchParamsBodySerializer,
+ url: '/api/v1/oauth/introspect',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ ...options.headers
+ }
+});
+
+/**
+ * List OAuth client metadata
+ *
+ * Lists registered OAuth client metadata records.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getOAuthClients = (options?: Options) => (options?.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/clients',
+ ...options
+});
+
+/**
+ * Create OAuth client metadata
+ *
+ * Creates a new OAuth client metadata record.
+ * The plaintext `client_secret` is returned only in this create response; later reads return
+ * a masked secret display value.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const postOAuthClient = (options: Options) => (options.client ?? client).post({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/clients',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
+
+/**
+ * Delete OAuth client metadata
+ *
+ * Deletes one OAuth client metadata record.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const deleteOAuthClient = (options: Options) => (options.client ?? client).delete({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/clients/{clientId}',
+ ...options
+});
+
+/**
+ * Get OAuth client metadata
+ *
+ * Retrieves one OAuth client metadata record.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication.
+ *
+ */
+export const getOAuthClient = (options: Options) => (options.client ?? client).get({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/clients/{clientId}',
+ ...options
+});
+
+/**
+ * Update OAuth client metadata
+ *
+ * Updates one OAuth client metadata record.
+ * Omitted or `null` optional properties leave the existing value unchanged.
+ * The returned `client_secret` value is masked and does not reveal the plaintext secret.
+ * Requires valid Cognito JWT authentication and the `ADMIN` role.
+ *
+ */
+export const patchOAuthClient = (options: Options) => (options.client ?? client).patch({
+ security: [{ scheme: 'bearer', type: 'http' }],
+ url: '/api/v1/oauth/clients/{clientId}',
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ }
+});
diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts
index cab871b1..e8983fbf 100644
--- a/src/client/types.gen.ts
+++ b/src/client/types.gen.ts
@@ -1600,15 +1600,284 @@ export type PostShopData = {
};
/**
- * Response body returned after creating or overwriting a partner shop API key.
+ * Aura Historia access-token scope string.
+ * - `shops:manage`: reserved for shop-management flows
+ * - `products:write`: required for partner batch product ingestion
+ *
+ */
+export type AccessTokenScopeData = 'shops:manage' | 'products:write';
+
+/**
+ * Token type returned for Aura Historia access tokens.
+ */
+export type AccessTokenTypeData = 'BEARER';
+
+/**
+ * Access-token read model.
+ * `POST /api/v1/me/access-tokens` returns the plaintext bearer token once;
+ * list/get/update endpoints return the masked display value instead.
+ *
+ */
+export type GetAccessTokenData = {
+ /**
+ * Unique identifier of the access token.
+ */
+ accessTokenId: string;
+ /**
+ * User-defined display name of the access token.
+ */
+ name: string;
+ /**
+ * Granted access-token scopes. Omitted when the token has no scopes.
+ */
+ scope?: Array;
+ /**
+ * Plaintext or masked bearer token value.
+ * Create responses return the full token once; subsequent reads return a masked value such as `aurahistoria_abcdefghijk_****`.
+ *
+ */
+ token: string;
+ tokenType: AccessTokenTypeData;
+ /**
+ * Optional absolute expiration timestamp in RFC 3339 format.
+ */
+ expiresAt?: string | null;
+ /**
+ * Optional non-negative number of seconds remaining until expiry.
+ */
+ expiresIn?: number | null;
+ /**
+ * Timestamp when the access token was created.
+ */
+ created: string;
+ /**
+ * Timestamp when the access token metadata was last updated.
+ */
+ updated: string;
+};
+
+/**
+ * Request body for creating an Aura Historia access token.
+ */
+export type PostAccessTokenData = {
+ /**
+ * User-defined display name of the new access token.
+ */
+ name: string;
+ /**
+ * Optional scopes granted to the access token. Defaults to an empty set.
+ */
+ scope?: Array;
+ /**
+ * Optional expiration timestamp in RFC 3339 format.
+ */
+ expiresAt?: string | null;
+};
+
+/**
+ * Request body for updating access-token metadata.
+ * Optional fields that are omitted or set to `null` leave the stored value unchanged.
+ *
+ */
+export type PatchAccessTokenData = {
+ /**
+ * Unique identifier of the access token to update.
+ */
+ accessTokenId: string;
+ /**
+ * Optional replacement display name.
+ */
+ name?: string | null;
+ /**
+ * Optional replacement scope set.
+ */
+ scope?: Array | null;
+ /**
+ * Optional replacement expiration timestamp in RFC 3339 format.
+ */
+ expiresAt?: string | null;
+};
+
+/**
+ * Request body for creating OAuth client metadata.
+ * `redirect_uris` must contain at least one HTTPS redirect URI.
+ *
+ */
+export type OAuthClientMetadataRequestData = {
+ /**
+ * Display name of the OAuth client.
+ */
+ client_name: string;
+ /**
+ * HTTPS URL of the OAuth client's terms-of-service document.
+ */
+ tos_uri: string;
+ /**
+ * HTTPS URL of the OAuth client's privacy policy document.
+ */
+ policy_uri: string;
+ /**
+ * HTTPS URL of the OAuth client's homepage or product site.
+ */
+ client_uri: string;
+ /**
+ * HTTPS URL of the OAuth client's logo image.
+ */
+ logo_uri: string;
+ /**
+ * Registered HTTPS redirect URIs for the OAuth client.
+ */
+ redirect_uris: Array;
+ /**
+ * Optional allowed scopes for tokens issued to this OAuth client.
+ */
+ scope?: Array;
+};
+
+/**
+ * Request body for updating OAuth client metadata.
+ * Omitted or `null` optional properties leave the stored value unchanged.
+ *
+ */
+export type OAuthClientMetadataPatchData = {
+ /**
+ * Optional replacement display name for the OAuth client.
+ */
+ client_name?: string | null;
+ /**
+ * Optional replacement terms-of-service document URL for the OAuth client.
+ */
+ tos_uri?: string | null;
+ /**
+ * Optional replacement privacy-policy document URL for the OAuth client.
+ */
+ policy_uri?: string | null;
+ /**
+ * Optional replacement homepage or product-site URL for the OAuth client.
+ */
+ client_uri?: string | null;
+ /**
+ * Optional replacement logo image URL for the OAuth client.
+ */
+ logo_uri?: string | null;
+ /**
+ * Optional replacement set of registered HTTPS redirect URIs.
+ */
+ redirect_uris?: Array | null;
+ /**
+ * Optional replacement set of allowed scopes for the OAuth client.
+ */
+ scope?: Array | null;
+};
+
+/**
+ * OAuth client metadata read model.
+ * `client_secret` contains the plaintext secret only in the create response.
+ * List/get/update responses return a masked secret display value instead.
+ *
*/
-export type PartnerShopApiKeyResponse = {
+export type OAuthClientMetadataResponseData = {
/**
- * Plaintext partner shop API key.
- * Returned only when the key is created; subsequent verification uses the `x-api-key` request header.
+ * UUIDv7 identifier of the OAuth client.
+ */
+ client_id: string;
+ /**
+ * OAuth client secret.
+ * Create responses return the plaintext secret once; later reads return a masked value such as `aurahistoria_oauth_client_secret_abcdefghijk_****`.
*
*/
- apiKey: string;
+ client_secret: string;
+ /**
+ * Display name of the OAuth client.
+ */
+ client_name: string;
+ /**
+ * Terms-of-service document URL registered for the OAuth client.
+ */
+ tos_uri: string;
+ /**
+ * Privacy-policy document URL registered for the OAuth client.
+ */
+ policy_uri: string;
+ /**
+ * Homepage or product-site URL registered for the OAuth client.
+ */
+ client_uri: string;
+ /**
+ * Logo image URL registered for the OAuth client.
+ */
+ logo_uri: string;
+ /**
+ * Registered redirect URIs for the OAuth client.
+ */
+ redirect_uris: Array;
+ /**
+ * Allowed scopes for tokens issued to this OAuth client.
+ */
+ scope: Array;
+ /**
+ * Unix timestamp (seconds) when the OAuth client was created and its client ID was issued.
+ */
+ client_id_issued_at: number;
+};
+
+/**
+ * Token response returned by `POST /api/v1/oauth/token` after a successful authorization code
+ * exchange (RFC 6749 §5.1). The `access_token` is an Aura Historia bearer access token.
+ * `expires_in` is omitted (`null`) for non-expiring tokens.
+ *
+ */
+export type OAuthTokenResponseData = {
+ /**
+ * The issued Aura Historia access token (plaintext bearer value).
+ */
+ access_token: string;
+ token_type: AccessTokenTypeData;
+ /**
+ * Seconds until the access token expires. `null` when the token does not expire.
+ */
+ expires_in?: number | null;
+ /**
+ * Space-separated list of scopes granted to the access token.
+ */
+ scope: string;
+};
+
+/**
+ * Introspection response returned by `POST /api/v1/oauth/introspect` (RFC 7662).
+ * When `active` is `false` all other fields are omitted.
+ * When `active` is `true` the available token metadata is populated.
+ *
+ */
+export type OAuthIntrospectionResponseData = {
+ /**
+ * Whether the token is currently active (not expired, not revoked, and known to this server).
+ */
+ active: boolean;
+ /**
+ * Space-separated list of scopes granted to the token. Present only when `active` is `true`.
+ */
+ scope?: string;
+ /**
+ * UUIDv7 OAuth client identifier that issued the token. Present only when `active` is `true` and the token was issued via the OAuth flow.
+ */
+ client_id?: string;
+ /**
+ * Subject — the Aura Historia user ID who authorized the token. Present only when `active` is `true`.
+ */
+ sub?: string;
+ /**
+ * Token type. Always `"Bearer"` when `active` is `true`.
+ */
+ token_type?: string;
+ /**
+ * Unix timestamp (seconds) when the token expires. Present only when `active` is `true` and the token has an expiry.
+ */
+ exp?: number;
+ /**
+ * Unix timestamp (seconds) when the token was issued. Present only when `active` is `true` and the issue time is known.
+ */
+ iat?: number;
};
/**
@@ -3272,13 +3541,13 @@ export type PatchPartnerProductsErrors = {
*/
400: ApiError;
/**
- * Unauthorized — the `x-api-key` header is missing, malformed, or the API key does not
- * match the key stored for the shop.
+ * Unauthorized — the bearer token is missing, invalid, expired, or the referenced
+ * Aura Historia access token no longer exists.
*
*/
401: ApiError;
/**
- * Forbidden — the shop exists but has not been granted partner status
+ * Forbidden — the authenticated caller is not allowed to ingest products for this shop
*/
403: ApiError;
/**
@@ -3330,13 +3599,13 @@ export type PostPartnerProductsErrors = {
*/
400: ApiError;
/**
- * Unauthorized — the `x-api-key` header is missing, malformed, or the API key does not
- * match the key stored for the shop.
+ * Unauthorized — the bearer token is missing, invalid, expired, or the referenced
+ * Aura Historia access token no longer exists.
*
*/
401: ApiError;
/**
- * Forbidden — the shop exists but has not been granted partner status
+ * Forbidden — the authenticated caller is not allowed to ingest products for this shop
*/
403: ApiError;
/**
@@ -3388,13 +3657,13 @@ export type PutPartnerProductsErrors = {
*/
400: ApiError;
/**
- * Unauthorized — the `x-api-key` header is missing, malformed, or the API key does not
- * match the key stored for the shop.
+ * Unauthorized — the bearer token is missing, invalid, expired, or the referenced
+ * Aura Historia access token no longer exists.
*
*/
401: ApiError;
/**
- * Forbidden — the shop exists but has not been granted partner status
+ * Forbidden — the authenticated caller is not allowed to ingest products for this shop
*/
403: ApiError;
/**
@@ -3467,13 +3736,13 @@ export type PostWoocommerceWebhookErrors = {
*/
400: ApiError;
/**
- * Unauthorized — the `x-api-key` header is missing, malformed, or does not match the
- * stored partner key, or the WooCommerce signature header is missing/invalid.
+ * Unauthorized — the bearer token is missing or invalid, the Aura Historia access token
+ * is unknown, or the WooCommerce signature header is missing/invalid.
*
*/
401: ApiError;
/**
- * Forbidden — the shop exists but has not been granted partner status
+ * Forbidden — the authenticated caller is not linked to the requested partner shop
*/
403: ApiError;
/**
@@ -4883,21 +5152,14 @@ export type UpdateUserAccountResponses = {
export type UpdateUserAccountResponse = UpdateUserAccountResponses[keyof UpdateUserAccountResponses];
-export type PostBillingCheckoutData2 = {
- /**
- * Desired subscription plan and billing cycle for the new Stripe checkout session.
- */
- body: PostBillingCheckoutData;
+export type GetMyAccessTokensData = {
+ body?: never;
path?: never;
query?: never;
- url: '/api/v1/me/billing/checkout';
+ url: '/api/v1/me/access-tokens';
};
-export type PostBillingCheckoutErrors = {
- /**
- * Bad request - request body is missing, empty, malformed JSON, or contains unsupported enum values
- */
- 400: ApiError;
+export type GetMyAccessTokensErrors = {
/**
* Unauthorized - invalid or missing JWT token
*/
@@ -4906,77 +5168,76 @@ export type PostBillingCheckoutErrors = {
* User not found
*/
404: ApiError;
- /**
- * Conflict - the authenticated user already has a Stripe customer record
- */
- 409: ApiError;
/**
* Internal server error
*/
500: ApiError;
};
-export type PostBillingCheckoutError = PostBillingCheckoutErrors[keyof PostBillingCheckoutErrors];
+export type GetMyAccessTokensError = GetMyAccessTokensErrors[keyof GetMyAccessTokensErrors];
-export type PostBillingCheckoutResponses = {
+export type GetMyAccessTokensResponses = {
/**
- * Checkout session created successfully
+ * Access tokens retrieved successfully
*/
- 201: BillingSessionUrlData;
+ 200: Array;
};
-export type PostBillingCheckoutResponse = PostBillingCheckoutResponses[keyof PostBillingCheckoutResponses];
+export type GetMyAccessTokensResponse = GetMyAccessTokensResponses[keyof GetMyAccessTokensResponses];
-export type PostBillingPortalData = {
- body?: never;
+export type PatchMyAccessTokenData = {
+ /**
+ * Partial access-token update payload.
+ */
+ body: PatchAccessTokenData;
path?: never;
query?: never;
- url: '/api/v1/me/billing/portal';
+ url: '/api/v1/me/access-tokens';
};
-export type PostBillingPortalErrors = {
+export type PatchMyAccessTokenErrors = {
+ /**
+ * Bad request - missing, empty, or invalid JSON body
+ */
+ 400: ApiError;
/**
* Unauthorized - invalid or missing JWT token
*/
401: ApiError;
/**
- * User not found
+ * Access token not found
*/
404: ApiError;
- /**
- * Unprocessable Content - the authenticated user has no Stripe customer record yet
- */
- 422: ApiError;
/**
* Internal server error
*/
500: ApiError;
};
-export type PostBillingPortalError = PostBillingPortalErrors[keyof PostBillingPortalErrors];
+export type PatchMyAccessTokenError = PatchMyAccessTokenErrors[keyof PatchMyAccessTokenErrors];
-export type PostBillingPortalResponses = {
+export type PatchMyAccessTokenResponses = {
/**
- * Billing portal session created successfully
+ * Access token updated successfully
*/
- 201: BillingSessionUrlData;
+ 200: GetAccessTokenData;
};
-export type PostBillingPortalResponse = PostBillingPortalResponses[keyof PostBillingPortalResponses];
+export type PatchMyAccessTokenResponse = PatchMyAccessTokenResponses[keyof PatchMyAccessTokenResponses];
-export type PostBillingManageData = {
+export type PostMyAccessTokenData = {
/**
- * Desired subscription plan and billing cycle. Required for all callers.
+ * Access-token creation payload.
*/
- body: PostBillingCheckoutData;
+ body: PostAccessTokenData;
path?: never;
query?: never;
- url: '/api/v1/me/billing/manage';
+ url: '/api/v1/me/access-tokens';
};
-export type PostBillingManageErrors = {
+export type PostMyAccessTokenErrors = {
/**
- * Bad request - request body is missing, empty, malformed JSON, or contains unsupported enum values
+ * Bad request - missing, empty, or invalid JSON body
*/
400: ApiError;
/**
@@ -4987,46 +5248,38 @@ export type PostBillingManageErrors = {
* User not found
*/
404: ApiError;
- /**
- * Unprocessable Content - the authenticated paid user has no Stripe customer record yet
- */
- 422: ApiError;
/**
* Internal server error
*/
500: ApiError;
};
-export type PostBillingManageError = PostBillingManageErrors[keyof PostBillingManageErrors];
+export type PostMyAccessTokenError = PostMyAccessTokenErrors[keyof PostMyAccessTokenErrors];
-export type PostBillingManageResponses = {
+export type PostMyAccessTokenResponses = {
/**
- * Billing management session created successfully
+ * Access token created successfully
*/
- 201: BillingSessionUrlData;
+ 201: GetAccessTokenData;
};
-export type PostBillingManageResponse = PostBillingManageResponses[keyof PostBillingManageResponses];
+export type PostMyAccessTokenResponse = PostMyAccessTokenResponses[keyof PostMyAccessTokenResponses];
-export type DeleteWatchlistProductData = {
+export type DeleteMyAccessTokenData = {
body?: never;
path: {
/**
- * Unique identifier of the shop
- */
- shopId: string;
- /**
- * Shop's unique identifier for the product
+ * Unique identifier of the access token to delete
*/
- shopsProductId: string;
+ accessTokenId: string;
};
query?: never;
- url: '/api/v1/me/watchlist/{shopId}/{shopsProductId}';
+ url: '/api/v1/me/access-tokens/{accessTokenId}';
};
-export type DeleteWatchlistProductErrors = {
+export type DeleteMyAccessTokenErrors = {
/**
- * Bad request - invalid parameters
+ * Bad request - invalid or missing access-token path parameter
*/
400: ApiError;
/**
@@ -5034,7 +5287,7 @@ export type DeleteWatchlistProductErrors = {
*/
401: ApiError;
/**
- * Watchlist entry not found
+ * Access token not found
*/
404: ApiError;
/**
@@ -5043,50 +5296,32 @@ export type DeleteWatchlistProductErrors = {
500: ApiError;
};
-export type DeleteWatchlistProductError = DeleteWatchlistProductErrors[keyof DeleteWatchlistProductErrors];
+export type DeleteMyAccessTokenError = DeleteMyAccessTokenErrors[keyof DeleteMyAccessTokenErrors];
-export type DeleteWatchlistProductResponses = {
+export type DeleteMyAccessTokenResponses = {
/**
- * Product removed from watchlist successfully
+ * Access token deleted successfully
*/
204: void;
};
-export type DeleteWatchlistProductResponse = DeleteWatchlistProductResponses[keyof DeleteWatchlistProductResponses];
+export type DeleteMyAccessTokenResponse = DeleteMyAccessTokenResponses[keyof DeleteMyAccessTokenResponses];
-export type PatchWatchlistProductData = {
- /**
- * Patch object containing fields to update
- */
- body: WatchlistProductPatch;
+export type GetMyAccessTokenData = {
+ body?: never;
path: {
/**
- * Unique identifier of the shop
- */
- shopId: string;
- /**
- * Shop's unique identifier for the product
- */
- shopsProductId: string;
- };
- query?: {
- /**
- * Preferred language for localized content in the response.
- * Defaults to `en` when omitted.
- *
- */
- language?: LanguageData;
- /**
- * Currency for price display in the response.
+ * Unique identifier of the access token
*/
- currency?: CurrencyData;
+ accessTokenId: string;
};
- url: '/api/v1/me/watchlist/{shopId}/{shopsProductId}';
+ query?: never;
+ url: '/api/v1/me/access-tokens/{accessTokenId}';
};
-export type PatchWatchlistProductErrors = {
+export type GetMyAccessTokenErrors = {
/**
- * Bad request - invalid parameters or body
+ * Bad request - invalid or missing access-token path parameter
*/
400: ApiError;
/**
@@ -5094,34 +5329,265 @@ export type PatchWatchlistProductErrors = {
*/
401: ApiError;
/**
- * Watchlist entry not found
+ * Access token not found
*/
404: ApiError;
- /**
- * Unprocessable Entity - watchlist quota exceeded during reactivation
- */
- 422: ApiError;
/**
* Internal server error
*/
500: ApiError;
};
-export type PatchWatchlistProductError = PatchWatchlistProductErrors[keyof PatchWatchlistProductErrors];
+export type GetMyAccessTokenError = GetMyAccessTokenErrors[keyof GetMyAccessTokenErrors];
-export type PatchWatchlistProductResponses = {
+export type GetMyAccessTokenResponses = {
/**
- * Watchlist product updated successfully
+ * Access token retrieved successfully
*/
- 200: PersonalizedGetProductData;
+ 200: GetAccessTokenData;
};
-export type PatchWatchlistProductResponse = PatchWatchlistProductResponses[keyof PatchWatchlistProductResponses];
+export type GetMyAccessTokenResponse = GetMyAccessTokenResponses[keyof GetMyAccessTokenResponses];
-export type DeleteAllNotificationsData = {
- body?: never;
- path?: never;
- query?: never;
+export type PostBillingCheckoutData2 = {
+ /**
+ * Desired subscription plan and billing cycle for the new Stripe checkout session.
+ */
+ body: PostBillingCheckoutData;
+ path?: never;
+ query?: never;
+ url: '/api/v1/me/billing/checkout';
+};
+
+export type PostBillingCheckoutErrors = {
+ /**
+ * Bad request - request body is missing, empty, malformed JSON, or contains unsupported enum values
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized - invalid or missing JWT token
+ */
+ 401: ApiError;
+ /**
+ * User not found
+ */
+ 404: ApiError;
+ /**
+ * Conflict - the authenticated user already has a Stripe customer record
+ */
+ 409: ApiError;
+ /**
+ * Internal server error
+ */
+ 500: ApiError;
+};
+
+export type PostBillingCheckoutError = PostBillingCheckoutErrors[keyof PostBillingCheckoutErrors];
+
+export type PostBillingCheckoutResponses = {
+ /**
+ * Checkout session created successfully
+ */
+ 201: BillingSessionUrlData;
+};
+
+export type PostBillingCheckoutResponse = PostBillingCheckoutResponses[keyof PostBillingCheckoutResponses];
+
+export type PostBillingPortalData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/api/v1/me/billing/portal';
+};
+
+export type PostBillingPortalErrors = {
+ /**
+ * Unauthorized - invalid or missing JWT token
+ */
+ 401: ApiError;
+ /**
+ * User not found
+ */
+ 404: ApiError;
+ /**
+ * Unprocessable Content - the authenticated user has no Stripe customer record yet
+ */
+ 422: ApiError;
+ /**
+ * Internal server error
+ */
+ 500: ApiError;
+};
+
+export type PostBillingPortalError = PostBillingPortalErrors[keyof PostBillingPortalErrors];
+
+export type PostBillingPortalResponses = {
+ /**
+ * Billing portal session created successfully
+ */
+ 201: BillingSessionUrlData;
+};
+
+export type PostBillingPortalResponse = PostBillingPortalResponses[keyof PostBillingPortalResponses];
+
+export type PostBillingManageData = {
+ /**
+ * Desired subscription plan and billing cycle. Required for all callers.
+ */
+ body: PostBillingCheckoutData;
+ path?: never;
+ query?: never;
+ url: '/api/v1/me/billing/manage';
+};
+
+export type PostBillingManageErrors = {
+ /**
+ * Bad request - request body is missing, empty, malformed JSON, or contains unsupported enum values
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized - invalid or missing JWT token
+ */
+ 401: ApiError;
+ /**
+ * User not found
+ */
+ 404: ApiError;
+ /**
+ * Unprocessable Content - the authenticated paid user has no Stripe customer record yet
+ */
+ 422: ApiError;
+ /**
+ * Internal server error
+ */
+ 500: ApiError;
+};
+
+export type PostBillingManageError = PostBillingManageErrors[keyof PostBillingManageErrors];
+
+export type PostBillingManageResponses = {
+ /**
+ * Billing management session created successfully
+ */
+ 201: BillingSessionUrlData;
+};
+
+export type PostBillingManageResponse = PostBillingManageResponses[keyof PostBillingManageResponses];
+
+export type DeleteWatchlistProductData = {
+ body?: never;
+ path: {
+ /**
+ * Unique identifier of the shop
+ */
+ shopId: string;
+ /**
+ * Shop's unique identifier for the product
+ */
+ shopsProductId: string;
+ };
+ query?: never;
+ url: '/api/v1/me/watchlist/{shopId}/{shopsProductId}';
+};
+
+export type DeleteWatchlistProductErrors = {
+ /**
+ * Bad request - invalid parameters
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized - invalid or missing JWT token
+ */
+ 401: ApiError;
+ /**
+ * Watchlist entry not found
+ */
+ 404: ApiError;
+ /**
+ * Internal server error
+ */
+ 500: ApiError;
+};
+
+export type DeleteWatchlistProductError = DeleteWatchlistProductErrors[keyof DeleteWatchlistProductErrors];
+
+export type DeleteWatchlistProductResponses = {
+ /**
+ * Product removed from watchlist successfully
+ */
+ 204: void;
+};
+
+export type DeleteWatchlistProductResponse = DeleteWatchlistProductResponses[keyof DeleteWatchlistProductResponses];
+
+export type PatchWatchlistProductData = {
+ /**
+ * Patch object containing fields to update
+ */
+ body: WatchlistProductPatch;
+ path: {
+ /**
+ * Unique identifier of the shop
+ */
+ shopId: string;
+ /**
+ * Shop's unique identifier for the product
+ */
+ shopsProductId: string;
+ };
+ query?: {
+ /**
+ * Preferred language for localized content in the response.
+ * Defaults to `en` when omitted.
+ *
+ */
+ language?: LanguageData;
+ /**
+ * Currency for price display in the response.
+ */
+ currency?: CurrencyData;
+ };
+ url: '/api/v1/me/watchlist/{shopId}/{shopsProductId}';
+};
+
+export type PatchWatchlistProductErrors = {
+ /**
+ * Bad request - invalid parameters or body
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized - invalid or missing JWT token
+ */
+ 401: ApiError;
+ /**
+ * Watchlist entry not found
+ */
+ 404: ApiError;
+ /**
+ * Unprocessable Entity - watchlist quota exceeded during reactivation
+ */
+ 422: ApiError;
+ /**
+ * Internal server error
+ */
+ 500: ApiError;
+};
+
+export type PatchWatchlistProductError = PatchWatchlistProductErrors[keyof PatchWatchlistProductErrors];
+
+export type PatchWatchlistProductResponses = {
+ /**
+ * Watchlist product updated successfully
+ */
+ 200: PersonalizedGetProductData;
+};
+
+export type PatchWatchlistProductResponse = PatchWatchlistProductResponses[keyof PatchWatchlistProductResponses];
+
+export type DeleteAllNotificationsData = {
+ body?: never;
+ path?: never;
+ query?: never;
url: '/api/v1/me/notifications';
};
@@ -5554,7 +6020,7 @@ export type PatchShopByIdErrors = {
*/
400: ApiError;
/**
- * Unauthorized – invalid or missing Cognito JWT when using bearer auth, or missing/malformed/mismatched `x-api-key` when using partner API-key auth.
+ * Unauthorized – the bearer token is missing or invalid.
*/
401: ApiError;
/**
@@ -5582,31 +6048,23 @@ export type PatchShopByIdResponses = {
export type PatchShopByIdResponse = PatchShopByIdResponses[keyof PatchShopByIdResponses];
-export type PutShopApiKeyData = {
+export type GetShopBySlugData = {
body?: never;
path: {
/**
- * Unique identifier of the shop (UUID format)
+ * Human-readable slug identifier of the shop (kebab-case, derived from shop name)
*/
- shopId: string;
+ shopSlugId: string;
};
query?: never;
- url: '/api/v1/shops/{shopId}/api-key';
+ url: '/api/v1/by-slug/shops/{shopSlugId}';
};
-export type PutShopApiKeyErrors = {
+export type GetShopBySlugErrors = {
/**
- * Bad request - invalid or missing shop ID
+ * Bad request - invalid or missing shop slug identifier
*/
400: ApiError;
- /**
- * Unauthorized – invalid or missing JWT token.
- */
- 401: ApiError;
- /**
- * Forbidden – caller is not allowed to manage an API key for this shop.
- */
- 403: ApiError;
/**
* Shop not found
*/
@@ -5617,45 +6075,7 @@ export type PutShopApiKeyErrors = {
500: ApiError;
};
-export type PutShopApiKeyError = PutShopApiKeyErrors[keyof PutShopApiKeyErrors];
-
-export type PutShopApiKeyResponses = {
- /**
- * API key created successfully
- */
- 200: PartnerShopApiKeyResponse;
-};
-
-export type PutShopApiKeyResponse = PutShopApiKeyResponses[keyof PutShopApiKeyResponses];
-
-export type GetShopBySlugData = {
- body?: never;
- path: {
- /**
- * Human-readable slug identifier of the shop (kebab-case, derived from shop name)
- */
- shopSlugId: string;
- };
- query?: never;
- url: '/api/v1/by-slug/shops/{shopSlugId}';
-};
-
-export type GetShopBySlugErrors = {
- /**
- * Bad request - invalid or missing shop slug identifier
- */
- 400: ApiError;
- /**
- * Shop not found
- */
- 404: ApiError;
- /**
- * Internal server error
- */
- 500: ApiError;
-};
-
-export type GetShopBySlugError = GetShopBySlugErrors[keyof GetShopBySlugErrors];
+export type GetShopBySlugError = GetShopBySlugErrors[keyof GetShopBySlugErrors];
export type GetShopBySlugResponses = {
/**
@@ -5992,47 +6412,38 @@ export type SearchPeriodsResponses = {
export type SearchPeriodsResponse = SearchPeriodsResponses[keyof SearchPeriodsResponses];
-export type GetPartnerShopsData = {
+export type GetMyPartnerShopsData = {
body?: never;
- path: {
- /**
- * Unique identifier of the partner user
- */
- partnerId: string;
- };
+ path?: never;
query?: never;
- url: '/api/v1/partner/{partnerId}/shops';
+ url: '/api/v1/me/partner-shops';
};
-export type GetPartnerShopsErrors = {
- /**
- * Bad request - invalid or missing partner ID
- */
- 400: ApiError;
+export type GetMyPartnerShopsErrors = {
/**
* Unauthorized – invalid or missing JWT token.
*/
401: ApiError;
/**
- * Forbidden – this endpoint requires the `ADMIN` role when requesting another partner's shops.
+ * User not found – the authenticated requester does not have a persisted user record.
*/
- 403: ApiError;
+ 404: ApiError;
/**
* Internal server error
*/
500: ApiError;
};
-export type GetPartnerShopsError = GetPartnerShopsErrors[keyof GetPartnerShopsErrors];
+export type GetMyPartnerShopsError = GetMyPartnerShopsErrors[keyof GetMyPartnerShopsErrors];
-export type GetPartnerShopsResponses = {
+export type GetMyPartnerShopsResponses = {
/**
* Partner shops retrieved successfully
*/
200: Array;
};
-export type GetPartnerShopsResponse = GetPartnerShopsResponses[keyof GetPartnerShopsResponses];
+export type GetMyPartnerShopsResponse = GetMyPartnerShopsResponses[keyof GetMyPartnerShopsResponses];
export type GetPartnerApplicationsData = {
body?: never;
@@ -6417,3 +6828,407 @@ export type AdminPostPartnerApplicationDecisionResponses = {
};
export type AdminPostPartnerApplicationDecisionResponse = AdminPostPartnerApplicationDecisionResponses[keyof AdminPostPartnerApplicationDecisionResponses];
+
+export type OauthAuthorizeData = {
+ body?: never;
+ path?: never;
+ query: {
+ /**
+ * OAuth response type. Must be `code`.
+ */
+ response_type: 'code';
+ /**
+ * UUIDv7 identifier of the registered OAuth client.
+ */
+ client_id: string;
+ /**
+ * Redirect URI registered for the OAuth client. Must exactly match one of the client's registered redirect URIs.
+ */
+ redirect_uri: string;
+ /**
+ * Space-separated list of requested scopes. Must be a subset of the client's allowed scopes.
+ */
+ scope?: string;
+ /**
+ * Opaque client state value. Returned unchanged in the redirect to prevent CSRF attacks.
+ */
+ state?: string;
+ /**
+ * PKCE code challenge. Must be the base64url-encoded SHA256 hash of the `code_verifier` (S256 method).
+ */
+ code_challenge: string;
+ /**
+ * PKCE challenge method. Must be `S256`.
+ */
+ code_challenge_method: 'S256';
+ };
+ url: '/api/v1/oauth/authorize';
+};
+
+export type OauthAuthorizeErrors = {
+ /**
+ * Bad request — a required query parameter is missing, malformed, or has an unsupported value.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — the Cognito bearer token is missing, invalid, or the OAuth client was not found / credentials are invalid.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type OauthAuthorizeError = OauthAuthorizeErrors[keyof OauthAuthorizeErrors];
+
+export type OauthTokenData = {
+ /**
+ * Authorization code exchange payload (form-urlencoded).
+ */
+ body: {
+ /**
+ * OAuth grant type. Must be `authorization_code`.
+ */
+ grant_type: 'authorization_code';
+ /**
+ * Single-use authorization code received from the authorize redirect.
+ */
+ code: string;
+ /**
+ * Must exactly match the `redirect_uri` used in the corresponding authorization request.
+ */
+ redirect_uri: string;
+ /**
+ * UUIDv7 OAuth client identifier.
+ */
+ client_id: string;
+ /**
+ * OAuth client secret.
+ */
+ client_secret: string;
+ /**
+ * PKCE code verifier. The SHA256 hash of this value must match the `code_challenge` from the authorization request.
+ */
+ code_verifier: string;
+ };
+ path?: never;
+ query?: never;
+ url: '/api/v1/oauth/token';
+};
+
+export type OauthTokenErrors = {
+ /**
+ * Bad request — a required form field is missing, `client_id` is not a valid UUID, `redirect_uri` is malformed, or an authorization code error occurred (code not found, expired, mismatched client or redirect URI, or invalid PKCE verifier).
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid OAuth client credentials.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type OauthTokenError = OauthTokenErrors[keyof OauthTokenErrors];
+
+export type OauthTokenResponses = {
+ /**
+ * Access token issued successfully.
+ */
+ 200: OAuthTokenResponseData;
+};
+
+export type OauthTokenResponse = OauthTokenResponses[keyof OauthTokenResponses];
+
+export type OauthRevokeData = {
+ /**
+ * Token revocation payload (form-urlencoded).
+ */
+ body: {
+ /**
+ * The access token to revoke.
+ */
+ token: string;
+ /**
+ * UUIDv7 OAuth client identifier.
+ */
+ client_id: string;
+ /**
+ * OAuth client secret.
+ */
+ client_secret: string;
+ };
+ path?: never;
+ query?: never;
+ url: '/api/v1/oauth/revoke';
+};
+
+export type OauthRevokeErrors = {
+ /**
+ * Bad request — a required form field is missing, `client_id` is not a valid UUID, or the token value is invalid.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid OAuth client credentials.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type OauthRevokeError = OauthRevokeErrors[keyof OauthRevokeErrors];
+
+export type OauthRevokeResponses = {
+ /**
+ * Token revoked successfully (or token was unknown/already inactive). Empty response body.
+ */
+ 200: unknown;
+};
+
+export type OauthIntrospectData = {
+ /**
+ * Token introspection payload (form-urlencoded).
+ */
+ body: {
+ /**
+ * The access token to introspect.
+ */
+ token: string;
+ /**
+ * UUIDv7 OAuth client identifier.
+ */
+ client_id: string;
+ /**
+ * OAuth client secret.
+ */
+ client_secret: string;
+ };
+ path?: never;
+ query?: never;
+ url: '/api/v1/oauth/introspect';
+};
+
+export type OauthIntrospectErrors = {
+ /**
+ * Bad request — a required form field is missing, `client_id` is not a valid UUID, or the token value is invalid.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid OAuth client credentials.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type OauthIntrospectError = OauthIntrospectErrors[keyof OauthIntrospectErrors];
+
+export type OauthIntrospectResponses = {
+ /**
+ * Introspection result. `active: false` means the token is unknown, expired, or revoked.
+ */
+ 200: OAuthIntrospectionResponseData;
+};
+
+export type OauthIntrospectResponse = OauthIntrospectResponses[keyof OauthIntrospectResponses];
+
+export type GetOAuthClientsData = {
+ body?: never;
+ path?: never;
+ query?: never;
+ url: '/api/v1/oauth/clients';
+};
+
+export type GetOAuthClientsErrors = {
+ /**
+ * Unauthorized — invalid or missing JWT token.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type GetOAuthClientsError = GetOAuthClientsErrors[keyof GetOAuthClientsErrors];
+
+export type GetOAuthClientsResponses = {
+ /**
+ * OAuth client metadata retrieved successfully.
+ */
+ 200: Array;
+};
+
+export type GetOAuthClientsResponse = GetOAuthClientsResponses[keyof GetOAuthClientsResponses];
+
+export type PostOAuthClientData = {
+ /**
+ * OAuth client metadata creation payload.
+ */
+ body: OAuthClientMetadataRequestData;
+ path?: never;
+ query?: never;
+ url: '/api/v1/oauth/clients';
+};
+
+export type PostOAuthClientErrors = {
+ /**
+ * Bad request — missing, empty, malformed JSON body, or invalid OAuth client metadata.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid or missing JWT token.
+ */
+ 401: ApiError;
+ /**
+ * Forbidden — the authenticated user is not an admin.
+ */
+ 403: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type PostOAuthClientError = PostOAuthClientErrors[keyof PostOAuthClientErrors];
+
+export type PostOAuthClientResponses = {
+ /**
+ * OAuth client metadata created successfully.
+ */
+ 201: OAuthClientMetadataResponseData;
+};
+
+export type PostOAuthClientResponse = PostOAuthClientResponses[keyof PostOAuthClientResponses];
+
+export type DeleteOAuthClientData = {
+ body?: never;
+ path: {
+ /**
+ * UUIDv7 identifier of the OAuth client.
+ */
+ clientId: string;
+ };
+ query?: never;
+ url: '/api/v1/oauth/clients/{clientId}';
+};
+
+export type DeleteOAuthClientErrors = {
+ /**
+ * Bad request — invalid or missing OAuth client path parameter.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid or missing JWT token, or the OAuth client does not exist.
+ */
+ 401: ApiError;
+ /**
+ * Forbidden — the authenticated user is not an admin.
+ */
+ 403: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type DeleteOAuthClientError = DeleteOAuthClientErrors[keyof DeleteOAuthClientErrors];
+
+export type DeleteOAuthClientResponses = {
+ /**
+ * OAuth client metadata deleted successfully. Empty response body.
+ */
+ 204: void;
+};
+
+export type DeleteOAuthClientResponse = DeleteOAuthClientResponses[keyof DeleteOAuthClientResponses];
+
+export type GetOAuthClientData = {
+ body?: never;
+ path: {
+ /**
+ * UUIDv7 identifier of the OAuth client.
+ */
+ clientId: string;
+ };
+ query?: never;
+ url: '/api/v1/oauth/clients/{clientId}';
+};
+
+export type GetOAuthClientErrors = {
+ /**
+ * Bad request — invalid or missing OAuth client path parameter.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid or missing JWT token, or the OAuth client does not exist.
+ */
+ 401: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type GetOAuthClientError = GetOAuthClientErrors[keyof GetOAuthClientErrors];
+
+export type GetOAuthClientResponses = {
+ /**
+ * OAuth client metadata retrieved successfully.
+ */
+ 200: OAuthClientMetadataResponseData;
+};
+
+export type GetOAuthClientResponse = GetOAuthClientResponses[keyof GetOAuthClientResponses];
+
+export type PatchOAuthClientData = {
+ /**
+ * Partial OAuth client metadata update payload.
+ */
+ body: OAuthClientMetadataPatchData;
+ path: {
+ /**
+ * UUIDv7 identifier of the OAuth client.
+ */
+ clientId: string;
+ };
+ query?: never;
+ url: '/api/v1/oauth/clients/{clientId}';
+};
+
+export type PatchOAuthClientErrors = {
+ /**
+ * Bad request — invalid or missing OAuth client path parameter, empty body, malformed JSON body, or invalid OAuth client metadata.
+ */
+ 400: ApiError;
+ /**
+ * Unauthorized — invalid or missing JWT token, or the OAuth client does not exist.
+ */
+ 401: ApiError;
+ /**
+ * Forbidden — the authenticated user is not an admin.
+ */
+ 403: ApiError;
+ /**
+ * Internal server error.
+ */
+ 500: ApiError;
+};
+
+export type PatchOAuthClientError = PatchOAuthClientErrors[keyof PatchOAuthClientErrors];
+
+export type PatchOAuthClientResponses = {
+ /**
+ * OAuth client metadata updated successfully.
+ */
+ 200: OAuthClientMetadataResponseData;
+};
+
+export type PatchOAuthClientResponse = PatchOAuthClientResponses[keyof PatchOAuthClientResponses];
diff --git a/src/components/admin/AdminOAuthClientCreateDialog.tsx b/src/components/admin/AdminOAuthClientCreateDialog.tsx
new file mode 100644
index 00000000..a3534095
--- /dev/null
+++ b/src/components/admin/AdminOAuthClientCreateDialog.tsx
@@ -0,0 +1,417 @@
+import { useEffect, useMemo, useState } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { Copy } from "lucide-react";
+import { z } from "zod";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog.tsx";
+import { Button } from "@/components/ui/button.tsx";
+import { Checkbox } from "@/components/ui/checkbox.tsx";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form.tsx";
+import { Input } from "@/components/ui/input.tsx";
+import { Spinner } from "@/components/ui/spinner.tsx";
+import { Textarea } from "@/components/ui/textarea.tsx";
+import type { AccessTokenScopeData } from "@/client";
+import type { OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+import { useCreateOAuthClient } from "@/hooks/admin/useAdminOAuthClientActions.ts";
+import { toast } from "sonner";
+
+const OAUTH_SCOPES = [
+ "shops:manage",
+ "products:write",
+] as const satisfies readonly AccessTokenScopeData[];
+
+interface AdminOAuthClientCreateDialogProps {
+ readonly open: boolean;
+ readonly onOpenChange: (open: boolean) => void;
+}
+
+function parseRedirectUris(value: string): string[] {
+ return value
+ .split("\n")
+ .map((uri) => uri.trim())
+ .filter(Boolean);
+}
+
+function isValidHttpsUrl(value: string): boolean {
+ if (!z.url().safeParse(value).success) {
+ return false;
+ }
+
+ try {
+ return new URL(value).protocol === "https:";
+ } catch {
+ return false;
+ }
+}
+
+function createOAuthClientSchema(t: (key: string) => string) {
+ return z.object({
+ clientName: z
+ .string()
+ .trim()
+ .min(1, t("adminDashboard.oauthClients.create.validation.nameRequired"))
+ .max(255, t("adminDashboard.oauthClients.create.validation.nameTooLong")),
+ tosUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.create.validation.uriInvalid")),
+ policyUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.create.validation.uriInvalid")),
+ clientUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.create.validation.uriInvalid")),
+ logoUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.create.validation.uriInvalid")),
+ redirectUris: z
+ .string()
+ .trim()
+ .refine(
+ (value) => parseRedirectUris(value).length > 0,
+ t("adminDashboard.oauthClients.create.validation.redirectUrisRequired"),
+ )
+ .refine(
+ (value) => parseRedirectUris(value).every(isValidHttpsUrl),
+ t("adminDashboard.oauthClients.create.validation.redirectUriInvalid"),
+ ),
+ scope: z.array(z.enum(OAUTH_SCOPES)),
+ });
+}
+
+type AdminOAuthClientCreateFormData = z.infer>;
+
+const DEFAULT_VALUES: AdminOAuthClientCreateFormData = {
+ clientName: "",
+ tosUri: "",
+ policyUri: "",
+ clientUri: "",
+ logoUri: "",
+ redirectUris: "",
+ scope: [],
+};
+
+async function copyValue(value: string) {
+ if (typeof navigator === "undefined" || navigator.clipboard === undefined) {
+ return;
+ }
+ await navigator.clipboard.writeText(value);
+}
+
+export function AdminOAuthClientCreateDialog({
+ open,
+ onOpenChange,
+}: AdminOAuthClientCreateDialogProps) {
+ const { t } = useTranslation();
+ const schema = useMemo(() => createOAuthClientSchema(t), [t]);
+ const createClient = useCreateOAuthClient();
+ const [createdClient, setCreatedClient] = useState(null);
+
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: DEFAULT_VALUES,
+ });
+
+ useEffect(() => {
+ if (!open) {
+ return;
+ }
+
+ setCreatedClient(null);
+ form.reset(DEFAULT_VALUES);
+ }, [form, open]);
+
+ const onSubmit = (values: AdminOAuthClientCreateFormData) => {
+ createClient.mutate(
+ {
+ clientName: values.clientName.trim(),
+ tosUri: values.tosUri.trim(),
+ policyUri: values.policyUri.trim(),
+ clientUri: values.clientUri.trim(),
+ logoUri: values.logoUri.trim(),
+ redirectUris: parseRedirectUris(values.redirectUris),
+ scope: values.scope,
+ },
+ {
+ onSuccess: (client) => {
+ toast.success(t("adminDashboard.oauthClients.create.success"));
+ setCreatedClient(client);
+ form.reset(DEFAULT_VALUES);
+ },
+ },
+ );
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/admin/AdminOAuthClientEditDialog.tsx b/src/components/admin/AdminOAuthClientEditDialog.tsx
new file mode 100644
index 00000000..3a8977b9
--- /dev/null
+++ b/src/components/admin/AdminOAuthClientEditDialog.tsx
@@ -0,0 +1,361 @@
+import { useEffect, useMemo } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { z } from "zod";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog.tsx";
+import { Button } from "@/components/ui/button.tsx";
+import { Checkbox } from "@/components/ui/checkbox.tsx";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form.tsx";
+import { Input } from "@/components/ui/input.tsx";
+import { Spinner } from "@/components/ui/spinner.tsx";
+import { Textarea } from "@/components/ui/textarea.tsx";
+import type { AccessTokenScopeData } from "@/client";
+import type { OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+import { usePatchOAuthClient } from "@/hooks/admin/useAdminOAuthClientActions.ts";
+import { toast } from "sonner";
+
+const OAUTH_SCOPES = [
+ "shops:manage",
+ "products:write",
+] as const satisfies readonly AccessTokenScopeData[];
+
+interface AdminOAuthClientEditDialogProps {
+ readonly client: OAuthClient | null;
+ readonly open: boolean;
+ readonly onOpenChange: (open: boolean) => void;
+}
+
+function parseRedirectUris(value: string): string[] {
+ return value
+ .split("\n")
+ .map((uri) => uri.trim())
+ .filter(Boolean);
+}
+
+function isValidHttpsUrl(value: string): boolean {
+ if (!z.url().safeParse(value).success) {
+ return false;
+ }
+
+ try {
+ return new URL(value).protocol === "https:";
+ } catch {
+ return false;
+ }
+}
+
+function createOAuthClientSchema(t: (key: string) => string) {
+ return z.object({
+ clientName: z
+ .string()
+ .trim()
+ .min(1, t("adminDashboard.oauthClients.edit.validation.nameRequired"))
+ .max(255, t("adminDashboard.oauthClients.edit.validation.nameTooLong")),
+ tosUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.edit.validation.uriInvalid")),
+ policyUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.edit.validation.uriInvalid")),
+ clientUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.edit.validation.uriInvalid")),
+ logoUri: z
+ .string()
+ .trim()
+ .refine(isValidHttpsUrl, t("adminDashboard.oauthClients.edit.validation.uriInvalid")),
+ redirectUris: z
+ .string()
+ .trim()
+ .refine(
+ (value) => parseRedirectUris(value).length > 0,
+ t("adminDashboard.oauthClients.edit.validation.redirectUrisRequired"),
+ )
+ .refine(
+ (value) => parseRedirectUris(value).every(isValidHttpsUrl),
+ t("adminDashboard.oauthClients.edit.validation.redirectUriInvalid"),
+ ),
+ scope: z.array(z.enum(OAUTH_SCOPES)),
+ });
+}
+
+type AdminOAuthClientEditFormData = z.infer>;
+
+const DEFAULT_VALUES: AdminOAuthClientEditFormData = {
+ clientName: "",
+ tosUri: "",
+ policyUri: "",
+ clientUri: "",
+ logoUri: "",
+ redirectUris: "",
+ scope: [],
+};
+
+export function AdminOAuthClientEditDialog({
+ client,
+ open,
+ onOpenChange,
+}: AdminOAuthClientEditDialogProps) {
+ const { t } = useTranslation();
+ const schema = useMemo(() => createOAuthClientSchema(t), [t]);
+ const patchClient = usePatchOAuthClient();
+
+ const form = useForm({
+ resolver: zodResolver(schema),
+ defaultValues: DEFAULT_VALUES,
+ });
+
+ useEffect(() => {
+ if (!open || !client) {
+ return;
+ }
+
+ form.reset({
+ clientName: client.clientName,
+ tosUri: client.tosUri,
+ policyUri: client.policyUri,
+ clientUri: client.clientUri,
+ logoUri: client.logoUri,
+ redirectUris: client.redirectUris.join("\n"),
+ scope: [...client.scope] as AccessTokenScopeData[],
+ });
+ }, [client, form, open]);
+
+ if (!client) {
+ return null;
+ }
+
+ const onSubmit = (values: AdminOAuthClientEditFormData) => {
+ patchClient.mutate(
+ {
+ clientId: client.clientId,
+ clientName: values.clientName.trim(),
+ tosUri: values.tosUri.trim(),
+ policyUri: values.policyUri.trim(),
+ clientUri: values.clientUri.trim(),
+ logoUri: values.logoUri.trim(),
+ redirectUris: parseRedirectUris(values.redirectUris),
+ scope: values.scope,
+ },
+ {
+ onSuccess: () => {
+ toast.success(t("adminDashboard.oauthClients.edit.success"));
+ onOpenChange(false);
+ },
+ },
+ );
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/admin/AdminOAuthClientsSection.tsx b/src/components/admin/AdminOAuthClientsSection.tsx
new file mode 100644
index 00000000..94d35d53
--- /dev/null
+++ b/src/components/admin/AdminOAuthClientsSection.tsx
@@ -0,0 +1,271 @@
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Globe, Pencil, Trash2 } from "lucide-react";
+import { H1 } from "@/components/typography/H1.tsx";
+import { AdminOAuthClientCreateDialog } from "@/components/admin/AdminOAuthClientCreateDialog.tsx";
+import { AdminOAuthClientEditDialog } from "@/components/admin/AdminOAuthClientEditDialog.tsx";
+import { ImageWithFallback } from "@/components/ui/image-with-fallback.tsx";
+import { Badge } from "@/components/ui/badge.tsx";
+import { Button } from "@/components/ui/button.tsx";
+import { Spinner } from "@/components/ui/spinner.tsx";
+import type { OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+import { useDeleteOAuthClient } from "@/hooks/admin/useAdminOAuthClientActions.ts";
+import { useAdminOAuthClients } from "@/hooks/admin/useAdminOAuthClients.ts";
+import { formatShortDate } from "@/lib/utils.ts";
+import { toast } from "sonner";
+
+export function AdminOAuthClientsSection() {
+ const { t, i18n } = useTranslation();
+ const { data, isPending, isError, refetch } = useAdminOAuthClients();
+ const deleteClient = useDeleteOAuthClient();
+ const [createOpen, setCreateOpen] = useState(false);
+ const [editTarget, setEditTarget] = useState(null);
+ const [isHydrated, setIsHydrated] = useState(false);
+
+ useEffect(() => {
+ setIsHydrated(true);
+ }, []);
+
+ const handleDelete = (client: OAuthClient) => {
+ const confirmed = window.confirm(
+ t("adminDashboard.oauthClients.deleteConfirm", {
+ client: client.clientName,
+ }),
+ );
+ if (!confirmed) {
+ return;
+ }
+
+ deleteClient.mutate(client.clientId, {
+ onSuccess: () => {
+ toast.success(t("adminDashboard.oauthClients.deleteSuccess"));
+ },
+ });
+ };
+
+ const renderClients = () => {
+ if (isPending) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
+ {t("adminDashboard.oauthClients.loadError")}
+
+
+
+ );
+ }
+
+ if ((data?.length ?? 0) === 0) {
+ return (
+
+ {t("adminDashboard.oauthClients.empty")}
+
+ );
+ }
+
+ return (
+
+ {data?.map((client) => (
+ -
+
+
+
+
+
+
+
+
+
+ {client.clientName}
+
+ {client.scope.map((scope) => (
+
+ {scope}
+
+ ))}
+
+ {isHydrated ? (
+
+ {t("adminDashboard.oauthClients.createdAt", {
+ date: formatShortDate(
+ client.createdAt,
+ i18n.language,
+ ),
+ })}
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("adminDashboard.oauthClients.clientId")}
+
+
+ {client.clientId}
+
+
+
+
+
+ {t("adminDashboard.oauthClients.scope")}
+
+
+ {client.scope.length > 0 ? (
+ client.scope.map((scope) => (
+
+ {scope}
+
+ ))
+ ) : (
+ —
+ )}
+
+
+
+ {[
+ {
+ key: "client-uri",
+ label: t("adminDashboard.oauthClients.fields.clientUri"),
+ value: client.clientUri,
+ },
+ {
+ key: "logo-uri",
+ label: t("adminDashboard.oauthClients.fields.logoUri"),
+ value: client.logoUri,
+ },
+ {
+ key: "tos-uri",
+ label: t("adminDashboard.oauthClients.fields.tosUri"),
+ value: client.tosUri,
+ },
+ {
+ key: "policy-uri",
+ label: t("adminDashboard.oauthClients.fields.policyUri"),
+ value: client.policyUri,
+ },
+ ].map((item) => (
+
+ ))}
+
+
+
+ {t("adminDashboard.oauthClients.redirectUris")}
+
+ {client.redirectUris.length > 0 ? (
+
+ {client.redirectUris.map((uri) => (
+ -
+
+ {uri}
+
+ ))}
+
+ ) : (
+
—
+ )}
+
+
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
+
+ {renderClients()}
+
+
+ {
+ if (!open) {
+ setEditTarget(null);
+ }
+ }}
+ />
+
+ );
+}
diff --git a/src/components/admin/AdminOverviewPage.tsx b/src/components/admin/AdminOverviewPage.tsx
index a6b195dd..03466e86 100644
--- a/src/components/admin/AdminOverviewPage.tsx
+++ b/src/components/admin/AdminOverviewPage.tsx
@@ -1,19 +1,22 @@
import { Link } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
-import { FileText, Store, Users } from "lucide-react";
+import { FileText, KeyRound, Store, Users } from "lucide-react";
import { H1 } from "@/components/typography/H1.tsx";
+import { useAdminOAuthClients } from "@/hooks/admin/useAdminOAuthClients.ts";
import { useAdminPartnerApplications } from "@/hooks/admin/useAdminPartnerApplications.ts";
import { useAdminUsers } from "@/hooks/admin/useAdminUsers.ts";
export function AdminOverviewPage() {
const { t } = useTranslation();
const { data: applications } = useAdminPartnerApplications();
+ const { data: oauthClients } = useAdminOAuthClients();
const { data: users } = useAdminUsers({ sort: "updated", order: "desc" });
const pendingApplications =
applications?.filter(
(a) => a.businessState === "SUBMITTED" || a.businessState === "IN_REVIEW",
).length ?? 0;
+ const totalOAuthClients = oauthClients?.length;
const totalUsers = users?.pages[0]?.total;
return (
@@ -25,7 +28,7 @@ export function AdminOverviewPage() {
-
+
+
+
+
+
+ {t("adminDashboard.overview.oauthClients.title")}
+
+
+
+ {t("adminDashboard.overview.oauthClients.description")}
+
+ {totalOAuthClients !== undefined && (
+
+ {t("adminDashboard.overview.oauthClients.totalCount", {
+ count: totalOAuthClients,
+ })}
+
+ )}
+
+
,
},
+ {
+ to: "/admin/oauth-clients",
+ labelKey: "adminDashboard.nav.oauthClients",
+ icon:
,
+ },
{
to: "/admin/users",
labelKey: "adminDashboard.nav.users",
diff --git a/src/components/admin/__tests__/AdminOAuthClientCreateDialog.test.tsx b/src/components/admin/__tests__/AdminOAuthClientCreateDialog.test.tsx
new file mode 100644
index 00000000..7b8be1bb
--- /dev/null
+++ b/src/components/admin/__tests__/AdminOAuthClientCreateDialog.test.tsx
@@ -0,0 +1,56 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { AdminOAuthClientCreateDialog } from "../AdminOAuthClientCreateDialog.tsx";
+
+const mockMutate = vi.hoisted(() => vi.fn());
+
+vi.mock("@/hooks/admin/useAdminOAuthClientActions.ts", () => ({
+ useCreateOAuthClient: () => ({
+ mutate: mockMutate,
+ isPending: false,
+ }),
+}));
+
+vi.mock("sonner", () => ({
+ toast: {
+ success: vi.fn(),
+ },
+}));
+
+describe("AdminOAuthClientCreateDialog", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("submits the additional oauth client metadata fields", async () => {
+ const user = userEvent.setup();
+
+ render(
);
+
+ await user.type(screen.getByLabelText("Client-Name"), "Example Client");
+ await user.type(screen.getByLabelText("Client-URL"), "https://example.com");
+ await user.type(screen.getByLabelText("Logo-URL"), "https://example.com/logo.png");
+ await user.type(screen.getByLabelText("AGB-URL"), "https://example.com/tos");
+ await user.type(
+ screen.getByLabelText("Datenschutzerklärungs-URL"),
+ "https://example.com/privacy",
+ );
+ await user.type(screen.getByLabelText("Redirect-URIs"), "https://example.com/callback");
+
+ await user.click(screen.getByRole("button", { name: "Client anlegen" }));
+
+ expect(mockMutate).toHaveBeenCalledWith(
+ {
+ clientName: "Example Client",
+ clientUri: "https://example.com",
+ logoUri: "https://example.com/logo.png",
+ tosUri: "https://example.com/tos",
+ policyUri: "https://example.com/privacy",
+ redirectUris: ["https://example.com/callback"],
+ scope: [],
+ },
+ expect.objectContaining({ onSuccess: expect.any(Function) }),
+ );
+ });
+});
diff --git a/src/components/admin/__tests__/AdminOAuthClientEditDialog.test.tsx b/src/components/admin/__tests__/AdminOAuthClientEditDialog.test.tsx
new file mode 100644
index 00000000..880ce4ca
--- /dev/null
+++ b/src/components/admin/__tests__/AdminOAuthClientEditDialog.test.tsx
@@ -0,0 +1,68 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { AdminOAuthClientEditDialog } from "../AdminOAuthClientEditDialog.tsx";
+
+const mockMutate = vi.hoisted(() => vi.fn());
+
+vi.mock("@/hooks/admin/useAdminOAuthClientActions.ts", () => ({
+ usePatchOAuthClient: () => ({
+ mutate: mockMutate,
+ isPending: false,
+ }),
+}));
+
+vi.mock("sonner", () => ({
+ toast: {
+ success: vi.fn(),
+ },
+}));
+
+const client = {
+ clientId: "client-123",
+ clientSecret: "secret-123",
+ clientName: "Example Client",
+ tosUri: "https://example.com/tos",
+ policyUri: "https://example.com/privacy",
+ clientUri: "https://example.com",
+ logoUri: "https://example.com/logo.png",
+ redirectUris: ["https://example.com/callback"],
+ scope: ["shops:manage", "products:write"] as const,
+ createdAt: new Date("2024-01-01T00:00:00Z"),
+};
+
+describe("AdminOAuthClientEditDialog", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("loads and submits the additional oauth client metadata fields", async () => {
+ const user = userEvent.setup();
+
+ render(
);
+
+ expect(screen.getByDisplayValue("https://example.com")).toBeInTheDocument();
+ expect(screen.getByDisplayValue("https://example.com/logo.png")).toBeInTheDocument();
+ expect(screen.getByDisplayValue("https://example.com/tos")).toBeInTheDocument();
+ expect(screen.getByDisplayValue("https://example.com/privacy")).toBeInTheDocument();
+
+ await user.clear(screen.getByLabelText("Client-URL"));
+ await user.type(screen.getByLabelText("Client-URL"), "https://example.org");
+
+ await user.click(screen.getByRole("button", { name: "Speichern" }));
+
+ expect(mockMutate).toHaveBeenCalledWith(
+ {
+ clientId: "client-123",
+ clientName: "Example Client",
+ clientUri: "https://example.org",
+ logoUri: "https://example.com/logo.png",
+ tosUri: "https://example.com/tos",
+ policyUri: "https://example.com/privacy",
+ redirectUris: ["https://example.com/callback"],
+ scope: ["shops:manage", "products:write"],
+ },
+ expect.objectContaining({ onSuccess: expect.any(Function) }),
+ );
+ });
+});
diff --git a/src/components/admin/__tests__/AdminOAuthClientsSection.test.tsx b/src/components/admin/__tests__/AdminOAuthClientsSection.test.tsx
new file mode 100644
index 00000000..f612099e
--- /dev/null
+++ b/src/components/admin/__tests__/AdminOAuthClientsSection.test.tsx
@@ -0,0 +1,172 @@
+import { act } from "react";
+import { hydrateRoot } from "react-dom/client";
+import { renderToString } from "react-dom/server";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { AdminOAuthClientsSection } from "../AdminOAuthClientsSection.tsx";
+import type { OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+
+const mockUseAdminOAuthClients = vi.hoisted(() => vi.fn());
+const mockDeleteMutate = vi.hoisted(() => vi.fn());
+const mockDateFormattingState = vi.hoisted(() => ({ value: "1. Jan. 2024" }));
+
+vi.mock("@/hooks/admin/useAdminOAuthClients.ts", () => ({
+ useAdminOAuthClients: mockUseAdminOAuthClients,
+}));
+
+vi.mock("@/lib/utils.ts", async (importOriginal) => {
+ const actual = await importOriginal
();
+ return {
+ ...actual,
+ formatShortDate: () => mockDateFormattingState.value,
+ };
+});
+
+vi.mock("@/hooks/admin/useAdminOAuthClientActions.ts", () => ({
+ useDeleteOAuthClient: () => ({
+ mutate: mockDeleteMutate,
+ isPending: false,
+ }),
+}));
+
+vi.mock("../AdminOAuthClientCreateDialog.tsx", () => ({
+ AdminOAuthClientCreateDialog: ({ open }: { open: boolean }) =>
+ open ? create-dialog
: null,
+}));
+
+vi.mock("../AdminOAuthClientEditDialog.tsx", () => ({
+ AdminOAuthClientEditDialog: ({
+ client,
+ open,
+ }: {
+ client: OAuthClient | null;
+ open: boolean;
+ }) => (open ? edit-dialog:{client?.clientId}
: null),
+}));
+
+vi.mock("@/components/ui/image-with-fallback.tsx", () => ({
+ ImageWithFallback: ({
+ src,
+ alt,
+ className,
+ }: {
+ src: string;
+ alt: string;
+ className?: string;
+ }) =>
,
+}));
+
+const oauthClient: OAuthClient = {
+ clientId: "client-123",
+ clientSecret: "secret-123",
+ clientName: "Beispiel Client",
+ tosUri: "https://example.com/tos",
+ policyUri: "https://example.com/privacy",
+ clientUri: "https://example.com",
+ logoUri: "https://example.com/logo.png",
+ redirectUris: ["https://example.com/callback", "https://example.com/return"],
+ scope: ["shops:manage", "products:write"],
+ createdAt: new Date("2024-01-01T00:00:00Z"),
+};
+
+describe("AdminOAuthClientsSection", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockDateFormattingState.value = "1. Jan. 2024";
+ mockUseAdminOAuthClients.mockReturnValue({
+ data: [oauthClient],
+ isPending: false,
+ isError: false,
+ refetch: vi.fn(),
+ });
+ });
+
+ it("shows a loading state", () => {
+ mockUseAdminOAuthClients.mockReturnValue({
+ data: undefined,
+ isPending: true,
+ isError: false,
+ refetch: vi.fn(),
+ });
+
+ render();
+
+ expect(screen.getAllByRole("status")).not.toHaveLength(0);
+ });
+
+ it("shows an error state", () => {
+ mockUseAdminOAuthClients.mockReturnValue({
+ data: undefined,
+ isPending: false,
+ isError: true,
+ refetch: vi.fn(),
+ });
+
+ render();
+
+ expect(screen.getByText("OAuth-Clients konnten nicht geladen werden.")).toBeInTheDocument();
+ });
+
+ it("lists oauth clients", () => {
+ render();
+
+ expect(screen.getByText("Beispiel Client")).toBeInTheDocument();
+ expect(screen.getByText("client-123")).toBeInTheDocument();
+ expect(screen.getByText("https://example.com/callback")).toBeInTheDocument();
+ expect(screen.getByRole("img", { name: "Logo von Beispiel Client" })).toHaveAttribute(
+ "src",
+ "https://example.com/logo.png",
+ );
+ expect(screen.getByRole("link", { name: "https://example.com/privacy" })).toHaveAttribute(
+ "href",
+ "https://example.com/privacy",
+ );
+ expect(screen.getAllByText("shops:manage")).toHaveLength(2);
+ });
+
+ it("opens the edit dialog for the selected client", async () => {
+ const user = userEvent.setup();
+
+ render();
+
+ await user.click(screen.getByRole("button", { name: /Beispiel Client bearbeiten/i }));
+
+ expect(screen.getByText("edit-dialog:client-123")).toBeInTheDocument();
+ });
+
+ it("submits a delete action for confirmed deletions", async () => {
+ const user = userEvent.setup();
+ vi.spyOn(window, "confirm").mockReturnValue(true);
+
+ render();
+
+ await user.click(screen.getByRole("button", { name: /Beispiel Client löschen/i }));
+
+ expect(mockDeleteMutate).toHaveBeenCalledWith(
+ "client-123",
+ expect.objectContaining({ onSuccess: expect.any(Function) }),
+ );
+ });
+
+ it("avoids hydration warnings for localized created-at text", async () => {
+ const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
+ mockDateFormattingState.value = "1. Jan. 2024";
+
+ const markup = renderToString();
+ const container = document.createElement("div");
+ container.innerHTML = markup;
+ document.body.appendChild(container);
+
+ mockDateFormattingState.value = "1 Jan 2024";
+
+ await act(async () => {
+ hydrateRoot(container, );
+ });
+
+ expect(consoleError).not.toHaveBeenCalled();
+
+ consoleError.mockRestore();
+ container.remove();
+ });
+});
diff --git a/src/components/admin/__tests__/AdminOverviewPage.test.tsx b/src/components/admin/__tests__/AdminOverviewPage.test.tsx
index 5b952010..d36adb0a 100644
--- a/src/components/admin/__tests__/AdminOverviewPage.test.tsx
+++ b/src/components/admin/__tests__/AdminOverviewPage.test.tsx
@@ -3,12 +3,17 @@ import { describe, expect, it, vi } from "vitest";
import { AdminOverviewPage } from "../AdminOverviewPage.tsx";
const mockUseAdminPartnerApplications = vi.hoisted(() => vi.fn());
+const mockUseAdminOAuthClients = vi.hoisted(() => vi.fn());
const mockUseAdminUsers = vi.hoisted(() => vi.fn());
vi.mock("@/hooks/admin/useAdminPartnerApplications.ts", () => ({
useAdminPartnerApplications: mockUseAdminPartnerApplications,
}));
+vi.mock("@/hooks/admin/useAdminOAuthClients.ts", () => ({
+ useAdminOAuthClients: mockUseAdminOAuthClients,
+}));
+
vi.mock("@/hooks/admin/useAdminUsers.ts", () => ({
useAdminUsers: mockUseAdminUsers,
}));
@@ -28,6 +33,9 @@ describe("AdminOverviewPage", () => {
{ businessState: "APPROVED" },
],
});
+ mockUseAdminOAuthClients.mockReturnValue({
+ data: [{ clientId: "client-1" }, { clientId: "client-2" }],
+ });
mockUseAdminUsers.mockReturnValue({
data: {
pages: [{ total: 42 }],
@@ -44,11 +52,16 @@ describe("AdminOverviewPage", () => {
"href",
"/admin/partner-applications",
);
+ expect(screen.getByRole("link", { name: /OAuth-Clients/i })).toHaveAttribute(
+ "href",
+ "/admin/oauth-clients",
+ );
expect(screen.getByRole("link", { name: /Benutzer/i })).toHaveAttribute(
"href",
"/admin/users",
);
expect(screen.getByText("2 Anträge warten auf Prüfung")).toBeInTheDocument();
+ expect(screen.getByText("2 OAuth-Clients")).toBeInTheDocument();
expect(screen.getByText("42 Benutzerkonten")).toBeInTheDocument();
});
});
diff --git a/src/data/internal/oauth/OAuthClient.ts b/src/data/internal/oauth/OAuthClient.ts
new file mode 100644
index 00000000..1e0683f8
--- /dev/null
+++ b/src/data/internal/oauth/OAuthClient.ts
@@ -0,0 +1,29 @@
+import type { OAuthClientMetadataResponseData } from "@/client";
+
+export type OAuthClient = {
+ readonly clientId: string;
+ readonly clientSecret: string;
+ readonly clientName: string;
+ readonly tosUri: string;
+ readonly policyUri: string;
+ readonly clientUri: string;
+ readonly logoUri: string;
+ readonly redirectUris: readonly string[];
+ readonly scope: readonly string[];
+ readonly createdAt: Date;
+};
+
+export function mapToOAuthClient(data: OAuthClientMetadataResponseData): OAuthClient {
+ return {
+ clientId: data.client_id,
+ clientSecret: data.client_secret,
+ clientName: data.client_name,
+ tosUri: data.tos_uri,
+ policyUri: data.policy_uri,
+ clientUri: data.client_uri,
+ logoUri: data.logo_uri,
+ redirectUris: data.redirect_uris,
+ scope: data.scope,
+ createdAt: new Date(data.client_id_issued_at * 1000),
+ };
+}
diff --git a/src/hooks/admin/useAdminOAuthClientActions.ts b/src/hooks/admin/useAdminOAuthClientActions.ts
new file mode 100644
index 00000000..2b64a1b6
--- /dev/null
+++ b/src/hooks/admin/useAdminOAuthClientActions.ts
@@ -0,0 +1,144 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ deleteOAuthClient,
+ patchOAuthClient,
+ postOAuthClient,
+ type AccessTokenScopeData,
+ type OAuthClientMetadataPatchData,
+} from "@/client";
+import { mapToOAuthClient, type OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+import { mapToInternalApiError } from "@/data/internal/hooks/ApiError.ts";
+import { useApiError } from "@/hooks/common/useApiError.ts";
+import { toast } from "sonner";
+
+const ADMIN_OAUTH_CLIENTS_QUERY_KEY = ["admin", "oauth-clients"] as const;
+
+function upsertOAuthClient(
+ existingClients: OAuthClient[] | undefined,
+ client: OAuthClient,
+): OAuthClient[] {
+ const clientsWithoutMatch = (existingClients ?? []).filter(
+ (entry) => entry.clientId !== client.clientId,
+ );
+
+ return [client, ...clientsWithoutMatch];
+}
+
+export type CreateOAuthClientInput = {
+ readonly clientName: string;
+ readonly tosUri: string;
+ readonly policyUri: string;
+ readonly clientUri: string;
+ readonly logoUri: string;
+ readonly redirectUris: string[];
+ readonly scope?: AccessTokenScopeData[];
+};
+
+export function useCreateOAuthClient() {
+ const queryClient = useQueryClient();
+ const { getErrorMessage } = useApiError();
+
+ return useMutation({
+ mutationFn: async (input) => {
+ const response = await postOAuthClient({
+ body: {
+ client_name: input.clientName,
+ tos_uri: input.tosUri,
+ policy_uri: input.policyUri,
+ client_uri: input.clientUri,
+ logo_uri: input.logoUri,
+ redirect_uris: input.redirectUris,
+ scope: input.scope,
+ },
+ });
+ if (response.error) {
+ throw new Error(getErrorMessage(mapToInternalApiError(response.error)));
+ }
+ return mapToOAuthClient(response.data);
+ },
+ onSuccess: (client) => {
+ queryClient.setQueryData(ADMIN_OAUTH_CLIENTS_QUERY_KEY, (existing) =>
+ upsertOAuthClient(existing, client),
+ );
+ queryClient.invalidateQueries({ queryKey: ADMIN_OAUTH_CLIENTS_QUERY_KEY });
+ },
+ onError: (error) => {
+ console.error("[useCreateOAuthClient]", error);
+ toast.error(error.message);
+ },
+ });
+}
+
+export type PatchOAuthClientInput = {
+ readonly clientId: string;
+ readonly clientName?: string;
+ readonly tosUri?: string;
+ readonly policyUri?: string;
+ readonly clientUri?: string;
+ readonly logoUri?: string;
+ readonly redirectUris?: string[];
+ readonly scope?: AccessTokenScopeData[];
+};
+
+export function usePatchOAuthClient() {
+ const queryClient = useQueryClient();
+ const { getErrorMessage } = useApiError();
+
+ return useMutation({
+ mutationFn: async ({ clientId, ...rest }) => {
+ const body: OAuthClientMetadataPatchData = {};
+ if (rest.clientName !== undefined) body.client_name = rest.clientName;
+ if (rest.tosUri !== undefined) body.tos_uri = rest.tosUri;
+ if (rest.policyUri !== undefined) body.policy_uri = rest.policyUri;
+ if (rest.clientUri !== undefined) body.client_uri = rest.clientUri;
+ if (rest.logoUri !== undefined) body.logo_uri = rest.logoUri;
+ if (rest.redirectUris !== undefined) body.redirect_uris = rest.redirectUris;
+ if (rest.scope !== undefined) body.scope = rest.scope;
+
+ const response = await patchOAuthClient({
+ path: { clientId },
+ body,
+ });
+ if (response.error) {
+ throw new Error(getErrorMessage(mapToInternalApiError(response.error)));
+ }
+ return mapToOAuthClient(response.data);
+ },
+ onSuccess: (client) => {
+ queryClient.setQueryData(ADMIN_OAUTH_CLIENTS_QUERY_KEY, (existing) =>
+ upsertOAuthClient(existing, client),
+ );
+ queryClient.invalidateQueries({ queryKey: ADMIN_OAUTH_CLIENTS_QUERY_KEY });
+ },
+ onError: (error) => {
+ console.error("[usePatchOAuthClient]", error);
+ toast.error(error.message);
+ },
+ });
+}
+
+export function useDeleteOAuthClient() {
+ const queryClient = useQueryClient();
+ const { getErrorMessage } = useApiError();
+
+ return useMutation({
+ mutationFn: async (clientId) => {
+ const response = await deleteOAuthClient({
+ path: { clientId },
+ });
+ if (response.error) {
+ throw new Error(getErrorMessage(mapToInternalApiError(response.error)));
+ }
+ },
+ onSuccess: (_, clientId) => {
+ queryClient.setQueryData(ADMIN_OAUTH_CLIENTS_QUERY_KEY, (existing) =>
+ (existing ?? []).filter((client) => client.clientId !== clientId),
+ );
+ queryClient.invalidateQueries({ queryKey: ADMIN_OAUTH_CLIENTS_QUERY_KEY });
+ },
+ onError: (error) => {
+ console.error("[useDeleteOAuthClient]", error);
+ toast.error(error.message);
+ },
+ });
+}
diff --git a/src/hooks/admin/useAdminOAuthClients.ts b/src/hooks/admin/useAdminOAuthClients.ts
new file mode 100644
index 00000000..b940a526
--- /dev/null
+++ b/src/hooks/admin/useAdminOAuthClients.ts
@@ -0,0 +1,21 @@
+import { useQuery } from "@tanstack/react-query";
+import { getOAuthClients } from "@/client";
+import { mapToOAuthClient, type OAuthClient } from "@/data/internal/oauth/OAuthClient.ts";
+import { mapToInternalApiError } from "@/data/internal/hooks/ApiError.ts";
+import { useApiError } from "@/hooks/common/useApiError.ts";
+
+export function useAdminOAuthClients() {
+ const { getErrorMessage } = useApiError();
+
+ return useQuery({
+ queryKey: ["admin", "oauth-clients"],
+ queryFn: async () => {
+ const response = await getOAuthClients();
+ if (response.error) {
+ throw new Error(getErrorMessage(mapToInternalApiError(response.error)));
+ }
+ return response.data.map(mapToOAuthClient);
+ },
+ staleTime: 30 * 1000,
+ });
+}
diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json
index 4e2f3199..4468b8dd 100644
--- a/src/i18n/locales/de/translation.json
+++ b/src/i18n/locales/de/translation.json
@@ -1365,7 +1365,8 @@
"overview": "Übersicht",
"shops": "Shops",
"partnerApplications": "Partner-Anträge",
- "users": "Benutzer"
+ "users": "Benutzer",
+ "oauthClients": "OAuth-Clients"
},
"overview": {
"title": "Admin-Dashboard",
@@ -1385,6 +1386,12 @@
"description": "Konten suchen, Benutzerdetails prüfen und Rolle, Tarif, Einstellungen sowie Stripe-Zuordnungen verwalten.",
"totalCount_one": "{{count}} Benutzerkonto",
"totalCount_other": "{{count}} Benutzerkonten"
+ },
+ "oauthClients": {
+ "title": "OAuth-Clients",
+ "description": "Registrierte OAuth-Client-Anwendungen verwalten.",
+ "totalCount_one": "{{count}} OAuth-Client",
+ "totalCount_other": "{{count}} OAuth-Clients"
}
},
"actions": {
@@ -1587,6 +1594,64 @@
"deleteConfirm": "Benutzer {{email}} löschen? Dies kann nicht rückgängig gemacht werden.",
"deleteSuccess": "Benutzer gelöscht."
}
+ },
+ "oauthClients": {
+ "title": "OAuth-Clients",
+ "description": "Registrierte OAuth-Client-Anwendungen und ihre Metadaten verwalten.",
+ "loadError": "OAuth-Clients konnten nicht geladen werden.",
+ "empty": "Noch keine OAuth-Clients registriert.",
+ "clientId": "Client-ID",
+ "clientSecret": "Client-Secret",
+ "clientName": "Client-Name",
+ "redirectUris": "Redirect-URIs",
+ "scope": "Scopes",
+ "logoAlt": "Logo von {{client}}",
+ "createdAt": "Erstellt {{date}}",
+ "editAriaLabel": "{{client}} bearbeiten",
+ "deleteAriaLabel": "{{client}} löschen",
+ "actions": {
+ "create": "Client anlegen",
+ "delete": "Löschen"
+ },
+ "deleteConfirm": "Möchten Sie den Client \"{{client}}\" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
+ "deleteSuccess": "OAuth-Client gelöscht.",
+ "create": {
+ "title": "OAuth-Client anlegen",
+ "description": "Einen neuen OAuth-Client registrieren. Das Client-Secret wird nur einmal angezeigt.",
+ "submit": "Client anlegen",
+ "success": "OAuth-Client angelegt.",
+ "secretNotice": "Kopieren Sie das Client-Secret jetzt – es wird nicht erneut angezeigt.",
+ "validation": {
+ "nameRequired": "Bitte geben Sie einen Client-Namen ein.",
+ "nameTooLong": "Der Client-Name darf nicht länger als 255 Zeichen sein.",
+ "uriInvalid": "Bitte geben Sie eine gültige HTTPS-URL ein.",
+ "redirectUrisRequired": "Bitte geben Sie mindestens eine Redirect-URI ein.",
+ "redirectUriInvalid": "Jede Redirect-URI muss eine gültige HTTPS-URL sein."
+ }
+ },
+ "edit": {
+ "title": "OAuth-Client bearbeiten",
+ "description": "\"{{client}}\" wird bearbeitet",
+ "success": "OAuth-Client aktualisiert.",
+ "validation": {
+ "nameRequired": "Bitte geben Sie einen Client-Namen ein.",
+ "nameTooLong": "Der Client-Name darf nicht länger als 255 Zeichen sein.",
+ "uriInvalid": "Bitte geben Sie eine gültige HTTPS-URL ein.",
+ "redirectUrisRequired": "Bitte geben Sie mindestens eine Redirect-URI ein.",
+ "redirectUriInvalid": "Jede Redirect-URI muss eine gültige HTTPS-URL sein."
+ }
+ },
+ "fields": {
+ "clientName": "Client-Name",
+ "clientUri": "Client-URL",
+ "logoUri": "Logo-URL",
+ "tosUri": "AGB-URL",
+ "policyUri": "Datenschutzerklärungs-URL",
+ "redirectUris": "Redirect-URIs",
+ "redirectUrisHint": "Eine HTTPS-URI pro Zeile.",
+ "scope": "Scopes",
+ "scopeHint": "Wählen Sie die Scopes aus, die dieser Client anfordern darf."
+ }
}
}
}
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json
index 03220b4b..dc2659d2 100644
--- a/src/i18n/locales/en/translation.json
+++ b/src/i18n/locales/en/translation.json
@@ -1354,7 +1354,8 @@
"overview": "Overview",
"shops": "Shops",
"partnerApplications": "Partner Applications",
- "users": "Users"
+ "users": "Users",
+ "oauthClients": "OAuth Clients"
},
"overview": {
"title": "Admin Dashboard",
@@ -1374,6 +1375,12 @@
"description": "Search accounts, inspect user details, and manage role, tier, preferences, and Stripe mappings.",
"totalCount_one": "{{count}} user account",
"totalCount_other": "{{count}} user accounts"
+ },
+ "oauthClients": {
+ "title": "OAuth Clients",
+ "description": "Manage registered OAuth client applications.",
+ "totalCount_one": "{{count}} OAuth client",
+ "totalCount_other": "{{count}} OAuth clients"
}
},
"actions": {
@@ -1576,6 +1583,64 @@
"deleteConfirm": "Delete user {{email}}? This cannot be undone.",
"deleteSuccess": "User deleted."
}
+ },
+ "oauthClients": {
+ "title": "OAuth Clients",
+ "description": "Manage registered OAuth client applications and their metadata.",
+ "loadError": "Could not load OAuth clients.",
+ "empty": "No OAuth clients registered yet.",
+ "clientId": "Client ID",
+ "clientSecret": "Client Secret",
+ "clientName": "Client Name",
+ "redirectUris": "Redirect URIs",
+ "scope": "Scopes",
+ "logoAlt": "{{client}} Logo",
+ "createdAt": "Created {{date}}",
+ "editAriaLabel": "Edit {{client}}",
+ "deleteAriaLabel": "Delete {{client}}",
+ "actions": {
+ "create": "Create client",
+ "delete": "Delete"
+ },
+ "deleteConfirm": "Are you sure you want to delete the client \"{{client}}\"? This action cannot be undone.",
+ "deleteSuccess": "OAuth client deleted.",
+ "create": {
+ "title": "Create OAuth Client",
+ "description": "Register a new OAuth client. The client secret will only be shown once.",
+ "submit": "Create client",
+ "success": "OAuth client created.",
+ "secretNotice": "Copy the client secret now — it will not be shown again.",
+ "validation": {
+ "nameRequired": "Please enter a client name.",
+ "nameTooLong": "Client name must not exceed 255 characters.",
+ "uriInvalid": "Please enter a valid HTTPS URL.",
+ "redirectUrisRequired": "Please enter at least one redirect URI.",
+ "redirectUriInvalid": "Each redirect URI must be a valid HTTPS URL."
+ }
+ },
+ "edit": {
+ "title": "Edit OAuth Client",
+ "description": "Editing {{client}}",
+ "success": "OAuth client updated.",
+ "validation": {
+ "nameRequired": "Please enter a client name.",
+ "nameTooLong": "Client name must not exceed 255 characters.",
+ "uriInvalid": "Please enter a valid HTTPS URL.",
+ "redirectUrisRequired": "Please enter at least one redirect URI.",
+ "redirectUriInvalid": "Each redirect URI must be a valid HTTPS URL."
+ }
+ },
+ "fields": {
+ "clientName": "Client name",
+ "clientUri": "Client URL",
+ "logoUri": "Logo URL",
+ "tosUri": "Terms of service URL",
+ "policyUri": "Privacy policy URL",
+ "redirectUris": "Redirect URIs",
+ "redirectUrisHint": "One HTTPS URI per line.",
+ "scope": "Scopes",
+ "scopeHint": "Select the scopes this client may request."
+ }
}
}
}
diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json
index 24f46c59..277983b9 100644
--- a/src/i18n/locales/es/translation.json
+++ b/src/i18n/locales/es/translation.json
@@ -1249,7 +1249,8 @@
"overview": "Resumen",
"shops": "Tiendas",
"partnerApplications": "Solicitudes de socios",
- "users": "Usuarios"
+ "users": "Usuarios",
+ "oauthClients": "Clientes OAuth"
},
"overview": {
"title": "Panel de administración",
@@ -1269,6 +1270,12 @@
"description": "Busca cuentas, revisa detalles de usuario y gestiona roles, planes, preferencias y asignaciones de Stripe.",
"totalCount_one": "{{count}} cuenta de usuario",
"totalCount_other": "{{count}} cuentas de usuario"
+ },
+ "oauthClients": {
+ "title": "Clientes OAuth",
+ "description": "Gestiona las aplicaciones cliente OAuth registradas.",
+ "totalCount_one": "{{count}} cliente OAuth",
+ "totalCount_other": "{{count}} clientes OAuth"
}
},
"actions": {
@@ -1471,6 +1478,64 @@
"deleteConfirm": "¿Eliminar el usuario {{email}}? Esta acción no se puede deshacer.",
"deleteSuccess": "Usuario eliminado."
}
+ },
+ "oauthClients": {
+ "title": "Clientes OAuth",
+ "description": "Gestiona las aplicaciones cliente OAuth registradas y sus metadatos.",
+ "loadError": "No se pudieron cargar los clientes OAuth.",
+ "empty": "Todavía no hay clientes OAuth registrados.",
+ "clientId": "ID del cliente",
+ "clientSecret": "Secreto del cliente",
+ "clientName": "Nombre del cliente",
+ "redirectUris": "URI de redirección",
+ "scope": "Ámbitos",
+ "logoAlt": "Logotipo de {{client}}",
+ "createdAt": "Creado {{date}}",
+ "editAriaLabel": "Editar {{client}}",
+ "deleteAriaLabel": "Eliminar {{client}}",
+ "actions": {
+ "create": "Crear cliente",
+ "delete": "Eliminar"
+ },
+ "deleteConfirm": "¿Seguro que quieres eliminar el cliente \"{{client}}\"? Esta acción no se puede deshacer.",
+ "deleteSuccess": "Cliente OAuth eliminado.",
+ "create": {
+ "title": "Crear cliente OAuth",
+ "description": "Registra un nuevo cliente OAuth. El secreto del cliente solo se mostrará una vez.",
+ "submit": "Crear cliente",
+ "success": "Cliente OAuth creado.",
+ "secretNotice": "Copia ahora el secreto del cliente: no se volverá a mostrar.",
+ "validation": {
+ "nameRequired": "Introduce un nombre para el cliente.",
+ "nameTooLong": "El nombre del cliente no debe superar los 255 caracteres.",
+ "uriInvalid": "Introduce una URL HTTPS válida.",
+ "redirectUrisRequired": "Introduce al menos una URI de redirección.",
+ "redirectUriInvalid": "Cada URI de redirección debe ser una URL HTTPS válida."
+ }
+ },
+ "edit": {
+ "title": "Editar cliente OAuth",
+ "description": "Editando {{client}}",
+ "success": "Cliente OAuth actualizado.",
+ "validation": {
+ "nameRequired": "Introduce un nombre para el cliente.",
+ "nameTooLong": "El nombre del cliente no debe superar los 255 caracteres.",
+ "uriInvalid": "Introduce una URL HTTPS válida.",
+ "redirectUrisRequired": "Introduce al menos una URI de redirección.",
+ "redirectUriInvalid": "Cada URI de redirección debe ser una URL HTTPS válida."
+ }
+ },
+ "fields": {
+ "clientName": "Nombre del cliente",
+ "clientUri": "URL del cliente",
+ "logoUri": "URL del logotipo",
+ "tosUri": "URL de los términos del servicio",
+ "policyUri": "URL de la política de privacidad",
+ "redirectUris": "URI de redirección",
+ "redirectUrisHint": "Una URI HTTPS por línea.",
+ "scope": "Ámbitos",
+ "scopeHint": "Selecciona los ámbitos que este cliente puede solicitar."
+ }
}
},
"searchFilter": {
diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json
index d50dd795..e55ef02a 100644
--- a/src/i18n/locales/fr/translation.json
+++ b/src/i18n/locales/fr/translation.json
@@ -1249,7 +1249,8 @@
"overview": "Vue d'ensemble",
"shops": "Boutiques",
"partnerApplications": "Candidatures partenaires",
- "users": "Utilisateurs"
+ "users": "Utilisateurs",
+ "oauthClients": "Clients OAuth"
},
"overview": {
"title": "Tableau de bord admin",
@@ -1269,6 +1270,12 @@
"description": "Recherchez des comptes, consultez les détails utilisateur et gérez les rôles, offres, préférences et correspondances Stripe.",
"totalCount_one": "{{count}} compte utilisateur",
"totalCount_other": "{{count}} comptes utilisateur"
+ },
+ "oauthClients": {
+ "title": "Clients OAuth",
+ "description": "Gérez les applications clientes OAuth enregistrées.",
+ "totalCount_one": "{{count}} client OAuth",
+ "totalCount_other": "{{count}} clients OAuth"
}
},
"actions": {
@@ -1471,6 +1478,64 @@
"deleteConfirm": "Supprimer l’utilisateur {{email}} ? Cette action est irréversible.",
"deleteSuccess": "Utilisateur supprimé."
}
+ },
+ "oauthClients": {
+ "title": "Clients OAuth",
+ "description": "Gérez les applications clientes OAuth enregistrées et leurs métadonnées.",
+ "loadError": "Impossible de charger les clients OAuth.",
+ "empty": "Aucun client OAuth enregistré pour le moment.",
+ "clientId": "ID client",
+ "clientSecret": "Secret client",
+ "clientName": "Nom du client",
+ "redirectUris": "URI de redirection",
+ "scope": "Scopes",
+ "logoAlt": "Logo de {{client}}",
+ "createdAt": "Créé le {{date}}",
+ "editAriaLabel": "Modifier {{client}}",
+ "deleteAriaLabel": "Supprimer {{client}}",
+ "actions": {
+ "create": "Créer un client",
+ "delete": "Supprimer"
+ },
+ "deleteConfirm": "Voulez-vous vraiment supprimer le client « {{client}} » ? Cette action est irréversible.",
+ "deleteSuccess": "Client OAuth supprimé.",
+ "create": {
+ "title": "Créer un client OAuth",
+ "description": "Enregistrez un nouveau client OAuth. Le secret client ne sera affiché qu’une seule fois.",
+ "submit": "Créer le client",
+ "success": "Client OAuth créé.",
+ "secretNotice": "Copiez le secret client maintenant — il ne sera plus affiché.",
+ "validation": {
+ "nameRequired": "Veuillez saisir un nom de client.",
+ "nameTooLong": "Le nom du client ne doit pas dépasser 255 caractères.",
+ "uriInvalid": "Veuillez saisir une URL HTTPS valide.",
+ "redirectUrisRequired": "Veuillez saisir au moins une URI de redirection.",
+ "redirectUriInvalid": "Chaque URI de redirection doit être une URL HTTPS valide."
+ }
+ },
+ "edit": {
+ "title": "Modifier le client OAuth",
+ "description": "Modification de {{client}}",
+ "success": "Client OAuth mis à jour.",
+ "validation": {
+ "nameRequired": "Veuillez saisir un nom de client.",
+ "nameTooLong": "Le nom du client ne doit pas dépasser 255 caractères.",
+ "uriInvalid": "Veuillez saisir une URL HTTPS valide.",
+ "redirectUrisRequired": "Veuillez saisir au moins une URI de redirection.",
+ "redirectUriInvalid": "Chaque URI de redirection doit être une URL HTTPS valide."
+ }
+ },
+ "fields": {
+ "clientName": "Nom du client",
+ "clientUri": "URL du client",
+ "logoUri": "URL du logo",
+ "tosUri": "URL des conditions d’utilisation",
+ "policyUri": "URL de la politique de confidentialité",
+ "redirectUris": "URI de redirection",
+ "redirectUrisHint": "Une URI HTTPS par ligne.",
+ "scope": "Scopes",
+ "scopeHint": "Sélectionnez les scopes que ce client peut demander."
+ }
}
},
"searchFilter": {
diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json
index e9e0b9c3..c75fac80 100644
--- a/src/i18n/locales/it/translation.json
+++ b/src/i18n/locales/it/translation.json
@@ -1249,7 +1249,8 @@
"overview": "Panoramica",
"shops": "Negozi",
"partnerApplications": "Domande di partner",
- "users": "Utenti"
+ "users": "Utenti",
+ "oauthClients": "Client OAuth"
},
"overview": {
"title": "Pannello di amministrazione",
@@ -1269,6 +1270,12 @@
"description": "Cerca account, controlla i dettagli utente e gestisci ruolo, piano, preferenze e collegamenti Stripe.",
"totalCount_one": "{{count}} account utente",
"totalCount_other": "{{count}} account utente"
+ },
+ "oauthClients": {
+ "title": "Client OAuth",
+ "description": "Gestisci le applicazioni client OAuth registrate.",
+ "totalCount_one": "{{count}} client OAuth",
+ "totalCount_other": "{{count}} client OAuth"
}
},
"actions": {
@@ -1471,6 +1478,64 @@
"deleteConfirm": "Eliminare l’utente {{email}}? Questa azione non può essere annullata.",
"deleteSuccess": "Utente eliminato."
}
+ },
+ "oauthClients": {
+ "title": "Client OAuth",
+ "description": "Gestisci le applicazioni client OAuth registrate e i relativi metadati.",
+ "loadError": "Impossibile caricare i client OAuth.",
+ "empty": "Nessun client OAuth registrato al momento.",
+ "clientId": "ID client",
+ "clientSecret": "Segreto client",
+ "clientName": "Nome client",
+ "redirectUris": "URI di reindirizzamento",
+ "scope": "Ambiti",
+ "logoAlt": "Logo di {{client}}",
+ "createdAt": "Creato {{date}}",
+ "editAriaLabel": "Modifica {{client}}",
+ "deleteAriaLabel": "Elimina {{client}}",
+ "actions": {
+ "create": "Crea client",
+ "delete": "Elimina"
+ },
+ "deleteConfirm": "Sei sicuro di voler eliminare il client \"{{client}}\"? Questa azione non può essere annullata.",
+ "deleteSuccess": "Client OAuth eliminato.",
+ "create": {
+ "title": "Crea client OAuth",
+ "description": "Registra un nuovo client OAuth. Il segreto client verrà mostrato una sola volta.",
+ "submit": "Crea client",
+ "success": "Client OAuth creato.",
+ "secretNotice": "Copia subito il segreto client: non verrà più mostrato.",
+ "validation": {
+ "nameRequired": "Inserisci un nome client.",
+ "nameTooLong": "Il nome client non deve superare i 255 caratteri.",
+ "uriInvalid": "Inserisci un URL HTTPS valido.",
+ "redirectUrisRequired": "Inserisci almeno un URI di reindirizzamento.",
+ "redirectUriInvalid": "Ogni URI di reindirizzamento deve essere un URL HTTPS valido."
+ }
+ },
+ "edit": {
+ "title": "Modifica client OAuth",
+ "description": "Modifica di {{client}}",
+ "success": "Client OAuth aggiornato.",
+ "validation": {
+ "nameRequired": "Inserisci un nome client.",
+ "nameTooLong": "Il nome client non deve superare i 255 caratteri.",
+ "uriInvalid": "Inserisci un URL HTTPS valido.",
+ "redirectUrisRequired": "Inserisci almeno un URI di reindirizzamento.",
+ "redirectUriInvalid": "Ogni URI di reindirizzamento deve essere un URL HTTPS valido."
+ }
+ },
+ "fields": {
+ "clientName": "Nome client",
+ "clientUri": "URL del client",
+ "logoUri": "URL del logo",
+ "tosUri": "URL dei termini di servizio",
+ "policyUri": "URL dell’informativa sulla privacy",
+ "redirectUris": "URI di reindirizzamento",
+ "redirectUrisHint": "Un URI HTTPS per riga.",
+ "scope": "Ambiti",
+ "scopeHint": "Seleziona gli ambiti che questo client può richiedere."
+ }
}
},
"searchFilter": {
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 06c22fdd..38d9ea77 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -32,6 +32,7 @@ import { Route as AuthMeAccountRouteImport } from './routes/_auth.me.account'
import { Route as AuthAdminUsersRouteImport } from './routes/_auth.admin.users'
import { Route as AuthAdminShopsRouteImport } from './routes/_auth.admin.shops'
import { Route as AuthAdminPartnerApplicationsRouteImport } from './routes/_auth.admin.partner-applications'
+import { Route as AuthAdminOauthClientsRouteImport } from './routes/_auth.admin.oauth-clients'
import { Route as ShopsShopSlugIdProductsProductSlugIdRouteImport } from './routes/shops.$shopSlugId.products.$productSlugId'
import { Route as AuthMeSearchFilterFilterIdRouteImport } from './routes/_auth.me.search-filter.$filterId'
import { Route as AuthMeBillingManageRouteImport } from './routes/_auth.me.billing.manage'
@@ -153,6 +154,11 @@ const AuthAdminPartnerApplicationsRoute =
path: '/partner-applications',
getParentRoute: () => AuthAdminRoute,
} as any)
+const AuthAdminOauthClientsRoute = AuthAdminOauthClientsRouteImport.update({
+ id: '/oauth-clients',
+ path: '/oauth-clients',
+ getParentRoute: () => AuthAdminRoute,
+} as any)
const ShopsShopSlugIdProductsProductSlugIdRoute =
ShopsShopSlugIdProductsProductSlugIdRouteImport.update({
id: '/shops/$shopSlugId/products/$productSlugId',
@@ -184,6 +190,7 @@ export interface FileRoutesByFullPath {
'/partners/custom-integration': typeof PartnersCustomIntegrationRoute
'/search/shops': typeof SearchShopsRoute
'/partners/': typeof PartnersIndexRoute
+ '/admin/oauth-clients': typeof AuthAdminOauthClientsRoute
'/admin/partner-applications': typeof AuthAdminPartnerApplicationsRoute
'/admin/shops': typeof AuthAdminShopsRoute
'/admin/users': typeof AuthAdminUsersRoute
@@ -209,6 +216,7 @@ export interface FileRoutesByTo {
'/partners/custom-integration': typeof PartnersCustomIntegrationRoute
'/search/shops': typeof SearchShopsRoute
'/partners': typeof PartnersIndexRoute
+ '/admin/oauth-clients': typeof AuthAdminOauthClientsRoute
'/admin/partner-applications': typeof AuthAdminPartnerApplicationsRoute
'/admin/shops': typeof AuthAdminShopsRoute
'/admin/users': typeof AuthAdminUsersRoute
@@ -238,6 +246,7 @@ export interface FileRoutesById {
'/partners/custom-integration': typeof PartnersCustomIntegrationRoute
'/search_/shops': typeof SearchShopsRoute
'/partners/': typeof PartnersIndexRoute
+ '/_auth/admin/oauth-clients': typeof AuthAdminOauthClientsRoute
'/_auth/admin/partner-applications': typeof AuthAdminPartnerApplicationsRoute
'/_auth/admin/shops': typeof AuthAdminShopsRoute
'/_auth/admin/users': typeof AuthAdminUsersRoute
@@ -267,6 +276,7 @@ export interface FileRouteTypes {
| '/partners/custom-integration'
| '/search/shops'
| '/partners/'
+ | '/admin/oauth-clients'
| '/admin/partner-applications'
| '/admin/shops'
| '/admin/users'
@@ -292,6 +302,7 @@ export interface FileRouteTypes {
| '/partners/custom-integration'
| '/search/shops'
| '/partners'
+ | '/admin/oauth-clients'
| '/admin/partner-applications'
| '/admin/shops'
| '/admin/users'
@@ -320,6 +331,7 @@ export interface FileRouteTypes {
| '/partners/custom-integration'
| '/search_/shops'
| '/partners/'
+ | '/_auth/admin/oauth-clients'
| '/_auth/admin/partner-applications'
| '/_auth/admin/shops'
| '/_auth/admin/users'
@@ -514,6 +526,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthAdminPartnerApplicationsRouteImport
parentRoute: typeof AuthAdminRoute
}
+ '/_auth/admin/oauth-clients': {
+ id: '/_auth/admin/oauth-clients'
+ path: '/oauth-clients'
+ fullPath: '/admin/oauth-clients'
+ preLoaderRoute: typeof AuthAdminOauthClientsRouteImport
+ parentRoute: typeof AuthAdminRoute
+ }
'/shops/$shopSlugId/products/$productSlugId': {
id: '/shops/$shopSlugId/products/$productSlugId'
path: '/shops/$shopSlugId/products/$productSlugId'
@@ -539,6 +558,7 @@ declare module '@tanstack/react-router' {
}
interface AuthAdminRouteChildren {
+ AuthAdminOauthClientsRoute: typeof AuthAdminOauthClientsRoute
AuthAdminPartnerApplicationsRoute: typeof AuthAdminPartnerApplicationsRoute
AuthAdminShopsRoute: typeof AuthAdminShopsRoute
AuthAdminUsersRoute: typeof AuthAdminUsersRoute
@@ -546,6 +566,7 @@ interface AuthAdminRouteChildren {
}
const AuthAdminRouteChildren: AuthAdminRouteChildren = {
+ AuthAdminOauthClientsRoute: AuthAdminOauthClientsRoute,
AuthAdminPartnerApplicationsRoute: AuthAdminPartnerApplicationsRoute,
AuthAdminShopsRoute: AuthAdminShopsRoute,
AuthAdminUsersRoute: AuthAdminUsersRoute,
diff --git a/src/routes/_auth.admin.oauth-clients.tsx b/src/routes/_auth.admin.oauth-clients.tsx
new file mode 100644
index 00000000..51c300c6
--- /dev/null
+++ b/src/routes/_auth.admin.oauth-clients.tsx
@@ -0,0 +1,6 @@
+import { createFileRoute } from "@tanstack/react-router";
+import { AdminOAuthClientsSection } from "@/components/admin/AdminOAuthClientsSection.tsx";
+
+export const Route = createFileRoute("/_auth/admin/oauth-clients")({
+ component: AdminOAuthClientsSection,
+});