Blame · Line-by-line history
schema.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.
| fc1817a | 1 | import { |
| 2 | pgTable, | |
| 3 | text, | |
| 4 | timestamp, | |
| 5 | uuid, | |
| 6 | boolean, | |
| 7 | integer, | |
| 8 | uniqueIndex, | |
| 79136bb | 9 | index, |
| 10 | serial, | |
| fc1817a | 11 | } from "drizzle-orm/pg-core"; |
| 12 | ||
| 13 | export const users = pgTable("users", { | |
| 14 | id: uuid("id").primaryKey().defaultRandom(), | |
| 15 | username: text("username").notNull().unique(), | |
| 16 | email: text("email").notNull().unique(), | |
| 17 | displayName: text("display_name"), | |
| 18 | passwordHash: text("password_hash").notNull(), | |
| 19 | avatarUrl: text("avatar_url"), | |
| 20 | bio: text("bio"), | |
| 21 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 22 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 23 | }); | |
| 24 | ||
| 06d5ffe | 25 | export const sessions = pgTable("sessions", { |
| 26 | id: uuid("id").primaryKey().defaultRandom(), | |
| 27 | userId: uuid("user_id") | |
| 28 | .notNull() | |
| 29 | .references(() => users.id, { onDelete: "cascade" }), | |
| 30 | token: text("token").notNull().unique(), | |
| 31 | expiresAt: timestamp("expires_at").notNull(), | |
| 32 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 33 | }); | |
| 34 | ||
| fc1817a | 35 | export const repositories = pgTable( |
| 36 | "repositories", | |
| 37 | { | |
| 38 | id: uuid("id").primaryKey().defaultRandom(), | |
| 39 | name: text("name").notNull(), | |
| 40 | ownerId: uuid("owner_id") | |
| 41 | .notNull() | |
| 42 | .references(() => users.id), | |
| 43 | description: text("description"), | |
| 44 | isPrivate: boolean("is_private").default(false).notNull(), | |
| 45 | defaultBranch: text("default_branch").default("main").notNull(), | |
| 46 | diskPath: text("disk_path").notNull(), | |
| c81ab7a | 47 | forkedFromId: uuid("forked_from_id").references(() => repositories.id, { |
| 48 | onDelete: "set null", | |
| 49 | }), | |
| fc1817a | 50 | createdAt: timestamp("created_at").defaultNow().notNull(), |
| 51 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 52 | pushedAt: timestamp("pushed_at"), | |
| 53 | starCount: integer("star_count").default(0).notNull(), | |
| 54 | forkCount: integer("fork_count").default(0).notNull(), | |
| 79136bb | 55 | issueCount: integer("issue_count").default(0).notNull(), |
| fc1817a | 56 | }, |
| 57 | (table) => [uniqueIndex("repos_owner_name").on(table.ownerId, table.name)] | |
| 58 | ); | |
| 59 | ||
| 06d5ffe | 60 | export const stars = pgTable( |
| 61 | "stars", | |
| 62 | { | |
| 63 | id: uuid("id").primaryKey().defaultRandom(), | |
| 64 | userId: uuid("user_id") | |
| 65 | .notNull() | |
| 66 | .references(() => users.id, { onDelete: "cascade" }), | |
| 67 | repositoryId: uuid("repository_id") | |
| 68 | .notNull() | |
| 69 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 70 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 71 | }, | |
| 79136bb | 72 | (table) => [ |
| 73 | uniqueIndex("stars_user_repo").on(table.userId, table.repositoryId), | |
| 74 | ] | |
| 75 | ); | |
| 76 | ||
| 77 | export const issues = pgTable( | |
| 78 | "issues", | |
| 79 | { | |
| 80 | id: uuid("id").primaryKey().defaultRandom(), | |
| 81 | number: serial("number"), | |
| 82 | repositoryId: uuid("repository_id") | |
| 83 | .notNull() | |
| 84 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 85 | authorId: uuid("author_id") | |
| 86 | .notNull() | |
| 87 | .references(() => users.id), | |
| 88 | title: text("title").notNull(), | |
| 89 | body: text("body"), | |
| 90 | state: text("state").notNull().default("open"), // open, closed | |
| 91 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 92 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 93 | closedAt: timestamp("closed_at"), | |
| 94 | }, | |
| 95 | (table) => [ | |
| 96 | index("issues_repo_state").on(table.repositoryId, table.state), | |
| 97 | index("issues_repo_number").on(table.repositoryId, table.number), | |
| 98 | ] | |
| 99 | ); | |
| 100 | ||
| 101 | export const issueComments = pgTable( | |
| 102 | "issue_comments", | |
| 103 | { | |
| 104 | id: uuid("id").primaryKey().defaultRandom(), | |
| 105 | issueId: uuid("issue_id") | |
| 106 | .notNull() | |
| 107 | .references(() => issues.id, { onDelete: "cascade" }), | |
| 108 | authorId: uuid("author_id") | |
| 109 | .notNull() | |
| 110 | .references(() => users.id), | |
| 111 | body: text("body").notNull(), | |
| 112 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 113 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 114 | }, | |
| 115 | (table) => [index("comments_issue").on(table.issueId)] | |
| 116 | ); | |
| 117 | ||
| 118 | export const labels = pgTable( | |
| 119 | "labels", | |
| 120 | { | |
| 121 | id: uuid("id").primaryKey().defaultRandom(), | |
| 122 | repositoryId: uuid("repository_id") | |
| 123 | .notNull() | |
| 124 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 125 | name: text("name").notNull(), | |
| 126 | color: text("color").notNull().default("#8b949e"), | |
| 127 | description: text("description"), | |
| 128 | }, | |
| 129 | (table) => [ | |
| 130 | uniqueIndex("labels_repo_name").on(table.repositoryId, table.name), | |
| 131 | ] | |
| 132 | ); | |
| 133 | ||
| 134 | export const issueLabels = pgTable( | |
| 135 | "issue_labels", | |
| 136 | { | |
| 137 | id: uuid("id").primaryKey().defaultRandom(), | |
| 138 | issueId: uuid("issue_id") | |
| 139 | .notNull() | |
| 140 | .references(() => issues.id, { onDelete: "cascade" }), | |
| 141 | labelId: uuid("label_id") | |
| 142 | .notNull() | |
| 143 | .references(() => labels.id, { onDelete: "cascade" }), | |
| 144 | }, | |
| 145 | (table) => [ | |
| 146 | uniqueIndex("issue_labels_unique").on(table.issueId, table.labelId), | |
| 147 | ] | |
| 06d5ffe | 148 | ); |
| 149 | ||
| 0074234 | 150 | export const pullRequests = pgTable( |
| 151 | "pull_requests", | |
| 152 | { | |
| 153 | id: uuid("id").primaryKey().defaultRandom(), | |
| 154 | number: serial("number"), | |
| 155 | repositoryId: uuid("repository_id") | |
| 156 | .notNull() | |
| 157 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 158 | authorId: uuid("author_id") | |
| 159 | .notNull() | |
| 160 | .references(() => users.id), | |
| 161 | title: text("title").notNull(), | |
| 162 | body: text("body"), | |
| 163 | state: text("state").notNull().default("open"), // open, closed, merged | |
| 164 | baseBranch: text("base_branch").notNull(), | |
| 165 | headBranch: text("head_branch").notNull(), | |
| 166 | mergedAt: timestamp("merged_at"), | |
| 167 | mergedBy: uuid("merged_by").references(() => users.id), | |
| 168 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 169 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 170 | closedAt: timestamp("closed_at"), | |
| 171 | }, | |
| 172 | (table) => [ | |
| 173 | index("prs_repo_state").on(table.repositoryId, table.state), | |
| 174 | index("prs_repo_number").on(table.repositoryId, table.number), | |
| 175 | ] | |
| 176 | ); | |
| 177 | ||
| 178 | export const prComments = pgTable( | |
| 179 | "pr_comments", | |
| 180 | { | |
| 181 | id: uuid("id").primaryKey().defaultRandom(), | |
| 182 | pullRequestId: uuid("pull_request_id") | |
| 183 | .notNull() | |
| 184 | .references(() => pullRequests.id, { onDelete: "cascade" }), | |
| 185 | authorId: uuid("author_id") | |
| 186 | .notNull() | |
| 187 | .references(() => users.id), | |
| 188 | body: text("body").notNull(), | |
| 189 | isAiReview: boolean("is_ai_review").default(false).notNull(), | |
| 190 | filePath: text("file_path"), | |
| 191 | lineNumber: integer("line_number"), | |
| 192 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 193 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 194 | }, | |
| 195 | (table) => [index("pr_comments_pr").on(table.pullRequestId)] | |
| 196 | ); | |
| 197 | ||
| 198 | export const activityFeed = pgTable( | |
| 199 | "activity_feed", | |
| 200 | { | |
| 201 | id: uuid("id").primaryKey().defaultRandom(), | |
| 202 | repositoryId: uuid("repository_id") | |
| 203 | .notNull() | |
| 204 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 205 | userId: uuid("user_id").references(() => users.id), | |
| 206 | action: text("action").notNull(), // push, issue_open, issue_close, pr_open, pr_merge, star, comment | |
| 207 | targetType: text("target_type"), // issue, pr, commit | |
| 208 | targetId: text("target_id"), | |
| 209 | metadata: text("metadata"), // JSON string for extra data | |
| 210 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 211 | }, | |
| 212 | (table) => [ | |
| 213 | index("activity_repo").on(table.repositoryId), | |
| 214 | index("activity_user").on(table.userId), | |
| 215 | ] | |
| 216 | ); | |
| 217 | ||
| c81ab7a | 218 | export const webhooks = pgTable( |
| 219 | "webhooks", | |
| 220 | { | |
| 221 | id: uuid("id").primaryKey().defaultRandom(), | |
| 222 | repositoryId: uuid("repository_id") | |
| 223 | .notNull() | |
| 224 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 225 | url: text("url").notNull(), | |
| 226 | secret: text("secret"), | |
| 227 | events: text("events").notNull().default("push"), // comma-separated: push,issue,pr | |
| 228 | isActive: boolean("is_active").default(true).notNull(), | |
| 229 | lastDeliveredAt: timestamp("last_delivered_at"), | |
| 230 | lastStatus: integer("last_status"), | |
| 231 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 232 | }, | |
| 233 | (table) => [index("webhooks_repo").on(table.repositoryId)] | |
| 234 | ); | |
| 235 | ||
| 236 | export const apiTokens = pgTable("api_tokens", { | |
| 237 | id: uuid("id").primaryKey().defaultRandom(), | |
| 238 | userId: uuid("user_id") | |
| 239 | .notNull() | |
| 240 | .references(() => users.id, { onDelete: "cascade" }), | |
| 241 | name: text("name").notNull(), | |
| 242 | tokenHash: text("token_hash").notNull(), | |
| 243 | tokenPrefix: text("token_prefix").notNull(), // first 8 chars for display | |
| 244 | scopes: text("scopes").notNull().default("repo"), // comma-separated | |
| 245 | lastUsedAt: timestamp("last_used_at"), | |
| 246 | expiresAt: timestamp("expires_at"), | |
| 247 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 248 | }); | |
| 249 | ||
| 250 | export const repoTopics = pgTable( | |
| 251 | "repo_topics", | |
| 252 | { | |
| 253 | id: uuid("id").primaryKey().defaultRandom(), | |
| 254 | repositoryId: uuid("repository_id") | |
| 255 | .notNull() | |
| 256 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 257 | topic: text("topic").notNull(), | |
| 258 | }, | |
| 259 | (table) => [ | |
| 260 | uniqueIndex("repo_topics_unique").on(table.repositoryId, table.topic), | |
| 261 | index("topics_name").on(table.topic), | |
| 262 | ] | |
| 263 | ); | |
| 264 | ||
| fc1817a | 265 | export const sshKeys = pgTable("ssh_keys", { |
| 266 | id: uuid("id").primaryKey().defaultRandom(), | |
| 267 | userId: uuid("user_id") | |
| 268 | .notNull() | |
| 06d5ffe | 269 | .references(() => users.id, { onDelete: "cascade" }), |
| fc1817a | 270 | title: text("title").notNull(), |
| 271 | fingerprint: text("fingerprint").notNull(), | |
| 272 | publicKey: text("public_key").notNull(), | |
| 273 | lastUsedAt: timestamp("last_used_at"), | |
| 274 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 275 | }); | |
| 276 | ||
| 277 | export type User = typeof users.$inferSelect; | |
| 278 | export type NewUser = typeof users.$inferInsert; | |
| 279 | export type Repository = typeof repositories.$inferSelect; | |
| 280 | export type NewRepository = typeof repositories.$inferInsert; | |
| 06d5ffe | 281 | export type Session = typeof sessions.$inferSelect; |
| 282 | export type Star = typeof stars.$inferSelect; | |
| 283 | export type SshKey = typeof sshKeys.$inferSelect; | |
| 79136bb | 284 | export type Issue = typeof issues.$inferSelect; |
| 285 | export type IssueComment = typeof issueComments.$inferSelect; | |
| 286 | export type Label = typeof labels.$inferSelect; | |
| 0074234 | 287 | export type PullRequest = typeof pullRequests.$inferSelect; |
| 288 | export type PrComment = typeof prComments.$inferSelect; | |
| 289 | export type ActivityEntry = typeof activityFeed.$inferSelect; | |
| c81ab7a | 290 | export type Webhook = typeof webhooks.$inferSelect; |
| 291 | export type ApiToken = typeof apiTokens.$inferSelect; | |
| 292 | export type RepoTopic = typeof repoTopics.$inferSelect; |