diff --git a/api/drizzle/0015_lethal_master_mold.sql b/api/drizzle/0015_lethal_master_mold.sql new file mode 100644 index 000000000..bc35cec81 --- /dev/null +++ b/api/drizzle/0015_lethal_master_mold.sql @@ -0,0 +1,4 @@ +ALTER TABLE "chat_messages" ADD COLUMN "object_uri" text;--> statement-breakpoint +CREATE UNIQUE INDEX "bookmarks_user_at_uri_unique_idx" ON "bookmarks" USING btree ("user_id","at_uri") WHERE "bookmarks"."at_uri" IS NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "bookmarks_user_object_uri_unique_idx" ON "bookmarks" USING btree ("user_id","object_uri") WHERE "bookmarks"."object_uri" IS NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "chat_messages_object_uri_unique_idx" ON "chat_messages" USING btree ("object_uri") WHERE "chat_messages"."object_uri" IS NOT NULL; \ No newline at end of file diff --git a/api/drizzle/0016_fix_unified_feed_views.sql b/api/drizzle/0016_fix_unified_feed_views.sql new file mode 100644 index 000000000..2e74f3d9c --- /dev/null +++ b/api/drizzle/0016_fix_unified_feed_views.sql @@ -0,0 +1,104 @@ +-- Recreate unified_feed_view with full column set (hashtags, post_type, +-- reply_parent_uri, reply_root_uri, author_avatar, canonical_url, etc.) +-- and create the missing unified_feed_candidates_view. + +DROP VIEW IF EXISTS unified_feed_candidates_view; +DROP VIEW IF EXISTS unified_feed_view; + +CREATE VIEW unified_feed_view AS + SELECT + posts.id, + posts.content, + posts.hashtags, + posts.post_type, + posts.name as title, + posts.summary, + COALESCE(posts.canonical_url, posts.object_uri) as canonical_url, + posts.created_at, + posts.is_public, + posts.author_id, + users.name as author_name, + users.web_id as author_web_id, + users.provider_endpoint as author_provider_endpoint, + NULL::varchar as author_avatar, + 'activitypods' as source, + NULL::varchar as at_uri, + posts.object_uri, + posts.reply_parent_uri, + posts.reply_root_uri + FROM posts + INNER JOIN users ON posts.author_id = users.id + WHERE posts.is_public = true + + UNION ALL + + SELECT + at_posts.id, + at_posts.content, + COALESCE( + ARRAY( + SELECT DISTINCT '#' || lower(trim(feature->>'tag')) + FROM jsonb_array_elements(COALESCE(at_posts.facets, '[]'::jsonb)) facet, + jsonb_array_elements(COALESCE(facet->'features', '[]'::jsonb)) feature + WHERE feature ? 'tag' AND length(trim(feature->>'tag')) > 0 + ), + ARRAY[]::text[] + ) as hashtags, + at_posts.post_type, + at_posts.title, + at_posts.summary, + at_posts.canonical_url, + at_posts.created_at, + at_posts.is_public, + NULL::integer as author_id, + COALESCE(at_identities.handle, at_posts.author_did) as author_name, + at_posts.author_did as author_web_id, + '' as author_provider_endpoint, + at_identities.avatar_url as author_avatar, + 'atproto' as source, + at_posts.at_uri, + NULL::text as object_uri, + at_posts.reply_parent_uri, + at_posts.reply_root_uri + FROM at_posts + LEFT JOIN at_identities ON at_posts.author_did = at_identities.did + WHERE at_posts.is_public = true + + UNION ALL + + SELECT + ap_remote_posts.id, + ap_remote_posts.content, + COALESCE(ap_remote_posts.hashtags, ARRAY[]::text[]) as hashtags, + ap_remote_posts.post_type, + ap_remote_posts.title, + ap_remote_posts.summary, + COALESCE(ap_remote_posts.canonical_url, ap_remote_posts.object_uri) as canonical_url, + ap_remote_posts.created_at, + ap_remote_posts.is_public, + NULL::integer as author_id, + ap_remote_posts.author_name as author_name, + ap_remote_posts.author_web_id as author_web_id, + COALESCE(ap_remote_posts.author_domain, '') as author_provider_endpoint, + ap_actor_cache.avatar_url as author_avatar, + 'activitypods' as source, + NULL::varchar as at_uri, + ap_remote_posts.object_uri, + ap_remote_posts.reply_parent_uri, + ap_remote_posts.reply_root_uri + FROM ap_remote_posts + LEFT JOIN ap_actor_cache ON ap_remote_posts.author_web_id = ap_actor_cache.actor_uri + WHERE ap_remote_posts.is_public = true; + +CREATE VIEW unified_feed_candidates_view AS + SELECT + ufv.*, + COALESCE(ufv.at_uri, ufv.object_uri) AS candidate_uri, + te.parent_author_id AS thread_parent_author_id, + te.root_author_id AS thread_root_author_id, + ts.reply_count AS thread_reply_count, + ts.participant_count AS thread_participant_count, + ts.last_activity_at AS thread_last_activity_at + FROM unified_feed_view ufv + LEFT JOIN thread_edges te ON te.item_uri = COALESCE(ufv.at_uri, ufv.object_uri) + LEFT JOIN thread_stats ts ON ts.root_uri = COALESCE(te.root_uri, COALESCE(ufv.at_uri, ufv.object_uri)); diff --git a/api/drizzle/meta/0015_snapshot.json b/api/drizzle/meta/0015_snapshot.json new file mode 100644 index 000000000..2374bec70 --- /dev/null +++ b/api/drizzle/meta/0015_snapshot.json @@ -0,0 +1,2334 @@ +{ + "id": "2643e129-d280-4491-84cf-21ff9233198a", + "prevId": "482e391e-af9e-4580-9e18-34df14d57e6a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "at_uri": { + "name": "at_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "bookmarked_at": { + "name": "bookmarked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bookmarks_user_at_uri_idx": { + "name": "bookmarks_user_at_uri_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_object_uri_idx": { + "name": "bookmarks_user_object_uri_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_at_uri_unique_idx": { + "name": "bookmarks_user_at_uri_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "at_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"at_uri\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_object_uri_unique_idx": { + "name": "bookmarks_user_object_uri_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"object_uri\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bookmarks_user_id_users_id_fk": { + "name": "bookmarks_user_id_users_id_fk", + "tableFrom": "bookmarks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversation_members": { + "name": "conversation_members", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "conversation_members_conversation_id_conversations_id_fk": { + "name": "conversation_members_conversation_id_conversations_id_fk", + "tableFrom": "conversation_members", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "conversation_members_user_id_users_id_fk": { + "name": "conversation_members_user_id_users_id_fk", + "tableFrom": "conversation_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sender_id": { + "name": "sender_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_sender_id_users_id_fk": { + "name": "messages_sender_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "sender_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivery_hash": { + "name": "delivery_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "activity_type": { + "name": "activity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_uri": { + "name": "actor_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_uri": { + "name": "target_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "notifications_delivery_hash_unique": { + "name": "notifications_delivery_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "delivery_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.posts": { + "name": "posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "post_type": { + "name": "post_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'note'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "posts_author_id_users_id_fk": { + "name": "posts_author_id_users_id_fk", + "tableFrom": "posts", + "tableTo": "users", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "web_id": { + "name": "web_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_endpoint": { + "name": "provider_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "atproto_did": { + "name": "atproto_did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "atproto_handle": { + "name": "atproto_handle", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "pod_token": { + "name": "pod_token", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_web_id_unique": { + "name": "users_web_id_unique", + "nullsNotDistinct": false, + "columns": [ + "web_id" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.viewer_relationship_cache": { + "name": "viewer_relationship_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "viewer_user_id": { + "name": "viewer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject_uri": { + "name": "subject_uri", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "subject_protocol": { + "name": "subject_protocol", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'activitypods'" + }, + "is_following": { + "name": "is_following", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_followed_by": { + "name": "is_followed_by", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_muted": { + "name": "is_muted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_blocked": { + "name": "is_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_blocked_by": { + "name": "is_blocked_by", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "viewer_rel_cache_viewer_subject_idx": { + "name": "viewer_rel_cache_viewer_subject_idx", + "columns": [ + { + "expression": "viewer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "viewer_relationship_cache_viewer_user_id_users_id_fk": { + "name": "viewer_relationship_cache_viewer_user_id_users_id_fk", + "tableFrom": "viewer_relationship_cache", + "tableTo": "users", + "columnsFrom": [ + "viewer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ap_actor_cache": { + "name": "ap_actor_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "actor_uri": { + "name": "actor_uri", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "preferred_username": { + "name": "preferred_username", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(640)", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "banner_url": { + "name": "banner_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "varchar(253)", + "primaryKey": false, + "notNull": false + }, + "followers_count": { + "name": "followers_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "following_count": { + "name": "following_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "posts_count": { + "name": "posts_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cached_at": { + "name": "cached_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ap_actor_cache_actor_uri_unique": { + "name": "ap_actor_cache_actor_uri_unique", + "nullsNotDistinct": false, + "columns": [ + "actor_uri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ap_remote_posts": { + "name": "ap_remote_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "object_uri": { + "name": "object_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": true + }, + "author_web_id": { + "name": "author_web_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "author_domain": { + "name": "author_domain", + "type": "varchar(253)", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "post_type": { + "name": "post_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'note'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ingested_at": { + "name": "ingested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "source_relay": { + "name": "source_relay", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "quoted_post_uri": { + "name": "quoted_post_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ap_remote_posts_object_uri_unique": { + "name": "ap_remote_posts_object_uri_unique", + "nullsNotDistinct": false, + "columns": [ + "object_uri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.at_firehose_cursors": { + "name": "at_firehose_cursors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'relay'" + }, + "committed_seq": { + "name": "committed_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hot_seq": { + "name": "hot_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_connected": { + "name": "is_connected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_event_at": { + "name": "last_event_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_commit_at": { + "name": "last_commit_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "at_firehose_cursors_source_id_unique": { + "name": "at_firehose_cursors_source_id_unique", + "nullsNotDistinct": false, + "columns": [ + "source_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.at_identities": { + "name": "at_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "did": { + "name": "did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(640)", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "banner_url": { + "name": "banner_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "followers_count": { + "name": "followers_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "follows_count": { + "name": "follows_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "posts_count": { + "name": "posts_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "did_document": { + "name": "did_document", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "local_user_id": { + "name": "local_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "at_identities_local_user_id_users_id_fk": { + "name": "at_identities_local_user_id_users_id_fk", + "tableFrom": "at_identities", + "tableTo": "users", + "columnsFrom": [ + "local_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "at_identities_did_unique": { + "name": "at_identities_did_unique", + "nullsNotDistinct": false, + "columns": [ + "did" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.at_posts": { + "name": "at_posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "at_uri": { + "name": "at_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "post_type": { + "name": "post_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'note'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "facets": { + "name": "facets", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "embeds": { + "name": "embeds", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ingested_at": { + "name": "ingested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "source_relay": { + "name": "source_relay", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "firehose_seq": { + "name": "firehose_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "ARRAY[]::text[]" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "at_posts_at_uri_unique": { + "name": "at_posts_at_uri_unique", + "nullsNotDistinct": false, + "columns": [ + "at_uri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.at_records": { + "name": "at_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "author_did": { + "name": "author_did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "collection": { + "name": "collection", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "rkey": { + "name": "rkey", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true + }, + "at_uri": { + "name": "at_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": true + }, + "cid": { + "name": "cid", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "operation": { + "name": "operation", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'create'" + }, + "record": { + "name": "record", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ingested_at": { + "name": "ingested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "source_relay": { + "name": "source_relay", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "firehose_seq": { + "name": "firehose_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "at_records_at_uri_unique": { + "name": "at_records_at_uri_unique", + "nullsNotDistinct": false, + "columns": [ + "at_uri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_edges": { + "name": "thread_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "item_source": { + "name": "item_source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "item_local_post_id": { + "name": "item_local_post_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_at_post_id": { + "name": "item_at_post_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_uri": { + "name": "item_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": true + }, + "reply_author_id": { + "name": "reply_author_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "parent_uri": { + "name": "parent_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "parent_author_id": { + "name": "parent_author_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "root_uri": { + "name": "root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "root_author_id": { + "name": "root_author_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "thread_edges_item_uri_unique": { + "name": "thread_edges_item_uri_unique", + "nullsNotDistinct": false, + "columns": [ + "item_uri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_participants": { + "name": "thread_participants", + "schema": "", + "columns": { + "root_uri": { + "name": "root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": true + }, + "participant_actor_id": { + "name": "participant_actor_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_reply_at": { + "name": "first_reply_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_stats": { + "name": "thread_stats", + "schema": "", + "columns": { + "root_uri": { + "name": "root_uri", + "type": "varchar(3072)", + "primaryKey": true, + "notNull": true + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participant_count": { + "name": "participant_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "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.chat_convos": { + "name": "chat_convos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(40)", + "primaryKey": true, + "notNull": true + }, + "convo_type": { + "name": "convo_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "name": { + "name": "name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "rev": { + "name": "rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_members": { + "name": "chat_members", + "schema": "", + "columns": { + "convo_id": { + "name": "convo_id", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "user_did": { + "name": "user_did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_read_rev": { + "name": "last_read_rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "chat_members_did_idx": { + "name": "chat_members_did_idx", + "columns": [ + { + "expression": "user_did", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "chat_members_convo_id_user_did_pk": { + "name": "chat_members_convo_id_user_did_pk", + "columns": [ + "convo_id", + "user_did" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_messages": { + "name": "chat_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(36)", + "primaryKey": true, + "notNull": true + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "convo_id": { + "name": "convo_id", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "sender_did": { + "name": "sender_did", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mentions": { + "name": "mentions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY[]::text[]" + }, + "attachments": { + "name": "attachments", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "in_reply_to_message_id": { + "name": "in_reply_to_message_id", + "type": "varchar(36)", + "primaryKey": false, + "notNull": false + }, + "quote_message_id": { + "name": "quote_message_id", + "type": "varchar(36)", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "rev": { + "name": "rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_messages_convo_sent_idx": { + "name": "chat_messages_convo_sent_idx", + "columns": [ + { + "expression": "convo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_messages_convo_id_idx": { + "name": "chat_messages_convo_id_idx", + "columns": [ + { + "expression": "convo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_messages_in_reply_to_idx": { + "name": "chat_messages_in_reply_to_idx", + "columns": [ + { + "expression": "convo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "in_reply_to_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_messages_quote_idx": { + "name": "chat_messages_quote_idx", + "columns": [ + { + "expression": "convo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "quote_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_messages_object_uri_unique_idx": { + "name": "chat_messages_object_uri_unique_idx", + "columns": [ + { + "expression": "object_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat_messages\".\"object_uri\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.posts_view": { + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "post_type": { + "name": "post_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_web_id": { + "name": "author_web_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_provider_endpoint": { + "name": "author_provider_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "definition": "SELECT\n posts.*,\n users.name as author_name,\n users.web_id as author_web_id,\n users.provider_endpoint as author_provider_endpoint\n FROM posts\n INNER JOIN users on posts.author_id = users.id\n WHERE posts.is_public = true", + "name": "posts_view", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.unified_feed_candidates_view": { + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_type": { + "name": "post_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_web_id": { + "name": "author_web_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_provider_endpoint": { + "name": "author_provider_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar": { + "name": "author_avatar", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "at_uri": { + "name": "at_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "candidate_uri": { + "name": "candidate_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "thread_parent_author_id": { + "name": "thread_parent_author_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "thread_root_author_id": { + "name": "thread_root_author_id", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "thread_reply_count": { + "name": "thread_reply_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "thread_participant_count": { + "name": "thread_participant_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "thread_last_activity_at": { + "name": "thread_last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n ufv.*,\n COALESCE(ufv.at_uri, ufv.object_uri) AS candidate_uri,\n te.parent_author_id AS thread_parent_author_id,\n te.root_author_id AS thread_root_author_id,\n ts.reply_count AS thread_reply_count,\n ts.participant_count AS thread_participant_count,\n ts.last_activity_at AS thread_last_activity_at\n FROM unified_feed_view ufv\n LEFT JOIN thread_edges te ON te.item_uri = COALESCE(ufv.at_uri, ufv.object_uri)\n LEFT JOIN thread_stats ts ON ts.root_uri = COALESCE(te.root_uri, COALESCE(ufv.at_uri, ufv.object_uri))\n", + "name": "unified_feed_candidates_view", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.unified_feed_view": { + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hashtags": { + "name": "hashtags", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_type": { + "name": "post_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_url": { + "name": "canonical_url", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_web_id": { + "name": "author_web_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_provider_endpoint": { + "name": "author_provider_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar": { + "name": "author_avatar", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "at_uri": { + "name": "at_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "object_uri": { + "name": "object_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_parent_uri": { + "name": "reply_parent_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + }, + "reply_root_uri": { + "name": "reply_root_uri", + "type": "varchar(3072)", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n posts.id,\n posts.content,\n posts.hashtags,\n posts.post_type,\n posts.name as title,\n posts.summary,\n COALESCE(posts.canonical_url, posts.object_uri) as canonical_url,\n posts.created_at,\n posts.is_public,\n posts.author_id,\n users.name as author_name,\n users.web_id as author_web_id,\n users.provider_endpoint as author_provider_endpoint,\n NULL::varchar as author_avatar,\n 'activitypods' as source,\n NULL::varchar as at_uri,\n posts.object_uri,\n posts.reply_parent_uri,\n posts.reply_root_uri\n FROM posts\n INNER JOIN users ON posts.author_id = users.id\n WHERE posts.is_public = true\n\n UNION ALL\n\n SELECT\n at_posts.id,\n at_posts.content,\n COALESCE(\n ARRAY(\n SELECT DISTINCT '#' || lower(trim(feature->>'tag'))\n FROM jsonb_array_elements(COALESCE(at_posts.facets, '[]'::jsonb)) facet,\n jsonb_array_elements(COALESCE(facet->'features', '[]'::jsonb)) feature\n WHERE feature ? 'tag' AND length(trim(feature->>'tag')) > 0\n ),\n ARRAY[]::text[]\n ) as hashtags,\n at_posts.post_type,\n at_posts.title,\n at_posts.summary,\n at_posts.canonical_url,\n at_posts.created_at,\n at_posts.is_public,\n NULL::integer as author_id,\n COALESCE(at_identities.handle, at_posts.author_did) as author_name,\n at_posts.author_did as author_web_id,\n '' as author_provider_endpoint,\n at_identities.avatar_url as author_avatar,\n 'atproto' as source,\n at_posts.at_uri,\n NULL::text as object_uri,\n at_posts.reply_parent_uri,\n at_posts.reply_root_uri\n FROM at_posts\n LEFT JOIN at_identities ON at_posts.author_did = at_identities.did\n WHERE at_posts.is_public = true\n\n UNION ALL\n\n SELECT\n ap_remote_posts.id,\n ap_remote_posts.content,\n COALESCE(ap_remote_posts.hashtags, ARRAY[]::text[]) as hashtags,\n ap_remote_posts.post_type,\n ap_remote_posts.title,\n ap_remote_posts.summary,\n COALESCE(ap_remote_posts.canonical_url, ap_remote_posts.object_uri) as canonical_url,\n ap_remote_posts.created_at,\n ap_remote_posts.is_public,\n NULL::integer as author_id,\n ap_remote_posts.author_name as author_name,\n ap_remote_posts.author_web_id as author_web_id,\n COALESCE(ap_remote_posts.author_domain, '') as author_provider_endpoint,\n ap_actor_cache.avatar_url as author_avatar,\n 'activitypods' as source,\n NULL::varchar as at_uri,\n ap_remote_posts.object_uri,\n ap_remote_posts.reply_parent_uri,\n ap_remote_posts.reply_root_uri\n FROM ap_remote_posts\n LEFT JOIN ap_actor_cache ON ap_remote_posts.author_web_id = ap_actor_cache.actor_uri\n WHERE ap_remote_posts.is_public = true\n", + "name": "unified_feed_view", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/api/drizzle/meta/_journal.json b/api/drizzle/meta/_journal.json index b32859eea..7fa40d3ea 100644 --- a/api/drizzle/meta/_journal.json +++ b/api/drizzle/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1777896060000, "tag": "0014_chat_pod_commit_projection", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1778086085032, + "tag": "0015_lethal_master_mold", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/api/src/routes/atBridge.ts b/api/src/routes/atBridge.ts index c37567579..4cd39c50c 100644 --- a/api/src/routes/atBridge.ts +++ b/api/src/routes/atBridge.ts @@ -750,33 +750,40 @@ async function queryFeedCandidates(params: { const includeAp = !source || source === 'all' || source === 'activitypods' const normalizedTag = hashtag ? normalizeHashtag(hashtag) : null - const atFilters: SQL[] = [sql`ap.is_public = true`] - const apFilters: SQL[] = [sql`p.is_public = true`] + // Build query as a plain string with inline params to avoid Drizzle template + // parser misinterpreting PostgreSQL array path literals like '{subject,uri}' + const lim = String(fetchLimit) // safe integer, inline directly + const esc = (v: string) => `'${v.replace(/'/g, "''")}'` // SQL string escape + + // Per-source WHERE clauses + const atWhereParts: string[] = ['ap.is_public = true'] + const apWhereParts: string[] = ['p.is_public = true'] if (normalizedTag) { const tagPattern = `%${normalizedTag}%` - atFilters.push(sql`(ap.content ILIKE ${tagPattern} OR ap.hashtags @> ARRAY[${normalizedTag}]::text[])`) - apFilters.push(sql`(p.content ILIKE ${tagPattern} OR p.hashtags @> ARRAY[${normalizedTag}]::text[])`) + atWhereParts.push(`(ap.content ILIKE ${esc(tagPattern)} OR ap.hashtags @> ARRAY[${esc(normalizedTag)}]::text[])`) + apWhereParts.push(`(p.content ILIKE ${esc(tagPattern)} OR p.hashtags @> ARRAY[${esc(normalizedTag)}]::text[])`) } if (sinceDate) { - atFilters.push(sql`ap.created_at > ${sinceDate}`) - apFilters.push(sql`p.created_at > ${sinceDate}`) + const isoDate = sinceDate.toISOString() + atWhereParts.push(`ap.created_at > ${esc(isoDate)}::timestamptz`) + apWhereParts.push(`p.created_at > ${esc(isoDate)}::timestamptz`) } - const atWhere = sql.join(atFilters, sql` AND `) - const apWhere = sql.join(apFilters, sql` AND `) + const atWhere = atWhereParts.join(' AND ') + const apWhere = apWhereParts.join(' AND ') - const cteFragments: SQL[] = [] - const unionArms: SQL[] = [] + const cteFragments: string[] = [] + const unionArms: string[] = [] if (includeAt) { - cteFragments.push(sql`at_limited AS MATERIALIZED ( + cteFragments.push(`at_limited AS MATERIALIZED ( SELECT ap.id, ap.content, COALESCE(ap.hashtags, ARRAY[]::text[]) AS hashtags, ap.post_type, ap.title, ap.summary, ap.canonical_url, (CASE WHEN jsonb_typeof(ap.embeds) = 'array' THEN jsonb_array_length(ap.embeds) > 0 - WHEN jsonb_typeof(ap.embeds) = 'object' THEN jsonb_object_length(ap.embeds) > 0 + WHEN jsonb_typeof(ap.embeds) = 'object' THEN (SELECT COUNT(*) > 0 FROM jsonb_object_keys(ap.embeds)) ELSE false END) AS has_media, ap.created_at, ap.is_public, @@ -792,13 +799,13 @@ async function queryFeedCandidates(params: { LEFT JOIN at_identities ai ON ap.author_did = ai.did WHERE ${atWhere} ORDER BY LEAST(ap.created_at, NOW()) DESC - LIMIT ${fetchLimit} - `) - unionArms.push(sql`SELECT * FROM at_limited`) + LIMIT ${lim} + )`) + unionArms.push('SELECT * FROM at_limited') } if (includeAp) { - cteFragments.push(sql`ap_limited AS MATERIALIZED ( + cteFragments.push(`ap_limited AS MATERIALIZED ( SELECT p.id, p.content, p.hashtags, p.post_type, p.name AS title, p.summary, @@ -815,11 +822,11 @@ async function queryFeedCandidates(params: { JOIN users u ON p.author_id = u.id WHERE ${apWhere} ORDER BY p.created_at DESC - LIMIT ${fetchLimit} + LIMIT ${lim} )`) - unionArms.push(sql`SELECT * FROM ap_limited`) + unionArms.push('SELECT * FROM ap_limited') - cteFragments.push(sql`ap_remote_limited AS MATERIALIZED ( + cteFragments.push(`ap_remote_limited AS MATERIALIZED ( SELECT apr.id, apr.content, COALESCE(apr.hashtags, ARRAY[]::text[]) AS hashtags, @@ -837,22 +844,22 @@ async function queryFeedCandidates(params: { FROM ap_remote_posts apr WHERE apr.is_public = true ORDER BY apr.created_at DESC - LIMIT ${fetchLimit} + LIMIT ${lim} )`) - unionArms.push(sql`SELECT * FROM ap_remote_limited`) + unionArms.push('SELECT * FROM ap_remote_limited') } - cteFragments.push(sql`combined AS MATERIALIZED ( - ${sql.join(unionArms, sql` UNION ALL `)} + cteFragments.push(`combined AS MATERIALIZED ( + ${unionArms.join(' UNION ALL ')} )`) - cteFragments.push(sql`candidate_uris AS MATERIALIZED ( + cteFragments.push(`candidate_uris AS MATERIALIZED ( SELECT DISTINCT c.candidate_uri FROM combined c WHERE c.candidate_uri IS NOT NULL )`) - cteFragments.push(sql`like_counts AS MATERIALIZED ( + cteFragments.push(`like_counts AS MATERIALIZED ( SELECT ar.record #>> '{subject,uri}' AS subject_uri, COUNT(*)::integer AS like_count @@ -863,10 +870,9 @@ async function queryFeedCandidates(params: { GROUP BY 1 )`) - cteFragments.push(sql`quote_counts AS MATERIALIZED ( + cteFragments.push(`quote_counts AS MATERIALIZED ( SELECT subject_uri, SUM(quote_count)::integer AS quote_count FROM ( - -- AT Protocol quote-posts (embed paths) SELECT COALESCE( ap.embeds #>> '{record,uri}', @@ -891,7 +897,6 @@ async function queryFeedCandidates(params: { ) IN (SELECT candidate_uri FROM candidate_uris) GROUP BY 1 UNION ALL - -- ActivityPub native quote-posts (quoteUrl / FEP-e232 / Misskey fields) SELECT apr.quoted_post_uri AS subject_uri, COUNT(*)::integer AS quote_count @@ -903,8 +908,8 @@ async function queryFeedCandidates(params: { GROUP BY subject_uri )`) - const fullQuery = sql` - WITH ${sql.join(cteFragments, sql`, `)} + const fullQueryText = ` + WITH ${cteFragments.join(', ')} SELECT c.*, te.parent_author_id AS thread_parent_author_id, @@ -920,10 +925,10 @@ async function queryFeedCandidates(params: { LEFT JOIN like_counts lc ON lc.subject_uri = c.candidate_uri LEFT JOIN quote_counts qc ON qc.subject_uri = c.candidate_uri ORDER BY LEAST(c.created_at, NOW()) DESC - LIMIT ${fetchLimit} + LIMIT ${lim} ` - const result = await db.execute(fullQuery) + const result = await db.execute(sql.raw(fullQueryText)) const rows = (result as unknown as { rows: Record[] }).rows const postRows = rows.map(mapDbRowToFeedRow) const repostRows = await loadRepostCandidateRows({ diff --git a/api/src/routes/oidcAuth.ts b/api/src/routes/oidcAuth.ts index a4f4b2cb2..b843b5235 100644 --- a/api/src/routes/oidcAuth.ts +++ b/api/src/routes/oidcAuth.ts @@ -8,6 +8,7 @@ import { getTokenObject } from '../services/jwt' import { syncAtprotoIdentity } from '../services/WebIdProfileService' import { localeFromHeaders, translate } from '../i18n' import { toPublicUser } from '../services/PodTokenService' +import { encryptToken } from '../services/TokenVault' /** * How long (in ms) to wait for the ATProto identity link before issuing the @@ -126,9 +127,27 @@ const oidcAuthPlugin = new Elysia({ prefix: '/oidc-auth' }) name: getNameFromWebId(webId), email: `${Buffer.from(webId).toString('base64url')}@memory.local`, webId, - providerEndpoint + providerEndpoint, + // Store the OIDC access_token as a fallback pod token. + // If the pod accepts Solid OIDC tokens for outbox writes this will + // work; otherwise the user must also sign in via /signin to store + // the pod-native JWT. + podToken: encryptToken(tokens.access_token) }) .returning() + } else { + // Only update the pod token if the existing one is missing/empty. + // The pod-native JWT (stored by /signin) takes priority over the OIDC + // access_token because ActivityPods outbox writes require the native JWT. + const existingToken = dbUserRows[0].podToken + if (!existingToken) { + dbUserRows = await db + .update(users) + .set({ podToken: encryptToken(tokens.access_token) }) + .where(eq(users.webId, webId)) + .returning() + } + // If a pod-native token already exists, leave it untouched. } let dbUser = dbUserRows[0] diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 7ce6d738f..cec8d4685 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -26,21 +26,23 @@ services: SEMAPPS_JENA_PASSWORD: admin SEMAPPS_APP_LANG: en SEMAPPS_MAPBOX_ACCESS_TOKEN: ${MAPBOX_ACCESS_TOKEN:?You must set it in the .env.local file} + SEMAPPS_FRONTEND_URL: 'http://localhost:5001' # Allow the Pod backend to access the app backend as localhost:3001 # See https://docs.docker.com/network/drivers/host/ network_mode: 'host' - #activitypods-frontend: - # image: activitypods/frontend - # depends_on: - # - activitypods-backend - # restart: always - # ports: - # - '5000:5000' - # expose: - # - '5000' - # environment: - # REACT_APP_CONFIG_URL: 'http://localhost:3000/.well-known/config.js' + activitypods-frontend: + image: activitypods/frontend + depends_on: + - activitypods-backend + restart: always + ports: + - '5000:5000' + expose: + - '5000' + environment: + REACT_APP_CONFIG_URL: 'http://localhost:3000/.well-known/config.js' + command: serve -s build -l 5000 #mailcatcher: # image: dockage/mailcatcher:0.7.1 # ports: diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 7277e7570..421fd6a63 100755 Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index c538b9dc0..0b6a129fd 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -1,9 +1,11 @@