Skip to content

Commit 8a0b02c

Browse files
committed
refactor(api): centralize AnswerPlatform in packages/api/src/platform.ts
PR #298 review: the 'web' | 'ios' | 'android' platform enum was duplicated across the answers router, trpc Context typing, and the DB schema comment. Single source of truth now lives in packages/api/src/platform.ts (ANSWER_PLATFORMS + AnswerPlatform), re-exported from the package root and used to type answers.ts's zod schema and trpc.ts's Context.recordAnswer. Also drops the answer_platform_idx index added in migration 0002_answer_platform.sql: the stats page's per-platform counts are a full-table GROUP BY, which an index on this low-cardinality (3-4 value) column wouldn't speed up — only add write overhead. Safe to hand-edit since this migration hasn't been applied to any real database yet (unmerged, in-PR).
1 parent cccb7b3 commit 8a0b02c

7 files changed

Lines changed: 29 additions & 30 deletions

File tree

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
ALTER TABLE "answer" ADD COLUMN "platform" text;--> statement-breakpoint
2-
CREATE INDEX "answer_platform_idx" ON "answer" USING btree ("platform");
1+
ALTER TABLE "answer" ADD COLUMN "platform" text;

‎apps/website/src/server/db/migrations/meta/0002_snapshot.json‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -346,21 +346,6 @@
346346
"concurrently": false,
347347
"method": "btree",
348348
"with": {}
349-
},
350-
"answer_platform_idx": {
351-
"name": "answer_platform_idx",
352-
"columns": [
353-
{
354-
"expression": "platform",
355-
"isExpression": false,
356-
"asc": true,
357-
"nulls": "last"
358-
}
359-
],
360-
"isUnique": false,
361-
"concurrently": false,
362-
"method": "btree",
363-
"with": {}
364349
}
365350
},
366351
"foreignKeys": {},

‎apps/website/src/server/db/schema.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,11 +447,16 @@ export const answer = pgTable(
447447
questionId: text('question_id').notNull(),
448448
language: text('language'),
449449
type: text('type').notNull().default('answered'),
450-
// Nullable, additive (public stats page, issue: analytics page). Rows written
451-
// before this column existed — and any client that hasn't updated yet — stay
452-
// NULL; the stats aggregation surfaces those as "Unattributed" rather than
453-
// guessing. New writes carry 'web' | 'ios' | 'android' from the host transport
450+
// Nullable, additive (public stats page). Rows written before this column
451+
// existed — and any client that hasn't updated yet — stay NULL; the stats
452+
// aggregation surfaces those as "Unattributed" rather than guessing. New
453+
// writes carry 'web' | 'ios' | 'android' from the host transport
454454
// (answer-transport.ts on each client), never guessed server-side.
455+
//
456+
// No index: the stats page's per-platform counts are a full-table
457+
// GROUP BY (see getPlatformCounts in server/stats/query.ts), which an
458+
// index on this low-cardinality (3-4 value) column wouldn't speed up —
459+
// it would only add write overhead.
455460
platform: text('platform'),
456461
},
457462
(table) => {
@@ -461,7 +466,6 @@ export const answer = pgTable(
461466
table.questionId
462467
),
463468
answerDeviceIdIdx: index('answer_device_id_idx').on(table.deviceId),
464-
answerPlatformIdx: index('answer_platform_idx').on(table.platform),
465469
}
466470
}
467471
)

‎packages/api/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const createCaller = createCallerFactory(appRouter)
1010
export {appRouter}
1111
export type {Context} from './trpc'
1212
export type {AppRouter}
13+
export {ANSWER_PLATFORMS} from './platform'
14+
export type {AnswerPlatform} from './platform'
1315

1416
/** Inference helpers for end-to-end typed clients. */
1517
export type RouterInputs = inferRouterInputs<AppRouter>

‎packages/api/src/platform.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Client platforms an Answer (or a catalog event) can originate from — the
3+
* single source of truth other packages/derive from, instead of hand-copying
4+
* the same three strings (previously duplicated across answers.ts, trpc.ts,
5+
* and the stats page's own types/query modules).
6+
*/
7+
export const ANSWER_PLATFORMS = ['web', 'ios', 'android'] as const
8+
9+
export type AnswerPlatform = (typeof ANSWER_PLATFORMS)[number]

‎packages/api/src/routers/answers.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import {z} from 'zod'
22

3+
import {ANSWER_PLATFORMS} from '../platform'
34
import {createTRPCRouter, publicProcedure} from '../trpc'
45

5-
/** Client platforms an Answer can originate from — see the stats page's per-platform split. */
6-
export const ANSWER_PLATFORMS = ['web', 'ios', 'android'] as const
7-
86
export const answersRouter = createTRPCRouter({
97
/**
108
* Record one Answer (CONTEXT.md → Answer record): the Device answered a
119
* Question. Validates the event, then hands it to the host's `recordAnswer`
1210
* port (the Drizzle adapter in apps/website). `type` defaults to `'answered'`,
1311
* today's only kind (a future dwell-timer / Skip may add others). `platform`
14-
* is optional (analytics page): each client's transport sets it once, so
12+
* is optional (stats page): each client's transport sets it once, so
1513
* older builds that omit it keep working — those rows land as "Unattributed"
1614
* in the stats breakdown.
1715
*/

‎packages/api/src/trpc.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {initTRPC} from '@trpc/server'
22

3+
import type {AnswerPlatform} from './platform'
4+
35
/**
46
* The API context. The content router is public and stateless, but the host
57
* supplies a `recordAnswer` port so `answers.record` can persist to the Answer
@@ -15,11 +17,11 @@ export type Context = {
1517
language: string
1618
type: string
1719
/**
18-
* Which client sent this Answer — 'web' | 'ios' | 'android'. Optional so
19-
* older, not-yet-updated clients keep working; absent/unset means the
20-
* stats page buckets the row as "Unattributed" rather than guessing.
20+
* Which client sent this Answer. Optional so older, not-yet-updated
21+
* clients keep working; absent/unset means the stats page buckets the
22+
* row as "Unattributed" rather than guessing.
2123
*/
22-
platform?: string
24+
platform?: AnswerPlatform
2325
}) => Promise<void>
2426
}
2527

0 commit comments

Comments
 (0)