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.tsBlame292 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
fc1817aClaude35export 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(),
c81ab7aClaude47 forkedFromId: uuid("forked_from_id").references(() => repositories.id, {
48 onDelete: "set null",
49 }),
fc1817aClaude50 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(),
79136bbClaude55 issueCount: integer("issue_count").default(0).notNull(),
fc1817aClaude56 },
57 (table) => [uniqueIndex("repos_owner_name").on(table.ownerId, table.name)]
58);
59
06d5ffeClaude60export 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 },
79136bbClaude72 (table) => [
73 uniqueIndex("stars_user_repo").on(table.userId, table.repositoryId),
74 ]
75);
76
77export 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
101export 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
118export 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
134export 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 ]
06d5ffeClaude148);
149
0074234Claude150export 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
178export 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
198export 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
c81ab7aClaude218export 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
236export 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
250export 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
fc1817aClaude265export const sshKeys = pgTable("ssh_keys", {
266 id: uuid("id").primaryKey().defaultRandom(),
267 userId: uuid("user_id")
268 .notNull()
06d5ffeClaude269 .references(() => users.id, { onDelete: "cascade" }),
fc1817aClaude270 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
277export type User = typeof users.$inferSelect;
278export type NewUser = typeof users.$inferInsert;
279export type Repository = typeof repositories.$inferSelect;
280export type NewRepository = typeof repositories.$inferInsert;
06d5ffeClaude281export type Session = typeof sessions.$inferSelect;
282export type Star = typeof stars.$inferSelect;
283export type SshKey = typeof sshKeys.$inferSelect;
79136bbClaude284export type Issue = typeof issues.$inferSelect;
285export type IssueComment = typeof issueComments.$inferSelect;
286export type Label = typeof labels.$inferSelect;
0074234Claude287export type PullRequest = typeof pullRequests.$inferSelect;
288export type PrComment = typeof prComments.$inferSelect;
289export type ActivityEntry = typeof activityFeed.$inferSelect;
c81ab7aClaude290export type Webhook = typeof webhooks.$inferSelect;
291export type ApiToken = typeof apiTokens.$inferSelect;
292export type RepoTopic = typeof repoTopics.$inferSelect;