feat(public): type the brand block so mobile clients get typed theming
Branding is already returned by GET /public/settings (the `brand` block: name/colors/logo/hero/favicon, per-shard from BRAND_*). §8.6 of the Android plan asks to confirm it — this makes it a first-class part of the contract so the app's OpenAPI codegen produces typed branding instead of an untyped map. - Swagger: add Brand + PublicSettings schemas; /public/settings now references PublicSettings (was additionalProperties:true). Brand documents that asset fields may be site-relative paths (resolve against the base URL). - test/publicBrand.test.js locks the brand theming contract the app depends on (all fields present; BRAND_* defaults; admin site_title/contact_email overrides; accentInt never leaked). No behavior change to the response — it already carried `brand`; this types and guards it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
This commit is contained in:
@@ -1636,16 +1636,15 @@
|
||||
"tags": [
|
||||
"Public"
|
||||
],
|
||||
"summary": "Public site settings",
|
||||
"description": "Whitelisted, non-sensitive settings the client needs to render the site.",
|
||||
"summary": "Public site settings + branding",
|
||||
"description": "Whitelisted, non-sensitive settings plus the per-shard brand block (name/colors/logo/hero/favicon) a client themes itself from, and derived registration / game-account-signup availability flags.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Key/value settings",
|
||||
"description": "Public settings + branding",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
"$ref": "#/components/schemas/PublicSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14005,6 +14004,275 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Brand": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "object"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Per-shard branding (BRAND_* env, with admin overrides for name/contactEmail). A client themes itself from this — one instance runs as any shard. Asset fields (logo/hero/favicon) may be site-relative paths; resolve them against the site base URL."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "Runic Gateway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortName": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "Runic Gateway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagline": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "an independent private Ultima Online shard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactEmail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"accent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "#7f99bd"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Seed/accent color (hex) for theming."
|
||||
}
|
||||
}
|
||||
},
|
||||
"logo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": ""
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Logo URL or site-relative path; empty = no logo."
|
||||
}
|
||||
}
|
||||
},
|
||||
"hero": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "/assets/img/runic-emblem.png"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Hero image URL or site-relative path."
|
||||
}
|
||||
}
|
||||
},
|
||||
"favicon": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "/assets/img/favicon.ico"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Favicon URL or site-relative path."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PublicSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "object"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Public site settings + branding (GET /public/settings). Whitelisted string settings, plus derived availability flags and the brand block a client themes from. Additional whitelisted keys may appear."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_title": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": "Runic Gateway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status_message": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"maintenance_message": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "string"
|
||||
},
|
||||
"example": {
|
||||
"type": "string",
|
||||
"example": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "object"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sso": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"gameAccountSignup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"example": "boolean"
|
||||
},
|
||||
"example": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"brand": {
|
||||
"$ref": "#/components/schemas/Brand"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeletedId": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user