CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
schema-standup.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| a686079 | 1 | /** |
| 2 | * AI Standup — Drizzle schema for the daily/weekly Claude-generated | |
| 3 | * team-brief feature. | |
| 4 | * | |
| 5 | * Defined in a SEPARATE module because `src/db/schema.ts` is listed in | |
| 6 | * §4 LOCKED BLOCKS of BUILD_BIBLE.md and must not be edited. The matching | |
| 7 | * migration is `drizzle/0057_ai_standup.sql`. | |
| 8 | * | |
| 9 | * Two tables: | |
| 10 | * - `user_standup_prefs` — per-user opt-in flags. Row missing == opted | |
| 11 | * out (the lazy-create-on-toggle pattern keeps the legacy user table | |
| 12 | * untouched). | |
| 13 | * - `ai_standups` — generated standup records, surfaced at /standups | |
| 14 | * and used for same-day dedupe. | |
| 15 | */ | |
| 16 | ||
| 17 | import { | |
| 18 | boolean, | |
| 19 | index, | |
| 20 | integer, | |
| 21 | pgTable, | |
| 22 | text, | |
| 23 | timestamp, | |
| 24 | uuid, | |
| 25 | } from "drizzle-orm/pg-core"; | |
| 26 | ||
| 27 | export const userStandupPrefs = pgTable("user_standup_prefs", { | |
| 28 | userId: uuid("user_id").primaryKey(), | |
| 29 | dailyEnabled: boolean("daily_enabled").default(false).notNull(), | |
| 30 | weeklyEnabled: boolean("weekly_enabled").default(false).notNull(), | |
| 31 | emailEnabled: boolean("email_enabled").default(false).notNull(), | |
| 32 | hourUtc: integer("hour_utc").default(9).notNull(), | |
| 33 | lastDailySentAt: timestamp("last_daily_sent_at", { withTimezone: true }), | |
| 34 | lastWeeklySentAt: timestamp("last_weekly_sent_at", { withTimezone: true }), | |
| 35 | createdAt: timestamp("created_at", { withTimezone: true }) | |
| 36 | .defaultNow() | |
| 37 | .notNull(), | |
| 38 | updatedAt: timestamp("updated_at", { withTimezone: true }) | |
| 39 | .defaultNow() | |
| 40 | .notNull(), | |
| 41 | }); | |
| 42 | ||
| 43 | export type UserStandupPrefsRow = typeof userStandupPrefs.$inferSelect; | |
| 44 | export type NewUserStandupPrefsRow = typeof userStandupPrefs.$inferInsert; | |
| 45 | ||
| 46 | export const aiStandups = pgTable( | |
| 47 | "ai_standups", | |
| 48 | { | |
| 49 | id: uuid("id").primaryKey().defaultRandom(), | |
| 50 | userId: uuid("user_id").notNull(), | |
| 51 | // 'daily' | 'weekly' | |
| 52 | scope: text("scope").notNull(), | |
| 53 | summary: text("summary").notNull(), | |
| 54 | // JSON-serialized string arrays so we don't depend on jsonb features. | |
| 55 | shippedItems: text("shipped_items").default("[]").notNull(), | |
| 56 | blockedItems: text("blocked_items").default("[]").notNull(), | |
| 57 | atRiskItems: text("at_risk_items").default("[]").notNull(), | |
| 58 | windowStart: timestamp("window_start", { withTimezone: true }).notNull(), | |
| 59 | windowEnd: timestamp("window_end", { withTimezone: true }).notNull(), | |
| 60 | aiAvailable: boolean("ai_available").default(false).notNull(), | |
| 61 | createdAt: timestamp("created_at", { withTimezone: true }) | |
| 62 | .defaultNow() | |
| 63 | .notNull(), | |
| 64 | }, | |
| 65 | (table) => [ | |
| 66 | index("idx_ai_standups_user_created").on(table.userId, table.createdAt), | |
| 67 | index("idx_ai_standups_user_scope_created").on( | |
| 68 | table.userId, | |
| 69 | table.scope, | |
| 70 | table.createdAt | |
| 71 | ), | |
| 72 | ] | |
| 73 | ); | |
| 74 | ||
| 75 | export type AiStandupRow = typeof aiStandups.$inferSelect; | |
| 76 | export type NewAiStandupRow = typeof aiStandups.$inferInsert; |