kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { createId } from "@paralleldrive/cuid2";
2import { relations, sql } from "drizzle-orm";
3import {
4 boolean,
5 foreignKey,
6 index,
7 integer,
8 jsonb,
9 pgTable,
10 text,
11 timestamp,
12 unique,
13 uniqueIndex,
14} from "drizzle-orm/pg-core";
15
16export const userTable = pgTable("user", {
17 id: text("id")
18 .$defaultFn(() => createId())
19 .primaryKey(),
20 name: text("name").notNull(),
21 email: text("email").notNull().unique(),
22 emailVerified: boolean("email_verified")
23 .$defaultFn(() => false)
24 .notNull(),
25 image: text("image"),
26 locale: text("locale"),
27 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
28 updatedAt: timestamp("updated_at", { mode: "date" })
29 .defaultNow()
30 .$onUpdate(() => /* @__PURE__ */ new Date())
31 .notNull(),
32 isAnonymous: boolean("is_anonymous").default(false),
33});
34
35export const sessionTable = pgTable(
36 "session",
37 {
38 id: text("id").primaryKey(),
39 expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
40 token: text("token").notNull().unique(),
41 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
42 updatedAt: timestamp("updated_at", { mode: "date" })
43 .$onUpdate(() => /* @__PURE__ */ new Date())
44 .notNull(),
45 ipAddress: text("ip_address"),
46 userAgent: text("user_agent"),
47 userId: text("user_id")
48 .notNull()
49 .references(() => userTable.id, { onDelete: "cascade" }),
50 activeOrganizationId: text("active_organization_id"),
51 activeTeamId: text("active_team_id"),
52 },
53 (table) => [index("session_userId_idx").on(table.userId)],
54);
55
56export const accountTable = pgTable(
57 "account",
58 {
59 id: text("id")
60 .$defaultFn(() => createId())
61 .primaryKey(),
62 accountId: text("account_id").notNull(),
63 providerId: text("provider_id").notNull(),
64 userId: text("user_id")
65 .notNull()
66 .references(() => userTable.id, { onDelete: "cascade" }),
67 accessToken: text("access_token"),
68 refreshToken: text("refresh_token"),
69 idToken: text("id_token"),
70 accessTokenExpiresAt: timestamp("access_token_expires_at", {
71 mode: "date",
72 }),
73 refreshTokenExpiresAt: timestamp("refresh_token_expires_at", {
74 mode: "date",
75 }),
76 scope: text("scope"),
77 password: text("password"),
78 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
79 updatedAt: timestamp("updated_at", { mode: "date" })
80 .$onUpdate(() => /* @__PURE__ */ new Date())
81 .notNull(),
82 },
83 (table) => [index("account_userId_idx").on(table.userId)],
84);
85
86export const verificationTable = pgTable(
87 "verification",
88 {
89 id: text("id")
90 .$defaultFn(() => createId())
91 .primaryKey(),
92 identifier: text("identifier").notNull(),
93 value: text("value").notNull(),
94 expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
95 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
96 updatedAt: timestamp("updated_at", { mode: "date" })
97 .defaultNow()
98 .$onUpdate(() => /* @__PURE__ */ new Date())
99 .notNull(),
100 },
101 (table) => [index("verification_identifier_idx").on(table.identifier)],
102);
103
104export const workspaceTable = pgTable("workspace", {
105 id: text("id")
106 .$defaultFn(() => createId())
107 .primaryKey(),
108 name: text("name").notNull(),
109 slug: text("slug").notNull().unique(),
110 logo: text("logo"),
111 metadata: text("metadata"),
112 description: text("description"),
113 createdAt: timestamp("created_at", { mode: "date" }).notNull(),
114});
115
116export const workspaceUserTable = pgTable(
117 "workspace_member",
118 {
119 id: text("id")
120 .$defaultFn(() => createId())
121 .primaryKey(),
122 workspaceId: text("workspace_id")
123 .notNull()
124 .references(() => workspaceTable.id, {
125 onDelete: "cascade",
126 }),
127 userId: text("user_id")
128 .notNull()
129 .references(() => userTable.id, {
130 onDelete: "cascade",
131 }),
132 role: text("role").default("member").notNull(),
133 joinedAt: timestamp("joined_at", { mode: "date" }).notNull(),
134 },
135 (table) => [
136 index("workspace_member_workspaceId_idx").on(table.workspaceId),
137 index("workspace_member_userId_idx").on(table.userId),
138 ],
139);
140
141export const teamTable = pgTable(
142 "team",
143 {
144 id: text("id").primaryKey(),
145 name: text("name").notNull(),
146 workspaceId: text("workspace_id")
147 .notNull()
148 .references(() => workspaceTable.id, { onDelete: "cascade" }),
149 createdAt: timestamp("created_at").notNull(),
150 updatedAt: timestamp("updated_at").$onUpdate(
151 () => /* @__PURE__ */ new Date(),
152 ),
153 },
154 (table) => [index("team_workspaceId_idx").on(table.workspaceId)],
155);
156
157export const teamMemberTable = pgTable(
158 "team_member",
159 {
160 id: text("id").primaryKey(),
161 teamId: text("team_id")
162 .notNull()
163 .references(() => teamTable.id, { onDelete: "cascade" }),
164 userId: text("user_id")
165 .notNull()
166 .references(() => userTable.id, { onDelete: "cascade" }),
167 createdAt: timestamp("created_at"),
168 },
169 (table) => [
170 index("teamMember_teamId_idx").on(table.teamId),
171 index("teamMember_userId_idx").on(table.userId),
172 ],
173);
174
175export const invitationTable = pgTable(
176 "invitation",
177 {
178 id: text("id")
179 .$defaultFn(() => createId())
180 .primaryKey(),
181 workspaceId: text("workspace_id")
182 .notNull()
183 .references(() => workspaceTable.id, { onDelete: "cascade" }),
184 email: text("email").notNull(),
185 role: text("role"),
186 teamId: text("team_id"),
187 status: text("status").default("pending").notNull(),
188 expiresAt: timestamp("expires_at").notNull(),
189 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
190 inviterId: text("inviter_id")
191 .notNull()
192 .references(() => userTable.id, { onDelete: "cascade" }),
193 },
194 (table) => [
195 index("invitation_workspaceId_idx").on(table.workspaceId),
196 index("invitation_email_idx").on(table.email),
197 ],
198);
199
200export const projectTable = pgTable(
201 "project",
202 {
203 id: text("id")
204 .$defaultFn(() => createId())
205 .primaryKey(),
206 workspaceId: text("workspace_id")
207 .notNull()
208 .references(() => workspaceTable.id, {
209 onDelete: "cascade",
210 onUpdate: "cascade",
211 }),
212 slug: text("slug").notNull(),
213 icon: text("icon").default("Layout"),
214 name: text("name").notNull(),
215 description: text("description"),
216 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
217 isPublic: boolean("is_public").default(false),
218 archivedAt: timestamp("archived_at", { mode: "date" }),
219 },
220 (table) => [
221 unique("project_workspace_id_id_unique").on(table.workspaceId, table.id),
222 ],
223);
224
225export const columnTable = pgTable(
226 "column",
227 {
228 id: text("id")
229 .$defaultFn(() => createId())
230 .primaryKey(),
231 projectId: text("project_id")
232 .notNull()
233 .references(() => projectTable.id, {
234 onDelete: "cascade",
235 onUpdate: "cascade",
236 }),
237 name: text("name").notNull(),
238 slug: text("slug").notNull(),
239 position: integer("position").notNull().default(0),
240 icon: text("icon"),
241 color: text("color"),
242 isFinal: boolean("is_final").default(false).notNull(),
243 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
244 updatedAt: timestamp("updated_at", { mode: "date" })
245 .defaultNow()
246 .$onUpdate(() => new Date())
247 .notNull(),
248 },
249 (table) => [index("column_projectId_idx").on(table.projectId)],
250);
251
252export const workflowRuleTable = pgTable(
253 "workflow_rule",
254 {
255 id: text("id")
256 .$defaultFn(() => createId())
257 .primaryKey(),
258 projectId: text("project_id")
259 .notNull()
260 .references(() => projectTable.id, {
261 onDelete: "cascade",
262 onUpdate: "cascade",
263 }),
264 integrationType: text("integration_type").notNull(),
265 eventType: text("event_type").notNull(),
266 columnId: text("column_id")
267 .notNull()
268 .references(() => columnTable.id, {
269 onDelete: "cascade",
270 onUpdate: "cascade",
271 }),
272 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
273 updatedAt: timestamp("updated_at", { mode: "date" })
274 .defaultNow()
275 .$onUpdate(() => new Date())
276 .notNull(),
277 },
278 (table) => [index("workflow_rule_projectId_idx").on(table.projectId)],
279);
280
281export const taskTable = pgTable(
282 "task",
283 {
284 id: text("id")
285 .$defaultFn(() => createId())
286 .primaryKey(),
287 projectId: text("project_id")
288 .notNull()
289 .references(() => projectTable.id, {
290 onDelete: "cascade",
291 onUpdate: "cascade",
292 }),
293 position: integer("position").default(0),
294 number: integer("number").default(1),
295 userId: text("assignee_id").references(() => userTable.id, {
296 onDelete: "cascade",
297 onUpdate: "cascade",
298 }),
299 title: text("title").notNull(),
300 description: text("description"),
301 status: text("status").notNull().default("to-do"),
302 columnId: text("column_id").references(() => columnTable.id, {
303 onDelete: "set null",
304 onUpdate: "cascade",
305 }),
306 priority: text("priority").default("low"),
307 startDate: timestamp("start_date", { mode: "date" }),
308 dueDate: timestamp("due_date", { mode: "date" }),
309 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
310 updatedAt: timestamp("updated_at", { mode: "date" })
311 .defaultNow()
312 .$onUpdate(() => new Date())
313 .notNull(),
314 },
315 (table) => [
316 index("task_projectId_idx").on(table.projectId),
317 index("task_dueDate_idx").on(table.dueDate),
318 unique("task_project_number_unique").on(table.projectId, table.number),
319 ],
320);
321
322export const taskReminderSentTable = pgTable(
323 "task_reminder_sent",
324 {
325 id: text("id")
326 .$defaultFn(() => createId())
327 .primaryKey(),
328 taskId: text("task_id")
329 .notNull()
330 .references(() => taskTable.id, {
331 onDelete: "cascade",
332 onUpdate: "cascade",
333 }),
334 reminderType: text("reminder_type").notNull(),
335 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
336 updatedAt: timestamp("updated_at", { mode: "date" })
337 .defaultNow()
338 .$onUpdate(() => new Date())
339 .notNull(),
340 },
341 (table) => [
342 index("task_reminder_sent_taskId_idx").on(table.taskId),
343 unique("task_reminder_sent_task_type_unique").on(
344 table.taskId,
345 table.reminderType,
346 ),
347 ],
348);
349
350export const timeEntryTable = pgTable("time_entry", {
351 id: text("id")
352 .$defaultFn(() => createId())
353 .primaryKey(),
354 taskId: text("task_id")
355 .notNull()
356 .references(() => taskTable.id, {
357 onDelete: "cascade",
358 onUpdate: "cascade",
359 }),
360 userId: text("user_id").references(() => userTable.id, {
361 onDelete: "cascade",
362 onUpdate: "cascade",
363 }),
364 description: text("description"),
365 startTime: timestamp("start_time", { mode: "date" }).notNull(),
366 endTime: timestamp("end_time", { mode: "date" }),
367 duration: integer("duration").default(0),
368 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
369});
370
371export const activityTable = pgTable(
372 "activity",
373 {
374 id: text("id")
375 .$defaultFn(() => createId())
376 .primaryKey(),
377 taskId: text("task_id")
378 .notNull()
379 .references(() => taskTable.id, {
380 onDelete: "cascade",
381 onUpdate: "cascade",
382 }),
383 type: text("type").notNull(),
384 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
385 updatedAt: timestamp("updated_at", { mode: "date" })
386 .defaultNow()
387 .$onUpdate(() => new Date())
388 .notNull(),
389 userId: text("user_id").references(() => userTable.id, {
390 onDelete: "cascade",
391 onUpdate: "cascade",
392 }),
393 content: text("content"),
394 eventData: jsonb("event_data"),
395 externalUserName: text("external_user_name"),
396 externalUserAvatar: text("external_user_avatar"),
397 externalSource: text("external_source"),
398 externalUrl: text("external_url"),
399 },
400 (table) => [
401 index("activity_task_id_idx").on(table.taskId),
402 unique("activity_task_external_source_external_url_unique").on(
403 table.taskId,
404 table.externalSource,
405 table.externalUrl,
406 ),
407 ],
408);
409
410export const assetTable = pgTable(
411 "asset",
412 {
413 id: text("id")
414 .$defaultFn(() => createId())
415 .primaryKey(),
416 workspaceId: text("workspace_id")
417 .notNull()
418 .references(() => workspaceTable.id, {
419 onDelete: "cascade",
420 onUpdate: "cascade",
421 }),
422 projectId: text("project_id")
423 .notNull()
424 .references(() => projectTable.id, {
425 onDelete: "cascade",
426 onUpdate: "cascade",
427 }),
428 taskId: text("task_id").references(() => taskTable.id, {
429 onDelete: "cascade",
430 onUpdate: "cascade",
431 }),
432 activityId: text("activity_id").references(() => activityTable.id, {
433 onDelete: "cascade",
434 onUpdate: "cascade",
435 }),
436 objectKey: text("object_key").notNull().unique(),
437 filename: text("filename").notNull(),
438 mimeType: text("mime_type").notNull(),
439 size: integer("size").notNull(),
440 kind: text("kind").notNull().default("image"),
441 surface: text("surface").notNull().default("description"),
442 createdBy: text("created_by").references(() => userTable.id, {
443 onDelete: "set null",
444 onUpdate: "cascade",
445 }),
446 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
447 },
448 (table) => [
449 index("asset_workspaceId_idx").on(table.workspaceId),
450 index("asset_projectId_idx").on(table.projectId),
451 index("asset_taskId_idx").on(table.taskId),
452 index("asset_activityId_idx").on(table.activityId),
453 ],
454);
455
456export const labelTable = pgTable(
457 "label",
458 {
459 id: text("id")
460 .$defaultFn(() => createId())
461 .primaryKey(),
462 name: text("name").notNull(),
463 color: text("color").notNull(),
464 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
465 updatedAt: timestamp("updated_at", { mode: "date" })
466 .defaultNow()
467 .$onUpdate(() => new Date())
468 .notNull(),
469 taskId: text("task_id").references(() => taskTable.id, {
470 onDelete: "cascade",
471 onUpdate: "cascade",
472 }),
473 workspaceId: text("workspace_id").references(() => workspaceTable.id, {
474 onDelete: "cascade",
475 onUpdate: "cascade",
476 }),
477 },
478 (table) => [
479 index("label_task_id_idx").on(table.taskId),
480 index("label_workspace_id_idx").on(table.workspaceId),
481 unique("label_task_name_unique").on(table.taskId, table.name),
482 uniqueIndex("label_workspace_name_unique")
483 .on(table.workspaceId, table.name)
484 .where(sql`${table.taskId} is null`),
485 ],
486);
487
488export const notificationTable = pgTable("notification", {
489 id: text("id")
490 .$defaultFn(() => createId())
491 .primaryKey(),
492 userId: text("user_id")
493 .notNull()
494 .references(() => userTable.id, {
495 onDelete: "cascade",
496 onUpdate: "cascade",
497 }),
498 title: text("title"),
499 content: text("content"),
500 type: text("type").notNull().default("info"),
501 eventData: jsonb("event_data"),
502 isRead: boolean("is_read").default(false),
503 resourceId: text("resource_id"),
504 resourceType: text("resource_type"),
505 createdAt: timestamp("created_at", { mode: "date", withTimezone: true })
506 .defaultNow()
507 .notNull(),
508});
509
510export const userNotificationPreferenceTable = pgTable(
511 "user_notification_preference",
512 {
513 id: text("id")
514 .$defaultFn(() => createId())
515 .primaryKey(),
516 userId: text("user_id")
517 .notNull()
518 .unique()
519 .references(() => userTable.id, {
520 onDelete: "cascade",
521 onUpdate: "cascade",
522 }),
523 emailEnabled: boolean("email_enabled").default(false).notNull(),
524 ntfyEnabled: boolean("ntfy_enabled").default(false).notNull(),
525 ntfyServerUrl: text("ntfy_server_url"),
526 ntfyTopic: text("ntfy_topic"),
527 ntfyToken: text("ntfy_token"),
528 gotifyEnabled: boolean("gotify_enabled").default(false).notNull(),
529 gotifyServerUrl: text("gotify_server_url"),
530 gotifyToken: text("gotify_token"),
531 webhookEnabled: boolean("webhook_enabled").default(false).notNull(),
532 webhookUrl: text("webhook_url"),
533 webhookSecret: text("webhook_secret"),
534 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
535 updatedAt: timestamp("updated_at", { mode: "date" })
536 .defaultNow()
537 .$onUpdate(() => new Date())
538 .notNull(),
539 },
540);
541
542export const userNotificationWorkspaceRuleTable = pgTable(
543 "user_notification_workspace_rule",
544 {
545 id: text("id")
546 .$defaultFn(() => createId())
547 .primaryKey(),
548 userId: text("user_id")
549 .notNull()
550 .references(() => userTable.id, {
551 onDelete: "cascade",
552 onUpdate: "cascade",
553 }),
554 workspaceId: text("workspace_id")
555 .notNull()
556 .references(() => workspaceTable.id, {
557 onDelete: "cascade",
558 onUpdate: "cascade",
559 }),
560 isActive: boolean("is_active").default(true).notNull(),
561 emailEnabled: boolean("email_enabled").default(false).notNull(),
562 ntfyEnabled: boolean("ntfy_enabled").default(false).notNull(),
563 gotifyEnabled: boolean("gotify_enabled").default(false).notNull(),
564 webhookEnabled: boolean("webhook_enabled").default(false).notNull(),
565 projectMode: text("project_mode").default("all").notNull(),
566 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
567 updatedAt: timestamp("updated_at", { mode: "date" })
568 .defaultNow()
569 .$onUpdate(() => new Date())
570 .notNull(),
571 },
572 (table) => [
573 index("user_notification_workspace_rule_userId_idx").on(table.userId),
574 index("user_notification_workspace_rule_workspaceId_idx").on(
575 table.workspaceId,
576 ),
577 unique("user_notification_workspace_rule_user_workspace_unique").on(
578 table.userId,
579 table.workspaceId,
580 ),
581 unique("user_notification_workspace_rule_workspace_id_id_unique").on(
582 table.workspaceId,
583 table.id,
584 ),
585 ],
586);
587
588export const userNotificationWorkspaceProjectTable = pgTable(
589 "user_notification_workspace_project",
590 {
591 id: text("id")
592 .$defaultFn(() => createId())
593 .primaryKey(),
594 workspaceId: text("workspace_id")
595 .notNull()
596 .references(() => workspaceTable.id, {
597 onDelete: "cascade",
598 onUpdate: "cascade",
599 }),
600 workspaceRuleId: text("workspace_rule_id").notNull(),
601 projectId: text("project_id").notNull(),
602 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
603 updatedAt: timestamp("updated_at", { mode: "date" })
604 .defaultNow()
605 .$onUpdate(() => new Date())
606 .notNull(),
607 },
608 (table) => [
609 foreignKey({
610 columns: [table.workspaceId, table.workspaceRuleId],
611 foreignColumns: [
612 userNotificationWorkspaceRuleTable.workspaceId,
613 userNotificationWorkspaceRuleTable.id,
614 ],
615 })
616 .onDelete("cascade")
617 .onUpdate("cascade"),
618 foreignKey({
619 columns: [table.workspaceId, table.projectId],
620 foreignColumns: [projectTable.workspaceId, projectTable.id],
621 })
622 .onDelete("cascade")
623 .onUpdate("cascade"),
624 index("user_notification_workspace_project_ruleId_idx").on(
625 table.workspaceRuleId,
626 ),
627 index("user_notification_workspace_project_projectId_idx").on(
628 table.projectId,
629 ),
630 unique("user_notification_workspace_project_rule_project_unique").on(
631 table.workspaceRuleId,
632 table.projectId,
633 ),
634 ],
635);
636
637export const githubIntegrationTable = pgTable("github_integration", {
638 id: text("id")
639 .$defaultFn(() => createId())
640 .primaryKey(),
641 projectId: text("project_id")
642 .notNull()
643 .references(() => projectTable.id, {
644 onDelete: "cascade",
645 onUpdate: "cascade",
646 })
647 .unique(),
648 repositoryOwner: text("repository_owner").notNull(),
649 repositoryName: text("repository_name").notNull(),
650 installationId: integer("installation_id"),
651 isActive: boolean("is_active").default(true),
652 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
653 updatedAt: timestamp("updated_at", { mode: "date" })
654 .defaultNow()
655 .$onUpdate(() => new Date())
656 .notNull(),
657});
658
659export const integrationTable = pgTable(
660 "integration",
661 {
662 id: text("id")
663 .$defaultFn(() => createId())
664 .primaryKey(),
665 projectId: text("project_id")
666 .notNull()
667 .references(() => projectTable.id, {
668 onDelete: "cascade",
669 onUpdate: "cascade",
670 }),
671 type: text("type").notNull(),
672 config: text("config").notNull(),
673 isActive: boolean("is_active").default(true),
674 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
675 updatedAt: timestamp("updated_at", { mode: "date" })
676 .defaultNow()
677 .$onUpdate(() => new Date())
678 .notNull(),
679 },
680 (table) => [
681 index("integration_projectId_idx").on(table.projectId),
682 index("integration_type_idx").on(table.type),
683 unique("integration_project_type_unique").on(table.projectId, table.type),
684 ],
685);
686
687export const externalLinkTable = pgTable(
688 "external_link",
689 {
690 id: text("id")
691 .$defaultFn(() => createId())
692 .primaryKey(),
693 taskId: text("task_id")
694 .notNull()
695 .references(() => taskTable.id, {
696 onDelete: "cascade",
697 onUpdate: "cascade",
698 }),
699 integrationId: text("integration_id")
700 .notNull()
701 .references(() => integrationTable.id, {
702 onDelete: "cascade",
703 onUpdate: "cascade",
704 }),
705 resourceType: text("resource_type").notNull(),
706 externalId: text("external_id").notNull(),
707 url: text("url").notNull(),
708 title: text("title"),
709 metadata: text("metadata"),
710 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
711 updatedAt: timestamp("updated_at", { mode: "date" })
712 .defaultNow()
713 .$onUpdate(() => new Date())
714 .notNull(),
715 },
716 (table) => [
717 index("external_link_taskId_idx").on(table.taskId),
718 index("external_link_integrationId_idx").on(table.integrationId),
719 index("external_link_externalId_idx").on(table.externalId),
720 index("external_link_resourceType_idx").on(table.resourceType),
721 ],
722);
723
724export const commentTable = pgTable(
725 "comment",
726 {
727 id: text("id")
728 .$defaultFn(() => createId())
729 .primaryKey(),
730 taskId: text("task_id")
731 .notNull()
732 .references(() => taskTable.id, {
733 onDelete: "cascade",
734 onUpdate: "cascade",
735 }),
736 userId: text("user_id")
737 .notNull()
738 .references(() => userTable.id, {
739 onDelete: "cascade",
740 onUpdate: "cascade",
741 }),
742 content: text("content").notNull(),
743 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
744 updatedAt: timestamp("updated_at", { mode: "date" })
745 .defaultNow()
746 .$onUpdate(() => new Date())
747 .notNull(),
748 },
749 (table) => [
750 index("comment_task_idx").on(table.taskId),
751 index("comment_user_idx").on(table.userId),
752 ],
753);
754
755export const taskRelationTable = pgTable(
756 "task_relation",
757 {
758 id: text("id")
759 .$defaultFn(() => createId())
760 .primaryKey(),
761 sourceTaskId: text("source_task_id")
762 .notNull()
763 .references(() => taskTable.id, {
764 onDelete: "cascade",
765 onUpdate: "cascade",
766 }),
767 targetTaskId: text("target_task_id")
768 .notNull()
769 .references(() => taskTable.id, {
770 onDelete: "cascade",
771 onUpdate: "cascade",
772 }),
773 relationType: text("relation_type").notNull(),
774 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
775 },
776 (table) => [
777 index("task_relation_source_idx").on(table.sourceTaskId),
778 index("task_relation_target_idx").on(table.targetTaskId),
779 ],
780);
781
782export const apikeyTable = pgTable(
783 "apikey",
784 {
785 id: text("id")
786 .$defaultFn(() => createId())
787 .primaryKey(),
788 configId: text("config_id").default("default").notNull(),
789 name: text("name"),
790 start: text("start"),
791 referenceId: text("reference_id")
792 .notNull()
793 .references(() => userTable.id, { onDelete: "cascade" }),
794 prefix: text("prefix"),
795 key: text("key").notNull(),
796 userId: text("user_id").references(() => userTable.id, {
797 onDelete: "cascade",
798 }),
799 refillInterval: integer("refill_interval"),
800 refillAmount: integer("refill_amount"),
801 lastRefillAt: timestamp("last_refill_at", { mode: "date" }),
802 enabled: boolean("enabled").default(true),
803 rateLimitEnabled: boolean("rate_limit_enabled").default(true),
804 rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000),
805 rateLimitMax: integer("rate_limit_max").default(10),
806 requestCount: integer("request_count").default(0),
807 remaining: integer("remaining"),
808 lastRequest: timestamp("last_request", { mode: "date" }),
809 expiresAt: timestamp("expires_at", { mode: "date" }),
810 createdAt: timestamp("created_at", { mode: "date" }).notNull(),
811 updatedAt: timestamp("updated_at", { mode: "date" }).notNull(),
812 permissions: text("permissions"),
813 metadata: text("metadata"),
814 },
815 (table) => [
816 index("apikey_configId_idx").on(table.configId),
817 index("apikey_key_idx").on(table.key),
818 index("apikey_referenceId_idx").on(table.referenceId),
819 index("apikey_userId_idx").on(table.userId),
820 ],
821);
822
823export const deviceCodeTable = pgTable(
824 "device_code",
825 {
826 id: text("id")
827 .$defaultFn(() => createId())
828 .primaryKey(),
829 deviceCode: text("device_code").notNull(),
830 userCode: text("user_code").notNull(),
831 userId: text("user_id").references(() => userTable.id, {
832 onDelete: "cascade",
833 onUpdate: "cascade",
834 }),
835 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
836 updatedAt: timestamp("updated_at", { mode: "date" })
837 .defaultNow()
838 .$onUpdate(() => new Date())
839 .notNull(),
840 expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
841 status: text("status").notNull(),
842 lastPolledAt: timestamp("last_polled_at", { mode: "date" }),
843 pollingInterval: integer("polling_interval"),
844 clientId: text("client_id"),
845 scope: text("scope"),
846 },
847 (table) => [
848 uniqueIndex("device_code_device_code_uidx").on(table.deviceCode),
849 uniqueIndex("device_code_user_code_uidx").on(table.userCode),
850 index("device_code_user_id_idx").on(table.userId),
851 ],
852);
853
854// Auth-schema compatible aliases in schema.ts
855export const user = userTable;
856export const session = sessionTable;
857export const account = accountTable;
858export const verification = verificationTable;
859export const workspace = workspaceTable;
860export const team = teamTable;
861export const teamMember = teamMemberTable;
862export const workspace_member = workspaceUserTable;
863export const invitation = invitationTable;
864export const apikey = apikeyTable;
865export const deviceCode = deviceCodeTable;
866
867// Auth-schema compatible relation exports in schema.ts
868export const userRelations = relations(user, ({ many }) => ({
869 sessions: many(session),
870 accounts: many(account),
871 teamMembers: many(teamMember),
872 workspace_members: many(workspace_member),
873 invitations: many(invitation),
874}));
875
876export const sessionRelations = relations(session, ({ one }) => ({
877 user: one(user, {
878 fields: [session.userId],
879 references: [user.id],
880 }),
881}));
882
883export const accountRelations = relations(account, ({ one }) => ({
884 user: one(user, {
885 fields: [account.userId],
886 references: [user.id],
887 }),
888}));
889
890export const workspaceRelations = relations(workspace, ({ many }) => ({
891 teams: many(team),
892 workspace_members: many(workspace_member),
893 invitations: many(invitation),
894}));
895
896export const teamRelations = relations(team, ({ one, many }) => ({
897 workspace: one(workspace, {
898 fields: [team.workspaceId],
899 references: [workspace.id],
900 }),
901 teamMembers: many(teamMember),
902}));
903
904export const teamMemberRelations = relations(teamMember, ({ one }) => ({
905 team: one(team, {
906 fields: [teamMember.teamId],
907 references: [team.id],
908 }),
909 user: one(user, {
910 fields: [teamMember.userId],
911 references: [user.id],
912 }),
913}));
914
915export const workspace_memberRelations = relations(
916 workspace_member,
917 ({ one }) => ({
918 workspace: one(workspace, {
919 fields: [workspace_member.workspaceId],
920 references: [workspace.id],
921 }),
922 user: one(user, {
923 fields: [workspace_member.userId],
924 references: [user.id],
925 }),
926 }),
927);
928
929export const invitationRelations = relations(invitation, ({ one }) => ({
930 workspace: one(workspace, {
931 fields: [invitation.workspaceId],
932 references: [workspace.id],
933 }),
934 user: one(user, {
935 fields: [invitation.inviterId],
936 references: [user.id],
937 }),
938}));