Exosphere is a set of small, modular, self-hostable community tools built on the AT Protocol. app.exosphere.site
7
fork

Configure Feed

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

fix: various fixes

Hugo c81e26d1 5198e52c

+1469 -22
+1 -1
CLAUDE.md
··· 39 39 40 40 ### Lexicons 41 41 42 - The project lexicons are located in `../landing` and hosted on tnagled: 42 + The project lexicons are located in `../landing` and hosted on tangled: 43 43 https://tangled.org/exosphere.site/landing. 44 44 45 45 ## TS/TSX coding style
+1
Dockerfile
··· 10 10 COPY packages/indexer/package.json packages/indexer/package.json 11 11 COPY packages/feeds/package.json packages/feeds/package.json 12 12 COPY packages/feature-requests/package.json packages/feature-requests/package.json 13 + COPY packages/kanban/package.json packages/kanban/package.json 13 14 COPY packages/mcp/package.json packages/mcp/package.json 14 15 COPY packages/app/package.json packages/app/package.json 15 16 RUN bun install --frozen-lockfile --ignore-scripts
+5
drizzle/0003_soft_tigra.sql
··· 1 + CREATE TABLE `sphere_entry_counter` ( 2 + `sphere_id` text PRIMARY KEY NOT NULL, 3 + `last_number` integer DEFAULT 0 NOT NULL, 4 + FOREIGN KEY (`sphere_id`) REFERENCES `spheres`(`id`) ON UPDATE no action ON DELETE no action 5 + );
+1253
drizzle/meta/0003_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "496173cb-98e0-46b7-baaf-47929d4cb526", 5 + "prevId": "0e06a92f-2199-4052-971b-1fc3be86eb03", 6 + "tables": { 7 + "oauth_sessions": { 8 + "name": "oauth_sessions", 9 + "columns": { 10 + "key": { 11 + "name": "key", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "session": { 18 + "name": "session", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "created_at": { 25 + "name": "created_at", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false, 30 + "default": "(datetime('now'))" 31 + }, 32 + "updated_at": { 33 + "name": "updated_at", 34 + "type": "text", 35 + "primaryKey": false, 36 + "notNull": true, 37 + "autoincrement": false, 38 + "default": "(datetime('now'))" 39 + } 40 + }, 41 + "indexes": {}, 42 + "foreignKeys": {}, 43 + "compositePrimaryKeys": {}, 44 + "uniqueConstraints": {}, 45 + "checkConstraints": {} 46 + }, 47 + "oauth_states": { 48 + "name": "oauth_states", 49 + "columns": { 50 + "key": { 51 + "name": "key", 52 + "type": "text", 53 + "primaryKey": true, 54 + "notNull": true, 55 + "autoincrement": false 56 + }, 57 + "state": { 58 + "name": "state", 59 + "type": "text", 60 + "primaryKey": false, 61 + "notNull": true, 62 + "autoincrement": false 63 + }, 64 + "created_at": { 65 + "name": "created_at", 66 + "type": "text", 67 + "primaryKey": false, 68 + "notNull": true, 69 + "autoincrement": false, 70 + "default": "(datetime('now'))" 71 + } 72 + }, 73 + "indexes": {}, 74 + "foreignKeys": {}, 75 + "compositePrimaryKeys": {}, 76 + "uniqueConstraints": {}, 77 + "checkConstraints": {} 78 + }, 79 + "indexer_cursor": { 80 + "name": "indexer_cursor", 81 + "columns": { 82 + "id": { 83 + "name": "id", 84 + "type": "text", 85 + "primaryKey": true, 86 + "notNull": true, 87 + "autoincrement": false, 88 + "default": "'jetstream'" 89 + }, 90 + "cursor": { 91 + "name": "cursor", 92 + "type": "integer", 93 + "primaryKey": false, 94 + "notNull": true, 95 + "autoincrement": false 96 + }, 97 + "updated_at": { 98 + "name": "updated_at", 99 + "type": "text", 100 + "primaryKey": false, 101 + "notNull": true, 102 + "autoincrement": false, 103 + "default": "(datetime('now'))" 104 + } 105 + }, 106 + "indexes": {}, 107 + "foreignKeys": {}, 108 + "compositePrimaryKeys": {}, 109 + "uniqueConstraints": {}, 110 + "checkConstraints": {} 111 + }, 112 + "sphere_entry_counter": { 113 + "name": "sphere_entry_counter", 114 + "columns": { 115 + "sphere_id": { 116 + "name": "sphere_id", 117 + "type": "text", 118 + "primaryKey": true, 119 + "notNull": true, 120 + "autoincrement": false 121 + }, 122 + "last_number": { 123 + "name": "last_number", 124 + "type": "integer", 125 + "primaryKey": false, 126 + "notNull": true, 127 + "autoincrement": false, 128 + "default": 0 129 + } 130 + }, 131 + "indexes": {}, 132 + "foreignKeys": { 133 + "sphere_entry_counter_sphere_id_spheres_id_fk": { 134 + "name": "sphere_entry_counter_sphere_id_spheres_id_fk", 135 + "tableFrom": "sphere_entry_counter", 136 + "tableTo": "spheres", 137 + "columnsFrom": ["sphere_id"], 138 + "columnsTo": ["id"], 139 + "onDelete": "no action", 140 + "onUpdate": "no action" 141 + } 142 + }, 143 + "compositePrimaryKeys": {}, 144 + "uniqueConstraints": {}, 145 + "checkConstraints": {} 146 + }, 147 + "sphere_members": { 148 + "name": "sphere_members", 149 + "columns": { 150 + "sphere_id": { 151 + "name": "sphere_id", 152 + "type": "text", 153 + "primaryKey": false, 154 + "notNull": true, 155 + "autoincrement": false 156 + }, 157 + "did": { 158 + "name": "did", 159 + "type": "text", 160 + "primaryKey": false, 161 + "notNull": true, 162 + "autoincrement": false 163 + }, 164 + "role": { 165 + "name": "role", 166 + "type": "text", 167 + "primaryKey": false, 168 + "notNull": true, 169 + "autoincrement": false, 170 + "default": "'member'" 171 + }, 172 + "status": { 173 + "name": "status", 174 + "type": "text", 175 + "primaryKey": false, 176 + "notNull": true, 177 + "autoincrement": false, 178 + "default": "'invited'" 179 + }, 180 + "invited_by": { 181 + "name": "invited_by", 182 + "type": "text", 183 + "primaryKey": false, 184 + "notNull": false, 185 + "autoincrement": false 186 + }, 187 + "pds_uri": { 188 + "name": "pds_uri", 189 + "type": "text", 190 + "primaryKey": false, 191 + "notNull": false, 192 + "autoincrement": false 193 + }, 194 + "approval_pds_uri": { 195 + "name": "approval_pds_uri", 196 + "type": "text", 197 + "primaryKey": false, 198 + "notNull": false, 199 + "autoincrement": false 200 + }, 201 + "created_at": { 202 + "name": "created_at", 203 + "type": "text", 204 + "primaryKey": false, 205 + "notNull": true, 206 + "autoincrement": false, 207 + "default": "(datetime('now'))" 208 + } 209 + }, 210 + "indexes": { 211 + "idx_sphere_members_did": { 212 + "name": "idx_sphere_members_did", 213 + "columns": ["did"], 214 + "isUnique": false 215 + } 216 + }, 217 + "foreignKeys": { 218 + "sphere_members_sphere_id_spheres_id_fk": { 219 + "name": "sphere_members_sphere_id_spheres_id_fk", 220 + "tableFrom": "sphere_members", 221 + "tableTo": "spheres", 222 + "columnsFrom": ["sphere_id"], 223 + "columnsTo": ["id"], 224 + "onDelete": "no action", 225 + "onUpdate": "no action" 226 + } 227 + }, 228 + "compositePrimaryKeys": { 229 + "sphere_members_sphere_id_did_pk": { 230 + "columns": ["sphere_id", "did"], 231 + "name": "sphere_members_sphere_id_did_pk" 232 + } 233 + }, 234 + "uniqueConstraints": {}, 235 + "checkConstraints": {} 236 + }, 237 + "sphere_modules": { 238 + "name": "sphere_modules", 239 + "columns": { 240 + "sphere_id": { 241 + "name": "sphere_id", 242 + "type": "text", 243 + "primaryKey": false, 244 + "notNull": true, 245 + "autoincrement": false 246 + }, 247 + "module_name": { 248 + "name": "module_name", 249 + "type": "text", 250 + "primaryKey": false, 251 + "notNull": true, 252 + "autoincrement": false 253 + }, 254 + "enabled_at": { 255 + "name": "enabled_at", 256 + "type": "text", 257 + "primaryKey": false, 258 + "notNull": true, 259 + "autoincrement": false, 260 + "default": "(datetime('now'))" 261 + } 262 + }, 263 + "indexes": {}, 264 + "foreignKeys": { 265 + "sphere_modules_sphere_id_spheres_id_fk": { 266 + "name": "sphere_modules_sphere_id_spheres_id_fk", 267 + "tableFrom": "sphere_modules", 268 + "tableTo": "spheres", 269 + "columnsFrom": ["sphere_id"], 270 + "columnsTo": ["id"], 271 + "onDelete": "no action", 272 + "onUpdate": "no action" 273 + } 274 + }, 275 + "compositePrimaryKeys": { 276 + "sphere_modules_sphere_id_module_name_pk": { 277 + "columns": ["sphere_id", "module_name"], 278 + "name": "sphere_modules_sphere_id_module_name_pk" 279 + } 280 + }, 281 + "uniqueConstraints": {}, 282 + "checkConstraints": {} 283 + }, 284 + "sphere_permissions": { 285 + "name": "sphere_permissions", 286 + "columns": { 287 + "sphere_id": { 288 + "name": "sphere_id", 289 + "type": "text", 290 + "primaryKey": false, 291 + "notNull": true, 292 + "autoincrement": false 293 + }, 294 + "action_key": { 295 + "name": "action_key", 296 + "type": "text", 297 + "primaryKey": false, 298 + "notNull": true, 299 + "autoincrement": false 300 + }, 301 + "min_role": { 302 + "name": "min_role", 303 + "type": "text", 304 + "primaryKey": false, 305 + "notNull": true, 306 + "autoincrement": false 307 + }, 308 + "updated_at": { 309 + "name": "updated_at", 310 + "type": "text", 311 + "primaryKey": false, 312 + "notNull": true, 313 + "autoincrement": false, 314 + "default": "(datetime('now'))" 315 + } 316 + }, 317 + "indexes": {}, 318 + "foreignKeys": { 319 + "sphere_permissions_sphere_id_spheres_id_fk": { 320 + "name": "sphere_permissions_sphere_id_spheres_id_fk", 321 + "tableFrom": "sphere_permissions", 322 + "tableTo": "spheres", 323 + "columnsFrom": ["sphere_id"], 324 + "columnsTo": ["id"], 325 + "onDelete": "no action", 326 + "onUpdate": "no action" 327 + } 328 + }, 329 + "compositePrimaryKeys": { 330 + "sphere_permissions_sphere_id_action_key_pk": { 331 + "columns": ["sphere_id", "action_key"], 332 + "name": "sphere_permissions_sphere_id_action_key_pk" 333 + } 334 + }, 335 + "uniqueConstraints": {}, 336 + "checkConstraints": {} 337 + }, 338 + "spheres": { 339 + "name": "spheres", 340 + "columns": { 341 + "id": { 342 + "name": "id", 343 + "type": "text", 344 + "primaryKey": true, 345 + "notNull": true, 346 + "autoincrement": false 347 + }, 348 + "handle": { 349 + "name": "handle", 350 + "type": "text", 351 + "primaryKey": false, 352 + "notNull": true, 353 + "autoincrement": false 354 + }, 355 + "name": { 356 + "name": "name", 357 + "type": "text", 358 + "primaryKey": false, 359 + "notNull": true, 360 + "autoincrement": false 361 + }, 362 + "description": { 363 + "name": "description", 364 + "type": "text", 365 + "primaryKey": false, 366 + "notNull": false, 367 + "autoincrement": false 368 + }, 369 + "visibility": { 370 + "name": "visibility", 371 + "type": "text", 372 + "primaryKey": false, 373 + "notNull": true, 374 + "autoincrement": false, 375 + "default": "'public'" 376 + }, 377 + "owner_did": { 378 + "name": "owner_did", 379 + "type": "text", 380 + "primaryKey": false, 381 + "notNull": true, 382 + "autoincrement": false 383 + }, 384 + "pds_uri": { 385 + "name": "pds_uri", 386 + "type": "text", 387 + "primaryKey": false, 388 + "notNull": false, 389 + "autoincrement": false 390 + }, 391 + "created_at": { 392 + "name": "created_at", 393 + "type": "text", 394 + "primaryKey": false, 395 + "notNull": true, 396 + "autoincrement": false, 397 + "default": "(datetime('now'))" 398 + }, 399 + "updated_at": { 400 + "name": "updated_at", 401 + "type": "text", 402 + "primaryKey": false, 403 + "notNull": true, 404 + "autoincrement": false, 405 + "default": "(datetime('now'))" 406 + } 407 + }, 408 + "indexes": { 409 + "spheres_handle_unique": { 410 + "name": "spheres_handle_unique", 411 + "columns": ["handle"], 412 + "isUnique": true 413 + } 414 + }, 415 + "foreignKeys": {}, 416 + "compositePrimaryKeys": {}, 417 + "uniqueConstraints": {}, 418 + "checkConstraints": {} 419 + }, 420 + "feature_request_comment_votes": { 421 + "name": "feature_request_comment_votes", 422 + "columns": { 423 + "comment_id": { 424 + "name": "comment_id", 425 + "type": "text", 426 + "primaryKey": false, 427 + "notNull": true, 428 + "autoincrement": false 429 + }, 430 + "author_did": { 431 + "name": "author_did", 432 + "type": "text", 433 + "primaryKey": false, 434 + "notNull": true, 435 + "autoincrement": false 436 + }, 437 + "pds_uri": { 438 + "name": "pds_uri", 439 + "type": "text", 440 + "primaryKey": false, 441 + "notNull": false, 442 + "autoincrement": false 443 + }, 444 + "created_at": { 445 + "name": "created_at", 446 + "type": "text", 447 + "primaryKey": false, 448 + "notNull": true, 449 + "autoincrement": false, 450 + "default": "(datetime('now'))" 451 + } 452 + }, 453 + "indexes": { 454 + "idx_feature_request_comment_votes_comment": { 455 + "name": "idx_feature_request_comment_votes_comment", 456 + "columns": ["comment_id"], 457 + "isUnique": false 458 + } 459 + }, 460 + "foreignKeys": { 461 + "feature_request_comment_votes_comment_id_feature_request_comments_id_fk": { 462 + "name": "feature_request_comment_votes_comment_id_feature_request_comments_id_fk", 463 + "tableFrom": "feature_request_comment_votes", 464 + "tableTo": "feature_request_comments", 465 + "columnsFrom": ["comment_id"], 466 + "columnsTo": ["id"], 467 + "onDelete": "no action", 468 + "onUpdate": "no action" 469 + } 470 + }, 471 + "compositePrimaryKeys": { 472 + "feature_request_comment_votes_comment_id_author_did_pk": { 473 + "columns": ["comment_id", "author_did"], 474 + "name": "feature_request_comment_votes_comment_id_author_did_pk" 475 + } 476 + }, 477 + "uniqueConstraints": {}, 478 + "checkConstraints": {} 479 + }, 480 + "feature_request_comments": { 481 + "name": "feature_request_comments", 482 + "columns": { 483 + "id": { 484 + "name": "id", 485 + "type": "text", 486 + "primaryKey": true, 487 + "notNull": true, 488 + "autoincrement": false 489 + }, 490 + "request_id": { 491 + "name": "request_id", 492 + "type": "text", 493 + "primaryKey": false, 494 + "notNull": true, 495 + "autoincrement": false 496 + }, 497 + "author_did": { 498 + "name": "author_did", 499 + "type": "text", 500 + "primaryKey": false, 501 + "notNull": true, 502 + "autoincrement": false 503 + }, 504 + "content": { 505 + "name": "content", 506 + "type": "text", 507 + "primaryKey": false, 508 + "notNull": true, 509 + "autoincrement": false 510 + }, 511 + "pds_uri": { 512 + "name": "pds_uri", 513 + "type": "text", 514 + "primaryKey": false, 515 + "notNull": false, 516 + "autoincrement": false 517 + }, 518 + "updated_at": { 519 + "name": "updated_at", 520 + "type": "text", 521 + "primaryKey": false, 522 + "notNull": true, 523 + "autoincrement": false, 524 + "default": "(datetime('now'))" 525 + }, 526 + "hidden_at": { 527 + "name": "hidden_at", 528 + "type": "text", 529 + "primaryKey": false, 530 + "notNull": false, 531 + "autoincrement": false 532 + }, 533 + "moderated_by": { 534 + "name": "moderated_by", 535 + "type": "text", 536 + "primaryKey": false, 537 + "notNull": false, 538 + "autoincrement": false 539 + } 540 + }, 541 + "indexes": { 542 + "idx_feature_request_comments_request": { 543 + "name": "idx_feature_request_comments_request", 544 + "columns": ["request_id"], 545 + "isUnique": false 546 + }, 547 + "idx_feature_request_comments_author_request": { 548 + "name": "idx_feature_request_comments_author_request", 549 + "columns": ["author_did", "request_id"], 550 + "isUnique": false 551 + } 552 + }, 553 + "foreignKeys": { 554 + "feature_request_comments_request_id_feature_requests_id_fk": { 555 + "name": "feature_request_comments_request_id_feature_requests_id_fk", 556 + "tableFrom": "feature_request_comments", 557 + "tableTo": "feature_requests", 558 + "columnsFrom": ["request_id"], 559 + "columnsTo": ["id"], 560 + "onDelete": "no action", 561 + "onUpdate": "no action" 562 + } 563 + }, 564 + "compositePrimaryKeys": {}, 565 + "uniqueConstraints": {}, 566 + "checkConstraints": {} 567 + }, 568 + "feature_request_statuses": { 569 + "name": "feature_request_statuses", 570 + "columns": { 571 + "id": { 572 + "name": "id", 573 + "type": "text", 574 + "primaryKey": true, 575 + "notNull": true, 576 + "autoincrement": false 577 + }, 578 + "request_id": { 579 + "name": "request_id", 580 + "type": "text", 581 + "primaryKey": false, 582 + "notNull": true, 583 + "autoincrement": false 584 + }, 585 + "author_did": { 586 + "name": "author_did", 587 + "type": "text", 588 + "primaryKey": false, 589 + "notNull": true, 590 + "autoincrement": false 591 + }, 592 + "status": { 593 + "name": "status", 594 + "type": "text", 595 + "primaryKey": false, 596 + "notNull": true, 597 + "autoincrement": false 598 + }, 599 + "pds_uri": { 600 + "name": "pds_uri", 601 + "type": "text", 602 + "primaryKey": false, 603 + "notNull": false, 604 + "autoincrement": false 605 + } 606 + }, 607 + "indexes": { 608 + "idx_feature_request_statuses_request": { 609 + "name": "idx_feature_request_statuses_request", 610 + "columns": ["request_id"], 611 + "isUnique": false 612 + } 613 + }, 614 + "foreignKeys": { 615 + "feature_request_statuses_request_id_feature_requests_id_fk": { 616 + "name": "feature_request_statuses_request_id_feature_requests_id_fk", 617 + "tableFrom": "feature_request_statuses", 618 + "tableTo": "feature_requests", 619 + "columnsFrom": ["request_id"], 620 + "columnsTo": ["id"], 621 + "onDelete": "no action", 622 + "onUpdate": "no action" 623 + } 624 + }, 625 + "compositePrimaryKeys": {}, 626 + "uniqueConstraints": {}, 627 + "checkConstraints": {} 628 + }, 629 + "feature_request_votes": { 630 + "name": "feature_request_votes", 631 + "columns": { 632 + "request_id": { 633 + "name": "request_id", 634 + "type": "text", 635 + "primaryKey": false, 636 + "notNull": true, 637 + "autoincrement": false 638 + }, 639 + "author_did": { 640 + "name": "author_did", 641 + "type": "text", 642 + "primaryKey": false, 643 + "notNull": true, 644 + "autoincrement": false 645 + }, 646 + "pds_uri": { 647 + "name": "pds_uri", 648 + "type": "text", 649 + "primaryKey": false, 650 + "notNull": false, 651 + "autoincrement": false 652 + }, 653 + "created_at": { 654 + "name": "created_at", 655 + "type": "text", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "autoincrement": false, 659 + "default": "(datetime('now'))" 660 + } 661 + }, 662 + "indexes": { 663 + "idx_feature_request_votes_request": { 664 + "name": "idx_feature_request_votes_request", 665 + "columns": ["request_id"], 666 + "isUnique": false 667 + } 668 + }, 669 + "foreignKeys": { 670 + "feature_request_votes_request_id_feature_requests_id_fk": { 671 + "name": "feature_request_votes_request_id_feature_requests_id_fk", 672 + "tableFrom": "feature_request_votes", 673 + "tableTo": "feature_requests", 674 + "columnsFrom": ["request_id"], 675 + "columnsTo": ["id"], 676 + "onDelete": "no action", 677 + "onUpdate": "no action" 678 + } 679 + }, 680 + "compositePrimaryKeys": { 681 + "feature_request_votes_request_id_author_did_pk": { 682 + "columns": ["request_id", "author_did"], 683 + "name": "feature_request_votes_request_id_author_did_pk" 684 + } 685 + }, 686 + "uniqueConstraints": {}, 687 + "checkConstraints": {} 688 + }, 689 + "feature_requests": { 690 + "name": "feature_requests", 691 + "columns": { 692 + "id": { 693 + "name": "id", 694 + "type": "text", 695 + "primaryKey": true, 696 + "notNull": true, 697 + "autoincrement": false 698 + }, 699 + "sphere_id": { 700 + "name": "sphere_id", 701 + "type": "text", 702 + "primaryKey": false, 703 + "notNull": true, 704 + "autoincrement": false 705 + }, 706 + "number": { 707 + "name": "number", 708 + "type": "integer", 709 + "primaryKey": false, 710 + "notNull": true, 711 + "autoincrement": false 712 + }, 713 + "author_did": { 714 + "name": "author_did", 715 + "type": "text", 716 + "primaryKey": false, 717 + "notNull": true, 718 + "autoincrement": false 719 + }, 720 + "title": { 721 + "name": "title", 722 + "type": "text", 723 + "primaryKey": false, 724 + "notNull": true, 725 + "autoincrement": false 726 + }, 727 + "description": { 728 + "name": "description", 729 + "type": "text", 730 + "primaryKey": false, 731 + "notNull": true, 732 + "autoincrement": false 733 + }, 734 + "category": { 735 + "name": "category", 736 + "type": "text", 737 + "primaryKey": false, 738 + "notNull": true, 739 + "autoincrement": false, 740 + "default": "'general'" 741 + }, 742 + "status": { 743 + "name": "status", 744 + "type": "text", 745 + "primaryKey": false, 746 + "notNull": true, 747 + "autoincrement": false, 748 + "default": "'requested'" 749 + }, 750 + "duplicate_of_id": { 751 + "name": "duplicate_of_id", 752 + "type": "text", 753 + "primaryKey": false, 754 + "notNull": false, 755 + "autoincrement": false 756 + }, 757 + "pds_uri": { 758 + "name": "pds_uri", 759 + "type": "text", 760 + "primaryKey": false, 761 + "notNull": false, 762 + "autoincrement": false 763 + }, 764 + "hidden_at": { 765 + "name": "hidden_at", 766 + "type": "text", 767 + "primaryKey": false, 768 + "notNull": false, 769 + "autoincrement": false 770 + }, 771 + "moderated_by": { 772 + "name": "moderated_by", 773 + "type": "text", 774 + "primaryKey": false, 775 + "notNull": false, 776 + "autoincrement": false 777 + }, 778 + "updated_at": { 779 + "name": "updated_at", 780 + "type": "text", 781 + "primaryKey": false, 782 + "notNull": true, 783 + "autoincrement": false, 784 + "default": "(datetime('now'))" 785 + } 786 + }, 787 + "indexes": { 788 + "idx_feature_requests_sphere_number": { 789 + "name": "idx_feature_requests_sphere_number", 790 + "columns": ["sphere_id", "number"], 791 + "isUnique": true 792 + }, 793 + "idx_feature_requests_sphere": { 794 + "name": "idx_feature_requests_sphere", 795 + "columns": ["sphere_id"], 796 + "isUnique": false 797 + }, 798 + "idx_feature_requests_status": { 799 + "name": "idx_feature_requests_status", 800 + "columns": ["status"], 801 + "isUnique": false 802 + }, 803 + "idx_feature_requests_category": { 804 + "name": "idx_feature_requests_category", 805 + "columns": ["category"], 806 + "isUnique": false 807 + } 808 + }, 809 + "foreignKeys": { 810 + "feature_requests_sphere_id_spheres_id_fk": { 811 + "name": "feature_requests_sphere_id_spheres_id_fk", 812 + "tableFrom": "feature_requests", 813 + "tableTo": "spheres", 814 + "columnsFrom": ["sphere_id"], 815 + "columnsTo": ["id"], 816 + "onDelete": "no action", 817 + "onUpdate": "no action" 818 + } 819 + }, 820 + "compositePrimaryKeys": {}, 821 + "uniqueConstraints": {}, 822 + "checkConstraints": {} 823 + }, 824 + "feed_posts": { 825 + "name": "feed_posts", 826 + "columns": { 827 + "id": { 828 + "name": "id", 829 + "type": "text", 830 + "primaryKey": true, 831 + "notNull": true, 832 + "autoincrement": false 833 + }, 834 + "author_did": { 835 + "name": "author_did", 836 + "type": "text", 837 + "primaryKey": false, 838 + "notNull": true, 839 + "autoincrement": false 840 + }, 841 + "content": { 842 + "name": "content", 843 + "type": "text", 844 + "primaryKey": false, 845 + "notNull": true, 846 + "autoincrement": false 847 + }, 848 + "parent_id": { 849 + "name": "parent_id", 850 + "type": "text", 851 + "primaryKey": false, 852 + "notNull": false, 853 + "autoincrement": false 854 + }, 855 + "pds_uri": { 856 + "name": "pds_uri", 857 + "type": "text", 858 + "primaryKey": false, 859 + "notNull": false, 860 + "autoincrement": false 861 + }, 862 + "updated_at": { 863 + "name": "updated_at", 864 + "type": "text", 865 + "primaryKey": false, 866 + "notNull": true, 867 + "autoincrement": false, 868 + "default": "(datetime('now'))" 869 + } 870 + }, 871 + "indexes": { 872 + "idx_feed_posts_parent": { 873 + "name": "idx_feed_posts_parent", 874 + "columns": ["parent_id"], 875 + "isUnique": false 876 + } 877 + }, 878 + "foreignKeys": {}, 879 + "compositePrimaryKeys": {}, 880 + "uniqueConstraints": {}, 881 + "checkConstraints": {} 882 + }, 883 + "kanban_columns": { 884 + "name": "kanban_columns", 885 + "columns": { 886 + "id": { 887 + "name": "id", 888 + "type": "text", 889 + "primaryKey": true, 890 + "notNull": true, 891 + "autoincrement": false 892 + }, 893 + "sphere_id": { 894 + "name": "sphere_id", 895 + "type": "text", 896 + "primaryKey": false, 897 + "notNull": true, 898 + "autoincrement": false 899 + }, 900 + "slug": { 901 + "name": "slug", 902 + "type": "text", 903 + "primaryKey": false, 904 + "notNull": true, 905 + "autoincrement": false 906 + }, 907 + "label": { 908 + "name": "label", 909 + "type": "text", 910 + "primaryKey": false, 911 + "notNull": true, 912 + "autoincrement": false 913 + }, 914 + "position": { 915 + "name": "position", 916 + "type": "integer", 917 + "primaryKey": false, 918 + "notNull": true, 919 + "autoincrement": false 920 + }, 921 + "created_at": { 922 + "name": "created_at", 923 + "type": "text", 924 + "primaryKey": false, 925 + "notNull": true, 926 + "autoincrement": false, 927 + "default": "(datetime('now'))" 928 + } 929 + }, 930 + "indexes": { 931 + "idx_kanban_columns_sphere_slug": { 932 + "name": "idx_kanban_columns_sphere_slug", 933 + "columns": ["sphere_id", "slug"], 934 + "isUnique": true 935 + }, 936 + "idx_kanban_columns_sphere_position": { 937 + "name": "idx_kanban_columns_sphere_position", 938 + "columns": ["sphere_id", "position"], 939 + "isUnique": false 940 + } 941 + }, 942 + "foreignKeys": { 943 + "kanban_columns_sphere_id_spheres_id_fk": { 944 + "name": "kanban_columns_sphere_id_spheres_id_fk", 945 + "tableFrom": "kanban_columns", 946 + "tableTo": "spheres", 947 + "columnsFrom": ["sphere_id"], 948 + "columnsTo": ["id"], 949 + "onDelete": "no action", 950 + "onUpdate": "no action" 951 + } 952 + }, 953 + "compositePrimaryKeys": {}, 954 + "uniqueConstraints": {}, 955 + "checkConstraints": {} 956 + }, 957 + "kanban_task_comments": { 958 + "name": "kanban_task_comments", 959 + "columns": { 960 + "id": { 961 + "name": "id", 962 + "type": "text", 963 + "primaryKey": true, 964 + "notNull": true, 965 + "autoincrement": false 966 + }, 967 + "task_id": { 968 + "name": "task_id", 969 + "type": "text", 970 + "primaryKey": false, 971 + "notNull": true, 972 + "autoincrement": false 973 + }, 974 + "author_did": { 975 + "name": "author_did", 976 + "type": "text", 977 + "primaryKey": false, 978 + "notNull": true, 979 + "autoincrement": false 980 + }, 981 + "content": { 982 + "name": "content", 983 + "type": "text", 984 + "primaryKey": false, 985 + "notNull": true, 986 + "autoincrement": false 987 + }, 988 + "pds_uri": { 989 + "name": "pds_uri", 990 + "type": "text", 991 + "primaryKey": false, 992 + "notNull": false, 993 + "autoincrement": false 994 + }, 995 + "updated_at": { 996 + "name": "updated_at", 997 + "type": "text", 998 + "primaryKey": false, 999 + "notNull": true, 1000 + "autoincrement": false, 1001 + "default": "(datetime('now'))" 1002 + }, 1003 + "hidden_at": { 1004 + "name": "hidden_at", 1005 + "type": "text", 1006 + "primaryKey": false, 1007 + "notNull": false, 1008 + "autoincrement": false 1009 + }, 1010 + "moderated_by": { 1011 + "name": "moderated_by", 1012 + "type": "text", 1013 + "primaryKey": false, 1014 + "notNull": false, 1015 + "autoincrement": false 1016 + } 1017 + }, 1018 + "indexes": { 1019 + "idx_kanban_task_comments_task": { 1020 + "name": "idx_kanban_task_comments_task", 1021 + "columns": ["task_id"], 1022 + "isUnique": false 1023 + }, 1024 + "idx_kanban_task_comments_author_task": { 1025 + "name": "idx_kanban_task_comments_author_task", 1026 + "columns": ["author_did", "task_id"], 1027 + "isUnique": false 1028 + } 1029 + }, 1030 + "foreignKeys": { 1031 + "kanban_task_comments_task_id_kanban_tasks_id_fk": { 1032 + "name": "kanban_task_comments_task_id_kanban_tasks_id_fk", 1033 + "tableFrom": "kanban_task_comments", 1034 + "tableTo": "kanban_tasks", 1035 + "columnsFrom": ["task_id"], 1036 + "columnsTo": ["id"], 1037 + "onDelete": "no action", 1038 + "onUpdate": "no action" 1039 + } 1040 + }, 1041 + "compositePrimaryKeys": {}, 1042 + "uniqueConstraints": {}, 1043 + "checkConstraints": {} 1044 + }, 1045 + "kanban_task_status_changes": { 1046 + "name": "kanban_task_status_changes", 1047 + "columns": { 1048 + "id": { 1049 + "name": "id", 1050 + "type": "text", 1051 + "primaryKey": true, 1052 + "notNull": true, 1053 + "autoincrement": false 1054 + }, 1055 + "task_id": { 1056 + "name": "task_id", 1057 + "type": "text", 1058 + "primaryKey": false, 1059 + "notNull": true, 1060 + "autoincrement": false 1061 + }, 1062 + "author_did": { 1063 + "name": "author_did", 1064 + "type": "text", 1065 + "primaryKey": false, 1066 + "notNull": true, 1067 + "autoincrement": false 1068 + }, 1069 + "status": { 1070 + "name": "status", 1071 + "type": "text", 1072 + "primaryKey": false, 1073 + "notNull": true, 1074 + "autoincrement": false 1075 + }, 1076 + "pds_uri": { 1077 + "name": "pds_uri", 1078 + "type": "text", 1079 + "primaryKey": false, 1080 + "notNull": false, 1081 + "autoincrement": false 1082 + } 1083 + }, 1084 + "indexes": { 1085 + "idx_kanban_task_status_changes_task": { 1086 + "name": "idx_kanban_task_status_changes_task", 1087 + "columns": ["task_id"], 1088 + "isUnique": false 1089 + } 1090 + }, 1091 + "foreignKeys": { 1092 + "kanban_task_status_changes_task_id_kanban_tasks_id_fk": { 1093 + "name": "kanban_task_status_changes_task_id_kanban_tasks_id_fk", 1094 + "tableFrom": "kanban_task_status_changes", 1095 + "tableTo": "kanban_tasks", 1096 + "columnsFrom": ["task_id"], 1097 + "columnsTo": ["id"], 1098 + "onDelete": "no action", 1099 + "onUpdate": "no action" 1100 + } 1101 + }, 1102 + "compositePrimaryKeys": {}, 1103 + "uniqueConstraints": {}, 1104 + "checkConstraints": {} 1105 + }, 1106 + "kanban_tasks": { 1107 + "name": "kanban_tasks", 1108 + "columns": { 1109 + "id": { 1110 + "name": "id", 1111 + "type": "text", 1112 + "primaryKey": true, 1113 + "notNull": true, 1114 + "autoincrement": false 1115 + }, 1116 + "sphere_id": { 1117 + "name": "sphere_id", 1118 + "type": "text", 1119 + "primaryKey": false, 1120 + "notNull": true, 1121 + "autoincrement": false 1122 + }, 1123 + "number": { 1124 + "name": "number", 1125 + "type": "integer", 1126 + "primaryKey": false, 1127 + "notNull": true, 1128 + "autoincrement": false 1129 + }, 1130 + "author_did": { 1131 + "name": "author_did", 1132 + "type": "text", 1133 + "primaryKey": false, 1134 + "notNull": true, 1135 + "autoincrement": false 1136 + }, 1137 + "title": { 1138 + "name": "title", 1139 + "type": "text", 1140 + "primaryKey": false, 1141 + "notNull": true, 1142 + "autoincrement": false 1143 + }, 1144 + "description": { 1145 + "name": "description", 1146 + "type": "text", 1147 + "primaryKey": false, 1148 + "notNull": true, 1149 + "autoincrement": false, 1150 + "default": "''" 1151 + }, 1152 + "status": { 1153 + "name": "status", 1154 + "type": "text", 1155 + "primaryKey": false, 1156 + "notNull": true, 1157 + "autoincrement": false, 1158 + "default": "'backlog'" 1159 + }, 1160 + "position": { 1161 + "name": "position", 1162 + "type": "integer", 1163 + "primaryKey": false, 1164 + "notNull": true, 1165 + "autoincrement": false, 1166 + "default": 0 1167 + }, 1168 + "assignee_did": { 1169 + "name": "assignee_did", 1170 + "type": "text", 1171 + "primaryKey": false, 1172 + "notNull": false, 1173 + "autoincrement": false 1174 + }, 1175 + "pds_uri": { 1176 + "name": "pds_uri", 1177 + "type": "text", 1178 + "primaryKey": false, 1179 + "notNull": false, 1180 + "autoincrement": false 1181 + }, 1182 + "hidden_at": { 1183 + "name": "hidden_at", 1184 + "type": "text", 1185 + "primaryKey": false, 1186 + "notNull": false, 1187 + "autoincrement": false 1188 + }, 1189 + "moderated_by": { 1190 + "name": "moderated_by", 1191 + "type": "text", 1192 + "primaryKey": false, 1193 + "notNull": false, 1194 + "autoincrement": false 1195 + }, 1196 + "updated_at": { 1197 + "name": "updated_at", 1198 + "type": "text", 1199 + "primaryKey": false, 1200 + "notNull": true, 1201 + "autoincrement": false, 1202 + "default": "(datetime('now'))" 1203 + } 1204 + }, 1205 + "indexes": { 1206 + "idx_kanban_tasks_sphere_number": { 1207 + "name": "idx_kanban_tasks_sphere_number", 1208 + "columns": ["sphere_id", "number"], 1209 + "isUnique": true 1210 + }, 1211 + "idx_kanban_tasks_sphere": { 1212 + "name": "idx_kanban_tasks_sphere", 1213 + "columns": ["sphere_id"], 1214 + "isUnique": false 1215 + }, 1216 + "idx_kanban_tasks_status": { 1217 + "name": "idx_kanban_tasks_status", 1218 + "columns": ["status"], 1219 + "isUnique": false 1220 + }, 1221 + "idx_kanban_tasks_sphere_status_position": { 1222 + "name": "idx_kanban_tasks_sphere_status_position", 1223 + "columns": ["sphere_id", "status", "position"], 1224 + "isUnique": false 1225 + } 1226 + }, 1227 + "foreignKeys": { 1228 + "kanban_tasks_sphere_id_spheres_id_fk": { 1229 + "name": "kanban_tasks_sphere_id_spheres_id_fk", 1230 + "tableFrom": "kanban_tasks", 1231 + "tableTo": "spheres", 1232 + "columnsFrom": ["sphere_id"], 1233 + "columnsTo": ["id"], 1234 + "onDelete": "no action", 1235 + "onUpdate": "no action" 1236 + } 1237 + }, 1238 + "compositePrimaryKeys": {}, 1239 + "uniqueConstraints": {}, 1240 + "checkConstraints": {} 1241 + } 1242 + }, 1243 + "views": {}, 1244 + "enums": {}, 1245 + "_meta": { 1246 + "schemas": {}, 1247 + "tables": {}, 1248 + "columns": {} 1249 + }, 1250 + "internal": { 1251 + "indexes": {} 1252 + } 1253 + }
+7
drizzle/meta/_journal.json
··· 22 22 "when": 1775163122388, 23 23 "tag": "0002_far_deadpool", 24 24 "breakpoints": true 25 + }, 26 + { 27 + "idx": 3, 28 + "version": "6", 29 + "when": 1775304470263, 30 + "tag": "0003_soft_tigra", 31 + "breakpoints": true 25 32 } 26 33 ] 27 34 }
+1
packages/core/package.json
··· 7 7 "./auth": "./src/auth/index.ts", 8 8 "./db": "./src/db/index.ts", 9 9 "./db/drizzle": "./src/db/drizzle.ts", 10 + "./db/entry-number": "./src/db/entry-number.ts", 10 11 "./db/schema": "./src/db/schema/index.ts", 11 12 "./indexer": "./src/indexer/index.ts", 12 13 "./pds": "./src/pds.ts",
+58
packages/core/src/__tests__/entry-number.test.ts
··· 1 + import { describe, it, expect, beforeEach } from "vitest"; 2 + import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; 3 + 4 + import { createTestDb, seedSphere } from "./helpers/test-db.ts"; 5 + import { nextEntryNumber } from "../db/entry-number.ts"; 6 + 7 + let db: BetterSQLite3Database; 8 + 9 + const SPHERE_A = "sphere-a"; 10 + const SPHERE_B = "sphere-b"; 11 + const OWNER = "did:plc:owner"; 12 + 13 + beforeEach(() => { 14 + db = createTestDb(); 15 + seedSphere(db, { id: SPHERE_A, handle: "sphere-a", ownerDid: OWNER }); 16 + seedSphere(db, { id: SPHERE_B, handle: "sphere-b", ownerDid: OWNER }); 17 + }); 18 + 19 + describe("nextEntryNumber", () => { 20 + it("returns sequential numbers starting at 1", () => { 21 + const results = db.transaction((tx) => { 22 + return [ 23 + nextEntryNumber(tx, SPHERE_A), 24 + nextEntryNumber(tx, SPHERE_A), 25 + nextEntryNumber(tx, SPHERE_A), 26 + ]; 27 + }); 28 + 29 + expect(results).toEqual([1, 2, 3]); 30 + }); 31 + 32 + it("maintains separate counters per sphere", () => { 33 + const [numA, numB, numA2] = db.transaction((tx) => { 34 + return [ 35 + nextEntryNumber(tx, SPHERE_A), 36 + nextEntryNumber(tx, SPHERE_B), 37 + nextEntryNumber(tx, SPHERE_A), 38 + ]; 39 + }); 40 + 41 + expect(numA).toBe(1); 42 + expect(numB).toBe(1); 43 + expect(numA2).toBe(2); 44 + }); 45 + 46 + it("persists across transactions", () => { 47 + db.transaction((tx) => { 48 + nextEntryNumber(tx, SPHERE_A); 49 + nextEntryNumber(tx, SPHERE_A); 50 + }); 51 + 52 + const next = db.transaction((tx) => { 53 + return nextEntryNumber(tx, SPHERE_A); 54 + }); 55 + 56 + expect(next).toBe(3); 57 + }); 58 + });
+23
packages/core/src/db/entry-number.ts
··· 1 + import { eq, sql } from "drizzle-orm"; 2 + import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"; 3 + import { sphereEntryCounter } from "./schema/entry-counter.ts"; 4 + 5 + type Transaction = Parameters<Parameters<BaseSQLiteDatabase<any, any>["transaction"]>[0]>[0]; 6 + 7 + export function nextEntryNumber(tx: Transaction, sphereId: string): number { 8 + tx.insert(sphereEntryCounter) 9 + .values({ sphereId, lastNumber: 1 }) 10 + .onConflictDoUpdate({ 11 + target: sphereEntryCounter.sphereId, 12 + set: { lastNumber: sql`${sphereEntryCounter.lastNumber} + 1` }, 13 + }) 14 + .run(); 15 + 16 + const row = tx 17 + .select({ lastNumber: sphereEntryCounter.lastNumber }) 18 + .from(sphereEntryCounter) 19 + .where(eq(sphereEntryCounter.sphereId, sphereId)) 20 + .get(); 21 + 22 + return row!.lastNumber; 23 + }
+80
packages/core/src/db/migrate.ts
··· 1 + import { Database } from "bun:sqlite"; 1 2 import { migrate } from "drizzle-orm/bun-sqlite/migrator"; 2 3 import { getDb } from "./index.ts"; 3 4 4 5 const migrationsFolder = process.env.MIGRATIONS_PATH || "drizzle"; 5 6 6 7 migrate(getDb(), { migrationsFolder }); 8 + 9 + // Data migration: renumber feature requests and kanban tasks to use the 10 + // shared sphere_entry_counter. Runs only once — skips if counter rows exist. 11 + seedEntryCounters(); 12 + 13 + function seedEntryCounters() { 14 + const dbPath = process.env.DATABASE_PATH || "exosphere.sqlite"; 15 + const db = new Database(dbPath); 16 + 17 + // Check if the table exists (fresh installs before the migration has run) 18 + const tableExists = db 19 + .query<{ name: string }, []>( 20 + "SELECT name FROM sqlite_master WHERE type='table' AND name='sphere_entry_counter'", 21 + ) 22 + .get(); 23 + if (!tableExists) { 24 + db.close(); 25 + return; 26 + } 27 + 28 + // Skip if already seeded 29 + const existing = db 30 + .query<{ c: number }, []>("SELECT count(*) as c FROM sphere_entry_counter") 31 + .get(); 32 + if (existing && existing.c > 0) { 33 + db.close(); 34 + return; 35 + } 36 + 37 + db.run("PRAGMA foreign_keys = OFF;"); 38 + 39 + const spheres = db.query<{ id: string }, []>("SELECT id FROM spheres").all(); 40 + 41 + for (const sphere of spheres) { 42 + const frRows = db 43 + .query<{ id: string }, [string]>( 44 + "SELECT id FROM feature_requests WHERE sphere_id = ? ORDER BY id", 45 + ) 46 + .all(sphere.id); 47 + 48 + const taskRows = db 49 + .query<{ id: string }, [string]>( 50 + "SELECT id FROM kanban_tasks WHERE sphere_id = ? ORDER BY id", 51 + ) 52 + .all(sphere.id); 53 + 54 + const entries: { id: string; table: "feature_requests" | "kanban_tasks" }[] = [ 55 + ...frRows.map((r) => ({ id: r.id, table: "feature_requests" as const })), 56 + ...taskRows.map((r) => ({ id: r.id, table: "kanban_tasks" as const })), 57 + ]; 58 + 59 + // TIDs are lexicographically sortable by time 60 + entries.sort((a, b) => a.id.localeCompare(b.id)); 61 + 62 + if (entries.length === 0) continue; 63 + 64 + const tx = db.transaction(() => { 65 + // Negate all numbers to avoid unique constraint conflicts during reassignment 66 + db.run("UPDATE feature_requests SET number = -number WHERE sphere_id = ?", [sphere.id]); 67 + db.run("UPDATE kanban_tasks SET number = -number WHERE sphere_id = ?", [sphere.id]); 68 + 69 + for (let i = 0; i < entries.length; i++) { 70 + const entry = entries[i]; 71 + db.run(`UPDATE ${entry.table} SET number = ? WHERE id = ?`, [i + 1, entry.id]); 72 + } 73 + 74 + db.run("INSERT INTO sphere_entry_counter (sphere_id, last_number) VALUES (?, ?)", [ 75 + sphere.id, 76 + entries.length, 77 + ]); 78 + }); 79 + 80 + tx(); 81 + console.log(`[migrate] Sphere ${sphere.id}: renumbered ${entries.length} entries`); 82 + } 83 + 84 + db.run("PRAGMA foreign_keys = ON;"); 85 + db.close(); 86 + }
+9
packages/core/src/db/schema/entry-counter.ts
··· 1 + import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; 2 + import { spheres } from "./spheres.ts"; 3 + 4 + export const sphereEntryCounter = sqliteTable("sphere_entry_counter", { 5 + sphereId: text("sphere_id") 6 + .primaryKey() 7 + .references(() => spheres.id), 8 + lastNumber: integer("last_number").notNull().default(0), 9 + });
+1
packages/core/src/db/schema/index.ts
··· 2 2 export type { Sphere, SphereMember, SphereModule, SpherePermission } from "./spheres.ts"; 3 3 export { oauthStates, oauthSessions } from "./auth.ts"; 4 4 export { indexerCursor } from "./cursor.ts"; 5 + export { sphereEntryCounter } from "./entry-counter.ts";
+8 -6
packages/feature-requests/src/db/operations.ts
··· 1 1 import { getDb } from "@exosphere/core/db"; 2 - import { eq, and, inArray, max } from "@exosphere/core/db/drizzle"; 2 + import { eq, and, inArray } from "@exosphere/core/db/drizzle"; 3 + import { nextEntryNumber } from "@exosphere/core/db/entry-number"; 3 4 import { tidToDate } from "@exosphere/core/pds"; 4 5 import type { ModerationHandler } from "@exosphere/core/sphere"; 5 6 import { ··· 24 25 }): typeof featureRequests.$inferSelect | undefined { 25 26 const db = getDb(); 26 27 return db.transaction((tx) => { 27 - const lastNumber = tx 28 - .select({ maxNumber: max(featureRequests.number) }) 28 + const existing = tx 29 + .select() 29 30 .from(featureRequests) 30 - .where(eq(featureRequests.sphereId, params.sphereId)) 31 + .where(eq(featureRequests.id, params.id)) 31 32 .get(); 32 - const number = (lastNumber?.maxNumber ?? 0) + 1; 33 + if (existing) return existing; 34 + 35 + const number = nextEntryNumber(tx, params.sphereId); 33 36 34 37 tx.insert(featureRequests) 35 38 .values({ ··· 42 45 category: params.category, 43 46 pdsUri: params.pdsUri, 44 47 }) 45 - .onConflictDoNothing() 46 48 .run(); 47 49 48 50 return tx.select().from(featureRequests).where(eq(featureRequests.id, params.id)).get();
+12 -7
packages/kanban/src/api/columns.ts
··· 83 83 }); 84 84 85 85 // Reorder columns 86 - app.post("/columns/reorder", requireAuth, requirePermission(MODULE, "manageSettings"), async (c) => { 87 - const body = await c.req.json(); 88 - const result = reorderColumnsSchema.safeParse(body); 89 - if (!result.success) return c.json({ error: z.flattenError(result.error) }, 400); 86 + app.post( 87 + "/columns/reorder", 88 + requireAuth, 89 + requirePermission(MODULE, "manageSettings"), 90 + async (c) => { 91 + const body = await c.req.json(); 92 + const result = reorderColumnsSchema.safeParse(body); 93 + if (!result.success) return c.json({ error: z.flattenError(result.error) }, 400); 90 94 91 - reorderColumns(c.var.sphereId, result.data.columnIds); 92 - return c.json({ ok: true }); 93 - }); 95 + reorderColumns(c.var.sphereId, result.data.columnIds); 96 + return c.json({ ok: true }); 97 + }, 98 + ); 94 99 95 100 export { app as columnsApi };
+5 -7
packages/kanban/src/db/operations.ts
··· 1 1 import { getDb } from "@exosphere/core/db"; 2 2 import { eq, and, max, asc } from "@exosphere/core/db/drizzle"; 3 + import { nextEntryNumber } from "@exosphere/core/db/entry-number"; 3 4 import { tidToDate, generateRkey } from "@exosphere/core/pds"; 4 5 import type { ModerationHandler } from "@exosphere/core/sphere"; 5 6 import { ··· 171 172 }): typeof kanbanTasks.$inferSelect | undefined { 172 173 const db = getDb(); 173 174 return db.transaction((tx) => { 174 - const lastNumber = tx 175 - .select({ maxNumber: max(kanbanTasks.number) }) 176 - .from(kanbanTasks) 177 - .where(eq(kanbanTasks.sphereId, params.sphereId)) 178 - .get(); 179 - const number = (lastNumber?.maxNumber ?? 0) + 1; 175 + const existing = tx.select().from(kanbanTasks).where(eq(kanbanTasks.id, params.id)).get(); 176 + if (existing) return existing; 177 + 178 + const number = nextEntryNumber(tx, params.sphereId); 180 179 181 180 const lastPosition = tx 182 181 .select({ maxPosition: max(kanbanTasks.position) }) ··· 198 197 assigneeDid: params.assigneeDid, 199 198 pdsUri: params.pdsUri, 200 199 }) 201 - .onConflictDoNothing() 202 200 .run(); 203 201 204 202 return tx.select().from(kanbanTasks).where(eq(kanbanTasks.id, params.id)).get();
+3 -1
packages/kanban/src/ui/pages/task.tsx
··· 500 500 <> 501 501 <div class={ui.metaRow}> 502 502 <span class={ui.muted}>#{task.number}</span> 503 - <span class={ui.badge}>{statusLabel(localStatus.value ?? task.status, cols)}</span> 503 + <span class={ui.badge}> 504 + {statusLabel(localStatus.value ?? task.status, cols)} 505 + </span> 504 506 {task.assigneeHandle && <span class={ui.muted}>@{task.assigneeHandle}</span>} 505 507 <span class={ui.muted}>{formatDate(task.createdAt, fullDateOpts)}</span> 506 508 </div>
+2
tsconfig.json
··· 20 20 "@exosphere/feeds/*": ["./packages/feeds/src/*"], 21 21 "@exosphere/feature-requests": ["./packages/feature-requests/src/index.ts"], 22 22 "@exosphere/feature-requests/*": ["./packages/feature-requests/src/*"], 23 + "@exosphere/kanban": ["./packages/kanban/src/index.ts"], 24 + "@exosphere/kanban/*": ["./packages/kanban/src/*"], 23 25 "@exosphere/mcp": ["./packages/mcp/src/index.ts"] 24 26 } 25 27 },