From 6add3437cf8ed6ef73daebafc14388ac2d35b1c0 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Wed, 19 Aug 2026 17:35:38 -0400 Subject: [PATCH] [pn] stub endpoint --- .../src/platformnotifications.app.ts | 18 ++++++++++++++++++ .../src/test/integration/api.test.ts | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/apps/platformnotifications/src/platformnotifications.app.ts b/apps/platformnotifications/src/platformnotifications.app.ts index bce6b25..eb2f2c4 100644 --- a/apps/platformnotifications/src/platformnotifications.app.ts +++ b/apps/platformnotifications/src/platformnotifications.app.ts @@ -87,6 +87,24 @@ const app = new Hono() return c.json(NOTIFICATION_CATEGORIES) }) + // The caller's CRM configuration — the campaign/messaging settings the client fetches on + // startup before it will ask about anything else here. An object, not an envelope. + // + // STUB: there is no CRM behind this, so the config is EMPTY rather than invented. An + // empty object is the honest answer and the client reads it as "nothing configured"; + // made-up keys would switch on messaging paths that have nothing to serve them. Unlike + // `/config/categories` there is no visible text to mark as a stub — nothing here is + // rendered — so the marker stays in this comment. + // + // Auth-gated because the route is `me`-scoped, like `/preferences` below: the config + // belongs to the caller even while the answer is the same for everyone. + .get('/crm/me/config/v3', async (c) => { + const accountId = await authedId(c) + if (accountId === null) return unauthorized(c) + + return c.json({}) + }) + // The caller's own notification preferences — which categories they have muted, by // CategoryId (the ids `/config/categories` above hands out). // diff --git a/apps/platformnotifications/src/test/integration/api.test.ts b/apps/platformnotifications/src/test/integration/api.test.ts index 951851c..25b22e9 100644 --- a/apps/platformnotifications/src/test/integration/api.test.ts +++ b/apps/platformnotifications/src/test/integration/api.test.ts @@ -112,3 +112,19 @@ it('401s the preferences read without a bearer token', async () => { expect(res.status).toBe(401) expect(await res.text()).toBe('') }) + +it('serves the caller’s CRM config', async () => { + const res = await SELF.fetch(`${ORIGIN}/crm/me/config/v3`, { headers: await bearer('205') }) + expect(res.status).toBe(200) + expect(res.headers.get('content-type')).toContain('application/json') + // Empty rather than invented: there is no CRM behind this, and made-up keys would switch + // on client messaging paths with nothing to serve them. + expect(await res.json()).toEqual({}) +}) + +it('401s the CRM config read without a bearer token', async () => { + // `me`-scoped, so it needs a token even though the answer is the same for everyone. + const res = await SELF.fetch(`${ORIGIN}/crm/me/config/v3`) + expect(res.status).toBe(401) + expect(await res.text()).toBe('') +})