kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
at main 632 lines 19 kB view raw
1import { createId } from "@paralleldrive/cuid2"; 2import { relations } from "drizzle-orm"; 3import { 4 boolean, 5 index, 6 integer, 7 pgTable, 8 text, 9 timestamp, 10} from "drizzle-orm/pg-core"; 11 12export const userTable = pgTable("user", { 13 id: text("id") 14 .$defaultFn(() => createId()) 15 .primaryKey(), 16 name: text("name").notNull(), 17 email: text("email").notNull().unique(), 18 emailVerified: boolean("email_verified") 19 .$defaultFn(() => false) 20 .notNull(), 21 image: text("image"), 22 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 23 updatedAt: timestamp("updated_at", { mode: "date" }) 24 .defaultNow() 25 .$onUpdate(() => /* @__PURE__ */ new Date()) 26 .notNull(), 27 isAnonymous: boolean("is_anonymous").default(false), 28}); 29 30export const sessionTable = pgTable( 31 "session", 32 { 33 id: text("id").primaryKey(), 34 expiresAt: timestamp("expires_at", { mode: "date" }).notNull(), 35 token: text("token").notNull().unique(), 36 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 37 updatedAt: timestamp("updated_at", { mode: "date" }) 38 .$onUpdate(() => /* @__PURE__ */ new Date()) 39 .notNull(), 40 ipAddress: text("ip_address"), 41 userAgent: text("user_agent"), 42 userId: text("user_id") 43 .notNull() 44 .references(() => userTable.id, { onDelete: "cascade" }), 45 activeOrganizationId: text("active_organization_id"), 46 activeTeamId: text("active_team_id"), 47 }, 48 (table) => [index("session_userId_idx").on(table.userId)], 49); 50 51export const accountTable = pgTable( 52 "account", 53 { 54 id: text("id") 55 .$defaultFn(() => createId()) 56 .primaryKey(), 57 accountId: text("account_id").notNull(), 58 providerId: text("provider_id").notNull(), 59 userId: text("user_id") 60 .notNull() 61 .references(() => userTable.id, { onDelete: "cascade" }), 62 accessToken: text("access_token"), 63 refreshToken: text("refresh_token"), 64 idToken: text("id_token"), 65 accessTokenExpiresAt: timestamp("access_token_expires_at", { 66 mode: "date", 67 }), 68 refreshTokenExpiresAt: timestamp("refresh_token_expires_at", { 69 mode: "date", 70 }), 71 scope: text("scope"), 72 password: text("password"), 73 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 74 updatedAt: timestamp("updated_at", { mode: "date" }) 75 .$onUpdate(() => /* @__PURE__ */ new Date()) 76 .notNull(), 77 }, 78 (table) => [index("account_userId_idx").on(table.userId)], 79); 80 81export const verificationTable = pgTable( 82 "verification", 83 { 84 id: text("id") 85 .$defaultFn(() => createId()) 86 .primaryKey(), 87 identifier: text("identifier").notNull(), 88 value: text("value").notNull(), 89 expiresAt: timestamp("expires_at", { mode: "date" }).notNull(), 90 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 91 updatedAt: timestamp("updated_at", { mode: "date" }) 92 .defaultNow() 93 .$onUpdate(() => /* @__PURE__ */ new Date()) 94 .notNull(), 95 }, 96 (table) => [index("verification_identifier_idx").on(table.identifier)], 97); 98 99export const workspaceTable = pgTable("workspace", { 100 id: text("id") 101 .$defaultFn(() => createId()) 102 .primaryKey(), 103 name: text("name").notNull(), 104 slug: text("slug").notNull().unique(), 105 logo: text("logo"), 106 metadata: text("metadata"), 107 description: text("description"), 108 createdAt: timestamp("created_at", { mode: "date" }).notNull(), 109}); 110 111export const workspaceUserTable = pgTable( 112 "workspace_member", 113 { 114 id: text("id") 115 .$defaultFn(() => createId()) 116 .primaryKey(), 117 workspaceId: text("workspace_id") 118 .notNull() 119 .references(() => workspaceTable.id, { 120 onDelete: "cascade", 121 }), 122 userId: text("user_id") 123 .notNull() 124 .references(() => userTable.id, { 125 onDelete: "cascade", 126 }), 127 role: text("role").default("member").notNull(), 128 joinedAt: timestamp("joined_at", { mode: "date" }).notNull(), 129 }, 130 (table) => [ 131 index("workspace_member_workspaceId_idx").on(table.workspaceId), 132 index("workspace_member_userId_idx").on(table.userId), 133 ], 134); 135 136export const teamTable = pgTable( 137 "team", 138 { 139 id: text("id").primaryKey(), 140 name: text("name").notNull(), 141 workspaceId: text("workspace_id") 142 .notNull() 143 .references(() => workspaceTable.id, { onDelete: "cascade" }), 144 createdAt: timestamp("created_at").notNull(), 145 updatedAt: timestamp("updated_at").$onUpdate( 146 () => /* @__PURE__ */ new Date(), 147 ), 148 }, 149 (table) => [index("team_workspaceId_idx").on(table.workspaceId)], 150); 151 152export const teamMemberTable = pgTable( 153 "team_member", 154 { 155 id: text("id").primaryKey(), 156 teamId: text("team_id") 157 .notNull() 158 .references(() => teamTable.id, { onDelete: "cascade" }), 159 userId: text("user_id") 160 .notNull() 161 .references(() => userTable.id, { onDelete: "cascade" }), 162 createdAt: timestamp("created_at"), 163 }, 164 (table) => [ 165 index("teamMember_teamId_idx").on(table.teamId), 166 index("teamMember_userId_idx").on(table.userId), 167 ], 168); 169 170export const invitationTable = pgTable( 171 "invitation", 172 { 173 id: text("id") 174 .$defaultFn(() => createId()) 175 .primaryKey(), 176 workspaceId: text("workspace_id") 177 .notNull() 178 .references(() => workspaceTable.id, { onDelete: "cascade" }), 179 email: text("email").notNull(), 180 role: text("role"), 181 teamId: text("team_id"), 182 status: text("status").default("pending").notNull(), 183 expiresAt: timestamp("expires_at").notNull(), 184 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 185 inviterId: text("inviter_id") 186 .notNull() 187 .references(() => userTable.id, { onDelete: "cascade" }), 188 }, 189 (table) => [ 190 index("invitation_workspaceId_idx").on(table.workspaceId), 191 index("invitation_email_idx").on(table.email), 192 ], 193); 194 195export const projectTable = pgTable("project", { 196 id: text("id") 197 .$defaultFn(() => createId()) 198 .primaryKey(), 199 workspaceId: text("workspace_id") 200 .notNull() 201 .references(() => workspaceTable.id, { 202 onDelete: "cascade", 203 onUpdate: "cascade", 204 }), 205 slug: text("slug").notNull(), 206 icon: text("icon").default("Layout"), 207 name: text("name").notNull(), 208 description: text("description"), 209 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 210 isPublic: boolean("is_public").default(false), 211}); 212 213export const columnTable = pgTable( 214 "column", 215 { 216 id: text("id") 217 .$defaultFn(() => createId()) 218 .primaryKey(), 219 projectId: text("project_id") 220 .notNull() 221 .references(() => projectTable.id, { 222 onDelete: "cascade", 223 onUpdate: "cascade", 224 }), 225 name: text("name").notNull(), 226 slug: text("slug").notNull(), 227 position: integer("position").notNull().default(0), 228 icon: text("icon"), 229 color: text("color"), 230 isFinal: boolean("is_final").default(false).notNull(), 231 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 232 updatedAt: timestamp("updated_at", { mode: "date" }) 233 .defaultNow() 234 .$onUpdate(() => new Date()) 235 .notNull(), 236 }, 237 (table) => [index("column_projectId_idx").on(table.projectId)], 238); 239 240export const workflowRuleTable = pgTable( 241 "workflow_rule", 242 { 243 id: text("id") 244 .$defaultFn(() => createId()) 245 .primaryKey(), 246 projectId: text("project_id") 247 .notNull() 248 .references(() => projectTable.id, { 249 onDelete: "cascade", 250 onUpdate: "cascade", 251 }), 252 integrationType: text("integration_type").notNull(), 253 eventType: text("event_type").notNull(), 254 columnId: text("column_id") 255 .notNull() 256 .references(() => columnTable.id, { 257 onDelete: "cascade", 258 onUpdate: "cascade", 259 }), 260 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 261 updatedAt: timestamp("updated_at", { mode: "date" }) 262 .defaultNow() 263 .$onUpdate(() => new Date()) 264 .notNull(), 265 }, 266 (table) => [index("workflow_rule_projectId_idx").on(table.projectId)], 267); 268 269export const taskTable = pgTable("task", { 270 id: text("id") 271 .$defaultFn(() => createId()) 272 .primaryKey(), 273 projectId: text("project_id") 274 .notNull() 275 .references(() => projectTable.id, { 276 onDelete: "cascade", 277 onUpdate: "cascade", 278 }), 279 position: integer("position").default(0), 280 number: integer("number").default(1), 281 userId: text("assignee_id").references(() => userTable.id, { 282 onDelete: "cascade", 283 onUpdate: "cascade", 284 }), 285 title: text("title").notNull(), 286 description: text("description"), 287 status: text("status").notNull().default("to-do"), 288 columnId: text("column_id").references(() => columnTable.id, { 289 onDelete: "set null", 290 onUpdate: "cascade", 291 }), 292 priority: text("priority").default("low"), 293 dueDate: timestamp("due_date", { mode: "date" }), 294 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 295}); 296 297export const timeEntryTable = pgTable("time_entry", { 298 id: text("id") 299 .$defaultFn(() => createId()) 300 .primaryKey(), 301 taskId: text("task_id") 302 .notNull() 303 .references(() => taskTable.id, { 304 onDelete: "cascade", 305 onUpdate: "cascade", 306 }), 307 userId: text("user_id").references(() => userTable.id, { 308 onDelete: "cascade", 309 onUpdate: "cascade", 310 }), 311 description: text("description"), 312 startTime: timestamp("start_time", { mode: "date" }).notNull(), 313 endTime: timestamp("end_time", { mode: "date" }), 314 duration: integer("duration").default(0), 315 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 316}); 317 318export const activityTable = pgTable("activity", { 319 id: text("id") 320 .$defaultFn(() => createId()) 321 .primaryKey(), 322 taskId: text("task_id") 323 .notNull() 324 .references(() => taskTable.id, { 325 onDelete: "cascade", 326 onUpdate: "cascade", 327 }), 328 type: text("type").notNull(), 329 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 330 userId: text("user_id").references(() => userTable.id, { 331 onDelete: "cascade", 332 onUpdate: "cascade", 333 }), 334 content: text("content"), 335 externalUserName: text("external_user_name"), 336 externalUserAvatar: text("external_user_avatar"), 337 externalSource: text("external_source"), 338 externalUrl: text("external_url"), 339}); 340 341export const assetTable = pgTable( 342 "asset", 343 { 344 id: text("id") 345 .$defaultFn(() => createId()) 346 .primaryKey(), 347 workspaceId: text("workspace_id") 348 .notNull() 349 .references(() => workspaceTable.id, { 350 onDelete: "cascade", 351 onUpdate: "cascade", 352 }), 353 projectId: text("project_id") 354 .notNull() 355 .references(() => projectTable.id, { 356 onDelete: "cascade", 357 onUpdate: "cascade", 358 }), 359 taskId: text("task_id").references(() => taskTable.id, { 360 onDelete: "cascade", 361 onUpdate: "cascade", 362 }), 363 activityId: text("activity_id").references(() => activityTable.id, { 364 onDelete: "cascade", 365 onUpdate: "cascade", 366 }), 367 objectKey: text("object_key").notNull().unique(), 368 filename: text("filename").notNull(), 369 mimeType: text("mime_type").notNull(), 370 size: integer("size").notNull(), 371 kind: text("kind").notNull().default("image"), 372 surface: text("surface").notNull().default("description"), 373 createdBy: text("created_by").references(() => userTable.id, { 374 onDelete: "set null", 375 onUpdate: "cascade", 376 }), 377 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 378 }, 379 (table) => [ 380 index("asset_workspaceId_idx").on(table.workspaceId), 381 index("asset_projectId_idx").on(table.projectId), 382 index("asset_taskId_idx").on(table.taskId), 383 index("asset_activityId_idx").on(table.activityId), 384 ], 385); 386 387export const labelTable = pgTable("label", { 388 id: text("id") 389 .$defaultFn(() => createId()) 390 .primaryKey(), 391 name: text("name").notNull(), 392 color: text("color").notNull(), 393 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 394 taskId: text("task_id").references(() => taskTable.id, { 395 onDelete: "cascade", 396 onUpdate: "cascade", 397 }), 398 workspaceId: text("workspace_id").references(() => workspaceTable.id, { 399 onDelete: "cascade", 400 onUpdate: "cascade", 401 }), 402}); 403 404export const notificationTable = pgTable("notification", { 405 id: text("id") 406 .$defaultFn(() => createId()) 407 .primaryKey(), 408 userId: text("user_id") 409 .notNull() 410 .references(() => userTable.id, { 411 onDelete: "cascade", 412 onUpdate: "cascade", 413 }), 414 title: text("title").notNull(), 415 content: text("content"), 416 type: text("type").notNull().default("info"), 417 isRead: boolean("is_read").default(false), 418 resourceId: text("resource_id"), 419 resourceType: text("resource_type"), 420 createdAt: timestamp("created_at", { mode: "date", withTimezone: true }) 421 .defaultNow() 422 .notNull(), 423}); 424 425export const githubIntegrationTable = pgTable("github_integration", { 426 id: text("id") 427 .$defaultFn(() => createId()) 428 .primaryKey(), 429 projectId: text("project_id") 430 .notNull() 431 .references(() => projectTable.id, { 432 onDelete: "cascade", 433 onUpdate: "cascade", 434 }) 435 .unique(), 436 repositoryOwner: text("repository_owner").notNull(), 437 repositoryName: text("repository_name").notNull(), 438 installationId: integer("installation_id"), 439 isActive: boolean("is_active").default(true), 440 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 441 updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull(), 442}); 443 444export const integrationTable = pgTable( 445 "integration", 446 { 447 id: text("id") 448 .$defaultFn(() => createId()) 449 .primaryKey(), 450 projectId: text("project_id") 451 .notNull() 452 .references(() => projectTable.id, { 453 onDelete: "cascade", 454 onUpdate: "cascade", 455 }), 456 type: text("type").notNull(), 457 config: text("config").notNull(), 458 isActive: boolean("is_active").default(true), 459 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 460 updatedAt: timestamp("updated_at", { mode: "date" }) 461 .defaultNow() 462 .$onUpdate(() => new Date()) 463 .notNull(), 464 }, 465 (table) => [ 466 index("integration_projectId_idx").on(table.projectId), 467 index("integration_type_idx").on(table.type), 468 ], 469); 470 471export const externalLinkTable = pgTable( 472 "external_link", 473 { 474 id: text("id") 475 .$defaultFn(() => createId()) 476 .primaryKey(), 477 taskId: text("task_id") 478 .notNull() 479 .references(() => taskTable.id, { 480 onDelete: "cascade", 481 onUpdate: "cascade", 482 }), 483 integrationId: text("integration_id") 484 .notNull() 485 .references(() => integrationTable.id, { 486 onDelete: "cascade", 487 onUpdate: "cascade", 488 }), 489 resourceType: text("resource_type").notNull(), 490 externalId: text("external_id").notNull(), 491 url: text("url").notNull(), 492 title: text("title"), 493 metadata: text("metadata"), 494 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 495 updatedAt: timestamp("updated_at", { mode: "date" }) 496 .defaultNow() 497 .$onUpdate(() => new Date()) 498 .notNull(), 499 }, 500 (table) => [ 501 index("external_link_taskId_idx").on(table.taskId), 502 index("external_link_integrationId_idx").on(table.integrationId), 503 index("external_link_externalId_idx").on(table.externalId), 504 index("external_link_resourceType_idx").on(table.resourceType), 505 ], 506); 507 508export const apikeyTable = pgTable( 509 "apikey", 510 { 511 id: text("id") 512 .$defaultFn(() => createId()) 513 .primaryKey(), 514 configId: text("config_id").default("default").notNull(), 515 name: text("name"), 516 start: text("start"), 517 referenceId: text("reference_id") 518 .notNull() 519 .references(() => userTable.id, { onDelete: "cascade" }), 520 prefix: text("prefix"), 521 key: text("key").notNull(), 522 userId: text("user_id").references(() => userTable.id, { 523 onDelete: "cascade", 524 }), 525 refillInterval: integer("refill_interval"), 526 refillAmount: integer("refill_amount"), 527 lastRefillAt: timestamp("last_refill_at", { mode: "date" }), 528 enabled: boolean("enabled").default(true), 529 rateLimitEnabled: boolean("rate_limit_enabled").default(true), 530 rateLimitTimeWindow: integer("rate_limit_time_window").default(86400000), 531 rateLimitMax: integer("rate_limit_max").default(10), 532 requestCount: integer("request_count").default(0), 533 remaining: integer("remaining"), 534 lastRequest: timestamp("last_request", { mode: "date" }), 535 expiresAt: timestamp("expires_at", { mode: "date" }), 536 createdAt: timestamp("created_at", { mode: "date" }).notNull(), 537 updatedAt: timestamp("updated_at", { mode: "date" }).notNull(), 538 permissions: text("permissions"), 539 metadata: text("metadata"), 540 }, 541 (table) => [ 542 index("apikey_configId_idx").on(table.configId), 543 index("apikey_key_idx").on(table.key), 544 index("apikey_referenceId_idx").on(table.referenceId), 545 index("apikey_userId_idx").on(table.userId), 546 ], 547); 548 549// Auth-schema compatible aliases in schema.ts 550export const user = userTable; 551export const session = sessionTable; 552export const account = accountTable; 553export const verification = verificationTable; 554export const workspace = workspaceTable; 555export const team = teamTable; 556export const teamMember = teamMemberTable; 557export const workspace_member = workspaceUserTable; 558export const invitation = invitationTable; 559export const apikey = apikeyTable; 560 561// Auth-schema compatible relation exports in schema.ts 562export const userRelations = relations(user, ({ many }) => ({ 563 sessions: many(session), 564 accounts: many(account), 565 teamMembers: many(teamMember), 566 workspace_members: many(workspace_member), 567 invitations: many(invitation), 568})); 569 570export const sessionRelations = relations(session, ({ one }) => ({ 571 user: one(user, { 572 fields: [session.userId], 573 references: [user.id], 574 }), 575})); 576 577export const accountRelations = relations(account, ({ one }) => ({ 578 user: one(user, { 579 fields: [account.userId], 580 references: [user.id], 581 }), 582})); 583 584export const workspaceRelations = relations(workspace, ({ many }) => ({ 585 teams: many(team), 586 workspace_members: many(workspace_member), 587 invitations: many(invitation), 588})); 589 590export const teamRelations = relations(team, ({ one, many }) => ({ 591 workspace: one(workspace, { 592 fields: [team.workspaceId], 593 references: [workspace.id], 594 }), 595 teamMembers: many(teamMember), 596})); 597 598export const teamMemberRelations = relations(teamMember, ({ one }) => ({ 599 team: one(team, { 600 fields: [teamMember.teamId], 601 references: [team.id], 602 }), 603 user: one(user, { 604 fields: [teamMember.userId], 605 references: [user.id], 606 }), 607})); 608 609export const workspace_memberRelations = relations( 610 workspace_member, 611 ({ one }) => ({ 612 workspace: one(workspace, { 613 fields: [workspace_member.workspaceId], 614 references: [workspace.id], 615 }), 616 user: one(user, { 617 fields: [workspace_member.userId], 618 references: [user.id], 619 }), 620 }), 621); 622 623export const invitationRelations = relations(invitation, ({ one }) => ({ 624 workspace: one(workspace, { 625 fields: [invitation.workspaceId], 626 references: [workspace.id], 627 }), 628 user: one(user, { 629 fields: [invitation.inviterId], 630 references: [user.id], 631 }), 632}));