diff --git a/drizzle/0005_threading-schema.sql b/drizzle/0005_threading-schema.sql new file mode 100644 index 0000000..1ccf108 --- /dev/null +++ b/drizzle/0005_threading-schema.sql @@ -0,0 +1,3 @@ +ALTER TABLE "replies" ADD COLUMN "depth" integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE "community_settings" ADD COLUMN "max_reply_depth" integer DEFAULT 9999 NOT NULL;--> statement-breakpoint +CREATE INDEX "replies_root_uri_depth_idx" ON "replies" USING btree ("root_uri","depth"); \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json index ca40abe..895e4b2 100644 --- a/drizzle/meta/0001_snapshot.json +++ b/drizzle/meta/0001_snapshot.json @@ -518,9 +518,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -796,9 +794,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -946,11 +942,7 @@ "reactions_author_subject_type_uniq": { "name": "reactions_author_subject_type_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri", - "type" - ] + "columns": ["author_did", "subject_uri", "type"] } }, "policies": { @@ -958,9 +950,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1087,10 +1077,7 @@ "votes_author_subject_uniq": { "name": "votes_author_subject_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri" - ] + "columns": ["author_did", "subject_uri"] } }, "policies": {}, @@ -1286,9 +1273,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1438,12 +1423,8 @@ "name": "categories_parent_id_fk", "tableFrom": "categories", "tableTo": "categories", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1455,9 +1436,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1604,9 +1583,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1839,9 +1816,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1965,9 +1940,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2065,10 +2038,7 @@ "compositePrimaryKeys": { "user_community_preferences_did_community_did_pk": { "name": "user_community_preferences_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -2077,9 +2047,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2349,9 +2317,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2422,11 +2388,7 @@ "compositePrimaryKeys": { "user_onboarding_responses_did_community_did_field_id_pk": { "name": "user_onboarding_responses_did_community_did_field_id_pk", - "columns": [ - "did", - "community_did", - "field_id" - ] + "columns": ["did", "community_did", "field_id"] } }, "uniqueConstraints": {}, @@ -2435,9 +2397,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2603,9 +2563,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2702,9 +2660,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2830,9 +2786,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3007,9 +2961,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3237,10 +3189,7 @@ "compositePrimaryKeys": { "community_profiles_did_community_did_pk": { "name": "community_profiles_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -3249,9 +3198,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3342,12 +3289,7 @@ "compositePrimaryKeys": { "interaction_graph_source_did_target_did_community_id_interaction_type_pk": { "name": "interaction_graph_source_did_target_did_community_id_interaction_type_pk", - "columns": [ - "source_did", - "target_did", - "community_id", - "interaction_type" - ] + "columns": ["source_did", "target_did", "community_id", "interaction_type"] } }, "uniqueConstraints": {}, @@ -3486,10 +3428,7 @@ "compositePrimaryKeys": { "trust_scores_did_community_id_pk": { "name": "trust_scores_did_community_id_pk", - "columns": [ - "did", - "community_id" - ] + "columns": ["did", "community_id"] } }, "uniqueConstraints": {}, @@ -3625,12 +3564,8 @@ "name": "sybil_cluster_members_cluster_id_sybil_clusters_id_fk", "tableFrom": "sybil_cluster_members", "tableTo": "sybil_clusters", - "columnsFrom": [ - "cluster_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3638,10 +3573,7 @@ "compositePrimaryKeys": { "sybil_cluster_members_cluster_id_did_pk": { "name": "sybil_cluster_members_cluster_id_did_pk", - "columns": [ - "cluster_id", - "did" - ] + "columns": ["cluster_id", "did"] } }, "uniqueConstraints": {}, @@ -3832,4 +3764,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json index 56b1c87..36e5a3b 100644 --- a/drizzle/meta/0002_snapshot.json +++ b/drizzle/meta/0002_snapshot.json @@ -518,9 +518,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -796,9 +794,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -946,11 +942,7 @@ "reactions_author_subject_type_uniq": { "name": "reactions_author_subject_type_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri", - "type" - ] + "columns": ["author_did", "subject_uri", "type"] } }, "policies": { @@ -958,9 +950,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1087,10 +1077,7 @@ "votes_author_subject_uniq": { "name": "votes_author_subject_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri" - ] + "columns": ["author_did", "subject_uri"] } }, "policies": {}, @@ -1286,9 +1273,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1438,12 +1423,8 @@ "name": "categories_parent_id_fk", "tableFrom": "categories", "tableTo": "categories", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1455,9 +1436,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1604,9 +1583,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1839,9 +1816,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1965,9 +1940,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2065,10 +2038,7 @@ "compositePrimaryKeys": { "user_community_preferences_did_community_did_pk": { "name": "user_community_preferences_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -2077,9 +2047,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2356,9 +2324,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2429,11 +2395,7 @@ "compositePrimaryKeys": { "user_onboarding_responses_did_community_did_field_id_pk": { "name": "user_onboarding_responses_did_community_did_field_id_pk", - "columns": [ - "did", - "community_did", - "field_id" - ] + "columns": ["did", "community_did", "field_id"] } }, "uniqueConstraints": {}, @@ -2442,9 +2404,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2610,9 +2570,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2709,9 +2667,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2837,9 +2793,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3014,9 +2968,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3244,10 +3196,7 @@ "compositePrimaryKeys": { "community_profiles_did_community_did_pk": { "name": "community_profiles_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -3256,9 +3205,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3349,12 +3296,7 @@ "compositePrimaryKeys": { "interaction_graph_source_did_target_did_community_id_interaction_type_pk": { "name": "interaction_graph_source_did_target_did_community_id_interaction_type_pk", - "columns": [ - "source_did", - "target_did", - "community_id", - "interaction_type" - ] + "columns": ["source_did", "target_did", "community_id", "interaction_type"] } }, "uniqueConstraints": {}, @@ -3493,10 +3435,7 @@ "compositePrimaryKeys": { "trust_scores_did_community_id_pk": { "name": "trust_scores_did_community_id_pk", - "columns": [ - "did", - "community_id" - ] + "columns": ["did", "community_id"] } }, "uniqueConstraints": {}, @@ -3632,12 +3571,8 @@ "name": "sybil_cluster_members_cluster_id_sybil_clusters_id_fk", "tableFrom": "sybil_cluster_members", "tableTo": "sybil_clusters", - "columnsFrom": [ - "cluster_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3645,10 +3580,7 @@ "compositePrimaryKeys": { "sybil_cluster_members_cluster_id_did_pk": { "name": "sybil_cluster_members_cluster_id_did_pk", - "columns": [ - "cluster_id", - "did" - ] + "columns": ["cluster_id", "did"] } }, "uniqueConstraints": {}, @@ -3839,4 +3771,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json index 04b7a6a..32adf03 100644 --- a/drizzle/meta/0004_snapshot.json +++ b/drizzle/meta/0004_snapshot.json @@ -518,9 +518,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -796,9 +794,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -946,11 +942,7 @@ "reactions_author_subject_type_uniq": { "name": "reactions_author_subject_type_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri", - "type" - ] + "columns": ["author_did", "subject_uri", "type"] } }, "policies": { @@ -958,9 +950,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1087,10 +1077,7 @@ "votes_author_subject_uniq": { "name": "votes_author_subject_uniq", "nullsNotDistinct": false, - "columns": [ - "author_did", - "subject_uri" - ] + "columns": ["author_did", "subject_uri"] } }, "policies": {}, @@ -1286,9 +1273,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1438,12 +1423,8 @@ "name": "categories_parent_id_fk", "tableFrom": "categories", "tableTo": "categories", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1455,9 +1436,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1604,9 +1583,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1839,9 +1816,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -1965,9 +1940,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2065,10 +2038,7 @@ "compositePrimaryKeys": { "user_community_preferences_did_community_did_pk": { "name": "user_community_preferences_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -2077,9 +2047,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2356,9 +2324,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2429,11 +2395,7 @@ "compositePrimaryKeys": { "user_onboarding_responses_did_community_did_field_id_pk": { "name": "user_onboarding_responses_did_community_did_field_id_pk", - "columns": [ - "did", - "community_did", - "field_id" - ] + "columns": ["did", "community_did", "field_id"] } }, "uniqueConstraints": {}, @@ -2442,9 +2404,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2610,9 +2570,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2709,9 +2667,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -2837,9 +2793,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3014,9 +2968,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3244,10 +3196,7 @@ "compositePrimaryKeys": { "community_profiles_did_community_did_pk": { "name": "community_profiles_did_community_did_pk", - "columns": [ - "did", - "community_did" - ] + "columns": ["did", "community_did"] } }, "uniqueConstraints": {}, @@ -3256,9 +3205,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -3349,12 +3296,7 @@ "compositePrimaryKeys": { "interaction_graph_source_did_target_did_community_id_interaction_type_pk": { "name": "interaction_graph_source_did_target_did_community_id_interaction_type_pk", - "columns": [ - "source_did", - "target_did", - "community_id", - "interaction_type" - ] + "columns": ["source_did", "target_did", "community_id", "interaction_type"] } }, "uniqueConstraints": {}, @@ -3493,10 +3435,7 @@ "compositePrimaryKeys": { "trust_scores_did_community_id_pk": { "name": "trust_scores_did_community_id_pk", - "columns": [ - "did", - "community_id" - ] + "columns": ["did", "community_id"] } }, "uniqueConstraints": {}, @@ -3632,12 +3571,8 @@ "name": "sybil_cluster_members_cluster_id_sybil_clusters_id_fk", "tableFrom": "sybil_cluster_members", "tableTo": "sybil_clusters", - "columnsFrom": [ - "cluster_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3645,10 +3580,7 @@ "compositePrimaryKeys": { "sybil_cluster_members_cluster_id_did_pk": { "name": "sybil_cluster_members_cluster_id_did_pk", - "columns": [ - "cluster_id", - "did" - ] + "columns": ["cluster_id", "did"] } }, "uniqueConstraints": {}, @@ -3974,12 +3906,8 @@ "name": "pages_parent_id_fk", "tableFrom": "pages", "tableTo": "pages", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -3991,9 +3919,7 @@ "name": "tenant_isolation", "as": "PERMISSIVE", "for": "ALL", - "to": [ - "barazo_app" - ], + "to": ["barazo_app"], "using": "community_did = current_setting('app.current_community_did', true)", "withCheck": "community_did = current_setting('app.current_community_did', true)" } @@ -4020,4 +3946,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..112d982 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,3984 @@ +{ + "id": "9015e774-1584-45d9-a654-9cf4437fa526", + "prevId": "fb61c80e-ea91-48e9-8d9c-7beaf93d08f1", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.users": { + "name": "users", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banner_url": { + "name": "banner_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "is_banned": { + "name": "is_banned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reputation_score": { + "name": "reputation_score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "declared_age": { + "name": "declared_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "maturity_pref": { + "name": "maturity_pref", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'safe'" + }, + "account_created_at": { + "name": "account_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "followers_count": { + "name": "followers_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "follows_count": { + "name": "follows_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "atproto_posts_count": { + "name": "atproto_posts_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "has_bluesky_profile": { + "name": "has_bluesky_profile", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "atproto_labels": { + "name": "atproto_labels", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "users_role_elevated_idx": { + "name": "users_role_elevated_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "role IN ('moderator', 'admin')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_handle_idx": { + "name": "users_handle_idx", + "columns": [ + { + "expression": "handle", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_account_created_at_idx": { + "name": "users_account_created_at_idx", + "columns": [ + { + "expression": "account_created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.firehose_cursor": { + "name": "firehose_cursor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "cursor": { + "name": "cursor", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.topics": { + "name": "topics", + "schema": "", + "columns": { + "uri": { + "name": "uri", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_format": { + "name": "content_format", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reaction_count": { + "name": "reaction_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "vote_count": { + "name": "vote_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_locked": { + "name": "is_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_mod_deleted": { + "name": "is_mod_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_author_deleted": { + "name": "is_author_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "moderation_status": { + "name": "moderation_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'approved'" + }, + "trust_status": { + "name": "trust_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'trusted'" + } + }, + "indexes": { + "topics_author_did_idx": { + "name": "topics_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_category_idx": { + "name": "topics_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_created_at_idx": { + "name": "topics_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_last_activity_at_idx": { + "name": "topics_last_activity_at_idx", + "columns": [ + { + "expression": "last_activity_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_community_did_idx": { + "name": "topics_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_moderation_status_idx": { + "name": "topics_moderation_status_idx", + "columns": [ + { + "expression": "moderation_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_trust_status_idx": { + "name": "topics_trust_status_idx", + "columns": [ + { + "expression": "trust_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "topics_community_category_activity_idx": { + "name": "topics_community_category_activity_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_activity_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.replies": { + "name": "replies", + "schema": "", + "columns": { + "uri": { + "name": "uri", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_format": { + "name": "content_format", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "root_uri": { + "name": "root_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "root_cid": { + "name": "root_cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_uri": { + "name": "parent_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_cid": { + "name": "parent_cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reaction_count": { + "name": "reaction_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "vote_count": { + "name": "vote_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_author_deleted": { + "name": "is_author_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_mod_deleted": { + "name": "is_mod_deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "moderation_status": { + "name": "moderation_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'approved'" + }, + "trust_status": { + "name": "trust_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'trusted'" + } + }, + "indexes": { + "replies_author_did_idx": { + "name": "replies_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_root_uri_idx": { + "name": "replies_root_uri_idx", + "columns": [ + { + "expression": "root_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_parent_uri_idx": { + "name": "replies_parent_uri_idx", + "columns": [ + { + "expression": "parent_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_created_at_idx": { + "name": "replies_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_community_did_idx": { + "name": "replies_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_moderation_status_idx": { + "name": "replies_moderation_status_idx", + "columns": [ + { + "expression": "moderation_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_trust_status_idx": { + "name": "replies_trust_status_idx", + "columns": [ + { + "expression": "trust_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_root_uri_created_at_idx": { + "name": "replies_root_uri_created_at_idx", + "columns": [ + { + "expression": "root_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "replies_root_uri_depth_idx": { + "name": "replies_root_uri_depth_idx", + "columns": [ + { + "expression": "root_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "uri": { + "name": "uri", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_uri": { + "name": "subject_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_cid": { + "name": "subject_cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reactions_author_did_idx": { + "name": "reactions_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reactions_subject_uri_idx": { + "name": "reactions_subject_uri_idx", + "columns": [ + { + "expression": "subject_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reactions_community_did_idx": { + "name": "reactions_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reactions_subject_uri_type_idx": { + "name": "reactions_subject_uri_type_idx", + "columns": [ + { + "expression": "subject_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reactions_author_subject_type_uniq": { + "name": "reactions_author_subject_type_uniq", + "nullsNotDistinct": false, + "columns": ["author_did", "subject_uri", "type"] + } + }, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.votes": { + "name": "votes", + "schema": "", + "columns": { + "uri": { + "name": "uri", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_uri": { + "name": "subject_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_cid": { + "name": "subject_cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "votes_author_did_idx": { + "name": "votes_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "votes_subject_uri_idx": { + "name": "votes_subject_uri_idx", + "columns": [ + { + "expression": "subject_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "votes_community_did_idx": { + "name": "votes_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "votes_author_subject_uniq": { + "name": "votes_author_subject_uniq", + "nullsNotDistinct": false, + "columns": ["author_did", "subject_uri"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tracked_repos": { + "name": "tracked_repos", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tracked_at": { + "name": "tracked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.community_settings": { + "name": "community_settings", + "schema": "", + "columns": { + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "domains": { + "name": "domains", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "initialized": { + "name": "initialized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "admin_did": { + "name": "admin_did", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "community_name": { + "name": "community_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Barazo Community'" + }, + "maturity_rating": { + "name": "maturity_rating", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'safe'" + }, + "reaction_set": { + "name": "reaction_set", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"like\"]'::jsonb" + }, + "moderation_thresholds": { + "name": "moderation_thresholds", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"autoBlockReportCount\":5,\"warnThreshold\":3,\"firstPostQueueCount\":3,\"newAccountDays\":7,\"newAccountWriteRatePerMin\":3,\"establishedWriteRatePerMin\":10,\"linkHoldEnabled\":true,\"topicCreationDelayEnabled\":true,\"burstPostCount\":5,\"burstWindowMinutes\":10,\"trustedPostThreshold\":10}'::jsonb" + }, + "word_filter": { + "name": "word_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "jurisdiction_country": { + "name": "jurisdiction_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "age_threshold": { + "name": "age_threshold", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 16 + }, + "max_reply_depth": { + "name": "max_reply_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 9999 + }, + "require_login_for_mature": { + "name": "require_login_for_mature", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "community_description": { + "name": "community_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_endpoint": { + "name": "service_endpoint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signing_key": { + "name": "signing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rotation_key": { + "name": "rotation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "community_logo_url": { + "name": "community_logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accent_color": { + "name": "accent_color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "maturity_rating": { + "name": "maturity_rating", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'safe'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "categories_slug_community_did_idx": { + "name": "categories_slug_community_did_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "categories_parent_id_idx": { + "name": "categories_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "categories_community_did_idx": { + "name": "categories_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "categories_maturity_rating_idx": { + "name": "categories_maturity_rating_idx", + "columns": [ + { + "expression": "maturity_rating", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "categories_parent_id_fk": { + "name": "categories_parent_id_fk", + "tableFrom": "categories", + "tableTo": "categories", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.moderation_actions": { + "name": "moderation_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_uri": { + "name": "target_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_did": { + "name": "target_did", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "moderator_did": { + "name": "moderator_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mod_actions_moderator_did_idx": { + "name": "mod_actions_moderator_did_idx", + "columns": [ + { + "expression": "moderator_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_actions_community_did_idx": { + "name": "mod_actions_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_actions_created_at_idx": { + "name": "mod_actions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_actions_target_uri_idx": { + "name": "mod_actions_target_uri_idx", + "columns": [ + { + "expression": "target_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_actions_target_did_idx": { + "name": "mod_actions_target_did_idx", + "columns": [ + { + "expression": "target_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.reports": { + "name": "reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reporter_did": { + "name": "reporter_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_uri": { + "name": "target_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_did": { + "name": "target_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_type": { + "name": "reason_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "resolution_type": { + "name": "resolution_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "appeal_reason": { + "name": "appeal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "appealed_at": { + "name": "appealed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "appeal_status": { + "name": "appeal_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reports_reporter_did_idx": { + "name": "reports_reporter_did_idx", + "columns": [ + { + "expression": "reporter_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_target_uri_idx": { + "name": "reports_target_uri_idx", + "columns": [ + { + "expression": "target_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_target_did_idx": { + "name": "reports_target_did_idx", + "columns": [ + { + "expression": "target_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_community_did_idx": { + "name": "reports_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_status_idx": { + "name": "reports_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_created_at_idx": { + "name": "reports_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reports_unique_reporter_target_idx": { + "name": "reports_unique_reporter_target_idx", + "columns": [ + { + "expression": "reporter_did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "recipient_did": { + "name": "recipient_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_uri": { + "name": "subject_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_did": { + "name": "actor_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notifications_recipient_did_idx": { + "name": "notifications_recipient_did_idx", + "columns": [ + { + "expression": "recipient_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_recipient_read_idx": { + "name": "notifications_recipient_read_idx", + "columns": [ + { + "expression": "recipient_did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_created_at_idx": { + "name": "notifications_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user_community_preferences": { + "name": "user_community_preferences", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "maturity_override": { + "name": "maturity_override", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "muted_words": { + "name": "muted_words", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_dids": { + "name": "blocked_dids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "muted_dids": { + "name": "muted_dids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "notification_prefs": { + "name": "notification_prefs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_community_prefs_did_idx": { + "name": "user_community_prefs_did_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_community_prefs_community_idx": { + "name": "user_community_prefs_community_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_community_preferences_did_community_did_pk": { + "name": "user_community_preferences_did_community_did_pk", + "columns": ["did", "community_did"] + } + }, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "maturity_level": { + "name": "maturity_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sfw'" + }, + "declared_age": { + "name": "declared_age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "muted_words": { + "name": "muted_words", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_dids": { + "name": "blocked_dids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "muted_dids": { + "name": "muted_dids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cross_post_bluesky": { + "name": "cross_post_bluesky", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cross_post_frontpage": { + "name": "cross_post_frontpage", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cross_post_scopes_granted": { + "name": "cross_post_scopes_granted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cross_posts": { + "name": "cross_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "topic_uri": { + "name": "topic_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cross_post_uri": { + "name": "cross_post_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cross_post_cid": { + "name": "cross_post_cid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cross_posts_topic_uri_idx": { + "name": "cross_posts_topic_uri_idx", + "columns": [ + { + "expression": "topic_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cross_posts_author_did_idx": { + "name": "cross_posts_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.community_onboarding_fields": { + "name": "community_onboarding_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_mandatory": { + "name": "is_mandatory", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "onboarding_fields_community_idx": { + "name": "onboarding_fields_community_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user_onboarding_responses": { + "name": "user_onboarding_responses", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "onboarding_responses_did_community_idx": { + "name": "onboarding_responses_did_community_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_onboarding_responses_did_community_did_field_id_pk": { + "name": "user_onboarding_responses_did_community_did_field_id_pk", + "columns": ["did", "community_did", "field_id"] + } + }, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.moderation_queue": { + "name": "moderation_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "content_uri": { + "name": "content_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queue_reason": { + "name": "queue_reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "matched_words": { + "name": "matched_words", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "reviewed_by": { + "name": "reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "mod_queue_author_did_idx": { + "name": "mod_queue_author_did_idx", + "columns": [ + { + "expression": "author_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_queue_community_did_idx": { + "name": "mod_queue_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_queue_status_idx": { + "name": "mod_queue_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_queue_created_at_idx": { + "name": "mod_queue_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mod_queue_content_uri_idx": { + "name": "mod_queue_content_uri_idx", + "columns": [ + { + "expression": "content_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.account_trust": { + "name": "account_trust", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_post_count": { + "name": "approved_post_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_trusted": { + "name": "is_trusted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trusted_at": { + "name": "trusted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "account_trust_did_community_idx": { + "name": "account_trust_did_community_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_trust_did_idx": { + "name": "account_trust_did_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.community_filters": { + "name": "community_filters", + "schema": "", + "columns": { + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "admin_did": { + "name": "admin_did", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "report_count": { + "name": "report_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_reviewed_at": { + "name": "last_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "filtered_by": { + "name": "filtered_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "community_filters_status_idx": { + "name": "community_filters_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "community_filters_admin_did_idx": { + "name": "community_filters_admin_did_idx", + "columns": [ + { + "expression": "admin_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "community_filters_updated_at_idx": { + "name": "community_filters_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.account_filters": { + "name": "account_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "report_count": { + "name": "report_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ban_count": { + "name": "ban_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_reviewed_at": { + "name": "last_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "filtered_by": { + "name": "filtered_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "account_filters_did_community_idx": { + "name": "account_filters_did_community_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_filters_did_idx": { + "name": "account_filters_did_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_filters_community_did_idx": { + "name": "account_filters_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_filters_status_idx": { + "name": "account_filters_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_filters_updated_at_idx": { + "name": "account_filters_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.ozone_labels": { + "name": "ozone_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "src": { + "name": "src", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "val": { + "name": "val", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "neg": { + "name": "neg", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cts": { + "name": "cts", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "exp": { + "name": "exp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ozone_labels_src_uri_val_idx": { + "name": "ozone_labels_src_uri_val_idx", + "columns": [ + { + "expression": "src", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uri", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "val", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ozone_labels_uri_idx": { + "name": "ozone_labels_uri_idx", + "columns": [ + { + "expression": "uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ozone_labels_val_idx": { + "name": "ozone_labels_val_idx", + "columns": [ + { + "expression": "val", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ozone_labels_indexed_at_idx": { + "name": "ozone_labels_indexed_at_idx", + "columns": [ + { + "expression": "indexed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.community_profiles": { + "name": "community_profiles", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banner_url": { + "name": "banner_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "community_profiles_did_idx": { + "name": "community_profiles_did_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "community_profiles_community_idx": { + "name": "community_profiles_community_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "community_profiles_did_community_did_pk": { + "name": "community_profiles_did_community_did_pk", + "columns": ["did", "community_did"] + } + }, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.interaction_graph": { + "name": "interaction_graph", + "schema": "", + "columns": { + "source_did": { + "name": "source_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_did": { + "name": "target_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_id": { + "name": "community_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interaction_type": { + "name": "interaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "first_interaction_at": { + "name": "first_interaction_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_interaction_at": { + "name": "last_interaction_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_graph_source_target_community_idx": { + "name": "interaction_graph_source_target_community_idx", + "columns": [ + { + "expression": "source_did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "interaction_graph_source_did_target_did_community_id_interaction_type_pk": { + "name": "interaction_graph_source_did_target_did_community_id_interaction_type_pk", + "columns": ["source_did", "target_did", "community_id", "interaction_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trust_seeds": { + "name": "trust_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_id": { + "name": "community_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "trust_seeds_did_community_idx": { + "name": "trust_seeds_did_community_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.trust_scores": { + "name": "trust_scores", + "schema": "", + "columns": { + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_id": { + "name": "community_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "score": { + "name": "score", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "trust_scores_did_community_idx": { + "name": "trust_scores_did_community_idx", + "columns": [ + { + "expression": "did", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "trust_scores_did_community_id_pk": { + "name": "trust_scores_did_community_id_pk", + "columns": ["did", "community_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sybil_clusters": { + "name": "sybil_clusters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "cluster_hash": { + "name": "cluster_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_edge_count": { + "name": "internal_edge_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "external_edge_count": { + "name": "external_edge_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "member_count": { + "name": "member_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'flagged'" + }, + "reviewed_by": { + "name": "reviewed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "detected_at": { + "name": "detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sybil_clusters_hash_idx": { + "name": "sybil_clusters_hash_idx", + "columns": [ + { + "expression": "cluster_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sybil_cluster_members": { + "name": "sybil_cluster_members", + "schema": "", + "columns": { + "cluster_id": { + "name": "cluster_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_in_cluster": { + "name": "role_in_cluster", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sybil_cluster_members_cluster_id_sybil_clusters_id_fk": { + "name": "sybil_cluster_members_cluster_id_sybil_clusters_id_fk", + "tableFrom": "sybil_cluster_members", + "tableTo": "sybil_clusters", + "columnsFrom": ["cluster_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sybil_cluster_members_cluster_id_did_pk": { + "name": "sybil_cluster_members_cluster_id_did_pk", + "columns": ["cluster_id", "did"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.behavioral_flags": { + "name": "behavioral_flags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "flag_type": { + "name": "flag_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "affected_dids": { + "name": "affected_dids", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "detected_at": { + "name": "detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "behavioral_flags_flag_type_idx": { + "name": "behavioral_flags_flag_type_idx", + "columns": [ + { + "expression": "flag_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "behavioral_flags_status_idx": { + "name": "behavioral_flags_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "behavioral_flags_detected_at_idx": { + "name": "behavioral_flags_detected_at_idx", + "columns": [ + { + "expression": "detected_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pds_trust_factors": { + "name": "pds_trust_factors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pds_host": { + "name": "pds_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trust_factor": { + "name": "trust_factor", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pds_trust_factors_pds_host_idx": { + "name": "pds_trust_factors_pds_host_idx", + "columns": [ + { + "expression": "pds_host", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pages": { + "name": "pages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "meta_description": { + "name": "meta_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "community_did": { + "name": "community_did", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pages_slug_community_did_idx": { + "name": "pages_slug_community_did_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pages_community_did_idx": { + "name": "pages_community_did_idx", + "columns": [ + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pages_parent_id_idx": { + "name": "pages_parent_id_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pages_status_community_did_idx": { + "name": "pages_status_community_did_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "community_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pages_parent_id_fk": { + "name": "pages_parent_id_fk", + "tableFrom": "pages", + "tableTo": "pages", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "tenant_isolation": { + "name": "tenant_isolation", + "as": "PERMISSIVE", + "for": "ALL", + "to": ["barazo_app"], + "using": "community_did = current_setting('app.current_community_did', true)", + "withCheck": "community_did = current_setting('app.current_community_did', true)" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": { + "barazo_app": { + "name": "barazo_app", + "createDb": false, + "createRole": false, + "inherit": true + } + }, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 572a5b6..0511790 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1772616692948, "tag": "0004_add_pages_table", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1772623579346, + "tag": "0005_threading-schema", + "breakpoints": true } ] } diff --git a/scripts/backfill-reply-depth.ts b/scripts/backfill-reply-depth.ts new file mode 100644 index 0000000..e982e7a --- /dev/null +++ b/scripts/backfill-reply-depth.ts @@ -0,0 +1,102 @@ +import { sql } from 'drizzle-orm' +import pino from 'pino' +import { createDb } from '../src/db/index.js' +import type { Database } from '../src/db/index.js' +import type { Logger } from '../src/lib/logger.js' + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface BackfillDeps { + db: Database + logger: Logger +} + +export interface BackfillResult { + updated: number +} + +// --------------------------------------------------------------------------- +// Core logic (testable) +// --------------------------------------------------------------------------- + +/** + * Backfill reply depth using a recursive CTE. + * + * Direct replies to a topic (parent_uri = root_uri) get depth 1. + * Nested replies get parent_depth + 1. + * + * Idempotent: safe to run multiple times. Overwrites existing depth values + * with correct computed values. + */ +export async function backfillReplyDepth(deps: BackfillDeps): Promise { + const { db, logger } = deps + + logger.info('Starting reply depth backfill') + + const result = await db.execute(sql` + WITH RECURSIVE reply_tree AS ( + -- Base case: direct replies to topic (depth 1) + SELECT uri, parent_uri, root_uri, 1 AS computed_depth + FROM replies + WHERE parent_uri = root_uri + + UNION ALL + + -- Recursive case: nested replies + SELECT r.uri, r.parent_uri, r.root_uri, rt.computed_depth + 1 + FROM replies r + INNER JOIN reply_tree rt ON r.parent_uri = rt.uri + WHERE r.parent_uri != r.root_uri + ) + UPDATE replies + SET depth = reply_tree.computed_depth + FROM reply_tree + WHERE replies.uri = reply_tree.uri + AND replies.depth != reply_tree.computed_depth + `) + + const updated = Number(result.rowCount ?? 0) + + logger.info({ updated }, 'Reply depth backfill complete') + + return { updated } +} + +// --------------------------------------------------------------------------- +// CLI entry point +// --------------------------------------------------------------------------- + +async function main(): Promise { + const logger = pino({ level: 'info' }) + + const databaseUrl = process.env['DATABASE_URL'] + if (!databaseUrl) { + logger.fatal('DATABASE_URL environment variable is required') + process.exit(1) + } + + const { db, client } = createDb(databaseUrl) + + try { + const result = await backfillReplyDepth({ db, logger }) + logger.info({ updated: result.updated }, 'Backfill complete') + } finally { + await client.end() + } + + process.exit(0) +} + +// Only run main when executed directly via tsx (not imported by Vitest) +const isDirectExecution = + process.argv[1]?.endsWith('backfill-reply-depth.ts') === true && + typeof process.env['VITEST'] === 'undefined' +if (isDirectExecution) { + main().catch((err: unknown) => { + // eslint-disable-next-line no-console -- CLI fallback for fatal errors before logger setup + console.error('Backfill failed:', err) + process.exit(1) + }) +} diff --git a/src/db/schema/community-settings.ts b/src/db/schema/community-settings.ts index 524775e..eda178a 100644 --- a/src/db/schema/community-settings.ts +++ b/src/db/schema/community-settings.ts @@ -2,67 +2,72 @@ import { pgTable, pgPolicy, text, boolean, timestamp, jsonb, integer } from 'dri import { sql } from 'drizzle-orm' import { appRole } from './roles.js' -export const communitySettings = pgTable('community_settings', { - communityDid: text('community_did').primaryKey(), - domains: jsonb('domains').$type().notNull().default([]), - initialized: boolean('initialized').notNull().default(false), - adminDid: text('admin_did'), - communityName: text('community_name').notNull().default('Barazo Community'), - maturityRating: text('maturity_rating', { - enum: ['safe', 'mature', 'adult'], - }) - .notNull() - .default('safe'), - reactionSet: jsonb('reaction_set').$type().notNull().default(['like']), - moderationThresholds: jsonb('moderation_thresholds') - .$type<{ - autoBlockReportCount: number - warnThreshold: number - firstPostQueueCount: number - newAccountDays: number - newAccountWriteRatePerMin: number - establishedWriteRatePerMin: number - linkHoldEnabled: boolean - topicCreationDelayEnabled: boolean - burstPostCount: number - burstWindowMinutes: number - trustedPostThreshold: number - }>() - .notNull() - .default({ - autoBlockReportCount: 5, - warnThreshold: 3, - firstPostQueueCount: 3, - newAccountDays: 7, - newAccountWriteRatePerMin: 3, - establishedWriteRatePerMin: 10, - linkHoldEnabled: true, - topicCreationDelayEnabled: true, - burstPostCount: 5, - burstWindowMinutes: 10, - trustedPostThreshold: 10, +export const communitySettings = pgTable( + 'community_settings', + { + communityDid: text('community_did').primaryKey(), + domains: jsonb('domains').$type().notNull().default([]), + initialized: boolean('initialized').notNull().default(false), + adminDid: text('admin_did'), + communityName: text('community_name').notNull().default('Barazo Community'), + maturityRating: text('maturity_rating', { + enum: ['safe', 'mature', 'adult'], + }) + .notNull() + .default('safe'), + reactionSet: jsonb('reaction_set').$type().notNull().default(['like']), + moderationThresholds: jsonb('moderation_thresholds') + .$type<{ + autoBlockReportCount: number + warnThreshold: number + firstPostQueueCount: number + newAccountDays: number + newAccountWriteRatePerMin: number + establishedWriteRatePerMin: number + linkHoldEnabled: boolean + topicCreationDelayEnabled: boolean + burstPostCount: number + burstWindowMinutes: number + trustedPostThreshold: number + }>() + .notNull() + .default({ + autoBlockReportCount: 5, + warnThreshold: 3, + firstPostQueueCount: 3, + newAccountDays: 7, + newAccountWriteRatePerMin: 3, + establishedWriteRatePerMin: 10, + linkHoldEnabled: true, + topicCreationDelayEnabled: true, + burstPostCount: 5, + burstWindowMinutes: 10, + trustedPostThreshold: 10, + }), + wordFilter: jsonb('word_filter').$type().notNull().default([]), + jurisdictionCountry: text('jurisdiction_country'), + ageThreshold: integer('age_threshold').notNull().default(16), + maxReplyDepth: integer('max_reply_depth').notNull().default(9999), + requireLoginForMature: boolean('require_login_for_mature').notNull().default(true), + communityDescription: text('community_description'), + handle: text('handle'), + serviceEndpoint: text('service_endpoint'), + signingKey: text('signing_key'), + rotationKey: text('rotation_key'), + communityLogoUrl: text('community_logo_url'), + faviconUrl: text('favicon_url'), + primaryColor: text('primary_color'), + accentColor: text('accent_color'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + () => [ + pgPolicy('tenant_isolation', { + as: 'permissive', + to: appRole, + for: 'all', + using: sql`community_did = current_setting('app.current_community_did', true)`, + withCheck: sql`community_did = current_setting('app.current_community_did', true)`, }), - wordFilter: jsonb('word_filter').$type().notNull().default([]), - jurisdictionCountry: text('jurisdiction_country'), - ageThreshold: integer('age_threshold').notNull().default(16), - requireLoginForMature: boolean('require_login_for_mature').notNull().default(true), - communityDescription: text('community_description'), - handle: text('handle'), - serviceEndpoint: text('service_endpoint'), - signingKey: text('signing_key'), - rotationKey: text('rotation_key'), - communityLogoUrl: text('community_logo_url'), - faviconUrl: text('favicon_url'), - primaryColor: text('primary_color'), - accentColor: text('accent_color'), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}, () => [ - pgPolicy('tenant_isolation', { - as: 'permissive', - to: appRole, - for: 'all', - using: sql`community_did = current_setting('app.current_community_did', true)`, - withCheck: sql`community_did = current_setting('app.current_community_did', true)`, - }), -]).enableRLS() + ] +).enableRLS() diff --git a/src/db/schema/onboarding-fields.ts b/src/db/schema/onboarding-fields.ts index c76195f..c9d4ff9 100644 --- a/src/db/schema/onboarding-fields.ts +++ b/src/db/schema/onboarding-fields.ts @@ -35,7 +35,9 @@ export const communityOnboardingFields = pgTable( sortOrder: integer('sort_order').notNull().default(0), source: text('source', { enum: ['platform', 'admin'], - }).notNull().default('admin'), + }) + .notNull() + .default('admin'), config: jsonb('config').$type>(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), diff --git a/src/db/schema/replies.ts b/src/db/schema/replies.ts index 6101dea..0ab55f2 100644 --- a/src/db/schema/replies.ts +++ b/src/db/schema/replies.ts @@ -1,4 +1,13 @@ -import { pgTable, pgPolicy, text, integer, timestamp, jsonb, boolean, index } from 'drizzle-orm/pg-core' +import { + pgTable, + pgPolicy, + text, + integer, + timestamp, + jsonb, + boolean, + index, +} from 'drizzle-orm/pg-core' import { sql } from 'drizzle-orm' import { appRole } from './roles.js' @@ -19,6 +28,7 @@ export const replies = pgTable( labels: jsonb('labels').$type<{ values: { val: string }[] }>(), reactionCount: integer('reaction_count').notNull().default(0), voteCount: integer('vote_count').notNull().default(0), + depth: integer('depth').notNull().default(1), createdAt: timestamp('created_at', { withTimezone: true }).notNull(), indexedAt: timestamp('indexed_at', { withTimezone: true }).notNull().defaultNow(), isAuthorDeleted: boolean('is_author_deleted').notNull().default(false), @@ -48,6 +58,7 @@ export const replies = pgTable( index('replies_moderation_status_idx').on(table.moderationStatus), index('replies_trust_status_idx').on(table.trustStatus), index('replies_root_uri_created_at_idx').on(table.rootUri, table.createdAt), + index('replies_root_uri_depth_idx').on(table.rootUri, table.depth), pgPolicy('tenant_isolation', { as: 'permissive', to: appRole, diff --git a/src/firehose/indexers/reply.ts b/src/firehose/indexers/reply.ts index 0279ecd..c6759da 100644 --- a/src/firehose/indexers/reply.ts +++ b/src/firehose/indexers/reply.ts @@ -49,6 +49,16 @@ export class ReplyIndexer { const createdAt = live ? clampCreatedAt(clientCreatedAt) : clientCreatedAt await this.db.transaction(async (tx) => { + // Compute depth: direct reply to topic = 1, nested = parent_depth + 1 + let depth = 1 + if (parent.uri !== root.uri) { + const parentRows = await tx + .select({ depth: replies.depth }) + .from(replies) + .where(eq(replies.uri, parent.uri)) + depth = parentRows[0] ? parentRows[0].depth + 1 : 1 + } + await tx .insert(replies) .values({ @@ -66,6 +76,7 @@ export class ReplyIndexer { labels: record.labels ?? null, createdAt, trustStatus, + depth, }) .onConflictDoNothing() diff --git a/src/lib/onboarding-gate.ts b/src/lib/onboarding-gate.ts index 472992b..b8accd0 100644 --- a/src/lib/onboarding-gate.ts +++ b/src/lib/onboarding-gate.ts @@ -37,7 +37,10 @@ export async function checkOnboardingComplete( .select() .from(userOnboardingResponses) .where( - and(eq(userOnboardingResponses.did, did), eq(userOnboardingResponses.communityDid, communityDid)) + and( + eq(userOnboardingResponses.did, did), + eq(userOnboardingResponses.communityDid, communityDid) + ) ) const answeredFieldIds = new Set(responses.map((r) => r.fieldId)) diff --git a/src/lib/threading-constants.ts b/src/lib/threading-constants.ts new file mode 100644 index 0000000..3e22691 --- /dev/null +++ b/src/lib/threading-constants.ts @@ -0,0 +1,8 @@ +/** Maximum reply depth stored in community_settings (unlimited). */ +export const MAX_REPLY_DEPTH_DEFAULT = 9999 + +/** Hard ceiling for the depth query parameter to prevent abuse. */ +export const MAX_DEPTH_QUERY_PARAM = 100 + +/** Default depth returned when the query parameter is omitted. */ +export const DEFAULT_DEPTH_QUERY_PARAM = 10 diff --git a/src/routes/admin-settings.ts b/src/routes/admin-settings.ts index fcbe39c..33da808 100644 --- a/src/routes/admin-settings.ts +++ b/src/routes/admin-settings.ts @@ -27,6 +27,7 @@ const settingsJsonSchema = { accentColor: { type: ['string', 'null'] as const }, jurisdictionCountry: { type: ['string', 'null'] as const }, ageThreshold: { type: 'integer' as const }, + maxReplyDepth: { type: 'integer' as const }, requireLoginForMature: { type: 'boolean' as const }, createdAt: { type: 'string' as const, format: 'date-time' as const }, updatedAt: { type: 'string' as const, format: 'date-time' as const }, @@ -92,6 +93,7 @@ function serializeSettings(row: typeof communitySettings.$inferSelect) { accentColor: row.accentColor ?? null, jurisdictionCountry: row.jurisdictionCountry ?? null, ageThreshold: row.ageThreshold, + maxReplyDepth: row.maxReplyDepth, requireLoginForMature: row.requireLoginForMature, createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), @@ -134,6 +136,7 @@ export function adminSettingsRoutes(): FastifyPluginCallback { communityDescription: { type: ['string', 'null'] as const }, communityLogoUrl: { type: ['string', 'null'] as const }, faviconUrl: { type: ['string', 'null'] as const }, + maxReplyDepth: { type: 'integer' as const }, }, }, 404: errorResponseSchema, @@ -159,6 +162,7 @@ export function adminSettingsRoutes(): FastifyPluginCallback { communityDescription: row.communityDescription ?? null, communityLogoUrl: row.communityLogoUrl ?? null, faviconUrl: row.faviconUrl ?? null, + maxReplyDepth: row.maxReplyDepth, }) } ) @@ -234,6 +238,7 @@ export function adminSettingsRoutes(): FastifyPluginCallback { }, jurisdictionCountry: { type: ['string', 'null'] }, ageThreshold: { type: 'integer', minimum: 13, maximum: 18 }, + maxReplyDepth: { type: 'integer', minimum: 1, maximum: 9999 }, requireLoginForMature: { type: 'boolean' }, }, }, @@ -268,6 +273,7 @@ export function adminSettingsRoutes(): FastifyPluginCallback { updates.accentColor === undefined && updates.jurisdictionCountry === undefined && updates.ageThreshold === undefined && + updates.maxReplyDepth === undefined && updates.requireLoginForMature === undefined ) { throw badRequest('At least one field must be provided') @@ -359,6 +365,9 @@ export function adminSettingsRoutes(): FastifyPluginCallback { if (updates.ageThreshold !== undefined) { dbUpdates.ageThreshold = updates.ageThreshold } + if (updates.maxReplyDepth !== undefined) { + dbUpdates.maxReplyDepth = updates.maxReplyDepth + } if (updates.requireLoginForMature !== undefined) { dbUpdates.requireLoginForMature = updates.requireLoginForMature } diff --git a/src/routes/pages.ts b/src/routes/pages.ts index 9c35248..bd08076 100644 --- a/src/routes/pages.ts +++ b/src/routes/pages.ts @@ -298,10 +298,7 @@ export function pageRoutes(): FastifyPluginCallback { async (request, reply) => { const communityDid = requireCommunityDid(request) - const rows = await db - .select() - .from(pages) - .where(eq(pages.communityDid, communityDid)) + const rows = await db.select().from(pages).where(eq(pages.communityDid, communityDid)) const tree = buildPageTree(rows) @@ -530,10 +527,7 @@ export function pageRoutes(): FastifyPluginCallback { } // Fetch all pages to check for cycles - const allPages = await db - .select() - .from(pages) - .where(eq(pages.communityDid, communityDid)) + const allPages = await db.select().from(pages).where(eq(pages.communityDid, communityDid)) if (wouldCreateCycle(id, updates.parentId, allPages)) { throw badRequest('Setting this parent would create a circular reference') @@ -553,11 +547,7 @@ export function pageRoutes(): FastifyPluginCallback { if (updates.parentId !== undefined) dbUpdates.parentId = updates.parentId ?? null if (updates.sortOrder !== undefined) dbUpdates.sortOrder = updates.sortOrder - const updated = await db - .update(pages) - .set(dbUpdates) - .where(eq(pages.id, id)) - .returning() + const updated = await db.update(pages).set(dbUpdates).where(eq(pages.id, id)).returning() const updatedRow = updated[0] if (!updatedRow) { diff --git a/src/routes/replies.ts b/src/routes/replies.ts index 57e5629..6c3a2f0 100644 --- a/src/routes/replies.ts +++ b/src/routes/replies.ts @@ -79,6 +79,7 @@ const replyJsonSchema = { communityDid: { type: 'string' as const }, cid: { type: 'string' as const }, depth: { type: 'integer' as const }, + childCount: { type: 'integer' as const }, reactionCount: { type: 'integer' as const }, isAuthorDeleted: { type: 'boolean' as const }, isModDeleted: { type: 'boolean' as const }, @@ -99,10 +100,7 @@ const replyJsonSchema = { * Converts Date fields to ISO strings and computes depth. */ function serializeReply(row: typeof replies.$inferSelect) { - // Simple depth calculation for MVP: - // depth 0 = direct reply to topic (parentUri === rootUri) - // depth 1 = reply to a reply (parentUri !== rootUri) - const depth = row.parentUri === row.rootUri ? 0 : 1 + const depth = row.depth const isDeleted = row.isAuthorDeleted || row.isModDeleted const placeholderContent = row.isModDeleted @@ -317,6 +315,7 @@ export function replyRoutes(): FastifyPluginCallback { let parentRefUri = topic.uri let parentRefCid = topic.cid + let depth = 1 if (parentUri) { // Look up the parent reply const parentReplyRows = await db.select().from(replies).where(eq(replies.uri, parentUri)) @@ -327,6 +326,7 @@ export function replyRoutes(): FastifyPluginCallback { } parentRefUri = parentReply.uri parentRefCid = parentReply.cid + depth = parentReply.depth + 1 } const now = new Date().toISOString() @@ -378,6 +378,7 @@ export function replyRoutes(): FastifyPluginCallback { cid: pdsResult.cid, labels: labels ?? null, reactionCount: 0, + depth, moderationStatus: contentModerationStatus, createdAt: new Date(now), indexedAt: new Date(), @@ -512,6 +513,7 @@ export function replyRoutes(): FastifyPluginCallback { properties: { cursor: { type: 'string' }, limit: { type: 'string' }, + depth: { type: 'string' }, }, }, response: { @@ -588,10 +590,11 @@ export function replyRoutes(): FastifyPluginCallback { // Block/mute filtering: load the authenticated user's preferences const { blockedDids, mutedDids } = await loadBlockMuteLists(request.user?.did, db) - const { cursor, limit } = parsedQuery.data + const { cursor, limit, depth: maxDepth } = parsedQuery.data const conditions = [ eq(replies.rootUri, decodedTopicUri), eq(replies.moderationStatus, 'approved'), + sql`${replies.depth} <= ${maxDepth}`, ] // Exclude replies by blocked authors @@ -624,6 +627,24 @@ export function replyRoutes(): FastifyPluginCallback { const resultRows = hasMore ? rows.slice(0, limit) : rows const serialized = resultRows.map(serializeReply) + // Query child counts for replies at the depth boundary + const childCountResult = await db + .select({ + parentUri: replies.parentUri, + childCount: sql`count(*)`.as('child_count'), + }) + .from(replies) + .where( + and( + eq(replies.rootUri, decodedTopicUri), + sql`${replies.depth} = ${maxDepth + 1}`, + eq(replies.moderationStatus, 'approved') + ) + ) + .groupBy(replies.parentUri) + + const childCountMap = new Map(childCountResult.map((r) => [r.parentUri, r.childCount])) + // Ozone label annotation: flag content from spam-labeled accounts const ozoneMap = new Map() if (app.ozoneService) { @@ -654,6 +675,7 @@ export function replyRoutes(): FastifyPluginCallback { displayName: null, avatarUrl: null, }, + childCount: childCountMap.get(r.uri) ?? 0, isMuted: mutedSet.has(r.authorDid), isMutedWord: contentMatchesMutedWords(r.content, mutedWords), ozoneLabel: ozoneMap.get(r.authorDid) ?? null, diff --git a/src/validation/admin-settings.ts b/src/validation/admin-settings.ts index bbb3054..3396dce 100644 --- a/src/validation/admin-settings.ts +++ b/src/validation/admin-settings.ts @@ -50,6 +50,12 @@ export const updateSettingsSchema = z.object({ .max(18, 'Age threshold must be at most 18') .optional(), requireLoginForMature: z.boolean().optional(), + maxReplyDepth: z + .number() + .int('Max reply depth must be an integer') + .min(1, 'Max reply depth must be at least 1') + .max(9999, 'Max reply depth must be at most 9999') + .optional(), }) export type UpdateSettingsInput = z.infer @@ -74,6 +80,7 @@ export const settingsResponseSchema = z.object({ accentColor: z.string().nullable(), jurisdictionCountry: z.string().nullable(), ageThreshold: z.number(), + maxReplyDepth: z.number(), requireLoginForMature: z.boolean(), createdAt: z.string(), updatedAt: z.string(), diff --git a/src/validation/pages.ts b/src/validation/pages.ts index 019840d..e0c90a7 100644 --- a/src/validation/pages.ts +++ b/src/validation/pages.ts @@ -71,10 +71,7 @@ export const updatePageSchema = z.object({ .max(200, 'Title must be at most 200 characters') .optional(), slug: slugField.optional(), - content: z - .string() - .max(100_000, 'Content must be at most 100000 characters') - .optional(), + content: z.string().max(100_000, 'Content must be at most 100000 characters').optional(), status: pageStatusSchema.optional(), metaDescription: z .string() diff --git a/src/validation/replies.ts b/src/validation/replies.ts index 6fc23f3..9c9913c 100644 --- a/src/validation/replies.ts +++ b/src/validation/replies.ts @@ -52,6 +52,12 @@ export const replyQuerySchema = z.object({ .pipe(z.number().int().min(1).max(100)) .optional() .default(25), + depth: z + .string() + .transform((val) => Number(val)) + .pipe(z.number().int().min(1).max(100)) + .optional() + .default(10), }) export type ReplyQueryInput = z.infer diff --git a/tests/helpers/mock-db.ts b/tests/helpers/mock-db.ts index 2fffa48..611976d 100644 --- a/tests/helpers/mock-db.ts +++ b/tests/helpers/mock-db.ts @@ -88,8 +88,9 @@ export function createChainableProxy(terminalResult: unknown = []): DbChain { // eslint-disable-next-line @typescript-eslint/no-misused-promises -- Intentionally thenable mock for Drizzle chain chain.where.mockImplementation(() => makeThenable()) - // groupBy chains to having; having is terminal (thenable) - chain.groupBy.mockImplementation(() => chain) + // groupBy is thenable (for standalone use) and also chains to having + // eslint-disable-next-line @typescript-eslint/no-misused-promises -- Intentionally thenable mock for Drizzle chain + chain.groupBy.mockImplementation(() => makeThenable()) // eslint-disable-next-line @typescript-eslint/no-misused-promises -- Intentionally thenable mock for Drizzle chain chain.having.mockImplementation(() => makeThenable()) diff --git a/tests/integration/tenant-isolation.test.ts b/tests/integration/tenant-isolation.test.ts index 1bd9b63..26c8625 100644 --- a/tests/integration/tenant-isolation.test.ts +++ b/tests/integration/tenant-isolation.test.ts @@ -34,6 +34,7 @@ async function pushSchema(client: ReturnType): Promise { word_filter JSONB NOT NULL DEFAULT '[]', jurisdiction_country TEXT, age_threshold INTEGER NOT NULL DEFAULT 16, + max_reply_depth INTEGER NOT NULL DEFAULT 9999, require_login_for_mature BOOLEAN NOT NULL DEFAULT true, community_description TEXT, handle TEXT, @@ -111,6 +112,7 @@ async function pushSchema(client: ReturnType): Promise { labels JSONB, reaction_count INTEGER NOT NULL DEFAULT 0, vote_count INTEGER NOT NULL DEFAULT 0, + depth INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL, indexed_at TIMESTAMPTZ NOT NULL DEFAULT now(), is_author_deleted BOOLEAN NOT NULL DEFAULT false, diff --git a/tests/unit/db/schema/community-settings.test.ts b/tests/unit/db/schema/community-settings.test.ts index 2e783a0..cdda442 100644 --- a/tests/unit/db/schema/community-settings.test.ts +++ b/tests/unit/db/schema/community-settings.test.ts @@ -24,6 +24,7 @@ describe('communitySettings schema', () => { 'communityName', 'maturityRating', 'reactionSet', + 'maxReplyDepth', 'handle', 'serviceEndpoint', 'signingKey', @@ -101,4 +102,9 @@ describe('communitySettings schema', () => { it('has default value for reactionSet', () => { expect(columns.reactionSet.hasDefault).toBe(true) }) + + it('has maxReplyDepth column with notNull and default value', () => { + expect(columns.maxReplyDepth.notNull).toBe(true) + expect(columns.maxReplyDepth.hasDefault).toBe(true) + }) }) diff --git a/tests/unit/db/schema/replies.test.ts b/tests/unit/db/schema/replies.test.ts index 96ec17a..ed39fe1 100644 --- a/tests/unit/db/schema/replies.test.ts +++ b/tests/unit/db/schema/replies.test.ts @@ -30,6 +30,7 @@ describe('replies schema', () => { 'cid', 'labels', 'reactionCount', + 'depth', 'createdAt', 'indexedAt', // Note: search_vector (tsvector) and embedding (vector) columns exist @@ -63,6 +64,11 @@ describe('replies schema', () => { expect(columns.reactionCount.hasDefault).toBe(true) }) + it('has depth column with notNull and default value', () => { + expect(columns.depth.notNull).toBe(true) + expect(columns.depth.hasDefault).toBe(true) + }) + it('has default value for indexed_at', () => { expect(columns.indexedAt.hasDefault).toBe(true) }) diff --git a/tests/unit/firehose/indexers/reply.test.ts b/tests/unit/firehose/indexers/reply.test.ts index 677d629..bbcb8d3 100644 --- a/tests/unit/firehose/indexers/reply.test.ts +++ b/tests/unit/firehose/indexers/reply.test.ts @@ -76,6 +76,133 @@ describe('ReplyIndexer', () => { expect(db.transaction).toHaveBeenCalledTimes(1) }) + + it('sets depth to 1 for direct replies to topic', async () => { + const insertValuesMock = vi.fn().mockReturnValue({ + onConflictDoNothing: vi.fn().mockResolvedValue(undefined), + }) + const mockTx = { + insert: vi.fn().mockReturnValue({ values: insertValuesMock }), + update: vi.fn().mockReturnValue({ + set: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(undefined), + }), + }), + select: vi.fn().mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue([]), + }), + }), + } + const db = { + ...createMockDb(), + transaction: vi + .fn() + .mockImplementation(async (fn: (tx: typeof mockTx) => Promise) => fn(mockTx)), + } + const logger = createMockLogger() + const indexer = new ReplyIndexer(db as never, logger as never) + + const topicUri = 'at://did:plc:test/forum.barazo.topic.post/topic1' + await indexer.handleCreate({ + ...baseParams, + record: { + content: 'Direct reply', + root: { uri: topicUri, cid: 'bafytopic' }, + parent: { uri: topicUri, cid: 'bafytopic' }, + community: 'did:plc:community', + createdAt: '2026-01-01T00:00:00.000Z', + }, + }) + + const values = insertValuesMock.mock.calls[0][0] as Record + expect(values.depth).toBe(1) + }) + + it('computes depth from parent for nested replies', async () => { + const insertValuesMock = vi.fn().mockReturnValue({ + onConflictDoNothing: vi.fn().mockResolvedValue(undefined), + }) + const mockTx = { + insert: vi.fn().mockReturnValue({ values: insertValuesMock }), + update: vi.fn().mockReturnValue({ + set: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(undefined), + }), + }), + select: vi.fn().mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue([{ depth: 2 }]), + }), + }), + } + const db = { + ...createMockDb(), + transaction: vi + .fn() + .mockImplementation(async (fn: (tx: typeof mockTx) => Promise) => fn(mockTx)), + } + const logger = createMockLogger() + const indexer = new ReplyIndexer(db as never, logger as never) + + await indexer.handleCreate({ + ...baseParams, + record: { + content: 'Nested reply', + root: { uri: 'at://did:plc:test/forum.barazo.topic.post/topic1', cid: 'bafytopic' }, + parent: { uri: 'at://did:plc:test/forum.barazo.topic.reply/reply2', cid: 'bafyreply2' }, + community: 'did:plc:community', + createdAt: '2026-01-01T00:00:00.000Z', + }, + }) + + const values = insertValuesMock.mock.calls[0][0] as Record + expect(values.depth).toBe(3) + }) + + it('defaults depth to 1 when parent reply not found', async () => { + const insertValuesMock = vi.fn().mockReturnValue({ + onConflictDoNothing: vi.fn().mockResolvedValue(undefined), + }) + const mockTx = { + insert: vi.fn().mockReturnValue({ values: insertValuesMock }), + update: vi.fn().mockReturnValue({ + set: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(undefined), + }), + }), + select: vi.fn().mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue([]), + }), + }), + } + const db = { + ...createMockDb(), + transaction: vi + .fn() + .mockImplementation(async (fn: (tx: typeof mockTx) => Promise) => fn(mockTx)), + } + const logger = createMockLogger() + const indexer = new ReplyIndexer(db as never, logger as never) + + await indexer.handleCreate({ + ...baseParams, + record: { + content: 'Orphaned nested reply', + root: { uri: 'at://did:plc:test/forum.barazo.topic.post/topic1', cid: 'bafytopic' }, + parent: { + uri: 'at://did:plc:test/forum.barazo.topic.reply/missing', + cid: 'bafymissing', + }, + community: 'did:plc:community', + createdAt: '2026-01-01T00:00:00.000Z', + }, + }) + + const values = insertValuesMock.mock.calls[0][0] as Record + expect(values.depth).toBe(1) + }) }) describe('handleUpdate', () => { diff --git a/tests/unit/routes/admin-design.test.ts b/tests/unit/routes/admin-design.test.ts index 66ed258..d7e8f1f 100644 --- a/tests/unit/routes/admin-design.test.ts +++ b/tests/unit/routes/admin-design.test.ts @@ -34,7 +34,13 @@ vi.mock('sharp', () => { import { adminDesignRoutes } from '../../../src/routes/admin-design.js' // Retrieve the mock instance exported from the vi.mock factory -const { __mockInstance: mockSharpInstance } = await vi.importMock<{ __mockInstance: { resize: ReturnType; webp: ReturnType; toBuffer: ReturnType } }>('sharp') +const { __mockInstance: mockSharpInstance } = await vi.importMock<{ + __mockInstance: { + resize: ReturnType + webp: ReturnType + toBuffer: ReturnType + } +}>('sharp') // --------------------------------------------------------------------------- // Mock env @@ -102,7 +108,11 @@ function createMockStorage(): StorageService { // --------------------------------------------------------------------------- function createMockRequireAdmin(user?: RequestUser) { - return (request: { user?: RequestUser }, reply: { status: (code: number) => { send: (body: unknown) => void } }, done: () => void) => { + return ( + request: { user?: RequestUser }, + reply: { status: (code: number) => { send: (body: unknown) => void } }, + done: () => void + ) => { if (!user) { reply.status(401).send({ error: 'Authentication required' }) return @@ -262,11 +272,7 @@ describe('admin design routes', () => { }) // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockStorage.store).toHaveBeenCalledWith( - expect.any(Buffer), - 'image/webp', - 'logos' - ) + expect(mockStorage.store).toHaveBeenCalledWith(expect.any(Buffer), 'image/webp', 'logos') }) it('returns 401 when not authenticated', async () => { @@ -423,11 +429,7 @@ describe('admin design routes', () => { }) // eslint-disable-next-line @typescript-eslint/unbound-method - expect(mockStorage.store).toHaveBeenCalledWith( - expect.any(Buffer), - 'image/webp', - 'favicons' - ) + expect(mockStorage.store).toHaveBeenCalledWith(expect.any(Buffer), 'image/webp', 'favicons') }) it('returns 401 when not authenticated', async () => { diff --git a/tests/unit/routes/onboarding.test.ts b/tests/unit/routes/onboarding.test.ts index 03eb422..9abb054 100644 --- a/tests/unit/routes/onboarding.test.ts +++ b/tests/unit/routes/onboarding.test.ts @@ -153,7 +153,10 @@ function sampleResponse(overrides?: Record) { // Helper: build test app // --------------------------------------------------------------------------- -async function buildTestApp(user?: RequestUser, envOverrides?: Partial): Promise { +async function buildTestApp( + user?: RequestUser, + envOverrides?: Partial +): Promise { const app = Fastify({ logger: false }) const authMiddleware = createMockAuthMiddleware(user) @@ -241,7 +244,10 @@ describe('onboarding admin routes', () => { }) expect(response.statusCode).toBe(200) - const body = response.json<{ fields: { id: string; source: string }[]; hostingMode: string }>() + const body = response.json<{ + fields: { id: string; source: string }[] + hostingMode: string + }>() expect(body.fields).toHaveLength(2) expect(body.fields[0]?.id).toBe('field-001') expect(body.fields[0]?.source).toBe('admin') diff --git a/tests/unit/routes/pages.test.ts b/tests/unit/routes/pages.test.ts index 682eed9..ea3016d 100644 --- a/tests/unit/routes/pages.test.ts +++ b/tests/unit/routes/pages.test.ts @@ -74,7 +74,7 @@ function resetAllDbMocks(): void { mockDb.select.mockReturnValue(selectChain) mockDb.update.mockReturnValue(updateChain) mockDb.delete.mockReturnValue(deleteChain) - // eslint-disable-next-line @typescript-eslint/no-misused-promises -- Intentionally async mock for Drizzle transaction + mockDb.transaction.mockImplementation(async (fn: (tx: typeof mockDb) => Promise) => { await fn(mockDb) }) @@ -205,10 +205,9 @@ describe('page routes', () => { const longContent = 'A'.repeat(300) selectChain.where.mockImplementation(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([ - samplePageRow({ content: longContent }), - ]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow({ content: longContent })]) + }, })) const response = await app.inject({ @@ -259,8 +258,9 @@ describe('page routes', () => { it('returns a published page with full content', async () => { selectChain.where.mockImplementation(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([samplePageRow()]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow()]) + }, })) const response = await app.inject({ @@ -278,7 +278,9 @@ describe('page routes', () => { // With status filter in the WHERE clause, a draft page is not returned by the DB selectChain.where.mockImplementation(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([]); }, + then: (resolve: (v: unknown) => void) => { + resolve([]) + }, })) const response = await app.inject({ @@ -322,11 +324,12 @@ describe('page routes', () => { it('returns all pages including drafts', async () => { selectChain.where.mockImplementation(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([ + then: (resolve: (v: unknown) => void) => { + resolve([ samplePageRow(), samplePageRow({ id: PAGE_ID_2, slug: 'draft-page', status: 'draft', sortOrder: 1 }), - ]); }, + ]) + }, })) const response = await app.inject({ @@ -383,8 +386,9 @@ describe('page routes', () => { it('returns a page by ID with full content', async () => { selectChain.where.mockImplementation(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([samplePageRow()]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow()]) + }, })) const response = await app.inject({ @@ -433,13 +437,17 @@ describe('page routes', () => { // Slug uniqueness check returns empty (no conflict) selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([]); }, + then: (resolve: (v: unknown) => void) => { + resolve([]) + }, })) // Insert returning insertChain.returning.mockImplementationOnce(() => ({ ...insertChain, - then: (resolve: (v: unknown) => void) => { resolve([created]); }, + then: (resolve: (v: unknown) => void) => { + resolve([created]) + }, })) const response = await app.inject({ @@ -488,7 +496,9 @@ describe('page routes', () => { // Slug uniqueness check returns existing page selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([samplePageRow()]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow()]) + }, })) const response = await app.inject({ @@ -507,12 +517,16 @@ describe('page routes', () => { // Slug uniqueness check returns empty selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([]); }, + then: (resolve: (v: unknown) => void) => { + resolve([]) + }, })) // Parent check returns empty selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([]); }, + then: (resolve: (v: unknown) => void) => { + resolve([]) + }, })) const response = await app.inject({ @@ -567,13 +581,17 @@ describe('page routes', () => { // Find existing selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([existing]); }, + then: (resolve: (v: unknown) => void) => { + resolve([existing]) + }, })) // Update returning updateChain.returning.mockImplementationOnce(() => ({ ...updateChain, - then: (resolve: (v: unknown) => void) => { resolve([updated]); }, + then: (resolve: (v: unknown) => void) => { + resolve([updated]) + }, })) const response = await app.inject({ @@ -603,14 +621,17 @@ describe('page routes', () => { // Find existing selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([existing]); }, + then: (resolve: (v: unknown) => void) => { + resolve([existing]) + }, })) // Slug uniqueness check returns existing page with different ID selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([samplePageRow({ id: PAGE_ID_2, slug: 'other-slug' })]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow({ id: PAGE_ID_2, slug: 'other-slug' })]) + }, })) const response = await app.inject({ @@ -629,19 +650,25 @@ describe('page routes', () => { // Find existing (the parent page) selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([parent]); }, + then: (resolve: (v: unknown) => void) => { + resolve([parent]) + }, })) // Parent exists check selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([child]); }, + then: (resolve: (v: unknown) => void) => { + resolve([child]) + }, })) // Fetch all pages for cycle detection selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([parent, child]); }, + then: (resolve: (v: unknown) => void) => { + resolve([parent, child]) + }, })) const response = await app.inject({ @@ -659,13 +686,17 @@ describe('page routes', () => { // Find existing selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([existing]); }, + then: (resolve: (v: unknown) => void) => { + resolve([existing]) + }, })) // Parent exists check (returns existing which is the same page) selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([existing]); }, + then: (resolve: (v: unknown) => void) => { + resolve([existing]) + }, })) const response = await app.inject({ @@ -702,12 +733,16 @@ describe('page routes', () => { // Find existing selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([samplePageRow()]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow()]) + }, })) // Check children returns empty selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([]); }, + then: (resolve: (v: unknown) => void) => { + resolve([]) + }, })) const response = await app.inject({ @@ -731,13 +766,16 @@ describe('page routes', () => { // Find existing selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => { resolve([samplePageRow()]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow()]) + }, })) // Check children returns a child selectChain.where.mockImplementationOnce(() => ({ ...selectChain, - then: (resolve: (v: unknown) => void) => - { resolve([samplePageRow({ id: PAGE_ID_2, parentId: PAGE_ID_1 })]); }, + then: (resolve: (v: unknown) => void) => { + resolve([samplePageRow({ id: PAGE_ID_2, parentId: PAGE_ID_1 })]) + }, })) const response = await app.inject({ diff --git a/tests/unit/routes/replies.test.ts b/tests/unit/routes/replies.test.ts index 9770f28..a9d3e69 100644 --- a/tests/unit/routes/replies.test.ts +++ b/tests/unit/routes/replies.test.ts @@ -249,6 +249,7 @@ function sampleReplyRow(overrides?: Record) { cid: TEST_REPLY_CID, labels: null, reactionCount: 0, + depth: 1, createdAt: new Date(TEST_NOW), indexedAt: new Date(TEST_NOW), isAuthorDeleted: false, @@ -720,17 +721,19 @@ describe('reply routes', () => { it('includes depth field in reply responses', async () => { selectChain.where.mockResolvedValueOnce([sampleTopicRow()]) - // A direct reply (parentUri === rootUri) should have depth 0 + // A direct reply to topic has depth 1 (stored in DB) const directReply = sampleReplyRow({ parentUri: TEST_TOPIC_URI, parentCid: TEST_TOPIC_CID, + depth: 1, }) - // A nested reply (parentUri !== rootUri) should have depth 1 + // A nested reply has depth 2 (stored in DB) const nestedReply = sampleReplyRow({ uri: `at://${TEST_DID}/forum.barazo.topic.reply/nested001`, rkey: 'nested001', parentUri: TEST_REPLY_URI, parentCid: TEST_REPLY_CID, + depth: 2, }) selectChain.limit.mockResolvedValueOnce([directReply, nestedReply]) @@ -743,8 +746,8 @@ describe('reply routes', () => { expect(response.statusCode).toBe(200) const body = response.json<{ replies: Array<{ depth: number; parentUri: string }> }>() expect(body.replies).toHaveLength(2) - expect(body.replies[0]?.depth).toBe(0) - expect(body.replies[1]?.depth).toBe(1) + expect(body.replies[0]?.depth).toBe(1) + expect(body.replies[1]?.depth).toBe(2) }) it('returns placeholder content for mod-deleted replies', async () => { @@ -1006,6 +1009,8 @@ describe('reply routes', () => { ] selectChain.limit.mockResolvedValueOnce(rows) + selectChain.where.mockImplementationOnce(() => selectChain) // 6.5: child count query + selectChain.where.mockResolvedValueOnce([ // 7: resolveAuthors users { @@ -2020,6 +2025,8 @@ describe('reply routes', () => { ] selectChain.limit.mockResolvedValueOnce(rows) + selectChain.where.mockImplementationOnce(() => selectChain) // child count query + // Ozone batch: spamDid is spam-labeled, TEST_DID is not mockOzoneService.batchIsSpamLabeled.mockResolvedValueOnce( new Map([ diff --git a/tests/unit/setup/service.test.ts b/tests/unit/setup/service.test.ts index b90ee67..0df0e37 100644 --- a/tests/unit/setup/service.test.ts +++ b/tests/unit/setup/service.test.ts @@ -27,7 +27,12 @@ function createMockDb() { }) const onConflictDoNothingFn = vi.fn<() => Promise>().mockResolvedValue([]) const valuesFn = vi - .fn<() => { onConflictDoUpdate: typeof onConflictDoUpdateFn; onConflictDoNothing: typeof onConflictDoNothingFn }>() + .fn< + () => { + onConflictDoUpdate: typeof onConflictDoUpdateFn + onConflictDoNothing: typeof onConflictDoNothingFn + } + >() .mockReturnValue({ onConflictDoUpdate: onConflictDoUpdateFn, onConflictDoNothing: onConflictDoNothingFn, @@ -274,9 +279,7 @@ describe('SetupService', () => { }) it('promotes initializing user to admin role in users table', async () => { - mocks.returningFn.mockResolvedValueOnce([ - { communityName: 'Test Forum', communityDid: null }, - ]) + mocks.returningFn.mockResolvedValueOnce([{ communityName: 'Test Forum', communityDid: null }]) const result = await service.initialize({ did: TEST_DID, @@ -496,10 +499,17 @@ describe('SetupService', () => { // Capture the values passed to the third insert call (pages) let capturedPageValues: Array<{ slug: string; status: string; communityDid: string }> = [] mocks.valuesFn.mockImplementation((vals: unknown) => { - if (Array.isArray(vals) && vals.length > 0 && 'slug' in (vals[0] as Record)) { + if ( + Array.isArray(vals) && + vals.length > 0 && + 'slug' in (vals[0] as Record) + ) { capturedPageValues = vals as typeof capturedPageValues } - return { onConflictDoUpdate: mocks.onConflictDoUpdateFn, onConflictDoNothing: mocks.onConflictDoNothingFn } + return { + onConflictDoUpdate: mocks.onConflictDoUpdateFn, + onConflictDoNothing: mocks.onConflictDoNothingFn, + } }) await service.initialize({ diff --git a/tests/unit/validation/pages.test.ts b/tests/unit/validation/pages.test.ts index 70b5f31..eee96fc 100644 --- a/tests/unit/validation/pages.test.ts +++ b/tests/unit/validation/pages.test.ts @@ -70,9 +70,7 @@ describe('createPageSchema', () => { }) it('accepts title at 200 chars', () => { - expect(createPageSchema.safeParse({ ...validInput, title: 'x'.repeat(200) }).success).toBe( - true - ) + expect(createPageSchema.safeParse({ ...validInput, title: 'x'.repeat(200) }).success).toBe(true) }) // Slug validation @@ -81,9 +79,7 @@ describe('createPageSchema', () => { }) it('rejects slug over 100 chars', () => { - expect(createPageSchema.safeParse({ ...validInput, slug: 'a'.repeat(101) }).success).toBe( - false - ) + expect(createPageSchema.safeParse({ ...validInput, slug: 'a'.repeat(101) }).success).toBe(false) }) it('rejects slug with uppercase', () => { @@ -107,9 +103,9 @@ describe('createPageSchema', () => { }) it('accepts valid slug with hyphens', () => { - expect( - createPageSchema.safeParse({ ...validInput, slug: 'terms-of-service' }).success - ).toBe(true) + expect(createPageSchema.safeParse({ ...validInput, slug: 'terms-of-service' }).success).toBe( + true + ) }) // Reserved slugs @@ -161,9 +157,7 @@ describe('createPageSchema', () => { // metaDescription validation it('accepts null metaDescription', () => { - expect( - createPageSchema.safeParse({ ...validInput, metaDescription: null }).success - ).toBe(true) + expect(createPageSchema.safeParse({ ...validInput, metaDescription: null }).success).toBe(true) }) it('rejects metaDescription over 320 chars', () => { @@ -180,9 +174,7 @@ describe('createPageSchema', () => { // parentId it('accepts parentId as a string', () => { - expect( - createPageSchema.safeParse({ ...validInput, parentId: 'page-123' }).success - ).toBe(true) + expect(createPageSchema.safeParse({ ...validInput, parentId: 'page-123' }).success).toBe(true) }) it('accepts null parentId', () => {