Barazo AppView backend barazo.forum

feat: add comment threading backend (depth, indexing, API) (#125)

* feat(api): add depth column to replies table

Add integer depth column (NOT NULL, default 1) for thread nesting.
Add composite index on (root_uri, depth) for depth-filtered queries.
Direct replies to topic = depth 1, nested = parent_depth + 1.

* feat(api): add maxReplyDepth to community settings

Add integer max_reply_depth column (NOT NULL, default 9999) for
admin-configurable threading depth. 9999 means effectively unlimited.

* feat(api): add threading constants

* chore(api): generate migration for threading schema

Adds depth column to replies, max_reply_depth to community_settings,
and composite index on (root_uri, depth).

* feat(api): add backfill script for reply depth

Recursive CTE computes correct depth for all existing replies.
Direct replies to topic = 1, nested = parent_depth + 1. Idempotent.

* feat(api): compute reply depth on firehose indexing and optimistic insert

Direct replies to topic get depth 1. Nested replies look up parent
depth and add 1. Falls back to depth 1 if parent not found.

* feat(api): add depth query param, childCount, and maxReplyDepth admin setting

- serializeReply now uses stored depth column instead of naive computation
- GET replies accepts ?depth= param (default 10, max 100)
- Response includes childCount for depth-limited branches
- maxReplyDepth added to admin settings GET/PUT and public settings
- Updated mock DB to support groupBy as terminal chain method
- Updated test fixtures with depth field and new convention (1-indexed)

* chore(api): regenerate migration after merge with main

Removes old 0001_modern_master_mold migration (conflicted with main's
0001_add_favicon_url). Regenerated as 0004_threading-schema with same
content: depth column on replies, max_reply_depth on community_settings,
composite index.

* fix(api): add threading columns to tenant-isolation test schema

The integration test creates its own DB schema via raw SQL rather than
using Drizzle migrations. Add the missing depth and max_reply_depth
columns so Drizzle ORM inserts don't fail.

* chore(api): trigger CI re-run

* docs(api): add JSDoc comments to threading constants

* chore(api): renumber threading migration to 0005 after merge

Main added 0004_add_pages_table, so our threading migration becomes
0005_threading-schema.

authored by

Guido X Jansen and committed by
GitHub
9715d4fc a2b4b14b

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