diff --git a/drizzle/0064_relay.sql b/drizzle/0064_relay.sql new file mode 100644 index 00000000..a9b1a8b7 --- /dev/null +++ b/drizzle/0064_relay.sql @@ -0,0 +1,12 @@ +CREATE TYPE "public"."relay_state" AS ENUM('idle', 'pending', 'accepted', 'rejected');--> statement-breakpoint +CREATE TABLE "relays" ( + "relay_server_actor_id" uuid, + "state" "relay_state" DEFAULT 'idle' NOT NULL, + "follow_request_id" text NOT NULL, + "inbox_url" text PRIMARY KEY NOT NULL, + "relay_client_actor_id" uuid NOT NULL, + CONSTRAINT "relays_follow_request_id_unique" UNIQUE("follow_request_id") +); +--> statement-breakpoint +ALTER TABLE "relays" ADD CONSTRAINT "relays_relay_server_actor_id_accounts_id_fk" FOREIGN KEY ("relay_server_actor_id") REFERENCES "public"."accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "relays" ADD CONSTRAINT "relays_relay_client_actor_id_accounts_id_fk" FOREIGN KEY ("relay_client_actor_id") REFERENCES "public"."accounts"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0062_snapshot.json b/drizzle/meta/0062_snapshot.json index 666cbd53..e8cfd128 100644 --- a/drizzle/meta/0062_snapshot.json +++ b/drizzle/meta/0062_snapshot.json @@ -2833,4 +2833,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0064_snapshot.json b/drizzle/meta/0064_snapshot.json new file mode 100644 index 00000000..92347511 --- /dev/null +++ b/drizzle/meta/0064_snapshot.json @@ -0,0 +1,2959 @@ +{ + "id": "7e4cdb06-f007-416d-958b-34723c4626dd", + "prevId": "d9963900-ab3c-4252-ad78-4a03a0f28e02", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.access_tokens": { + "name": "access_tokens", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_owner_id": { + "name": "account_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "grant_type": { + "name": "grant_type", + "type": "grant_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'authorization_code'" + }, + "scopes": { + "name": "scopes", + "type": "scope[]", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "access_tokens_application_id_applications_id_fk": { + "name": "access_tokens_application_id_applications_id_fk", + "tableFrom": "access_tokens", + "tableTo": "applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "access_tokens_account_owner_id_account_owners_id_fk": { + "name": "access_tokens_account_owner_id_account_owners_id_fk", + "tableFrom": "access_tokens", + "tableTo": "account_owners", + "columnsFrom": [ + "account_owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account_owners": { + "name": "account_owners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rsa_private_key_jwk": { + "name": "rsa_private_key_jwk", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "rsa_public_key_jwk": { + "name": "rsa_public_key_jwk", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "ed25519_private_key_jwk": { + "name": "ed25519_private_key_jwk", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "ed25519_public_key_jwk": { + "name": "ed25519_public_key_jwk", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "fields": { + "name": "fields", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "followed_tags": { + "name": "followed_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "visibility": { + "name": "visibility", + "type": "post_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'en'" + }, + "discoverable": { + "name": "discoverable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "theme_color": { + "name": "theme_color", + "type": "theme_color", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_owners_id_accounts_id_fk": { + "name": "account_owners_id_accounts_id_fk", + "tableFrom": "account_owners", + "tableTo": "accounts", + "columnsFrom": [ + "id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "account_owners_handle_unique": { + "name": "account_owners_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "iri": { + "name": "iri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "account_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bio_html": { + "name": "bio_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protected": { + "name": "protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_url": { + "name": "inbox_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "followers_url": { + "name": "followers_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_inbox_url": { + "name": "shared_inbox_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "featured_url": { + "name": "featured_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "following_count": { + "name": "following_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "followers_count": { + "name": "followers_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "posts_count": { + "name": "posts_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "field_htmls": { + "name": "field_htmls", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "emojis": { + "name": "emojis", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "successor_id": { + "name": "successor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "(ARRAY[]::text[])" + }, + "instance_host": { + "name": "instance_host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "published": { + "name": "published", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "accounts_successor_id_accounts_id_fk": { + "name": "accounts_successor_id_accounts_id_fk", + "tableFrom": "accounts", + "tableTo": "accounts", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "accounts_instance_host_instances_host_fk": { + "name": "accounts_instance_host_instances_host_fk", + "tableFrom": "accounts", + "tableTo": "instances", + "columnsFrom": [ + "instance_host" + ], + "columnsTo": [ + "host" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "accounts_iri_unique": { + "name": "accounts_iri_unique", + "nullsNotDistinct": false, + "columns": [ + "iri" + ] + }, + "accounts_handle_unique": { + "name": "accounts_handle_unique", + "nullsNotDistinct": false, + "columns": [ + "handle" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.applications": { + "name": "applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "scope[]", + "primaryKey": false, + "notNull": true + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "applications_client_id_unique": { + "name": "applications_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.blocks": { + "name": "blocks", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_account_id": { + "name": "blocked_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "blocks_account_id_index": { + "name": "blocks_account_id_index", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "blocks_blocked_account_id_index": { + "name": "blocks_blocked_account_id_index", + "columns": [ + { + "expression": "blocked_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "blocks_account_id_accounts_id_fk": { + "name": "blocks_account_id_accounts_id_fk", + "tableFrom": "blocks", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "blocks_blocked_account_id_accounts_id_fk": { + "name": "blocks_blocked_account_id_accounts_id_fk", + "tableFrom": "blocks", + "tableTo": "accounts", + "columnsFrom": [ + "blocked_account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "blocks_account_id_blocked_account_id_pk": { + "name": "blocks_account_id_blocked_account_id_pk", + "columns": [ + "account_id", + "blocked_account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_owner_id": { + "name": "account_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "bookmarks_post_id_account_owner_id_index": { + "name": "bookmarks_post_id_account_owner_id_index", + "columns": [ + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bookmarks_post_id_posts_id_fk": { + "name": "bookmarks_post_id_posts_id_fk", + "tableFrom": "bookmarks", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bookmarks_account_owner_id_account_owners_id_fk": { + "name": "bookmarks_account_owner_id_account_owners_id_fk", + "tableFrom": "bookmarks", + "tableTo": "account_owners", + "columnsFrom": [ + "account_owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bookmarks_post_id_account_owner_id_pk": { + "name": "bookmarks_post_id_account_owner_id_pk", + "columns": [ + "post_id", + "account_owner_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credentials": { + "name": "credentials", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "varchar(254)", + "primaryKey": true, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_emojis": { + "name": "custom_emojis", + "schema": "", + "columns": { + "shortcode": { + "name": "shortcode", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.featured_tags": { + "name": "featured_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_owner_id": { + "name": "account_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "featured_tags_account_owner_id_account_owners_id_fk": { + "name": "featured_tags_account_owner_id_account_owners_id_fk", + "tableFrom": "featured_tags", + "tableTo": "account_owners", + "columnsFrom": [ + "account_owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "featured_tags_account_owner_id_name_unique": { + "name": "featured_tags_account_owner_id_name_unique", + "nullsNotDistinct": false, + "columns": [ + "account_owner_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.follows": { + "name": "follows", + "schema": "", + "columns": { + "iri": { + "name": "iri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "following_id": { + "name": "following_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "follower_id": { + "name": "follower_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "shares": { + "name": "shares", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify": { + "name": "notify", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "languages": { + "name": "languages", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "approved": { + "name": "approved", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "follows_following_id_accounts_id_fk": { + "name": "follows_following_id_accounts_id_fk", + "tableFrom": "follows", + "tableTo": "accounts", + "columnsFrom": [ + "following_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "follows_follower_id_accounts_id_fk": { + "name": "follows_follower_id_accounts_id_fk", + "tableFrom": "follows", + "tableTo": "accounts", + "columnsFrom": [ + "follower_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "follows_following_id_follower_id_pk": { + "name": "follows_following_id_follower_id_pk", + "columns": [ + "following_id", + "follower_id" + ] + } + }, + "uniqueConstraints": { + "follows_iri_unique": { + "name": "follows_iri_unique", + "nullsNotDistinct": false, + "columns": [ + "iri" + ] + } + }, + "policies": {}, + "checkConstraints": { + "ck_follows_self": { + "name": "ck_follows_self", + "value": "\"follows\".\"following_id\" != \"follows\".\"follower_id\"" + } + }, + "isRLSEnabled": false + }, + "public.instances": { + "name": "instances", + "schema": "", + "columns": { + "host": { + "name": "host", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "software": { + "name": "software", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.likes": { + "name": "likes", + "schema": "", + "columns": { + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "likes_account_id_post_id_index": { + "name": "likes_account_id_post_id_index", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "likes_post_id_posts_id_fk": { + "name": "likes_post_id_posts_id_fk", + "tableFrom": "likes", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "likes_account_id_accounts_id_fk": { + "name": "likes_account_id_accounts_id_fk", + "tableFrom": "likes", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "likes_post_id_account_id_pk": { + "name": "likes_post_id_account_id_pk", + "columns": [ + "post_id", + "account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.list_members": { + "name": "list_members", + "schema": "", + "columns": { + "list_id": { + "name": "list_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "list_members_list_id_lists_id_fk": { + "name": "list_members_list_id_lists_id_fk", + "tableFrom": "list_members", + "tableTo": "lists", + "columnsFrom": [ + "list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "list_members_account_id_accounts_id_fk": { + "name": "list_members_account_id_accounts_id_fk", + "tableFrom": "list_members", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "list_members_list_id_account_id_pk": { + "name": "list_members_list_id_account_id_pk", + "columns": [ + "list_id", + "account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.list_posts": { + "name": "list_posts", + "schema": "", + "columns": { + "list_id": { + "name": "list_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "list_posts_list_id_post_id_index": { + "name": "list_posts_list_id_post_id_index", + "columns": [ + { + "expression": "list_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "list_posts_list_id_lists_id_fk": { + "name": "list_posts_list_id_lists_id_fk", + "tableFrom": "list_posts", + "tableTo": "lists", + "columnsFrom": [ + "list_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "list_posts_post_id_posts_id_fk": { + "name": "list_posts_post_id_posts_id_fk", + "tableFrom": "list_posts", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "list_posts_list_id_post_id_pk": { + "name": "list_posts_list_id_post_id_pk", + "columns": [ + "list_id", + "post_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lists": { + "name": "lists", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_owner_id": { + "name": "account_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replies_policy": { + "name": "replies_policy", + "type": "list_replies_policy", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'list'" + }, + "exclusive": { + "name": "exclusive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "lists_account_owner_id_account_owners_id_fk": { + "name": "lists_account_owner_id_account_owners_id_fk", + "tableFrom": "lists", + "tableTo": "account_owners", + "columnsFrom": [ + "account_owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.markers": { + "name": "markers", + "schema": "", + "columns": { + "account_owner_id": { + "name": "account_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "marker_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_read_id": { + "name": "last_read_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "markers_account_owner_id_account_owners_id_fk": { + "name": "markers_account_owner_id_account_owners_id_fk", + "tableFrom": "markers", + "tableTo": "account_owners", + "columnsFrom": [ + "account_owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "markers_account_owner_id_type_pk": { + "name": "markers_account_owner_id_type_pk", + "columns": [ + "account_owner_id", + "type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumbnail_type": { + "name": "thumbnail_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thumbnail_url": { + "name": "thumbnail_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thumbnail_width": { + "name": "thumbnail_width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "thumbnail_height": { + "name": "thumbnail_height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "media_post_id_index": { + "name": "media_post_id_index", + "columns": [ + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_post_id_posts_id_fk": { + "name": "media_post_id_posts_id_fk", + "tableFrom": "media", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mentions": { + "name": "mentions", + "schema": "", + "columns": { + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mentions_post_id_account_id_index": { + "name": "mentions_post_id_account_id_index", + "columns": [ + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mentions_post_id_posts_id_fk": { + "name": "mentions_post_id_posts_id_fk", + "tableFrom": "mentions", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mentions_account_id_accounts_id_fk": { + "name": "mentions_account_id_accounts_id_fk", + "tableFrom": "mentions", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mentions_post_id_account_id_pk": { + "name": "mentions_post_id_account_id_pk", + "columns": [ + "post_id", + "account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mutes": { + "name": "mutes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "muted_account_id": { + "name": "muted_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "notifications": { + "name": "notifications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "duration": { + "name": "duration", + "type": "interval", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "mutes_account_id_accounts_id_fk": { + "name": "mutes_account_id_accounts_id_fk", + "tableFrom": "mutes", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mutes_muted_account_id_accounts_id_fk": { + "name": "mutes_muted_account_id_accounts_id_fk", + "tableFrom": "mutes", + "tableTo": "accounts", + "columnsFrom": [ + "muted_account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mutes_account_id_muted_account_id_unique": { + "name": "mutes_account_id_muted_account_id_unique", + "nullsNotDistinct": false, + "columns": [ + "account_id", + "muted_account_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pinned_posts": { + "name": "pinned_posts", + "schema": "", + "columns": { + "index": { + "name": "index", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "pinned_posts_account_id_post_id_index": { + "name": "pinned_posts_account_id_post_id_index", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pinned_posts_account_id_accounts_id_fk": { + "name": "pinned_posts_account_id_accounts_id_fk", + "tableFrom": "pinned_posts", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pinned_posts_post_id_account_id_posts_id_actor_id_fk": { + "name": "pinned_posts_post_id_account_id_posts_id_actor_id_fk", + "tableFrom": "pinned_posts", + "tableTo": "posts", + "columnsFrom": [ + "post_id", + "account_id" + ], + "columnsTo": [ + "id", + "actor_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "pinned_posts_post_id_account_id_unique": { + "name": "pinned_posts_post_id_account_id_unique", + "nullsNotDistinct": false, + "columns": [ + "post_id", + "account_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_options": { + "name": "poll_options", + "schema": "", + "columns": { + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "votes_count": { + "name": "votes_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "poll_options_poll_id_index_index": { + "name": "poll_options_poll_id_index_index", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "poll_options_poll_id_polls_id_fk": { + "name": "poll_options_poll_id_polls_id_fk", + "tableFrom": "poll_options", + "tableTo": "polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "poll_options_poll_id_index_pk": { + "name": "poll_options_poll_id_index_pk", + "columns": [ + "poll_id", + "index" + ] + } + }, + "uniqueConstraints": { + "poll_options_poll_id_title_unique": { + "name": "poll_options_poll_id_title_unique", + "nullsNotDistinct": false, + "columns": [ + "poll_id", + "title" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.poll_votes": { + "name": "poll_votes", + "schema": "", + "columns": { + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "option_index": { + "name": "option_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "poll_votes_poll_id_account_id_index": { + "name": "poll_votes_poll_id_account_id_index", + "columns": [ + { + "expression": "poll_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "poll_votes_poll_id_polls_id_fk": { + "name": "poll_votes_poll_id_polls_id_fk", + "tableFrom": "poll_votes", + "tableTo": "polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "poll_votes_account_id_accounts_id_fk": { + "name": "poll_votes_account_id_accounts_id_fk", + "tableFrom": "poll_votes", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "poll_votes_poll_id_option_index_poll_options_poll_id_index_fk": { + "name": "poll_votes_poll_id_option_index_poll_options_poll_id_index_fk", + "tableFrom": "poll_votes", + "tableTo": "poll_options", + "columnsFrom": [ + "poll_id", + "option_index" + ], + "columnsTo": [ + "poll_id", + "index" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "poll_votes_poll_id_option_index_account_id_pk": { + "name": "poll_votes_poll_id_option_index_account_id_pk", + "columns": [ + "poll_id", + "option_index", + "account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.polls": { + "name": "polls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "multiple": { + "name": "multiple", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "voters_count": { + "name": "voters_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.posts": { + "name": "posts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "iri": { + "name": "iri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "post_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reply_target_id": { + "name": "reply_target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "sharing_id": { + "name": "sharing_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "quote_target_id": { + "name": "quote_target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "post_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "poll_id": { + "name": "poll_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "emojis": { + "name": "emojis", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_card": { + "name": "preview_card", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "replies_count": { + "name": "replies_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "shares_count": { + "name": "shares_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "likes_count": { + "name": "likes_count", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "idempotence_key": { + "name": "idempotence_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "posts_sharing_id_index": { + "name": "posts_sharing_id_index", + "columns": [ + { + "expression": "sharing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_actor_id_index": { + "name": "posts_actor_id_index", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_actor_id_sharing_id_index": { + "name": "posts_actor_id_sharing_id_index", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_reply_target_id_index": { + "name": "posts_reply_target_id_index", + "columns": [ + { + "expression": "reply_target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_visibility_actor_id_index": { + "name": "posts_visibility_actor_id_index", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_visibility_actor_id_sharing_id_index": { + "name": "posts_visibility_actor_id_sharing_id_index", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"posts\".\"sharing_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "posts_visibility_actor_id_reply_target_id_index": { + "name": "posts_visibility_actor_id_reply_target_id_index", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reply_target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"posts\".\"reply_target_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "posts_actor_id_accounts_id_fk": { + "name": "posts_actor_id_accounts_id_fk", + "tableFrom": "posts", + "tableTo": "accounts", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "posts_application_id_applications_id_fk": { + "name": "posts_application_id_applications_id_fk", + "tableFrom": "posts", + "tableTo": "applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "posts_reply_target_id_posts_id_fk": { + "name": "posts_reply_target_id_posts_id_fk", + "tableFrom": "posts", + "tableTo": "posts", + "columnsFrom": [ + "reply_target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "posts_sharing_id_posts_id_fk": { + "name": "posts_sharing_id_posts_id_fk", + "tableFrom": "posts", + "tableTo": "posts", + "columnsFrom": [ + "sharing_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "posts_quote_target_id_posts_id_fk": { + "name": "posts_quote_target_id_posts_id_fk", + "tableFrom": "posts", + "tableTo": "posts", + "columnsFrom": [ + "quote_target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "posts_poll_id_polls_id_fk": { + "name": "posts_poll_id_polls_id_fk", + "tableFrom": "posts", + "tableTo": "polls", + "columnsFrom": [ + "poll_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "posts_iri_unique": { + "name": "posts_iri_unique", + "nullsNotDistinct": false, + "columns": [ + "iri" + ] + }, + "posts_id_actor_id_unique": { + "name": "posts_id_actor_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id", + "actor_id" + ] + }, + "posts_poll_id_unique": { + "name": "posts_poll_id_unique", + "nullsNotDistinct": false, + "columns": [ + "poll_id" + ] + }, + "posts_actor_id_sharing_id_unique": { + "name": "posts_actor_id_sharing_id_unique", + "nullsNotDistinct": false, + "columns": [ + "actor_id", + "sharing_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "custom_emoji": { + "name": "custom_emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emoji_iri": { + "name": "emoji_iri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "reactions_post_id_index": { + "name": "reactions_post_id_index", + "columns": [ + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reactions_post_id_account_id_index": { + "name": "reactions_post_id_account_id_index", + "columns": [ + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reactions_post_id_posts_id_fk": { + "name": "reactions_post_id_posts_id_fk", + "tableFrom": "reactions", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reactions_account_id_accounts_id_fk": { + "name": "reactions_account_id_accounts_id_fk", + "tableFrom": "reactions", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "reactions_post_id_account_id_emoji_pk": { + "name": "reactions_post_id_account_id_emoji_pk", + "columns": [ + "post_id", + "account_id", + "emoji" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.relays": { + "name": "relays", + "schema": "", + "columns": { + "relay_server_actor_id": { + "name": "relay_server_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "relay_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "follow_request_id": { + "name": "follow_request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inbox_url": { + "name": "inbox_url", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "relay_client_actor_id": { + "name": "relay_client_actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "relays_relay_server_actor_id_accounts_id_fk": { + "name": "relays_relay_server_actor_id_accounts_id_fk", + "tableFrom": "relays", + "tableTo": "accounts", + "columnsFrom": [ + "relay_server_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "relays_relay_client_actor_id_accounts_id_fk": { + "name": "relays_relay_client_actor_id_accounts_id_fk", + "tableFrom": "relays", + "tableTo": "accounts", + "columnsFrom": [ + "relay_client_actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "relays_follow_request_id_unique": { + "name": "relays_follow_request_id_unique", + "nullsNotDistinct": false, + "columns": [ + "follow_request_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reports": { + "name": "reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "iri": { + "name": "iri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_account_id": { + "name": "target_account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "posts": { + "name": "posts", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::uuid[]" + } + }, + "indexes": {}, + "foreignKeys": { + "reports_account_id_accounts_id_fk": { + "name": "reports_account_id_accounts_id_fk", + "tableFrom": "reports", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reports_target_account_id_accounts_id_fk": { + "name": "reports_target_account_id_accounts_id_fk", + "tableFrom": "reports", + "tableTo": "accounts", + "columnsFrom": [ + "target_account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reports_iri_unique": { + "name": "reports_iri_unique", + "nullsNotDistinct": false, + "columns": [ + "iri" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.timeline_posts": { + "name": "timeline_posts", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "post_id": { + "name": "post_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "timeline_posts_account_id_post_id_index": { + "name": "timeline_posts_account_id_post_id_index", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "post_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "timeline_posts_account_id_account_owners_id_fk": { + "name": "timeline_posts_account_id_account_owners_id_fk", + "tableFrom": "timeline_posts", + "tableTo": "account_owners", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "timeline_posts_post_id_posts_id_fk": { + "name": "timeline_posts_post_id_posts_id_fk", + "tableFrom": "timeline_posts", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "timeline_posts_account_id_post_id_pk": { + "name": "timeline_posts_account_id_post_id_pk", + "columns": [ + "account_id", + "post_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.totps": { + "name": "totps", + "schema": "", + "columns": { + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "digits": { + "name": "digits", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "period": { + "name": "period", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.account_type": { + "name": "account_type", + "schema": "public", + "values": [ + "Application", + "Group", + "Organization", + "Person", + "Service" + ] + }, + "public.grant_type": { + "name": "grant_type", + "schema": "public", + "values": [ + "authorization_code", + "client_credentials" + ] + }, + "public.list_replies_policy": { + "name": "list_replies_policy", + "schema": "public", + "values": [ + "followed", + "list", + "none" + ] + }, + "public.marker_type": { + "name": "marker_type", + "schema": "public", + "values": [ + "notifications", + "home" + ] + }, + "public.post_type": { + "name": "post_type", + "schema": "public", + "values": [ + "Article", + "Note", + "Question" + ] + }, + "public.post_visibility": { + "name": "post_visibility", + "schema": "public", + "values": [ + "public", + "unlisted", + "private", + "direct" + ] + }, + "public.relay_state": { + "name": "relay_state", + "schema": "public", + "values": [ + "idle", + "pending", + "accepted", + "rejected" + ] + }, + "public.scope": { + "name": "scope", + "schema": "public", + "values": [ + "read", + "read:accounts", + "read:blocks", + "read:bookmarks", + "read:favourites", + "read:filters", + "read:follows", + "read:lists", + "read:mutes", + "read:notifications", + "read:search", + "read:statuses", + "write", + "write:accounts", + "write:blocks", + "write:bookmarks", + "write:conversations", + "write:favourites", + "write:filters", + "write:follows", + "write:lists", + "write:media", + "write:mutes", + "write:notifications", + "write:reports", + "write:statuses", + "follow", + "push" + ] + }, + "public.theme_color": { + "name": "theme_color", + "schema": "public", + "values": [ + "amber", + "azure", + "blue", + "cyan", + "fuchsia", + "green", + "grey", + "indigo", + "jade", + "lime", + "orange", + "pink", + "pumpkin", + "purple", + "red", + "sand", + "slate", + "violet", + "yellow", + "zinc" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e4e71531..409658fc 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -449,6 +449,13 @@ "when": 1740044940940, "tag": "0063_theme_color", "breakpoints": true + }, + { + "idx": 64, + "version": "7", + "when": 1741109746824, + "tag": "0064_relay", + "breakpoints": true } ] } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df9f4b15..7594898d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,7 +134,7 @@ importers: version: 1.9.4 '@dotenvx/dotenvx': specifier: ^1.37.0 - version: 1.41.0 + version: 1.37.0 '@reporters/github': specifier: ^1.7.2 version: 1.7.2 @@ -146,7 +146,7 @@ importers: version: 14.1.2 '@types/node': specifier: ^22.13.4 - version: 22.15.3 + version: 22.13.4 '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 @@ -520,15 +520,15 @@ packages: resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==} engines: {node: '>=14.0'} - '@dotenvx/dotenvx@1.41.0': - resolution: {integrity: sha512-lFZOSKLM2/Jm7FXYUIvnciUhMsuEatyxCgau4lnjDD59LaSYiaNLjyjnUL/aYpH1+iaDhD37+mPOzH9kBZlUJQ==} + '@dotenvx/dotenvx@1.37.0': + resolution: {integrity: sha512-AMPts6UnBQoJ+ash2M5MLSVETa0MZCY89p5msW+ojq7sbgM7R+H9Kmd8Gj1yTpcBoKbiOElWq5OLSHwG5nyCNg==} hasBin: true '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@ecies/ciphers@0.2.3': - resolution: {integrity: sha512-tapn6XhOueMwht3E2UzY0ZZjYokdaw9XtL9kEyjhQ/Fb9vL9xTFbOaI+fV0AWvTpYu4BNloC6getKW6NtSg4mA==} + '@ecies/ciphers@0.2.2': + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} peerDependencies: '@noble/ciphers': ^1.0.0 @@ -1131,20 +1131,20 @@ packages: '@multiformats/base-x@4.0.1': resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.0': - resolution: {integrity: sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==} + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} '@noble/hashes@1.6.1': resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} '@opentelemetry/api-logs@0.53.0': @@ -1664,8 +1664,8 @@ packages: '@types/node@20.12.14': resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} - '@types/node@22.15.3': - resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} + '@types/node@22.13.4': + resolution: {integrity: sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==} '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -1854,8 +1854,8 @@ packages: domutils@3.2.1: resolution: {integrity: sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==} - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} drizzle-kit@0.30.1: @@ -1954,8 +1954,8 @@ packages: sqlite3: optional: true - eciesjs@0.4.14: - resolution: {integrity: sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==} + eciesjs@0.4.13: + resolution: {integrity: sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} emoji-regex@8.0.0: @@ -2011,8 +2011,8 @@ packages: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2486,8 +2486,8 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -3607,13 +3607,13 @@ snapshots: transitivePeerDependencies: - web-streams-polyfill - '@dotenvx/dotenvx@1.41.0': + '@dotenvx/dotenvx@1.37.0': dependencies: commander: 11.1.0 - dotenv: 16.5.0 - eciesjs: 0.4.14 + dotenv: 16.4.7 + eciesjs: 0.4.13 execa: 5.1.1 - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.3(picomatch@4.0.2) ignore: 5.3.2 object-treeify: 1.1.33 picomatch: 4.0.2 @@ -3621,9 +3621,9 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} - '@ecies/ciphers@0.2.3(@noble/ciphers@1.3.0)': + '@ecies/ciphers@0.2.2(@noble/ciphers@1.2.1)': dependencies: - '@noble/ciphers': 1.3.0 + '@noble/ciphers': 1.2.1 '@emnapi/runtime@1.3.1': dependencies: @@ -4023,15 +4023,15 @@ snapshots: '@multiformats/base-x@4.0.1': {} - '@noble/ciphers@1.3.0': {} + '@noble/ciphers@1.2.1': {} - '@noble/curves@1.9.0': + '@noble/curves@1.8.1': dependencies: - '@noble/hashes': 1.8.0 + '@noble/hashes': 1.7.1 '@noble/hashes@1.6.1': {} - '@noble/hashes@1.8.0': {} + '@noble/hashes@1.7.1': {} '@opentelemetry/api-logs@0.53.0': dependencies: @@ -4839,11 +4839,11 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 '@types/fluent-ffmpeg@2.1.27': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 '@types/linkify-it@5.0.0': {} @@ -4856,16 +4856,16 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 '@types/node@20.12.14': dependencies: undici-types: 5.26.5 optional: true - '@types/node@22.15.3': + '@types/node@22.13.4': dependencies: - undici-types: 6.21.0 + undici-types: 6.20.0 '@types/pg-pool@2.0.6': dependencies: @@ -4873,7 +4873,7 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 pg-protocol: 1.7.0 pg-types: 2.2.0 @@ -4881,7 +4881,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 '@types/semver@7.5.8': {} @@ -4889,11 +4889,11 @@ snapshots: '@types/tedious@4.0.14': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 '@types/ws@8.5.14': dependencies: - '@types/node': 22.15.3 + '@types/node': 22.13.4 optional: true abort-controller@3.0.0: @@ -5055,7 +5055,7 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 - dotenv@16.5.0: {} + dotenv@16.4.7: {} drizzle-kit@0.30.1: dependencies: @@ -5073,11 +5073,11 @@ snapshots: bun-types: 1.1.37 postgres: 3.4.5 - eciesjs@0.4.14: + eciesjs@0.4.13: dependencies: - '@ecies/ciphers': 0.2.3(@noble/ciphers@1.3.0) - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.0 + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.2.1) + '@noble/ciphers': 1.2.1 + '@noble/curves': 1.8.1 '@noble/hashes': 1.6.1 emoji-regex@8.0.0: {} @@ -5198,7 +5198,7 @@ snapshots: dependencies: strnum: 1.0.5 - fdir@6.4.4(picomatch@4.0.2): + fdir@6.4.3(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -5464,7 +5464,7 @@ snapshots: pkijs@3.2.4: dependencies: - '@noble/hashes': 1.6.1 + '@noble/hashes': 1.7.1 asn1js: 3.0.5 bytestreamjs: 2.0.1 pvtsutils: 1.3.6 @@ -5634,7 +5634,7 @@ snapshots: undici-types@5.26.5: optional: true - undici-types@6.21.0: {} + undici-types@6.20.0: {} undici@5.28.4: dependencies: diff --git a/src/api/v1/accounts.ts b/src/api/v1/accounts.ts index dd592f0c..08c06d2a 100644 --- a/src/api/v1/accounts.ts +++ b/src/api/v1/accounts.ts @@ -12,6 +12,7 @@ import { isNotNull, isNull, lt, + not, notInArray, or, sql, @@ -26,6 +27,7 @@ import { serializeRelationship, } from "../../entities/account"; import { serializeList } from "../../entities/list"; +import { HOLLO_RELAY_ACTOR_ID } from "../../entities/relay"; import { getPostRelations, serializePost } from "../../entities/status"; import { federation } from "../../federation"; import { @@ -416,9 +418,12 @@ app.get( } } const accountList = await db.query.accounts.findMany({ - where: or( - ilike(accounts.handle, `%${query.q}%`), - ilike(accounts.name, `%${query.q}%`), + where: and( + or( + ilike(accounts.handle, `%${query.q}%`), + ilike(accounts.name, `%${query.q}%`), + ), + not(eq(accounts.id, HOLLO_RELAY_ACTOR_ID)), ), with: { owner: true, successor: true }, orderBy: [ diff --git a/src/api/v1/statuses.ts b/src/api/v1/statuses.ts index 689bd7f1..190bc399 100644 --- a/src/api/v1/statuses.ts +++ b/src/api/v1/statuses.ts @@ -28,6 +28,7 @@ import { serializeAccount, serializeAccountOwner, } from "../../entities/account"; +import { forwardActivityToRelays } from "../../entities/relay"; import { getPostRelations, serializePost } from "../../entities/status"; import federation from "../../federation"; import { updateAccountStats } from "../../federation/account"; @@ -67,6 +68,7 @@ import { polls, posts, reactions, + relays, } from "../../schema"; import { formatPostContent } from "../../text"; import { type Uuid, isUuid, uuid, uuidv7 } from "../../uuid"; @@ -281,6 +283,9 @@ app.post( excludeBaseUris: [new URL(c.req.url)], }); } + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, { handle }, activity); + } return c.json(serializePost(post, owner, c.req.url)); }, ); @@ -371,6 +376,9 @@ app.put( preferSharedInbox: true, excludeBaseUris: [new URL(c.req.url)], }); + if (owner.discoverable && post!.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json(serializePost(post!, owner, c.req.url)); }, ); @@ -434,6 +442,9 @@ app.delete( }, ); } + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json({ ...serializePost(post, owner, c.req.url), text: post.content ?? "", @@ -797,15 +808,19 @@ app.post( where: eq(posts.id, id), with: getPostRelations(owner.id), }); + const activity = toAnnounce(post!, fedCtx); await fedCtx.sendActivity( { username: owner.handle }, "followers", - toAnnounce(post!, fedCtx), + activity, { preferSharedInbox: true, excludeBaseUris: [new URL(c.req.url)], }, ); + if (owner.discoverable && post!.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json(serializePost(post!, owner, c.req.url)); }, ); @@ -852,18 +867,22 @@ app.post( .where(eq(posts.id, originalPostId)); const fedCtx = federation.createContext(c.req.raw, undefined); for (const post of postList) { + const activity = new Undo({ + actor: new URL(owner.account.iri), + object: toAnnounce(post, fedCtx), + }); await fedCtx.sendActivity( { username: owner.handle }, "followers", - new Undo({ - actor: new URL(owner.account.iri), - object: toAnnounce(post, fedCtx), - }), + activity, { preferSharedInbox: true, excludeBaseUris: [new URL(c.req.url)], }, ); + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } } const originalPost = await db.query.posts.findFirst({ where: eq(posts.id, originalPostId), @@ -1014,23 +1033,22 @@ app.post( } satisfies NewPinnedPost) .returning(); const fedCtx = federation.createContext(c.req.raw, undefined); - await fedCtx.sendActivity( - owner, - "followers", - new Add({ - id: new URL( - `#add/${result[0].index}`, - fedCtx.getFeaturedUri(owner.handle), - ), - actor: new URL(owner.account.iri), - object: new URL(post.iri), - target: fedCtx.getFeaturedUri(owner.handle), - }), - { - preferSharedInbox: true, - excludeBaseUris: [new URL(c.req.url)], - }, - ); + const activity = new Add({ + id: new URL( + `#add/${result[0].index}`, + fedCtx.getFeaturedUri(owner.handle), + ), + actor: new URL(owner.account.iri), + object: new URL(post.iri), + target: fedCtx.getFeaturedUri(owner.handle), + }); + await fedCtx.sendActivity(owner, "followers", activity, { + preferSharedInbox: true, + excludeBaseUris: [new URL(c.req.url)], + }); + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } const resultPost = await db.query.posts.findFirst({ where: eq(posts.id, postId), with: getPostRelations(owner.id), @@ -1070,23 +1088,22 @@ app.post( with: getPostRelations(owner.id), }); const fedCtx = federation.createContext(c.req.raw, undefined); - await fedCtx.sendActivity( - owner, - "followers", - new Remove({ - id: new URL( - `#remove/${result[0].index}`, - fedCtx.getFeaturedUri(owner.handle), - ), - actor: new URL(owner.account.iri), - object: new URL(post!.iri), - target: fedCtx.getFeaturedUri(owner.handle), - }), - { - preferSharedInbox: true, - excludeBaseUris: [new URL(c.req.url)], - }, - ); + const activity = new Remove({ + id: new URL( + `#remove/${result[0].index}`, + fedCtx.getFeaturedUri(owner.handle), + ), + actor: new URL(owner.account.iri), + object: new URL(post!.iri), + target: fedCtx.getFeaturedUri(owner.handle), + }); + await fedCtx.sendActivity(owner, "followers", activity, { + preferSharedInbox: true, + excludeBaseUris: [new URL(c.req.url)], + }); + if (owner.discoverable && post!.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json(serializePost(post!, owner, c.req.url)); }, ); @@ -1208,6 +1225,9 @@ async function addEmojiReaction( activity, { preferSharedInbox: true, excludeBaseUris: [new URL(c.req.url)] }, ); + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json(serializePost(post, owner, c.req.url)); } @@ -1301,6 +1321,9 @@ async function removeEmojiReaction( activity, { preferSharedInbox: true, excludeBaseUris: [new URL(c.req.url)] }, ); + if (owner.discoverable && post.visibility === "public") { + await forwardActivityToRelays(db, fedCtx, owner, activity); + } return c.json(serializePost(post, owner, c.req.url)); } diff --git a/src/entities/relay.ts b/src/entities/relay.ts new file mode 100644 index 00000000..ef37a071 --- /dev/null +++ b/src/entities/relay.ts @@ -0,0 +1,142 @@ +import { + type Activity, + type Context, + type DocumentLoader, + Follow, + type Recipient, + type SenderKeyPair, + Undo, +} from "@fedify/fedify"; +import { + type ExtractTablesWithRelations, + and, + eq, + isNotNull, +} from "drizzle-orm"; +import type { PgDatabase } from "drizzle-orm/pg-core"; +import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"; +import * as schema from "../schema"; + +// Use fixed username and id for the relay client actor +// Why use fixed username and id? +// Because the relay client actor is a special actor that is not supposed to be interacted with by users. +// Also, if we delete the a relay and send the Undo activity, the relay client needs to be online. +// For example, AodeRelay first fetches the actor from our server again, and if we already deleted the actor, it will fail +// and the realy keeps our server in the database. +// And because we won't receive an activity to check whether the Undo was successful, we can't delete the relay client actor safely. +export const HOLLO_RELAY_ACTOR_ID = "8a683714-6fa2-4e53-9f05-b4acbcda4db7"; +export const HOLLO_RELAY_ACTOR_USERNAME = "~hollo~relay~follower~"; + +/** + * Workaround fedify jsonld serialization for relay follow. + * + * Without this, the jsonld serialization would be: + * ```json + * { + * "@context": "https://www.w3.org/ns/activitystreams", + * "id": "https://client.example/6ae15297", + * "type": "Follow", + * "actor": "https://client.example/actor", + * "object": "as:public" + * } + * ``` + * instead of + * ```json + * { + * "@context": "https://www.w3.org/ns/activitystreams", + * "id": "https://client.example/6ae15297", + * "type": "Follow", + * "actor": "https://client.example/actor", + * "object": "https://www.w3.org/ns/activitystreams#Public" + * } + * ``` + */ +export class RelayFollow extends Follow { + async toJsonLd(options?: { + format?: "compact" | "expand"; + contextLoader?: DocumentLoader; + context?: + | string + | Record + | (string | Record)[]; + }): Promise { + const json = (await super.toJsonLd(options)) as { object: string }; + json.object = "https://www.w3.org/ns/activitystreams#Public"; + return json; + } +} + +/** + * Similar workaround as RelayFollow. However, normaly we should not need to do this, because the spec is very clear that we are allowed to set object to just the id. + * + * But AodeRelay does not support this, so we need to work around this and send the full Follow object. + */ +export class RelayUndo extends Undo { + async toJsonLd(options?: { + format?: "compact" | "expand"; + contextLoader?: DocumentLoader; + context?: + | string + | Record + | (string | Record)[]; + }): Promise { + await this.getObject(); + const json = (await super.toJsonLd(options)) as { object: unknown }; + json.object = await (await this.getObject())?.toJsonLd(); + return json; + } +} + +export async function getRelayRecipients( + db: PgDatabase< + PostgresJsQueryResultHKT, + typeof schema, + ExtractTablesWithRelations + >, +): Promise { + const acceptedRelays = await db.query.relays.findMany({ + where: and( + eq(schema.relays.state, "accepted"), + isNotNull(schema.relays.relayServerActorId), + ), + with: { + relayServerActor: true, + }, + }); + return acceptedRelays.map((relay) => ({ + id: new URL(relay.relayServerActor!.iri), + inboxId: new URL(relay.relayServerActor!.inboxUrl), + })); +} + +/** + * Forward a given activity to all relays currently active + * @param db Database instance or transaction + * @param ctx fedify context + * @param sender Sender of the activity + * @param activity Activity to forward to relays + */ +export async function forwardActivityToRelays( + db: PgDatabase< + PostgresJsQueryResultHKT, + typeof schema, + ExtractTablesWithRelations + >, + ctx: Context, + sender: + | SenderKeyPair + | SenderKeyPair[] + | { + identifier: string; + } + | { + username: string; + } + | { + handle: string; + }, + activity: Activity, +) { + const recipients = await getRelayRecipients(db); + await ctx.sendActivity(sender, recipients, activity); +} diff --git a/src/federation/inbox.ts b/src/federation/inbox.ts index 429f37c7..6aaacdb2 100644 --- a/src/federation/inbox.ts +++ b/src/federation/inbox.ts @@ -38,6 +38,7 @@ import { pollOptions, posts, reactions, + relays, } from "../schema"; import { isUuid } from "../uuid"; import { @@ -173,6 +174,7 @@ export async function onFollowAccepted( inboxLogger.debug("Invalid actor: {actor}", { actor }); return; } + const account = await persistAccount(db, actor, ctx.origin, ctx); if (account == null) return; if (accept.objectId != null) { @@ -222,6 +224,7 @@ export async function onFollowRejected( inboxLogger.debug("Invalid actor: {actor}", { actor }); return; } + const account = await persistAccount(db, actor, ctx.origin, ctx); if (account == null) return; if (reject.objectId != null) { @@ -841,3 +844,56 @@ export async function onAccountMoved( ); } } + +export async function onRelayFollowAccepted( + ctx: InboxContext, + accept: Accept, +): Promise { + const actor = await accept.getActor(); + if (!isActor(actor) || actor.id == null) { + inboxLogger.debug("Invalid actor: {actor}", { actor }); + return; + } + + const relayServerActor = await persistAccount(db, actor, ctx.origin, ctx); + + inboxLogger.debug("Relay follow accepted: {actor} {relayServerActor}", { + actor, + relayServerActor, + }); + + if (!relayServerActor) { + inboxLogger.debug("Actor not persited: {actor}", { actor }); + return; + } + + // Update relay state to accepted + await db + .update(relays) + .set({ state: "accepted", relayServerActorId: relayServerActor.id }) + .where(eq(relays.followRequestId, accept.objectId!.href)); +} + +export async function onRelayFollowRejected( + ctx: InboxContext, + reject: Reject, +): Promise { + const actor = await reject.getActor(); + if (!isActor(actor) || actor.id == null) { + inboxLogger.debug("Invalid actor: {actor}", { actor }); + return; + } + + const relayServerActor = await persistAccount(db, actor, ctx.origin, ctx); + + if (!relayServerActor) { + inboxLogger.debug("Actor not persited: {actor}", { actor }); + return; + } + + // Update relay state to rejected + await db + .update(relays) + .set({ state: "rejected", relayServerActorId: relayServerActor.id }) + .where(eq(relays.followRequestId, reject.objectId!.href)); +} diff --git a/src/federation/index.ts b/src/federation/index.ts index 766ce646..ed707cd1 100644 --- a/src/federation/index.ts +++ b/src/federation/index.ts @@ -40,6 +40,8 @@ import { onPostUnpinned, onPostUnshared, onPostUpdated, + onRelayFollowAccepted, + onRelayFollowRejected, onUnblocked, onUnfollowed, onUnliked, @@ -58,8 +60,24 @@ federation return anyOwner ?? null; }) .on(Follow, onFollowed) - .on(Accept, onFollowAccepted) - .on(Reject, onFollowRejected) + .on(Accept, async (ctx, accept) => { + const isRelayAccept = accept.objectId?.hash.startsWith("#relay-follows/"); + + if (isRelayAccept) { + return await onRelayFollowAccepted(ctx, accept); + } + + await onFollowAccepted(ctx, accept); + }) + .on(Reject, async (ctx, reject) => { + const isRelayReject = reject.objectId?.hash.startsWith("#relay-follows/"); + + if (isRelayReject) { + return await onRelayFollowRejected(ctx, reject); + } + + await onFollowRejected(ctx, reject); + }) .on(Create, async (ctx, create) => { const object = await create.getObject(); if ( diff --git a/src/oauth.tsx b/src/oauth.tsx index 1bc6e16a..81cfd83b 100644 --- a/src/oauth.tsx +++ b/src/oauth.tsx @@ -1,11 +1,12 @@ import { zValidator } from "@hono/zod-validator"; -import { eq } from "drizzle-orm"; +import { eq, not } from "drizzle-orm"; import { escape } from "es-toolkit"; import { type Context, Hono } from "hono"; import { cors } from "hono/cors"; import { z } from "zod"; import { Layout } from "./components/Layout"; import { db } from "./db"; +import { HOLLO_RELAY_ACTOR_ID } from "./entities/relay"; import { loginRequired } from "./login"; import { createAccessToken, @@ -20,6 +21,7 @@ import { type Application, type Scope, accessTokens, + accountOwners as accountOwnersTable, applications, scopeEnum, } from "./schema"; @@ -55,6 +57,7 @@ app.get( return c.json({ error: "invalid_redirect_uri" }, 400); } const accountOwners = await db.query.accountOwners.findMany({ + where: not(eq(accountOwnersTable.id, HOLLO_RELAY_ACTOR_ID)), with: { account: true }, }); return c.html( diff --git a/src/pages/accounts.tsx b/src/pages/accounts.tsx index 8a338453..9d0724bf 100644 --- a/src/pages/accounts.tsx +++ b/src/pages/accounts.tsx @@ -14,7 +14,7 @@ import { import { getLogger } from "@logtape/logtape"; import { PromisePool } from "@supercharge/promise-pool"; import { createObjectCsvStringifier } from "csv-writer-portable"; -import { and, count, eq, inArray } from "drizzle-orm"; +import { and, count, eq, inArray, ne } from "drizzle-orm"; import { uniq } from "es-toolkit"; import { Hono } from "hono"; import { streamText } from "hono/streaming"; @@ -27,6 +27,7 @@ import { type NewAccountPageProps, } from "../components/NewAccountPage.tsx"; import db from "../db.ts"; +import { HOLLO_RELAY_ACTOR_USERNAME } from "../entities/relay.ts"; import federation from "../federation"; import { REMOTE_ACTOR_FETCH_POSTS, @@ -67,6 +68,7 @@ accounts.use(loginRequired); accounts.get("/", async (c) => { const owners = await db.query.accountOwners.findMany({ + where: ne(accountOwners.handle, HOLLO_RELAY_ACTOR_USERNAME), with: { account: true }, }); return c.html(); @@ -115,10 +117,32 @@ accounts.post("/", async (c) => { 400, ); } + if (username === HOLLO_RELAY_ACTOR_USERNAME) { + return c.html( + , + 400, + ); + } const fedCtx = federation.createContext(c.req.raw, undefined); const bioResult = await formatText(db, bio ?? "", fedCtx); const nameEmojis = await extractCustomEmojis(db, name); const emojis = { ...nameEmojis, ...bioResult.emojis }; + const handle = `@${username}@${fedCtx.host}`; const [account, owner] = await db.transaction(async (tx) => { await tx .insert(instances) @@ -137,7 +161,7 @@ accounts.post("/", async (c) => { type: "Person", name, emojis, - handle: `@${username}@${fedCtx.host}`, + handle: handle, bioHtml: bioResult.html, url: fedCtx.getActorUri(username).href, protected: protected_, diff --git a/src/pages/federation.tsx b/src/pages/federation.tsx index 065fe677..c54f03ce 100644 --- a/src/pages/federation.tsx +++ b/src/pages/federation.tsx @@ -1,12 +1,22 @@ -import { isActor } from "@fedify/fedify"; -import { count, sql } from "drizzle-orm"; +import { exportJwk, generateCryptoKeyPair, isActor } from "@fedify/fedify"; +import { Temporal } from "@js-temporal/polyfill"; +import { count, eq, sql } from "drizzle-orm"; +import { interval, jsonb, pgTable, timestamp, uuid } from "drizzle-orm/pg-core"; import { Hono } from "hono"; +import { HTTPException } from "hono/http-exception"; import { DashboardLayout } from "../components/DashboardLayout"; import db from "../db"; +import { + HOLLO_RELAY_ACTOR_ID, + HOLLO_RELAY_ACTOR_USERNAME, + RelayFollow, + RelayUndo, +} from "../entities/relay"; import federation from "../federation"; import { persistAccount } from "../federation/account"; import { isPost, persistPost } from "../federation/post"; import { loginRequired } from "../login"; +import { accountOwners, accounts, instances, relays } from "../schema"; const data = new Hono(); @@ -30,6 +40,12 @@ data.get("/", async (c) => { queueMessages = []; } + const relays = await db.query.relays.findMany({ + with: { + relayServerActor: true, + }, + }); + return c.html(
@@ -87,6 +103,73 @@ data.get("/", async (c) => { +
+
+
+

Relays

+

Manage relays.

+
+
+
+
+ + +
+ {error === "invalid_relay" ? ( + The given relay URL is invalid. Please try again. + ) : error === "relay_alread_exists" ? ( + The given relay URL already exists. Please try again. + ) : ( + + A relay conforming to{" "} + + FEP-ae0c + {" "} + is supported. + + )} +
+ + + + + + + + + + {relays.map((relay) => ( + + + + + + ))} + +
InboxStatusActions
{relay.relayServerActor?.inboxUrl ?? relay.inboxUrl}{relay.state} +
+ +
+
+
+
@@ -160,4 +243,202 @@ data.post("/refresh", async (c) => { return c.redirect("/federation?error=refresh"); }); +data.post("/relay", async (c) => { + const fedCtx = federation.createContext(c.req.raw, undefined); + const form = await c.req.formData(); + + const inboxUrl = form.get("inbox_url"); + + if (typeof inboxUrl !== "string") { + return c.redirect("/federation?error=invalid_relay"); + } + + const [loadedRelay] = await db.transaction(async (tx) => { + await tx + .insert(instances) + .values({ + host: fedCtx.host, + software: "hollo", + softwareVersion: null, + }) + .onConflictDoNothing(); + + const existingRelay = await tx.query.relays.findFirst({ + where: eq(relays.inboxUrl, inboxUrl), + }); + + if (existingRelay) { + throw tx.rollback(); + } + + // Create client actor for the relay if it doesn't exist + const account = await tx + .insert(accounts) + .values({ + id: HOLLO_RELAY_ACTOR_ID, + iri: fedCtx.getActorUri(HOLLO_RELAY_ACTOR_USERNAME).href, + instanceHost: fedCtx.host, + type: "Application", + name: HOLLO_RELAY_ACTOR_USERNAME, + handle: `@${HOLLO_RELAY_ACTOR_USERNAME}@${fedCtx.host}`, + url: fedCtx.getActorUri(HOLLO_RELAY_ACTOR_USERNAME).href, + protected: false, + inboxUrl: fedCtx.getInboxUri(HOLLO_RELAY_ACTOR_USERNAME).href, + followersUrl: fedCtx.getFollowersUri(HOLLO_RELAY_ACTOR_USERNAME).href, + sharedInboxUrl: fedCtx.getInboxUri().href, + featuredUrl: fedCtx.getFeaturedUri(HOLLO_RELAY_ACTOR_USERNAME).href, + published: new Date(), + }) + .onConflictDoUpdate({ + target: accounts.id, + set: { + // Set anything to still return a value if the account already exists + protected: false, + }, + }) + .returning(); + + const rsaKeyPair = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"); + const ed25519KeyPair = await generateCryptoKeyPair("Ed25519"); + + await tx + .insert(accountOwners) + .values({ + id: HOLLO_RELAY_ACTOR_ID, + handle: HOLLO_RELAY_ACTOR_USERNAME, + rsaPrivateKeyJwk: await exportJwk(rsaKeyPair.privateKey), + rsaPublicKeyJwk: await exportJwk(rsaKeyPair.publicKey), + ed25519PrivateKeyJwk: await exportJwk(ed25519KeyPair.privateKey), + ed25519PublicKeyJwk: await exportJwk(ed25519KeyPair.publicKey), + bio: "This account is an internal account used by Hollo. It is used to follow remote relays.", + language: "en", + // TODO: Which visibility should be set? + visibility: "public", + discoverable: false, + themeColor: "pink", + }) + .onConflictDoNothing() + .returning(); + + // Create follow request for this relay + const followRequestId = new URL( + `#relay-follows/${crypto.randomUUID()}`, + account[0].iri, + ); + + // Create relay + const [relay] = await tx + .insert(relays) + .values({ + state: "idle", + relayClientActorId: HOLLO_RELAY_ACTOR_ID, + followRequestId: followRequestId.href, + inboxUrl, + }) + .returning(); + + const loadedRelay = await tx.query.relays.findFirst({ + where: eq(relays.inboxUrl, relay.inboxUrl), + with: { + relayClientActor: { + with: { + owner: true, + }, + }, + relayServerActor: { + with: { + owner: true, + }, + }, + }, + }); + + if (!loadedRelay) { + throw tx.rollback(); + } + + return [loadedRelay]; + }); + + await fedCtx.sendActivity( + { username: loadedRelay.relayClientActor.owner.handle }, + [ + { + id: new URL(loadedRelay.inboxUrl), + inboxId: new URL(loadedRelay.inboxUrl), + }, + ], + new RelayFollow({ + id: new URL(loadedRelay.followRequestId), + actor: new URL(loadedRelay.relayClientActor.iri), + object: new URL("https://www.w3.org/ns/activitystreams#Public"), + }), + ); + + await db + .update(relays) + .set({ + state: "pending", + }) + .where(eq(relays.inboxUrl, loadedRelay.inboxUrl)); + + return c.redirect("/federation?done=relay:add"); +}); + +data.post("/relay/:followRequestId/delete", async (c) => { + const fedCtx = federation.createContext(c.req.raw, undefined); + + const followRequestId = decodeURIComponent(c.req.param("followRequestId")); + + await db.transaction(async (tx) => { + const relay = await tx.query.relays.findFirst({ + where: eq(relays.followRequestId, followRequestId), + with: { + relayServerActor: { + with: { + owner: true, + }, + }, + relayClientActor: { + with: { + owner: true, + }, + }, + }, + }); + + if (!relay) { + throw new HTTPException(404, { res: await c.notFound() }); + } + + // Delete all outgoing activities for this relay + await tx + .delete(pgTable("fedify_message_v2", {})) + .where(sql`"message"->>'inbox' = ${relay.inboxUrl}`); + + await fedCtx.sendActivity( + { username: relay.relayClientActor.owner.handle }, + [ + { + id: new URL(relay.relayServerActor?.iri ?? relay.inboxUrl), + inboxId: new URL(relay.relayServerActor?.inboxUrl ?? relay.inboxUrl), + }, + ], + new RelayUndo({ + actor: new URL(relay.relayClientActor.iri), + object: new RelayFollow({ + id: new URL(relay.followRequestId), + actor: new URL(relay.relayClientActor.iri), + object: new URL("https://www.w3.org/ns/activitystreams#Public"), + }), + published: Temporal.Now.instant(), + }), + ); + + await tx.delete(relays).where(eq(relays.followRequestId, followRequestId)); + }); + + return c.redirect("/federation?done=relay:removed"); +}); + export default data; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index bd3b8575..b5283cc6 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,7 +1,10 @@ +import { eq, not } from "drizzle-orm"; import { escape } from "es-toolkit"; import { Hono } from "hono"; import { Layout } from "../../components/Layout.tsx"; import db from "../../db.ts"; +import { HOLLO_RELAY_ACTOR_ID } from "../../entities/relay.ts"; +import { accountOwners } from "../../schema.ts"; import { renderCustomEmojis } from "../../text.ts"; const homePage = new Hono().basePath("/"); @@ -10,6 +13,7 @@ homePage.get("/", async (c) => { const credential = await db.query.credentials.findFirst(); if (credential == null) return c.redirect("/setup"); const owners = await db.query.accountOwners.findMany({ + where: not(eq(accountOwners.id, HOLLO_RELAY_ACTOR_ID)), with: { account: true }, }); if (owners.length < 1) return c.redirect("/accounts"); diff --git a/src/schema.ts b/src/schema.ts index 40a90661..23f171f5 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1090,3 +1090,37 @@ export const listPostRelations = relations(listPosts, ({ one }) => ({ references: [posts.id], }), })); + +export const relayStateEnum = pgEnum("relay_state", [ + "idle", + "pending", + "accepted", + "rejected", +]); + +export const relays = pgTable("relays", { + relayServerActorId: uuid("relay_server_actor_id") + .$type() + .references(() => accounts.id, { onDelete: "cascade" }), + state: relayStateEnum("state").notNull().default("idle"), + followRequestId: text("follow_request_id").notNull().unique(), + inboxUrl: text("inbox_url").notNull().primaryKey(), + relayClientActorId: uuid("relay_client_actor_id") + .$type() + .notNull() + .references(() => accounts.id, { onDelete: "cascade" }), +}); + +export type ListRelays = typeof relays.$inferSelect; +export type NewRelays = typeof relays.$inferInsert; + +export const relayRelations = relations(relays, ({ one }) => ({ + relayServerActor: one(accounts, { + fields: [relays.relayServerActorId], + references: [accounts.id], + }), + relayClientActor: one(accounts, { + fields: [relays.relayClientActorId], + references: [accounts.id], + }), +}));