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(), | |
| 47 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 48 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 49 | pushedAt: timestamp("pushed_at"), | |
| 50 | starCount: integer("star_count").default(0).notNull(), | |
| 51 | forkCount: integer("fork_count").default(0).notNull(), | |
| 79136bb | 52 | issueCount: integer("issue_count").default(0).notNull(), |
| fc1817a | 53 | }, |
| 54 | (table) => [uniqueIndex("repos_owner_name").on(table.ownerId, table.name)] | |
| 55 | ); | |
| 56 | ||
| 06d5ffe | 57 | export const stars = pgTable( |
| 58 | "stars", | |
| 59 | { | |
| 60 | id: uuid("id").primaryKey().defaultRandom(), | |
| 61 | userId: uuid("user_id") | |
| 62 | .notNull() | |
| 63 | .references(() => users.id, { onDelete: "cascade" }), | |
| 64 | repositoryId: uuid("repository_id") | |
| 65 | .notNull() | |
| 66 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 67 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 68 | }, | |
| 79136bb | 69 | (table) => [ |
| 70 | uniqueIndex("stars_user_repo").on(table.userId, table.repositoryId), | |
| 71 | ] | |
| 72 | ); | |
| 73 | ||
| 74 | export const issues = pgTable( | |
| 75 | "issues", | |
| 76 | { | |
| 77 | id: uuid("id").primaryKey().defaultRandom(), | |
| 78 | number: serial("number"), | |
| 79 | repositoryId: uuid("repository_id") | |
| 80 | .notNull() | |
| 81 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 82 | authorId: uuid("author_id") | |
| 83 | .notNull() | |
| 84 | .references(() => users.id), | |
| 85 | title: text("title").notNull(), | |
| 86 | body: text("body"), | |
| 87 | state: text("state").notNull().default("open"), // open, closed | |
| 88 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 89 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 90 | closedAt: timestamp("closed_at"), | |
| 91 | }, | |
| 92 | (table) => [ | |
| 93 | index("issues_repo_state").on(table.repositoryId, table.state), | |
| 94 | index("issues_repo_number").on(table.repositoryId, table.number), | |
| 95 | ] | |
| 96 | ); | |
| 97 | ||
| 98 | export const issueComments = pgTable( | |
| 99 | "issue_comments", | |
| 100 | { | |
| 101 | id: uuid("id").primaryKey().defaultRandom(), | |
| 102 | issueId: uuid("issue_id") | |
| 103 | .notNull() | |
| 104 | .references(() => issues.id, { onDelete: "cascade" }), | |
| 105 | authorId: uuid("author_id") | |
| 106 | .notNull() | |
| 107 | .references(() => users.id), | |
| 108 | body: text("body").notNull(), | |
| 109 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 110 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 111 | }, | |
| 112 | (table) => [index("comments_issue").on(table.issueId)] | |
| 113 | ); | |
| 114 | ||
| 115 | export const labels = pgTable( | |
| 116 | "labels", | |
| 117 | { | |
| 118 | id: uuid("id").primaryKey().defaultRandom(), | |
| 119 | repositoryId: uuid("repository_id") | |
| 120 | .notNull() | |
| 121 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 122 | name: text("name").notNull(), | |
| 123 | color: text("color").notNull().default("#8b949e"), | |
| 124 | description: text("description"), | |
| 125 | }, | |
| 126 | (table) => [ | |
| 127 | uniqueIndex("labels_repo_name").on(table.repositoryId, table.name), | |
| 128 | ] | |
| 129 | ); | |
| 130 | ||
| 131 | export const issueLabels = pgTable( | |
| 132 | "issue_labels", | |
| 133 | { | |
| 134 | id: uuid("id").primaryKey().defaultRandom(), | |
| 135 | issueId: uuid("issue_id") | |
| 136 | .notNull() | |
| 137 | .references(() => issues.id, { onDelete: "cascade" }), | |
| 138 | labelId: uuid("label_id") | |
| 139 | .notNull() | |
| 140 | .references(() => labels.id, { onDelete: "cascade" }), | |
| 141 | }, | |
| 142 | (table) => [ | |
| 143 | uniqueIndex("issue_labels_unique").on(table.issueId, table.labelId), | |
| 144 | ] | |
| 06d5ffe | 145 | ); |
| 146 | ||
| 0074234 | 147 | export const pullRequests = pgTable( |
| 148 | "pull_requests", | |
| 149 | { | |
| 150 | id: uuid("id").primaryKey().defaultRandom(), | |
| 151 | number: serial("number"), | |
| 152 | repositoryId: uuid("repository_id") | |
| 153 | .notNull() | |
| 154 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 155 | authorId: uuid("author_id") | |
| 156 | .notNull() | |
| 157 | .references(() => users.id), | |
| 158 | title: text("title").notNull(), | |
| 159 | body: text("body"), | |
| 160 | state: text("state").notNull().default("open"), // open, closed, merged | |
| 161 | baseBranch: text("base_branch").notNull(), | |
| 162 | headBranch: text("head_branch").notNull(), | |
| 163 | mergedAt: timestamp("merged_at"), | |
| 164 | mergedBy: uuid("merged_by").references(() => users.id), | |
| 165 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 166 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 167 | closedAt: timestamp("closed_at"), | |
| 168 | }, | |
| 169 | (table) => [ | |
| 170 | index("prs_repo_state").on(table.repositoryId, table.state), | |
| 171 | index("prs_repo_number").on(table.repositoryId, table.number), | |
| 172 | ] | |
| 173 | ); | |
| 174 | ||
| 175 | export const prComments = pgTable( | |
| 176 | "pr_comments", | |
| 177 | { | |
| 178 | id: uuid("id").primaryKey().defaultRandom(), | |
| 179 | pullRequestId: uuid("pull_request_id") | |
| 180 | .notNull() | |
| 181 | .references(() => pullRequests.id, { onDelete: "cascade" }), | |
| 182 | authorId: uuid("author_id") | |
| 183 | .notNull() | |
| 184 | .references(() => users.id), | |
| 185 | body: text("body").notNull(), | |
| 186 | isAiReview: boolean("is_ai_review").default(false).notNull(), | |
| 187 | filePath: text("file_path"), | |
| 188 | lineNumber: integer("line_number"), | |
| 189 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 190 | updatedAt: timestamp("updated_at").defaultNow().notNull(), | |
| 191 | }, | |
| 192 | (table) => [index("pr_comments_pr").on(table.pullRequestId)] | |
| 193 | ); | |
| 194 | ||
| 195 | export const activityFeed = pgTable( | |
| 196 | "activity_feed", | |
| 197 | { | |
| 198 | id: uuid("id").primaryKey().defaultRandom(), | |
| 199 | repositoryId: uuid("repository_id") | |
| 200 | .notNull() | |
| 201 | .references(() => repositories.id, { onDelete: "cascade" }), | |
| 202 | userId: uuid("user_id").references(() => users.id), | |
| 203 | action: text("action").notNull(), // push, issue_open, issue_close, pr_open, pr_merge, star, comment | |
| 204 | targetType: text("target_type"), // issue, pr, commit | |
| 205 | targetId: text("target_id"), | |
| 206 | metadata: text("metadata"), // JSON string for extra data | |
| 207 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 208 | }, | |
| 209 | (table) => [ | |
| 210 | index("activity_repo").on(table.repositoryId), | |
| 211 | index("activity_user").on(table.userId), | |
| 212 | ] | |
| 213 | ); | |
| 214 | ||
| fc1817a | 215 | export const sshKeys = pgTable("ssh_keys", { |
| 216 | id: uuid("id").primaryKey().defaultRandom(), | |
| 217 | userId: uuid("user_id") | |
| 218 | .notNull() | |
| 06d5ffe | 219 | .references(() => users.id, { onDelete: "cascade" }), |
| fc1817a | 220 | title: text("title").notNull(), |
| 221 | fingerprint: text("fingerprint").notNull(), | |
| 222 | publicKey: text("public_key").notNull(), | |
| 223 | lastUsedAt: timestamp("last_used_at"), | |
| 224 | createdAt: timestamp("created_at").defaultNow().notNull(), | |
| 225 | }); | |
| 226 | ||
| 227 | export type User = typeof users.$inferSelect; | |
| 228 | export type NewUser = typeof users.$inferInsert; | |
| 229 | export type Repository = typeof repositories.$inferSelect; | |
| 230 | export type NewRepository = typeof repositories.$inferInsert; | |
| 06d5ffe | 231 | export type Session = typeof sessions.$inferSelect; |
| 232 | export type Star = typeof stars.$inferSelect; | |
| 233 | export type SshKey = typeof sshKeys.$inferSelect; | |
| 79136bb | 234 | export type Issue = typeof issues.$inferSelect; |
| 235 | export type IssueComment = typeof issueComments.$inferSelect; | |
| 236 | export type Label = typeof labels.$inferSelect; | |
| 0074234 | 237 | export type PullRequest = typeof pullRequests.$inferSelect; |
| 238 | export type PrComment = typeof prComments.$inferSelect; | |
| 239 | export type ActivityEntry = typeof activityFeed.$inferSelect; |