Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
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.

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