mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-28 17:17:58 +00:00
Gate public lobby listing on the canCreatePublicLobbies entitlement (#4620)
## What The API now returns an explicit `canCreatePublicLobbies` boolean on `/users/@me` (player) and on each subscription tier in `cosmetics.json`. This PR gates public lobby listing on that entitlement instead of inferring it from subscription status (`active`/`trialing`), and advertises the perk on the store's subscription tiles. ## Changes **Entitlement check** - `UserMeResponseSchema` gains a required `canCreatePublicLobbies` boolean (same pattern as `unlimitedRanked`) - The worker's listing endpoint and `HostLobbyModal`'s Public toggle both check the boolean directly; the Dev bypass is unchanged - `hasActiveSubscription()` is removed — these were its only call sites - The server error stays `subscription_required` (subscribing is still how you get the entitlement; renaming would ripple through i18n for no behavior change) **Store** - `SubscriptionSchema` (cosmetics catalog) gains a required `canCreatePublicLobbies` boolean - Subscription tiles show a "Create public lobbies" badge below "Unlimited ranked" when the tier grants it (`cosmetics.public_lobbies` added to en.json) **Tests** - Schema tests for both new fields (accepts true, rejects missing, rejects non-boolean); `UserMeResponse` fixtures updated ## Deploy ordering Both new schema fields are required, so this client must deploy after the API serves them (same constraint as `unlimitedRanked` / infra#427). The API already returns both fields. ## Verified Typecheck, ESLint, and the affected test files pass (165 tests). Confirmed the live payloads against a local API: `/users/@me` returns `player.canCreatePublicLobbies` and both subscription tiers in `cosmetics.json` carry the field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,9 @@ export const UserMeResponseSchema = z.object({
|
||||
// True when the player's active subscription tier exempts them from the
|
||||
// free-ranked-play limits.
|
||||
unlimitedRanked: z.boolean(),
|
||||
// True when the player may list a custom lobby publicly. The API decides
|
||||
// which subscriptions/grants confer this.
|
||||
canCreatePublicLobbies: z.boolean(),
|
||||
flares: z.string().array().optional(),
|
||||
achievements: z.object({
|
||||
singleplayerMap: z.array(SingleplayerMapAchievementSchema),
|
||||
@@ -187,13 +190,6 @@ export type UserSubscription = NonNullable<
|
||||
NonNullable<UserMeResponse["player"]["subscription"]>
|
||||
>;
|
||||
|
||||
// Whether the player currently has subscriber entitlements (e.g. may list a
|
||||
// private lobby publicly). Trialing counts; past_due/canceled do not.
|
||||
export function hasActiveSubscription(userMe: UserMeResponse): boolean {
|
||||
const status = userMe.player.subscription?.status;
|
||||
return status === "active" || status === "trialing";
|
||||
}
|
||||
|
||||
export const PlayerStatsLeafSchema = z.object({
|
||||
wins: BigIntStringSchema,
|
||||
losses: BigIntStringSchema,
|
||||
|
||||
@@ -361,6 +361,9 @@ export const SubscriptionSchema = CosmeticSchema.extend({
|
||||
// Whether this tier exempts subscribers from the free-ranked-play limits
|
||||
// (advertised on the store tile).
|
||||
unlimitedRanked: z.boolean(),
|
||||
// Whether this tier lets subscribers list custom lobbies publicly
|
||||
// (advertised on the store tile).
|
||||
canCreatePublicLobbies: z.boolean(),
|
||||
});
|
||||
|
||||
// Schema for resources/cosmetics/cosmetics.json
|
||||
|
||||
Reference in New Issue
Block a user