CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
schema-extensions.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.
| 45e31d0 | 1 | /** |
| 2 | * Extended database schema — branch protection, status checks, | |
| 3 | * notifications, organizations, and teams. | |
| 4 | * | |
| 5 | * These extend the base schema to add enterprise-grade features. | |
| 6 | */ | |
| 7 | ||
| 8 | import { | |
| 9 | pgTable, | |
| 10 | text, | |
| 11 | timestamp, | |
| 12 | uuid, | |
| 13 | boolean, | |
| 14 | integer, | |
| 15 | uniqueIndex, | |
| 16 | index, | |
| 17 | serial, | |
| 18 | jsonb, | |
| 19 | } from "drizzle-orm/pg-core"; | |
| 20 | import { users, repositories } from "./schema"; | |
| 21 | ||
| 22 | // ─── Branch Protection Rules ──────────────────────────────────────────────── | |
| 23 | ||
| 24 | export const branchProtection = pgTable( | |
| 25 | "branch_protection", | |
| 26 | { | |
| 27 | id: uuid("id").primaryKey().defaultRandom(), | |
| 28 | repositoryId: uuid("repository_id") | |
| 29 | .notNull() | |
| 30 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 31 | pattern: text("pattern").notNull(), // e.g. "main", "release/*" | |
| 32 | requireStatusChecks: boolean("require_status_checks").default(false).notNull(), | |
| 33 | requiredChecks: text("required_checks").default(""), // comma-separated check names | |
| 34 | requireReviews: boolean("require_reviews").default(false).notNull(), | |
| 35 | requiredReviewCount: integer("required_review_count").default(1).notNull(), | |
| 36 | requireUpToDate: boolean("require_up_to_date").default(false).notNull(), | |
| 37 | dismissStaleReviews: boolean("dismiss_stale_reviews").default(false).notNull(), | |
| 38 | restrictPush: boolean("restrict_push").default(false).notNull(), | |
| 39 | allowForcePush: boolean("allow_force_push").default(false).notNull(), | |
| 40 | allowDeletion: boolean("allow_deletion").default(false).notNull(), | |
| 41 | isActive: boolean("is_active").default(true).notNull(), | |
| 42 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 43 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 44 | }, | |
| 45 | (table) => [ | |
| 46 | index("branch_protection_repo").on(table.repositoryId), | |
| 47 | uniqueIndex("branch_protection_repo_pattern").on( | |
| 48 | table.repositoryId, | |
| 49 | table.pattern | |
| 50 | ), | |
| 51 | ] | |
| 52 | ); | |
| 53 | ||
| 54 | // ─── Status Checks (CI Integration) ──────────────────────────────────────── | |
| 55 | ||
| 56 | export const statusChecks = pgTable( | |
| 57 | "status_checks", | |
| 58 | { | |
| 59 | id: uuid("id").primaryKey().defaultRandom(), | |
| 60 | repositoryId: uuid("repository_id") | |
| 61 | .notNull() | |
| 62 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 63 | commitSha: text("commit_sha").notNull(), | |
| 64 | context: text("context").notNull(), // e.g. "ci/build", "gatetest/scan" | |
| 65 | state: text("state").notNull().default("pending"), // pending, success, failure, error | |
| 66 | description: text("description"), | |
| 67 | targetUrl: text("target_url"), // link to CI build | |
| 68 | createdBy: text("created_by"), // token name or integration name | |
| 69 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 70 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 71 | }, | |
| 72 | (table) => [ | |
| 73 | index("status_checks_repo_sha").on(table.repositoryId, table.commitSha), | |
| 74 | index("status_checks_repo_context").on(table.repositoryId, table.context), | |
| 75 | ] | |
| 76 | ); | |
| 77 | ||
| 78 | // ─── Notifications ────────────────────────────────────────────────────────── | |
| 79 | ||
| 80 | export const notifications = pgTable( | |
| 81 | "notifications", | |
| 82 | { | |
| 83 | id: uuid("id").primaryKey().defaultRandom(), | |
| 84 | userId: uuid("user_id") | |
| 85 | .notNull() | |
| 86 | .references(() => users.id, { onDelete: "cascade" }), | |
| 87 | type: text("type").notNull(), // issue_comment, pr_review, mention, star, ci_status | |
| 88 | title: text("title").notNull(), | |
| 89 | body: text("body"), | |
| 90 | url: text("url"), // link to the relevant page | |
| 91 | repositoryId: uuid("repository_id").references(() => repositories.id, { | |
| 92 | onDelete: "cascade", | |
| 93 | }), | |
| 94 | actorId: uuid("actor_id").references(() => users.id), | |
| 95 | isRead: boolean("is_read").default(false).notNull(), | |
| 96 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 97 | }, | |
| 98 | (table) => [ | |
| 99 | index("notifications_user_read").on(table.userId, table.isRead), | |
| 100 | index("notifications_user_created").on(table.userId, table.createdAt), | |
| 101 | ] | |
| 102 | ); | |
| 103 | ||
| 104 | // ─── Organizations ────────────────────────────────────────────────────────── | |
| 105 | ||
| 106 | export const organizations = pgTable("organizations", { | |
| 107 | id: uuid("id").primaryKey().defaultRandom(), | |
| 108 | name: text("name").notNull().unique(), | |
| 109 | displayName: text("display_name"), | |
| 110 | description: text("description"), | |
| 111 | avatarUrl: text("avatar_url"), | |
| 112 | website: text("website"), | |
| 113 | location: text("location"), | |
| 114 | isVerified: boolean("is_verified").default(false).notNull(), | |
| 115 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 116 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 117 | }); | |
| 118 | ||
| 119 | export const orgMembers = pgTable( | |
| 120 | "org_members", | |
| 121 | { | |
| 122 | id: uuid("id").primaryKey().defaultRandom(), | |
| 123 | orgId: uuid("org_id") | |
| 124 | .notNull() | |
| 125 | .references(() => organizations.id, { onDelete: "cascade" }), | |
| 126 | userId: uuid("user_id") | |
| 127 | .notNull() | |
| 128 | .references(() => users.id, { onDelete: "cascade" }), | |
| 129 | role: text("role").notNull().default("member"), // owner, admin, member | |
| 130 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 131 | }, | |
| 132 | (table) => [ | |
| 133 | uniqueIndex("org_members_unique").on(table.orgId, table.userId), | |
| 134 | index("org_members_user").on(table.userId), | |
| 135 | ] | |
| 136 | ); | |
| 137 | ||
| 138 | export const teams = pgTable( | |
| 139 | "teams", | |
| 140 | { | |
| 141 | id: uuid("id").primaryKey().defaultRandom(), | |
| 142 | orgId: uuid("org_id") | |
| 143 | .notNull() | |
| 144 | .references(() => organizations.id, { onDelete: "cascade" }), | |
| 145 | name: text("name").notNull(), | |
| 146 | description: text("description"), | |
| 147 | permission: text("permission").notNull().default("read"), // read, write, admin | |
| 148 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 149 | }, | |
| 150 | (table) => [ | |
| 151 | uniqueIndex("teams_org_name").on(table.orgId, table.name), | |
| 152 | ] | |
| 153 | ); | |
| 154 | ||
| 155 | export const teamMembers = pgTable( | |
| 156 | "team_members", | |
| 157 | { | |
| 158 | id: uuid("id").primaryKey().defaultRandom(), | |
| 159 | teamId: uuid("team_id") | |
| 160 | .notNull() | |
| 161 | .references(() => teams.id, { onDelete: "cascade" }), | |
| 162 | userId: uuid("user_id") | |
| 163 | .notNull() | |
| 164 | .references(() => users.id, { onDelete: "cascade" }), | |
| 165 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 166 | }, | |
| 167 | (table) => [ | |
| 168 | uniqueIndex("team_members_unique").on(table.teamId, table.userId), | |
| 169 | ] | |
| 170 | ); | |
| 171 | ||
| 172 | export const teamRepos = pgTable( | |
| 173 | "team_repos", | |
| 174 | { | |
| 175 | id: uuid("id").primaryKey().defaultRandom(), | |
| 176 | teamId: uuid("team_id") | |
| 177 | .notNull() | |
| 178 | .references(() => teams.id, { onDelete: "cascade" }), | |
| 179 | repositoryId: uuid("repository_id") | |
| 180 | .notNull() | |
| 181 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 182 | permission: text("permission").notNull().default("read"), // read, write, admin | |
| 183 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 184 | }, | |
| 185 | (table) => [ | |
| 186 | uniqueIndex("team_repos_unique").on(table.teamId, table.repositoryId), | |
| 187 | ] | |
| 188 | ); | |
| 189 | ||
| 190 | // ─── Type Exports ─────────────────────────────────────────────────────────── | |
| 191 | ||
| 192 | export type BranchProtectionRule = typeof branchProtection.$inferSelect; | |
| 193 | export type StatusCheck = typeof statusChecks.$inferSelect; | |
| 194 | export type Notification = typeof notifications.$inferSelect; | |
| 195 | export type Organization = typeof organizations.$inferSelect; | |
| 196 | export type OrgMember = typeof orgMembers.$inferSelect; | |
| 197 | export type Team = typeof teams.$inferSelect; | |
| 198 | export type TeamMember = typeof teamMembers.$inferSelect; |