kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: enhance activity schema and GitHub integration

+4606 -121
+4
apps/api/drizzle/0010_bouncy_taskmaster.sql
··· 1 + ALTER TABLE "activity" ALTER COLUMN "user_id" DROP NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "activity" ADD COLUMN "external_user_name" text;--> statement-breakpoint 3 + ALTER TABLE "activity" ADD COLUMN "external_user_avatar" text;--> statement-breakpoint 4 + ALTER TABLE "activity" ADD COLUMN "external_source" text;
+1
apps/api/drizzle/0011_flashy_masked_marvel.sql
··· 1 + ALTER TABLE "activity" ADD COLUMN "external_url" text;
+1824
apps/api/drizzle/meta/0010_snapshot.json
··· 1 + { 2 + "id": "a7746291-0fc6-497f-9d2f-c6f5ba2f49b9", 3 + "prevId": "4cfeef35-e58e-4076-bbe7-9b7c9396c32f", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.account": { 8 + "name": "account", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true 16 + }, 17 + "account_id": { 18 + "name": "account_id", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true 22 + }, 23 + "provider_id": { 24 + "name": "provider_id", 25 + "type": "text", 26 + "primaryKey": false, 27 + "notNull": true 28 + }, 29 + "user_id": { 30 + "name": "user_id", 31 + "type": "text", 32 + "primaryKey": false, 33 + "notNull": true 34 + }, 35 + "access_token": { 36 + "name": "access_token", 37 + "type": "text", 38 + "primaryKey": false, 39 + "notNull": false 40 + }, 41 + "refresh_token": { 42 + "name": "refresh_token", 43 + "type": "text", 44 + "primaryKey": false, 45 + "notNull": false 46 + }, 47 + "id_token": { 48 + "name": "id_token", 49 + "type": "text", 50 + "primaryKey": false, 51 + "notNull": false 52 + }, 53 + "access_token_expires_at": { 54 + "name": "access_token_expires_at", 55 + "type": "timestamp", 56 + "primaryKey": false, 57 + "notNull": false 58 + }, 59 + "refresh_token_expires_at": { 60 + "name": "refresh_token_expires_at", 61 + "type": "timestamp", 62 + "primaryKey": false, 63 + "notNull": false 64 + }, 65 + "scope": { 66 + "name": "scope", 67 + "type": "text", 68 + "primaryKey": false, 69 + "notNull": false 70 + }, 71 + "password": { 72 + "name": "password", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": false 76 + }, 77 + "created_at": { 78 + "name": "created_at", 79 + "type": "timestamp", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "default": "now()" 83 + }, 84 + "updated_at": { 85 + "name": "updated_at", 86 + "type": "timestamp", 87 + "primaryKey": false, 88 + "notNull": true 89 + } 90 + }, 91 + "indexes": { 92 + "account_userId_idx": { 93 + "name": "account_userId_idx", 94 + "columns": [ 95 + { 96 + "expression": "user_id", 97 + "isExpression": false, 98 + "asc": true, 99 + "nulls": "last" 100 + } 101 + ], 102 + "isUnique": false, 103 + "concurrently": false, 104 + "method": "btree", 105 + "with": {} 106 + } 107 + }, 108 + "foreignKeys": { 109 + "account_user_id_user_id_fk": { 110 + "name": "account_user_id_user_id_fk", 111 + "tableFrom": "account", 112 + "tableTo": "user", 113 + "columnsFrom": ["user_id"], 114 + "columnsTo": ["id"], 115 + "onDelete": "cascade", 116 + "onUpdate": "no action" 117 + } 118 + }, 119 + "compositePrimaryKeys": {}, 120 + "uniqueConstraints": {}, 121 + "policies": {}, 122 + "checkConstraints": {}, 123 + "isRLSEnabled": false 124 + }, 125 + "public.activity": { 126 + "name": "activity", 127 + "schema": "", 128 + "columns": { 129 + "id": { 130 + "name": "id", 131 + "type": "text", 132 + "primaryKey": true, 133 + "notNull": true 134 + }, 135 + "task_id": { 136 + "name": "task_id", 137 + "type": "text", 138 + "primaryKey": false, 139 + "notNull": true 140 + }, 141 + "type": { 142 + "name": "type", 143 + "type": "text", 144 + "primaryKey": false, 145 + "notNull": true 146 + }, 147 + "created_at": { 148 + "name": "created_at", 149 + "type": "timestamp", 150 + "primaryKey": false, 151 + "notNull": true, 152 + "default": "now()" 153 + }, 154 + "user_id": { 155 + "name": "user_id", 156 + "type": "text", 157 + "primaryKey": false, 158 + "notNull": false 159 + }, 160 + "content": { 161 + "name": "content", 162 + "type": "text", 163 + "primaryKey": false, 164 + "notNull": false 165 + }, 166 + "external_user_name": { 167 + "name": "external_user_name", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": false 171 + }, 172 + "external_user_avatar": { 173 + "name": "external_user_avatar", 174 + "type": "text", 175 + "primaryKey": false, 176 + "notNull": false 177 + }, 178 + "external_source": { 179 + "name": "external_source", 180 + "type": "text", 181 + "primaryKey": false, 182 + "notNull": false 183 + } 184 + }, 185 + "indexes": {}, 186 + "foreignKeys": { 187 + "activity_task_id_task_id_fk": { 188 + "name": "activity_task_id_task_id_fk", 189 + "tableFrom": "activity", 190 + "tableTo": "task", 191 + "columnsFrom": ["task_id"], 192 + "columnsTo": ["id"], 193 + "onDelete": "cascade", 194 + "onUpdate": "cascade" 195 + }, 196 + "activity_user_id_user_id_fk": { 197 + "name": "activity_user_id_user_id_fk", 198 + "tableFrom": "activity", 199 + "tableTo": "user", 200 + "columnsFrom": ["user_id"], 201 + "columnsTo": ["id"], 202 + "onDelete": "cascade", 203 + "onUpdate": "cascade" 204 + } 205 + }, 206 + "compositePrimaryKeys": {}, 207 + "uniqueConstraints": {}, 208 + "policies": {}, 209 + "checkConstraints": {}, 210 + "isRLSEnabled": false 211 + }, 212 + "public.apikey": { 213 + "name": "apikey", 214 + "schema": "", 215 + "columns": { 216 + "id": { 217 + "name": "id", 218 + "type": "text", 219 + "primaryKey": true, 220 + "notNull": true 221 + }, 222 + "name": { 223 + "name": "name", 224 + "type": "text", 225 + "primaryKey": false, 226 + "notNull": false 227 + }, 228 + "start": { 229 + "name": "start", 230 + "type": "text", 231 + "primaryKey": false, 232 + "notNull": false 233 + }, 234 + "prefix": { 235 + "name": "prefix", 236 + "type": "text", 237 + "primaryKey": false, 238 + "notNull": false 239 + }, 240 + "key": { 241 + "name": "key", 242 + "type": "text", 243 + "primaryKey": false, 244 + "notNull": true 245 + }, 246 + "user_id": { 247 + "name": "user_id", 248 + "type": "text", 249 + "primaryKey": false, 250 + "notNull": true 251 + }, 252 + "refill_interval": { 253 + "name": "refill_interval", 254 + "type": "integer", 255 + "primaryKey": false, 256 + "notNull": false 257 + }, 258 + "refill_amount": { 259 + "name": "refill_amount", 260 + "type": "integer", 261 + "primaryKey": false, 262 + "notNull": false 263 + }, 264 + "last_refill_at": { 265 + "name": "last_refill_at", 266 + "type": "timestamp", 267 + "primaryKey": false, 268 + "notNull": false 269 + }, 270 + "enabled": { 271 + "name": "enabled", 272 + "type": "boolean", 273 + "primaryKey": false, 274 + "notNull": false, 275 + "default": true 276 + }, 277 + "rate_limit_enabled": { 278 + "name": "rate_limit_enabled", 279 + "type": "boolean", 280 + "primaryKey": false, 281 + "notNull": false, 282 + "default": true 283 + }, 284 + "rate_limit_time_window": { 285 + "name": "rate_limit_time_window", 286 + "type": "integer", 287 + "primaryKey": false, 288 + "notNull": false, 289 + "default": 86400000 290 + }, 291 + "rate_limit_max": { 292 + "name": "rate_limit_max", 293 + "type": "integer", 294 + "primaryKey": false, 295 + "notNull": false, 296 + "default": 10 297 + }, 298 + "request_count": { 299 + "name": "request_count", 300 + "type": "integer", 301 + "primaryKey": false, 302 + "notNull": false, 303 + "default": 0 304 + }, 305 + "remaining": { 306 + "name": "remaining", 307 + "type": "integer", 308 + "primaryKey": false, 309 + "notNull": false 310 + }, 311 + "last_request": { 312 + "name": "last_request", 313 + "type": "timestamp", 314 + "primaryKey": false, 315 + "notNull": false 316 + }, 317 + "expires_at": { 318 + "name": "expires_at", 319 + "type": "timestamp", 320 + "primaryKey": false, 321 + "notNull": false 322 + }, 323 + "created_at": { 324 + "name": "created_at", 325 + "type": "timestamp", 326 + "primaryKey": false, 327 + "notNull": true 328 + }, 329 + "updated_at": { 330 + "name": "updated_at", 331 + "type": "timestamp", 332 + "primaryKey": false, 333 + "notNull": true 334 + }, 335 + "permissions": { 336 + "name": "permissions", 337 + "type": "text", 338 + "primaryKey": false, 339 + "notNull": false 340 + }, 341 + "metadata": { 342 + "name": "metadata", 343 + "type": "text", 344 + "primaryKey": false, 345 + "notNull": false 346 + } 347 + }, 348 + "indexes": { 349 + "apikey_key_idx": { 350 + "name": "apikey_key_idx", 351 + "columns": [ 352 + { 353 + "expression": "key", 354 + "isExpression": false, 355 + "asc": true, 356 + "nulls": "last" 357 + } 358 + ], 359 + "isUnique": false, 360 + "concurrently": false, 361 + "method": "btree", 362 + "with": {} 363 + }, 364 + "apikey_userId_idx": { 365 + "name": "apikey_userId_idx", 366 + "columns": [ 367 + { 368 + "expression": "user_id", 369 + "isExpression": false, 370 + "asc": true, 371 + "nulls": "last" 372 + } 373 + ], 374 + "isUnique": false, 375 + "concurrently": false, 376 + "method": "btree", 377 + "with": {} 378 + } 379 + }, 380 + "foreignKeys": { 381 + "apikey_user_id_user_id_fk": { 382 + "name": "apikey_user_id_user_id_fk", 383 + "tableFrom": "apikey", 384 + "tableTo": "user", 385 + "columnsFrom": ["user_id"], 386 + "columnsTo": ["id"], 387 + "onDelete": "cascade", 388 + "onUpdate": "no action" 389 + } 390 + }, 391 + "compositePrimaryKeys": {}, 392 + "uniqueConstraints": {}, 393 + "policies": {}, 394 + "checkConstraints": {}, 395 + "isRLSEnabled": false 396 + }, 397 + "public.external_link": { 398 + "name": "external_link", 399 + "schema": "", 400 + "columns": { 401 + "id": { 402 + "name": "id", 403 + "type": "text", 404 + "primaryKey": true, 405 + "notNull": true 406 + }, 407 + "task_id": { 408 + "name": "task_id", 409 + "type": "text", 410 + "primaryKey": false, 411 + "notNull": true 412 + }, 413 + "integration_id": { 414 + "name": "integration_id", 415 + "type": "text", 416 + "primaryKey": false, 417 + "notNull": true 418 + }, 419 + "resource_type": { 420 + "name": "resource_type", 421 + "type": "text", 422 + "primaryKey": false, 423 + "notNull": true 424 + }, 425 + "external_id": { 426 + "name": "external_id", 427 + "type": "text", 428 + "primaryKey": false, 429 + "notNull": true 430 + }, 431 + "url": { 432 + "name": "url", 433 + "type": "text", 434 + "primaryKey": false, 435 + "notNull": true 436 + }, 437 + "title": { 438 + "name": "title", 439 + "type": "text", 440 + "primaryKey": false, 441 + "notNull": false 442 + }, 443 + "metadata": { 444 + "name": "metadata", 445 + "type": "text", 446 + "primaryKey": false, 447 + "notNull": false 448 + }, 449 + "created_at": { 450 + "name": "created_at", 451 + "type": "timestamp", 452 + "primaryKey": false, 453 + "notNull": true, 454 + "default": "now()" 455 + }, 456 + "updated_at": { 457 + "name": "updated_at", 458 + "type": "timestamp", 459 + "primaryKey": false, 460 + "notNull": true, 461 + "default": "now()" 462 + } 463 + }, 464 + "indexes": { 465 + "external_link_taskId_idx": { 466 + "name": "external_link_taskId_idx", 467 + "columns": [ 468 + { 469 + "expression": "task_id", 470 + "isExpression": false, 471 + "asc": true, 472 + "nulls": "last" 473 + } 474 + ], 475 + "isUnique": false, 476 + "concurrently": false, 477 + "method": "btree", 478 + "with": {} 479 + }, 480 + "external_link_integrationId_idx": { 481 + "name": "external_link_integrationId_idx", 482 + "columns": [ 483 + { 484 + "expression": "integration_id", 485 + "isExpression": false, 486 + "asc": true, 487 + "nulls": "last" 488 + } 489 + ], 490 + "isUnique": false, 491 + "concurrently": false, 492 + "method": "btree", 493 + "with": {} 494 + }, 495 + "external_link_externalId_idx": { 496 + "name": "external_link_externalId_idx", 497 + "columns": [ 498 + { 499 + "expression": "external_id", 500 + "isExpression": false, 501 + "asc": true, 502 + "nulls": "last" 503 + } 504 + ], 505 + "isUnique": false, 506 + "concurrently": false, 507 + "method": "btree", 508 + "with": {} 509 + }, 510 + "external_link_resourceType_idx": { 511 + "name": "external_link_resourceType_idx", 512 + "columns": [ 513 + { 514 + "expression": "resource_type", 515 + "isExpression": false, 516 + "asc": true, 517 + "nulls": "last" 518 + } 519 + ], 520 + "isUnique": false, 521 + "concurrently": false, 522 + "method": "btree", 523 + "with": {} 524 + } 525 + }, 526 + "foreignKeys": { 527 + "external_link_task_id_task_id_fk": { 528 + "name": "external_link_task_id_task_id_fk", 529 + "tableFrom": "external_link", 530 + "tableTo": "task", 531 + "columnsFrom": ["task_id"], 532 + "columnsTo": ["id"], 533 + "onDelete": "cascade", 534 + "onUpdate": "cascade" 535 + }, 536 + "external_link_integration_id_integration_id_fk": { 537 + "name": "external_link_integration_id_integration_id_fk", 538 + "tableFrom": "external_link", 539 + "tableTo": "integration", 540 + "columnsFrom": ["integration_id"], 541 + "columnsTo": ["id"], 542 + "onDelete": "cascade", 543 + "onUpdate": "cascade" 544 + } 545 + }, 546 + "compositePrimaryKeys": {}, 547 + "uniqueConstraints": {}, 548 + "policies": {}, 549 + "checkConstraints": {}, 550 + "isRLSEnabled": false 551 + }, 552 + "public.github_integration": { 553 + "name": "github_integration", 554 + "schema": "", 555 + "columns": { 556 + "id": { 557 + "name": "id", 558 + "type": "text", 559 + "primaryKey": true, 560 + "notNull": true 561 + }, 562 + "project_id": { 563 + "name": "project_id", 564 + "type": "text", 565 + "primaryKey": false, 566 + "notNull": true 567 + }, 568 + "repository_owner": { 569 + "name": "repository_owner", 570 + "type": "text", 571 + "primaryKey": false, 572 + "notNull": true 573 + }, 574 + "repository_name": { 575 + "name": "repository_name", 576 + "type": "text", 577 + "primaryKey": false, 578 + "notNull": true 579 + }, 580 + "installation_id": { 581 + "name": "installation_id", 582 + "type": "integer", 583 + "primaryKey": false, 584 + "notNull": false 585 + }, 586 + "is_active": { 587 + "name": "is_active", 588 + "type": "boolean", 589 + "primaryKey": false, 590 + "notNull": false, 591 + "default": true 592 + }, 593 + "created_at": { 594 + "name": "created_at", 595 + "type": "timestamp", 596 + "primaryKey": false, 597 + "notNull": true, 598 + "default": "now()" 599 + }, 600 + "updated_at": { 601 + "name": "updated_at", 602 + "type": "timestamp", 603 + "primaryKey": false, 604 + "notNull": true, 605 + "default": "now()" 606 + } 607 + }, 608 + "indexes": {}, 609 + "foreignKeys": { 610 + "github_integration_project_id_project_id_fk": { 611 + "name": "github_integration_project_id_project_id_fk", 612 + "tableFrom": "github_integration", 613 + "tableTo": "project", 614 + "columnsFrom": ["project_id"], 615 + "columnsTo": ["id"], 616 + "onDelete": "cascade", 617 + "onUpdate": "cascade" 618 + } 619 + }, 620 + "compositePrimaryKeys": {}, 621 + "uniqueConstraints": { 622 + "github_integration_project_id_unique": { 623 + "name": "github_integration_project_id_unique", 624 + "nullsNotDistinct": false, 625 + "columns": ["project_id"] 626 + } 627 + }, 628 + "policies": {}, 629 + "checkConstraints": {}, 630 + "isRLSEnabled": false 631 + }, 632 + "public.integration": { 633 + "name": "integration", 634 + "schema": "", 635 + "columns": { 636 + "id": { 637 + "name": "id", 638 + "type": "text", 639 + "primaryKey": true, 640 + "notNull": true 641 + }, 642 + "project_id": { 643 + "name": "project_id", 644 + "type": "text", 645 + "primaryKey": false, 646 + "notNull": true 647 + }, 648 + "type": { 649 + "name": "type", 650 + "type": "text", 651 + "primaryKey": false, 652 + "notNull": true 653 + }, 654 + "config": { 655 + "name": "config", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": true 659 + }, 660 + "is_active": { 661 + "name": "is_active", 662 + "type": "boolean", 663 + "primaryKey": false, 664 + "notNull": false, 665 + "default": true 666 + }, 667 + "created_at": { 668 + "name": "created_at", 669 + "type": "timestamp", 670 + "primaryKey": false, 671 + "notNull": true, 672 + "default": "now()" 673 + }, 674 + "updated_at": { 675 + "name": "updated_at", 676 + "type": "timestamp", 677 + "primaryKey": false, 678 + "notNull": true, 679 + "default": "now()" 680 + } 681 + }, 682 + "indexes": { 683 + "integration_projectId_idx": { 684 + "name": "integration_projectId_idx", 685 + "columns": [ 686 + { 687 + "expression": "project_id", 688 + "isExpression": false, 689 + "asc": true, 690 + "nulls": "last" 691 + } 692 + ], 693 + "isUnique": false, 694 + "concurrently": false, 695 + "method": "btree", 696 + "with": {} 697 + }, 698 + "integration_type_idx": { 699 + "name": "integration_type_idx", 700 + "columns": [ 701 + { 702 + "expression": "type", 703 + "isExpression": false, 704 + "asc": true, 705 + "nulls": "last" 706 + } 707 + ], 708 + "isUnique": false, 709 + "concurrently": false, 710 + "method": "btree", 711 + "with": {} 712 + } 713 + }, 714 + "foreignKeys": { 715 + "integration_project_id_project_id_fk": { 716 + "name": "integration_project_id_project_id_fk", 717 + "tableFrom": "integration", 718 + "tableTo": "project", 719 + "columnsFrom": ["project_id"], 720 + "columnsTo": ["id"], 721 + "onDelete": "cascade", 722 + "onUpdate": "cascade" 723 + } 724 + }, 725 + "compositePrimaryKeys": {}, 726 + "uniqueConstraints": {}, 727 + "policies": {}, 728 + "checkConstraints": {}, 729 + "isRLSEnabled": false 730 + }, 731 + "public.invitation": { 732 + "name": "invitation", 733 + "schema": "", 734 + "columns": { 735 + "id": { 736 + "name": "id", 737 + "type": "text", 738 + "primaryKey": true, 739 + "notNull": true 740 + }, 741 + "workspace_id": { 742 + "name": "workspace_id", 743 + "type": "text", 744 + "primaryKey": false, 745 + "notNull": true 746 + }, 747 + "email": { 748 + "name": "email", 749 + "type": "text", 750 + "primaryKey": false, 751 + "notNull": true 752 + }, 753 + "role": { 754 + "name": "role", 755 + "type": "text", 756 + "primaryKey": false, 757 + "notNull": false 758 + }, 759 + "team_id": { 760 + "name": "team_id", 761 + "type": "text", 762 + "primaryKey": false, 763 + "notNull": false 764 + }, 765 + "status": { 766 + "name": "status", 767 + "type": "text", 768 + "primaryKey": false, 769 + "notNull": true, 770 + "default": "'pending'" 771 + }, 772 + "expires_at": { 773 + "name": "expires_at", 774 + "type": "timestamp", 775 + "primaryKey": false, 776 + "notNull": true 777 + }, 778 + "created_at": { 779 + "name": "created_at", 780 + "type": "timestamp", 781 + "primaryKey": false, 782 + "notNull": true, 783 + "default": "now()" 784 + }, 785 + "inviter_id": { 786 + "name": "inviter_id", 787 + "type": "text", 788 + "primaryKey": false, 789 + "notNull": true 790 + } 791 + }, 792 + "indexes": { 793 + "invitation_workspaceId_idx": { 794 + "name": "invitation_workspaceId_idx", 795 + "columns": [ 796 + { 797 + "expression": "workspace_id", 798 + "isExpression": false, 799 + "asc": true, 800 + "nulls": "last" 801 + } 802 + ], 803 + "isUnique": false, 804 + "concurrently": false, 805 + "method": "btree", 806 + "with": {} 807 + }, 808 + "invitation_email_idx": { 809 + "name": "invitation_email_idx", 810 + "columns": [ 811 + { 812 + "expression": "email", 813 + "isExpression": false, 814 + "asc": true, 815 + "nulls": "last" 816 + } 817 + ], 818 + "isUnique": false, 819 + "concurrently": false, 820 + "method": "btree", 821 + "with": {} 822 + } 823 + }, 824 + "foreignKeys": { 825 + "invitation_workspace_id_workspace_id_fk": { 826 + "name": "invitation_workspace_id_workspace_id_fk", 827 + "tableFrom": "invitation", 828 + "tableTo": "workspace", 829 + "columnsFrom": ["workspace_id"], 830 + "columnsTo": ["id"], 831 + "onDelete": "cascade", 832 + "onUpdate": "no action" 833 + }, 834 + "invitation_inviter_id_user_id_fk": { 835 + "name": "invitation_inviter_id_user_id_fk", 836 + "tableFrom": "invitation", 837 + "tableTo": "user", 838 + "columnsFrom": ["inviter_id"], 839 + "columnsTo": ["id"], 840 + "onDelete": "cascade", 841 + "onUpdate": "no action" 842 + } 843 + }, 844 + "compositePrimaryKeys": {}, 845 + "uniqueConstraints": {}, 846 + "policies": {}, 847 + "checkConstraints": {}, 848 + "isRLSEnabled": false 849 + }, 850 + "public.label": { 851 + "name": "label", 852 + "schema": "", 853 + "columns": { 854 + "id": { 855 + "name": "id", 856 + "type": "text", 857 + "primaryKey": true, 858 + "notNull": true 859 + }, 860 + "name": { 861 + "name": "name", 862 + "type": "text", 863 + "primaryKey": false, 864 + "notNull": true 865 + }, 866 + "color": { 867 + "name": "color", 868 + "type": "text", 869 + "primaryKey": false, 870 + "notNull": true 871 + }, 872 + "created_at": { 873 + "name": "created_at", 874 + "type": "timestamp", 875 + "primaryKey": false, 876 + "notNull": true, 877 + "default": "now()" 878 + }, 879 + "task_id": { 880 + "name": "task_id", 881 + "type": "text", 882 + "primaryKey": false, 883 + "notNull": false 884 + }, 885 + "workspace_id": { 886 + "name": "workspace_id", 887 + "type": "text", 888 + "primaryKey": false, 889 + "notNull": false 890 + } 891 + }, 892 + "indexes": {}, 893 + "foreignKeys": { 894 + "label_task_id_task_id_fk": { 895 + "name": "label_task_id_task_id_fk", 896 + "tableFrom": "label", 897 + "tableTo": "task", 898 + "columnsFrom": ["task_id"], 899 + "columnsTo": ["id"], 900 + "onDelete": "cascade", 901 + "onUpdate": "cascade" 902 + }, 903 + "label_workspace_id_workspace_id_fk": { 904 + "name": "label_workspace_id_workspace_id_fk", 905 + "tableFrom": "label", 906 + "tableTo": "workspace", 907 + "columnsFrom": ["workspace_id"], 908 + "columnsTo": ["id"], 909 + "onDelete": "cascade", 910 + "onUpdate": "cascade" 911 + } 912 + }, 913 + "compositePrimaryKeys": {}, 914 + "uniqueConstraints": {}, 915 + "policies": {}, 916 + "checkConstraints": {}, 917 + "isRLSEnabled": false 918 + }, 919 + "public.notification": { 920 + "name": "notification", 921 + "schema": "", 922 + "columns": { 923 + "id": { 924 + "name": "id", 925 + "type": "text", 926 + "primaryKey": true, 927 + "notNull": true 928 + }, 929 + "user_id": { 930 + "name": "user_id", 931 + "type": "text", 932 + "primaryKey": false, 933 + "notNull": true 934 + }, 935 + "title": { 936 + "name": "title", 937 + "type": "text", 938 + "primaryKey": false, 939 + "notNull": true 940 + }, 941 + "content": { 942 + "name": "content", 943 + "type": "text", 944 + "primaryKey": false, 945 + "notNull": false 946 + }, 947 + "type": { 948 + "name": "type", 949 + "type": "text", 950 + "primaryKey": false, 951 + "notNull": true, 952 + "default": "'info'" 953 + }, 954 + "is_read": { 955 + "name": "is_read", 956 + "type": "boolean", 957 + "primaryKey": false, 958 + "notNull": false, 959 + "default": false 960 + }, 961 + "resource_id": { 962 + "name": "resource_id", 963 + "type": "text", 964 + "primaryKey": false, 965 + "notNull": false 966 + }, 967 + "resource_type": { 968 + "name": "resource_type", 969 + "type": "text", 970 + "primaryKey": false, 971 + "notNull": false 972 + }, 973 + "created_at": { 974 + "name": "created_at", 975 + "type": "timestamp with time zone", 976 + "primaryKey": false, 977 + "notNull": true, 978 + "default": "now()" 979 + } 980 + }, 981 + "indexes": {}, 982 + "foreignKeys": { 983 + "notification_user_id_user_id_fk": { 984 + "name": "notification_user_id_user_id_fk", 985 + "tableFrom": "notification", 986 + "tableTo": "user", 987 + "columnsFrom": ["user_id"], 988 + "columnsTo": ["id"], 989 + "onDelete": "cascade", 990 + "onUpdate": "cascade" 991 + } 992 + }, 993 + "compositePrimaryKeys": {}, 994 + "uniqueConstraints": {}, 995 + "policies": {}, 996 + "checkConstraints": {}, 997 + "isRLSEnabled": false 998 + }, 999 + "public.project": { 1000 + "name": "project", 1001 + "schema": "", 1002 + "columns": { 1003 + "id": { 1004 + "name": "id", 1005 + "type": "text", 1006 + "primaryKey": true, 1007 + "notNull": true 1008 + }, 1009 + "workspace_id": { 1010 + "name": "workspace_id", 1011 + "type": "text", 1012 + "primaryKey": false, 1013 + "notNull": true 1014 + }, 1015 + "slug": { 1016 + "name": "slug", 1017 + "type": "text", 1018 + "primaryKey": false, 1019 + "notNull": true 1020 + }, 1021 + "icon": { 1022 + "name": "icon", 1023 + "type": "text", 1024 + "primaryKey": false, 1025 + "notNull": false, 1026 + "default": "'Layout'" 1027 + }, 1028 + "name": { 1029 + "name": "name", 1030 + "type": "text", 1031 + "primaryKey": false, 1032 + "notNull": true 1033 + }, 1034 + "description": { 1035 + "name": "description", 1036 + "type": "text", 1037 + "primaryKey": false, 1038 + "notNull": false 1039 + }, 1040 + "created_at": { 1041 + "name": "created_at", 1042 + "type": "timestamp", 1043 + "primaryKey": false, 1044 + "notNull": true, 1045 + "default": "now()" 1046 + }, 1047 + "is_public": { 1048 + "name": "is_public", 1049 + "type": "boolean", 1050 + "primaryKey": false, 1051 + "notNull": false, 1052 + "default": false 1053 + } 1054 + }, 1055 + "indexes": {}, 1056 + "foreignKeys": { 1057 + "project_workspace_id_workspace_id_fk": { 1058 + "name": "project_workspace_id_workspace_id_fk", 1059 + "tableFrom": "project", 1060 + "tableTo": "workspace", 1061 + "columnsFrom": ["workspace_id"], 1062 + "columnsTo": ["id"], 1063 + "onDelete": "cascade", 1064 + "onUpdate": "cascade" 1065 + } 1066 + }, 1067 + "compositePrimaryKeys": {}, 1068 + "uniqueConstraints": {}, 1069 + "policies": {}, 1070 + "checkConstraints": {}, 1071 + "isRLSEnabled": false 1072 + }, 1073 + "public.session": { 1074 + "name": "session", 1075 + "schema": "", 1076 + "columns": { 1077 + "id": { 1078 + "name": "id", 1079 + "type": "text", 1080 + "primaryKey": true, 1081 + "notNull": true 1082 + }, 1083 + "expires_at": { 1084 + "name": "expires_at", 1085 + "type": "timestamp", 1086 + "primaryKey": false, 1087 + "notNull": true 1088 + }, 1089 + "token": { 1090 + "name": "token", 1091 + "type": "text", 1092 + "primaryKey": false, 1093 + "notNull": true 1094 + }, 1095 + "created_at": { 1096 + "name": "created_at", 1097 + "type": "timestamp", 1098 + "primaryKey": false, 1099 + "notNull": true, 1100 + "default": "now()" 1101 + }, 1102 + "updated_at": { 1103 + "name": "updated_at", 1104 + "type": "timestamp", 1105 + "primaryKey": false, 1106 + "notNull": true 1107 + }, 1108 + "ip_address": { 1109 + "name": "ip_address", 1110 + "type": "text", 1111 + "primaryKey": false, 1112 + "notNull": false 1113 + }, 1114 + "user_agent": { 1115 + "name": "user_agent", 1116 + "type": "text", 1117 + "primaryKey": false, 1118 + "notNull": false 1119 + }, 1120 + "user_id": { 1121 + "name": "user_id", 1122 + "type": "text", 1123 + "primaryKey": false, 1124 + "notNull": true 1125 + }, 1126 + "active_organization_id": { 1127 + "name": "active_organization_id", 1128 + "type": "text", 1129 + "primaryKey": false, 1130 + "notNull": false 1131 + }, 1132 + "active_team_id": { 1133 + "name": "active_team_id", 1134 + "type": "text", 1135 + "primaryKey": false, 1136 + "notNull": false 1137 + } 1138 + }, 1139 + "indexes": { 1140 + "session_userId_idx": { 1141 + "name": "session_userId_idx", 1142 + "columns": [ 1143 + { 1144 + "expression": "user_id", 1145 + "isExpression": false, 1146 + "asc": true, 1147 + "nulls": "last" 1148 + } 1149 + ], 1150 + "isUnique": false, 1151 + "concurrently": false, 1152 + "method": "btree", 1153 + "with": {} 1154 + } 1155 + }, 1156 + "foreignKeys": { 1157 + "session_user_id_user_id_fk": { 1158 + "name": "session_user_id_user_id_fk", 1159 + "tableFrom": "session", 1160 + "tableTo": "user", 1161 + "columnsFrom": ["user_id"], 1162 + "columnsTo": ["id"], 1163 + "onDelete": "cascade", 1164 + "onUpdate": "no action" 1165 + } 1166 + }, 1167 + "compositePrimaryKeys": {}, 1168 + "uniqueConstraints": { 1169 + "session_token_unique": { 1170 + "name": "session_token_unique", 1171 + "nullsNotDistinct": false, 1172 + "columns": ["token"] 1173 + } 1174 + }, 1175 + "policies": {}, 1176 + "checkConstraints": {}, 1177 + "isRLSEnabled": false 1178 + }, 1179 + "public.task": { 1180 + "name": "task", 1181 + "schema": "", 1182 + "columns": { 1183 + "id": { 1184 + "name": "id", 1185 + "type": "text", 1186 + "primaryKey": true, 1187 + "notNull": true 1188 + }, 1189 + "project_id": { 1190 + "name": "project_id", 1191 + "type": "text", 1192 + "primaryKey": false, 1193 + "notNull": true 1194 + }, 1195 + "position": { 1196 + "name": "position", 1197 + "type": "integer", 1198 + "primaryKey": false, 1199 + "notNull": false, 1200 + "default": 0 1201 + }, 1202 + "number": { 1203 + "name": "number", 1204 + "type": "integer", 1205 + "primaryKey": false, 1206 + "notNull": false, 1207 + "default": 1 1208 + }, 1209 + "assignee_id": { 1210 + "name": "assignee_id", 1211 + "type": "text", 1212 + "primaryKey": false, 1213 + "notNull": false 1214 + }, 1215 + "title": { 1216 + "name": "title", 1217 + "type": "text", 1218 + "primaryKey": false, 1219 + "notNull": true 1220 + }, 1221 + "description": { 1222 + "name": "description", 1223 + "type": "text", 1224 + "primaryKey": false, 1225 + "notNull": false 1226 + }, 1227 + "status": { 1228 + "name": "status", 1229 + "type": "text", 1230 + "primaryKey": false, 1231 + "notNull": true, 1232 + "default": "'to-do'" 1233 + }, 1234 + "priority": { 1235 + "name": "priority", 1236 + "type": "text", 1237 + "primaryKey": false, 1238 + "notNull": false, 1239 + "default": "'low'" 1240 + }, 1241 + "due_date": { 1242 + "name": "due_date", 1243 + "type": "timestamp", 1244 + "primaryKey": false, 1245 + "notNull": false 1246 + }, 1247 + "created_at": { 1248 + "name": "created_at", 1249 + "type": "timestamp", 1250 + "primaryKey": false, 1251 + "notNull": true, 1252 + "default": "now()" 1253 + } 1254 + }, 1255 + "indexes": {}, 1256 + "foreignKeys": { 1257 + "task_project_id_project_id_fk": { 1258 + "name": "task_project_id_project_id_fk", 1259 + "tableFrom": "task", 1260 + "tableTo": "project", 1261 + "columnsFrom": ["project_id"], 1262 + "columnsTo": ["id"], 1263 + "onDelete": "cascade", 1264 + "onUpdate": "cascade" 1265 + }, 1266 + "task_assignee_id_user_id_fk": { 1267 + "name": "task_assignee_id_user_id_fk", 1268 + "tableFrom": "task", 1269 + "tableTo": "user", 1270 + "columnsFrom": ["assignee_id"], 1271 + "columnsTo": ["id"], 1272 + "onDelete": "cascade", 1273 + "onUpdate": "cascade" 1274 + } 1275 + }, 1276 + "compositePrimaryKeys": {}, 1277 + "uniqueConstraints": {}, 1278 + "policies": {}, 1279 + "checkConstraints": {}, 1280 + "isRLSEnabled": false 1281 + }, 1282 + "public.team_member": { 1283 + "name": "team_member", 1284 + "schema": "", 1285 + "columns": { 1286 + "id": { 1287 + "name": "id", 1288 + "type": "text", 1289 + "primaryKey": true, 1290 + "notNull": true 1291 + }, 1292 + "team_id": { 1293 + "name": "team_id", 1294 + "type": "text", 1295 + "primaryKey": false, 1296 + "notNull": true 1297 + }, 1298 + "user_id": { 1299 + "name": "user_id", 1300 + "type": "text", 1301 + "primaryKey": false, 1302 + "notNull": true 1303 + }, 1304 + "created_at": { 1305 + "name": "created_at", 1306 + "type": "timestamp", 1307 + "primaryKey": false, 1308 + "notNull": false 1309 + } 1310 + }, 1311 + "indexes": { 1312 + "teamMember_teamId_idx": { 1313 + "name": "teamMember_teamId_idx", 1314 + "columns": [ 1315 + { 1316 + "expression": "team_id", 1317 + "isExpression": false, 1318 + "asc": true, 1319 + "nulls": "last" 1320 + } 1321 + ], 1322 + "isUnique": false, 1323 + "concurrently": false, 1324 + "method": "btree", 1325 + "with": {} 1326 + }, 1327 + "teamMember_userId_idx": { 1328 + "name": "teamMember_userId_idx", 1329 + "columns": [ 1330 + { 1331 + "expression": "user_id", 1332 + "isExpression": false, 1333 + "asc": true, 1334 + "nulls": "last" 1335 + } 1336 + ], 1337 + "isUnique": false, 1338 + "concurrently": false, 1339 + "method": "btree", 1340 + "with": {} 1341 + } 1342 + }, 1343 + "foreignKeys": { 1344 + "team_member_team_id_team_id_fk": { 1345 + "name": "team_member_team_id_team_id_fk", 1346 + "tableFrom": "team_member", 1347 + "tableTo": "team", 1348 + "columnsFrom": ["team_id"], 1349 + "columnsTo": ["id"], 1350 + "onDelete": "cascade", 1351 + "onUpdate": "no action" 1352 + }, 1353 + "team_member_user_id_user_id_fk": { 1354 + "name": "team_member_user_id_user_id_fk", 1355 + "tableFrom": "team_member", 1356 + "tableTo": "user", 1357 + "columnsFrom": ["user_id"], 1358 + "columnsTo": ["id"], 1359 + "onDelete": "cascade", 1360 + "onUpdate": "no action" 1361 + } 1362 + }, 1363 + "compositePrimaryKeys": {}, 1364 + "uniqueConstraints": {}, 1365 + "policies": {}, 1366 + "checkConstraints": {}, 1367 + "isRLSEnabled": false 1368 + }, 1369 + "public.team": { 1370 + "name": "team", 1371 + "schema": "", 1372 + "columns": { 1373 + "id": { 1374 + "name": "id", 1375 + "type": "text", 1376 + "primaryKey": true, 1377 + "notNull": true 1378 + }, 1379 + "name": { 1380 + "name": "name", 1381 + "type": "text", 1382 + "primaryKey": false, 1383 + "notNull": true 1384 + }, 1385 + "workspace_id": { 1386 + "name": "workspace_id", 1387 + "type": "text", 1388 + "primaryKey": false, 1389 + "notNull": true 1390 + }, 1391 + "created_at": { 1392 + "name": "created_at", 1393 + "type": "timestamp", 1394 + "primaryKey": false, 1395 + "notNull": true 1396 + }, 1397 + "updated_at": { 1398 + "name": "updated_at", 1399 + "type": "timestamp", 1400 + "primaryKey": false, 1401 + "notNull": false 1402 + } 1403 + }, 1404 + "indexes": { 1405 + "team_workspaceId_idx": { 1406 + "name": "team_workspaceId_idx", 1407 + "columns": [ 1408 + { 1409 + "expression": "workspace_id", 1410 + "isExpression": false, 1411 + "asc": true, 1412 + "nulls": "last" 1413 + } 1414 + ], 1415 + "isUnique": false, 1416 + "concurrently": false, 1417 + "method": "btree", 1418 + "with": {} 1419 + } 1420 + }, 1421 + "foreignKeys": { 1422 + "team_workspace_id_workspace_id_fk": { 1423 + "name": "team_workspace_id_workspace_id_fk", 1424 + "tableFrom": "team", 1425 + "tableTo": "workspace", 1426 + "columnsFrom": ["workspace_id"], 1427 + "columnsTo": ["id"], 1428 + "onDelete": "cascade", 1429 + "onUpdate": "no action" 1430 + } 1431 + }, 1432 + "compositePrimaryKeys": {}, 1433 + "uniqueConstraints": {}, 1434 + "policies": {}, 1435 + "checkConstraints": {}, 1436 + "isRLSEnabled": false 1437 + }, 1438 + "public.time_entry": { 1439 + "name": "time_entry", 1440 + "schema": "", 1441 + "columns": { 1442 + "id": { 1443 + "name": "id", 1444 + "type": "text", 1445 + "primaryKey": true, 1446 + "notNull": true 1447 + }, 1448 + "task_id": { 1449 + "name": "task_id", 1450 + "type": "text", 1451 + "primaryKey": false, 1452 + "notNull": true 1453 + }, 1454 + "user_id": { 1455 + "name": "user_id", 1456 + "type": "text", 1457 + "primaryKey": false, 1458 + "notNull": false 1459 + }, 1460 + "description": { 1461 + "name": "description", 1462 + "type": "text", 1463 + "primaryKey": false, 1464 + "notNull": false 1465 + }, 1466 + "start_time": { 1467 + "name": "start_time", 1468 + "type": "timestamp", 1469 + "primaryKey": false, 1470 + "notNull": true 1471 + }, 1472 + "end_time": { 1473 + "name": "end_time", 1474 + "type": "timestamp", 1475 + "primaryKey": false, 1476 + "notNull": false 1477 + }, 1478 + "duration": { 1479 + "name": "duration", 1480 + "type": "integer", 1481 + "primaryKey": false, 1482 + "notNull": false, 1483 + "default": 0 1484 + }, 1485 + "created_at": { 1486 + "name": "created_at", 1487 + "type": "timestamp", 1488 + "primaryKey": false, 1489 + "notNull": true, 1490 + "default": "now()" 1491 + } 1492 + }, 1493 + "indexes": {}, 1494 + "foreignKeys": { 1495 + "time_entry_task_id_task_id_fk": { 1496 + "name": "time_entry_task_id_task_id_fk", 1497 + "tableFrom": "time_entry", 1498 + "tableTo": "task", 1499 + "columnsFrom": ["task_id"], 1500 + "columnsTo": ["id"], 1501 + "onDelete": "cascade", 1502 + "onUpdate": "cascade" 1503 + }, 1504 + "time_entry_user_id_user_id_fk": { 1505 + "name": "time_entry_user_id_user_id_fk", 1506 + "tableFrom": "time_entry", 1507 + "tableTo": "user", 1508 + "columnsFrom": ["user_id"], 1509 + "columnsTo": ["id"], 1510 + "onDelete": "cascade", 1511 + "onUpdate": "cascade" 1512 + } 1513 + }, 1514 + "compositePrimaryKeys": {}, 1515 + "uniqueConstraints": {}, 1516 + "policies": {}, 1517 + "checkConstraints": {}, 1518 + "isRLSEnabled": false 1519 + }, 1520 + "public.user": { 1521 + "name": "user", 1522 + "schema": "", 1523 + "columns": { 1524 + "id": { 1525 + "name": "id", 1526 + "type": "text", 1527 + "primaryKey": true, 1528 + "notNull": true 1529 + }, 1530 + "name": { 1531 + "name": "name", 1532 + "type": "text", 1533 + "primaryKey": false, 1534 + "notNull": true 1535 + }, 1536 + "email": { 1537 + "name": "email", 1538 + "type": "text", 1539 + "primaryKey": false, 1540 + "notNull": true 1541 + }, 1542 + "email_verified": { 1543 + "name": "email_verified", 1544 + "type": "boolean", 1545 + "primaryKey": false, 1546 + "notNull": true 1547 + }, 1548 + "image": { 1549 + "name": "image", 1550 + "type": "text", 1551 + "primaryKey": false, 1552 + "notNull": false 1553 + }, 1554 + "created_at": { 1555 + "name": "created_at", 1556 + "type": "timestamp", 1557 + "primaryKey": false, 1558 + "notNull": true, 1559 + "default": "now()" 1560 + }, 1561 + "updated_at": { 1562 + "name": "updated_at", 1563 + "type": "timestamp", 1564 + "primaryKey": false, 1565 + "notNull": true, 1566 + "default": "now()" 1567 + }, 1568 + "is_anonymous": { 1569 + "name": "is_anonymous", 1570 + "type": "boolean", 1571 + "primaryKey": false, 1572 + "notNull": false, 1573 + "default": false 1574 + } 1575 + }, 1576 + "indexes": {}, 1577 + "foreignKeys": {}, 1578 + "compositePrimaryKeys": {}, 1579 + "uniqueConstraints": { 1580 + "user_email_unique": { 1581 + "name": "user_email_unique", 1582 + "nullsNotDistinct": false, 1583 + "columns": ["email"] 1584 + } 1585 + }, 1586 + "policies": {}, 1587 + "checkConstraints": {}, 1588 + "isRLSEnabled": false 1589 + }, 1590 + "public.verification": { 1591 + "name": "verification", 1592 + "schema": "", 1593 + "columns": { 1594 + "id": { 1595 + "name": "id", 1596 + "type": "text", 1597 + "primaryKey": true, 1598 + "notNull": true 1599 + }, 1600 + "identifier": { 1601 + "name": "identifier", 1602 + "type": "text", 1603 + "primaryKey": false, 1604 + "notNull": true 1605 + }, 1606 + "value": { 1607 + "name": "value", 1608 + "type": "text", 1609 + "primaryKey": false, 1610 + "notNull": true 1611 + }, 1612 + "expires_at": { 1613 + "name": "expires_at", 1614 + "type": "timestamp", 1615 + "primaryKey": false, 1616 + "notNull": true 1617 + }, 1618 + "created_at": { 1619 + "name": "created_at", 1620 + "type": "timestamp", 1621 + "primaryKey": false, 1622 + "notNull": true, 1623 + "default": "now()" 1624 + }, 1625 + "updated_at": { 1626 + "name": "updated_at", 1627 + "type": "timestamp", 1628 + "primaryKey": false, 1629 + "notNull": true, 1630 + "default": "now()" 1631 + } 1632 + }, 1633 + "indexes": { 1634 + "verification_identifier_idx": { 1635 + "name": "verification_identifier_idx", 1636 + "columns": [ 1637 + { 1638 + "expression": "identifier", 1639 + "isExpression": false, 1640 + "asc": true, 1641 + "nulls": "last" 1642 + } 1643 + ], 1644 + "isUnique": false, 1645 + "concurrently": false, 1646 + "method": "btree", 1647 + "with": {} 1648 + } 1649 + }, 1650 + "foreignKeys": {}, 1651 + "compositePrimaryKeys": {}, 1652 + "uniqueConstraints": {}, 1653 + "policies": {}, 1654 + "checkConstraints": {}, 1655 + "isRLSEnabled": false 1656 + }, 1657 + "public.workspace": { 1658 + "name": "workspace", 1659 + "schema": "", 1660 + "columns": { 1661 + "id": { 1662 + "name": "id", 1663 + "type": "text", 1664 + "primaryKey": true, 1665 + "notNull": true 1666 + }, 1667 + "name": { 1668 + "name": "name", 1669 + "type": "text", 1670 + "primaryKey": false, 1671 + "notNull": true 1672 + }, 1673 + "slug": { 1674 + "name": "slug", 1675 + "type": "text", 1676 + "primaryKey": false, 1677 + "notNull": true 1678 + }, 1679 + "logo": { 1680 + "name": "logo", 1681 + "type": "text", 1682 + "primaryKey": false, 1683 + "notNull": false 1684 + }, 1685 + "metadata": { 1686 + "name": "metadata", 1687 + "type": "text", 1688 + "primaryKey": false, 1689 + "notNull": false 1690 + }, 1691 + "description": { 1692 + "name": "description", 1693 + "type": "text", 1694 + "primaryKey": false, 1695 + "notNull": false 1696 + }, 1697 + "created_at": { 1698 + "name": "created_at", 1699 + "type": "timestamp", 1700 + "primaryKey": false, 1701 + "notNull": true 1702 + } 1703 + }, 1704 + "indexes": {}, 1705 + "foreignKeys": {}, 1706 + "compositePrimaryKeys": {}, 1707 + "uniqueConstraints": { 1708 + "workspace_slug_unique": { 1709 + "name": "workspace_slug_unique", 1710 + "nullsNotDistinct": false, 1711 + "columns": ["slug"] 1712 + } 1713 + }, 1714 + "policies": {}, 1715 + "checkConstraints": {}, 1716 + "isRLSEnabled": false 1717 + }, 1718 + "public.workspace_member": { 1719 + "name": "workspace_member", 1720 + "schema": "", 1721 + "columns": { 1722 + "id": { 1723 + "name": "id", 1724 + "type": "text", 1725 + "primaryKey": true, 1726 + "notNull": true 1727 + }, 1728 + "workspace_id": { 1729 + "name": "workspace_id", 1730 + "type": "text", 1731 + "primaryKey": false, 1732 + "notNull": true 1733 + }, 1734 + "user_id": { 1735 + "name": "user_id", 1736 + "type": "text", 1737 + "primaryKey": false, 1738 + "notNull": true 1739 + }, 1740 + "role": { 1741 + "name": "role", 1742 + "type": "text", 1743 + "primaryKey": false, 1744 + "notNull": true, 1745 + "default": "'member'" 1746 + }, 1747 + "joined_at": { 1748 + "name": "joined_at", 1749 + "type": "timestamp", 1750 + "primaryKey": false, 1751 + "notNull": true 1752 + } 1753 + }, 1754 + "indexes": { 1755 + "workspace_member_workspaceId_idx": { 1756 + "name": "workspace_member_workspaceId_idx", 1757 + "columns": [ 1758 + { 1759 + "expression": "workspace_id", 1760 + "isExpression": false, 1761 + "asc": true, 1762 + "nulls": "last" 1763 + } 1764 + ], 1765 + "isUnique": false, 1766 + "concurrently": false, 1767 + "method": "btree", 1768 + "with": {} 1769 + }, 1770 + "workspace_member_userId_idx": { 1771 + "name": "workspace_member_userId_idx", 1772 + "columns": [ 1773 + { 1774 + "expression": "user_id", 1775 + "isExpression": false, 1776 + "asc": true, 1777 + "nulls": "last" 1778 + } 1779 + ], 1780 + "isUnique": false, 1781 + "concurrently": false, 1782 + "method": "btree", 1783 + "with": {} 1784 + } 1785 + }, 1786 + "foreignKeys": { 1787 + "workspace_member_workspace_id_workspace_id_fk": { 1788 + "name": "workspace_member_workspace_id_workspace_id_fk", 1789 + "tableFrom": "workspace_member", 1790 + "tableTo": "workspace", 1791 + "columnsFrom": ["workspace_id"], 1792 + "columnsTo": ["id"], 1793 + "onDelete": "cascade", 1794 + "onUpdate": "no action" 1795 + }, 1796 + "workspace_member_user_id_user_id_fk": { 1797 + "name": "workspace_member_user_id_user_id_fk", 1798 + "tableFrom": "workspace_member", 1799 + "tableTo": "user", 1800 + "columnsFrom": ["user_id"], 1801 + "columnsTo": ["id"], 1802 + "onDelete": "cascade", 1803 + "onUpdate": "no action" 1804 + } 1805 + }, 1806 + "compositePrimaryKeys": {}, 1807 + "uniqueConstraints": {}, 1808 + "policies": {}, 1809 + "checkConstraints": {}, 1810 + "isRLSEnabled": false 1811 + } 1812 + }, 1813 + "enums": {}, 1814 + "schemas": {}, 1815 + "sequences": {}, 1816 + "roles": {}, 1817 + "policies": {}, 1818 + "views": {}, 1819 + "_meta": { 1820 + "columns": {}, 1821 + "schemas": {}, 1822 + "tables": {} 1823 + } 1824 + }
+1830
apps/api/drizzle/meta/0011_snapshot.json
··· 1 + { 2 + "id": "95fa9105-bddc-4049-b23d-2c413b803c23", 3 + "prevId": "a7746291-0fc6-497f-9d2f-c6f5ba2f49b9", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.account": { 8 + "name": "account", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true 16 + }, 17 + "account_id": { 18 + "name": "account_id", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true 22 + }, 23 + "provider_id": { 24 + "name": "provider_id", 25 + "type": "text", 26 + "primaryKey": false, 27 + "notNull": true 28 + }, 29 + "user_id": { 30 + "name": "user_id", 31 + "type": "text", 32 + "primaryKey": false, 33 + "notNull": true 34 + }, 35 + "access_token": { 36 + "name": "access_token", 37 + "type": "text", 38 + "primaryKey": false, 39 + "notNull": false 40 + }, 41 + "refresh_token": { 42 + "name": "refresh_token", 43 + "type": "text", 44 + "primaryKey": false, 45 + "notNull": false 46 + }, 47 + "id_token": { 48 + "name": "id_token", 49 + "type": "text", 50 + "primaryKey": false, 51 + "notNull": false 52 + }, 53 + "access_token_expires_at": { 54 + "name": "access_token_expires_at", 55 + "type": "timestamp", 56 + "primaryKey": false, 57 + "notNull": false 58 + }, 59 + "refresh_token_expires_at": { 60 + "name": "refresh_token_expires_at", 61 + "type": "timestamp", 62 + "primaryKey": false, 63 + "notNull": false 64 + }, 65 + "scope": { 66 + "name": "scope", 67 + "type": "text", 68 + "primaryKey": false, 69 + "notNull": false 70 + }, 71 + "password": { 72 + "name": "password", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": false 76 + }, 77 + "created_at": { 78 + "name": "created_at", 79 + "type": "timestamp", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "default": "now()" 83 + }, 84 + "updated_at": { 85 + "name": "updated_at", 86 + "type": "timestamp", 87 + "primaryKey": false, 88 + "notNull": true 89 + } 90 + }, 91 + "indexes": { 92 + "account_userId_idx": { 93 + "name": "account_userId_idx", 94 + "columns": [ 95 + { 96 + "expression": "user_id", 97 + "isExpression": false, 98 + "asc": true, 99 + "nulls": "last" 100 + } 101 + ], 102 + "isUnique": false, 103 + "concurrently": false, 104 + "method": "btree", 105 + "with": {} 106 + } 107 + }, 108 + "foreignKeys": { 109 + "account_user_id_user_id_fk": { 110 + "name": "account_user_id_user_id_fk", 111 + "tableFrom": "account", 112 + "tableTo": "user", 113 + "columnsFrom": ["user_id"], 114 + "columnsTo": ["id"], 115 + "onDelete": "cascade", 116 + "onUpdate": "no action" 117 + } 118 + }, 119 + "compositePrimaryKeys": {}, 120 + "uniqueConstraints": {}, 121 + "policies": {}, 122 + "checkConstraints": {}, 123 + "isRLSEnabled": false 124 + }, 125 + "public.activity": { 126 + "name": "activity", 127 + "schema": "", 128 + "columns": { 129 + "id": { 130 + "name": "id", 131 + "type": "text", 132 + "primaryKey": true, 133 + "notNull": true 134 + }, 135 + "task_id": { 136 + "name": "task_id", 137 + "type": "text", 138 + "primaryKey": false, 139 + "notNull": true 140 + }, 141 + "type": { 142 + "name": "type", 143 + "type": "text", 144 + "primaryKey": false, 145 + "notNull": true 146 + }, 147 + "created_at": { 148 + "name": "created_at", 149 + "type": "timestamp", 150 + "primaryKey": false, 151 + "notNull": true, 152 + "default": "now()" 153 + }, 154 + "user_id": { 155 + "name": "user_id", 156 + "type": "text", 157 + "primaryKey": false, 158 + "notNull": false 159 + }, 160 + "content": { 161 + "name": "content", 162 + "type": "text", 163 + "primaryKey": false, 164 + "notNull": false 165 + }, 166 + "external_user_name": { 167 + "name": "external_user_name", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": false 171 + }, 172 + "external_user_avatar": { 173 + "name": "external_user_avatar", 174 + "type": "text", 175 + "primaryKey": false, 176 + "notNull": false 177 + }, 178 + "external_source": { 179 + "name": "external_source", 180 + "type": "text", 181 + "primaryKey": false, 182 + "notNull": false 183 + }, 184 + "external_url": { 185 + "name": "external_url", 186 + "type": "text", 187 + "primaryKey": false, 188 + "notNull": false 189 + } 190 + }, 191 + "indexes": {}, 192 + "foreignKeys": { 193 + "activity_task_id_task_id_fk": { 194 + "name": "activity_task_id_task_id_fk", 195 + "tableFrom": "activity", 196 + "tableTo": "task", 197 + "columnsFrom": ["task_id"], 198 + "columnsTo": ["id"], 199 + "onDelete": "cascade", 200 + "onUpdate": "cascade" 201 + }, 202 + "activity_user_id_user_id_fk": { 203 + "name": "activity_user_id_user_id_fk", 204 + "tableFrom": "activity", 205 + "tableTo": "user", 206 + "columnsFrom": ["user_id"], 207 + "columnsTo": ["id"], 208 + "onDelete": "cascade", 209 + "onUpdate": "cascade" 210 + } 211 + }, 212 + "compositePrimaryKeys": {}, 213 + "uniqueConstraints": {}, 214 + "policies": {}, 215 + "checkConstraints": {}, 216 + "isRLSEnabled": false 217 + }, 218 + "public.apikey": { 219 + "name": "apikey", 220 + "schema": "", 221 + "columns": { 222 + "id": { 223 + "name": "id", 224 + "type": "text", 225 + "primaryKey": true, 226 + "notNull": true 227 + }, 228 + "name": { 229 + "name": "name", 230 + "type": "text", 231 + "primaryKey": false, 232 + "notNull": false 233 + }, 234 + "start": { 235 + "name": "start", 236 + "type": "text", 237 + "primaryKey": false, 238 + "notNull": false 239 + }, 240 + "prefix": { 241 + "name": "prefix", 242 + "type": "text", 243 + "primaryKey": false, 244 + "notNull": false 245 + }, 246 + "key": { 247 + "name": "key", 248 + "type": "text", 249 + "primaryKey": false, 250 + "notNull": true 251 + }, 252 + "user_id": { 253 + "name": "user_id", 254 + "type": "text", 255 + "primaryKey": false, 256 + "notNull": true 257 + }, 258 + "refill_interval": { 259 + "name": "refill_interval", 260 + "type": "integer", 261 + "primaryKey": false, 262 + "notNull": false 263 + }, 264 + "refill_amount": { 265 + "name": "refill_amount", 266 + "type": "integer", 267 + "primaryKey": false, 268 + "notNull": false 269 + }, 270 + "last_refill_at": { 271 + "name": "last_refill_at", 272 + "type": "timestamp", 273 + "primaryKey": false, 274 + "notNull": false 275 + }, 276 + "enabled": { 277 + "name": "enabled", 278 + "type": "boolean", 279 + "primaryKey": false, 280 + "notNull": false, 281 + "default": true 282 + }, 283 + "rate_limit_enabled": { 284 + "name": "rate_limit_enabled", 285 + "type": "boolean", 286 + "primaryKey": false, 287 + "notNull": false, 288 + "default": true 289 + }, 290 + "rate_limit_time_window": { 291 + "name": "rate_limit_time_window", 292 + "type": "integer", 293 + "primaryKey": false, 294 + "notNull": false, 295 + "default": 86400000 296 + }, 297 + "rate_limit_max": { 298 + "name": "rate_limit_max", 299 + "type": "integer", 300 + "primaryKey": false, 301 + "notNull": false, 302 + "default": 10 303 + }, 304 + "request_count": { 305 + "name": "request_count", 306 + "type": "integer", 307 + "primaryKey": false, 308 + "notNull": false, 309 + "default": 0 310 + }, 311 + "remaining": { 312 + "name": "remaining", 313 + "type": "integer", 314 + "primaryKey": false, 315 + "notNull": false 316 + }, 317 + "last_request": { 318 + "name": "last_request", 319 + "type": "timestamp", 320 + "primaryKey": false, 321 + "notNull": false 322 + }, 323 + "expires_at": { 324 + "name": "expires_at", 325 + "type": "timestamp", 326 + "primaryKey": false, 327 + "notNull": false 328 + }, 329 + "created_at": { 330 + "name": "created_at", 331 + "type": "timestamp", 332 + "primaryKey": false, 333 + "notNull": true 334 + }, 335 + "updated_at": { 336 + "name": "updated_at", 337 + "type": "timestamp", 338 + "primaryKey": false, 339 + "notNull": true 340 + }, 341 + "permissions": { 342 + "name": "permissions", 343 + "type": "text", 344 + "primaryKey": false, 345 + "notNull": false 346 + }, 347 + "metadata": { 348 + "name": "metadata", 349 + "type": "text", 350 + "primaryKey": false, 351 + "notNull": false 352 + } 353 + }, 354 + "indexes": { 355 + "apikey_key_idx": { 356 + "name": "apikey_key_idx", 357 + "columns": [ 358 + { 359 + "expression": "key", 360 + "isExpression": false, 361 + "asc": true, 362 + "nulls": "last" 363 + } 364 + ], 365 + "isUnique": false, 366 + "concurrently": false, 367 + "method": "btree", 368 + "with": {} 369 + }, 370 + "apikey_userId_idx": { 371 + "name": "apikey_userId_idx", 372 + "columns": [ 373 + { 374 + "expression": "user_id", 375 + "isExpression": false, 376 + "asc": true, 377 + "nulls": "last" 378 + } 379 + ], 380 + "isUnique": false, 381 + "concurrently": false, 382 + "method": "btree", 383 + "with": {} 384 + } 385 + }, 386 + "foreignKeys": { 387 + "apikey_user_id_user_id_fk": { 388 + "name": "apikey_user_id_user_id_fk", 389 + "tableFrom": "apikey", 390 + "tableTo": "user", 391 + "columnsFrom": ["user_id"], 392 + "columnsTo": ["id"], 393 + "onDelete": "cascade", 394 + "onUpdate": "no action" 395 + } 396 + }, 397 + "compositePrimaryKeys": {}, 398 + "uniqueConstraints": {}, 399 + "policies": {}, 400 + "checkConstraints": {}, 401 + "isRLSEnabled": false 402 + }, 403 + "public.external_link": { 404 + "name": "external_link", 405 + "schema": "", 406 + "columns": { 407 + "id": { 408 + "name": "id", 409 + "type": "text", 410 + "primaryKey": true, 411 + "notNull": true 412 + }, 413 + "task_id": { 414 + "name": "task_id", 415 + "type": "text", 416 + "primaryKey": false, 417 + "notNull": true 418 + }, 419 + "integration_id": { 420 + "name": "integration_id", 421 + "type": "text", 422 + "primaryKey": false, 423 + "notNull": true 424 + }, 425 + "resource_type": { 426 + "name": "resource_type", 427 + "type": "text", 428 + "primaryKey": false, 429 + "notNull": true 430 + }, 431 + "external_id": { 432 + "name": "external_id", 433 + "type": "text", 434 + "primaryKey": false, 435 + "notNull": true 436 + }, 437 + "url": { 438 + "name": "url", 439 + "type": "text", 440 + "primaryKey": false, 441 + "notNull": true 442 + }, 443 + "title": { 444 + "name": "title", 445 + "type": "text", 446 + "primaryKey": false, 447 + "notNull": false 448 + }, 449 + "metadata": { 450 + "name": "metadata", 451 + "type": "text", 452 + "primaryKey": false, 453 + "notNull": false 454 + }, 455 + "created_at": { 456 + "name": "created_at", 457 + "type": "timestamp", 458 + "primaryKey": false, 459 + "notNull": true, 460 + "default": "now()" 461 + }, 462 + "updated_at": { 463 + "name": "updated_at", 464 + "type": "timestamp", 465 + "primaryKey": false, 466 + "notNull": true, 467 + "default": "now()" 468 + } 469 + }, 470 + "indexes": { 471 + "external_link_taskId_idx": { 472 + "name": "external_link_taskId_idx", 473 + "columns": [ 474 + { 475 + "expression": "task_id", 476 + "isExpression": false, 477 + "asc": true, 478 + "nulls": "last" 479 + } 480 + ], 481 + "isUnique": false, 482 + "concurrently": false, 483 + "method": "btree", 484 + "with": {} 485 + }, 486 + "external_link_integrationId_idx": { 487 + "name": "external_link_integrationId_idx", 488 + "columns": [ 489 + { 490 + "expression": "integration_id", 491 + "isExpression": false, 492 + "asc": true, 493 + "nulls": "last" 494 + } 495 + ], 496 + "isUnique": false, 497 + "concurrently": false, 498 + "method": "btree", 499 + "with": {} 500 + }, 501 + "external_link_externalId_idx": { 502 + "name": "external_link_externalId_idx", 503 + "columns": [ 504 + { 505 + "expression": "external_id", 506 + "isExpression": false, 507 + "asc": true, 508 + "nulls": "last" 509 + } 510 + ], 511 + "isUnique": false, 512 + "concurrently": false, 513 + "method": "btree", 514 + "with": {} 515 + }, 516 + "external_link_resourceType_idx": { 517 + "name": "external_link_resourceType_idx", 518 + "columns": [ 519 + { 520 + "expression": "resource_type", 521 + "isExpression": false, 522 + "asc": true, 523 + "nulls": "last" 524 + } 525 + ], 526 + "isUnique": false, 527 + "concurrently": false, 528 + "method": "btree", 529 + "with": {} 530 + } 531 + }, 532 + "foreignKeys": { 533 + "external_link_task_id_task_id_fk": { 534 + "name": "external_link_task_id_task_id_fk", 535 + "tableFrom": "external_link", 536 + "tableTo": "task", 537 + "columnsFrom": ["task_id"], 538 + "columnsTo": ["id"], 539 + "onDelete": "cascade", 540 + "onUpdate": "cascade" 541 + }, 542 + "external_link_integration_id_integration_id_fk": { 543 + "name": "external_link_integration_id_integration_id_fk", 544 + "tableFrom": "external_link", 545 + "tableTo": "integration", 546 + "columnsFrom": ["integration_id"], 547 + "columnsTo": ["id"], 548 + "onDelete": "cascade", 549 + "onUpdate": "cascade" 550 + } 551 + }, 552 + "compositePrimaryKeys": {}, 553 + "uniqueConstraints": {}, 554 + "policies": {}, 555 + "checkConstraints": {}, 556 + "isRLSEnabled": false 557 + }, 558 + "public.github_integration": { 559 + "name": "github_integration", 560 + "schema": "", 561 + "columns": { 562 + "id": { 563 + "name": "id", 564 + "type": "text", 565 + "primaryKey": true, 566 + "notNull": true 567 + }, 568 + "project_id": { 569 + "name": "project_id", 570 + "type": "text", 571 + "primaryKey": false, 572 + "notNull": true 573 + }, 574 + "repository_owner": { 575 + "name": "repository_owner", 576 + "type": "text", 577 + "primaryKey": false, 578 + "notNull": true 579 + }, 580 + "repository_name": { 581 + "name": "repository_name", 582 + "type": "text", 583 + "primaryKey": false, 584 + "notNull": true 585 + }, 586 + "installation_id": { 587 + "name": "installation_id", 588 + "type": "integer", 589 + "primaryKey": false, 590 + "notNull": false 591 + }, 592 + "is_active": { 593 + "name": "is_active", 594 + "type": "boolean", 595 + "primaryKey": false, 596 + "notNull": false, 597 + "default": true 598 + }, 599 + "created_at": { 600 + "name": "created_at", 601 + "type": "timestamp", 602 + "primaryKey": false, 603 + "notNull": true, 604 + "default": "now()" 605 + }, 606 + "updated_at": { 607 + "name": "updated_at", 608 + "type": "timestamp", 609 + "primaryKey": false, 610 + "notNull": true, 611 + "default": "now()" 612 + } 613 + }, 614 + "indexes": {}, 615 + "foreignKeys": { 616 + "github_integration_project_id_project_id_fk": { 617 + "name": "github_integration_project_id_project_id_fk", 618 + "tableFrom": "github_integration", 619 + "tableTo": "project", 620 + "columnsFrom": ["project_id"], 621 + "columnsTo": ["id"], 622 + "onDelete": "cascade", 623 + "onUpdate": "cascade" 624 + } 625 + }, 626 + "compositePrimaryKeys": {}, 627 + "uniqueConstraints": { 628 + "github_integration_project_id_unique": { 629 + "name": "github_integration_project_id_unique", 630 + "nullsNotDistinct": false, 631 + "columns": ["project_id"] 632 + } 633 + }, 634 + "policies": {}, 635 + "checkConstraints": {}, 636 + "isRLSEnabled": false 637 + }, 638 + "public.integration": { 639 + "name": "integration", 640 + "schema": "", 641 + "columns": { 642 + "id": { 643 + "name": "id", 644 + "type": "text", 645 + "primaryKey": true, 646 + "notNull": true 647 + }, 648 + "project_id": { 649 + "name": "project_id", 650 + "type": "text", 651 + "primaryKey": false, 652 + "notNull": true 653 + }, 654 + "type": { 655 + "name": "type", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": true 659 + }, 660 + "config": { 661 + "name": "config", 662 + "type": "text", 663 + "primaryKey": false, 664 + "notNull": true 665 + }, 666 + "is_active": { 667 + "name": "is_active", 668 + "type": "boolean", 669 + "primaryKey": false, 670 + "notNull": false, 671 + "default": true 672 + }, 673 + "created_at": { 674 + "name": "created_at", 675 + "type": "timestamp", 676 + "primaryKey": false, 677 + "notNull": true, 678 + "default": "now()" 679 + }, 680 + "updated_at": { 681 + "name": "updated_at", 682 + "type": "timestamp", 683 + "primaryKey": false, 684 + "notNull": true, 685 + "default": "now()" 686 + } 687 + }, 688 + "indexes": { 689 + "integration_projectId_idx": { 690 + "name": "integration_projectId_idx", 691 + "columns": [ 692 + { 693 + "expression": "project_id", 694 + "isExpression": false, 695 + "asc": true, 696 + "nulls": "last" 697 + } 698 + ], 699 + "isUnique": false, 700 + "concurrently": false, 701 + "method": "btree", 702 + "with": {} 703 + }, 704 + "integration_type_idx": { 705 + "name": "integration_type_idx", 706 + "columns": [ 707 + { 708 + "expression": "type", 709 + "isExpression": false, 710 + "asc": true, 711 + "nulls": "last" 712 + } 713 + ], 714 + "isUnique": false, 715 + "concurrently": false, 716 + "method": "btree", 717 + "with": {} 718 + } 719 + }, 720 + "foreignKeys": { 721 + "integration_project_id_project_id_fk": { 722 + "name": "integration_project_id_project_id_fk", 723 + "tableFrom": "integration", 724 + "tableTo": "project", 725 + "columnsFrom": ["project_id"], 726 + "columnsTo": ["id"], 727 + "onDelete": "cascade", 728 + "onUpdate": "cascade" 729 + } 730 + }, 731 + "compositePrimaryKeys": {}, 732 + "uniqueConstraints": {}, 733 + "policies": {}, 734 + "checkConstraints": {}, 735 + "isRLSEnabled": false 736 + }, 737 + "public.invitation": { 738 + "name": "invitation", 739 + "schema": "", 740 + "columns": { 741 + "id": { 742 + "name": "id", 743 + "type": "text", 744 + "primaryKey": true, 745 + "notNull": true 746 + }, 747 + "workspace_id": { 748 + "name": "workspace_id", 749 + "type": "text", 750 + "primaryKey": false, 751 + "notNull": true 752 + }, 753 + "email": { 754 + "name": "email", 755 + "type": "text", 756 + "primaryKey": false, 757 + "notNull": true 758 + }, 759 + "role": { 760 + "name": "role", 761 + "type": "text", 762 + "primaryKey": false, 763 + "notNull": false 764 + }, 765 + "team_id": { 766 + "name": "team_id", 767 + "type": "text", 768 + "primaryKey": false, 769 + "notNull": false 770 + }, 771 + "status": { 772 + "name": "status", 773 + "type": "text", 774 + "primaryKey": false, 775 + "notNull": true, 776 + "default": "'pending'" 777 + }, 778 + "expires_at": { 779 + "name": "expires_at", 780 + "type": "timestamp", 781 + "primaryKey": false, 782 + "notNull": true 783 + }, 784 + "created_at": { 785 + "name": "created_at", 786 + "type": "timestamp", 787 + "primaryKey": false, 788 + "notNull": true, 789 + "default": "now()" 790 + }, 791 + "inviter_id": { 792 + "name": "inviter_id", 793 + "type": "text", 794 + "primaryKey": false, 795 + "notNull": true 796 + } 797 + }, 798 + "indexes": { 799 + "invitation_workspaceId_idx": { 800 + "name": "invitation_workspaceId_idx", 801 + "columns": [ 802 + { 803 + "expression": "workspace_id", 804 + "isExpression": false, 805 + "asc": true, 806 + "nulls": "last" 807 + } 808 + ], 809 + "isUnique": false, 810 + "concurrently": false, 811 + "method": "btree", 812 + "with": {} 813 + }, 814 + "invitation_email_idx": { 815 + "name": "invitation_email_idx", 816 + "columns": [ 817 + { 818 + "expression": "email", 819 + "isExpression": false, 820 + "asc": true, 821 + "nulls": "last" 822 + } 823 + ], 824 + "isUnique": false, 825 + "concurrently": false, 826 + "method": "btree", 827 + "with": {} 828 + } 829 + }, 830 + "foreignKeys": { 831 + "invitation_workspace_id_workspace_id_fk": { 832 + "name": "invitation_workspace_id_workspace_id_fk", 833 + "tableFrom": "invitation", 834 + "tableTo": "workspace", 835 + "columnsFrom": ["workspace_id"], 836 + "columnsTo": ["id"], 837 + "onDelete": "cascade", 838 + "onUpdate": "no action" 839 + }, 840 + "invitation_inviter_id_user_id_fk": { 841 + "name": "invitation_inviter_id_user_id_fk", 842 + "tableFrom": "invitation", 843 + "tableTo": "user", 844 + "columnsFrom": ["inviter_id"], 845 + "columnsTo": ["id"], 846 + "onDelete": "cascade", 847 + "onUpdate": "no action" 848 + } 849 + }, 850 + "compositePrimaryKeys": {}, 851 + "uniqueConstraints": {}, 852 + "policies": {}, 853 + "checkConstraints": {}, 854 + "isRLSEnabled": false 855 + }, 856 + "public.label": { 857 + "name": "label", 858 + "schema": "", 859 + "columns": { 860 + "id": { 861 + "name": "id", 862 + "type": "text", 863 + "primaryKey": true, 864 + "notNull": true 865 + }, 866 + "name": { 867 + "name": "name", 868 + "type": "text", 869 + "primaryKey": false, 870 + "notNull": true 871 + }, 872 + "color": { 873 + "name": "color", 874 + "type": "text", 875 + "primaryKey": false, 876 + "notNull": true 877 + }, 878 + "created_at": { 879 + "name": "created_at", 880 + "type": "timestamp", 881 + "primaryKey": false, 882 + "notNull": true, 883 + "default": "now()" 884 + }, 885 + "task_id": { 886 + "name": "task_id", 887 + "type": "text", 888 + "primaryKey": false, 889 + "notNull": false 890 + }, 891 + "workspace_id": { 892 + "name": "workspace_id", 893 + "type": "text", 894 + "primaryKey": false, 895 + "notNull": false 896 + } 897 + }, 898 + "indexes": {}, 899 + "foreignKeys": { 900 + "label_task_id_task_id_fk": { 901 + "name": "label_task_id_task_id_fk", 902 + "tableFrom": "label", 903 + "tableTo": "task", 904 + "columnsFrom": ["task_id"], 905 + "columnsTo": ["id"], 906 + "onDelete": "cascade", 907 + "onUpdate": "cascade" 908 + }, 909 + "label_workspace_id_workspace_id_fk": { 910 + "name": "label_workspace_id_workspace_id_fk", 911 + "tableFrom": "label", 912 + "tableTo": "workspace", 913 + "columnsFrom": ["workspace_id"], 914 + "columnsTo": ["id"], 915 + "onDelete": "cascade", 916 + "onUpdate": "cascade" 917 + } 918 + }, 919 + "compositePrimaryKeys": {}, 920 + "uniqueConstraints": {}, 921 + "policies": {}, 922 + "checkConstraints": {}, 923 + "isRLSEnabled": false 924 + }, 925 + "public.notification": { 926 + "name": "notification", 927 + "schema": "", 928 + "columns": { 929 + "id": { 930 + "name": "id", 931 + "type": "text", 932 + "primaryKey": true, 933 + "notNull": true 934 + }, 935 + "user_id": { 936 + "name": "user_id", 937 + "type": "text", 938 + "primaryKey": false, 939 + "notNull": true 940 + }, 941 + "title": { 942 + "name": "title", 943 + "type": "text", 944 + "primaryKey": false, 945 + "notNull": true 946 + }, 947 + "content": { 948 + "name": "content", 949 + "type": "text", 950 + "primaryKey": false, 951 + "notNull": false 952 + }, 953 + "type": { 954 + "name": "type", 955 + "type": "text", 956 + "primaryKey": false, 957 + "notNull": true, 958 + "default": "'info'" 959 + }, 960 + "is_read": { 961 + "name": "is_read", 962 + "type": "boolean", 963 + "primaryKey": false, 964 + "notNull": false, 965 + "default": false 966 + }, 967 + "resource_id": { 968 + "name": "resource_id", 969 + "type": "text", 970 + "primaryKey": false, 971 + "notNull": false 972 + }, 973 + "resource_type": { 974 + "name": "resource_type", 975 + "type": "text", 976 + "primaryKey": false, 977 + "notNull": false 978 + }, 979 + "created_at": { 980 + "name": "created_at", 981 + "type": "timestamp with time zone", 982 + "primaryKey": false, 983 + "notNull": true, 984 + "default": "now()" 985 + } 986 + }, 987 + "indexes": {}, 988 + "foreignKeys": { 989 + "notification_user_id_user_id_fk": { 990 + "name": "notification_user_id_user_id_fk", 991 + "tableFrom": "notification", 992 + "tableTo": "user", 993 + "columnsFrom": ["user_id"], 994 + "columnsTo": ["id"], 995 + "onDelete": "cascade", 996 + "onUpdate": "cascade" 997 + } 998 + }, 999 + "compositePrimaryKeys": {}, 1000 + "uniqueConstraints": {}, 1001 + "policies": {}, 1002 + "checkConstraints": {}, 1003 + "isRLSEnabled": false 1004 + }, 1005 + "public.project": { 1006 + "name": "project", 1007 + "schema": "", 1008 + "columns": { 1009 + "id": { 1010 + "name": "id", 1011 + "type": "text", 1012 + "primaryKey": true, 1013 + "notNull": true 1014 + }, 1015 + "workspace_id": { 1016 + "name": "workspace_id", 1017 + "type": "text", 1018 + "primaryKey": false, 1019 + "notNull": true 1020 + }, 1021 + "slug": { 1022 + "name": "slug", 1023 + "type": "text", 1024 + "primaryKey": false, 1025 + "notNull": true 1026 + }, 1027 + "icon": { 1028 + "name": "icon", 1029 + "type": "text", 1030 + "primaryKey": false, 1031 + "notNull": false, 1032 + "default": "'Layout'" 1033 + }, 1034 + "name": { 1035 + "name": "name", 1036 + "type": "text", 1037 + "primaryKey": false, 1038 + "notNull": true 1039 + }, 1040 + "description": { 1041 + "name": "description", 1042 + "type": "text", 1043 + "primaryKey": false, 1044 + "notNull": false 1045 + }, 1046 + "created_at": { 1047 + "name": "created_at", 1048 + "type": "timestamp", 1049 + "primaryKey": false, 1050 + "notNull": true, 1051 + "default": "now()" 1052 + }, 1053 + "is_public": { 1054 + "name": "is_public", 1055 + "type": "boolean", 1056 + "primaryKey": false, 1057 + "notNull": false, 1058 + "default": false 1059 + } 1060 + }, 1061 + "indexes": {}, 1062 + "foreignKeys": { 1063 + "project_workspace_id_workspace_id_fk": { 1064 + "name": "project_workspace_id_workspace_id_fk", 1065 + "tableFrom": "project", 1066 + "tableTo": "workspace", 1067 + "columnsFrom": ["workspace_id"], 1068 + "columnsTo": ["id"], 1069 + "onDelete": "cascade", 1070 + "onUpdate": "cascade" 1071 + } 1072 + }, 1073 + "compositePrimaryKeys": {}, 1074 + "uniqueConstraints": {}, 1075 + "policies": {}, 1076 + "checkConstraints": {}, 1077 + "isRLSEnabled": false 1078 + }, 1079 + "public.session": { 1080 + "name": "session", 1081 + "schema": "", 1082 + "columns": { 1083 + "id": { 1084 + "name": "id", 1085 + "type": "text", 1086 + "primaryKey": true, 1087 + "notNull": true 1088 + }, 1089 + "expires_at": { 1090 + "name": "expires_at", 1091 + "type": "timestamp", 1092 + "primaryKey": false, 1093 + "notNull": true 1094 + }, 1095 + "token": { 1096 + "name": "token", 1097 + "type": "text", 1098 + "primaryKey": false, 1099 + "notNull": true 1100 + }, 1101 + "created_at": { 1102 + "name": "created_at", 1103 + "type": "timestamp", 1104 + "primaryKey": false, 1105 + "notNull": true, 1106 + "default": "now()" 1107 + }, 1108 + "updated_at": { 1109 + "name": "updated_at", 1110 + "type": "timestamp", 1111 + "primaryKey": false, 1112 + "notNull": true 1113 + }, 1114 + "ip_address": { 1115 + "name": "ip_address", 1116 + "type": "text", 1117 + "primaryKey": false, 1118 + "notNull": false 1119 + }, 1120 + "user_agent": { 1121 + "name": "user_agent", 1122 + "type": "text", 1123 + "primaryKey": false, 1124 + "notNull": false 1125 + }, 1126 + "user_id": { 1127 + "name": "user_id", 1128 + "type": "text", 1129 + "primaryKey": false, 1130 + "notNull": true 1131 + }, 1132 + "active_organization_id": { 1133 + "name": "active_organization_id", 1134 + "type": "text", 1135 + "primaryKey": false, 1136 + "notNull": false 1137 + }, 1138 + "active_team_id": { 1139 + "name": "active_team_id", 1140 + "type": "text", 1141 + "primaryKey": false, 1142 + "notNull": false 1143 + } 1144 + }, 1145 + "indexes": { 1146 + "session_userId_idx": { 1147 + "name": "session_userId_idx", 1148 + "columns": [ 1149 + { 1150 + "expression": "user_id", 1151 + "isExpression": false, 1152 + "asc": true, 1153 + "nulls": "last" 1154 + } 1155 + ], 1156 + "isUnique": false, 1157 + "concurrently": false, 1158 + "method": "btree", 1159 + "with": {} 1160 + } 1161 + }, 1162 + "foreignKeys": { 1163 + "session_user_id_user_id_fk": { 1164 + "name": "session_user_id_user_id_fk", 1165 + "tableFrom": "session", 1166 + "tableTo": "user", 1167 + "columnsFrom": ["user_id"], 1168 + "columnsTo": ["id"], 1169 + "onDelete": "cascade", 1170 + "onUpdate": "no action" 1171 + } 1172 + }, 1173 + "compositePrimaryKeys": {}, 1174 + "uniqueConstraints": { 1175 + "session_token_unique": { 1176 + "name": "session_token_unique", 1177 + "nullsNotDistinct": false, 1178 + "columns": ["token"] 1179 + } 1180 + }, 1181 + "policies": {}, 1182 + "checkConstraints": {}, 1183 + "isRLSEnabled": false 1184 + }, 1185 + "public.task": { 1186 + "name": "task", 1187 + "schema": "", 1188 + "columns": { 1189 + "id": { 1190 + "name": "id", 1191 + "type": "text", 1192 + "primaryKey": true, 1193 + "notNull": true 1194 + }, 1195 + "project_id": { 1196 + "name": "project_id", 1197 + "type": "text", 1198 + "primaryKey": false, 1199 + "notNull": true 1200 + }, 1201 + "position": { 1202 + "name": "position", 1203 + "type": "integer", 1204 + "primaryKey": false, 1205 + "notNull": false, 1206 + "default": 0 1207 + }, 1208 + "number": { 1209 + "name": "number", 1210 + "type": "integer", 1211 + "primaryKey": false, 1212 + "notNull": false, 1213 + "default": 1 1214 + }, 1215 + "assignee_id": { 1216 + "name": "assignee_id", 1217 + "type": "text", 1218 + "primaryKey": false, 1219 + "notNull": false 1220 + }, 1221 + "title": { 1222 + "name": "title", 1223 + "type": "text", 1224 + "primaryKey": false, 1225 + "notNull": true 1226 + }, 1227 + "description": { 1228 + "name": "description", 1229 + "type": "text", 1230 + "primaryKey": false, 1231 + "notNull": false 1232 + }, 1233 + "status": { 1234 + "name": "status", 1235 + "type": "text", 1236 + "primaryKey": false, 1237 + "notNull": true, 1238 + "default": "'to-do'" 1239 + }, 1240 + "priority": { 1241 + "name": "priority", 1242 + "type": "text", 1243 + "primaryKey": false, 1244 + "notNull": false, 1245 + "default": "'low'" 1246 + }, 1247 + "due_date": { 1248 + "name": "due_date", 1249 + "type": "timestamp", 1250 + "primaryKey": false, 1251 + "notNull": false 1252 + }, 1253 + "created_at": { 1254 + "name": "created_at", 1255 + "type": "timestamp", 1256 + "primaryKey": false, 1257 + "notNull": true, 1258 + "default": "now()" 1259 + } 1260 + }, 1261 + "indexes": {}, 1262 + "foreignKeys": { 1263 + "task_project_id_project_id_fk": { 1264 + "name": "task_project_id_project_id_fk", 1265 + "tableFrom": "task", 1266 + "tableTo": "project", 1267 + "columnsFrom": ["project_id"], 1268 + "columnsTo": ["id"], 1269 + "onDelete": "cascade", 1270 + "onUpdate": "cascade" 1271 + }, 1272 + "task_assignee_id_user_id_fk": { 1273 + "name": "task_assignee_id_user_id_fk", 1274 + "tableFrom": "task", 1275 + "tableTo": "user", 1276 + "columnsFrom": ["assignee_id"], 1277 + "columnsTo": ["id"], 1278 + "onDelete": "cascade", 1279 + "onUpdate": "cascade" 1280 + } 1281 + }, 1282 + "compositePrimaryKeys": {}, 1283 + "uniqueConstraints": {}, 1284 + "policies": {}, 1285 + "checkConstraints": {}, 1286 + "isRLSEnabled": false 1287 + }, 1288 + "public.team_member": { 1289 + "name": "team_member", 1290 + "schema": "", 1291 + "columns": { 1292 + "id": { 1293 + "name": "id", 1294 + "type": "text", 1295 + "primaryKey": true, 1296 + "notNull": true 1297 + }, 1298 + "team_id": { 1299 + "name": "team_id", 1300 + "type": "text", 1301 + "primaryKey": false, 1302 + "notNull": true 1303 + }, 1304 + "user_id": { 1305 + "name": "user_id", 1306 + "type": "text", 1307 + "primaryKey": false, 1308 + "notNull": true 1309 + }, 1310 + "created_at": { 1311 + "name": "created_at", 1312 + "type": "timestamp", 1313 + "primaryKey": false, 1314 + "notNull": false 1315 + } 1316 + }, 1317 + "indexes": { 1318 + "teamMember_teamId_idx": { 1319 + "name": "teamMember_teamId_idx", 1320 + "columns": [ 1321 + { 1322 + "expression": "team_id", 1323 + "isExpression": false, 1324 + "asc": true, 1325 + "nulls": "last" 1326 + } 1327 + ], 1328 + "isUnique": false, 1329 + "concurrently": false, 1330 + "method": "btree", 1331 + "with": {} 1332 + }, 1333 + "teamMember_userId_idx": { 1334 + "name": "teamMember_userId_idx", 1335 + "columns": [ 1336 + { 1337 + "expression": "user_id", 1338 + "isExpression": false, 1339 + "asc": true, 1340 + "nulls": "last" 1341 + } 1342 + ], 1343 + "isUnique": false, 1344 + "concurrently": false, 1345 + "method": "btree", 1346 + "with": {} 1347 + } 1348 + }, 1349 + "foreignKeys": { 1350 + "team_member_team_id_team_id_fk": { 1351 + "name": "team_member_team_id_team_id_fk", 1352 + "tableFrom": "team_member", 1353 + "tableTo": "team", 1354 + "columnsFrom": ["team_id"], 1355 + "columnsTo": ["id"], 1356 + "onDelete": "cascade", 1357 + "onUpdate": "no action" 1358 + }, 1359 + "team_member_user_id_user_id_fk": { 1360 + "name": "team_member_user_id_user_id_fk", 1361 + "tableFrom": "team_member", 1362 + "tableTo": "user", 1363 + "columnsFrom": ["user_id"], 1364 + "columnsTo": ["id"], 1365 + "onDelete": "cascade", 1366 + "onUpdate": "no action" 1367 + } 1368 + }, 1369 + "compositePrimaryKeys": {}, 1370 + "uniqueConstraints": {}, 1371 + "policies": {}, 1372 + "checkConstraints": {}, 1373 + "isRLSEnabled": false 1374 + }, 1375 + "public.team": { 1376 + "name": "team", 1377 + "schema": "", 1378 + "columns": { 1379 + "id": { 1380 + "name": "id", 1381 + "type": "text", 1382 + "primaryKey": true, 1383 + "notNull": true 1384 + }, 1385 + "name": { 1386 + "name": "name", 1387 + "type": "text", 1388 + "primaryKey": false, 1389 + "notNull": true 1390 + }, 1391 + "workspace_id": { 1392 + "name": "workspace_id", 1393 + "type": "text", 1394 + "primaryKey": false, 1395 + "notNull": true 1396 + }, 1397 + "created_at": { 1398 + "name": "created_at", 1399 + "type": "timestamp", 1400 + "primaryKey": false, 1401 + "notNull": true 1402 + }, 1403 + "updated_at": { 1404 + "name": "updated_at", 1405 + "type": "timestamp", 1406 + "primaryKey": false, 1407 + "notNull": false 1408 + } 1409 + }, 1410 + "indexes": { 1411 + "team_workspaceId_idx": { 1412 + "name": "team_workspaceId_idx", 1413 + "columns": [ 1414 + { 1415 + "expression": "workspace_id", 1416 + "isExpression": false, 1417 + "asc": true, 1418 + "nulls": "last" 1419 + } 1420 + ], 1421 + "isUnique": false, 1422 + "concurrently": false, 1423 + "method": "btree", 1424 + "with": {} 1425 + } 1426 + }, 1427 + "foreignKeys": { 1428 + "team_workspace_id_workspace_id_fk": { 1429 + "name": "team_workspace_id_workspace_id_fk", 1430 + "tableFrom": "team", 1431 + "tableTo": "workspace", 1432 + "columnsFrom": ["workspace_id"], 1433 + "columnsTo": ["id"], 1434 + "onDelete": "cascade", 1435 + "onUpdate": "no action" 1436 + } 1437 + }, 1438 + "compositePrimaryKeys": {}, 1439 + "uniqueConstraints": {}, 1440 + "policies": {}, 1441 + "checkConstraints": {}, 1442 + "isRLSEnabled": false 1443 + }, 1444 + "public.time_entry": { 1445 + "name": "time_entry", 1446 + "schema": "", 1447 + "columns": { 1448 + "id": { 1449 + "name": "id", 1450 + "type": "text", 1451 + "primaryKey": true, 1452 + "notNull": true 1453 + }, 1454 + "task_id": { 1455 + "name": "task_id", 1456 + "type": "text", 1457 + "primaryKey": false, 1458 + "notNull": true 1459 + }, 1460 + "user_id": { 1461 + "name": "user_id", 1462 + "type": "text", 1463 + "primaryKey": false, 1464 + "notNull": false 1465 + }, 1466 + "description": { 1467 + "name": "description", 1468 + "type": "text", 1469 + "primaryKey": false, 1470 + "notNull": false 1471 + }, 1472 + "start_time": { 1473 + "name": "start_time", 1474 + "type": "timestamp", 1475 + "primaryKey": false, 1476 + "notNull": true 1477 + }, 1478 + "end_time": { 1479 + "name": "end_time", 1480 + "type": "timestamp", 1481 + "primaryKey": false, 1482 + "notNull": false 1483 + }, 1484 + "duration": { 1485 + "name": "duration", 1486 + "type": "integer", 1487 + "primaryKey": false, 1488 + "notNull": false, 1489 + "default": 0 1490 + }, 1491 + "created_at": { 1492 + "name": "created_at", 1493 + "type": "timestamp", 1494 + "primaryKey": false, 1495 + "notNull": true, 1496 + "default": "now()" 1497 + } 1498 + }, 1499 + "indexes": {}, 1500 + "foreignKeys": { 1501 + "time_entry_task_id_task_id_fk": { 1502 + "name": "time_entry_task_id_task_id_fk", 1503 + "tableFrom": "time_entry", 1504 + "tableTo": "task", 1505 + "columnsFrom": ["task_id"], 1506 + "columnsTo": ["id"], 1507 + "onDelete": "cascade", 1508 + "onUpdate": "cascade" 1509 + }, 1510 + "time_entry_user_id_user_id_fk": { 1511 + "name": "time_entry_user_id_user_id_fk", 1512 + "tableFrom": "time_entry", 1513 + "tableTo": "user", 1514 + "columnsFrom": ["user_id"], 1515 + "columnsTo": ["id"], 1516 + "onDelete": "cascade", 1517 + "onUpdate": "cascade" 1518 + } 1519 + }, 1520 + "compositePrimaryKeys": {}, 1521 + "uniqueConstraints": {}, 1522 + "policies": {}, 1523 + "checkConstraints": {}, 1524 + "isRLSEnabled": false 1525 + }, 1526 + "public.user": { 1527 + "name": "user", 1528 + "schema": "", 1529 + "columns": { 1530 + "id": { 1531 + "name": "id", 1532 + "type": "text", 1533 + "primaryKey": true, 1534 + "notNull": true 1535 + }, 1536 + "name": { 1537 + "name": "name", 1538 + "type": "text", 1539 + "primaryKey": false, 1540 + "notNull": true 1541 + }, 1542 + "email": { 1543 + "name": "email", 1544 + "type": "text", 1545 + "primaryKey": false, 1546 + "notNull": true 1547 + }, 1548 + "email_verified": { 1549 + "name": "email_verified", 1550 + "type": "boolean", 1551 + "primaryKey": false, 1552 + "notNull": true 1553 + }, 1554 + "image": { 1555 + "name": "image", 1556 + "type": "text", 1557 + "primaryKey": false, 1558 + "notNull": false 1559 + }, 1560 + "created_at": { 1561 + "name": "created_at", 1562 + "type": "timestamp", 1563 + "primaryKey": false, 1564 + "notNull": true, 1565 + "default": "now()" 1566 + }, 1567 + "updated_at": { 1568 + "name": "updated_at", 1569 + "type": "timestamp", 1570 + "primaryKey": false, 1571 + "notNull": true, 1572 + "default": "now()" 1573 + }, 1574 + "is_anonymous": { 1575 + "name": "is_anonymous", 1576 + "type": "boolean", 1577 + "primaryKey": false, 1578 + "notNull": false, 1579 + "default": false 1580 + } 1581 + }, 1582 + "indexes": {}, 1583 + "foreignKeys": {}, 1584 + "compositePrimaryKeys": {}, 1585 + "uniqueConstraints": { 1586 + "user_email_unique": { 1587 + "name": "user_email_unique", 1588 + "nullsNotDistinct": false, 1589 + "columns": ["email"] 1590 + } 1591 + }, 1592 + "policies": {}, 1593 + "checkConstraints": {}, 1594 + "isRLSEnabled": false 1595 + }, 1596 + "public.verification": { 1597 + "name": "verification", 1598 + "schema": "", 1599 + "columns": { 1600 + "id": { 1601 + "name": "id", 1602 + "type": "text", 1603 + "primaryKey": true, 1604 + "notNull": true 1605 + }, 1606 + "identifier": { 1607 + "name": "identifier", 1608 + "type": "text", 1609 + "primaryKey": false, 1610 + "notNull": true 1611 + }, 1612 + "value": { 1613 + "name": "value", 1614 + "type": "text", 1615 + "primaryKey": false, 1616 + "notNull": true 1617 + }, 1618 + "expires_at": { 1619 + "name": "expires_at", 1620 + "type": "timestamp", 1621 + "primaryKey": false, 1622 + "notNull": true 1623 + }, 1624 + "created_at": { 1625 + "name": "created_at", 1626 + "type": "timestamp", 1627 + "primaryKey": false, 1628 + "notNull": true, 1629 + "default": "now()" 1630 + }, 1631 + "updated_at": { 1632 + "name": "updated_at", 1633 + "type": "timestamp", 1634 + "primaryKey": false, 1635 + "notNull": true, 1636 + "default": "now()" 1637 + } 1638 + }, 1639 + "indexes": { 1640 + "verification_identifier_idx": { 1641 + "name": "verification_identifier_idx", 1642 + "columns": [ 1643 + { 1644 + "expression": "identifier", 1645 + "isExpression": false, 1646 + "asc": true, 1647 + "nulls": "last" 1648 + } 1649 + ], 1650 + "isUnique": false, 1651 + "concurrently": false, 1652 + "method": "btree", 1653 + "with": {} 1654 + } 1655 + }, 1656 + "foreignKeys": {}, 1657 + "compositePrimaryKeys": {}, 1658 + "uniqueConstraints": {}, 1659 + "policies": {}, 1660 + "checkConstraints": {}, 1661 + "isRLSEnabled": false 1662 + }, 1663 + "public.workspace": { 1664 + "name": "workspace", 1665 + "schema": "", 1666 + "columns": { 1667 + "id": { 1668 + "name": "id", 1669 + "type": "text", 1670 + "primaryKey": true, 1671 + "notNull": true 1672 + }, 1673 + "name": { 1674 + "name": "name", 1675 + "type": "text", 1676 + "primaryKey": false, 1677 + "notNull": true 1678 + }, 1679 + "slug": { 1680 + "name": "slug", 1681 + "type": "text", 1682 + "primaryKey": false, 1683 + "notNull": true 1684 + }, 1685 + "logo": { 1686 + "name": "logo", 1687 + "type": "text", 1688 + "primaryKey": false, 1689 + "notNull": false 1690 + }, 1691 + "metadata": { 1692 + "name": "metadata", 1693 + "type": "text", 1694 + "primaryKey": false, 1695 + "notNull": false 1696 + }, 1697 + "description": { 1698 + "name": "description", 1699 + "type": "text", 1700 + "primaryKey": false, 1701 + "notNull": false 1702 + }, 1703 + "created_at": { 1704 + "name": "created_at", 1705 + "type": "timestamp", 1706 + "primaryKey": false, 1707 + "notNull": true 1708 + } 1709 + }, 1710 + "indexes": {}, 1711 + "foreignKeys": {}, 1712 + "compositePrimaryKeys": {}, 1713 + "uniqueConstraints": { 1714 + "workspace_slug_unique": { 1715 + "name": "workspace_slug_unique", 1716 + "nullsNotDistinct": false, 1717 + "columns": ["slug"] 1718 + } 1719 + }, 1720 + "policies": {}, 1721 + "checkConstraints": {}, 1722 + "isRLSEnabled": false 1723 + }, 1724 + "public.workspace_member": { 1725 + "name": "workspace_member", 1726 + "schema": "", 1727 + "columns": { 1728 + "id": { 1729 + "name": "id", 1730 + "type": "text", 1731 + "primaryKey": true, 1732 + "notNull": true 1733 + }, 1734 + "workspace_id": { 1735 + "name": "workspace_id", 1736 + "type": "text", 1737 + "primaryKey": false, 1738 + "notNull": true 1739 + }, 1740 + "user_id": { 1741 + "name": "user_id", 1742 + "type": "text", 1743 + "primaryKey": false, 1744 + "notNull": true 1745 + }, 1746 + "role": { 1747 + "name": "role", 1748 + "type": "text", 1749 + "primaryKey": false, 1750 + "notNull": true, 1751 + "default": "'member'" 1752 + }, 1753 + "joined_at": { 1754 + "name": "joined_at", 1755 + "type": "timestamp", 1756 + "primaryKey": false, 1757 + "notNull": true 1758 + } 1759 + }, 1760 + "indexes": { 1761 + "workspace_member_workspaceId_idx": { 1762 + "name": "workspace_member_workspaceId_idx", 1763 + "columns": [ 1764 + { 1765 + "expression": "workspace_id", 1766 + "isExpression": false, 1767 + "asc": true, 1768 + "nulls": "last" 1769 + } 1770 + ], 1771 + "isUnique": false, 1772 + "concurrently": false, 1773 + "method": "btree", 1774 + "with": {} 1775 + }, 1776 + "workspace_member_userId_idx": { 1777 + "name": "workspace_member_userId_idx", 1778 + "columns": [ 1779 + { 1780 + "expression": "user_id", 1781 + "isExpression": false, 1782 + "asc": true, 1783 + "nulls": "last" 1784 + } 1785 + ], 1786 + "isUnique": false, 1787 + "concurrently": false, 1788 + "method": "btree", 1789 + "with": {} 1790 + } 1791 + }, 1792 + "foreignKeys": { 1793 + "workspace_member_workspace_id_workspace_id_fk": { 1794 + "name": "workspace_member_workspace_id_workspace_id_fk", 1795 + "tableFrom": "workspace_member", 1796 + "tableTo": "workspace", 1797 + "columnsFrom": ["workspace_id"], 1798 + "columnsTo": ["id"], 1799 + "onDelete": "cascade", 1800 + "onUpdate": "no action" 1801 + }, 1802 + "workspace_member_user_id_user_id_fk": { 1803 + "name": "workspace_member_user_id_user_id_fk", 1804 + "tableFrom": "workspace_member", 1805 + "tableTo": "user", 1806 + "columnsFrom": ["user_id"], 1807 + "columnsTo": ["id"], 1808 + "onDelete": "cascade", 1809 + "onUpdate": "no action" 1810 + } 1811 + }, 1812 + "compositePrimaryKeys": {}, 1813 + "uniqueConstraints": {}, 1814 + "policies": {}, 1815 + "checkConstraints": {}, 1816 + "isRLSEnabled": false 1817 + } 1818 + }, 1819 + "enums": {}, 1820 + "schemas": {}, 1821 + "sequences": {}, 1822 + "roles": {}, 1823 + "policies": {}, 1824 + "views": {}, 1825 + "_meta": { 1826 + "columns": {}, 1827 + "schemas": {}, 1828 + "tables": {} 1829 + } 1830 + }
+14
apps/api/drizzle/meta/_journal.json
··· 71 71 "when": 1766950050412, 72 72 "tag": "0009_harsh_blacklash", 73 73 "breakpoints": true 74 + }, 75 + { 76 + "idx": 10, 77 + "version": "7", 78 + "when": 1767037216750, 79 + "tag": "0010_bouncy_taskmaster", 80 + "breakpoints": true 81 + }, 82 + { 83 + "idx": 11, 84 + "version": "7", 85 + "when": 1767037714394, 86 + "tag": "0011_flashy_masked_marvel", 87 + "breakpoints": true 74 88 } 75 89 ] 76 90 }
+8 -6
apps/api/src/database/schema.ts
··· 266 266 }), 267 267 type: text("type").notNull(), 268 268 createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 269 - userId: text("user_id") 270 - .notNull() 271 - .references(() => userTable.id, { 272 - onDelete: "cascade", 273 - onUpdate: "cascade", 274 - }), 269 + userId: text("user_id").references(() => userTable.id, { 270 + onDelete: "cascade", 271 + onUpdate: "cascade", 272 + }), 275 273 content: text("content"), 274 + externalUserName: text("external_user_name"), 275 + externalUserAvatar: text("external_user_avatar"), 276 + externalSource: text("external_source"), 277 + externalUrl: text("external_url"), 276 278 }); 277 279 278 280 export const labelTable = pgTable("label", {
+7
apps/api/src/label/controllers/create-label.ts
··· 1 1 import db from "../../database"; 2 2 import { labelTable } from "../../database/schema"; 3 + import { syncLabelToGitHub } from "../../plugins/github/utils/sync-label-to-github"; 3 4 4 5 async function createLabel( 5 6 name: string, ··· 11 12 .insert(labelTable) 12 13 .values({ name, color, taskId, workspaceId }) 13 14 .returning(); 15 + 16 + if (taskId) { 17 + syncLabelToGitHub(taskId, name, color).catch((error) => { 18 + console.error("Failed to sync label to GitHub:", error); 19 + }); 20 + } 14 21 15 22 return label; 16 23 }
+10 -1
apps/api/src/label/controllers/delete-label.ts
··· 2 2 import { HTTPException } from "hono/http-exception"; 3 3 import db from "../../database"; 4 4 import { labelTable } from "../../database/schema"; 5 + import { removeLabelFromGitHub } from "../../plugins/github/utils/sync-label-to-github"; 5 6 6 7 async function deleteLabel(id: string) { 7 - const label = db.query.labelTable.findFirst({ 8 + const label = await db.query.labelTable.findFirst({ 8 9 where: (label, { eq }) => eq(label.id, id), 9 10 }); 10 11 ··· 18 19 .delete(labelTable) 19 20 .where(eq(labelTable.id, id)) 20 21 .returning(); 22 + 23 + if (deletedLabel?.taskId) { 24 + removeLabelFromGitHub(deletedLabel.taskId, deletedLabel.name).catch( 25 + (error) => { 26 + console.error("Failed to remove label from GitHub:", error); 27 + }, 28 + ); 29 + } 21 30 22 31 return deletedLabel; 23 32 }
+2 -1
apps/api/src/plugins/github/utils/branch-matcher.ts
··· 31 31 .replace("\\{number\\}", "(\\d+)") 32 32 .replace("\\{title\\}", "([a-z0-9-]+)"); 33 33 34 - return new RegExp(`^${regexPattern}$`, "i"); 34 + // Allow optional suffix after the pattern (e.g., lif-3-part-1) 35 + return new RegExp(`^${regexPattern}(?:-.*)?$`, "i"); 35 36 } 36 37 37 38 export function extractTaskNumberFromBranch(
+156
apps/api/src/plugins/github/utils/sync-label-to-github.ts
··· 1 + import { eq } from "drizzle-orm"; 2 + import db from "../../../database"; 3 + import { externalLinkTable } from "../../../database/schema"; 4 + import { getInstallationOctokit } from "./github-app"; 5 + 6 + const namedColorToHex: Record<string, string> = { 7 + red: "EF4444", 8 + orange: "F97316", 9 + amber: "F59E0B", 10 + yellow: "EAB308", 11 + lime: "84CC16", 12 + green: "22C55E", 13 + emerald: "10B981", 14 + teal: "14B8A6", 15 + cyan: "06B6D4", 16 + sky: "0EA5E9", 17 + blue: "3B82F6", 18 + indigo: "6366F1", 19 + violet: "8B5CF6", 20 + purple: "A855F7", 21 + fuchsia: "D946EF", 22 + pink: "EC4899", 23 + rose: "F43F5E", 24 + gray: "6B7280", 25 + slate: "64748B", 26 + zinc: "71717A", 27 + neutral: "737373", 28 + stone: "78716C", 29 + }; 30 + 31 + function toHexColor(color: string): string { 32 + const lower = color.toLowerCase().replace(/^#/, ""); 33 + if (namedColorToHex[lower]) { 34 + return namedColorToHex[lower]; 35 + } 36 + if (/^[0-9a-f]{6}$/i.test(lower)) { 37 + return lower; 38 + } 39 + if (/^[0-9a-f]{3}$/i.test(lower)) { 40 + const [r, g, b] = lower.split(""); 41 + return `${r}${r}${g}${g}${b}${b}`; 42 + } 43 + return "6B7280"; 44 + } 45 + 46 + async function getGitHubContext(taskId: string) { 47 + const externalLink = await db.query.externalLinkTable.findFirst({ 48 + where: eq(externalLinkTable.taskId, taskId), 49 + with: { 50 + integration: true, 51 + }, 52 + }); 53 + 54 + if (!externalLink || externalLink.resourceType !== "issue") { 55 + return null; 56 + } 57 + 58 + const integration = externalLink.integration; 59 + if (!integration || integration.type !== "github") { 60 + return null; 61 + } 62 + 63 + let config: { 64 + repositoryOwner: string; 65 + repositoryName: string; 66 + installationId?: number; 67 + }; 68 + try { 69 + config = JSON.parse(integration.config); 70 + } catch { 71 + return null; 72 + } 73 + 74 + if (!config.installationId) { 75 + return null; 76 + } 77 + 78 + const octokit = await getInstallationOctokit(config.installationId); 79 + if (!octokit) { 80 + return null; 81 + } 82 + 83 + return { 84 + octokit, 85 + owner: config.repositoryOwner, 86 + repo: config.repositoryName, 87 + issueNumber: Number.parseInt(externalLink.externalId, 10), 88 + }; 89 + } 90 + 91 + export async function syncLabelToGitHub( 92 + taskId: string, 93 + labelName: string, 94 + labelColor: string, 95 + ) { 96 + const ctx = await getGitHubContext(taskId); 97 + if (!ctx) return; 98 + 99 + const { octokit, owner, repo, issueNumber } = ctx; 100 + const color = toHexColor(labelColor); 101 + 102 + try { 103 + await octokit.rest.issues.getLabel({ 104 + owner, 105 + repo, 106 + name: labelName, 107 + }); 108 + } catch { 109 + try { 110 + await octokit.rest.issues.createLabel({ 111 + owner, 112 + repo, 113 + name: labelName, 114 + color, 115 + }); 116 + } catch (createError) { 117 + console.error( 118 + `Failed to create label "${labelName}" in GitHub:`, 119 + createError, 120 + ); 121 + return; 122 + } 123 + } 124 + 125 + try { 126 + await octokit.rest.issues.addLabels({ 127 + owner, 128 + repo, 129 + issue_number: issueNumber, 130 + labels: [labelName], 131 + }); 132 + } catch (error) { 133 + console.error(`Failed to add label "${labelName}" to GitHub issue:`, error); 134 + } 135 + } 136 + 137 + export async function removeLabelFromGitHub(taskId: string, labelName: string) { 138 + const ctx = await getGitHubContext(taskId); 139 + if (!ctx) return; 140 + 141 + const { octokit, owner, repo, issueNumber } = ctx; 142 + 143 + try { 144 + await octokit.rest.issues.removeLabel({ 145 + owner, 146 + repo, 147 + issue_number: issueNumber, 148 + name: labelName, 149 + }); 150 + } catch (error) { 151 + console.error( 152 + `Failed to remove label "${labelName}" from GitHub issue:`, 153 + error, 154 + ); 155 + } 156 + }
+33 -1
apps/api/src/plugins/github/webhook-handler.ts
··· 1 1 import { getGithubApp } from "./utils/github-app"; 2 2 import { handleIssueClosed } from "./webhooks/issue-closed"; 3 + import { handleIssueCommentCreated } from "./webhooks/issue-comment-created"; 4 + import { handleIssueLabeled } from "./webhooks/issue-labeled"; 3 5 import { handleIssueOpened } from "./webhooks/issue-opened"; 6 + import { handleLabelCreated } from "./webhooks/label-created"; 4 7 import { handlePullRequestClosed } from "./webhooks/pull-request-closed"; 5 8 import { handlePullRequestOpened } from "./webhooks/pull-request-opened"; 6 9 import { handlePush } from "./webhooks/push"; ··· 20 23 try { 21 24 await githubApp.webhooks.verifyAndReceive({ 22 25 id: deliveryId, 23 - name: eventName as "issues" | "pull_request" | "push", 26 + name: eventName as 27 + | "issues" 28 + | "pull_request" 29 + | "push" 30 + | "label" 31 + | "issue_comment", 24 32 signature, 25 33 payload: body, 26 34 }); ··· 52 60 await handleIssueClosed(payload as Parameters<typeof handleIssueClosed>[0]); 53 61 }); 54 62 63 + githubApp.webhooks.on("issues.labeled", async ({ payload }) => { 64 + await handleIssueLabeled( 65 + payload as Parameters<typeof handleIssueLabeled>[0], 66 + ); 67 + }); 68 + 69 + githubApp.webhooks.on("issues.unlabeled", async ({ payload }) => { 70 + await handleIssueLabeled( 71 + payload as Parameters<typeof handleIssueLabeled>[0], 72 + ); 73 + }); 74 + 55 75 githubApp.webhooks.on("push", async ({ payload }) => { 56 76 await handlePush(payload as Parameters<typeof handlePush>[0]); 57 77 }); ··· 71 91 githubApp.webhooks.on("pull_request.reopened", async ({ payload }) => { 72 92 await handlePullRequestOpened( 73 93 payload as Parameters<typeof handlePullRequestOpened>[0], 94 + ); 95 + }); 96 + 97 + githubApp.webhooks.on("label.created", async ({ payload }) => { 98 + await handleLabelCreated( 99 + payload as Parameters<typeof handleLabelCreated>[0], 100 + ); 101 + }); 102 + 103 + githubApp.webhooks.on("issue_comment.created", async ({ payload }) => { 104 + await handleIssueCommentCreated( 105 + payload as Parameters<typeof handleIssueCommentCreated>[0], 74 106 ); 75 107 }); 76 108
+69
apps/api/src/plugins/github/webhooks/issue-comment-created.ts
··· 1 + import db from "../../../database"; 2 + import { activityTable } from "../../../database/schema"; 3 + import { findExternalLink } from "../services/link-manager"; 4 + import { findIntegrationByRepo } from "../services/task-service"; 5 + 6 + type IssueCommentCreatedPayload = { 7 + action: string; 8 + issue: { 9 + number: number; 10 + }; 11 + comment: { 12 + id: number; 13 + body: string; 14 + html_url: string; 15 + user: { 16 + login: string; 17 + avatar_url: string; 18 + } | null; 19 + created_at: string; 20 + }; 21 + repository: { 22 + owner: { login: string }; 23 + name: string; 24 + }; 25 + }; 26 + 27 + export async function handleIssueCommentCreated( 28 + payload: IssueCommentCreatedPayload, 29 + ) { 30 + const { issue, comment, repository } = payload; 31 + 32 + if (payload.action !== "created") { 33 + return; 34 + } 35 + 36 + const username = comment.user?.login ?? ""; 37 + if (username.endsWith("[bot]")) { 38 + return; 39 + } 40 + 41 + const integration = await findIntegrationByRepo( 42 + repository.owner.login, 43 + repository.name, 44 + ); 45 + 46 + if (!integration) { 47 + return; 48 + } 49 + 50 + const existingLink = await findExternalLink( 51 + integration.id, 52 + "issue", 53 + issue.number.toString(), 54 + ); 55 + 56 + if (!existingLink) { 57 + return; 58 + } 59 + 60 + await db.insert(activityTable).values({ 61 + taskId: existingLink.taskId, 62 + type: "comment", 63 + content: comment.body, 64 + externalUserName: comment.user?.login ?? "Unknown", 65 + externalUserAvatar: comment.user?.avatar_url ?? null, 66 + externalSource: "github", 67 + externalUrl: comment.html_url, 68 + }); 69 + }
+124
apps/api/src/plugins/github/webhooks/issue-labeled.ts
··· 1 + import { eq } from "drizzle-orm"; 2 + import db from "../../../database"; 3 + import { labelTable, projectTable, taskTable } from "../../../database/schema"; 4 + import { findExternalLink } from "../services/link-manager"; 5 + import { findIntegrationByRepo } from "../services/task-service"; 6 + import { 7 + extractIssuePriority, 8 + extractIssueStatus, 9 + } from "../utils/extract-priority"; 10 + 11 + type IssueLabeledPayload = { 12 + action: string; 13 + issue: { 14 + number: number; 15 + labels?: Array<string | { name?: string }>; 16 + }; 17 + label?: { 18 + name: string; 19 + color: string; 20 + }; 21 + repository: { 22 + owner: { login: string }; 23 + name: string; 24 + }; 25 + }; 26 + 27 + export async function handleIssueLabeled(payload: IssueLabeledPayload) { 28 + const { issue, repository, label: addedLabel } = payload; 29 + 30 + const integration = await findIntegrationByRepo( 31 + repository.owner.login, 32 + repository.name, 33 + ); 34 + 35 + if (!integration) { 36 + return; 37 + } 38 + 39 + const existingLink = await findExternalLink( 40 + integration.id, 41 + "issue", 42 + issue.number.toString(), 43 + ); 44 + 45 + if (!existingLink) { 46 + return; 47 + } 48 + 49 + const priority = extractIssuePriority(issue.labels); 50 + const status = extractIssueStatus(issue.labels); 51 + 52 + const updateData: Record<string, unknown> = {}; 53 + 54 + if (priority) { 55 + updateData.priority = priority; 56 + } 57 + 58 + if (status) { 59 + updateData.status = status; 60 + } 61 + 62 + if (Object.keys(updateData).length > 0) { 63 + await db 64 + .update(taskTable) 65 + .set(updateData) 66 + .where(eq(taskTable.id, existingLink.taskId)); 67 + } 68 + 69 + if (!addedLabel) { 70 + return; 71 + } 72 + 73 + const isSystemLabel = 74 + addedLabel.name.startsWith("priority:") || 75 + addedLabel.name.startsWith("status:"); 76 + 77 + if (isSystemLabel) { 78 + return; 79 + } 80 + 81 + if (payload.action === "labeled") { 82 + const task = await db.query.taskTable.findFirst({ 83 + where: eq(taskTable.id, existingLink.taskId), 84 + with: { 85 + project: true, 86 + }, 87 + }); 88 + 89 + if (task?.project?.workspaceId) { 90 + const existingLabel = await db.query.labelTable.findFirst({ 91 + where: (table, { and, eq }) => 92 + and( 93 + eq(table.workspaceId, task.project.workspaceId), 94 + eq(table.name, addedLabel.name), 95 + eq(table.taskId, task.id), 96 + ), 97 + }); 98 + 99 + if (!existingLabel) { 100 + const color = addedLabel.color ? `#${addedLabel.color}` : "#6B7280"; 101 + await db.insert(labelTable).values({ 102 + name: addedLabel.name, 103 + color, 104 + taskId: task.id, 105 + workspaceId: task.project.workspaceId, 106 + }); 107 + } 108 + } 109 + } 110 + 111 + if (payload.action === "unlabeled") { 112 + const labelsToDelete = await db.query.labelTable.findMany({ 113 + where: (table, { and, eq }) => 114 + and( 115 + eq(table.taskId, existingLink.taskId), 116 + eq(table.name, addedLabel.name), 117 + ), 118 + }); 119 + 120 + for (const label of labelsToDelete) { 121 + await db.delete(labelTable).where(eq(labelTable.id, label.id)); 122 + } 123 + } 124 + }
+23 -2
apps/api/src/plugins/github/webhooks/issue-opened.ts
··· 1 + import { eq } from "drizzle-orm"; 1 2 import db from "../../../database"; 2 - import { taskTable } from "../../../database/schema"; 3 + import { projectTable, taskTable } from "../../../database/schema"; 3 4 import getNextTaskNumber from "../../../task/controllers/get-next-task-number"; 4 5 import type { GitHubConfig } from "../config"; 5 6 import { createExternalLink, findExternalLink } from "../services/link-manager"; ··· 104 105 }, 105 106 }); 106 107 108 + const project = await db.query.projectTable.findFirst({ 109 + where: eq(projectTable.id, projectId), 110 + }); 111 + 112 + if (!project) { 113 + console.error("Project not found for task linking comment"); 114 + return; 115 + } 116 + 117 + const clientUrl = process.env.KANEO_CLIENT_URL || "http://localhost:5173"; 118 + const taskUrl = `${clientUrl}/dashboard/workspace/${project.workspaceId}/project/${projectId}/task/${createdTask.id}`; 119 + const taskIdentifier = `${project.slug.toUpperCase()}-${createdTask.number}`; 120 + 107 121 try { 108 122 let installationId = config.installationId; 109 123 if (!installationId) { ··· 141 155 labelsToAdd, 142 156 ); 143 157 } 158 + 159 + await octokit.rest.issues.createComment({ 160 + owner: repository.owner.login, 161 + repo: repository.name, 162 + issue_number: issue.number, 163 + body: `[${taskIdentifier}](${taskUrl})`, 164 + }); 144 165 } catch (error) { 145 - console.error("Failed to add labels to GitHub issue:", error); 166 + console.error("Failed to process GitHub issue:", error); 146 167 } 147 168 }
+58
apps/api/src/plugins/github/webhooks/label-created.ts
··· 1 + import { eq } from "drizzle-orm"; 2 + import db from "../../../database"; 3 + import { labelTable, projectTable } from "../../../database/schema"; 4 + import { findIntegrationByRepo } from "../services/task-service"; 5 + 6 + type LabelCreatedPayload = { 7 + action: string; 8 + label: { 9 + name: string; 10 + color: string; 11 + description?: string | null; 12 + }; 13 + repository: { 14 + owner: { login: string }; 15 + name: string; 16 + }; 17 + }; 18 + 19 + export async function handleLabelCreated(payload: LabelCreatedPayload) { 20 + const { repository, label } = payload; 21 + 22 + const integration = await findIntegrationByRepo( 23 + repository.owner.login, 24 + repository.name, 25 + ); 26 + 27 + if (!integration?.project) { 28 + return; 29 + } 30 + 31 + const project = await db.query.projectTable.findFirst({ 32 + where: eq(projectTable.id, integration.project.id), 33 + }); 34 + 35 + if (!project?.workspaceId) { 36 + return; 37 + } 38 + 39 + const labelExists = await db.query.labelTable.findFirst({ 40 + where: (table, { and, eq }) => 41 + and( 42 + eq(table.workspaceId, project.workspaceId), 43 + eq(table.name, label.name), 44 + ), 45 + }); 46 + 47 + if (labelExists) { 48 + return; 49 + } 50 + 51 + const color = label.color ? `#${label.color}` : "#6B7280"; 52 + 53 + await db.insert(labelTable).values({ 54 + name: label.name, 55 + color, 56 + workspaceId: project.workspaceId, 57 + }); 58 + }
+17 -2
apps/api/src/plugins/github/webhooks/pull-request-closed.ts
··· 74 74 }); 75 75 76 76 if (pull_request.merged) { 77 - const targetStatus = config.statusTransitions?.onPRMerge || "done"; 78 - await updateTaskStatus(task.id, targetStatus); 77 + const allTaskPRs = await db.query.externalLinkTable.findMany({ 78 + where: and( 79 + eq(externalLinkTable.taskId, task.id), 80 + eq(externalLinkTable.resourceType, "pull_request"), 81 + ), 82 + }); 83 + 84 + const hasOpenPRs = allTaskPRs.some((pr) => { 85 + if (pr.id === externalLink.id) return false; 86 + const metadata = pr.metadata ? JSON.parse(pr.metadata) : {}; 87 + return metadata.state === "open"; 88 + }); 89 + 90 + if (!hasOpenPRs) { 91 + const targetStatus = config.statusTransitions?.onPRMerge || "done"; 92 + await updateTaskStatus(task.id, targetStatus); 93 + } 79 94 } 80 95 }
+5 -1
apps/api/src/schemas.ts
··· 63 63 "create", 64 64 ] as const), 65 65 createdAt: v.date(), 66 - userId: v.string(), 66 + userId: v.nullable(v.string()), 67 67 content: v.nullable(v.string()), 68 + externalUserName: v.nullable(v.string()), 69 + externalUserAvatar: v.nullable(v.string()), 70 + externalSource: v.nullable(v.string()), 71 + externalUrl: v.nullable(v.string()), 68 72 }); 69 73 70 74 export const timeEntrySchema = v.object({
+50 -5
apps/web/src/components/activity/comment-card.tsx
··· 16 16 Bold, 17 17 Check, 18 18 Code, 19 + ExternalLink, 20 + Github, 19 21 Heading1, 20 22 Heading2, 21 23 Heading3, ··· 60 62 image?: string | null; 61 63 } | null; 62 64 createdAt: string; 65 + externalSource?: string | null; 66 + externalUrl?: string | null; 63 67 }; 64 68 65 69 export default function CommentCard({ ··· 68 72 content, 69 73 user, 70 74 createdAt, 75 + externalSource, 76 + externalUrl, 71 77 }: CommentCardProps) { 72 78 const { theme } = useUserPreferencesStore(); 73 79 const { user: currentUser } = useAuth(); ··· 211 217 editorElement?.removeEventListener("keydown", handleKeyDown); 212 218 }; 213 219 }, [editor, isEditing, handleSave, handleCancel]); 220 + const isFromGitHub = externalSource === "github"; 221 + const githubProfileUrl = 222 + isFromGitHub && user?.name ? `https://github.com/${user.name}` : null; 223 + const commentUrl = externalUrl || null; 214 224 215 225 return ( 216 226 <div className="w-full group"> ··· 229 239 </span> 230 240 </div> 231 241 </HoverCardTrigger> 232 - <HoverCardContent className="w-52 p-3"> 242 + <HoverCardContent className="w-64 p-3"> 233 243 <div className="flex items-center gap-3"> 234 - <Avatar className="h-8 w-8"> 244 + <Avatar className="h-10 w-10"> 235 245 <AvatarImage src={user?.image ?? ""} alt={user?.name || ""} /> 236 246 <AvatarFallback className="text-xs font-medium bg-muted"> 237 247 {user?.name?.charAt(0).toUpperCase()} ··· 241 251 <p className="text-sm font-medium text-foreground leading-none"> 242 252 {user?.name} 243 253 </p> 244 - <p className="text-xs text-muted-foreground mt-1"> 245 - {user?.email} 246 - </p> 254 + {user?.email && ( 255 + <p className="text-xs text-muted-foreground mt-1"> 256 + {user.email} 257 + </p> 258 + )} 259 + {isFromGitHub && ( 260 + <div className="flex items-center gap-1 mt-1.5"> 261 + <Github className="size-3 text-muted-foreground" /> 262 + <span className="text-xs text-muted-foreground"> 263 + GitHub 264 + </span> 265 + </div> 266 + )} 247 267 </div> 248 268 </div> 269 + {githubProfileUrl && ( 270 + <a 271 + href={githubProfileUrl} 272 + target="_blank" 273 + rel="noopener noreferrer" 274 + className="flex items-center gap-1.5 mt-3 pt-3 border-t border-border text-xs text-muted-foreground hover:text-foreground transition-colors" 275 + > 276 + <ExternalLink className="size-3" /> 277 + View GitHub Profile 278 + </a> 279 + )} 249 280 </HoverCardContent> 250 281 </HoverCard> 251 282 <span className="text-xs text-muted-foreground/60"> 252 283 {formatDistanceToNow(createdAt, { addSuffix: true })} 253 284 </span> 285 + {commentUrl && ( 286 + <> 287 + <span className="text-xs text-muted-foreground/40">·</span> 288 + <a 289 + href={commentUrl} 290 + target="_blank" 291 + rel="noopener noreferrer" 292 + className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1" 293 + > 294 + <Github className="size-3" /> 295 + commented on GitHub 296 + </a> 297 + </> 298 + )} 254 299 {canEdit && !isEditing && ( 255 300 <TooltipProvider> 256 301 <Tooltip>
+26 -11
apps/web/src/components/activity/index.tsx
··· 40 40 content: string | null; 41 41 id: string; 42 42 createdAt: string; 43 - userId: string; 43 + userId: string | null; 44 44 taskId: string; 45 + externalUserName?: string | null; 46 + externalUserAvatar?: string | null; 47 + externalSource?: string | null; 48 + externalUrl?: string | null; 45 49 }; 46 50 isLast?: boolean; 47 51 }) { ··· 51 55 workspaceId: workspace?.id, 52 56 }); 53 57 54 - const user = workspaceUsers?.find( 55 - (user) => user.user?.id === activity.userId, 56 - ); 58 + const user = activity.userId 59 + ? workspaceUsers?.find((user) => user.user?.id === activity.userId) 60 + : null; 57 61 58 - console.log(activity); 62 + const isExternalComment = Boolean(activity.externalSource); 59 63 60 64 if (activity.type === "comment" && activity.content) { 65 + const commentUser = isExternalComment 66 + ? { 67 + id: undefined, 68 + name: activity.externalUserName ?? "GitHub User", 69 + email: undefined, 70 + image: activity.externalUserAvatar ?? undefined, 71 + } 72 + : { 73 + id: user?.user?.id, 74 + name: user?.user?.name, 75 + email: user?.user?.email, 76 + image: user?.user?.image, 77 + }; 78 + 61 79 return ( 62 80 <div className="relative flex gap-3 py-2 last:pb-0"> 63 81 {!isLast && ( ··· 80 98 commentId={activity.id} 81 99 taskId={activity.taskId} 82 100 content={activity.content} 83 - user={{ 84 - id: user?.user?.id, 85 - name: user?.user?.name, 86 - email: user?.user?.email, 87 - image: user?.user?.image, 88 - }} 101 + user={commentUser} 89 102 createdAt={activity.createdAt} 103 + externalSource={activity.externalSource} 104 + externalUrl={activity.externalUrl} 90 105 /> 91 106 </div> 92 107 </div>
+137 -19
apps/web/src/components/kanban-board/task-card.tsx
··· 6 6 Calendar, 7 7 CalendarClock, 8 8 CalendarX, 9 + GitMerge, 9 10 GitPullRequest, 10 11 } from "lucide-react"; 11 12 import { type CSSProperties, useMemo, useState } from "react"; ··· 21 22 AlertDialogTitle, 22 23 } from "@/components/ui/alert-dialog"; 23 24 import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 25 + import { 26 + HoverCard, 27 + HoverCardContent, 28 + HoverCardTrigger, 29 + } from "@/components/ui/hover-card"; 24 30 import { useDeleteTask } from "@/hooks/mutations/task/use-delete-task"; 25 31 import useExternalLinks from "@/hooks/queries/external-link/use-external-links"; 26 32 import useActiveWorkspace from "@/hooks/queries/workspace/use-active-workspace"; ··· 62 68 const { mutateAsync: deleteTask } = useDeleteTask(); 63 69 const { data: externalLinks } = useExternalLinks(task.id); 64 70 65 - const activePullRequest = useMemo(() => { 66 - if (!externalLinks) return null; 67 - return externalLinks.find( 68 - (link) => 69 - link.resourceType === "pull_request" && link.metadata?.merged !== true, 70 - ); 71 + const pullRequests = useMemo(() => { 72 + if (!externalLinks) return []; 73 + return externalLinks.filter((link) => link.resourceType === "pull_request"); 71 74 }, [externalLinks]); 75 + 76 + const getPRInfo = (pr: (typeof pullRequests)[number]) => { 77 + const isMerged = pr.metadata?.merged === true; 78 + const isDraft = pr.metadata?.draft === true; 79 + 80 + if (isMerged) { 81 + return { 82 + icon: <GitMerge className="h-3 w-3 text-purple-400" />, 83 + status: "Merged", 84 + statusClass: "text-purple-400", 85 + }; 86 + } 87 + 88 + if (isDraft) { 89 + return { 90 + icon: <GitPullRequest className="h-3 w-3 text-muted-foreground" />, 91 + status: "Draft", 92 + statusClass: "text-muted-foreground", 93 + }; 94 + } 95 + 96 + return { 97 + icon: <GitPullRequest className="h-3 w-3 text-green-400" />, 98 + status: "Open", 99 + statusClass: "text-green-400", 100 + }; 101 + }; 72 102 73 103 const style: CSSProperties = { 74 104 transform: CSS.Transform.toString(transform), ··· 210 240 </div> 211 241 )} 212 242 213 - {activePullRequest && ( 214 - <button 215 - type="button" 216 - onClick={(e) => { 217 - e.stopPropagation(); 218 - window.open(activePullRequest.url, "_blank"); 219 - }} 220 - className="inline-flex items-center gap-1 px-2 py-1 rounded border border-violet-200 bg-violet-50 text-[10px] font-medium text-violet-700 hover:bg-violet-100 hover:border-violet-300 transition-colors dark:bg-violet-950/50 dark:border-violet-800 dark:text-violet-300 dark:hover:bg-violet-900/50" 221 - title={activePullRequest.title || "Open Pull Request"} 222 - > 223 - <GitPullRequest className="w-3 h-3" /> 224 - <span>#{activePullRequest.externalId}</span> 225 - </button> 243 + {pullRequests.length === 1 && ( 244 + <HoverCard openDelay={200} closeDelay={100}> 245 + <HoverCardTrigger asChild> 246 + <button 247 + type="button" 248 + onClick={(e) => { 249 + e.stopPropagation(); 250 + window.open(pullRequests[0].url, "_blank"); 251 + }} 252 + className="inline-flex items-center gap-1.5 px-2 py-1 rounded border border-border bg-sidebar text-[10px] font-medium text-muted-foreground" 253 + > 254 + {getPRInfo(pullRequests[0]).icon} 255 + <span>#{pullRequests[0].externalId}</span> 256 + </button> 257 + </HoverCardTrigger> 258 + <HoverCardContent 259 + className="w-72 p-3" 260 + side="bottom" 261 + onClick={(e) => e.stopPropagation()} 262 + > 263 + <div className="space-y-2"> 264 + <div className="flex items-center gap-2 text-xs text-muted-foreground"> 265 + {getPRInfo(pullRequests[0]).icon} 266 + <span>{getPRInfo(pullRequests[0]).status}</span> 267 + <span className="text-muted-foreground/50">•</span> 268 + <span>#{pullRequests[0].externalId}</span> 269 + </div> 270 + <p className="text-sm font-medium leading-snug"> 271 + {pullRequests[0].title || "Pull Request"} 272 + </p> 273 + </div> 274 + </HoverCardContent> 275 + </HoverCard> 226 276 )} 277 + 278 + {pullRequests.length > 1 && 279 + (() => { 280 + const hasOpen = pullRequests.some( 281 + (pr) => !pr.metadata?.merged && !pr.metadata?.draft, 282 + ); 283 + const allMerged = pullRequests.every( 284 + (pr) => pr.metadata?.merged, 285 + ); 286 + const iconColor = allMerged 287 + ? "text-purple-400" 288 + : hasOpen 289 + ? "text-green-400" 290 + : "text-muted-foreground"; 291 + 292 + return ( 293 + <HoverCard openDelay={200} closeDelay={100}> 294 + <HoverCardTrigger asChild> 295 + <button 296 + type="button" 297 + onClick={(e) => e.stopPropagation()} 298 + className="inline-flex items-center gap-1.5 px-2 py-1 rounded border border-border bg-sidebar text-[10px] font-medium text-muted-foreground" 299 + > 300 + <GitPullRequest className={`h-3 w-3 ${iconColor}`} /> 301 + <span>{pullRequests.length} PRs</span> 302 + </button> 303 + </HoverCardTrigger> 304 + <HoverCardContent 305 + className="w-auto min-w-56 max-w-96 p-1" 306 + side="bottom" 307 + onClick={(e) => e.stopPropagation()} 308 + > 309 + {pullRequests.map((pr, index) => { 310 + const prInfo = getPRInfo(pr); 311 + const repoMatch = pr.url.match( 312 + /github\.com\/([^/]+\/[^/]+)\/pull/, 313 + ); 314 + const repoName = repoMatch ? repoMatch[1] : null; 315 + return ( 316 + <div key={pr.id}> 317 + {index > 0 && ( 318 + <hr className="border-border my-1" /> 319 + )} 320 + <button 321 + type="button" 322 + onClick={() => window.open(pr.url, "_blank")} 323 + className="w-full px-2 py-1.5 text-left hover:bg-muted/50 rounded transition-colors" 324 + > 325 + <div className="flex items-center gap-1.5 text-[11px] text-muted-foreground"> 326 + {prInfo.icon} 327 + <span> 328 + {repoName}#{pr.externalId} 329 + </span> 330 + </div> 331 + <p className="text-xs leading-tight line-clamp-2 mt-0.5"> 332 + {pr.title || "Pull Request"} 333 + </p> 334 + <span className="text-[10px] text-muted-foreground"> 335 + {prInfo.status} 336 + </span> 337 + </button> 338 + </div> 339 + ); 340 + })} 341 + </HoverCardContent> 342 + </HoverCard> 343 + ); 344 + })()} 227 345 </div> 228 346 </div> 229 347 </ContextMenuTrigger>
+55
apps/web/src/components/ui/accordion.tsx
··· 1 + import * as AccordionPrimitive from "@radix-ui/react-accordion"; 2 + import { ChevronDown } from "lucide-react"; 3 + import * as React from "react"; 4 + 5 + import { cn } from "@/lib/cn"; 6 + 7 + const Accordion = AccordionPrimitive.Root; 8 + 9 + const AccordionItem = React.forwardRef< 10 + React.ElementRef<typeof AccordionPrimitive.Item>, 11 + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> 12 + >(({ className, ...props }, ref) => ( 13 + <AccordionPrimitive.Item 14 + ref={ref} 15 + className={cn("border-b", className)} 16 + {...props} 17 + /> 18 + )); 19 + AccordionItem.displayName = "AccordionItem"; 20 + 21 + const AccordionTrigger = React.forwardRef< 22 + React.ElementRef<typeof AccordionPrimitive.Trigger>, 23 + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> 24 + >(({ className, children, ...props }, ref) => ( 25 + <AccordionPrimitive.Header className="flex"> 26 + <AccordionPrimitive.Trigger 27 + ref={ref} 28 + className={cn( 29 + "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180", 30 + className, 31 + )} 32 + {...props} 33 + > 34 + {children} 35 + <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" /> 36 + </AccordionPrimitive.Trigger> 37 + </AccordionPrimitive.Header> 38 + )); 39 + AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; 40 + 41 + const AccordionContent = React.forwardRef< 42 + React.ElementRef<typeof AccordionPrimitive.Content>, 43 + React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> 44 + >(({ className, children, ...props }, ref) => ( 45 + <AccordionPrimitive.Content 46 + ref={ref} 47 + className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" 48 + {...props} 49 + > 50 + <div className={cn("pb-4 pt-0", className)}>{children}</div> 51 + </AccordionPrimitive.Content> 52 + )); 53 + AccordionContent.displayName = AccordionPrimitive.Content.displayName; 54 + 55 + export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
+9 -69
apps/web/src/routes/_layout/_authenticated/dashboard/workspace/$workspaceId/project/$projectId/task/$taskId_.tsx
··· 4 4 Calendar, 5 5 CalendarClock, 6 6 CalendarX, 7 - CircleDot, 8 7 Copy, 9 8 GitBranch, 10 - GitPullRequest, 11 9 Plus, 12 10 } from "lucide-react"; 13 11 import { toast } from "sonner"; 14 12 import Activity from "@/components/activity"; 15 13 import CommentInput from "@/components/activity/comment-input"; 16 14 import TaskLayout from "@/components/common/task-layout"; 15 + import { ExternalLinksAccordion } from "@/components/external-links/external-links-accordion"; 17 16 import PageTitle from "@/components/page-title"; 18 17 import useAuth from "@/components/providers/auth-provider/hooks/use-auth"; 19 18 import TaskAssigneePopover from "@/components/task/task-assignee-popover"; ··· 327 326 )} 328 327 </div> 329 328 </div> 330 - {!isLoadingExternalLinks && ( 331 - <div className="flex flex-col gap-1"> 332 - <div className="flex items-center justify-between"> 333 - <span className="text-xs font-medium text-muted-foreground pl-2"> 334 - External Links 335 - </span> 336 - </div> 337 - <div className="flex flex-col gap-1.5 px-2"> 338 - {externalLinks.length === 0 ? ( 339 - <span className="text-xs text-muted-foreground/70 py-1"> 340 - No linked resources 341 - </span> 342 - ) : ( 343 - externalLinks.map((link) => { 344 - const isMerged = link.metadata?.merged === true; 345 - const isPR = link.resourceType === "pull_request"; 346 - const isBranch = link.resourceType === "branch"; 347 - const isIssue = link.resourceType === "issue"; 348 - 349 - const getIcon = () => { 350 - if (isPR) 351 - return ( 352 - <GitPullRequest 353 - className={`size-3.5 flex-shrink-0 ${isMerged ? "text-emerald-500" : "text-violet-500"}`} 354 - /> 355 - ); 356 - if (isBranch) 357 - return ( 358 - <GitBranch className="size-3.5 flex-shrink-0 text-sky-500" /> 359 - ); 360 - return ( 361 - <CircleDot className="size-3.5 flex-shrink-0 text-muted-foreground" /> 362 - ); 363 - }; 364 - 365 - const getLabel = () => { 366 - if (link.title) return link.title; 367 - if (isPR) return `PR #${link.externalId}`; 368 - if (isIssue) return `Issue #${link.externalId}`; 369 - if (isBranch) return link.externalId; 370 - return link.externalId; 371 - }; 372 - 373 - return ( 374 - <a 375 - key={link.id} 376 - href={link.url} 377 - target="_blank" 378 - rel="noopener noreferrer" 379 - className="group flex items-center gap-2 py-1.5 px-2 -mx-2 rounded-md hover:bg-accent/50 transition-colors" 380 - > 381 - {getIcon()} 382 - <span className="text-xs truncate flex-1 text-foreground/80 group-hover:text-foreground"> 383 - {getLabel()} 384 - </span> 385 - {isPR && isMerged && ( 386 - <span className="text-[10px] font-medium text-emerald-500/80 bg-emerald-500/10 px-1.5 py-0.5 rounded"> 387 - merged 388 - </span> 389 - )} 390 - </a> 391 - ); 392 - }) 393 - )} 394 - </div> 395 - </div> 396 - )} 397 329 </div> 398 330 </div> 399 331 } ··· 408 340 </p> 409 341 <TaskTitle taskId={taskId} /> 410 342 <TaskDescription taskId={taskId} /> 343 + {!isLoadingExternalLinks && externalLinks.length > 0 && ( 344 + <div className="mt-4"> 345 + <ExternalLinksAccordion 346 + externalLinks={externalLinks} 347 + isLoading={isLoadingExternalLinks} 348 + /> 349 + </div> 350 + )} 411 351 <span className="text-sm font-medium text-muted-foreground h-[1px] bg-border w-full" /> 412 352 <div className="flex flex-col gap-4 pt-8"> 413 353 <h1 className="text-md font-semibold">Activity</h1>
+3 -3
pnpm-lock.yaml
··· 71 71 version: 6.0.0 72 72 better-auth: 73 73 specifier: ^1.4.7 74 - version: 1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(@babel/core@7.28.5)(babel-plugin-react-compiler@19.0.0-beta-ebf51a3-20250411)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) 74 + version: 1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) 75 75 croner: 76 76 specifier: ^9.1.0 77 77 version: 9.1.0 ··· 313 313 version: 19.0.0-beta-ebf51a3-20250411 314 314 better-auth: 315 315 specifier: ^1.4.5 316 - version: 1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(@babel/core@7.28.5)(babel-plugin-react-compiler@19.0.0-beta-ebf51a3-20250411)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) 316 + version: 1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) 317 317 class-variance-authority: 318 318 specifier: ^0.7.1 319 319 version: 0.7.1 ··· 9825 9825 9826 9826 before-after-hook@4.0.0: {} 9827 9827 9828 - better-auth@1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(@babel/core@7.28.5)(babel-plugin-react-compiler@19.0.0-beta-ebf51a3-20250411)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10): 9828 + better-auth@1.4.7(drizzle-kit@0.31.8)(drizzle-orm@0.41.0(@types/pg@8.16.0)(kysely@0.28.9)(pg@8.16.3))(next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(pg@8.16.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10): 9829 9829 dependencies: 9830 9830 '@better-auth/core': 1.4.7(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.5(zod@4.2.0))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0) 9831 9831 '@better-auth/telemetry': 1.4.7(@better-auth/core@1.4.7(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.5(zod@4.2.0))(jose@6.1.3)(kysely@0.28.9)(nanostores@1.1.0))