Barazo AppView backend barazo.forum

feat: pinned topics with category/forum-wide scope (#142)

* feat(db): add pinnedAt and pinnedScope columns to topics

* feat(api): expose isPinned, isLocked, pinnedScope in topic responses

Add isPinned, isLocked, pinnedScope, and pinnedAt fields to both
topicResponseProperties (JSON schema) and serializeTopic() so the
frontend receives pinned/locked state in API responses.

* feat(api): add scope support to pin endpoint (category/forum)

* feat(api): sort pinned topics first in topic list queries

Add CASE-based ORDER BY to GET /api/topics so pinned topics float to
the top. When a category filter is active, both category-pinned and
forum-pinned topics are promoted. On the homepage (no category filter),
only forum-pinned topics get promoted. Secondary sort remains the
user's chosen order (latest or popular).

* fix(test): add pinned_at and pinned_scope columns to tenant isolation test schema

authored by

Guido X Jansen and committed by
GitHub
9522cefd f08e7fc6

+4342 -6
+3
drizzle/0009_brainy_sunspot.sql
··· 1 + ALTER TABLE "topics" ADD COLUMN "pinned_at" timestamp with time zone;--> statement-breakpoint 2 + ALTER TABLE "topics" ADD COLUMN "pinned_scope" text;--> statement-breakpoint 3 + CREATE INDEX "topics_pinned_scope_idx" ON "topics" USING btree ("pinned_scope");
+4066
drizzle/meta/0009_snapshot.json
··· 1 + { 2 + "id": "2e645169-605f-48d4-8fae-e20020f0d918", 3 + "prevId": "d168c26e-76b0-40aa-b1b3-81eeba8a99c8", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.users": { 8 + "name": "users", 9 + "schema": "", 10 + "columns": { 11 + "did": { 12 + "name": "did", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true 16 + }, 17 + "handle": { 18 + "name": "handle", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true 22 + }, 23 + "display_name": { 24 + "name": "display_name", 25 + "type": "text", 26 + "primaryKey": false, 27 + "notNull": false 28 + }, 29 + "avatar_url": { 30 + "name": "avatar_url", 31 + "type": "text", 32 + "primaryKey": false, 33 + "notNull": false 34 + }, 35 + "banner_url": { 36 + "name": "banner_url", 37 + "type": "text", 38 + "primaryKey": false, 39 + "notNull": false 40 + }, 41 + "bio": { 42 + "name": "bio", 43 + "type": "text", 44 + "primaryKey": false, 45 + "notNull": false 46 + }, 47 + "role": { 48 + "name": "role", 49 + "type": "text", 50 + "primaryKey": false, 51 + "notNull": true, 52 + "default": "'user'" 53 + }, 54 + "is_banned": { 55 + "name": "is_banned", 56 + "type": "boolean", 57 + "primaryKey": false, 58 + "notNull": true, 59 + "default": false 60 + }, 61 + "reputation_score": { 62 + "name": "reputation_score", 63 + "type": "integer", 64 + "primaryKey": false, 65 + "notNull": true, 66 + "default": 0 67 + }, 68 + "first_seen_at": { 69 + "name": "first_seen_at", 70 + "type": "timestamp with time zone", 71 + "primaryKey": false, 72 + "notNull": true, 73 + "default": "now()" 74 + }, 75 + "last_active_at": { 76 + "name": "last_active_at", 77 + "type": "timestamp with time zone", 78 + "primaryKey": false, 79 + "notNull": true, 80 + "default": "now()" 81 + }, 82 + "declared_age": { 83 + "name": "declared_age", 84 + "type": "integer", 85 + "primaryKey": false, 86 + "notNull": false 87 + }, 88 + "maturity_pref": { 89 + "name": "maturity_pref", 90 + "type": "text", 91 + "primaryKey": false, 92 + "notNull": true, 93 + "default": "'safe'" 94 + }, 95 + "account_created_at": { 96 + "name": "account_created_at", 97 + "type": "timestamp with time zone", 98 + "primaryKey": false, 99 + "notNull": false 100 + }, 101 + "followers_count": { 102 + "name": "followers_count", 103 + "type": "integer", 104 + "primaryKey": false, 105 + "notNull": true, 106 + "default": 0 107 + }, 108 + "follows_count": { 109 + "name": "follows_count", 110 + "type": "integer", 111 + "primaryKey": false, 112 + "notNull": true, 113 + "default": 0 114 + }, 115 + "atproto_posts_count": { 116 + "name": "atproto_posts_count", 117 + "type": "integer", 118 + "primaryKey": false, 119 + "notNull": true, 120 + "default": 0 121 + }, 122 + "has_bluesky_profile": { 123 + "name": "has_bluesky_profile", 124 + "type": "boolean", 125 + "primaryKey": false, 126 + "notNull": true, 127 + "default": false 128 + }, 129 + "atproto_labels": { 130 + "name": "atproto_labels", 131 + "type": "jsonb", 132 + "primaryKey": false, 133 + "notNull": true, 134 + "default": "'[]'::jsonb" 135 + } 136 + }, 137 + "indexes": { 138 + "users_role_elevated_idx": { 139 + "name": "users_role_elevated_idx", 140 + "columns": [ 141 + { 142 + "expression": "role", 143 + "isExpression": false, 144 + "asc": true, 145 + "nulls": "last" 146 + } 147 + ], 148 + "isUnique": false, 149 + "where": "role IN ('moderator', 'admin')", 150 + "concurrently": false, 151 + "method": "btree", 152 + "with": {} 153 + }, 154 + "users_handle_idx": { 155 + "name": "users_handle_idx", 156 + "columns": [ 157 + { 158 + "expression": "handle", 159 + "isExpression": false, 160 + "asc": true, 161 + "nulls": "last" 162 + } 163 + ], 164 + "isUnique": false, 165 + "concurrently": false, 166 + "method": "btree", 167 + "with": {} 168 + }, 169 + "users_account_created_at_idx": { 170 + "name": "users_account_created_at_idx", 171 + "columns": [ 172 + { 173 + "expression": "account_created_at", 174 + "isExpression": false, 175 + "asc": true, 176 + "nulls": "last" 177 + } 178 + ], 179 + "isUnique": false, 180 + "concurrently": false, 181 + "method": "btree", 182 + "with": {} 183 + } 184 + }, 185 + "foreignKeys": {}, 186 + "compositePrimaryKeys": {}, 187 + "uniqueConstraints": {}, 188 + "policies": {}, 189 + "checkConstraints": {}, 190 + "isRLSEnabled": false 191 + }, 192 + "public.firehose_cursor": { 193 + "name": "firehose_cursor", 194 + "schema": "", 195 + "columns": { 196 + "id": { 197 + "name": "id", 198 + "type": "text", 199 + "primaryKey": true, 200 + "notNull": true, 201 + "default": "'default'" 202 + }, 203 + "cursor": { 204 + "name": "cursor", 205 + "type": "bigint", 206 + "primaryKey": false, 207 + "notNull": false 208 + }, 209 + "updated_at": { 210 + "name": "updated_at", 211 + "type": "timestamp with time zone", 212 + "primaryKey": false, 213 + "notNull": true, 214 + "default": "now()" 215 + } 216 + }, 217 + "indexes": {}, 218 + "foreignKeys": {}, 219 + "compositePrimaryKeys": {}, 220 + "uniqueConstraints": {}, 221 + "policies": {}, 222 + "checkConstraints": {}, 223 + "isRLSEnabled": false 224 + }, 225 + "public.topics": { 226 + "name": "topics", 227 + "schema": "", 228 + "columns": { 229 + "uri": { 230 + "name": "uri", 231 + "type": "text", 232 + "primaryKey": true, 233 + "notNull": true 234 + }, 235 + "rkey": { 236 + "name": "rkey", 237 + "type": "text", 238 + "primaryKey": false, 239 + "notNull": true 240 + }, 241 + "author_did": { 242 + "name": "author_did", 243 + "type": "text", 244 + "primaryKey": false, 245 + "notNull": true 246 + }, 247 + "title": { 248 + "name": "title", 249 + "type": "text", 250 + "primaryKey": false, 251 + "notNull": true 252 + }, 253 + "content": { 254 + "name": "content", 255 + "type": "text", 256 + "primaryKey": false, 257 + "notNull": true 258 + }, 259 + "content_format": { 260 + "name": "content_format", 261 + "type": "text", 262 + "primaryKey": false, 263 + "notNull": false 264 + }, 265 + "category": { 266 + "name": "category", 267 + "type": "text", 268 + "primaryKey": false, 269 + "notNull": true 270 + }, 271 + "tags": { 272 + "name": "tags", 273 + "type": "jsonb", 274 + "primaryKey": false, 275 + "notNull": false 276 + }, 277 + "community_did": { 278 + "name": "community_did", 279 + "type": "text", 280 + "primaryKey": false, 281 + "notNull": true 282 + }, 283 + "cid": { 284 + "name": "cid", 285 + "type": "text", 286 + "primaryKey": false, 287 + "notNull": true 288 + }, 289 + "labels": { 290 + "name": "labels", 291 + "type": "jsonb", 292 + "primaryKey": false, 293 + "notNull": false 294 + }, 295 + "reply_count": { 296 + "name": "reply_count", 297 + "type": "integer", 298 + "primaryKey": false, 299 + "notNull": true, 300 + "default": 0 301 + }, 302 + "reaction_count": { 303 + "name": "reaction_count", 304 + "type": "integer", 305 + "primaryKey": false, 306 + "notNull": true, 307 + "default": 0 308 + }, 309 + "vote_count": { 310 + "name": "vote_count", 311 + "type": "integer", 312 + "primaryKey": false, 313 + "notNull": true, 314 + "default": 0 315 + }, 316 + "last_activity_at": { 317 + "name": "last_activity_at", 318 + "type": "timestamp with time zone", 319 + "primaryKey": false, 320 + "notNull": true, 321 + "default": "now()" 322 + }, 323 + "created_at": { 324 + "name": "created_at", 325 + "type": "timestamp with time zone", 326 + "primaryKey": false, 327 + "notNull": true 328 + }, 329 + "indexed_at": { 330 + "name": "indexed_at", 331 + "type": "timestamp with time zone", 332 + "primaryKey": false, 333 + "notNull": true, 334 + "default": "now()" 335 + }, 336 + "is_locked": { 337 + "name": "is_locked", 338 + "type": "boolean", 339 + "primaryKey": false, 340 + "notNull": true, 341 + "default": false 342 + }, 343 + "is_pinned": { 344 + "name": "is_pinned", 345 + "type": "boolean", 346 + "primaryKey": false, 347 + "notNull": true, 348 + "default": false 349 + }, 350 + "pinned_at": { 351 + "name": "pinned_at", 352 + "type": "timestamp with time zone", 353 + "primaryKey": false, 354 + "notNull": false 355 + }, 356 + "pinned_scope": { 357 + "name": "pinned_scope", 358 + "type": "text", 359 + "primaryKey": false, 360 + "notNull": false 361 + }, 362 + "is_mod_deleted": { 363 + "name": "is_mod_deleted", 364 + "type": "boolean", 365 + "primaryKey": false, 366 + "notNull": true, 367 + "default": false 368 + }, 369 + "is_author_deleted": { 370 + "name": "is_author_deleted", 371 + "type": "boolean", 372 + "primaryKey": false, 373 + "notNull": true, 374 + "default": false 375 + }, 376 + "moderation_status": { 377 + "name": "moderation_status", 378 + "type": "text", 379 + "primaryKey": false, 380 + "notNull": true, 381 + "default": "'approved'" 382 + }, 383 + "trust_status": { 384 + "name": "trust_status", 385 + "type": "text", 386 + "primaryKey": false, 387 + "notNull": true, 388 + "default": "'trusted'" 389 + } 390 + }, 391 + "indexes": { 392 + "topics_author_did_idx": { 393 + "name": "topics_author_did_idx", 394 + "columns": [ 395 + { 396 + "expression": "author_did", 397 + "isExpression": false, 398 + "asc": true, 399 + "nulls": "last" 400 + } 401 + ], 402 + "isUnique": false, 403 + "concurrently": false, 404 + "method": "btree", 405 + "with": {} 406 + }, 407 + "topics_category_idx": { 408 + "name": "topics_category_idx", 409 + "columns": [ 410 + { 411 + "expression": "category", 412 + "isExpression": false, 413 + "asc": true, 414 + "nulls": "last" 415 + } 416 + ], 417 + "isUnique": false, 418 + "concurrently": false, 419 + "method": "btree", 420 + "with": {} 421 + }, 422 + "topics_created_at_idx": { 423 + "name": "topics_created_at_idx", 424 + "columns": [ 425 + { 426 + "expression": "created_at", 427 + "isExpression": false, 428 + "asc": true, 429 + "nulls": "last" 430 + } 431 + ], 432 + "isUnique": false, 433 + "concurrently": false, 434 + "method": "btree", 435 + "with": {} 436 + }, 437 + "topics_last_activity_at_idx": { 438 + "name": "topics_last_activity_at_idx", 439 + "columns": [ 440 + { 441 + "expression": "last_activity_at", 442 + "isExpression": false, 443 + "asc": true, 444 + "nulls": "last" 445 + } 446 + ], 447 + "isUnique": false, 448 + "concurrently": false, 449 + "method": "btree", 450 + "with": {} 451 + }, 452 + "topics_community_did_idx": { 453 + "name": "topics_community_did_idx", 454 + "columns": [ 455 + { 456 + "expression": "community_did", 457 + "isExpression": false, 458 + "asc": true, 459 + "nulls": "last" 460 + } 461 + ], 462 + "isUnique": false, 463 + "concurrently": false, 464 + "method": "btree", 465 + "with": {} 466 + }, 467 + "topics_moderation_status_idx": { 468 + "name": "topics_moderation_status_idx", 469 + "columns": [ 470 + { 471 + "expression": "moderation_status", 472 + "isExpression": false, 473 + "asc": true, 474 + "nulls": "last" 475 + } 476 + ], 477 + "isUnique": false, 478 + "concurrently": false, 479 + "method": "btree", 480 + "with": {} 481 + }, 482 + "topics_trust_status_idx": { 483 + "name": "topics_trust_status_idx", 484 + "columns": [ 485 + { 486 + "expression": "trust_status", 487 + "isExpression": false, 488 + "asc": true, 489 + "nulls": "last" 490 + } 491 + ], 492 + "isUnique": false, 493 + "concurrently": false, 494 + "method": "btree", 495 + "with": {} 496 + }, 497 + "topics_community_category_activity_idx": { 498 + "name": "topics_community_category_activity_idx", 499 + "columns": [ 500 + { 501 + "expression": "community_did", 502 + "isExpression": false, 503 + "asc": true, 504 + "nulls": "last" 505 + }, 506 + { 507 + "expression": "category", 508 + "isExpression": false, 509 + "asc": true, 510 + "nulls": "last" 511 + }, 512 + { 513 + "expression": "last_activity_at", 514 + "isExpression": false, 515 + "asc": true, 516 + "nulls": "last" 517 + } 518 + ], 519 + "isUnique": false, 520 + "concurrently": false, 521 + "method": "btree", 522 + "with": {} 523 + }, 524 + "topics_pinned_scope_idx": { 525 + "name": "topics_pinned_scope_idx", 526 + "columns": [ 527 + { 528 + "expression": "pinned_scope", 529 + "isExpression": false, 530 + "asc": true, 531 + "nulls": "last" 532 + } 533 + ], 534 + "isUnique": false, 535 + "concurrently": false, 536 + "method": "btree", 537 + "with": {} 538 + }, 539 + "topics_author_did_rkey_idx": { 540 + "name": "topics_author_did_rkey_idx", 541 + "columns": [ 542 + { 543 + "expression": "author_did", 544 + "isExpression": false, 545 + "asc": true, 546 + "nulls": "last" 547 + }, 548 + { 549 + "expression": "rkey", 550 + "isExpression": false, 551 + "asc": true, 552 + "nulls": "last" 553 + } 554 + ], 555 + "isUnique": false, 556 + "concurrently": false, 557 + "method": "btree", 558 + "with": {} 559 + } 560 + }, 561 + "foreignKeys": {}, 562 + "compositePrimaryKeys": {}, 563 + "uniqueConstraints": {}, 564 + "policies": { 565 + "tenant_isolation": { 566 + "name": "tenant_isolation", 567 + "as": "PERMISSIVE", 568 + "for": "ALL", 569 + "to": ["barazo_app"], 570 + "using": "community_did = current_setting('app.current_community_did', true)", 571 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 572 + } 573 + }, 574 + "checkConstraints": {}, 575 + "isRLSEnabled": true 576 + }, 577 + "public.replies": { 578 + "name": "replies", 579 + "schema": "", 580 + "columns": { 581 + "uri": { 582 + "name": "uri", 583 + "type": "text", 584 + "primaryKey": true, 585 + "notNull": true 586 + }, 587 + "rkey": { 588 + "name": "rkey", 589 + "type": "text", 590 + "primaryKey": false, 591 + "notNull": true 592 + }, 593 + "author_did": { 594 + "name": "author_did", 595 + "type": "text", 596 + "primaryKey": false, 597 + "notNull": true 598 + }, 599 + "content": { 600 + "name": "content", 601 + "type": "text", 602 + "primaryKey": false, 603 + "notNull": true 604 + }, 605 + "content_format": { 606 + "name": "content_format", 607 + "type": "text", 608 + "primaryKey": false, 609 + "notNull": false 610 + }, 611 + "root_uri": { 612 + "name": "root_uri", 613 + "type": "text", 614 + "primaryKey": false, 615 + "notNull": true 616 + }, 617 + "root_cid": { 618 + "name": "root_cid", 619 + "type": "text", 620 + "primaryKey": false, 621 + "notNull": true 622 + }, 623 + "parent_uri": { 624 + "name": "parent_uri", 625 + "type": "text", 626 + "primaryKey": false, 627 + "notNull": true 628 + }, 629 + "parent_cid": { 630 + "name": "parent_cid", 631 + "type": "text", 632 + "primaryKey": false, 633 + "notNull": true 634 + }, 635 + "community_did": { 636 + "name": "community_did", 637 + "type": "text", 638 + "primaryKey": false, 639 + "notNull": true 640 + }, 641 + "cid": { 642 + "name": "cid", 643 + "type": "text", 644 + "primaryKey": false, 645 + "notNull": true 646 + }, 647 + "labels": { 648 + "name": "labels", 649 + "type": "jsonb", 650 + "primaryKey": false, 651 + "notNull": false 652 + }, 653 + "reaction_count": { 654 + "name": "reaction_count", 655 + "type": "integer", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "default": 0 659 + }, 660 + "vote_count": { 661 + "name": "vote_count", 662 + "type": "integer", 663 + "primaryKey": false, 664 + "notNull": true, 665 + "default": 0 666 + }, 667 + "depth": { 668 + "name": "depth", 669 + "type": "integer", 670 + "primaryKey": false, 671 + "notNull": true, 672 + "default": 1 673 + }, 674 + "created_at": { 675 + "name": "created_at", 676 + "type": "timestamp with time zone", 677 + "primaryKey": false, 678 + "notNull": true 679 + }, 680 + "indexed_at": { 681 + "name": "indexed_at", 682 + "type": "timestamp with time zone", 683 + "primaryKey": false, 684 + "notNull": true, 685 + "default": "now()" 686 + }, 687 + "is_author_deleted": { 688 + "name": "is_author_deleted", 689 + "type": "boolean", 690 + "primaryKey": false, 691 + "notNull": true, 692 + "default": false 693 + }, 694 + "is_mod_deleted": { 695 + "name": "is_mod_deleted", 696 + "type": "boolean", 697 + "primaryKey": false, 698 + "notNull": true, 699 + "default": false 700 + }, 701 + "moderation_status": { 702 + "name": "moderation_status", 703 + "type": "text", 704 + "primaryKey": false, 705 + "notNull": true, 706 + "default": "'approved'" 707 + }, 708 + "trust_status": { 709 + "name": "trust_status", 710 + "type": "text", 711 + "primaryKey": false, 712 + "notNull": true, 713 + "default": "'trusted'" 714 + } 715 + }, 716 + "indexes": { 717 + "replies_author_did_idx": { 718 + "name": "replies_author_did_idx", 719 + "columns": [ 720 + { 721 + "expression": "author_did", 722 + "isExpression": false, 723 + "asc": true, 724 + "nulls": "last" 725 + } 726 + ], 727 + "isUnique": false, 728 + "concurrently": false, 729 + "method": "btree", 730 + "with": {} 731 + }, 732 + "replies_root_uri_idx": { 733 + "name": "replies_root_uri_idx", 734 + "columns": [ 735 + { 736 + "expression": "root_uri", 737 + "isExpression": false, 738 + "asc": true, 739 + "nulls": "last" 740 + } 741 + ], 742 + "isUnique": false, 743 + "concurrently": false, 744 + "method": "btree", 745 + "with": {} 746 + }, 747 + "replies_parent_uri_idx": { 748 + "name": "replies_parent_uri_idx", 749 + "columns": [ 750 + { 751 + "expression": "parent_uri", 752 + "isExpression": false, 753 + "asc": true, 754 + "nulls": "last" 755 + } 756 + ], 757 + "isUnique": false, 758 + "concurrently": false, 759 + "method": "btree", 760 + "with": {} 761 + }, 762 + "replies_created_at_idx": { 763 + "name": "replies_created_at_idx", 764 + "columns": [ 765 + { 766 + "expression": "created_at", 767 + "isExpression": false, 768 + "asc": true, 769 + "nulls": "last" 770 + } 771 + ], 772 + "isUnique": false, 773 + "concurrently": false, 774 + "method": "btree", 775 + "with": {} 776 + }, 777 + "replies_community_did_idx": { 778 + "name": "replies_community_did_idx", 779 + "columns": [ 780 + { 781 + "expression": "community_did", 782 + "isExpression": false, 783 + "asc": true, 784 + "nulls": "last" 785 + } 786 + ], 787 + "isUnique": false, 788 + "concurrently": false, 789 + "method": "btree", 790 + "with": {} 791 + }, 792 + "replies_moderation_status_idx": { 793 + "name": "replies_moderation_status_idx", 794 + "columns": [ 795 + { 796 + "expression": "moderation_status", 797 + "isExpression": false, 798 + "asc": true, 799 + "nulls": "last" 800 + } 801 + ], 802 + "isUnique": false, 803 + "concurrently": false, 804 + "method": "btree", 805 + "with": {} 806 + }, 807 + "replies_trust_status_idx": { 808 + "name": "replies_trust_status_idx", 809 + "columns": [ 810 + { 811 + "expression": "trust_status", 812 + "isExpression": false, 813 + "asc": true, 814 + "nulls": "last" 815 + } 816 + ], 817 + "isUnique": false, 818 + "concurrently": false, 819 + "method": "btree", 820 + "with": {} 821 + }, 822 + "replies_root_uri_created_at_idx": { 823 + "name": "replies_root_uri_created_at_idx", 824 + "columns": [ 825 + { 826 + "expression": "root_uri", 827 + "isExpression": false, 828 + "asc": true, 829 + "nulls": "last" 830 + }, 831 + { 832 + "expression": "created_at", 833 + "isExpression": false, 834 + "asc": true, 835 + "nulls": "last" 836 + } 837 + ], 838 + "isUnique": false, 839 + "concurrently": false, 840 + "method": "btree", 841 + "with": {} 842 + }, 843 + "replies_root_uri_depth_idx": { 844 + "name": "replies_root_uri_depth_idx", 845 + "columns": [ 846 + { 847 + "expression": "root_uri", 848 + "isExpression": false, 849 + "asc": true, 850 + "nulls": "last" 851 + }, 852 + { 853 + "expression": "depth", 854 + "isExpression": false, 855 + "asc": true, 856 + "nulls": "last" 857 + } 858 + ], 859 + "isUnique": false, 860 + "concurrently": false, 861 + "method": "btree", 862 + "with": {} 863 + }, 864 + "replies_author_did_rkey_idx": { 865 + "name": "replies_author_did_rkey_idx", 866 + "columns": [ 867 + { 868 + "expression": "author_did", 869 + "isExpression": false, 870 + "asc": true, 871 + "nulls": "last" 872 + }, 873 + { 874 + "expression": "rkey", 875 + "isExpression": false, 876 + "asc": true, 877 + "nulls": "last" 878 + } 879 + ], 880 + "isUnique": false, 881 + "concurrently": false, 882 + "method": "btree", 883 + "with": {} 884 + } 885 + }, 886 + "foreignKeys": {}, 887 + "compositePrimaryKeys": {}, 888 + "uniqueConstraints": {}, 889 + "policies": { 890 + "tenant_isolation": { 891 + "name": "tenant_isolation", 892 + "as": "PERMISSIVE", 893 + "for": "ALL", 894 + "to": ["barazo_app"], 895 + "using": "community_did = current_setting('app.current_community_did', true)", 896 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 897 + } 898 + }, 899 + "checkConstraints": {}, 900 + "isRLSEnabled": true 901 + }, 902 + "public.reactions": { 903 + "name": "reactions", 904 + "schema": "", 905 + "columns": { 906 + "uri": { 907 + "name": "uri", 908 + "type": "text", 909 + "primaryKey": true, 910 + "notNull": true 911 + }, 912 + "rkey": { 913 + "name": "rkey", 914 + "type": "text", 915 + "primaryKey": false, 916 + "notNull": true 917 + }, 918 + "author_did": { 919 + "name": "author_did", 920 + "type": "text", 921 + "primaryKey": false, 922 + "notNull": true 923 + }, 924 + "subject_uri": { 925 + "name": "subject_uri", 926 + "type": "text", 927 + "primaryKey": false, 928 + "notNull": true 929 + }, 930 + "subject_cid": { 931 + "name": "subject_cid", 932 + "type": "text", 933 + "primaryKey": false, 934 + "notNull": true 935 + }, 936 + "type": { 937 + "name": "type", 938 + "type": "text", 939 + "primaryKey": false, 940 + "notNull": true 941 + }, 942 + "community_did": { 943 + "name": "community_did", 944 + "type": "text", 945 + "primaryKey": false, 946 + "notNull": true 947 + }, 948 + "cid": { 949 + "name": "cid", 950 + "type": "text", 951 + "primaryKey": false, 952 + "notNull": true 953 + }, 954 + "created_at": { 955 + "name": "created_at", 956 + "type": "timestamp with time zone", 957 + "primaryKey": false, 958 + "notNull": true 959 + }, 960 + "indexed_at": { 961 + "name": "indexed_at", 962 + "type": "timestamp with time zone", 963 + "primaryKey": false, 964 + "notNull": true, 965 + "default": "now()" 966 + } 967 + }, 968 + "indexes": { 969 + "reactions_author_did_idx": { 970 + "name": "reactions_author_did_idx", 971 + "columns": [ 972 + { 973 + "expression": "author_did", 974 + "isExpression": false, 975 + "asc": true, 976 + "nulls": "last" 977 + } 978 + ], 979 + "isUnique": false, 980 + "concurrently": false, 981 + "method": "btree", 982 + "with": {} 983 + }, 984 + "reactions_subject_uri_idx": { 985 + "name": "reactions_subject_uri_idx", 986 + "columns": [ 987 + { 988 + "expression": "subject_uri", 989 + "isExpression": false, 990 + "asc": true, 991 + "nulls": "last" 992 + } 993 + ], 994 + "isUnique": false, 995 + "concurrently": false, 996 + "method": "btree", 997 + "with": {} 998 + }, 999 + "reactions_community_did_idx": { 1000 + "name": "reactions_community_did_idx", 1001 + "columns": [ 1002 + { 1003 + "expression": "community_did", 1004 + "isExpression": false, 1005 + "asc": true, 1006 + "nulls": "last" 1007 + } 1008 + ], 1009 + "isUnique": false, 1010 + "concurrently": false, 1011 + "method": "btree", 1012 + "with": {} 1013 + }, 1014 + "reactions_subject_uri_type_idx": { 1015 + "name": "reactions_subject_uri_type_idx", 1016 + "columns": [ 1017 + { 1018 + "expression": "subject_uri", 1019 + "isExpression": false, 1020 + "asc": true, 1021 + "nulls": "last" 1022 + }, 1023 + { 1024 + "expression": "type", 1025 + "isExpression": false, 1026 + "asc": true, 1027 + "nulls": "last" 1028 + } 1029 + ], 1030 + "isUnique": false, 1031 + "concurrently": false, 1032 + "method": "btree", 1033 + "with": {} 1034 + } 1035 + }, 1036 + "foreignKeys": {}, 1037 + "compositePrimaryKeys": {}, 1038 + "uniqueConstraints": { 1039 + "reactions_author_subject_type_uniq": { 1040 + "name": "reactions_author_subject_type_uniq", 1041 + "nullsNotDistinct": false, 1042 + "columns": ["author_did", "subject_uri", "type"] 1043 + } 1044 + }, 1045 + "policies": { 1046 + "tenant_isolation": { 1047 + "name": "tenant_isolation", 1048 + "as": "PERMISSIVE", 1049 + "for": "ALL", 1050 + "to": ["barazo_app"], 1051 + "using": "community_did = current_setting('app.current_community_did', true)", 1052 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 1053 + } 1054 + }, 1055 + "checkConstraints": {}, 1056 + "isRLSEnabled": true 1057 + }, 1058 + "public.votes": { 1059 + "name": "votes", 1060 + "schema": "", 1061 + "columns": { 1062 + "uri": { 1063 + "name": "uri", 1064 + "type": "text", 1065 + "primaryKey": true, 1066 + "notNull": true 1067 + }, 1068 + "rkey": { 1069 + "name": "rkey", 1070 + "type": "text", 1071 + "primaryKey": false, 1072 + "notNull": true 1073 + }, 1074 + "author_did": { 1075 + "name": "author_did", 1076 + "type": "text", 1077 + "primaryKey": false, 1078 + "notNull": true 1079 + }, 1080 + "subject_uri": { 1081 + "name": "subject_uri", 1082 + "type": "text", 1083 + "primaryKey": false, 1084 + "notNull": true 1085 + }, 1086 + "subject_cid": { 1087 + "name": "subject_cid", 1088 + "type": "text", 1089 + "primaryKey": false, 1090 + "notNull": true 1091 + }, 1092 + "direction": { 1093 + "name": "direction", 1094 + "type": "text", 1095 + "primaryKey": false, 1096 + "notNull": true 1097 + }, 1098 + "community_did": { 1099 + "name": "community_did", 1100 + "type": "text", 1101 + "primaryKey": false, 1102 + "notNull": true 1103 + }, 1104 + "cid": { 1105 + "name": "cid", 1106 + "type": "text", 1107 + "primaryKey": false, 1108 + "notNull": true 1109 + }, 1110 + "created_at": { 1111 + "name": "created_at", 1112 + "type": "timestamp with time zone", 1113 + "primaryKey": false, 1114 + "notNull": true 1115 + }, 1116 + "indexed_at": { 1117 + "name": "indexed_at", 1118 + "type": "timestamp with time zone", 1119 + "primaryKey": false, 1120 + "notNull": true, 1121 + "default": "now()" 1122 + } 1123 + }, 1124 + "indexes": { 1125 + "votes_author_did_idx": { 1126 + "name": "votes_author_did_idx", 1127 + "columns": [ 1128 + { 1129 + "expression": "author_did", 1130 + "isExpression": false, 1131 + "asc": true, 1132 + "nulls": "last" 1133 + } 1134 + ], 1135 + "isUnique": false, 1136 + "concurrently": false, 1137 + "method": "btree", 1138 + "with": {} 1139 + }, 1140 + "votes_subject_uri_idx": { 1141 + "name": "votes_subject_uri_idx", 1142 + "columns": [ 1143 + { 1144 + "expression": "subject_uri", 1145 + "isExpression": false, 1146 + "asc": true, 1147 + "nulls": "last" 1148 + } 1149 + ], 1150 + "isUnique": false, 1151 + "concurrently": false, 1152 + "method": "btree", 1153 + "with": {} 1154 + }, 1155 + "votes_community_did_idx": { 1156 + "name": "votes_community_did_idx", 1157 + "columns": [ 1158 + { 1159 + "expression": "community_did", 1160 + "isExpression": false, 1161 + "asc": true, 1162 + "nulls": "last" 1163 + } 1164 + ], 1165 + "isUnique": false, 1166 + "concurrently": false, 1167 + "method": "btree", 1168 + "with": {} 1169 + } 1170 + }, 1171 + "foreignKeys": {}, 1172 + "compositePrimaryKeys": {}, 1173 + "uniqueConstraints": { 1174 + "votes_author_subject_uniq": { 1175 + "name": "votes_author_subject_uniq", 1176 + "nullsNotDistinct": false, 1177 + "columns": ["author_did", "subject_uri"] 1178 + } 1179 + }, 1180 + "policies": {}, 1181 + "checkConstraints": {}, 1182 + "isRLSEnabled": false 1183 + }, 1184 + "public.tracked_repos": { 1185 + "name": "tracked_repos", 1186 + "schema": "", 1187 + "columns": { 1188 + "did": { 1189 + "name": "did", 1190 + "type": "text", 1191 + "primaryKey": true, 1192 + "notNull": true 1193 + }, 1194 + "tracked_at": { 1195 + "name": "tracked_at", 1196 + "type": "timestamp with time zone", 1197 + "primaryKey": false, 1198 + "notNull": true, 1199 + "default": "now()" 1200 + } 1201 + }, 1202 + "indexes": {}, 1203 + "foreignKeys": {}, 1204 + "compositePrimaryKeys": {}, 1205 + "uniqueConstraints": {}, 1206 + "policies": {}, 1207 + "checkConstraints": {}, 1208 + "isRLSEnabled": false 1209 + }, 1210 + "public.community_settings": { 1211 + "name": "community_settings", 1212 + "schema": "", 1213 + "columns": { 1214 + "community_did": { 1215 + "name": "community_did", 1216 + "type": "text", 1217 + "primaryKey": true, 1218 + "notNull": true 1219 + }, 1220 + "domains": { 1221 + "name": "domains", 1222 + "type": "jsonb", 1223 + "primaryKey": false, 1224 + "notNull": true, 1225 + "default": "'[]'::jsonb" 1226 + }, 1227 + "initialized": { 1228 + "name": "initialized", 1229 + "type": "boolean", 1230 + "primaryKey": false, 1231 + "notNull": true, 1232 + "default": false 1233 + }, 1234 + "admin_did": { 1235 + "name": "admin_did", 1236 + "type": "text", 1237 + "primaryKey": false, 1238 + "notNull": false 1239 + }, 1240 + "community_name": { 1241 + "name": "community_name", 1242 + "type": "text", 1243 + "primaryKey": false, 1244 + "notNull": true, 1245 + "default": "'Barazo Community'" 1246 + }, 1247 + "maturity_rating": { 1248 + "name": "maturity_rating", 1249 + "type": "text", 1250 + "primaryKey": false, 1251 + "notNull": true, 1252 + "default": "'safe'" 1253 + }, 1254 + "reaction_set": { 1255 + "name": "reaction_set", 1256 + "type": "jsonb", 1257 + "primaryKey": false, 1258 + "notNull": true, 1259 + "default": "'[\"like\"]'::jsonb" 1260 + }, 1261 + "moderation_thresholds": { 1262 + "name": "moderation_thresholds", 1263 + "type": "jsonb", 1264 + "primaryKey": false, 1265 + "notNull": true, 1266 + "default": "'{\"autoBlockReportCount\":5,\"warnThreshold\":3,\"firstPostQueueCount\":0,\"newAccountDays\":7,\"newAccountWriteRatePerMin\":3,\"establishedWriteRatePerMin\":10,\"linkHoldEnabled\":false,\"topicCreationDelayEnabled\":false,\"burstPostCount\":5,\"burstWindowMinutes\":10,\"trustedPostThreshold\":10}'::jsonb" 1267 + }, 1268 + "word_filter": { 1269 + "name": "word_filter", 1270 + "type": "jsonb", 1271 + "primaryKey": false, 1272 + "notNull": true, 1273 + "default": "'[]'::jsonb" 1274 + }, 1275 + "jurisdiction_country": { 1276 + "name": "jurisdiction_country", 1277 + "type": "text", 1278 + "primaryKey": false, 1279 + "notNull": false 1280 + }, 1281 + "age_threshold": { 1282 + "name": "age_threshold", 1283 + "type": "integer", 1284 + "primaryKey": false, 1285 + "notNull": true, 1286 + "default": 16 1287 + }, 1288 + "max_reply_depth": { 1289 + "name": "max_reply_depth", 1290 + "type": "integer", 1291 + "primaryKey": false, 1292 + "notNull": true, 1293 + "default": 9999 1294 + }, 1295 + "require_login_for_mature": { 1296 + "name": "require_login_for_mature", 1297 + "type": "boolean", 1298 + "primaryKey": false, 1299 + "notNull": true, 1300 + "default": true 1301 + }, 1302 + "community_description": { 1303 + "name": "community_description", 1304 + "type": "text", 1305 + "primaryKey": false, 1306 + "notNull": false 1307 + }, 1308 + "handle": { 1309 + "name": "handle", 1310 + "type": "text", 1311 + "primaryKey": false, 1312 + "notNull": false 1313 + }, 1314 + "service_endpoint": { 1315 + "name": "service_endpoint", 1316 + "type": "text", 1317 + "primaryKey": false, 1318 + "notNull": false 1319 + }, 1320 + "signing_key": { 1321 + "name": "signing_key", 1322 + "type": "text", 1323 + "primaryKey": false, 1324 + "notNull": false 1325 + }, 1326 + "rotation_key": { 1327 + "name": "rotation_key", 1328 + "type": "text", 1329 + "primaryKey": false, 1330 + "notNull": false 1331 + }, 1332 + "community_logo_url": { 1333 + "name": "community_logo_url", 1334 + "type": "text", 1335 + "primaryKey": false, 1336 + "notNull": false 1337 + }, 1338 + "favicon_url": { 1339 + "name": "favicon_url", 1340 + "type": "text", 1341 + "primaryKey": false, 1342 + "notNull": false 1343 + }, 1344 + "header_logo_url": { 1345 + "name": "header_logo_url", 1346 + "type": "text", 1347 + "primaryKey": false, 1348 + "notNull": false 1349 + }, 1350 + "show_community_name": { 1351 + "name": "show_community_name", 1352 + "type": "boolean", 1353 + "primaryKey": false, 1354 + "notNull": true, 1355 + "default": true 1356 + }, 1357 + "primary_color": { 1358 + "name": "primary_color", 1359 + "type": "text", 1360 + "primaryKey": false, 1361 + "notNull": false 1362 + }, 1363 + "accent_color": { 1364 + "name": "accent_color", 1365 + "type": "text", 1366 + "primaryKey": false, 1367 + "notNull": false 1368 + }, 1369 + "created_at": { 1370 + "name": "created_at", 1371 + "type": "timestamp with time zone", 1372 + "primaryKey": false, 1373 + "notNull": true, 1374 + "default": "now()" 1375 + }, 1376 + "updated_at": { 1377 + "name": "updated_at", 1378 + "type": "timestamp with time zone", 1379 + "primaryKey": false, 1380 + "notNull": true, 1381 + "default": "now()" 1382 + } 1383 + }, 1384 + "indexes": {}, 1385 + "foreignKeys": {}, 1386 + "compositePrimaryKeys": {}, 1387 + "uniqueConstraints": {}, 1388 + "policies": { 1389 + "tenant_isolation": { 1390 + "name": "tenant_isolation", 1391 + "as": "PERMISSIVE", 1392 + "for": "ALL", 1393 + "to": ["barazo_app"], 1394 + "using": "community_did = current_setting('app.current_community_did', true)", 1395 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 1396 + } 1397 + }, 1398 + "checkConstraints": {}, 1399 + "isRLSEnabled": true 1400 + }, 1401 + "public.categories": { 1402 + "name": "categories", 1403 + "schema": "", 1404 + "columns": { 1405 + "id": { 1406 + "name": "id", 1407 + "type": "text", 1408 + "primaryKey": true, 1409 + "notNull": true 1410 + }, 1411 + "slug": { 1412 + "name": "slug", 1413 + "type": "text", 1414 + "primaryKey": false, 1415 + "notNull": true 1416 + }, 1417 + "name": { 1418 + "name": "name", 1419 + "type": "text", 1420 + "primaryKey": false, 1421 + "notNull": true 1422 + }, 1423 + "description": { 1424 + "name": "description", 1425 + "type": "text", 1426 + "primaryKey": false, 1427 + "notNull": false 1428 + }, 1429 + "parent_id": { 1430 + "name": "parent_id", 1431 + "type": "text", 1432 + "primaryKey": false, 1433 + "notNull": false 1434 + }, 1435 + "sort_order": { 1436 + "name": "sort_order", 1437 + "type": "integer", 1438 + "primaryKey": false, 1439 + "notNull": true, 1440 + "default": 0 1441 + }, 1442 + "community_did": { 1443 + "name": "community_did", 1444 + "type": "text", 1445 + "primaryKey": false, 1446 + "notNull": true 1447 + }, 1448 + "maturity_rating": { 1449 + "name": "maturity_rating", 1450 + "type": "text", 1451 + "primaryKey": false, 1452 + "notNull": true, 1453 + "default": "'safe'" 1454 + }, 1455 + "created_at": { 1456 + "name": "created_at", 1457 + "type": "timestamp with time zone", 1458 + "primaryKey": false, 1459 + "notNull": true, 1460 + "default": "now()" 1461 + }, 1462 + "updated_at": { 1463 + "name": "updated_at", 1464 + "type": "timestamp with time zone", 1465 + "primaryKey": false, 1466 + "notNull": true, 1467 + "default": "now()" 1468 + } 1469 + }, 1470 + "indexes": { 1471 + "categories_slug_community_did_idx": { 1472 + "name": "categories_slug_community_did_idx", 1473 + "columns": [ 1474 + { 1475 + "expression": "slug", 1476 + "isExpression": false, 1477 + "asc": true, 1478 + "nulls": "last" 1479 + }, 1480 + { 1481 + "expression": "community_did", 1482 + "isExpression": false, 1483 + "asc": true, 1484 + "nulls": "last" 1485 + } 1486 + ], 1487 + "isUnique": true, 1488 + "concurrently": false, 1489 + "method": "btree", 1490 + "with": {} 1491 + }, 1492 + "categories_parent_id_idx": { 1493 + "name": "categories_parent_id_idx", 1494 + "columns": [ 1495 + { 1496 + "expression": "parent_id", 1497 + "isExpression": false, 1498 + "asc": true, 1499 + "nulls": "last" 1500 + } 1501 + ], 1502 + "isUnique": false, 1503 + "concurrently": false, 1504 + "method": "btree", 1505 + "with": {} 1506 + }, 1507 + "categories_community_did_idx": { 1508 + "name": "categories_community_did_idx", 1509 + "columns": [ 1510 + { 1511 + "expression": "community_did", 1512 + "isExpression": false, 1513 + "asc": true, 1514 + "nulls": "last" 1515 + } 1516 + ], 1517 + "isUnique": false, 1518 + "concurrently": false, 1519 + "method": "btree", 1520 + "with": {} 1521 + }, 1522 + "categories_maturity_rating_idx": { 1523 + "name": "categories_maturity_rating_idx", 1524 + "columns": [ 1525 + { 1526 + "expression": "maturity_rating", 1527 + "isExpression": false, 1528 + "asc": true, 1529 + "nulls": "last" 1530 + } 1531 + ], 1532 + "isUnique": false, 1533 + "concurrently": false, 1534 + "method": "btree", 1535 + "with": {} 1536 + } 1537 + }, 1538 + "foreignKeys": { 1539 + "categories_parent_id_fk": { 1540 + "name": "categories_parent_id_fk", 1541 + "tableFrom": "categories", 1542 + "tableTo": "categories", 1543 + "columnsFrom": ["parent_id"], 1544 + "columnsTo": ["id"], 1545 + "onDelete": "set null", 1546 + "onUpdate": "no action" 1547 + } 1548 + }, 1549 + "compositePrimaryKeys": {}, 1550 + "uniqueConstraints": {}, 1551 + "policies": { 1552 + "tenant_isolation": { 1553 + "name": "tenant_isolation", 1554 + "as": "PERMISSIVE", 1555 + "for": "ALL", 1556 + "to": ["barazo_app"], 1557 + "using": "community_did = current_setting('app.current_community_did', true)", 1558 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 1559 + } 1560 + }, 1561 + "checkConstraints": {}, 1562 + "isRLSEnabled": true 1563 + }, 1564 + "public.moderation_actions": { 1565 + "name": "moderation_actions", 1566 + "schema": "", 1567 + "columns": { 1568 + "id": { 1569 + "name": "id", 1570 + "type": "serial", 1571 + "primaryKey": true, 1572 + "notNull": true 1573 + }, 1574 + "action": { 1575 + "name": "action", 1576 + "type": "text", 1577 + "primaryKey": false, 1578 + "notNull": true 1579 + }, 1580 + "target_uri": { 1581 + "name": "target_uri", 1582 + "type": "text", 1583 + "primaryKey": false, 1584 + "notNull": false 1585 + }, 1586 + "target_did": { 1587 + "name": "target_did", 1588 + "type": "text", 1589 + "primaryKey": false, 1590 + "notNull": false 1591 + }, 1592 + "moderator_did": { 1593 + "name": "moderator_did", 1594 + "type": "text", 1595 + "primaryKey": false, 1596 + "notNull": true 1597 + }, 1598 + "community_did": { 1599 + "name": "community_did", 1600 + "type": "text", 1601 + "primaryKey": false, 1602 + "notNull": true 1603 + }, 1604 + "reason": { 1605 + "name": "reason", 1606 + "type": "text", 1607 + "primaryKey": false, 1608 + "notNull": false 1609 + }, 1610 + "created_at": { 1611 + "name": "created_at", 1612 + "type": "timestamp with time zone", 1613 + "primaryKey": false, 1614 + "notNull": true, 1615 + "default": "now()" 1616 + } 1617 + }, 1618 + "indexes": { 1619 + "mod_actions_moderator_did_idx": { 1620 + "name": "mod_actions_moderator_did_idx", 1621 + "columns": [ 1622 + { 1623 + "expression": "moderator_did", 1624 + "isExpression": false, 1625 + "asc": true, 1626 + "nulls": "last" 1627 + } 1628 + ], 1629 + "isUnique": false, 1630 + "concurrently": false, 1631 + "method": "btree", 1632 + "with": {} 1633 + }, 1634 + "mod_actions_community_did_idx": { 1635 + "name": "mod_actions_community_did_idx", 1636 + "columns": [ 1637 + { 1638 + "expression": "community_did", 1639 + "isExpression": false, 1640 + "asc": true, 1641 + "nulls": "last" 1642 + } 1643 + ], 1644 + "isUnique": false, 1645 + "concurrently": false, 1646 + "method": "btree", 1647 + "with": {} 1648 + }, 1649 + "mod_actions_created_at_idx": { 1650 + "name": "mod_actions_created_at_idx", 1651 + "columns": [ 1652 + { 1653 + "expression": "created_at", 1654 + "isExpression": false, 1655 + "asc": true, 1656 + "nulls": "last" 1657 + } 1658 + ], 1659 + "isUnique": false, 1660 + "concurrently": false, 1661 + "method": "btree", 1662 + "with": {} 1663 + }, 1664 + "mod_actions_target_uri_idx": { 1665 + "name": "mod_actions_target_uri_idx", 1666 + "columns": [ 1667 + { 1668 + "expression": "target_uri", 1669 + "isExpression": false, 1670 + "asc": true, 1671 + "nulls": "last" 1672 + } 1673 + ], 1674 + "isUnique": false, 1675 + "concurrently": false, 1676 + "method": "btree", 1677 + "with": {} 1678 + }, 1679 + "mod_actions_target_did_idx": { 1680 + "name": "mod_actions_target_did_idx", 1681 + "columns": [ 1682 + { 1683 + "expression": "target_did", 1684 + "isExpression": false, 1685 + "asc": true, 1686 + "nulls": "last" 1687 + } 1688 + ], 1689 + "isUnique": false, 1690 + "concurrently": false, 1691 + "method": "btree", 1692 + "with": {} 1693 + } 1694 + }, 1695 + "foreignKeys": {}, 1696 + "compositePrimaryKeys": {}, 1697 + "uniqueConstraints": {}, 1698 + "policies": { 1699 + "tenant_isolation": { 1700 + "name": "tenant_isolation", 1701 + "as": "PERMISSIVE", 1702 + "for": "ALL", 1703 + "to": ["barazo_app"], 1704 + "using": "community_did = current_setting('app.current_community_did', true)", 1705 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 1706 + } 1707 + }, 1708 + "checkConstraints": {}, 1709 + "isRLSEnabled": true 1710 + }, 1711 + "public.reports": { 1712 + "name": "reports", 1713 + "schema": "", 1714 + "columns": { 1715 + "id": { 1716 + "name": "id", 1717 + "type": "serial", 1718 + "primaryKey": true, 1719 + "notNull": true 1720 + }, 1721 + "reporter_did": { 1722 + "name": "reporter_did", 1723 + "type": "text", 1724 + "primaryKey": false, 1725 + "notNull": true 1726 + }, 1727 + "target_uri": { 1728 + "name": "target_uri", 1729 + "type": "text", 1730 + "primaryKey": false, 1731 + "notNull": true 1732 + }, 1733 + "target_did": { 1734 + "name": "target_did", 1735 + "type": "text", 1736 + "primaryKey": false, 1737 + "notNull": true 1738 + }, 1739 + "reason_type": { 1740 + "name": "reason_type", 1741 + "type": "text", 1742 + "primaryKey": false, 1743 + "notNull": true 1744 + }, 1745 + "description": { 1746 + "name": "description", 1747 + "type": "text", 1748 + "primaryKey": false, 1749 + "notNull": false 1750 + }, 1751 + "community_did": { 1752 + "name": "community_did", 1753 + "type": "text", 1754 + "primaryKey": false, 1755 + "notNull": true 1756 + }, 1757 + "status": { 1758 + "name": "status", 1759 + "type": "text", 1760 + "primaryKey": false, 1761 + "notNull": true, 1762 + "default": "'pending'" 1763 + }, 1764 + "resolution_type": { 1765 + "name": "resolution_type", 1766 + "type": "text", 1767 + "primaryKey": false, 1768 + "notNull": false 1769 + }, 1770 + "resolved_by": { 1771 + "name": "resolved_by", 1772 + "type": "text", 1773 + "primaryKey": false, 1774 + "notNull": false 1775 + }, 1776 + "resolved_at": { 1777 + "name": "resolved_at", 1778 + "type": "timestamp with time zone", 1779 + "primaryKey": false, 1780 + "notNull": false 1781 + }, 1782 + "appeal_reason": { 1783 + "name": "appeal_reason", 1784 + "type": "text", 1785 + "primaryKey": false, 1786 + "notNull": false 1787 + }, 1788 + "appealed_at": { 1789 + "name": "appealed_at", 1790 + "type": "timestamp with time zone", 1791 + "primaryKey": false, 1792 + "notNull": false 1793 + }, 1794 + "appeal_status": { 1795 + "name": "appeal_status", 1796 + "type": "text", 1797 + "primaryKey": false, 1798 + "notNull": true, 1799 + "default": "'none'" 1800 + }, 1801 + "created_at": { 1802 + "name": "created_at", 1803 + "type": "timestamp with time zone", 1804 + "primaryKey": false, 1805 + "notNull": true, 1806 + "default": "now()" 1807 + } 1808 + }, 1809 + "indexes": { 1810 + "reports_reporter_did_idx": { 1811 + "name": "reports_reporter_did_idx", 1812 + "columns": [ 1813 + { 1814 + "expression": "reporter_did", 1815 + "isExpression": false, 1816 + "asc": true, 1817 + "nulls": "last" 1818 + } 1819 + ], 1820 + "isUnique": false, 1821 + "concurrently": false, 1822 + "method": "btree", 1823 + "with": {} 1824 + }, 1825 + "reports_target_uri_idx": { 1826 + "name": "reports_target_uri_idx", 1827 + "columns": [ 1828 + { 1829 + "expression": "target_uri", 1830 + "isExpression": false, 1831 + "asc": true, 1832 + "nulls": "last" 1833 + } 1834 + ], 1835 + "isUnique": false, 1836 + "concurrently": false, 1837 + "method": "btree", 1838 + "with": {} 1839 + }, 1840 + "reports_target_did_idx": { 1841 + "name": "reports_target_did_idx", 1842 + "columns": [ 1843 + { 1844 + "expression": "target_did", 1845 + "isExpression": false, 1846 + "asc": true, 1847 + "nulls": "last" 1848 + } 1849 + ], 1850 + "isUnique": false, 1851 + "concurrently": false, 1852 + "method": "btree", 1853 + "with": {} 1854 + }, 1855 + "reports_community_did_idx": { 1856 + "name": "reports_community_did_idx", 1857 + "columns": [ 1858 + { 1859 + "expression": "community_did", 1860 + "isExpression": false, 1861 + "asc": true, 1862 + "nulls": "last" 1863 + } 1864 + ], 1865 + "isUnique": false, 1866 + "concurrently": false, 1867 + "method": "btree", 1868 + "with": {} 1869 + }, 1870 + "reports_status_idx": { 1871 + "name": "reports_status_idx", 1872 + "columns": [ 1873 + { 1874 + "expression": "status", 1875 + "isExpression": false, 1876 + "asc": true, 1877 + "nulls": "last" 1878 + } 1879 + ], 1880 + "isUnique": false, 1881 + "concurrently": false, 1882 + "method": "btree", 1883 + "with": {} 1884 + }, 1885 + "reports_created_at_idx": { 1886 + "name": "reports_created_at_idx", 1887 + "columns": [ 1888 + { 1889 + "expression": "created_at", 1890 + "isExpression": false, 1891 + "asc": true, 1892 + "nulls": "last" 1893 + } 1894 + ], 1895 + "isUnique": false, 1896 + "concurrently": false, 1897 + "method": "btree", 1898 + "with": {} 1899 + }, 1900 + "reports_unique_reporter_target_idx": { 1901 + "name": "reports_unique_reporter_target_idx", 1902 + "columns": [ 1903 + { 1904 + "expression": "reporter_did", 1905 + "isExpression": false, 1906 + "asc": true, 1907 + "nulls": "last" 1908 + }, 1909 + { 1910 + "expression": "target_uri", 1911 + "isExpression": false, 1912 + "asc": true, 1913 + "nulls": "last" 1914 + }, 1915 + { 1916 + "expression": "community_did", 1917 + "isExpression": false, 1918 + "asc": true, 1919 + "nulls": "last" 1920 + } 1921 + ], 1922 + "isUnique": true, 1923 + "concurrently": false, 1924 + "method": "btree", 1925 + "with": {} 1926 + } 1927 + }, 1928 + "foreignKeys": {}, 1929 + "compositePrimaryKeys": {}, 1930 + "uniqueConstraints": {}, 1931 + "policies": { 1932 + "tenant_isolation": { 1933 + "name": "tenant_isolation", 1934 + "as": "PERMISSIVE", 1935 + "for": "ALL", 1936 + "to": ["barazo_app"], 1937 + "using": "community_did = current_setting('app.current_community_did', true)", 1938 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 1939 + } 1940 + }, 1941 + "checkConstraints": {}, 1942 + "isRLSEnabled": true 1943 + }, 1944 + "public.notifications": { 1945 + "name": "notifications", 1946 + "schema": "", 1947 + "columns": { 1948 + "id": { 1949 + "name": "id", 1950 + "type": "serial", 1951 + "primaryKey": true, 1952 + "notNull": true 1953 + }, 1954 + "recipient_did": { 1955 + "name": "recipient_did", 1956 + "type": "text", 1957 + "primaryKey": false, 1958 + "notNull": true 1959 + }, 1960 + "type": { 1961 + "name": "type", 1962 + "type": "text", 1963 + "primaryKey": false, 1964 + "notNull": true 1965 + }, 1966 + "subject_uri": { 1967 + "name": "subject_uri", 1968 + "type": "text", 1969 + "primaryKey": false, 1970 + "notNull": true 1971 + }, 1972 + "actor_did": { 1973 + "name": "actor_did", 1974 + "type": "text", 1975 + "primaryKey": false, 1976 + "notNull": true 1977 + }, 1978 + "community_did": { 1979 + "name": "community_did", 1980 + "type": "text", 1981 + "primaryKey": false, 1982 + "notNull": true 1983 + }, 1984 + "read": { 1985 + "name": "read", 1986 + "type": "boolean", 1987 + "primaryKey": false, 1988 + "notNull": true, 1989 + "default": false 1990 + }, 1991 + "created_at": { 1992 + "name": "created_at", 1993 + "type": "timestamp with time zone", 1994 + "primaryKey": false, 1995 + "notNull": true, 1996 + "default": "now()" 1997 + } 1998 + }, 1999 + "indexes": { 2000 + "notifications_recipient_did_idx": { 2001 + "name": "notifications_recipient_did_idx", 2002 + "columns": [ 2003 + { 2004 + "expression": "recipient_did", 2005 + "isExpression": false, 2006 + "asc": true, 2007 + "nulls": "last" 2008 + } 2009 + ], 2010 + "isUnique": false, 2011 + "concurrently": false, 2012 + "method": "btree", 2013 + "with": {} 2014 + }, 2015 + "notifications_recipient_read_idx": { 2016 + "name": "notifications_recipient_read_idx", 2017 + "columns": [ 2018 + { 2019 + "expression": "recipient_did", 2020 + "isExpression": false, 2021 + "asc": true, 2022 + "nulls": "last" 2023 + }, 2024 + { 2025 + "expression": "read", 2026 + "isExpression": false, 2027 + "asc": true, 2028 + "nulls": "last" 2029 + } 2030 + ], 2031 + "isUnique": false, 2032 + "concurrently": false, 2033 + "method": "btree", 2034 + "with": {} 2035 + }, 2036 + "notifications_created_at_idx": { 2037 + "name": "notifications_created_at_idx", 2038 + "columns": [ 2039 + { 2040 + "expression": "created_at", 2041 + "isExpression": false, 2042 + "asc": true, 2043 + "nulls": "last" 2044 + } 2045 + ], 2046 + "isUnique": false, 2047 + "concurrently": false, 2048 + "method": "btree", 2049 + "with": {} 2050 + } 2051 + }, 2052 + "foreignKeys": {}, 2053 + "compositePrimaryKeys": {}, 2054 + "uniqueConstraints": {}, 2055 + "policies": { 2056 + "tenant_isolation": { 2057 + "name": "tenant_isolation", 2058 + "as": "PERMISSIVE", 2059 + "for": "ALL", 2060 + "to": ["barazo_app"], 2061 + "using": "community_did = current_setting('app.current_community_did', true)", 2062 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2063 + } 2064 + }, 2065 + "checkConstraints": {}, 2066 + "isRLSEnabled": true 2067 + }, 2068 + "public.user_community_preferences": { 2069 + "name": "user_community_preferences", 2070 + "schema": "", 2071 + "columns": { 2072 + "did": { 2073 + "name": "did", 2074 + "type": "text", 2075 + "primaryKey": false, 2076 + "notNull": true 2077 + }, 2078 + "community_did": { 2079 + "name": "community_did", 2080 + "type": "text", 2081 + "primaryKey": false, 2082 + "notNull": true 2083 + }, 2084 + "maturity_override": { 2085 + "name": "maturity_override", 2086 + "type": "text", 2087 + "primaryKey": false, 2088 + "notNull": false 2089 + }, 2090 + "muted_words": { 2091 + "name": "muted_words", 2092 + "type": "jsonb", 2093 + "primaryKey": false, 2094 + "notNull": false 2095 + }, 2096 + "blocked_dids": { 2097 + "name": "blocked_dids", 2098 + "type": "jsonb", 2099 + "primaryKey": false, 2100 + "notNull": false 2101 + }, 2102 + "muted_dids": { 2103 + "name": "muted_dids", 2104 + "type": "jsonb", 2105 + "primaryKey": false, 2106 + "notNull": false 2107 + }, 2108 + "notification_prefs": { 2109 + "name": "notification_prefs", 2110 + "type": "jsonb", 2111 + "primaryKey": false, 2112 + "notNull": false 2113 + }, 2114 + "updated_at": { 2115 + "name": "updated_at", 2116 + "type": "timestamp with time zone", 2117 + "primaryKey": false, 2118 + "notNull": true, 2119 + "default": "now()" 2120 + } 2121 + }, 2122 + "indexes": { 2123 + "user_community_prefs_did_idx": { 2124 + "name": "user_community_prefs_did_idx", 2125 + "columns": [ 2126 + { 2127 + "expression": "did", 2128 + "isExpression": false, 2129 + "asc": true, 2130 + "nulls": "last" 2131 + } 2132 + ], 2133 + "isUnique": false, 2134 + "concurrently": false, 2135 + "method": "btree", 2136 + "with": {} 2137 + }, 2138 + "user_community_prefs_community_idx": { 2139 + "name": "user_community_prefs_community_idx", 2140 + "columns": [ 2141 + { 2142 + "expression": "community_did", 2143 + "isExpression": false, 2144 + "asc": true, 2145 + "nulls": "last" 2146 + } 2147 + ], 2148 + "isUnique": false, 2149 + "concurrently": false, 2150 + "method": "btree", 2151 + "with": {} 2152 + } 2153 + }, 2154 + "foreignKeys": {}, 2155 + "compositePrimaryKeys": { 2156 + "user_community_preferences_did_community_did_pk": { 2157 + "name": "user_community_preferences_did_community_did_pk", 2158 + "columns": ["did", "community_did"] 2159 + } 2160 + }, 2161 + "uniqueConstraints": {}, 2162 + "policies": { 2163 + "tenant_isolation": { 2164 + "name": "tenant_isolation", 2165 + "as": "PERMISSIVE", 2166 + "for": "ALL", 2167 + "to": ["barazo_app"], 2168 + "using": "community_did = current_setting('app.current_community_did', true)", 2169 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2170 + } 2171 + }, 2172 + "checkConstraints": {}, 2173 + "isRLSEnabled": true 2174 + }, 2175 + "public.user_preferences": { 2176 + "name": "user_preferences", 2177 + "schema": "", 2178 + "columns": { 2179 + "did": { 2180 + "name": "did", 2181 + "type": "text", 2182 + "primaryKey": true, 2183 + "notNull": true 2184 + }, 2185 + "maturity_level": { 2186 + "name": "maturity_level", 2187 + "type": "text", 2188 + "primaryKey": false, 2189 + "notNull": true, 2190 + "default": "'sfw'" 2191 + }, 2192 + "declared_age": { 2193 + "name": "declared_age", 2194 + "type": "integer", 2195 + "primaryKey": false, 2196 + "notNull": false 2197 + }, 2198 + "muted_words": { 2199 + "name": "muted_words", 2200 + "type": "jsonb", 2201 + "primaryKey": false, 2202 + "notNull": true, 2203 + "default": "'[]'::jsonb" 2204 + }, 2205 + "blocked_dids": { 2206 + "name": "blocked_dids", 2207 + "type": "jsonb", 2208 + "primaryKey": false, 2209 + "notNull": true, 2210 + "default": "'[]'::jsonb" 2211 + }, 2212 + "muted_dids": { 2213 + "name": "muted_dids", 2214 + "type": "jsonb", 2215 + "primaryKey": false, 2216 + "notNull": true, 2217 + "default": "'[]'::jsonb" 2218 + }, 2219 + "cross_post_bluesky": { 2220 + "name": "cross_post_bluesky", 2221 + "type": "boolean", 2222 + "primaryKey": false, 2223 + "notNull": true, 2224 + "default": false 2225 + }, 2226 + "cross_post_frontpage": { 2227 + "name": "cross_post_frontpage", 2228 + "type": "boolean", 2229 + "primaryKey": false, 2230 + "notNull": true, 2231 + "default": false 2232 + }, 2233 + "cross_post_scopes_granted": { 2234 + "name": "cross_post_scopes_granted", 2235 + "type": "boolean", 2236 + "primaryKey": false, 2237 + "notNull": true, 2238 + "default": false 2239 + }, 2240 + "updated_at": { 2241 + "name": "updated_at", 2242 + "type": "timestamp with time zone", 2243 + "primaryKey": false, 2244 + "notNull": true, 2245 + "default": "now()" 2246 + } 2247 + }, 2248 + "indexes": {}, 2249 + "foreignKeys": {}, 2250 + "compositePrimaryKeys": {}, 2251 + "uniqueConstraints": {}, 2252 + "policies": {}, 2253 + "checkConstraints": {}, 2254 + "isRLSEnabled": false 2255 + }, 2256 + "public.cross_posts": { 2257 + "name": "cross_posts", 2258 + "schema": "", 2259 + "columns": { 2260 + "id": { 2261 + "name": "id", 2262 + "type": "text", 2263 + "primaryKey": true, 2264 + "notNull": true 2265 + }, 2266 + "topic_uri": { 2267 + "name": "topic_uri", 2268 + "type": "text", 2269 + "primaryKey": false, 2270 + "notNull": true 2271 + }, 2272 + "service": { 2273 + "name": "service", 2274 + "type": "text", 2275 + "primaryKey": false, 2276 + "notNull": true 2277 + }, 2278 + "cross_post_uri": { 2279 + "name": "cross_post_uri", 2280 + "type": "text", 2281 + "primaryKey": false, 2282 + "notNull": true 2283 + }, 2284 + "cross_post_cid": { 2285 + "name": "cross_post_cid", 2286 + "type": "text", 2287 + "primaryKey": false, 2288 + "notNull": true 2289 + }, 2290 + "author_did": { 2291 + "name": "author_did", 2292 + "type": "text", 2293 + "primaryKey": false, 2294 + "notNull": true 2295 + }, 2296 + "created_at": { 2297 + "name": "created_at", 2298 + "type": "timestamp with time zone", 2299 + "primaryKey": false, 2300 + "notNull": true, 2301 + "default": "now()" 2302 + } 2303 + }, 2304 + "indexes": { 2305 + "cross_posts_topic_uri_idx": { 2306 + "name": "cross_posts_topic_uri_idx", 2307 + "columns": [ 2308 + { 2309 + "expression": "topic_uri", 2310 + "isExpression": false, 2311 + "asc": true, 2312 + "nulls": "last" 2313 + } 2314 + ], 2315 + "isUnique": false, 2316 + "concurrently": false, 2317 + "method": "btree", 2318 + "with": {} 2319 + }, 2320 + "cross_posts_author_did_idx": { 2321 + "name": "cross_posts_author_did_idx", 2322 + "columns": [ 2323 + { 2324 + "expression": "author_did", 2325 + "isExpression": false, 2326 + "asc": true, 2327 + "nulls": "last" 2328 + } 2329 + ], 2330 + "isUnique": false, 2331 + "concurrently": false, 2332 + "method": "btree", 2333 + "with": {} 2334 + } 2335 + }, 2336 + "foreignKeys": {}, 2337 + "compositePrimaryKeys": {}, 2338 + "uniqueConstraints": {}, 2339 + "policies": {}, 2340 + "checkConstraints": {}, 2341 + "isRLSEnabled": false 2342 + }, 2343 + "public.community_onboarding_fields": { 2344 + "name": "community_onboarding_fields", 2345 + "schema": "", 2346 + "columns": { 2347 + "id": { 2348 + "name": "id", 2349 + "type": "text", 2350 + "primaryKey": true, 2351 + "notNull": true 2352 + }, 2353 + "community_did": { 2354 + "name": "community_did", 2355 + "type": "text", 2356 + "primaryKey": false, 2357 + "notNull": true 2358 + }, 2359 + "field_type": { 2360 + "name": "field_type", 2361 + "type": "text", 2362 + "primaryKey": false, 2363 + "notNull": true 2364 + }, 2365 + "label": { 2366 + "name": "label", 2367 + "type": "text", 2368 + "primaryKey": false, 2369 + "notNull": true 2370 + }, 2371 + "description": { 2372 + "name": "description", 2373 + "type": "text", 2374 + "primaryKey": false, 2375 + "notNull": false 2376 + }, 2377 + "is_mandatory": { 2378 + "name": "is_mandatory", 2379 + "type": "boolean", 2380 + "primaryKey": false, 2381 + "notNull": true, 2382 + "default": true 2383 + }, 2384 + "sort_order": { 2385 + "name": "sort_order", 2386 + "type": "integer", 2387 + "primaryKey": false, 2388 + "notNull": true, 2389 + "default": 0 2390 + }, 2391 + "source": { 2392 + "name": "source", 2393 + "type": "text", 2394 + "primaryKey": false, 2395 + "notNull": true, 2396 + "default": "'admin'" 2397 + }, 2398 + "config": { 2399 + "name": "config", 2400 + "type": "jsonb", 2401 + "primaryKey": false, 2402 + "notNull": false 2403 + }, 2404 + "created_at": { 2405 + "name": "created_at", 2406 + "type": "timestamp with time zone", 2407 + "primaryKey": false, 2408 + "notNull": true, 2409 + "default": "now()" 2410 + }, 2411 + "updated_at": { 2412 + "name": "updated_at", 2413 + "type": "timestamp with time zone", 2414 + "primaryKey": false, 2415 + "notNull": true, 2416 + "default": "now()" 2417 + } 2418 + }, 2419 + "indexes": { 2420 + "onboarding_fields_community_idx": { 2421 + "name": "onboarding_fields_community_idx", 2422 + "columns": [ 2423 + { 2424 + "expression": "community_did", 2425 + "isExpression": false, 2426 + "asc": true, 2427 + "nulls": "last" 2428 + } 2429 + ], 2430 + "isUnique": false, 2431 + "concurrently": false, 2432 + "method": "btree", 2433 + "with": {} 2434 + } 2435 + }, 2436 + "foreignKeys": {}, 2437 + "compositePrimaryKeys": {}, 2438 + "uniqueConstraints": {}, 2439 + "policies": { 2440 + "tenant_isolation": { 2441 + "name": "tenant_isolation", 2442 + "as": "PERMISSIVE", 2443 + "for": "ALL", 2444 + "to": ["barazo_app"], 2445 + "using": "community_did = current_setting('app.current_community_did', true)", 2446 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2447 + } 2448 + }, 2449 + "checkConstraints": {}, 2450 + "isRLSEnabled": true 2451 + }, 2452 + "public.user_onboarding_responses": { 2453 + "name": "user_onboarding_responses", 2454 + "schema": "", 2455 + "columns": { 2456 + "did": { 2457 + "name": "did", 2458 + "type": "text", 2459 + "primaryKey": false, 2460 + "notNull": true 2461 + }, 2462 + "community_did": { 2463 + "name": "community_did", 2464 + "type": "text", 2465 + "primaryKey": false, 2466 + "notNull": true 2467 + }, 2468 + "field_id": { 2469 + "name": "field_id", 2470 + "type": "text", 2471 + "primaryKey": false, 2472 + "notNull": true 2473 + }, 2474 + "response": { 2475 + "name": "response", 2476 + "type": "jsonb", 2477 + "primaryKey": false, 2478 + "notNull": true 2479 + }, 2480 + "completed_at": { 2481 + "name": "completed_at", 2482 + "type": "timestamp with time zone", 2483 + "primaryKey": false, 2484 + "notNull": true, 2485 + "default": "now()" 2486 + } 2487 + }, 2488 + "indexes": { 2489 + "onboarding_responses_did_community_idx": { 2490 + "name": "onboarding_responses_did_community_idx", 2491 + "columns": [ 2492 + { 2493 + "expression": "did", 2494 + "isExpression": false, 2495 + "asc": true, 2496 + "nulls": "last" 2497 + }, 2498 + { 2499 + "expression": "community_did", 2500 + "isExpression": false, 2501 + "asc": true, 2502 + "nulls": "last" 2503 + } 2504 + ], 2505 + "isUnique": false, 2506 + "concurrently": false, 2507 + "method": "btree", 2508 + "with": {} 2509 + } 2510 + }, 2511 + "foreignKeys": {}, 2512 + "compositePrimaryKeys": { 2513 + "user_onboarding_responses_did_community_did_field_id_pk": { 2514 + "name": "user_onboarding_responses_did_community_did_field_id_pk", 2515 + "columns": ["did", "community_did", "field_id"] 2516 + } 2517 + }, 2518 + "uniqueConstraints": {}, 2519 + "policies": { 2520 + "tenant_isolation": { 2521 + "name": "tenant_isolation", 2522 + "as": "PERMISSIVE", 2523 + "for": "ALL", 2524 + "to": ["barazo_app"], 2525 + "using": "community_did = current_setting('app.current_community_did', true)", 2526 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2527 + } 2528 + }, 2529 + "checkConstraints": {}, 2530 + "isRLSEnabled": true 2531 + }, 2532 + "public.moderation_queue": { 2533 + "name": "moderation_queue", 2534 + "schema": "", 2535 + "columns": { 2536 + "id": { 2537 + "name": "id", 2538 + "type": "serial", 2539 + "primaryKey": true, 2540 + "notNull": true 2541 + }, 2542 + "content_uri": { 2543 + "name": "content_uri", 2544 + "type": "text", 2545 + "primaryKey": false, 2546 + "notNull": true 2547 + }, 2548 + "content_type": { 2549 + "name": "content_type", 2550 + "type": "text", 2551 + "primaryKey": false, 2552 + "notNull": true 2553 + }, 2554 + "author_did": { 2555 + "name": "author_did", 2556 + "type": "text", 2557 + "primaryKey": false, 2558 + "notNull": true 2559 + }, 2560 + "community_did": { 2561 + "name": "community_did", 2562 + "type": "text", 2563 + "primaryKey": false, 2564 + "notNull": true 2565 + }, 2566 + "queue_reason": { 2567 + "name": "queue_reason", 2568 + "type": "text", 2569 + "primaryKey": false, 2570 + "notNull": true 2571 + }, 2572 + "matched_words": { 2573 + "name": "matched_words", 2574 + "type": "jsonb", 2575 + "primaryKey": false, 2576 + "notNull": false 2577 + }, 2578 + "status": { 2579 + "name": "status", 2580 + "type": "text", 2581 + "primaryKey": false, 2582 + "notNull": true, 2583 + "default": "'pending'" 2584 + }, 2585 + "reviewed_by": { 2586 + "name": "reviewed_by", 2587 + "type": "text", 2588 + "primaryKey": false, 2589 + "notNull": false 2590 + }, 2591 + "created_at": { 2592 + "name": "created_at", 2593 + "type": "timestamp with time zone", 2594 + "primaryKey": false, 2595 + "notNull": true, 2596 + "default": "now()" 2597 + }, 2598 + "reviewed_at": { 2599 + "name": "reviewed_at", 2600 + "type": "timestamp with time zone", 2601 + "primaryKey": false, 2602 + "notNull": false 2603 + } 2604 + }, 2605 + "indexes": { 2606 + "mod_queue_author_did_idx": { 2607 + "name": "mod_queue_author_did_idx", 2608 + "columns": [ 2609 + { 2610 + "expression": "author_did", 2611 + "isExpression": false, 2612 + "asc": true, 2613 + "nulls": "last" 2614 + } 2615 + ], 2616 + "isUnique": false, 2617 + "concurrently": false, 2618 + "method": "btree", 2619 + "with": {} 2620 + }, 2621 + "mod_queue_community_did_idx": { 2622 + "name": "mod_queue_community_did_idx", 2623 + "columns": [ 2624 + { 2625 + "expression": "community_did", 2626 + "isExpression": false, 2627 + "asc": true, 2628 + "nulls": "last" 2629 + } 2630 + ], 2631 + "isUnique": false, 2632 + "concurrently": false, 2633 + "method": "btree", 2634 + "with": {} 2635 + }, 2636 + "mod_queue_status_idx": { 2637 + "name": "mod_queue_status_idx", 2638 + "columns": [ 2639 + { 2640 + "expression": "status", 2641 + "isExpression": false, 2642 + "asc": true, 2643 + "nulls": "last" 2644 + } 2645 + ], 2646 + "isUnique": false, 2647 + "concurrently": false, 2648 + "method": "btree", 2649 + "with": {} 2650 + }, 2651 + "mod_queue_created_at_idx": { 2652 + "name": "mod_queue_created_at_idx", 2653 + "columns": [ 2654 + { 2655 + "expression": "created_at", 2656 + "isExpression": false, 2657 + "asc": true, 2658 + "nulls": "last" 2659 + } 2660 + ], 2661 + "isUnique": false, 2662 + "concurrently": false, 2663 + "method": "btree", 2664 + "with": {} 2665 + }, 2666 + "mod_queue_content_uri_idx": { 2667 + "name": "mod_queue_content_uri_idx", 2668 + "columns": [ 2669 + { 2670 + "expression": "content_uri", 2671 + "isExpression": false, 2672 + "asc": true, 2673 + "nulls": "last" 2674 + } 2675 + ], 2676 + "isUnique": false, 2677 + "concurrently": false, 2678 + "method": "btree", 2679 + "with": {} 2680 + } 2681 + }, 2682 + "foreignKeys": {}, 2683 + "compositePrimaryKeys": {}, 2684 + "uniqueConstraints": {}, 2685 + "policies": { 2686 + "tenant_isolation": { 2687 + "name": "tenant_isolation", 2688 + "as": "PERMISSIVE", 2689 + "for": "ALL", 2690 + "to": ["barazo_app"], 2691 + "using": "community_did = current_setting('app.current_community_did', true)", 2692 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2693 + } 2694 + }, 2695 + "checkConstraints": {}, 2696 + "isRLSEnabled": true 2697 + }, 2698 + "public.account_trust": { 2699 + "name": "account_trust", 2700 + "schema": "", 2701 + "columns": { 2702 + "id": { 2703 + "name": "id", 2704 + "type": "serial", 2705 + "primaryKey": true, 2706 + "notNull": true 2707 + }, 2708 + "did": { 2709 + "name": "did", 2710 + "type": "text", 2711 + "primaryKey": false, 2712 + "notNull": true 2713 + }, 2714 + "community_did": { 2715 + "name": "community_did", 2716 + "type": "text", 2717 + "primaryKey": false, 2718 + "notNull": true 2719 + }, 2720 + "approved_post_count": { 2721 + "name": "approved_post_count", 2722 + "type": "integer", 2723 + "primaryKey": false, 2724 + "notNull": true, 2725 + "default": 0 2726 + }, 2727 + "is_trusted": { 2728 + "name": "is_trusted", 2729 + "type": "boolean", 2730 + "primaryKey": false, 2731 + "notNull": true, 2732 + "default": false 2733 + }, 2734 + "trusted_at": { 2735 + "name": "trusted_at", 2736 + "type": "timestamp with time zone", 2737 + "primaryKey": false, 2738 + "notNull": false 2739 + } 2740 + }, 2741 + "indexes": { 2742 + "account_trust_did_community_idx": { 2743 + "name": "account_trust_did_community_idx", 2744 + "columns": [ 2745 + { 2746 + "expression": "did", 2747 + "isExpression": false, 2748 + "asc": true, 2749 + "nulls": "last" 2750 + }, 2751 + { 2752 + "expression": "community_did", 2753 + "isExpression": false, 2754 + "asc": true, 2755 + "nulls": "last" 2756 + } 2757 + ], 2758 + "isUnique": true, 2759 + "concurrently": false, 2760 + "method": "btree", 2761 + "with": {} 2762 + }, 2763 + "account_trust_did_idx": { 2764 + "name": "account_trust_did_idx", 2765 + "columns": [ 2766 + { 2767 + "expression": "did", 2768 + "isExpression": false, 2769 + "asc": true, 2770 + "nulls": "last" 2771 + } 2772 + ], 2773 + "isUnique": false, 2774 + "concurrently": false, 2775 + "method": "btree", 2776 + "with": {} 2777 + } 2778 + }, 2779 + "foreignKeys": {}, 2780 + "compositePrimaryKeys": {}, 2781 + "uniqueConstraints": {}, 2782 + "policies": { 2783 + "tenant_isolation": { 2784 + "name": "tenant_isolation", 2785 + "as": "PERMISSIVE", 2786 + "for": "ALL", 2787 + "to": ["barazo_app"], 2788 + "using": "community_did = current_setting('app.current_community_did', true)", 2789 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2790 + } 2791 + }, 2792 + "checkConstraints": {}, 2793 + "isRLSEnabled": true 2794 + }, 2795 + "public.community_filters": { 2796 + "name": "community_filters", 2797 + "schema": "", 2798 + "columns": { 2799 + "community_did": { 2800 + "name": "community_did", 2801 + "type": "text", 2802 + "primaryKey": true, 2803 + "notNull": true 2804 + }, 2805 + "status": { 2806 + "name": "status", 2807 + "type": "text", 2808 + "primaryKey": false, 2809 + "notNull": true, 2810 + "default": "'active'" 2811 + }, 2812 + "admin_did": { 2813 + "name": "admin_did", 2814 + "type": "text", 2815 + "primaryKey": false, 2816 + "notNull": false 2817 + }, 2818 + "reason": { 2819 + "name": "reason", 2820 + "type": "text", 2821 + "primaryKey": false, 2822 + "notNull": false 2823 + }, 2824 + "report_count": { 2825 + "name": "report_count", 2826 + "type": "integer", 2827 + "primaryKey": false, 2828 + "notNull": true, 2829 + "default": 0 2830 + }, 2831 + "last_reviewed_at": { 2832 + "name": "last_reviewed_at", 2833 + "type": "timestamp with time zone", 2834 + "primaryKey": false, 2835 + "notNull": false 2836 + }, 2837 + "filtered_by": { 2838 + "name": "filtered_by", 2839 + "type": "text", 2840 + "primaryKey": false, 2841 + "notNull": false 2842 + }, 2843 + "created_at": { 2844 + "name": "created_at", 2845 + "type": "timestamp with time zone", 2846 + "primaryKey": false, 2847 + "notNull": true, 2848 + "default": "now()" 2849 + }, 2850 + "updated_at": { 2851 + "name": "updated_at", 2852 + "type": "timestamp with time zone", 2853 + "primaryKey": false, 2854 + "notNull": true, 2855 + "default": "now()" 2856 + } 2857 + }, 2858 + "indexes": { 2859 + "community_filters_status_idx": { 2860 + "name": "community_filters_status_idx", 2861 + "columns": [ 2862 + { 2863 + "expression": "status", 2864 + "isExpression": false, 2865 + "asc": true, 2866 + "nulls": "last" 2867 + } 2868 + ], 2869 + "isUnique": false, 2870 + "concurrently": false, 2871 + "method": "btree", 2872 + "with": {} 2873 + }, 2874 + "community_filters_admin_did_idx": { 2875 + "name": "community_filters_admin_did_idx", 2876 + "columns": [ 2877 + { 2878 + "expression": "admin_did", 2879 + "isExpression": false, 2880 + "asc": true, 2881 + "nulls": "last" 2882 + } 2883 + ], 2884 + "isUnique": false, 2885 + "concurrently": false, 2886 + "method": "btree", 2887 + "with": {} 2888 + }, 2889 + "community_filters_updated_at_idx": { 2890 + "name": "community_filters_updated_at_idx", 2891 + "columns": [ 2892 + { 2893 + "expression": "updated_at", 2894 + "isExpression": false, 2895 + "asc": true, 2896 + "nulls": "last" 2897 + } 2898 + ], 2899 + "isUnique": false, 2900 + "concurrently": false, 2901 + "method": "btree", 2902 + "with": {} 2903 + } 2904 + }, 2905 + "foreignKeys": {}, 2906 + "compositePrimaryKeys": {}, 2907 + "uniqueConstraints": {}, 2908 + "policies": { 2909 + "tenant_isolation": { 2910 + "name": "tenant_isolation", 2911 + "as": "PERMISSIVE", 2912 + "for": "ALL", 2913 + "to": ["barazo_app"], 2914 + "using": "community_did = current_setting('app.current_community_did', true)", 2915 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 2916 + } 2917 + }, 2918 + "checkConstraints": {}, 2919 + "isRLSEnabled": true 2920 + }, 2921 + "public.account_filters": { 2922 + "name": "account_filters", 2923 + "schema": "", 2924 + "columns": { 2925 + "id": { 2926 + "name": "id", 2927 + "type": "serial", 2928 + "primaryKey": true, 2929 + "notNull": true 2930 + }, 2931 + "did": { 2932 + "name": "did", 2933 + "type": "text", 2934 + "primaryKey": false, 2935 + "notNull": true 2936 + }, 2937 + "community_did": { 2938 + "name": "community_did", 2939 + "type": "text", 2940 + "primaryKey": false, 2941 + "notNull": true 2942 + }, 2943 + "status": { 2944 + "name": "status", 2945 + "type": "text", 2946 + "primaryKey": false, 2947 + "notNull": true, 2948 + "default": "'active'" 2949 + }, 2950 + "reason": { 2951 + "name": "reason", 2952 + "type": "text", 2953 + "primaryKey": false, 2954 + "notNull": false 2955 + }, 2956 + "report_count": { 2957 + "name": "report_count", 2958 + "type": "integer", 2959 + "primaryKey": false, 2960 + "notNull": true, 2961 + "default": 0 2962 + }, 2963 + "ban_count": { 2964 + "name": "ban_count", 2965 + "type": "integer", 2966 + "primaryKey": false, 2967 + "notNull": true, 2968 + "default": 0 2969 + }, 2970 + "last_reviewed_at": { 2971 + "name": "last_reviewed_at", 2972 + "type": "timestamp with time zone", 2973 + "primaryKey": false, 2974 + "notNull": false 2975 + }, 2976 + "filtered_by": { 2977 + "name": "filtered_by", 2978 + "type": "text", 2979 + "primaryKey": false, 2980 + "notNull": false 2981 + }, 2982 + "created_at": { 2983 + "name": "created_at", 2984 + "type": "timestamp with time zone", 2985 + "primaryKey": false, 2986 + "notNull": true, 2987 + "default": "now()" 2988 + }, 2989 + "updated_at": { 2990 + "name": "updated_at", 2991 + "type": "timestamp with time zone", 2992 + "primaryKey": false, 2993 + "notNull": true, 2994 + "default": "now()" 2995 + } 2996 + }, 2997 + "indexes": { 2998 + "account_filters_did_community_idx": { 2999 + "name": "account_filters_did_community_idx", 3000 + "columns": [ 3001 + { 3002 + "expression": "did", 3003 + "isExpression": false, 3004 + "asc": true, 3005 + "nulls": "last" 3006 + }, 3007 + { 3008 + "expression": "community_did", 3009 + "isExpression": false, 3010 + "asc": true, 3011 + "nulls": "last" 3012 + } 3013 + ], 3014 + "isUnique": true, 3015 + "concurrently": false, 3016 + "method": "btree", 3017 + "with": {} 3018 + }, 3019 + "account_filters_did_idx": { 3020 + "name": "account_filters_did_idx", 3021 + "columns": [ 3022 + { 3023 + "expression": "did", 3024 + "isExpression": false, 3025 + "asc": true, 3026 + "nulls": "last" 3027 + } 3028 + ], 3029 + "isUnique": false, 3030 + "concurrently": false, 3031 + "method": "btree", 3032 + "with": {} 3033 + }, 3034 + "account_filters_community_did_idx": { 3035 + "name": "account_filters_community_did_idx", 3036 + "columns": [ 3037 + { 3038 + "expression": "community_did", 3039 + "isExpression": false, 3040 + "asc": true, 3041 + "nulls": "last" 3042 + } 3043 + ], 3044 + "isUnique": false, 3045 + "concurrently": false, 3046 + "method": "btree", 3047 + "with": {} 3048 + }, 3049 + "account_filters_status_idx": { 3050 + "name": "account_filters_status_idx", 3051 + "columns": [ 3052 + { 3053 + "expression": "status", 3054 + "isExpression": false, 3055 + "asc": true, 3056 + "nulls": "last" 3057 + } 3058 + ], 3059 + "isUnique": false, 3060 + "concurrently": false, 3061 + "method": "btree", 3062 + "with": {} 3063 + }, 3064 + "account_filters_updated_at_idx": { 3065 + "name": "account_filters_updated_at_idx", 3066 + "columns": [ 3067 + { 3068 + "expression": "updated_at", 3069 + "isExpression": false, 3070 + "asc": true, 3071 + "nulls": "last" 3072 + } 3073 + ], 3074 + "isUnique": false, 3075 + "concurrently": false, 3076 + "method": "btree", 3077 + "with": {} 3078 + } 3079 + }, 3080 + "foreignKeys": {}, 3081 + "compositePrimaryKeys": {}, 3082 + "uniqueConstraints": {}, 3083 + "policies": { 3084 + "tenant_isolation": { 3085 + "name": "tenant_isolation", 3086 + "as": "PERMISSIVE", 3087 + "for": "ALL", 3088 + "to": ["barazo_app"], 3089 + "using": "community_did = current_setting('app.current_community_did', true)", 3090 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 3091 + } 3092 + }, 3093 + "checkConstraints": {}, 3094 + "isRLSEnabled": true 3095 + }, 3096 + "public.ozone_labels": { 3097 + "name": "ozone_labels", 3098 + "schema": "", 3099 + "columns": { 3100 + "id": { 3101 + "name": "id", 3102 + "type": "serial", 3103 + "primaryKey": true, 3104 + "notNull": true 3105 + }, 3106 + "src": { 3107 + "name": "src", 3108 + "type": "text", 3109 + "primaryKey": false, 3110 + "notNull": true 3111 + }, 3112 + "uri": { 3113 + "name": "uri", 3114 + "type": "text", 3115 + "primaryKey": false, 3116 + "notNull": true 3117 + }, 3118 + "val": { 3119 + "name": "val", 3120 + "type": "text", 3121 + "primaryKey": false, 3122 + "notNull": true 3123 + }, 3124 + "neg": { 3125 + "name": "neg", 3126 + "type": "boolean", 3127 + "primaryKey": false, 3128 + "notNull": true, 3129 + "default": false 3130 + }, 3131 + "cts": { 3132 + "name": "cts", 3133 + "type": "timestamp with time zone", 3134 + "primaryKey": false, 3135 + "notNull": true 3136 + }, 3137 + "exp": { 3138 + "name": "exp", 3139 + "type": "timestamp with time zone", 3140 + "primaryKey": false, 3141 + "notNull": false 3142 + }, 3143 + "indexed_at": { 3144 + "name": "indexed_at", 3145 + "type": "timestamp with time zone", 3146 + "primaryKey": false, 3147 + "notNull": true, 3148 + "default": "now()" 3149 + } 3150 + }, 3151 + "indexes": { 3152 + "ozone_labels_src_uri_val_idx": { 3153 + "name": "ozone_labels_src_uri_val_idx", 3154 + "columns": [ 3155 + { 3156 + "expression": "src", 3157 + "isExpression": false, 3158 + "asc": true, 3159 + "nulls": "last" 3160 + }, 3161 + { 3162 + "expression": "uri", 3163 + "isExpression": false, 3164 + "asc": true, 3165 + "nulls": "last" 3166 + }, 3167 + { 3168 + "expression": "val", 3169 + "isExpression": false, 3170 + "asc": true, 3171 + "nulls": "last" 3172 + } 3173 + ], 3174 + "isUnique": true, 3175 + "concurrently": false, 3176 + "method": "btree", 3177 + "with": {} 3178 + }, 3179 + "ozone_labels_uri_idx": { 3180 + "name": "ozone_labels_uri_idx", 3181 + "columns": [ 3182 + { 3183 + "expression": "uri", 3184 + "isExpression": false, 3185 + "asc": true, 3186 + "nulls": "last" 3187 + } 3188 + ], 3189 + "isUnique": false, 3190 + "concurrently": false, 3191 + "method": "btree", 3192 + "with": {} 3193 + }, 3194 + "ozone_labels_val_idx": { 3195 + "name": "ozone_labels_val_idx", 3196 + "columns": [ 3197 + { 3198 + "expression": "val", 3199 + "isExpression": false, 3200 + "asc": true, 3201 + "nulls": "last" 3202 + } 3203 + ], 3204 + "isUnique": false, 3205 + "concurrently": false, 3206 + "method": "btree", 3207 + "with": {} 3208 + }, 3209 + "ozone_labels_indexed_at_idx": { 3210 + "name": "ozone_labels_indexed_at_idx", 3211 + "columns": [ 3212 + { 3213 + "expression": "indexed_at", 3214 + "isExpression": false, 3215 + "asc": true, 3216 + "nulls": "last" 3217 + } 3218 + ], 3219 + "isUnique": false, 3220 + "concurrently": false, 3221 + "method": "btree", 3222 + "with": {} 3223 + } 3224 + }, 3225 + "foreignKeys": {}, 3226 + "compositePrimaryKeys": {}, 3227 + "uniqueConstraints": {}, 3228 + "policies": {}, 3229 + "checkConstraints": {}, 3230 + "isRLSEnabled": false 3231 + }, 3232 + "public.community_profiles": { 3233 + "name": "community_profiles", 3234 + "schema": "", 3235 + "columns": { 3236 + "did": { 3237 + "name": "did", 3238 + "type": "text", 3239 + "primaryKey": false, 3240 + "notNull": true 3241 + }, 3242 + "community_did": { 3243 + "name": "community_did", 3244 + "type": "text", 3245 + "primaryKey": false, 3246 + "notNull": true 3247 + }, 3248 + "display_name": { 3249 + "name": "display_name", 3250 + "type": "text", 3251 + "primaryKey": false, 3252 + "notNull": false 3253 + }, 3254 + "avatar_url": { 3255 + "name": "avatar_url", 3256 + "type": "text", 3257 + "primaryKey": false, 3258 + "notNull": false 3259 + }, 3260 + "banner_url": { 3261 + "name": "banner_url", 3262 + "type": "text", 3263 + "primaryKey": false, 3264 + "notNull": false 3265 + }, 3266 + "bio": { 3267 + "name": "bio", 3268 + "type": "text", 3269 + "primaryKey": false, 3270 + "notNull": false 3271 + }, 3272 + "updated_at": { 3273 + "name": "updated_at", 3274 + "type": "timestamp with time zone", 3275 + "primaryKey": false, 3276 + "notNull": true, 3277 + "default": "now()" 3278 + } 3279 + }, 3280 + "indexes": { 3281 + "community_profiles_did_idx": { 3282 + "name": "community_profiles_did_idx", 3283 + "columns": [ 3284 + { 3285 + "expression": "did", 3286 + "isExpression": false, 3287 + "asc": true, 3288 + "nulls": "last" 3289 + } 3290 + ], 3291 + "isUnique": false, 3292 + "concurrently": false, 3293 + "method": "btree", 3294 + "with": {} 3295 + }, 3296 + "community_profiles_community_idx": { 3297 + "name": "community_profiles_community_idx", 3298 + "columns": [ 3299 + { 3300 + "expression": "community_did", 3301 + "isExpression": false, 3302 + "asc": true, 3303 + "nulls": "last" 3304 + } 3305 + ], 3306 + "isUnique": false, 3307 + "concurrently": false, 3308 + "method": "btree", 3309 + "with": {} 3310 + } 3311 + }, 3312 + "foreignKeys": {}, 3313 + "compositePrimaryKeys": { 3314 + "community_profiles_did_community_did_pk": { 3315 + "name": "community_profiles_did_community_did_pk", 3316 + "columns": ["did", "community_did"] 3317 + } 3318 + }, 3319 + "uniqueConstraints": {}, 3320 + "policies": { 3321 + "tenant_isolation": { 3322 + "name": "tenant_isolation", 3323 + "as": "PERMISSIVE", 3324 + "for": "ALL", 3325 + "to": ["barazo_app"], 3326 + "using": "community_did = current_setting('app.current_community_did', true)", 3327 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 3328 + } 3329 + }, 3330 + "checkConstraints": {}, 3331 + "isRLSEnabled": true 3332 + }, 3333 + "public.interaction_graph": { 3334 + "name": "interaction_graph", 3335 + "schema": "", 3336 + "columns": { 3337 + "source_did": { 3338 + "name": "source_did", 3339 + "type": "text", 3340 + "primaryKey": false, 3341 + "notNull": true 3342 + }, 3343 + "target_did": { 3344 + "name": "target_did", 3345 + "type": "text", 3346 + "primaryKey": false, 3347 + "notNull": true 3348 + }, 3349 + "community_id": { 3350 + "name": "community_id", 3351 + "type": "text", 3352 + "primaryKey": false, 3353 + "notNull": true 3354 + }, 3355 + "interaction_type": { 3356 + "name": "interaction_type", 3357 + "type": "text", 3358 + "primaryKey": false, 3359 + "notNull": true 3360 + }, 3361 + "weight": { 3362 + "name": "weight", 3363 + "type": "integer", 3364 + "primaryKey": false, 3365 + "notNull": true, 3366 + "default": 1 3367 + }, 3368 + "first_interaction_at": { 3369 + "name": "first_interaction_at", 3370 + "type": "timestamp with time zone", 3371 + "primaryKey": false, 3372 + "notNull": true, 3373 + "default": "now()" 3374 + }, 3375 + "last_interaction_at": { 3376 + "name": "last_interaction_at", 3377 + "type": "timestamp with time zone", 3378 + "primaryKey": false, 3379 + "notNull": true, 3380 + "default": "now()" 3381 + } 3382 + }, 3383 + "indexes": { 3384 + "interaction_graph_source_target_community_idx": { 3385 + "name": "interaction_graph_source_target_community_idx", 3386 + "columns": [ 3387 + { 3388 + "expression": "source_did", 3389 + "isExpression": false, 3390 + "asc": true, 3391 + "nulls": "last" 3392 + }, 3393 + { 3394 + "expression": "target_did", 3395 + "isExpression": false, 3396 + "asc": true, 3397 + "nulls": "last" 3398 + }, 3399 + { 3400 + "expression": "community_id", 3401 + "isExpression": false, 3402 + "asc": true, 3403 + "nulls": "last" 3404 + } 3405 + ], 3406 + "isUnique": false, 3407 + "concurrently": false, 3408 + "method": "btree", 3409 + "with": {} 3410 + } 3411 + }, 3412 + "foreignKeys": {}, 3413 + "compositePrimaryKeys": { 3414 + "interaction_graph_source_did_target_did_community_id_interaction_type_pk": { 3415 + "name": "interaction_graph_source_did_target_did_community_id_interaction_type_pk", 3416 + "columns": ["source_did", "target_did", "community_id", "interaction_type"] 3417 + } 3418 + }, 3419 + "uniqueConstraints": {}, 3420 + "policies": {}, 3421 + "checkConstraints": {}, 3422 + "isRLSEnabled": false 3423 + }, 3424 + "public.trust_seeds": { 3425 + "name": "trust_seeds", 3426 + "schema": "", 3427 + "columns": { 3428 + "id": { 3429 + "name": "id", 3430 + "type": "serial", 3431 + "primaryKey": true, 3432 + "notNull": true 3433 + }, 3434 + "did": { 3435 + "name": "did", 3436 + "type": "text", 3437 + "primaryKey": false, 3438 + "notNull": true 3439 + }, 3440 + "community_id": { 3441 + "name": "community_id", 3442 + "type": "text", 3443 + "primaryKey": false, 3444 + "notNull": true, 3445 + "default": "''" 3446 + }, 3447 + "added_by": { 3448 + "name": "added_by", 3449 + "type": "text", 3450 + "primaryKey": false, 3451 + "notNull": true 3452 + }, 3453 + "reason": { 3454 + "name": "reason", 3455 + "type": "text", 3456 + "primaryKey": false, 3457 + "notNull": false 3458 + }, 3459 + "created_at": { 3460 + "name": "created_at", 3461 + "type": "timestamp with time zone", 3462 + "primaryKey": false, 3463 + "notNull": true, 3464 + "default": "now()" 3465 + } 3466 + }, 3467 + "indexes": { 3468 + "trust_seeds_did_community_idx": { 3469 + "name": "trust_seeds_did_community_idx", 3470 + "columns": [ 3471 + { 3472 + "expression": "did", 3473 + "isExpression": false, 3474 + "asc": true, 3475 + "nulls": "last" 3476 + }, 3477 + { 3478 + "expression": "community_id", 3479 + "isExpression": false, 3480 + "asc": true, 3481 + "nulls": "last" 3482 + } 3483 + ], 3484 + "isUnique": true, 3485 + "concurrently": false, 3486 + "method": "btree", 3487 + "with": {} 3488 + } 3489 + }, 3490 + "foreignKeys": {}, 3491 + "compositePrimaryKeys": {}, 3492 + "uniqueConstraints": {}, 3493 + "policies": {}, 3494 + "checkConstraints": {}, 3495 + "isRLSEnabled": false 3496 + }, 3497 + "public.trust_scores": { 3498 + "name": "trust_scores", 3499 + "schema": "", 3500 + "columns": { 3501 + "did": { 3502 + "name": "did", 3503 + "type": "text", 3504 + "primaryKey": false, 3505 + "notNull": true 3506 + }, 3507 + "community_id": { 3508 + "name": "community_id", 3509 + "type": "text", 3510 + "primaryKey": false, 3511 + "notNull": true, 3512 + "default": "''" 3513 + }, 3514 + "score": { 3515 + "name": "score", 3516 + "type": "real", 3517 + "primaryKey": false, 3518 + "notNull": true 3519 + }, 3520 + "computed_at": { 3521 + "name": "computed_at", 3522 + "type": "timestamp with time zone", 3523 + "primaryKey": false, 3524 + "notNull": true, 3525 + "default": "now()" 3526 + } 3527 + }, 3528 + "indexes": { 3529 + "trust_scores_did_community_idx": { 3530 + "name": "trust_scores_did_community_idx", 3531 + "columns": [ 3532 + { 3533 + "expression": "did", 3534 + "isExpression": false, 3535 + "asc": true, 3536 + "nulls": "last" 3537 + }, 3538 + { 3539 + "expression": "community_id", 3540 + "isExpression": false, 3541 + "asc": true, 3542 + "nulls": "last" 3543 + } 3544 + ], 3545 + "isUnique": false, 3546 + "concurrently": false, 3547 + "method": "btree", 3548 + "with": {} 3549 + } 3550 + }, 3551 + "foreignKeys": {}, 3552 + "compositePrimaryKeys": { 3553 + "trust_scores_did_community_id_pk": { 3554 + "name": "trust_scores_did_community_id_pk", 3555 + "columns": ["did", "community_id"] 3556 + } 3557 + }, 3558 + "uniqueConstraints": {}, 3559 + "policies": {}, 3560 + "checkConstraints": {}, 3561 + "isRLSEnabled": false 3562 + }, 3563 + "public.sybil_clusters": { 3564 + "name": "sybil_clusters", 3565 + "schema": "", 3566 + "columns": { 3567 + "id": { 3568 + "name": "id", 3569 + "type": "serial", 3570 + "primaryKey": true, 3571 + "notNull": true 3572 + }, 3573 + "cluster_hash": { 3574 + "name": "cluster_hash", 3575 + "type": "text", 3576 + "primaryKey": false, 3577 + "notNull": true 3578 + }, 3579 + "internal_edge_count": { 3580 + "name": "internal_edge_count", 3581 + "type": "integer", 3582 + "primaryKey": false, 3583 + "notNull": true 3584 + }, 3585 + "external_edge_count": { 3586 + "name": "external_edge_count", 3587 + "type": "integer", 3588 + "primaryKey": false, 3589 + "notNull": true 3590 + }, 3591 + "member_count": { 3592 + "name": "member_count", 3593 + "type": "integer", 3594 + "primaryKey": false, 3595 + "notNull": true 3596 + }, 3597 + "status": { 3598 + "name": "status", 3599 + "type": "text", 3600 + "primaryKey": false, 3601 + "notNull": true, 3602 + "default": "'flagged'" 3603 + }, 3604 + "reviewed_by": { 3605 + "name": "reviewed_by", 3606 + "type": "text", 3607 + "primaryKey": false, 3608 + "notNull": false 3609 + }, 3610 + "reviewed_at": { 3611 + "name": "reviewed_at", 3612 + "type": "timestamp with time zone", 3613 + "primaryKey": false, 3614 + "notNull": false 3615 + }, 3616 + "detected_at": { 3617 + "name": "detected_at", 3618 + "type": "timestamp with time zone", 3619 + "primaryKey": false, 3620 + "notNull": true, 3621 + "default": "now()" 3622 + }, 3623 + "updated_at": { 3624 + "name": "updated_at", 3625 + "type": "timestamp with time zone", 3626 + "primaryKey": false, 3627 + "notNull": true, 3628 + "default": "now()" 3629 + } 3630 + }, 3631 + "indexes": { 3632 + "sybil_clusters_hash_idx": { 3633 + "name": "sybil_clusters_hash_idx", 3634 + "columns": [ 3635 + { 3636 + "expression": "cluster_hash", 3637 + "isExpression": false, 3638 + "asc": true, 3639 + "nulls": "last" 3640 + } 3641 + ], 3642 + "isUnique": true, 3643 + "concurrently": false, 3644 + "method": "btree", 3645 + "with": {} 3646 + } 3647 + }, 3648 + "foreignKeys": {}, 3649 + "compositePrimaryKeys": {}, 3650 + "uniqueConstraints": {}, 3651 + "policies": {}, 3652 + "checkConstraints": {}, 3653 + "isRLSEnabled": false 3654 + }, 3655 + "public.sybil_cluster_members": { 3656 + "name": "sybil_cluster_members", 3657 + "schema": "", 3658 + "columns": { 3659 + "cluster_id": { 3660 + "name": "cluster_id", 3661 + "type": "integer", 3662 + "primaryKey": false, 3663 + "notNull": true 3664 + }, 3665 + "did": { 3666 + "name": "did", 3667 + "type": "text", 3668 + "primaryKey": false, 3669 + "notNull": true 3670 + }, 3671 + "role_in_cluster": { 3672 + "name": "role_in_cluster", 3673 + "type": "text", 3674 + "primaryKey": false, 3675 + "notNull": true 3676 + }, 3677 + "joined_at": { 3678 + "name": "joined_at", 3679 + "type": "timestamp with time zone", 3680 + "primaryKey": false, 3681 + "notNull": true, 3682 + "default": "now()" 3683 + } 3684 + }, 3685 + "indexes": {}, 3686 + "foreignKeys": { 3687 + "sybil_cluster_members_cluster_id_sybil_clusters_id_fk": { 3688 + "name": "sybil_cluster_members_cluster_id_sybil_clusters_id_fk", 3689 + "tableFrom": "sybil_cluster_members", 3690 + "tableTo": "sybil_clusters", 3691 + "columnsFrom": ["cluster_id"], 3692 + "columnsTo": ["id"], 3693 + "onDelete": "no action", 3694 + "onUpdate": "no action" 3695 + } 3696 + }, 3697 + "compositePrimaryKeys": { 3698 + "sybil_cluster_members_cluster_id_did_pk": { 3699 + "name": "sybil_cluster_members_cluster_id_did_pk", 3700 + "columns": ["cluster_id", "did"] 3701 + } 3702 + }, 3703 + "uniqueConstraints": {}, 3704 + "policies": {}, 3705 + "checkConstraints": {}, 3706 + "isRLSEnabled": false 3707 + }, 3708 + "public.behavioral_flags": { 3709 + "name": "behavioral_flags", 3710 + "schema": "", 3711 + "columns": { 3712 + "id": { 3713 + "name": "id", 3714 + "type": "serial", 3715 + "primaryKey": true, 3716 + "notNull": true 3717 + }, 3718 + "flag_type": { 3719 + "name": "flag_type", 3720 + "type": "text", 3721 + "primaryKey": false, 3722 + "notNull": true 3723 + }, 3724 + "affected_dids": { 3725 + "name": "affected_dids", 3726 + "type": "jsonb", 3727 + "primaryKey": false, 3728 + "notNull": true 3729 + }, 3730 + "details": { 3731 + "name": "details", 3732 + "type": "text", 3733 + "primaryKey": false, 3734 + "notNull": true 3735 + }, 3736 + "community_did": { 3737 + "name": "community_did", 3738 + "type": "text", 3739 + "primaryKey": false, 3740 + "notNull": false 3741 + }, 3742 + "status": { 3743 + "name": "status", 3744 + "type": "text", 3745 + "primaryKey": false, 3746 + "notNull": true, 3747 + "default": "'pending'" 3748 + }, 3749 + "detected_at": { 3750 + "name": "detected_at", 3751 + "type": "timestamp with time zone", 3752 + "primaryKey": false, 3753 + "notNull": true, 3754 + "default": "now()" 3755 + } 3756 + }, 3757 + "indexes": { 3758 + "behavioral_flags_flag_type_idx": { 3759 + "name": "behavioral_flags_flag_type_idx", 3760 + "columns": [ 3761 + { 3762 + "expression": "flag_type", 3763 + "isExpression": false, 3764 + "asc": true, 3765 + "nulls": "last" 3766 + } 3767 + ], 3768 + "isUnique": false, 3769 + "concurrently": false, 3770 + "method": "btree", 3771 + "with": {} 3772 + }, 3773 + "behavioral_flags_status_idx": { 3774 + "name": "behavioral_flags_status_idx", 3775 + "columns": [ 3776 + { 3777 + "expression": "status", 3778 + "isExpression": false, 3779 + "asc": true, 3780 + "nulls": "last" 3781 + } 3782 + ], 3783 + "isUnique": false, 3784 + "concurrently": false, 3785 + "method": "btree", 3786 + "with": {} 3787 + }, 3788 + "behavioral_flags_detected_at_idx": { 3789 + "name": "behavioral_flags_detected_at_idx", 3790 + "columns": [ 3791 + { 3792 + "expression": "detected_at", 3793 + "isExpression": false, 3794 + "asc": true, 3795 + "nulls": "last" 3796 + } 3797 + ], 3798 + "isUnique": false, 3799 + "concurrently": false, 3800 + "method": "btree", 3801 + "with": {} 3802 + } 3803 + }, 3804 + "foreignKeys": {}, 3805 + "compositePrimaryKeys": {}, 3806 + "uniqueConstraints": {}, 3807 + "policies": {}, 3808 + "checkConstraints": {}, 3809 + "isRLSEnabled": false 3810 + }, 3811 + "public.pds_trust_factors": { 3812 + "name": "pds_trust_factors", 3813 + "schema": "", 3814 + "columns": { 3815 + "id": { 3816 + "name": "id", 3817 + "type": "serial", 3818 + "primaryKey": true, 3819 + "notNull": true 3820 + }, 3821 + "pds_host": { 3822 + "name": "pds_host", 3823 + "type": "text", 3824 + "primaryKey": false, 3825 + "notNull": true 3826 + }, 3827 + "trust_factor": { 3828 + "name": "trust_factor", 3829 + "type": "real", 3830 + "primaryKey": false, 3831 + "notNull": true 3832 + }, 3833 + "is_default": { 3834 + "name": "is_default", 3835 + "type": "boolean", 3836 + "primaryKey": false, 3837 + "notNull": true, 3838 + "default": false 3839 + }, 3840 + "updated_at": { 3841 + "name": "updated_at", 3842 + "type": "timestamp with time zone", 3843 + "primaryKey": false, 3844 + "notNull": true, 3845 + "default": "now()" 3846 + } 3847 + }, 3848 + "indexes": { 3849 + "pds_trust_factors_pds_host_idx": { 3850 + "name": "pds_trust_factors_pds_host_idx", 3851 + "columns": [ 3852 + { 3853 + "expression": "pds_host", 3854 + "isExpression": false, 3855 + "asc": true, 3856 + "nulls": "last" 3857 + } 3858 + ], 3859 + "isUnique": true, 3860 + "concurrently": false, 3861 + "method": "btree", 3862 + "with": {} 3863 + } 3864 + }, 3865 + "foreignKeys": {}, 3866 + "compositePrimaryKeys": {}, 3867 + "uniqueConstraints": {}, 3868 + "policies": {}, 3869 + "checkConstraints": {}, 3870 + "isRLSEnabled": false 3871 + }, 3872 + "public.pages": { 3873 + "name": "pages", 3874 + "schema": "", 3875 + "columns": { 3876 + "id": { 3877 + "name": "id", 3878 + "type": "text", 3879 + "primaryKey": true, 3880 + "notNull": true 3881 + }, 3882 + "slug": { 3883 + "name": "slug", 3884 + "type": "text", 3885 + "primaryKey": false, 3886 + "notNull": true 3887 + }, 3888 + "title": { 3889 + "name": "title", 3890 + "type": "text", 3891 + "primaryKey": false, 3892 + "notNull": true 3893 + }, 3894 + "content": { 3895 + "name": "content", 3896 + "type": "text", 3897 + "primaryKey": false, 3898 + "notNull": true 3899 + }, 3900 + "status": { 3901 + "name": "status", 3902 + "type": "text", 3903 + "primaryKey": false, 3904 + "notNull": true, 3905 + "default": "'draft'" 3906 + }, 3907 + "meta_description": { 3908 + "name": "meta_description", 3909 + "type": "text", 3910 + "primaryKey": false, 3911 + "notNull": false 3912 + }, 3913 + "parent_id": { 3914 + "name": "parent_id", 3915 + "type": "text", 3916 + "primaryKey": false, 3917 + "notNull": false 3918 + }, 3919 + "sort_order": { 3920 + "name": "sort_order", 3921 + "type": "integer", 3922 + "primaryKey": false, 3923 + "notNull": true, 3924 + "default": 0 3925 + }, 3926 + "community_did": { 3927 + "name": "community_did", 3928 + "type": "text", 3929 + "primaryKey": false, 3930 + "notNull": true 3931 + }, 3932 + "created_at": { 3933 + "name": "created_at", 3934 + "type": "timestamp with time zone", 3935 + "primaryKey": false, 3936 + "notNull": true, 3937 + "default": "now()" 3938 + }, 3939 + "updated_at": { 3940 + "name": "updated_at", 3941 + "type": "timestamp with time zone", 3942 + "primaryKey": false, 3943 + "notNull": true, 3944 + "default": "now()" 3945 + } 3946 + }, 3947 + "indexes": { 3948 + "pages_slug_community_did_idx": { 3949 + "name": "pages_slug_community_did_idx", 3950 + "columns": [ 3951 + { 3952 + "expression": "slug", 3953 + "isExpression": false, 3954 + "asc": true, 3955 + "nulls": "last" 3956 + }, 3957 + { 3958 + "expression": "community_did", 3959 + "isExpression": false, 3960 + "asc": true, 3961 + "nulls": "last" 3962 + } 3963 + ], 3964 + "isUnique": true, 3965 + "concurrently": false, 3966 + "method": "btree", 3967 + "with": {} 3968 + }, 3969 + "pages_community_did_idx": { 3970 + "name": "pages_community_did_idx", 3971 + "columns": [ 3972 + { 3973 + "expression": "community_did", 3974 + "isExpression": false, 3975 + "asc": true, 3976 + "nulls": "last" 3977 + } 3978 + ], 3979 + "isUnique": false, 3980 + "concurrently": false, 3981 + "method": "btree", 3982 + "with": {} 3983 + }, 3984 + "pages_parent_id_idx": { 3985 + "name": "pages_parent_id_idx", 3986 + "columns": [ 3987 + { 3988 + "expression": "parent_id", 3989 + "isExpression": false, 3990 + "asc": true, 3991 + "nulls": "last" 3992 + } 3993 + ], 3994 + "isUnique": false, 3995 + "concurrently": false, 3996 + "method": "btree", 3997 + "with": {} 3998 + }, 3999 + "pages_status_community_did_idx": { 4000 + "name": "pages_status_community_did_idx", 4001 + "columns": [ 4002 + { 4003 + "expression": "status", 4004 + "isExpression": false, 4005 + "asc": true, 4006 + "nulls": "last" 4007 + }, 4008 + { 4009 + "expression": "community_did", 4010 + "isExpression": false, 4011 + "asc": true, 4012 + "nulls": "last" 4013 + } 4014 + ], 4015 + "isUnique": false, 4016 + "concurrently": false, 4017 + "method": "btree", 4018 + "with": {} 4019 + } 4020 + }, 4021 + "foreignKeys": { 4022 + "pages_parent_id_fk": { 4023 + "name": "pages_parent_id_fk", 4024 + "tableFrom": "pages", 4025 + "tableTo": "pages", 4026 + "columnsFrom": ["parent_id"], 4027 + "columnsTo": ["id"], 4028 + "onDelete": "set null", 4029 + "onUpdate": "no action" 4030 + } 4031 + }, 4032 + "compositePrimaryKeys": {}, 4033 + "uniqueConstraints": {}, 4034 + "policies": { 4035 + "tenant_isolation": { 4036 + "name": "tenant_isolation", 4037 + "as": "PERMISSIVE", 4038 + "for": "ALL", 4039 + "to": ["barazo_app"], 4040 + "using": "community_did = current_setting('app.current_community_did', true)", 4041 + "withCheck": "community_did = current_setting('app.current_community_did', true)" 4042 + } 4043 + }, 4044 + "checkConstraints": {}, 4045 + "isRLSEnabled": true 4046 + } 4047 + }, 4048 + "enums": {}, 4049 + "schemas": {}, 4050 + "sequences": {}, 4051 + "roles": { 4052 + "barazo_app": { 4053 + "name": "barazo_app", 4054 + "createDb": false, 4055 + "createRole": false, 4056 + "inherit": true 4057 + } 4058 + }, 4059 + "policies": {}, 4060 + "views": {}, 4061 + "_meta": { 4062 + "columns": {}, 4063 + "schemas": {}, 4064 + "tables": {} 4065 + } 4066 + }
+8 -1
drizzle/meta/_journal.json
··· 64 64 "when": 1772706380033, 65 65 "tag": "0008_add_author_rkey_indexes", 66 66 "breakpoints": true 67 + }, 68 + { 69 + "idx": 9, 70 + "version": "7", 71 + "when": 1772728814082, 72 + "tag": "0009_brainy_sunspot", 73 + "breakpoints": true 67 74 } 68 75 ] 69 - } 76 + }
+13 -1
src/db/schema/topics.ts
··· 1 - import { pgTable, pgPolicy, text, integer, timestamp, jsonb, boolean, index } from 'drizzle-orm/pg-core' 1 + import { 2 + pgTable, 3 + pgPolicy, 4 + text, 5 + integer, 6 + timestamp, 7 + jsonb, 8 + boolean, 9 + index, 10 + } from 'drizzle-orm/pg-core' 2 11 import { sql } from 'drizzle-orm' 3 12 import { appRole } from './roles.js' 4 13 ··· 24 33 indexedAt: timestamp('indexed_at', { withTimezone: true }).notNull().defaultNow(), 25 34 isLocked: boolean('is_locked').notNull().default(false), 26 35 isPinned: boolean('is_pinned').notNull().default(false), 36 + pinnedAt: timestamp('pinned_at', { withTimezone: true }), 37 + pinnedScope: text('pinned_scope', { enum: ['category', 'forum'] }), 27 38 isModDeleted: boolean('is_mod_deleted').notNull().default(false), 28 39 isAuthorDeleted: boolean('is_author_deleted').notNull().default(false), 29 40 moderationStatus: text('moderation_status', { ··· 55 66 table.category, 56 67 table.lastActivityAt 57 68 ), 69 + index('topics_pinned_scope_idx').on(table.pinnedScope), 58 70 index('topics_author_did_rkey_idx').on(table.authorDid, table.rkey), 59 71 pgPolicy('tenant_isolation', { 60 72 as: 'permissive',
+26 -2
src/routes/moderation.ts
··· 260 260 type: 'object', 261 261 properties: { 262 262 reason: { type: 'string', maxLength: 500 }, 263 + scope: { type: 'string', enum: ['category', 'forum'] }, 263 264 }, 264 265 }, 265 266 response: { ··· 268 269 properties: { 269 270 uri: { type: 'string' }, 270 271 isPinned: { type: 'boolean' }, 272 + pinnedScope: { type: 'string', nullable: true }, 273 + pinnedAt: { type: 'string', nullable: true }, 271 274 }, 272 275 }, 273 276 401: errorResponseSchema, ··· 286 289 const { id } = request.params as { id: string } 287 290 const decodedUri = decodeURIComponent(id) 288 291 const parsed = pinTopicSchema.safeParse(request.body) 292 + const scope = parsed.success ? parsed.data.scope : 'category' 289 293 290 294 const topicRows = await db 291 295 .select() ··· 300 304 const newPinned = !topic.isPinned 301 305 const action = newPinned ? 'pin' : 'unpin' 302 306 307 + // Forum-wide pins require admin role 308 + if (newPinned && scope === 'forum') { 309 + const userRows = await db.select().from(users).where(eq(users.did, user.did)) 310 + const userRecord = userRows[0] 311 + if (!userRecord || userRecord.role !== 'admin') { 312 + throw forbidden('Forum-wide pins require admin privileges') 313 + } 314 + } 315 + 316 + const pinnedAt = newPinned ? new Date() : null 317 + const pinnedScope = newPinned ? scope : null 318 + 303 319 await db.transaction(async (tx) => { 304 - await tx.update(topics).set({ isPinned: newPinned }).where(eq(topics.uri, decodedUri)) 320 + await tx 321 + .update(topics) 322 + .set({ isPinned: newPinned, pinnedAt, pinnedScope }) 323 + .where(eq(topics.uri, decodedUri)) 305 324 306 325 await tx.insert(moderationActions).values({ 307 326 action, ··· 312 331 }) 313 332 }) 314 333 315 - app.log.info({ action, topicUri: decodedUri, moderatorDid: user.did }, `Topic ${action}ned`) 334 + app.log.info( 335 + { action, topicUri: decodedUri, moderatorDid: user.did, pinnedScope }, 336 + `Topic ${action}ned` 337 + ) 316 338 317 339 // Fire-and-forget: notify topic author of pin/unpin 318 340 notificationService ··· 329 351 return reply.status(200).send({ 330 352 uri: decodedUri, 331 353 isPinned: newPinned, 354 + pinnedScope, 355 + pinnedAt: pinnedAt?.toISOString() ?? null, 332 356 }) 333 357 } 334 358 )
+20 -2
src/routes/topics.ts
··· 1 - import { eq, and, desc, sql, inArray, notInArray, isNotNull, ne, or } from 'drizzle-orm' 1 + import { eq, and, asc, desc, sql, inArray, notInArray, isNotNull, ne, or } from 'drizzle-orm' 2 2 import { requireCommunityDid } from '../middleware/community-resolver.js' 3 3 import type { FastifyPluginCallback } from 'fastify' 4 4 import { createPdsClient } from '../lib/pds-client.js' ··· 84 84 isMuted: { type: 'boolean' as const }, 85 85 isMutedWord: { type: 'boolean' as const }, 86 86 ozoneLabel: { type: ['string', 'null'] as const }, 87 + isPinned: { type: 'boolean' as const }, 88 + isLocked: { type: 'boolean' as const }, 89 + pinnedScope: { type: ['string', 'null'] as const }, 90 + pinnedAt: { type: ['string', 'null'] as const, format: 'date-time' as const }, 87 91 categoryMaturityRating: { type: 'string' as const, enum: ['safe', 'mature', 'adult'] }, 88 92 lastActivityAt: { type: 'string' as const, format: 'date-time' as const }, 89 93 createdAt: { type: 'string' as const, format: 'date-time' as const }, ··· 123 127 reactionCount: row.reactionCount, 124 128 isAuthorDeleted: row.isAuthorDeleted, 125 129 isModDeleted: row.isModDeleted, 130 + isPinned: row.isPinned, 131 + isLocked: row.isLocked, 132 + pinnedScope: row.pinnedScope ?? null, 133 + pinnedAt: row.pinnedAt?.toISOString() ?? null, 126 134 categoryMaturityRating, 127 135 lastActivityAt: row.lastActivityAt.toISOString(), 128 136 createdAt: row.createdAt.toISOString(), ··· 707 715 // score = (reply_count + reaction_count * 0.3) / (age_in_hours + 2) ^ 1.2 708 716 const popularityScore = sql`(${topics.replyCount} + ${topics.reactionCount} * 0.3) / POWER(EXTRACT(EPOCH FROM (NOW() - ${topics.createdAt})) / 3600.0 + 2, 1.2)` 709 717 718 + // Pinned-first ordering: when browsing a category, both category-pinned 719 + // and forum-pinned topics float to the top. On the homepage (no category 720 + // filter), only forum-pinned topics get promoted. 721 + const pinnedFirst = category 722 + ? sql`CASE WHEN ${topics.pinnedScope} IS NOT NULL THEN 0 ELSE 1 END` 723 + : sql`CASE WHEN ${topics.pinnedScope} = 'forum' THEN 0 ELSE 1 END` 724 + 710 725 const rows = await db 711 726 .select() 712 727 .from(topics) 713 728 .where(whereClause) 714 - .orderBy(sort === 'popular' ? desc(popularityScore) : desc(topics.lastActivityAt)) 729 + .orderBy( 730 + asc(pinnedFirst), 731 + sort === 'popular' ? desc(popularityScore) : desc(topics.lastActivityAt) 732 + ) 715 733 .limit(fetchLimit) 716 734 717 735 const hasMore = rows.length > limit
+1
src/validation/moderation.ts
··· 10 10 11 11 export const pinTopicSchema = z.object({ 12 12 reason: z.string().max(500).optional(), 13 + scope: z.enum(['category', 'forum']).default('category'), 13 14 }) 14 15 15 16 export const modDeleteSchema = z.object({
+2
tests/integration/tenant-isolation.test.ts
··· 90 90 indexed_at TIMESTAMPTZ NOT NULL DEFAULT now(), 91 91 is_locked BOOLEAN NOT NULL DEFAULT false, 92 92 is_pinned BOOLEAN NOT NULL DEFAULT false, 93 + pinned_at TIMESTAMPTZ, 94 + pinned_scope TEXT, 93 95 is_mod_deleted BOOLEAN NOT NULL DEFAULT false, 94 96 is_author_deleted BOOLEAN NOT NULL DEFAULT false, 95 97 moderation_status TEXT NOT NULL DEFAULT 'approved',
+12
tests/unit/db/schema/topics.test.ts
··· 82 82 expect(columns.isPinned.hasDefault).toBe(true) 83 83 expect(columns.isModDeleted.hasDefault).toBe(true) 84 84 }) 85 + 86 + it('has nullable pinnedAt timestamp column', () => { 87 + const columnNames = Object.keys(columns) 88 + expect(columnNames).toContain('pinnedAt') 89 + expect(columns.pinnedAt.notNull).toBe(false) 90 + }) 91 + 92 + it('has nullable pinnedScope text column', () => { 93 + const columnNames = Object.keys(columns) 94 + expect(columnNames).toContain('pinnedScope') 95 + expect(columns.pinnedScope.notNull).toBe(false) 96 + }) 85 97 })
+73
tests/unit/routes/moderation.test.ts
··· 173 173 indexedAt: new Date(TEST_NOW), 174 174 isLocked: false, 175 175 isPinned: false, 176 + pinnedAt: null, 177 + pinnedScope: null, 176 178 isModDeleted: false, 177 179 embedding: null, 178 180 ...overrides, ··· 505 507 }) 506 508 507 509 expect(response.statusCode).toBe(404) 510 + }) 511 + 512 + it('should pin with category scope by default', async () => { 513 + selectChain.where.mockResolvedValueOnce([sampleTopicRow({ isPinned: false })]) 514 + 515 + const encodedUri = encodeURIComponent(TEST_TOPIC_URI) 516 + const response = await app.inject({ 517 + method: 'POST', 518 + url: `/api/moderation/pin/${encodedUri}`, 519 + headers: { authorization: 'Bearer test-token' }, 520 + payload: {}, 521 + }) 522 + 523 + expect(response.statusCode).toBe(200) 524 + const body = response.json<{ uri: string; isPinned: boolean; pinnedScope: string | null }>() 525 + expect(body.isPinned).toBe(true) 526 + expect(body.pinnedScope).toBe('category') 527 + }) 528 + 529 + it('should pin with forum scope', async () => { 530 + // User lookup for admin check -- return admin role 531 + selectChain.where.mockResolvedValueOnce([sampleTopicRow({ isPinned: false })]) 532 + selectChain.where.mockResolvedValueOnce([sampleUserRow({ did: TEST_DID, role: 'admin' })]) 533 + 534 + const encodedUri = encodeURIComponent(TEST_TOPIC_URI) 535 + const response = await app.inject({ 536 + method: 'POST', 537 + url: `/api/moderation/pin/${encodedUri}`, 538 + headers: { authorization: 'Bearer test-token' }, 539 + payload: { scope: 'forum' }, 540 + }) 541 + 542 + expect(response.statusCode).toBe(200) 543 + const body = response.json<{ uri: string; isPinned: boolean; pinnedScope: string | null }>() 544 + expect(body.isPinned).toBe(true) 545 + expect(body.pinnedScope).toBe('forum') 546 + }) 547 + 548 + it('should clear pinnedScope and pinnedAt when unpinning', async () => { 549 + selectChain.where.mockResolvedValueOnce([ 550 + sampleTopicRow({ isPinned: true, pinnedAt: new Date(TEST_NOW), pinnedScope: 'category' }), 551 + ]) 552 + 553 + const encodedUri = encodeURIComponent(TEST_TOPIC_URI) 554 + const response = await app.inject({ 555 + method: 'POST', 556 + url: `/api/moderation/pin/${encodedUri}`, 557 + headers: { authorization: 'Bearer test-token' }, 558 + payload: {}, 559 + }) 560 + 561 + expect(response.statusCode).toBe(200) 562 + const body = response.json<{ uri: string; isPinned: boolean; pinnedScope: string | null }>() 563 + expect(body.isPinned).toBe(false) 564 + expect(body.pinnedScope).toBeNull() 565 + }) 566 + 567 + it('should reject forum-wide pin from non-admin moderator', async () => { 568 + selectChain.where.mockResolvedValueOnce([sampleTopicRow({ isPinned: false })]) 569 + // User lookup returns moderator role (not admin) 570 + selectChain.where.mockResolvedValueOnce([sampleUserRow({ did: TEST_DID, role: 'moderator' })]) 571 + 572 + const encodedUri = encodeURIComponent(TEST_TOPIC_URI) 573 + const response = await app.inject({ 574 + method: 'POST', 575 + url: `/api/moderation/pin/${encodedUri}`, 576 + headers: { authorization: 'Bearer test-token' }, 577 + payload: { scope: 'forum' }, 578 + }) 579 + 580 + expect(response.statusCode).toBe(403) 508 581 }) 509 582 }) 510 583
+118
tests/unit/routes/topics.test.ts
··· 225 225 labels: null, 226 226 replyCount: 0, 227 227 reactionCount: 0, 228 + isPinned: false, 229 + isLocked: false, 230 + pinnedScope: null, 231 + pinnedAt: null, 228 232 lastActivityAt: new Date(TEST_NOW), 229 233 createdAt: new Date(TEST_NOW), 230 234 indexedAt: new Date(TEST_NOW), ··· 866 870 expect(body.topics.every((t) => !t.isMuted)).toBe(true) 867 871 868 872 await noAuthApp.close() 873 + }) 874 + 875 + it('includes pinned topics in list response with pinned fields', async () => { 876 + setupMaturityMocks(true) 877 + const pinnedRow = sampleTopicRow({ 878 + uri: `at://${TEST_DID}/forum.barazo.topic.post/pinned1`, 879 + rkey: 'pinned1', 880 + isPinned: true, 881 + pinnedScope: 'forum', 882 + pinnedAt: new Date('2026-02-14T00:00:00.000Z'), 883 + }) 884 + const normalRow = sampleTopicRow({ 885 + uri: `at://${TEST_DID}/forum.barazo.topic.post/normal1`, 886 + rkey: 'normal1', 887 + lastActivityAt: new Date('2026-02-15T00:00:00.000Z'), 888 + }) 889 + selectChain.limit.mockResolvedValueOnce([pinnedRow, normalRow]) 890 + 891 + const response = await app.inject({ 892 + method: 'GET', 893 + url: '/api/topics', 894 + }) 895 + 896 + expect(response.statusCode).toBe(200) 897 + const body = response.json<{ 898 + topics: Array<{ 899 + uri: string 900 + isPinned: boolean 901 + pinnedScope: string | null 902 + pinnedAt: string | null 903 + }> 904 + }>() 905 + expect(body.topics).toHaveLength(2) 906 + expect(body.topics[0]?.isPinned).toBe(true) 907 + expect(body.topics[0]?.pinnedScope).toBe('forum') 908 + expect(body.topics[0]?.pinnedAt).toBe('2026-02-14T00:00:00.000Z') 909 + expect(body.topics[1]?.isPinned).toBe(false) 910 + expect(body.topics[1]?.pinnedScope).toBeNull() 911 + }) 912 + 913 + it('calls orderBy for pinned-first sorting', async () => { 914 + setupMaturityMocks(true) 915 + selectChain.limit.mockResolvedValueOnce([]) 916 + 917 + const response = await app.inject({ 918 + method: 'GET', 919 + url: '/api/topics', 920 + }) 921 + 922 + expect(response.statusCode).toBe(200) 923 + // The query chain goes .where().orderBy().limit(), so orderBy must have been called 924 + expect(selectChain.orderBy).toHaveBeenCalled() 869 925 }) 870 926 871 927 it('includes author profile in topic response', async () => { ··· 2722 2778 expect(response.statusCode).toBe(200) 2723 2779 const body = response.json<{ topics: Array<{ tags: string[] | null }> }>() 2724 2780 expect(body.topics[0]?.tags).toBeNull() 2781 + }) 2782 + 2783 + it('includes isPinned, isLocked, pinnedScope, and pinnedAt in response', async () => { 2784 + setupMaturityMocks(true) 2785 + 2786 + const pinnedDate = new Date('2026-03-01T12:00:00.000Z') 2787 + const rows = [ 2788 + sampleTopicRow({ 2789 + isPinned: true, 2790 + isLocked: true, 2791 + pinnedScope: 'category', 2792 + pinnedAt: pinnedDate, 2793 + }), 2794 + ] 2795 + selectChain.limit.mockResolvedValueOnce(rows) 2796 + 2797 + const response = await app.inject({ 2798 + method: 'GET', 2799 + url: '/api/topics', 2800 + }) 2801 + 2802 + expect(response.statusCode).toBe(200) 2803 + const body = response.json<{ 2804 + topics: Array<{ 2805 + isPinned: boolean 2806 + isLocked: boolean 2807 + pinnedScope: string | null 2808 + pinnedAt: string | null 2809 + }> 2810 + }>() 2811 + expect(body.topics).toHaveLength(1) 2812 + expect(body.topics[0]?.isPinned).toBe(true) 2813 + expect(body.topics[0]?.isLocked).toBe(true) 2814 + expect(body.topics[0]?.pinnedScope).toBe('category') 2815 + expect(body.topics[0]?.pinnedAt).toBe('2026-03-01T12:00:00.000Z') 2816 + }) 2817 + 2818 + it('returns null for pinnedScope and pinnedAt when topic is not pinned', async () => { 2819 + setupMaturityMocks(true) 2820 + 2821 + const rows = [sampleTopicRow()] 2822 + selectChain.limit.mockResolvedValueOnce(rows) 2823 + 2824 + const response = await app.inject({ 2825 + method: 'GET', 2826 + url: '/api/topics', 2827 + }) 2828 + 2829 + expect(response.statusCode).toBe(200) 2830 + const body = response.json<{ 2831 + topics: Array<{ 2832 + isPinned: boolean 2833 + isLocked: boolean 2834 + pinnedScope: string | null 2835 + pinnedAt: string | null 2836 + }> 2837 + }>() 2838 + expect(body.topics).toHaveLength(1) 2839 + expect(body.topics[0]?.isPinned).toBe(false) 2840 + expect(body.topics[0]?.isLocked).toBe(false) 2841 + expect(body.topics[0]?.pinnedScope).toBeNull() 2842 + expect(body.topics[0]?.pinnedAt).toBeNull() 2725 2843 }) 2726 2844 2727 2845 it('provides fallback author profile when author not found in DB', async () => {