From e225a8a2f12f6755df78bf2084be2290dd003dae Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Thu, 15 Oct 2026 13:38:31 -0400 Subject: [PATCH] [api] stub out progression event record --- apps/api/src/openapi.ts | 28 ++++++++++++++-- apps/api/src/routes/progression.ts | 40 +++++++++++++++++++++++ apps/api/src/test/integration/api.test.ts | 26 +++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/apps/api/src/openapi.ts b/apps/api/src/openapi.ts index b9469e9..af68c0b 100644 --- a/apps/api/src/openapi.ts +++ b/apps/api/src/openapi.ts @@ -310,6 +310,22 @@ export const ProgressionDto = z.object({ XP: z.int(), }) +/** + * The caller's progress through one progression event (`/api/progressionEvents/record/:id`). + * A stub for now: every counter is 0 and nothing is collected. + */ +export const ProgressionEventRecordDto = z.object({ + AccountId: z.int().describe('The caller'), + ProgressionEventId: z.int().describe('The event requested'), + Xp: z.int(), + GameMinutesToday: z.int(), + RewardsCollected: z.int(), + BonusRewardsCollected: z.int(), + XpBoostLastPurchasedAt: z.string().nullable().describe('ISO-8601 UTC; null when never bought'), + PremiumRewardsCollected: z.int(), + LimitedRewardCollectionState: z.int(), +}) + /** * The form body of `POST /api/PlayerCheer/v1/create`. Nothing here is stored beyond the * counter the cheer increments: `Anonymous` is spent on the notification it triggers and @@ -567,7 +583,9 @@ export const InventionV9Dto = z.object({ Name: z.string(), Description: z.string(), ImageName: z.string(), - UgcVersion: z.int().describe('The UGC format the blob was written in; 1 when the save named none'), + UgcVersion: z + .int() + .describe('The UGC format the blob was written in; 1 when the save named none'), CurrentVersionNumber: z.int(), LatestVersionNumber: z.int().describe('The same as CurrentVersionNumber on a fresh save'), Accessibility: z.int(), @@ -760,7 +778,9 @@ export const PublishInventionRequest = z.object({ .int() .nullable() .optional() - .describe('Public 1 publishes it into the store; Unlisted 2 publishes it but keeps it out of search and the feeds'), + .describe( + 'Public 1 publishes it into the store; Unlisted 2 publishes it but keeps it out of search and the feeds' + ), Price: z .int() .nullable() @@ -1359,7 +1379,9 @@ export const PlayerEventsAll = z.object({ export const PlayerPublicEvents = z.object({ Created: z.array(PlayerEventBaseDto).describe('Public events the player created'), Responses: z - .array(z.object({ PlayerEvent: PlayerEventBaseDto, PlayerEventResponse: PlayerEventResponseDto })) + .array( + z.object({ PlayerEvent: PlayerEventBaseDto, PlayerEventResponse: PlayerEventResponseDto }) + ) .describe('Public events the player is attending, each beside their Going RSVP'), }) diff --git a/apps/api/src/routes/progression.ts b/apps/api/src/routes/progression.ts index e49f33d..1539f18 100644 --- a/apps/api/src/routes/progression.ts +++ b/apps/api/src/routes/progression.ts @@ -41,6 +41,7 @@ import { JsonArray, JsonObject, ProgressionDto, + ProgressionEventRecordDto, ReputationDto, SetSelectedCheerRequest, UNAUTHORIZED_RESPONSE, @@ -632,3 +633,42 @@ export const progressionRoutes = new Hono({ strict: false }) return c.body(event, 200, { 'Content-Type': 'application/json' }) } ) + + // The caller's progress through one progression event — the XP, minutes and rewards the + // client draws its event track from. + // + // STUB: every counter 0, for any event id, echoing the id requested. Nothing tracks + // progress through an event yet (see `/active` above). Auth-gated because the record is + // the CALLER's and the path names no player. + .get( + '/api/progressionEvents/record/:id{[0-9]+}', + describeRoute({ + tags: ['Progression'], + summary: 'The caller’s progress through a progression event (stub)', + description: + 'The caller’s record for the event with this `ProgressionEventId`. Always empty ' + + 'for now — 0 XP, nothing collected, `XpBoostLastPurchasedAt` null — for any id: ' + + 'nothing on this server tracks progress through an event yet.', + security: AUTHED, + parameters: [idParam('id', 'The `ProgressionEventId`')], + responses: { + 200: json(ProgressionEventRecordDto, 'An empty record for the event requested'), + 401: UNAUTHORIZED_RESPONSE, + }, + }), + async (c) => { + const id = await authedId(c) + if (id === null) return unauthorized(c) + return c.json({ + AccountId: id, + ProgressionEventId: Number(c.req.param('id')), + Xp: 0, + GameMinutesToday: 0, + RewardsCollected: 0, + BonusRewardsCollected: 0, + XpBoostLastPurchasedAt: null, + PremiumRewardsCollected: 0, + LimitedRewardCollectionState: 0, + }) + } + ) diff --git a/apps/api/src/test/integration/api.test.ts b/apps/api/src/test/integration/api.test.ts index fcb6e7d..6a9286b 100644 --- a/apps/api/src/test/integration/api.test.ts +++ b/apps/api/src/test/integration/api.test.ts @@ -2119,6 +2119,31 @@ describe('public endpoints', () => { expect(missing.status).toBe(404) }) + test('GET /api/progressionEvents/record/:id is an empty record for the caller', async () => { + const url = `${ORIGIN}/api/progressionEvents/record/18` + expect((await exports.default.fetch(url)).status).toBe(401) + + const res = await exports.default.fetch(url, { headers: await bearer('2') }) + expect(res.status).toBe(200) + expect(await res.json()).toStrictEqual({ + AccountId: 2, + ProgressionEventId: 18, + Xp: 0, + GameMinutesToday: 0, + RewardsCollected: 0, + BonusRewardsCollected: 0, + XpBoostLastPurchasedAt: null, + PremiumRewardsCollected: 0, + LimitedRewardCollectionState: 0, + }) + + // The id is echoed, whichever event is asked for. + const other = await exports.default.fetch(`${ORIGIN}/api/progressionEvents/record/7`, { + headers: await bearer('2'), + }) + expect(await other.json()).toMatchObject({ ProgressionEventId: 7 }) + }) + test('GET /api/rooms/v1/filters returns an object with filter arrays', async () => { const res = await exports.default.fetch(`${ORIGIN}/api/rooms/v1/filters`) expect(res.status).toBe(200) @@ -10221,6 +10246,7 @@ describe('openapi', () => { 'GET /api/players/v2/progression/bulk', 'GET /api/progressionEvents/active', 'GET /api/progressionEvents/event/{id}', + 'GET /api/progressionEvents/record/{id}', 'GET /api/quickPlay/v1/getandclear', 'GET /api/referee/files', 'GET /api/relationships/mutualfriends',