diff --git a/.sqlx/query-017b04caf42b30f2c8f9468acf61a83244b7c2fa5cacfaee41a946a6af5ef68e.json b/.sqlx/query-017b04caf42b30f2c8f9468acf61a83244b7c2fa5cacfaee41a946a6af5ef68e.json deleted file mode 100644 index 94f6c80..0000000 --- a/.sqlx/query-017b04caf42b30f2c8f9468acf61a83244b7c2fa5cacfaee41a946a6af5ef68e.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, backup_enabled FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "backup_enabled", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "017b04caf42b30f2c8f9468acf61a83244b7c2fa5cacfaee41a946a6af5ef68e" -} diff --git a/.sqlx/query-05fd99170e31e68fa5028c862417cdf535cd70e09fde0a8a28249df0070eb2fc.json b/.sqlx/query-05fd99170e31e68fa5028c862417cdf535cd70e09fde0a8a28249df0070eb2fc.json deleted file mode 100644 index 15ba9ce..0000000 --- a/.sqlx/query-05fd99170e31e68fa5028c862417cdf535cd70e09fde0a8a28249df0070eb2fc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT t.token FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "05fd99170e31e68fa5028c862417cdf535cd70e09fde0a8a28249df0070eb2fc" -} diff --git a/.sqlx/query-0710b57fb9aa933525f617b15e6e2e5feaa9c59c38ec9175568abdacda167107.json b/.sqlx/query-0710b57fb9aa933525f617b15e6e2e5feaa9c59c38ec9175568abdacda167107.json deleted file mode 100644 index 734848a..0000000 --- a/.sqlx/query-0710b57fb9aa933525f617b15e6e2e5feaa9c59c38ec9175568abdacda167107.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET deactivated_at = $1 WHERE did = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Timestamptz", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0710b57fb9aa933525f617b15e6e2e5feaa9c59c38ec9175568abdacda167107" -} diff --git a/.sqlx/query-07c54d6bcfdc08d821e788c31dc46cc240dc8507c7dd5bdd2ceeb36d2084e8ce.json b/.sqlx/query-07c54d6bcfdc08d821e788c31dc46cc240dc8507c7dd5bdd2ceeb36d2084e8ce.json deleted file mode 100644 index 1d66fb2..0000000 --- a/.sqlx/query-07c54d6bcfdc08d821e788c31dc46cc240dc8507c7dd5bdd2ceeb36d2084e8ce.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT u.id, u.did, u.backup_enabled, u.deactivated_at, r.repo_root_cid, r.repo_rev\n FROM users u\n JOIN repos r ON r.user_id = u.id\n WHERE u.backup_enabled = true\n AND u.deactivated_at IS NULL\n AND (\n NOT EXISTS (\n SELECT 1 FROM account_backups ab WHERE ab.user_id = u.id\n )\n OR (\n SELECT MAX(ab.created_at) FROM account_backups ab WHERE ab.user_id = u.id\n ) < NOW() - make_interval(secs => $1)\n )\n LIMIT $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "backup_enabled", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "deactivated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "repo_root_cid", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "repo_rev", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Float8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - true, - false, - true - ] - }, - "hash": "07c54d6bcfdc08d821e788c31dc46cc240dc8507c7dd5bdd2ceeb36d2084e8ce" -} diff --git a/.sqlx/query-0ce61cd393867522de318c1f86c787851ec7bbc31b147b9c2fb2bac9b2676ba6.json b/.sqlx/query-0ce61cd393867522de318c1f86c787851ec7bbc31b147b9c2fb2bac9b2676ba6.json deleted file mode 100644 index 1778ec8..0000000 --- a/.sqlx/query-0ce61cd393867522de318c1f86c787851ec7bbc31b147b9c2fb2bac9b2676ba6.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n delegated_did,\n actor_did,\n controller_did,\n action_type as \"action_type: PgDelegationActionType\",\n action_details,\n ip_address,\n user_agent,\n created_at\n FROM delegation_audit_log\n WHERE controller_did = $1\n ORDER BY created_at DESC\n LIMIT $2 OFFSET $3\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "delegated_did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "actor_did", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "controller_did", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "action_type: PgDelegationActionType", - "type_info": { - "Custom": { - "name": "delegation_action_type", - "kind": { - "Enum": [ - "grant_created", - "grant_revoked", - "scopes_modified", - "token_issued", - "repo_write", - "blob_upload", - "account_action" - ] - } - } - } - }, - { - "ordinal": 5, - "name": "action_details", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "ip_address", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "user_agent", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - true, - false, - true, - true, - true, - false - ] - }, - "hash": "0ce61cd393867522de318c1f86c787851ec7bbc31b147b9c2fb2bac9b2676ba6" -} diff --git a/.sqlx/query-0d70da6b077d6fbab5056c38490acbe772626a3447182b76b0cbcee15c5a46dc.json b/.sqlx/query-0d70da6b077d6fbab5056c38490acbe772626a3447182b76b0cbcee15c5a46dc.json deleted file mode 100644 index 42ef672..0000000 --- a/.sqlx/query-0d70da6b077d6fbab5056c38490acbe772626a3447182b76b0cbcee15c5a46dc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT ab.storage_key, ab.repo_rev\n FROM account_backups ab\n JOIN users u ON u.id = ab.user_id\n WHERE ab.id = $1 AND u.did = $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "storage_key", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "repo_rev", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "0d70da6b077d6fbab5056c38490acbe772626a3447182b76b0cbcee15c5a46dc" -} diff --git a/.sqlx/query-0ec60bb854a4991d0d7249a68f7445b65c8cc8c723baca221d85f5e4f2478b99.json b/.sqlx/query-0ec60bb854a4991d0d7249a68f7445b65c8cc8c723baca221d85f5e4f2478b99.json deleted file mode 100644 index 9e5e897..0000000 --- a/.sqlx/query-0ec60bb854a4991d0d7249a68f7445b65c8cc8c723baca221d85f5e4f2478b99.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "body", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "0ec60bb854a4991d0d7249a68f7445b65c8cc8c723baca221d85f5e4f2478b99" -} diff --git a/.sqlx/query-0fae1be7a75bdc58c69a9af97cad4aec23c32a9378764b8d6d7eb2cc89c562b1.json b/.sqlx/query-0fae1be7a75bdc58c69a9af97cad4aec23c32a9378764b8d6d7eb2cc89c562b1.json deleted file mode 100644 index 7da18e9..0000000 --- a/.sqlx/query-0fae1be7a75bdc58c69a9af97cad4aec23c32a9378764b8d6d7eb2cc89c562b1.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT token FROM sso_pending_registration WHERE token = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "0fae1be7a75bdc58c69a9af97cad4aec23c32a9378764b8d6d7eb2cc89c562b1" -} diff --git a/.sqlx/query-120c30d563b3cc40a2bb670f0c38c4ad63360b4a4f8e20c21afbe60c6fb7087a.json b/.sqlx/query-120c30d563b3cc40a2bb670f0c38c4ad63360b4a4f8e20c21afbe60c6fb7087a.json deleted file mode 100644 index 51a6eb8..0000000 --- a/.sqlx/query-120c30d563b3cc40a2bb670f0c38c4ad63360b4a4f8e20c21afbe60c6fb7087a.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id, storage_key\n FROM account_backups\n WHERE user_id = $1\n ORDER BY created_at DESC\n OFFSET $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "storage_key", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Int8" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "120c30d563b3cc40a2bb670f0c38c4ad63360b4a4f8e20c21afbe60c6fb7087a" -} diff --git a/.sqlx/query-1c84643fd6bc57c76517849a64d2d877df337e823d4c2c2b077f695bbfc9e9ac.json b/.sqlx/query-1c84643fd6bc57c76517849a64d2d877df337e823d4c2c2b077f695bbfc9e9ac.json deleted file mode 100644 index d2228ff..0000000 --- a/.sqlx/query-1c84643fd6bc57c76517849a64d2d877df337e823d4c2c2b077f695bbfc9e9ac.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM sso_pending_registration\n WHERE token = $1 AND expires_at > NOW()\n RETURNING token\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "1c84643fd6bc57c76517849a64d2d877df337e823d4c2c2b077f695bbfc9e9ac" -} diff --git a/.sqlx/query-24b823043ab60f36c29029137fef30dfe33922bb06067f2fdbfc1fbb4b0a2a81.json b/.sqlx/query-24b823043ab60f36c29029137fef30dfe33922bb06067f2fdbfc1fbb4b0a2a81.json deleted file mode 100644 index 06e8ff2..0000000 --- a/.sqlx/query-24b823043ab60f36c29029137fef30dfe33922bb06067f2fdbfc1fbb4b0a2a81.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM sso_pending_registration\n WHERE token = $1 AND expires_at > NOW()\n RETURNING token, request_uri\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "request_uri", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "24b823043ab60f36c29029137fef30dfe33922bb06067f2fdbfc1fbb4b0a2a81" -} diff --git a/.sqlx/query-2841093a67480e75e1e9e4046bf3eb74afae2d04f5ea0ec17a4d433983e6d71c.json b/.sqlx/query-2841093a67480e75e1e9e4046bf3eb74afae2d04f5ea0ec17a4d433983e6d71c.json deleted file mode 100644 index ffaa87b..0000000 --- a/.sqlx/query-2841093a67480e75e1e9e4046bf3eb74afae2d04f5ea0ec17a4d433983e6d71c.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO external_identities (did, provider, provider_user_id)\n VALUES ($1, $2, $3)\n RETURNING id\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "2841093a67480e75e1e9e4046bf3eb74afae2d04f5ea0ec17a4d433983e6d71c" -} diff --git a/.sqlx/query-29ef76852bb89af1ab9e679ceaa4abcf8bc8268a348d3be0da9840d1708d20b5.json b/.sqlx/query-29ef76852bb89af1ab9e679ceaa4abcf8bc8268a348d3be0da9840d1708d20b5.json deleted file mode 100644 index 8d0d055..0000000 --- a/.sqlx/query-29ef76852bb89af1ab9e679ceaa4abcf8bc8268a348d3be0da9840d1708d20b5.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "29ef76852bb89af1ab9e679ceaa4abcf8bc8268a348d3be0da9840d1708d20b5" -} diff --git a/.sqlx/query-3043db19db2d0f51d1db6965a3ba0af4a1a4a76cc4ab1cad8395ba6babecc37d.json b/.sqlx/query-3043db19db2d0f51d1db6965a3ba0af4a1a4a76cc4ab1cad8395ba6babecc37d.json deleted file mode 100644 index 972bd93..0000000 --- a/.sqlx/query-3043db19db2d0f51d1db6965a3ba0af4a1a4a76cc4ab1cad8395ba6babecc37d.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT u.id, u.did, u.backup_enabled, u.deactivated_at, r.repo_root_cid, r.repo_rev\n FROM users u\n JOIN repos r ON r.user_id = u.id\n WHERE u.did = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "backup_enabled", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "deactivated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "repo_root_cid", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "repo_rev", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true, - false, - true - ] - }, - "hash": "3043db19db2d0f51d1db6965a3ba0af4a1a4a76cc4ab1cad8395ba6babecc37d" -} diff --git a/.sqlx/query-376b72306b50f747bc9161985ff4f50c35c53025a55ccf5e9933dc3795d29313.json b/.sqlx/query-376b72306b50f747bc9161985ff4f50c35c53025a55ccf5e9933dc3795d29313.json deleted file mode 100644 index 621ea6b..0000000 --- a/.sqlx/query-376b72306b50f747bc9161985ff4f50c35c53025a55ccf5e9933dc3795d29313.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_pending_registration (token, request_uri, provider, provider_user_id, provider_email_verified)\n VALUES ($1, $2, $3, $4, $5)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "376b72306b50f747bc9161985ff4f50c35c53025a55ccf5e9933dc3795d29313" -} diff --git a/.sqlx/query-3933ea5b147ab6294936de147b98e116cfae848ecd76ea5d367585eb5117f2ad.json b/.sqlx/query-3933ea5b147ab6294936de147b98e116cfae848ecd76ea5d367585eb5117f2ad.json deleted file mode 100644 index 8192722..0000000 --- a/.sqlx/query-3933ea5b147ab6294936de147b98e116cfae848ecd76ea5d367585eb5117f2ad.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id FROM external_identities WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false - ] - }, - "hash": "3933ea5b147ab6294936de147b98e116cfae848ecd76ea5d367585eb5117f2ad" -} diff --git a/.sqlx/query-3bed8d4843545f4a9676207513806603c50eb2af92957994abaf1c89c0294c12.json b/.sqlx/query-3bed8d4843545f4a9676207513806603c50eb2af92957994abaf1c89c0294c12.json deleted file mode 100644 index 097e2cc..0000000 --- a/.sqlx/query-3bed8d4843545f4a9676207513806603c50eb2af92957994abaf1c89c0294c12.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (did, handle, email, password_hash) VALUES ($1, $2, $3, 'hash')", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "3bed8d4843545f4a9676207513806603c50eb2af92957994abaf1c89c0294c12" -} diff --git a/.sqlx/query-4445cc86cdf04894b340e67661b79a3c411917144a011f50849b737130b24dbe.json b/.sqlx/query-4445cc86cdf04894b340e67661b79a3c411917144a011f50849b737130b24dbe.json deleted file mode 100644 index ac57c00..0000000 --- a/.sqlx/query-4445cc86cdf04894b340e67661b79a3c411917144a011f50849b737130b24dbe.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT subject, body, comms_type as \"comms_type: String\" FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' ORDER BY created_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "subject", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "body", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "comms_type: String", - "type_info": { - "Custom": { - "name": "comms_type", - "kind": { - "Enum": [ - "welcome", - "email_verification", - "password_reset", - "email_update", - "account_deletion", - "admin_email", - "plc_operation", - "two_factor_code", - "channel_verification", - "passkey_recovery", - "legacy_login_alert", - "migration_verification", - "channel_verified" - ] - } - } - } - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - true, - false, - false - ] - }, - "hash": "4445cc86cdf04894b340e67661b79a3c411917144a011f50849b737130b24dbe" -} diff --git a/.sqlx/query-4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068.json b/.sqlx/query-4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068.json deleted file mode 100644 index b81fee7..0000000 --- a/.sqlx/query-4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id FROM users WHERE email = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "4560c237741ce9d4166aecd669770b3360a3ac71e649b293efb88d92c3254068" -} diff --git a/.sqlx/query-47fe4a54857344d8f789f37092a294cd58f64b4fb431b54b5deda13d64525e88.json b/.sqlx/query-47fe4a54857344d8f789f37092a294cd58f64b4fb431b54b5deda13d64525e88.json deleted file mode 100644 index e232907..0000000 --- a/.sqlx/query-47fe4a54857344d8f789f37092a294cd58f64b4fb431b54b5deda13d64525e88.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT token, expires_at FROM account_deletion_requests WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "expires_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "47fe4a54857344d8f789f37092a294cd58f64b4fb431b54b5deda13d64525e88" -} diff --git a/.sqlx/query-49cbc923cc4a0dcf7dea4ead5ab9580ff03b717586c4ca2d5343709e2dac86b6.json b/.sqlx/query-49cbc923cc4a0dcf7dea4ead5ab9580ff03b717586c4ca2d5343709e2dac86b6.json deleted file mode 100644 index 873f0f7..0000000 --- a/.sqlx/query-49cbc923cc4a0dcf7dea4ead5ab9580ff03b717586c4ca2d5343709e2dac86b6.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT email_verified FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "email_verified", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "49cbc923cc4a0dcf7dea4ead5ab9580ff03b717586c4ca2d5343709e2dac86b6" -} diff --git a/.sqlx/query-4fef326fa2d03d04869af3fec702c901d1ecf392545a3a032438b2c1859d46cc.json b/.sqlx/query-4fef326fa2d03d04869af3fec702c901d1ecf392545a3a032438b2c1859d46cc.json deleted file mode 100644 index 3780e70..0000000 --- a/.sqlx/query-4fef326fa2d03d04869af3fec702c901d1ecf392545a3a032438b2c1859d46cc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT token FROM sso_pending_registration\n WHERE token = $1 AND expires_at > NOW()\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "4fef326fa2d03d04869af3fec702c901d1ecf392545a3a032438b2c1859d46cc" -} diff --git a/.sqlx/query-575c1e5529874f8f523e6fe22ccf4ee3296806581b1765dfb91a84ffab347f15.json b/.sqlx/query-575c1e5529874f8f523e6fe22ccf4ee3296806581b1765dfb91a84ffab347f15.json deleted file mode 100644 index e11b68f..0000000 --- a/.sqlx/query-575c1e5529874f8f523e6fe22ccf4ee3296806581b1765dfb91a84ffab347f15.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO oauth_authorization_request (id, client_id, parameters, expires_at)\n VALUES ($1, 'https://test.example.com', $2, NOW() + INTERVAL '1 hour')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "575c1e5529874f8f523e6fe22ccf4ee3296806581b1765dfb91a84ffab347f15" -} diff --git a/.sqlx/query-596c3400a60c77c7645fd46fcea61fa7898b6832e58c0f647f382b23b81d350e.json b/.sqlx/query-596c3400a60c77c7645fd46fcea61fa7898b6832e58c0f647f382b23b81d350e.json deleted file mode 100644 index dfa5ed9..0000000 --- a/.sqlx/query-596c3400a60c77c7645fd46fcea61fa7898b6832e58c0f647f382b23b81d350e.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_pending_registration (token, request_uri, provider, provider_user_id, provider_username, provider_email)\n VALUES ($1, $2, $3, $4, $5, $6)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "596c3400a60c77c7645fd46fcea61fa7898b6832e58c0f647f382b23b81d350e" -} diff --git a/.sqlx/query-59e63c5cf92985714e9586d1ce012efef733d4afaa4ea09974daf8303805e5d2.json b/.sqlx/query-59e63c5cf92985714e9586d1ce012efef733d4afaa4ea09974daf8303805e5d2.json deleted file mode 100644 index 6b1501f..0000000 --- a/.sqlx/query-59e63c5cf92985714e9586d1ce012efef733d4afaa4ea09974daf8303805e5d2.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id, did, provider as \"provider: SsoProviderType\", provider_user_id, provider_username, provider_email\n FROM external_identities\n WHERE provider = $1 AND provider_user_id = $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "provider: SsoProviderType", - "type_info": { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - } - }, - { - "ordinal": 3, - "name": "provider_user_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "provider_username", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "provider_email", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - true - ] - }, - "hash": "59e63c5cf92985714e9586d1ce012efef733d4afaa4ea09974daf8303805e5d2" -} diff --git a/.sqlx/query-5a016f289caf75177731711e56e92881ba343c73a9a6e513e205c801c5943ec0.json b/.sqlx/query-5a016f289caf75177731711e56e92881ba343c73a9a6e513e205c801c5943ec0.json deleted file mode 100644 index 32bd1aa..0000000 --- a/.sqlx/query-5a016f289caf75177731711e56e92881ba343c73a9a6e513e205c801c5943ec0.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT k.key_bytes, k.encryption_version\n FROM user_keys k\n JOIN users u ON k.user_id = u.id\n WHERE u.did = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "key_bytes", - "type_info": "Bytea" - }, - { - "ordinal": 1, - "name": "encryption_version", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "5a016f289caf75177731711e56e92881ba343c73a9a6e513e205c801c5943ec0" -} diff --git a/.sqlx/query-5af4a386c1632903ad7102551a5bd148bcf541baab6a84c8649666a695f9c4d1.json b/.sqlx/query-5af4a386c1632903ad7102551a5bd148bcf541baab6a84c8649666a695f9c4d1.json deleted file mode 100644 index 0f764da..0000000 --- a/.sqlx/query-5af4a386c1632903ad7102551a5bd148bcf541baab6a84c8649666a695f9c4d1.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM sso_auth_state\n WHERE state = $1 AND expires_at > NOW()\n RETURNING state\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "state", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "5af4a386c1632903ad7102551a5bd148bcf541baab6a84c8649666a695f9c4d1" -} diff --git a/.sqlx/query-5e4c0dd92ac3c4b5e2eae5d129f2649cf3a8f068105f44a8dca9625427affc06.json b/.sqlx/query-5e4c0dd92ac3c4b5e2eae5d129f2649cf3a8f068105f44a8dca9625427affc06.json deleted file mode 100644 index 525f4ef..0000000 --- a/.sqlx/query-5e4c0dd92ac3c4b5e2eae5d129f2649cf3a8f068105f44a8dca9625427affc06.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT provider_user_id, provider_email_verified\n FROM external_identities\n WHERE did = $1 AND provider = $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "provider_user_id", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "provider_email_verified", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - } - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "5e4c0dd92ac3c4b5e2eae5d129f2649cf3a8f068105f44a8dca9625427affc06" -} diff --git a/.sqlx/query-5e9c6ec72c2c0ea1c8dff551d01baddd1dd953c828a5656db2ee39dea996f890.json b/.sqlx/query-5e9c6ec72c2c0ea1c8dff551d01baddd1dd953c828a5656db2ee39dea996f890.json deleted file mode 100644 index f109216..0000000 --- a/.sqlx/query-5e9c6ec72c2c0ea1c8dff551d01baddd1dd953c828a5656db2ee39dea996f890.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_auth_state (state, request_uri, provider, action, nonce, code_verifier)\n VALUES ($1, $2, $3, $4, $5, $6)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "5e9c6ec72c2c0ea1c8dff551d01baddd1dd953c828a5656db2ee39dea996f890" -} diff --git a/.sqlx/query-63f6f2a89650794fe90e10ce7fc785a6b9f7d37c12b31a6ff13f7c5214eef19e.json b/.sqlx/query-63f6f2a89650794fe90e10ce7fc785a6b9f7d37c12b31a6ff13f7c5214eef19e.json deleted file mode 100644 index cb057f9..0000000 --- a/.sqlx/query-63f6f2a89650794fe90e10ce7fc785a6b9f7d37c12b31a6ff13f7c5214eef19e.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT did, email_verified FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "email_verified", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "63f6f2a89650794fe90e10ce7fc785a6b9f7d37c12b31a6ff13f7c5214eef19e" -} diff --git a/.sqlx/query-6c7ace2a64848adc757af6c93b9162e1d95788b372370a7ad0d7540338bb73ee.json b/.sqlx/query-6c7ace2a64848adc757af6c93b9162e1d95788b372370a7ad0d7540338bb73ee.json deleted file mode 100644 index eed954b..0000000 --- a/.sqlx/query-6c7ace2a64848adc757af6c93b9162e1d95788b372370a7ad0d7540338bb73ee.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT state, request_uri, provider as \"provider: SsoProviderType\", action, nonce, code_verifier\n FROM sso_auth_state\n WHERE state = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "state", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "request_uri", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "provider: SsoProviderType", - "type_info": { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - } - }, - { - "ordinal": 3, - "name": "action", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "nonce", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "code_verifier", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - true - ] - }, - "hash": "6c7ace2a64848adc757af6c93b9162e1d95788b372370a7ad0d7540338bb73ee" -} diff --git a/.sqlx/query-6c7d5b62546ed7581edbfdb6946492a6cc41b94b04009b5602f1ef72e0138edc.json b/.sqlx/query-6c7d5b62546ed7581edbfdb6946492a6cc41b94b04009b5602f1ef72e0138edc.json deleted file mode 100644 index 3d4eed4..0000000 --- a/.sqlx/query-6c7d5b62546ed7581edbfdb6946492a6cc41b94b04009b5602f1ef72e0138edc.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO account_backups (user_id, storage_key, repo_root_cid, repo_rev, block_count, size_bytes)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (storage_key) DO UPDATE SET created_at = NOW()\n RETURNING id\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text", - "Text", - "Text", - "Int4", - "Int8" - ] - }, - "nullable": [ - false - ] - }, - "hash": "6c7d5b62546ed7581edbfdb6946492a6cc41b94b04009b5602f1ef72e0138edc" -} diff --git a/.sqlx/query-6fbcff0206599484bfb6cef165b6f729d27e7a342f7718ee4ac07f0ca94412ba.json b/.sqlx/query-6fbcff0206599484bfb6cef165b6f729d27e7a342f7718ee4ac07f0ca94412ba.json deleted file mode 100644 index dbd52e6..0000000 --- a/.sqlx/query-6fbcff0206599484bfb6cef165b6f729d27e7a342f7718ee4ac07f0ca94412ba.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT state FROM sso_auth_state WHERE state = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "state", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "6fbcff0206599484bfb6cef165b6f729d27e7a342f7718ee4ac07f0ca94412ba" -} diff --git a/.sqlx/query-712459c27fc037f45389e2766cf1057e86e93ef756a784ed12beb453b03c5da1.json b/.sqlx/query-712459c27fc037f45389e2766cf1057e86e93ef756a784ed12beb453b03c5da1.json deleted file mode 100644 index e831be8..0000000 --- a/.sqlx/query-712459c27fc037f45389e2766cf1057e86e93ef756a784ed12beb453b03c5da1.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_pending_registration (token, request_uri, provider, provider_user_id, provider_username, provider_email_verified)\n VALUES ($1, $2, $3, $4, $5, $6)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Text", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "712459c27fc037f45389e2766cf1057e86e93ef756a784ed12beb453b03c5da1" -} diff --git a/.sqlx/query-785a864944c5939331704c71b0cd3ed26ffdd64f3fd0f26ecc28b6a0557bbe8f.json b/.sqlx/query-785a864944c5939331704c71b0cd3ed26ffdd64f3fd0f26ecc28b6a0557bbe8f.json deleted file mode 100644 index af80ee3..0000000 --- a/.sqlx/query-785a864944c5939331704c71b0cd3ed26ffdd64f3fd0f26ecc28b6a0557bbe8f.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT subject FROM comms_queue WHERE user_id = $1 AND comms_type = 'admin_email' AND body = 'Email without subject' LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "subject", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - true - ] - }, - "hash": "785a864944c5939331704c71b0cd3ed26ffdd64f3fd0f26ecc28b6a0557bbe8f" -} diff --git a/.sqlx/query-78be3d837cfb54d5e915ff0c5b1aaedc104776a22ad28b637de574e58d052036.json b/.sqlx/query-78be3d837cfb54d5e915ff0c5b1aaedc104776a22ad28b637de574e58d052036.json deleted file mode 100644 index b238d4a..0000000 --- a/.sqlx/query-78be3d837cfb54d5e915ff0c5b1aaedc104776a22ad28b637de574e58d052036.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT ab.id, ab.storage_key, u.deactivated_at\n FROM account_backups ab\n JOIN users u ON u.id = ab.user_id\n WHERE ab.id = $1 AND u.did = $2\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "storage_key", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "deactivated_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - false, - false, - true - ] - }, - "hash": "78be3d837cfb54d5e915ff0c5b1aaedc104776a22ad28b637de574e58d052036" -} diff --git a/.sqlx/query-7caa8f9083b15ec1209dda35c4c6f6fba9fe338e4a6a10636b5389d426df1631.json b/.sqlx/query-7caa8f9083b15ec1209dda35c4c6f6fba9fe338e4a6a10636b5389d426df1631.json deleted file mode 100644 index 416f681..0000000 --- a/.sqlx/query-7caa8f9083b15ec1209dda35c4c6f6fba9fe338e4a6a10636b5389d426df1631.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT t.token\n FROM plc_operation_tokens t\n JOIN users u ON t.user_id = u.id\n WHERE u.did = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "7caa8f9083b15ec1209dda35c4c6f6fba9fe338e4a6a10636b5389d426df1631" -} diff --git a/.sqlx/query-7d24e744a4e63570b1410e50b45b745ce8915ab3715b3eff7efc2d84f27735d0.json b/.sqlx/query-7d24e744a4e63570b1410e50b45b745ce8915ab3715b3eff7efc2d84f27735d0.json deleted file mode 100644 index 25e1b1d..0000000 --- a/.sqlx/query-7d24e744a4e63570b1410e50b45b745ce8915ab3715b3eff7efc2d84f27735d0.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT provider_username, last_login_at FROM external_identities WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "provider_username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - true, - true - ] - }, - "hash": "7d24e744a4e63570b1410e50b45b745ce8915ab3715b3eff7efc2d84f27735d0" -} diff --git a/.sqlx/query-7d43e7e917104d7bce29459149a345145361c6940e9b8e2ccc561a0a032f63c0.json b/.sqlx/query-7d43e7e917104d7bce29459149a345145361c6940e9b8e2ccc561a0a032f63c0.json deleted file mode 100644 index 21118d5..0000000 --- a/.sqlx/query-7d43e7e917104d7bce29459149a345145361c6940e9b8e2ccc561a0a032f63c0.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n u.did,\n u.handle,\n d.granted_scopes,\n d.granted_at,\n (u.deactivated_at IS NULL AND u.takedown_ref IS NULL) as \"is_active!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1 AND d.revoked_at IS NULL\n ORDER BY d.granted_at DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "handle", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "granted_scopes", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "granted_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "is_active!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - null - ] - }, - "hash": "7d43e7e917104d7bce29459149a345145361c6940e9b8e2ccc561a0a032f63c0" -} diff --git a/.sqlx/query-7ec9500e28b450ab569ec50f968f5f66b7e8e80b160db9734cb78f91fc9f2cc6.json b/.sqlx/query-7ec9500e28b450ab569ec50f968f5f66b7e8e80b160db9734cb78f91fc9f2cc6.json deleted file mode 100644 index 752c42f..0000000 --- a/.sqlx/query-7ec9500e28b450ab569ec50f968f5f66b7e8e80b160db9734cb78f91fc9f2cc6.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id, repo_rev, repo_root_cid, block_count, size_bytes, created_at\n FROM account_backups\n WHERE user_id = $1\n ORDER BY created_at DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "repo_rev", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "repo_root_cid", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "block_count", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "size_bytes", - "type_info": "Int8" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false - ] - }, - "hash": "7ec9500e28b450ab569ec50f968f5f66b7e8e80b160db9734cb78f91fc9f2cc6" -} diff --git a/.sqlx/query-82717b6f61cd79347e1ca7e92c4413743ba168d1e0d8b85566711e54d4048f81.json b/.sqlx/query-82717b6f61cd79347e1ca7e92c4413743ba168d1e0d8b85566711e54d4048f81.json deleted file mode 100644 index 8d5a9b7..0000000 --- a/.sqlx/query-82717b6f61cd79347e1ca7e92c4413743ba168d1e0d8b85566711e54d4048f81.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT t.token, t.expires_at FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "expires_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "82717b6f61cd79347e1ca7e92c4413743ba168d1e0d8b85566711e54d4048f81" -} diff --git a/.sqlx/query-85ffc37a77af832d7795f5f37efe304fced4bf56b4f2287fe9aeb3fc97e1b191.json b/.sqlx/query-85ffc37a77af832d7795f5f37efe304fced4bf56b4f2287fe9aeb3fc97e1b191.json deleted file mode 100644 index 7938c39..0000000 --- a/.sqlx/query-85ffc37a77af832d7795f5f37efe304fced4bf56b4f2287fe9aeb3fc97e1b191.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_pending_registration (token, request_uri, provider, provider_user_id, provider_username, provider_email, provider_email_verified)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Text", - "Text", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "85ffc37a77af832d7795f5f37efe304fced4bf56b4f2287fe9aeb3fc97e1b191" -} diff --git a/.sqlx/query-9ad422bf3c43e3cfd86fc88c73594246ead214ca794760d3fe77bb5cf4f27be5.json b/.sqlx/query-9ad422bf3c43e3cfd86fc88c73594246ead214ca794760d3fe77bb5cf4f27be5.json deleted file mode 100644 index ef52899..0000000 --- a/.sqlx/query-9ad422bf3c43e3cfd86fc88c73594246ead214ca794760d3fe77bb5cf4f27be5.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "body", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9ad422bf3c43e3cfd86fc88c73594246ead214ca794760d3fe77bb5cf4f27be5" -} diff --git a/.sqlx/query-9b035b051769e6b9d45910a8bb42ac0f84c73de8c244ba4560f004ee3f4b7002.json b/.sqlx/query-9b035b051769e6b9d45910a8bb42ac0f84c73de8c244ba4560f004ee3f4b7002.json deleted file mode 100644 index 178bc9e..0000000 --- a/.sqlx/query-9b035b051769e6b9d45910a8bb42ac0f84c73de8c244ba4560f004ee3f4b7002.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT did, public_key_did_key FROM reserved_signing_keys WHERE public_key_did_key = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "public_key_did_key", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true, - false - ] - }, - "hash": "9b035b051769e6b9d45910a8bb42ac0f84c73de8c244ba4560f004ee3f4b7002" -} diff --git a/.sqlx/query-9dba64081d4f95b5490c9a9bf30a7175db3429f39df4f25e212f38f33882fc65.json b/.sqlx/query-9dba64081d4f95b5490c9a9bf30a7175db3429f39df4f25e212f38f33882fc65.json deleted file mode 100644 index ea2847f..0000000 --- a/.sqlx/query-9dba64081d4f95b5490c9a9bf30a7175db3429f39df4f25e212f38f33882fc65.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id FROM external_identities WHERE did = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "9dba64081d4f95b5490c9a9bf30a7175db3429f39df4f25e212f38f33882fc65" -} diff --git a/.sqlx/query-9fd56986c1c843d386d1e5884acef8573eb55a3e9f5cb0122fcf8b93d6d667a5.json b/.sqlx/query-9fd56986c1c843d386d1e5884acef8573eb55a3e9f5cb0122fcf8b93d6d667a5.json deleted file mode 100644 index c77d51a..0000000 --- a/.sqlx/query-9fd56986c1c843d386d1e5884acef8573eb55a3e9f5cb0122fcf8b93d6d667a5.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT token, request_uri, provider as \"provider: SsoProviderType\", provider_user_id,\n provider_username, provider_email\n FROM sso_pending_registration\n WHERE token = $1 AND expires_at > NOW()\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "request_uri", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "provider: SsoProviderType", - "type_info": { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - } - }, - { - "ordinal": 3, - "name": "provider_user_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "provider_username", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "provider_email", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - true - ] - }, - "hash": "9fd56986c1c843d386d1e5884acef8573eb55a3e9f5cb0122fcf8b93d6d667a5" -} diff --git a/.sqlx/query-a23a390659616779d7dbceaa3b5d5171e70fa25e3b8393e142cebcbff752f0f5.json b/.sqlx/query-a23a390659616779d7dbceaa3b5d5171e70fa25e3b8393e142cebcbff752f0f5.json deleted file mode 100644 index 2640f70..0000000 --- a/.sqlx/query-a23a390659616779d7dbceaa3b5d5171e70fa25e3b8393e142cebcbff752f0f5.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT private_key_bytes, expires_at, used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "private_key_bytes", - "type_info": "Bytea" - }, - { - "ordinal": 1, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 2, - "name": "used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true - ] - }, - "hash": "a23a390659616779d7dbceaa3b5d5171e70fa25e3b8393e142cebcbff752f0f5" -} diff --git a/.sqlx/query-a3d549a32e76c24e265c73a98dd739067623f275de0740bd576ee288f4444496.json b/.sqlx/query-a3d549a32e76c24e265c73a98dd739067623f275de0740bd576ee288f4444496.json deleted file mode 100644 index 06c9dee..0000000 --- a/.sqlx/query-a3d549a32e76c24e265c73a98dd739067623f275de0740bd576ee288f4444496.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE external_identities\n SET provider_username = $2, last_login_at = NOW()\n WHERE id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [] - }, - "hash": "a3d549a32e76c24e265c73a98dd739067623f275de0740bd576ee288f4444496" -} diff --git a/.sqlx/query-a802d7d860f263eace39ce82bb27b633cec7287c1cc177f0e1d47ec6571564d5.json b/.sqlx/query-a802d7d860f263eace39ce82bb27b633cec7287c1cc177f0e1d47ec6571564d5.json deleted file mode 100644 index f10b2bc..0000000 --- a/.sqlx/query-a802d7d860f263eace39ce82bb27b633cec7287c1cc177f0e1d47ec6571564d5.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT token FROM account_deletion_requests WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "token", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "a802d7d860f263eace39ce82bb27b633cec7287c1cc177f0e1d47ec6571564d5" -} diff --git a/.sqlx/query-a844774d8dd3c50c5faf3de5d43f534b80234759c8437434e467ca33ea10fd1f.json b/.sqlx/query-a844774d8dd3c50c5faf3de5d43f534b80234759c8437434e467ca33ea10fd1f.json deleted file mode 100644 index 9cfbdba..0000000 --- a/.sqlx/query-a844774d8dd3c50c5faf3de5d43f534b80234759c8437434e467ca33ea10fd1f.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT preferred_comms_channel as \"preferred_comms_channel: String\", discord_username FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "preferred_comms_channel: String", - "type_info": { - "Custom": { - "name": "comms_channel", - "kind": { - "Enum": [ - "email", - "discord", - "telegram", - "signal" - ] - } - } - } - }, - { - "ordinal": 1, - "name": "discord_username", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true - ] - }, - "hash": "a844774d8dd3c50c5faf3de5d43f534b80234759c8437434e467ca33ea10fd1f" -} diff --git a/.sqlx/query-aee3e8e1d8924d41bec7d866e274f8bb2ddef833eb03326103c2d0a17ee56154.json b/.sqlx/query-aee3e8e1d8924d41bec7d866e274f8bb2ddef833eb03326103c2d0a17ee56154.json deleted file mode 100644 index 3e281aa..0000000 --- a/.sqlx/query-aee3e8e1d8924d41bec7d866e274f8bb2ddef833eb03326103c2d0a17ee56154.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM sso_auth_state\n WHERE state = $1 AND expires_at > NOW()\n RETURNING state, request_uri\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "state", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "request_uri", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "aee3e8e1d8924d41bec7d866e274f8bb2ddef833eb03326103c2d0a17ee56154" -} diff --git a/.sqlx/query-b22003e74a58303beb993547a8e2bf2415152a5546d6a10c9aebc223420b0088.json b/.sqlx/query-b22003e74a58303beb993547a8e2bf2415152a5546d6a10c9aebc223420b0088.json deleted file mode 100644 index 2e31cca..0000000 --- a/.sqlx/query-b22003e74a58303beb993547a8e2bf2415152a5546d6a10c9aebc223420b0088.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT DISTINCT b.cid, b.storage_key, b.mime_type\n FROM blobs b\n JOIN record_blobs rb ON rb.blob_cid = b.cid\n WHERE rb.repo_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "cid", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "storage_key", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "mime_type", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "b22003e74a58303beb993547a8e2bf2415152a5546d6a10c9aebc223420b0088" -} diff --git a/.sqlx/query-ba9684872fad5201b8504c2606c29364a2df9631fe98817e7bfacd3f3f51f6cb.json b/.sqlx/query-ba9684872fad5201b8504c2606c29364a2df9631fe98817e7bfacd3f3f51f6cb.json deleted file mode 100644 index 5a2db77..0000000 --- a/.sqlx/query-ba9684872fad5201b8504c2606c29364a2df9631fe98817e7bfacd3f3f51f6cb.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_pending_registration (token, request_uri, provider, provider_user_id, expires_at)\n VALUES ($1, $2, $3, $4, NOW() - INTERVAL '1 hour')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text" - ] - }, - "nullable": [] - }, - "hash": "ba9684872fad5201b8504c2606c29364a2df9631fe98817e7bfacd3f3f51f6cb" -} diff --git a/.sqlx/query-bb4460f75d30f48b79d71b97f2c7d54190260deba2d2ade177dbdaa507ab275b.json b/.sqlx/query-bb4460f75d30f48b79d71b97f2c7d54190260deba2d2ade177dbdaa507ab275b.json deleted file mode 100644 index 97297bf..0000000 --- a/.sqlx/query-bb4460f75d30f48b79d71b97f2c7d54190260deba2d2ade177dbdaa507ab275b.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM sso_auth_state WHERE expires_at < NOW()", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "bb4460f75d30f48b79d71b97f2c7d54190260deba2d2ade177dbdaa507ab275b" -} diff --git a/.sqlx/query-bb83d46014c20edc99ef7e81808087097e719771c6c8163e4e8e11a98bb97dde.json b/.sqlx/query-bb83d46014c20edc99ef7e81808087097e719771c6c8163e4e8e11a98bb97dde.json deleted file mode 100644 index 9ea6934..0000000 --- a/.sqlx/query-bb83d46014c20edc99ef7e81808087097e719771c6c8163e4e8e11a98bb97dde.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM account_backups WHERE user_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "bb83d46014c20edc99ef7e81808087097e719771c6c8163e4e8e11a98bb97dde" -} diff --git a/.sqlx/query-bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f.json b/.sqlx/query-bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f.json deleted file mode 100644 index 947e598..0000000 --- a/.sqlx/query-bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT deactivated_at FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "deactivated_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f" -} diff --git a/.sqlx/query-c69577bc8e88e2f2cc516d6bec3d201419b589b6088ceb933cb182265b14e363.json b/.sqlx/query-c69577bc8e88e2f2cc516d6bec3d201419b589b6088ceb933cb182265b14e363.json deleted file mode 100644 index 8aa0251..0000000 --- a/.sqlx/query-c69577bc8e88e2f2cc516d6bec3d201419b589b6088ceb933cb182265b14e363.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n u.did,\n u.handle,\n d.granted_scopes,\n d.granted_at,\n true as \"is_active!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1\n AND d.revoked_at IS NULL\n AND u.deactivated_at IS NULL\n AND u.takedown_ref IS NULL\n ORDER BY d.granted_at DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "handle", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "granted_scopes", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "granted_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "is_active!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - null - ] - }, - "hash": "c69577bc8e88e2f2cc516d6bec3d201419b589b6088ceb933cb182265b14e363" -} diff --git a/.sqlx/query-cd3b8098ad4c1056c1d23acd8a6b29f7abfe18ee6f559bd94ab16274b1cfdfee.json b/.sqlx/query-cd3b8098ad4c1056c1d23acd8a6b29f7abfe18ee6f559bd94ab16274b1cfdfee.json deleted file mode 100644 index 6b53208..0000000 --- a/.sqlx/query-cd3b8098ad4c1056c1d23acd8a6b29f7abfe18ee6f559bd94ab16274b1cfdfee.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT password_reset_code FROM users WHERE email = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "password_reset_code", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "cd3b8098ad4c1056c1d23acd8a6b29f7abfe18ee6f559bd94ab16274b1cfdfee" -} diff --git a/.sqlx/query-cda68f9b6c60295a196fc853b70ec5fd51a8ffaa2bac5942c115c99d1cbcafa3.json b/.sqlx/query-cda68f9b6c60295a196fc853b70ec5fd51a8ffaa2bac5942c115c99d1cbcafa3.json deleted file mode 100644 index 08e8e23..0000000 --- a/.sqlx/query-cda68f9b6c60295a196fc853b70ec5fd51a8ffaa2bac5942c115c99d1cbcafa3.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) as \"count!\" FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "cda68f9b6c60295a196fc853b70ec5fd51a8ffaa2bac5942c115c99d1cbcafa3" -} diff --git a/.sqlx/query-d0d4fb4b44cda3442b20037b4d5efaa032e1d004c775e2b6077c5050d7d62041.json b/.sqlx/query-d0d4fb4b44cda3442b20037b4d5efaa032e1d004c775e2b6077c5050d7d62041.json deleted file mode 100644 index d122744..0000000 --- a/.sqlx/query-d0d4fb4b44cda3442b20037b4d5efaa032e1d004c775e2b6077c5050d7d62041.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO sso_auth_state (state, request_uri, provider, action, expires_at)\n VALUES ($1, $2, $3, $4, NOW() - INTERVAL '1 hour')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text" - ] - }, - "nullable": [] - }, - "hash": "d0d4fb4b44cda3442b20037b4d5efaa032e1d004c775e2b6077c5050d7d62041" -} diff --git a/.sqlx/query-d529d6dc9858c1da360f0417e94a3b40041b043bae57e95002d4bf5df46a4ab4.json b/.sqlx/query-d529d6dc9858c1da360f0417e94a3b40041b043bae57e95002d4bf5df46a4ab4.json deleted file mode 100644 index be751e8..0000000 --- a/.sqlx/query-d529d6dc9858c1da360f0417e94a3b40041b043bae57e95002d4bf5df46a4ab4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE account_deletion_requests SET expires_at = NOW() - INTERVAL '1 hour' WHERE token = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "d529d6dc9858c1da360f0417e94a3b40041b043bae57e95002d4bf5df46a4ab4" -} diff --git a/.sqlx/query-d8e646324c93b375cceccea533ddd880225931f29ce4d8c5184197fecce25fa7.json b/.sqlx/query-d8e646324c93b375cceccea533ddd880225931f29ce4d8c5184197fecce25fa7.json deleted file mode 100644 index d2f730f..0000000 --- a/.sqlx/query-d8e646324c93b375cceccea533ddd880225931f29ce4d8c5184197fecce25fa7.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n d.controller_did,\n u.handle as \"handle?\",\n d.granted_scopes,\n d.granted_at,\n true as \"is_active!\",\n u.did IS NOT NULL as \"is_local!\"\n FROM account_delegations d\n LEFT JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1\n AND d.revoked_at IS NULL\n AND (u.did IS NULL OR (u.deactivated_at IS NULL AND u.takedown_ref IS NULL))\n ORDER BY d.granted_at DESC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "controller_did", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "handle?", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "granted_scopes", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "granted_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "is_active!", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "is_local!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - null, - null - ] - }, - "hash": "d8e646324c93b375cceccea533ddd880225931f29ce4d8c5184197fecce25fa7" -} diff --git a/.sqlx/query-d92bfa1e67c60c4fb333b6390e49224381c1dd23c3ef09aff3a6d59eadd13c64.json b/.sqlx/query-d92bfa1e67c60c4fb333b6390e49224381c1dd23c3ef09aff3a6d59eadd13c64.json deleted file mode 100644 index 9dc1d25..0000000 --- a/.sqlx/query-d92bfa1e67c60c4fb333b6390e49224381c1dd23c3ef09aff3a6d59eadd13c64.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT COUNT(*) as \"count!\"\n FROM account_delegations d\n JOIN users u ON u.did = d.controller_did\n WHERE d.delegated_did = $1\n AND d.revoked_at IS NULL\n AND u.deactivated_at IS NULL\n AND u.takedown_ref IS NULL\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "d92bfa1e67c60c4fb333b6390e49224381c1dd23c3ef09aff3a6d59eadd13c64" -} diff --git a/.sqlx/query-dd7d80d4d118a5fc95b574e2ca9ffaccf974e52fb6ac368f716409c55f9d3ab0.json b/.sqlx/query-dd7d80d4d118a5fc95b574e2ca9ffaccf974e52fb6ac368f716409c55f9d3ab0.json deleted file mode 100644 index 03f2acb..0000000 --- a/.sqlx/query-dd7d80d4d118a5fc95b574e2ca9ffaccf974e52fb6ac368f716409c55f9d3ab0.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO external_identities (did, provider, provider_user_id, provider_username, provider_email)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "dd7d80d4d118a5fc95b574e2ca9ffaccf974e52fb6ac368f716409c55f9d3ab0" -} diff --git a/.sqlx/query-e20cbe2a939d790aaea718b084a80d8ede655ba1cc0fd4346d7e91d6de7d6cf3.json b/.sqlx/query-e20cbe2a939d790aaea718b084a80d8ede655ba1cc0fd4346d7e91d6de7d6cf3.json deleted file mode 100644 index 0576d91..0000000 --- a/.sqlx/query-e20cbe2a939d790aaea718b084a80d8ede655ba1cc0fd4346d7e91d6de7d6cf3.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM comms_queue WHERE user_id = $1 AND comms_type = 'password_reset'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - null - ] - }, - "hash": "e20cbe2a939d790aaea718b084a80d8ede655ba1cc0fd4346d7e91d6de7d6cf3" -} diff --git a/.sqlx/query-e64cd36284d10ab7f3d9f6959975a1a627809f444b0faff7e611d985f31b90e9.json b/.sqlx/query-e64cd36284d10ab7f3d9f6959975a1a627809f444b0faff7e611d985f31b90e9.json deleted file mode 100644 index edb2f4b..0000000 --- a/.sqlx/query-e64cd36284d10ab7f3d9f6959975a1a627809f444b0faff7e611d985f31b90e9.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT used_at FROM reserved_signing_keys WHERE public_key_did_key = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "e64cd36284d10ab7f3d9f6959975a1a627809f444b0faff7e611d985f31b90e9" -} diff --git a/.sqlx/query-eb54d2ce02cab7c2e7f9926bd469b19e5f0513f47173b2738fc01a57082d7abb.json b/.sqlx/query-eb54d2ce02cab7c2e7f9926bd469b19e5f0513f47173b2738fc01a57082d7abb.json deleted file mode 100644 index 494c6cb..0000000 --- a/.sqlx/query-eb54d2ce02cab7c2e7f9926bd469b19e5f0513f47173b2738fc01a57082d7abb.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO external_identities (did, provider, provider_user_id)\n VALUES ($1, $2, $3)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - { - "Custom": { - "name": "sso_provider_type", - "kind": { - "Enum": [ - "github", - "discord", - "google", - "gitlab", - "oidc", - "apple" - ] - } - } - }, - "Text" - ] - }, - "nullable": [] - }, - "hash": "eb54d2ce02cab7c2e7f9926bd469b19e5f0513f47173b2738fc01a57082d7abb" -} diff --git a/.sqlx/query-ec22a8cc89e480c403a239eac44288e144d83364129491de6156760616666d3d.json b/.sqlx/query-ec22a8cc89e480c403a239eac44288e144d83364129491de6156760616666d3d.json deleted file mode 100644 index 8cc56b0..0000000 --- a/.sqlx/query-ec22a8cc89e480c403a239eac44288e144d83364129491de6156760616666d3d.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM external_identities WHERE id = $1 AND did = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [] - }, - "hash": "ec22a8cc89e480c403a239eac44288e144d83364129491de6156760616666d3d" -} diff --git a/.sqlx/query-f26c13023b47b908ec96da2e6b8bf8b34ca6a2246c20fc96f76f0e95530762a7.json b/.sqlx/query-f26c13023b47b908ec96da2e6b8bf8b34ca6a2246c20fc96f76f0e95530762a7.json deleted file mode 100644 index e8c8434..0000000 --- a/.sqlx/query-f26c13023b47b908ec96da2e6b8bf8b34ca6a2246c20fc96f76f0e95530762a7.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT email FROM users WHERE did = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "email", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true - ] - }, - "hash": "f26c13023b47b908ec96da2e6b8bf8b34ca6a2246c20fc96f76f0e95530762a7" -} diff --git a/.sqlx/query-f29da3bdfbbc547b339b4cdb059fac26435b0feec65cf1c56f851d1c4d6b1814.json b/.sqlx/query-f29da3bdfbbc547b339b4cdb059fac26435b0feec65cf1c56f851d1c4d6b1814.json deleted file mode 100644 index db8bde6..0000000 --- a/.sqlx/query-f29da3bdfbbc547b339b4cdb059fac26435b0feec65cf1c56f851d1c4d6b1814.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET is_admin = TRUE WHERE did = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "f29da3bdfbbc547b339b4cdb059fac26435b0feec65cf1c56f851d1c4d6b1814" -} diff --git a/.sqlx/query-f71428b1ce982504cd531937131d49196ec092b4d13e9ae7dcdaedfe98de5a70.json b/.sqlx/query-f71428b1ce982504cd531937131d49196ec092b4d13e9ae7dcdaedfe98de5a70.json deleted file mode 100644 index be0a807..0000000 --- a/.sqlx/query-f71428b1ce982504cd531937131d49196ec092b4d13e9ae7dcdaedfe98de5a70.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET backup_enabled = $1 WHERE did = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Bool", - "Text" - ] - }, - "nullable": [] - }, - "hash": "f71428b1ce982504cd531937131d49196ec092b4d13e9ae7dcdaedfe98de5a70" -} diff --git a/.sqlx/query-f7af28963099aec12cf1d4f8a9a03699bb3a90f39bc9c4c0f738a37827e8f382.json b/.sqlx/query-f7af28963099aec12cf1d4f8a9a03699bb3a90f39bc9c4c0f738a37827e8f382.json deleted file mode 100644 index 32320a7..0000000 --- a/.sqlx/query-f7af28963099aec12cf1d4f8a9a03699bb3a90f39bc9c4c0f738a37827e8f382.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "password_reset_code", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "password_reset_code_expires_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - true, - true - ] - }, - "hash": "f7af28963099aec12cf1d4f8a9a03699bb3a90f39bc9c4c0f738a37827e8f382" -} diff --git a/.sqlx/query-f85f8d49bbd2d5e048bd8c29081aef5b8097e2384793e85df72eeeb858b7c532.json b/.sqlx/query-f85f8d49bbd2d5e048bd8c29081aef5b8097e2384793e85df72eeeb858b7c532.json deleted file mode 100644 index 1cbc9fd..0000000 --- a/.sqlx/query-f85f8d49bbd2d5e048bd8c29081aef5b8097e2384793e85df72eeeb858b7c532.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM account_backups WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [] - }, - "hash": "f85f8d49bbd2d5e048bd8c29081aef5b8097e2384793e85df72eeeb858b7c532" -} diff --git a/Cargo.toml b/Cargo.toml index 02ae042..1cffd97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.4.3" +version = "0.4.4" edition = "2024" license = "AGPL-3.0-or-later" diff --git a/crates/tranquil-api/src/actor/preferences.rs b/crates/tranquil-api/src/actor/preferences.rs index 8cc7fdc..c674194 100644 --- a/crates/tranquil-api/src/actor/preferences.rs +++ b/crates/tranquil-api/src/actor/preferences.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Auth, NotTakendown, Permissive}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +7,9 @@ use axum::{ use chrono::{Datelike, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Auth, NotTakendown, Permissive}; +use tranquil_pds::state::AppState; const APP_BSKY_NAMESPACE: &str = "app.bsky"; const MAX_PREFERENCES_COUNT: usize = 100; diff --git a/crates/tranquil-api/src/admin/account/delete.rs b/crates/tranquil-api/src/admin/account/delete.rs index e0c128c..0e783ad 100644 --- a/crates/tranquil-api/src/admin/account/delete.rs +++ b/crates/tranquil-api/src/admin/account/delete.rs @@ -1,8 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Did; use axum::{ Json, extract::State, @@ -10,6 +5,11 @@ use axum::{ }; use serde::Deserialize; use tracing::warn; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; #[derive(Deserialize)] pub struct DeleteAccountInput { diff --git a/crates/tranquil-api/src/admin/account/email.rs b/crates/tranquil-api/src/admin/account/email.rs index afa16f9..f78a15c 100644 --- a/crates/tranquil-api/src/admin/account/email.rs +++ b/crates/tranquil-api/src/admin/account/email.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Did; use axum::{ Json, extract::State, @@ -10,6 +6,10 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use tracing::warn; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/admin/account/info.rs b/crates/tranquil-api/src/admin/account/info.rs index 83cb6b4..b19d42e 100644 --- a/crates/tranquil-api/src/admin/account/info.rs +++ b/crates/tranquil-api/src/admin/account/info.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle}; use axum::{ Json, extract::{Query, RawQuery, State}, @@ -10,6 +6,10 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle}; #[derive(Deserialize)] pub struct GetAccountInfoParams { @@ -196,10 +196,11 @@ pub async fn get_account_infos( _auth: Auth, RawQuery(raw_query): RawQuery, ) -> Result { - let dids: Vec = tranquil_pds::util::parse_repeated_query_param(raw_query.as_deref(), "dids") - .into_iter() - .filter(|d| !d.is_empty()) - .collect(); + let dids: Vec = + tranquil_pds::util::parse_repeated_query_param(raw_query.as_deref(), "dids") + .into_iter() + .filter(|d| !d.is_empty()) + .collect(); if dids.is_empty() { return Err(ApiError::InvalidRequest("dids is required".into())); diff --git a/crates/tranquil-api/src/admin/account/search.rs b/crates/tranquil-api/src/admin/account/search.rs index 554944d..96cab7c 100644 --- a/crates/tranquil-api/src/admin/account/search.rs +++ b/crates/tranquil-api/src/admin/account/search.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle}; use axum::{ Json, extract::{Query, State}, @@ -9,6 +5,10 @@ use axum::{ response::{IntoResponse, Response}, }; use serde::{Deserialize, Serialize}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle}; #[derive(Deserialize)] pub struct SearchAccountsParams { diff --git a/crates/tranquil-api/src/admin/account/update.rs b/crates/tranquil-api/src/admin/account/update.rs index 411c823..5953176 100644 --- a/crates/tranquil-api/src/admin/account/update.rs +++ b/crates/tranquil-api/src/admin/account/update.rs @@ -1,8 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle, PlainPassword}; use axum::{ Json, extract::State, @@ -10,6 +5,11 @@ use axum::{ }; use serde::Deserialize; use tracing::{error, warn}; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle, PlainPassword}; #[derive(Deserialize)] pub struct UpdateAccountEmailInput { diff --git a/crates/tranquil-api/src/admin/config.rs b/crates/tranquil-api/src/admin/config.rs index c27fff1..4c20ca8 100644 --- a/crates/tranquil-api/src/admin/config.rs +++ b/crates/tranquil-api/src/admin/config.rs @@ -1,9 +1,9 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; use axum::{Json, extract::State}; use serde::{Deserialize, Serialize}; use tracing::{error, warn}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; use tranquil_types::CidLink; #[derive(Serialize)] diff --git a/crates/tranquil-api/src/admin/invite.rs b/crates/tranquil-api/src/admin/invite.rs index 4faa019..9079ef0 100644 --- a/crates/tranquil-api/src/admin/invite.rs +++ b/crates/tranquil-api/src/admin/invite.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::{Query, State}, @@ -11,6 +7,10 @@ use axum::{ use serde::{Deserialize, Serialize}; use tracing::error; use tranquil_db_traits::InviteCodeSortOrder; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/admin/server_stats.rs b/crates/tranquil-api/src/admin/server_stats.rs index 7004290..6f26c2b 100644 --- a/crates/tranquil-api/src/admin/server_stats.rs +++ b/crates/tranquil-api/src/admin/server_stats.rs @@ -1,12 +1,12 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, response::{IntoResponse, Response}, }; use serde::Serialize; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; #[derive(Serialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/admin/status.rs b/crates/tranquil-api/src/admin/status.rs index cb516c2..49570a8 100644 --- a/crates/tranquil-api/src/admin/status.rs +++ b/crates/tranquil-api/src/admin/status.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Admin, Auth}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{CidLink, Did}; use axum::{ Json, extract::{Query, State}, @@ -11,6 +7,10 @@ use axum::{ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{error, warn}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Admin, Auth}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{CidLink, Did}; #[derive(Deserialize)] pub struct GetSubjectStatusParams { diff --git a/crates/tranquil-api/src/age_assurance.rs b/crates/tranquil-api/src/age_assurance.rs index d3e4b6d..8c8cfba 100644 --- a/crates/tranquil-api/src/age_assurance.rs +++ b/crates/tranquil-api/src/age_assurance.rs @@ -1,5 +1,3 @@ -use tranquil_pds::auth::{AccountRequirement, extract_auth_token_from_header, validate_token_with_dpop}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -7,6 +5,10 @@ use axum::{ response::{IntoResponse, Response}, }; use serde_json::json; +use tranquil_pds::auth::{ + AccountRequirement, extract_auth_token_from_header, validate_token_with_dpop, +}; +use tranquil_pds::state::AppState; pub async fn get_state(State(state): State, headers: HeaderMap) -> Response { let created_at = get_account_created_at(&state, &headers).await; diff --git a/crates/tranquil-api/src/backup.rs b/crates/tranquil-api/src/backup.rs deleted file mode 100644 index fd7fbd3..0000000 --- a/crates/tranquil-api/src/backup.rs +++ /dev/null @@ -1,766 +0,0 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::api::{EmptyResponse, EnabledResponse}; -use tranquil_pds::auth::{Active, Auth}; -use tranquil_pds::scheduled::generate_full_backup; -use tranquil_pds::state::AppState; -use tranquil_pds::storage::{BackupStorage, backup_retention_count}; -use anyhow::Context; -use axum::{ - Json, - extract::{Query, State}, - http::StatusCode, - response::{IntoResponse, Response}, -}; -use cid::Cid; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use std::str::FromStr; -use tracing::{error, info, warn}; -use tranquil_db::{BackupRepository, OldBackupInfo}; - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BackupInfo { - pub id: String, - pub repo_rev: String, - pub repo_root_cid: String, - pub block_count: i32, - pub size_bytes: i64, - pub created_at: String, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ListBackupsOutput { - pub backups: Vec, - pub backup_enabled: bool, -} - -pub async fn list_backups( - State(state): State, - auth: Auth, -) -> Result { - let (user_id, backup_enabled) = match state.backup_repo.get_user_backup_status(&auth.did).await - { - Ok(Some(status)) => status, - Ok(None) => { - return Ok(ApiError::AccountNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching user: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - let backups = match state.backup_repo.list_backups_for_user(user_id).await { - Ok(rows) => rows, - Err(e) => { - error!("DB error fetching backups: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - let backup_list: Vec = backups - .into_iter() - .map(|b| BackupInfo { - id: b.id.to_string(), - repo_rev: b.repo_rev, - repo_root_cid: b.repo_root_cid, - block_count: b.block_count, - size_bytes: b.size_bytes, - created_at: b.created_at.to_rfc3339(), - }) - .collect(); - - Ok(( - StatusCode::OK, - Json(ListBackupsOutput { - backups: backup_list, - backup_enabled, - }), - ) - .into_response()) -} - -#[derive(Deserialize)] -pub struct GetBackupQuery { - pub id: String, -} - -pub async fn get_backup( - State(state): State, - auth: Auth, - Query(query): Query, -) -> Result { - let backup_id = match uuid::Uuid::parse_str(&query.id) { - Ok(id) => id, - Err(_) => { - return Ok(ApiError::InvalidRequest("Invalid backup ID".into()).into_response()); - } - }; - - let backup_info = match state - .backup_repo - .get_backup_storage_info(backup_id, &auth.did) - .await - { - Ok(Some(b)) => b, - Ok(None) => { - return Ok(ApiError::BackupNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching backup: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - let backup_storage = match state.backup_storage.as_ref() { - Some(storage) => storage, - None => { - return Ok(ApiError::BackupsDisabled.into_response()); - } - }; - - let car_bytes = match backup_storage.get_backup(&backup_info.storage_key).await { - Ok(bytes) => bytes, - Err(e) => { - error!("Failed to fetch backup from storage: {:?}", e); - return Ok( - ApiError::InternalError(Some("Failed to retrieve backup".into())).into_response(), - ); - } - }; - - Ok(( - StatusCode::OK, - [ - (axum::http::header::CONTENT_TYPE, "application/vnd.ipld.car"), - ( - axum::http::header::CONTENT_DISPOSITION, - &format!("attachment; filename=\"{}.car\"", backup_info.repo_rev), - ), - ], - car_bytes, - ) - .into_response()) -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateBackupOutput { - pub id: String, - pub repo_rev: String, - pub size_bytes: i64, - pub block_count: i32, -} - -pub async fn create_backup( - State(state): State, - auth: Auth, -) -> Result { - let backup_storage = match state.backup_storage.as_ref() { - Some(storage) => storage, - None => { - return Ok(ApiError::BackupsDisabled.into_response()); - } - }; - - let user = match state.backup_repo.get_user_for_backup(&auth.did).await { - Ok(Some(u)) => u, - Ok(None) => { - return Ok(ApiError::AccountNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching user: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - if user.deactivated_at.is_some() { - return Ok(ApiError::AccountDeactivated.into_response()); - } - - let repo_rev = match &user.repo_rev { - Some(rev) => rev.clone(), - None => { - return Ok(ApiError::RepoNotReady.into_response()); - } - }; - - let head_cid = match Cid::from_str(&user.repo_root_cid) { - Ok(c) => c, - Err(_) => { - return Ok( - ApiError::InternalError(Some("Invalid repo root CID".into())).into_response(), - ); - } - }; - - let car_bytes = match generate_full_backup( - state.repo_repo.as_ref(), - &state.block_store, - user.id, - &head_cid, - ) - .await - { - Ok(bytes) => bytes, - Err(e) => { - error!("Failed to generate CAR: {:?}", e); - return Ok( - ApiError::InternalError(Some("Failed to generate backup".into())).into_response(), - ); - } - }; - - let block_count = tranquil_pds::scheduled::count_car_blocks(&car_bytes); - let size_bytes = i64::try_from(car_bytes.len()).unwrap_or(i64::MAX); - - let storage_key = match backup_storage - .put_backup(&user.did, &repo_rev, &car_bytes) - .await - { - Ok(key) => key, - Err(e) => { - error!("Failed to upload backup: {:?}", e); - return Ok( - ApiError::InternalError(Some("Failed to store backup".into())).into_response(), - ); - } - }; - - let backup_id = match state - .backup_repo - .insert_backup( - user.id, - &storage_key, - &user.repo_root_cid, - &repo_rev, - block_count, - size_bytes, - ) - .await - { - Ok(id) => id, - Err(e) => { - error!("DB error inserting backup: {:?}", e); - if let Err(rollback_err) = backup_storage.delete_backup(&storage_key).await { - error!( - storage_key = %storage_key, - error = %rollback_err, - "Failed to rollback orphaned backup from S3" - ); - } - return Ok( - ApiError::InternalError(Some("Failed to record backup".into())).into_response(), - ); - } - }; - - info!( - did = %user.did, - rev = %repo_rev, - size_bytes, - "Created manual backup" - ); - - let retention = backup_retention_count(); - if let Err(e) = cleanup_old_backups( - state.backup_repo.as_ref(), - backup_storage.as_ref(), - user.id, - retention, - ) - .await - { - warn!(did = %user.did, error = %e, "Failed to cleanup old backups after manual backup"); - } - - Ok(( - StatusCode::OK, - Json(CreateBackupOutput { - id: backup_id.to_string(), - repo_rev, - size_bytes, - block_count, - }), - ) - .into_response()) -} - -async fn cleanup_old_backups( - backup_repo: &dyn BackupRepository, - backup_storage: &dyn BackupStorage, - user_id: uuid::Uuid, - retention_count: u32, -) -> anyhow::Result<()> { - let old_backups: Vec = backup_repo - .get_old_backups(user_id, i64::from(retention_count)) - .await - .context("DB error fetching old backups")?; - - for backup in old_backups { - if let Err(e) = backup_storage.delete_backup(&backup.storage_key).await { - warn!( - storage_key = %backup.storage_key, - error = %e, - "Failed to delete old backup from storage, skipping DB cleanup to avoid orphan" - ); - continue; - } - - backup_repo - .delete_backup(backup.id) - .await - .context("Failed to delete old backup record")?; - } - - Ok(()) -} - -#[derive(Deserialize)] -pub struct DeleteBackupQuery { - pub id: String, -} - -pub async fn delete_backup( - State(state): State, - auth: Auth, - Query(query): Query, -) -> Result { - let backup_id = match uuid::Uuid::parse_str(&query.id) { - Ok(id) => id, - Err(_) => { - return Ok(ApiError::InvalidRequest("Invalid backup ID".into()).into_response()); - } - }; - - let backup = match state - .backup_repo - .get_backup_for_deletion(backup_id, &auth.did) - .await - { - Ok(Some(b)) => b, - Ok(None) => { - return Ok(ApiError::BackupNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching backup: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - if backup.deactivated_at.is_some() { - return Ok(ApiError::AccountDeactivated.into_response()); - } - - if let Some(backup_storage) = state.backup_storage.as_ref() - && let Err(e) = backup_storage.delete_backup(&backup.storage_key).await - { - warn!( - storage_key = %backup.storage_key, - error = %e, - "Failed to delete backup from storage (continuing anyway)" - ); - } - - if let Err(e) = state.backup_repo.delete_backup(backup.id).await { - error!("DB error deleting backup: {:?}", e); - return Ok(ApiError::InternalError(Some("Failed to delete backup".into())).into_response()); - } - - info!(did = %auth.did, backup_id = %backup_id, "Deleted backup"); - - Ok(EmptyResponse::ok().into_response()) -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SetBackupEnabledInput { - pub enabled: bool, -} - -pub async fn set_backup_enabled( - State(state): State, - auth: Auth, - Json(input): Json, -) -> Result { - let deactivated_at = match state - .backup_repo - .get_user_deactivated_status(&auth.did) - .await - { - Ok(Some(status)) => status, - Ok(None) => { - return Ok(ApiError::AccountNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching user: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - if deactivated_at.is_some() { - return Ok(ApiError::AccountDeactivated.into_response()); - } - - if let Err(e) = state - .backup_repo - .update_backup_enabled(&auth.did, input.enabled) - .await - { - error!("DB error updating backup_enabled: {:?}", e); - return Ok( - ApiError::InternalError(Some("Failed to update setting".into())).into_response(), - ); - } - - info!(did = %auth.did, enabled = input.enabled, "Updated backup_enabled setting"); - - Ok(EnabledResponse::response(input.enabled).into_response()) -} - -pub async fn export_blobs( - State(state): State, - auth: Auth, -) -> Result { - let user_id = match state.backup_repo.get_user_id_by_did(&auth.did).await { - Ok(Some(id)) => id, - Ok(None) => { - return Ok(ApiError::AccountNotFound.into_response()); - } - Err(e) => { - error!("DB error fetching user: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - let blobs = match state.backup_repo.get_blobs_for_export(user_id).await { - Ok(rows) => rows, - Err(e) => { - error!("DB error fetching blobs: {:?}", e); - return Ok(ApiError::InternalError(None).into_response()); - } - }; - - if blobs.is_empty() { - return Ok(( - StatusCode::OK, - [ - (axum::http::header::CONTENT_TYPE, "application/zip"), - ( - axum::http::header::CONTENT_DISPOSITION, - "attachment; filename=\"blobs.zip\"", - ), - ], - Vec::::new(), - ) - .into_response()); - } - - let mut zip_buffer = std::io::Cursor::new(Vec::new()); - { - let mut zip = zip::ZipWriter::new(&mut zip_buffer); - - let options = zip::write::SimpleFileOptions::default() - .compression_method(zip::CompressionMethod::Deflated); - - let mut exported: Vec = Vec::new(); - let mut skipped: Vec = Vec::new(); - - for blob in &blobs { - let blob_data = match state.blob_store.get(&blob.storage_key).await { - Ok(data) => data, - Err(e) => { - warn!(cid = %blob.cid, error = %e, "Failed to fetch blob, skipping"); - skipped.push(json!({ - "cid": blob.cid, - "mimeType": blob.mime_type, - "reason": "fetch_failed" - })); - continue; - } - }; - - let extension = mime_to_extension(&blob.mime_type); - let filename = format!("{}{}", blob.cid, extension); - - if let Err(e) = zip.start_file(&filename, options) { - warn!(filename = %filename, error = %e, "Failed to start zip file entry"); - skipped.push(json!({ - "cid": blob.cid, - "mimeType": blob.mime_type, - "reason": "zip_entry_failed" - })); - continue; - } - - if let Err(e) = std::io::Write::write_all(&mut zip, &blob_data) { - warn!(filename = %filename, error = %e, "Failed to write blob to zip"); - skipped.push(json!({ - "cid": blob.cid, - "mimeType": blob.mime_type, - "reason": "write_failed" - })); - continue; - } - - exported.push(json!({ - "cid": blob.cid, - "filename": filename, - "mimeType": blob.mime_type, - "sizeBytes": blob_data.len() - })); - } - - let manifest = json!({ - "exportedAt": chrono::Utc::now().to_rfc3339(), - "totalBlobs": blobs.len(), - "exportedCount": exported.len(), - "skippedCount": skipped.len(), - "exported": exported, - "skipped": skipped - }); - - if zip.start_file("manifest.json", options).is_ok() { - let _ = std::io::Write::write_all( - &mut zip, - serde_json::to_string_pretty(&manifest) - .unwrap_or_else(|_| "{}".to_string()) - .as_bytes(), - ); - } - - if let Err(e) = zip.finish() { - error!("Failed to finish zip: {:?}", e); - return Ok( - ApiError::InternalError(Some("Failed to create zip file".into())).into_response(), - ); - } - } - - let zip_bytes = zip_buffer.into_inner(); - - info!(did = %auth.did, blob_count = blobs.len(), size_bytes = zip_bytes.len(), "Exported blobs"); - - Ok(( - StatusCode::OK, - [ - (axum::http::header::CONTENT_TYPE, "application/zip"), - ( - axum::http::header::CONTENT_DISPOSITION, - "attachment; filename=\"blobs.zip\"", - ), - ], - zip_bytes, - ) - .into_response()) -} - -fn mime_to_extension(mime_type: &str) -> &'static str { - match mime_type { - "application/font-sfnt" => ".otf", - "application/font-tdpfr" => ".pfr", - "application/font-woff" => ".woff", - "application/gzip" => ".gz", - "application/json" => ".json", - "application/json5" => ".json5", - "application/jsonml+json" => ".jsonml", - "application/octet-stream" => ".bin", - "application/pdf" => ".pdf", - "application/zip" => ".zip", - "audio/aac" => ".aac", - "audio/ac3" => ".ac3", - "audio/aiff" => ".aiff", - "audio/annodex" => ".axa", - "audio/audible" => ".aa", - "audio/basic" => ".au", - "audio/flac" => ".flac", - "audio/m4a" => ".m4a", - "audio/m4b" => ".m4b", - "audio/m4p" => ".m4p", - "audio/mid" => ".mid", - "audio/midi" => ".midi", - "audio/mp4" => ".mp4a", - "audio/mpeg" => ".mp3", - "audio/ogg" => ".ogg", - "audio/s3m" => ".s3m", - "audio/scpls" => ".pls", - "audio/silk" => ".sil", - "audio/vnd.audible.aax" => ".aax", - "audio/vnd.dece.audio" => ".uva", - "audio/vnd.digital-winds" => ".eol", - "audio/vnd.dlna.adts" => ".adt", - "audio/vnd.dra" => ".dra", - "audio/vnd.dts" => ".dts", - "audio/vnd.dts.hd" => ".dtshd", - "audio/vnd.lucent.voice" => ".lvp", - "audio/vnd.ms-playready.media.pya" => ".pya", - "audio/vnd.nuera.ecelp4800" => ".ecelp4800", - "audio/vnd.nuera.ecelp7470" => ".ecelp7470", - "audio/vnd.nuera.ecelp9600" => ".ecelp9600", - "audio/vnd.rip" => ".rip", - "audio/wav" => ".wav", - "audio/webm" => ".weba", - "audio/x-caf" => ".caf", - "audio/x-gsm" => ".gsm", - "audio/x-m4r" => ".m4r", - "audio/x-matroska" => ".mka", - "audio/x-mpegurl" => ".m3u", - "audio/x-ms-wax" => ".wax", - "audio/x-ms-wma" => ".wma", - "audio/x-pn-realaudio" => ".ra", - "audio/x-pn-realaudio-plugin" => ".rpm", - "audio/x-sd2" => ".sd2", - "audio/x-smd" => ".smd", - "audio/xm" => ".xm", - "font/collection" => ".ttc", - "font/ttf" => ".ttf", - "font/woff" => ".woff", - "font/woff2" => ".woff2", - "image/apng" => ".apng", - "image/avif" => ".avif", - "image/avif-sequence" => ".avifs", - "image/bmp" => ".bmp", - "image/cgm" => ".cgm", - "image/cis-cod" => ".cod", - "image/g3fax" => ".g3", - "image/gif" => ".gif", - "image/heic" => ".heic", - "image/heic-sequence" => ".heics", - "image/heif" => ".heif", - "image/heif-sequence" => ".heifs", - "image/ief" => ".ief", - "image/jp2" => ".jp2", - "image/jpeg" => ".jpg", - "image/jpm" => ".jpm", - "image/jpx" => ".jpf", - "image/jxl" => ".jxl", - "image/ktx" => ".ktx", - "image/pict" => ".pct", - "image/png" => ".png", - "image/prs.btif" => ".btif", - "image/qoi" => ".qoi", - "image/sgi" => ".sgi", - "image/svg+xml" => ".svg", - "image/tiff" => ".tiff", - "image/vnd.dece.graphic" => ".uvg", - "image/vnd.djvu" => ".djv", - "image/vnd.fastbidsheet" => ".fbs", - "image/vnd.fpx" => ".fpx", - "image/vnd.fst" => ".fst", - "image/vnd.fujixerox.edmics-mmr" => ".mmr", - "image/vnd.fujixerox.edmics-rlc" => ".rlc", - "image/vnd.ms-modi" => ".mdi", - "image/vnd.ms-photo" => ".wdp", - "image/vnd.net-fpx" => ".npx", - "image/vnd.radiance" => ".hdr", - "image/vnd.rn-realflash" => ".rf", - "image/vnd.wap.wbmp" => ".wbmp", - "image/vnd.xiff" => ".xif", - "image/webp" => ".webp", - "image/x-3ds" => ".3ds", - "image/x-adobe-dng" => ".dng", - "image/x-canon-cr2" => ".cr2", - "image/x-canon-cr3" => ".cr3", - "image/x-canon-crw" => ".crw", - "image/x-cmu-raster" => ".ras", - "image/x-cmx" => ".cmx", - "image/x-epson-erf" => ".erf", - "image/x-freehand" => ".fh", - "image/x-fuji-raf" => ".raf", - "image/x-icon" => ".ico", - "image/x-jg" => ".art", - "image/x-jng" => ".jng", - "image/x-kodak-dcr" => ".dcr", - "image/x-kodak-k25" => ".k25", - "image/x-kodak-kdc" => ".kdc", - "image/x-macpaint" => ".mac", - "image/x-minolta-mrw" => ".mrw", - "image/x-mrsid-image" => ".sid", - "image/x-nikon-nef" => ".nef", - "image/x-nikon-nrw" => ".nrw", - "image/x-olympus-orf" => ".orf", - "image/x-panasonic-rw" => ".raw", - "image/x-panasonic-rw2" => ".rw2", - "image/x-pentax-pef" => ".pef", - "image/x-portable-anymap" => ".pnm", - "image/x-portable-bitmap" => ".pbm", - "image/x-portable-graymap" => ".pgm", - "image/x-portable-pixmap" => ".ppm", - "image/x-qoi" => ".qoi", - "image/x-quicktime" => ".qti", - "image/x-rgb" => ".rgb", - "image/x-sigma-x3f" => ".x3f", - "image/x-sony-arw" => ".arw", - "image/x-sony-sr2" => ".sr2", - "image/x-sony-srf" => ".srf", - "image/x-tga" => ".tga", - "image/x-xbitmap" => ".xbm", - "image/x-xcf" => ".xcf", - "image/x-xpixmap" => ".xpm", - "image/x-xwindowdump" => ".xwd", - "model/gltf+json" => ".gltf", - "model/gltf-binary" => ".glb", - "model/iges" => ".igs", - "model/mesh" => ".msh", - "model/vnd.collada+xml" => ".dae", - "model/vnd.gdl" => ".gdl", - "model/vnd.gtw" => ".gtw", - "model/vnd.vtu" => ".vtu", - "model/vrml" => ".vrml", - "model/x3d+binary" => ".x3db", - "model/x3d+vrml" => ".x3dv", - "model/x3d+xml" => ".x3d", - "text/css" => ".css", - "text/html" => ".html", - "text/plain" => ".txt", - "video/3gpp" => ".3gp", - "video/3gpp2" => ".3g2", - "video/annodex" => ".axv", - "video/divx" => ".divx", - "video/h261" => ".h261", - "video/h263" => ".h263", - "video/h264" => ".h264", - "video/jpeg" => ".jpgv", - "video/jpm" => ".jpgm", - "video/mj2" => ".mj2", - "video/mp4" => ".mp4", - "video/mpeg" => ".mpg", - "video/ogg" => ".ogv", - "video/quicktime" => ".mov", - "video/vnd.dece.hd" => ".uvh", - "video/vnd.dece.mobile" => ".uvm", - "video/vnd.dece.pd" => ".uvp", - "video/vnd.dece.sd" => ".uvs", - "video/vnd.dece.video" => ".uvv", - "video/vnd.dlna.mpeg-tts" => ".ts", - "video/vnd.dvb.file" => ".dvb", - "video/vnd.fvt" => ".fvt", - "video/vnd.mpegurl" => ".m4u", - "video/vnd.ms-playready.media.pyv" => ".pyv", - "video/vnd.uvvu.mp4" => ".uvu", - "video/vnd.vivo" => ".viv", - "video/webm" => ".webm", - "video/x-dv" => ".dv", - "video/x-f4v" => ".f4v", - "video/x-fli" => ".fli", - "video/x-flv" => ".flv", - "video/x-ivf" => ".ivf", - "video/x-la-asf" => ".lsf", - "video/x-m4v" => ".m4v", - "video/x-matroska" => ".mkv", - "video/x-mng" => ".mng", - "video/x-ms-asf" => ".asf", - "video/x-ms-vob" => ".vob", - "video/x-ms-wm" => ".wm", - "video/x-ms-wmp" => ".wmp", - "video/x-ms-wmv" => ".wmv", - "video/x-ms-wmx" => ".wmx", - "video/x-ms-wvx" => ".wvx", - "video/x-msvideo" => ".avi", - "video/x-sgi-movie" => ".movie", - "video/x-smv" => ".smv", - _ => ".bin", - } -} diff --git a/crates/tranquil-api/src/delegation.rs b/crates/tranquil-api/src/delegation.rs index 7fbd8c5..e109838 100644 --- a/crates/tranquil-api/src/delegation.rs +++ b/crates/tranquil-api/src/delegation.rs @@ -1,13 +1,4 @@ use crate::identity::provision::{create_plc_did, init_genesis_repo}; -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Active, Auth}; -use tranquil_pds::delegation::{ - DelegationActionType, SCOPE_PRESETS, ValidatedDelegationScope, verify_can_add_controllers, - verify_can_control_accounts, -}; -use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle}; use axum::{ Json, extract::{Query, State}, @@ -17,6 +8,15 @@ use axum::{ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{error, info, warn}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Active, Auth}; +use tranquil_pds::delegation::{ + DelegationActionType, SCOPE_PRESETS, ValidatedDelegationScope, verify_can_add_controllers, + verify_can_control_accounts, +}; +use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle}; pub async fn list_controllers( State(state): State, @@ -70,33 +70,32 @@ pub async fn add_controller( .await .ok_or(ApiError::ControllerNotFound)?; - if !resolved.is_local { - if let Some(ref pds_url) = resolved.pds_url { - if !pds_url.starts_with("https://") { - return Ok( - ApiError::InvalidDelegation("Controller PDS must use HTTPS".into()) - .into_response(), - ); + if !resolved.is_local + && let Some(ref pds_url) = resolved.pds_url + { + if !pds_url.starts_with("https://") { + return Ok( + ApiError::InvalidDelegation("Controller PDS must use HTTPS".into()).into_response(), + ); + } + match state + .cross_pds_oauth + .check_remote_is_delegated(pds_url, input.controller_did.as_str()) + .await + { + Some(true) => { + return Ok(ApiError::InvalidDelegation( + "Cannot add a delegated account from another PDS as a controller".into(), + ) + .into_response()); } - match state - .cross_pds_oauth - .check_remote_is_delegated(pds_url, input.controller_did.as_str()) - .await - { - Some(true) => { - return Ok(ApiError::InvalidDelegation( - "Cannot add a delegated account from another PDS as a controller".into(), - ) - .into_response()); - } - Some(false) => {} - None => { - warn!( - controller = %input.controller_did, - pds = %pds_url, - "Could not verify remote controller delegation status" - ); - } + Some(false) => {} + None => { + warn!( + controller = %input.controller_did, + pds = %pds_url, + "Could not verify remote controller delegation status" + ); } } } @@ -106,12 +105,17 @@ pub async fn add_controller( Err(response) => return Ok(response), }; - if resolved.is_local { - if state.delegation_repo.is_delegated_account(&input.controller_did).await.unwrap_or(false) { - return Ok(ApiError::InvalidDelegation( - "Cannot add a controlled account as a controller".into(), - ).into_response()); - } + if resolved.is_local + && state + .delegation_repo + .is_delegated_account(&input.controller_did) + .await + .unwrap_or(false) + { + return Ok(ApiError::InvalidDelegation( + "Cannot add a controlled account as a controller".into(), + ) + .into_response()); } match state @@ -511,7 +515,9 @@ pub async fn resolve_controller( let identifier = params.identifier.trim().trim_start_matches('@'); let did: Did = if identifier.starts_with("did:") { - identifier.parse().map_err(|_| ApiError::ControllerNotFound)? + identifier + .parse() + .map_err(|_| ApiError::ControllerNotFound)? } else { let local_handle: Option = identifier.parse().ok(); let local_user = match local_handle { @@ -534,4 +540,3 @@ pub async fn resolve_controller( Ok(Json(resolved).into_response()) } - diff --git a/crates/tranquil-api/src/identity/account.rs b/crates/tranquil-api/src/identity/account.rs index 5b62352..0248eda 100644 --- a/crates/tranquil-api/src/identity/account.rs +++ b/crates/tranquil-api/src/identity/account.rs @@ -1,10 +1,4 @@ use super::did::verify_did_web; -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{ServiceTokenVerifier, extract_auth_token_from_header, is_service_token}; -use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle, PlainPassword}; -use tranquil_pds::validation::validate_password; use axum::{ Json, extract::State, @@ -17,6 +11,12 @@ use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{debug, error, info, warn}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{ServiceTokenVerifier, extract_auth_token_from_header, is_service_token}; +use tranquil_pds::rate_limit::{AccountCreationLimit, RateLimited}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle, PlainPassword}; +use tranquil_pds::validation::validate_password; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] @@ -335,14 +335,16 @@ pub async fn create_account( .into_response(); } }; - let access_meta = - match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) { - Ok(m) => m, - Err(e) => { - error!("Error creating access token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; + let access_meta = match tranquil_pds::auth::create_access_token_with_metadata( + &did, + &secret_key_bytes, + ) { + Ok(m) => m, + Err(e) => { + error!("Error creating access token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata( &did, &secret_key_bytes, @@ -646,8 +648,9 @@ pub async fn create_account( verification_channel, recipient, ); - let formatted_token = - tranquil_pds::auth::verification_token::format_token_for_display(&verification_token); + let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display( + &verification_token, + ); if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_signup_verification( state.user_repo.as_ref(), state.infra_repo.as_ref(), @@ -666,9 +669,12 @@ pub async fn create_account( } } } else if let Some(ref user_email) = email { - let token = - tranquil_pds::auth::verification_token::generate_migration_token(&did_for_commit, user_email); - let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token); + let token = tranquil_pds::auth::verification_token::generate_migration_token( + &did_for_commit, + user_email, + ); + let formatted_token = + tranquil_pds::auth::verification_token::format_token_for_display(&token); if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_migration_verification( state.user_repo.as_ref(), state.infra_repo.as_ref(), @@ -683,14 +689,14 @@ pub async fn create_account( } } - let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) - { - Ok(m) => m, - Err(e) => { - error!("createAccount: Error creating access token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; + let access_meta = + match tranquil_pds::auth::create_access_token_with_metadata(&did, &secret_key_bytes) { + Ok(m) => m, + Err(e) => { + error!("createAccount: Error creating access token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&did, &secret_key_bytes) { Ok(m) => m, diff --git a/crates/tranquil-api/src/identity/did.rs b/crates/tranquil-api/src/identity/did.rs index 6c52e27..3405b58 100644 --- a/crates/tranquil-api/src/identity/did.rs +++ b/crates/tranquil-api/src/identity/did.rs @@ -1,12 +1,3 @@ -use tranquil_pds::api::{ApiError, DidResponse, EmptyResponse}; -use tranquil_pds::auth::{Auth, NotTakendown}; -use tranquil_pds::plc::signing_key_to_did_key; -use tranquil_pds::rate_limit::{ - HandleUpdateDailyLimit, HandleUpdateLimit, check_user_rate_limit_with_message, -}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Handle; -use tranquil_pds::util::get_header_str; use axum::{ Json, extract::{Path, Query, State}, @@ -19,6 +10,15 @@ use k256::elliptic_curve::sec1::ToEncodedPoint; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{error, warn}; +use tranquil_pds::api::{ApiError, DidResponse, EmptyResponse}; +use tranquil_pds::auth::{Auth, NotTakendown}; +use tranquil_pds::plc::signing_key_to_did_key; +use tranquil_pds::rate_limit::{ + HandleUpdateDailyLimit, HandleUpdateLimit, check_user_rate_limit_with_message, +}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Handle; +use tranquil_pds::util::get_header_str; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -503,7 +503,8 @@ pub async fn verify_did_web( ))?; let pds_endpoint = format!("https://{}", hostname); let has_valid_service = services.iter().any(|s| { - s["type"] == tranquil_pds::plc::ServiceType::Pds.as_str() && s["serviceEndpoint"] == pds_endpoint + s["type"] == tranquil_pds::plc::ServiceType::Pds.as_str() + && s["serviceEndpoint"] == pds_endpoint }); if !has_valid_service { return Err(DidWebVerifyError::PdsNotListed(pds_endpoint)); @@ -811,7 +812,8 @@ pub async fn update_plc_handle( Some(r) => r, None => return Ok(()), }; - let key_bytes = tranquil_pds::config::decrypt_key(&user_row.key_bytes, user_row.encryption_version)?; + let key_bytes = + tranquil_pds::config::decrypt_key(&user_row.key_bytes, user_row.encryption_version)?; let signing_key = k256::ecdsa::SigningKey::from_slice(&key_bytes)?; let plc_client = state.plc_client(); let last_op = plc_client.get_last_op(did).await?; diff --git a/crates/tranquil-api/src/identity/handle.rs b/crates/tranquil-api/src/identity/handle.rs index d2d5188..60c7e2a 100644 --- a/crates/tranquil-api/src/identity/handle.rs +++ b/crates/tranquil-api/src/identity/handle.rs @@ -1,10 +1,10 @@ -use tranquil_pds::rate_limit::{HandleVerificationLimit, RateLimited}; -use tranquil_pds::types::{Did, Handle}; use axum::{ Json, response::{IntoResponse, Response}, }; use serde::{Deserialize, Serialize}; +use tranquil_pds::rate_limit::{HandleVerificationLimit, RateLimited}; +use tranquil_pds::types::{Did, Handle}; #[derive(Deserialize)] pub struct VerifyHandleOwnershipInput { diff --git a/crates/tranquil-api/src/identity/plc/request.rs b/crates/tranquil-api/src/identity/plc/request.rs index 7192b71..600f54f 100644 --- a/crates/tranquil-api/src/identity/plc/request.rs +++ b/crates/tranquil-api/src/identity/plc/request.rs @@ -1,13 +1,13 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Auth, Permissive}; -use tranquil_pds::state::AppState; use axum::{ extract::State, response::{IntoResponse, Response}, }; use chrono::{Duration, Utc}; use tracing::{info, warn}; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Auth, Permissive}; +use tranquil_pds::state::AppState; fn generate_plc_token() -> String { tranquil_pds::util::generate_token_code() diff --git a/crates/tranquil-api/src/identity/plc/sign.rs b/crates/tranquil-api/src/identity/plc/sign.rs index 6f8d602..4c1fd0f 100644 --- a/crates/tranquil-api/src/identity/plc/sign.rs +++ b/crates/tranquil-api/src/identity/plc/sign.rs @@ -1,9 +1,3 @@ -use tranquil_pds::api::ApiError; -use tranquil_pds::api::error::DbResultExt; -use tranquil_pds::auth::{Auth, Permissive}; -use tranquil_pds::circuit_breaker::with_circuit_breaker; -use tranquil_pds::plc::{PlcError, PlcService, ServiceType, create_update_op, sign_operation}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -16,6 +10,12 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; use tracing::{error, info}; +use tranquil_pds::api::ApiError; +use tranquil_pds::api::error::DbResultExt; +use tranquil_pds::auth::{Auth, Permissive}; +use tranquil_pds::circuit_breaker::with_circuit_breaker; +use tranquil_pds::plc::{PlcError, PlcService, ServiceType, create_update_op, sign_operation}; +use tranquil_pds::state::AppState; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -86,11 +86,13 @@ pub async fn sign_plc_operation( .log_db_err("fetching user key")? .ok_or_else(|| ApiError::InternalError(Some("User signing key not found".into())))?; - let key_bytes = tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) - .map_err(|e| { - error!("Failed to decrypt user key: {}", e); - ApiError::InternalError(None) - })?; + let key_bytes = + tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version).map_err( + |e| { + error!("Failed to decrypt user key: {}", e); + ApiError::InternalError(None) + }, + )?; let signing_key = SigningKey::from_slice(&key_bytes).map_err(|e| { error!("Failed to create signing key: {:?}", e); diff --git a/crates/tranquil-api/src/identity/plc/submit.rs b/crates/tranquil-api/src/identity/plc/submit.rs index a863a5b..8dfa8a0 100644 --- a/crates/tranquil-api/src/identity/plc/submit.rs +++ b/crates/tranquil-api/src/identity/plc/submit.rs @@ -1,9 +1,3 @@ -use tranquil_pds::api::error::DbResultExt; -use tranquil_pds::api::{ApiError, EmptyResponse}; -use tranquil_pds::auth::{Auth, Permissive}; -use tranquil_pds::circuit_breaker::with_circuit_breaker; -use tranquil_pds::plc::{signing_key_to_did_key, validate_plc_operation}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -13,6 +7,12 @@ use k256::ecdsa::SigningKey; use serde::Deserialize; use serde_json::Value; use tracing::{error, info, warn}; +use tranquil_pds::api::error::DbResultExt; +use tranquil_pds::api::{ApiError, EmptyResponse}; +use tranquil_pds::auth::{Auth, Permissive}; +use tranquil_pds::circuit_breaker::with_circuit_breaker; +use tranquil_pds::plc::{signing_key_to_did_key, validate_plc_operation}; +use tranquil_pds::state::AppState; #[derive(Debug, Deserialize)] pub struct SubmitPlcOperationInput { @@ -57,11 +57,13 @@ pub async fn submit_plc_operation( .log_db_err("fetching user key")? .ok_or_else(|| ApiError::InternalError(Some("User signing key not found".into())))?; - let key_bytes = tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) - .map_err(|e| { - error!("Failed to decrypt user key: {}", e); - ApiError::InternalError(None) - })?; + let key_bytes = + tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version).map_err( + |e| { + error!("Failed to decrypt user key: {}", e); + ApiError::InternalError(None) + }, + )?; let signing_key = SigningKey::from_slice(&key_bytes).map_err(|e| { error!("Failed to create signing key: {:?}", e); diff --git a/crates/tranquil-api/src/identity/provision.rs b/crates/tranquil-api/src/identity/provision.rs index 2e43136..a863fee 100644 --- a/crates/tranquil-api/src/identity/provision.rs +++ b/crates/tranquil-api/src/identity/provision.rs @@ -1,11 +1,11 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::repo_ops::create_signed_commit; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Did; use jacquard_common::types::{integer::LimitedU32, string::Tid}; use jacquard_repo::{mst::Mst, storage::BlockStore}; use k256::ecdsa::SigningKey; use std::sync::Arc; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::repo_ops::create_signed_commit; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; pub struct PlcDidResult { pub did: Did, @@ -50,12 +50,16 @@ pub async fn submit_plc_genesis( .clone() .unwrap_or_else(|| tranquil_pds::plc::signing_key_to_did_key(signing_key)); - let genesis_result = - tranquil_pds::plc::create_genesis_operation(signing_key, &rotation_key, handle, &pds_endpoint) - .map_err(|e| { - tracing::error!("Error creating PLC genesis operation: {:?}", e); - ApiError::InternalError(Some("Failed to create PLC operation".into())) - })?; + let genesis_result = tranquil_pds::plc::create_genesis_operation( + signing_key, + &rotation_key, + handle, + &pds_endpoint, + ) + .map_err(|e| { + tracing::error!("Error creating PLC genesis operation: {:?}", e); + ApiError::InternalError(Some("Failed to create PLC operation".into())) + })?; state .plc_client() @@ -84,10 +88,11 @@ pub async fn init_genesis_repo( signing_key: &SigningKey, signing_key_bytes: &[u8], ) -> Result { - let encrypted_key_bytes = tranquil_pds::config::encrypt_key(signing_key_bytes).map_err(|e| { - tracing::error!("Error encrypting signing key: {:?}", e); - ApiError::InternalError(None) - })?; + let encrypted_key_bytes = + tranquil_pds::config::encrypt_key(signing_key_bytes).map_err(|e| { + tracing::error!("Error encrypting signing key: {:?}", e); + ApiError::InternalError(None) + })?; let mst = Mst::new(Arc::new(state.block_store.clone())); let mst_root = mst.persist().await.map_err(|e| { diff --git a/crates/tranquil-api/src/lib.rs b/crates/tranquil-api/src/lib.rs index 05269ab..d547fef 100644 --- a/crates/tranquil-api/src/lib.rs +++ b/crates/tranquil-api/src/lib.rs @@ -1,7 +1,6 @@ pub mod actor; pub mod admin; pub mod age_assurance; -pub mod backup; pub mod delegation; pub mod discord_webhook; pub mod identity; @@ -32,10 +31,7 @@ pub fn api_routes() -> axum::Router { "/com.atproto.server.createSession", post(server::create_session), ) - .route( - "/com.atproto.server.getSession", - get(server::get_session), - ) + .route("/com.atproto.server.getSession", get(server::get_session)) .route("/_account.listSessions", get(server::list_sessions)) .route("/_account.revokeSession", post(server::revoke_session)) .route( @@ -66,29 +62,14 @@ pub fn api_routes() -> axum::Router { "/com.atproto.identity.resolveHandle", get(identity::resolve_handle), ) - .route( - "/com.atproto.repo.createRecord", - post(repo::create_record), - ) + .route("/com.atproto.repo.createRecord", post(repo::create_record)) .route("/com.atproto.repo.putRecord", post(repo::put_record)) .route("/com.atproto.repo.getRecord", get(repo::get_record)) - .route( - "/com.atproto.repo.deleteRecord", - post(repo::delete_record), - ) - .route( - "/com.atproto.repo.listRecords", - get(repo::list_records), - ) - .route( - "/com.atproto.repo.describeRepo", - get(repo::describe_repo), - ) + .route("/com.atproto.repo.deleteRecord", post(repo::delete_record)) + .route("/com.atproto.repo.listRecords", get(repo::list_records)) + .route("/com.atproto.repo.describeRepo", get(repo::describe_repo)) .route("/com.atproto.repo.uploadBlob", post(repo::upload_blob)) - .route( - "/com.atproto.repo.applyWrites", - post(repo::apply_writes), - ) + .route("/com.atproto.repo.applyWrites", post(repo::apply_writes)) .route( "/com.atproto.server.checkAccountStatus", get(server::check_account_status), @@ -144,99 +125,315 @@ pub fn api_routes() -> axum::Router { .route("/_account.changePassword", post(server::change_password)) .route("/_account.removePassword", post(server::remove_password)) .route("/_account.setPassword", post(server::set_password)) - .route("/_account.getPasswordStatus", get(server::get_password_status)) + .route( + "/_account.getPasswordStatus", + get(server::get_password_status), + ) .route("/_account.getReauthStatus", get(server::get_reauth_status)) .route("/_account.reauthPassword", post(server::reauth_password)) .route("/_account.reauthTotp", post(server::reauth_totp)) - .route("/_account.reauthPasskeyStart", post(server::reauth_passkey_start)) - .route("/_account.reauthPasskeyFinish", post(server::reauth_passkey_finish)) - .route("/_account.getLegacyLoginPreference", get(server::get_legacy_login_preference)) - .route("/_account.updateLegacyLoginPreference", post(server::update_legacy_login_preference)) + .route( + "/_account.reauthPasskeyStart", + post(server::reauth_passkey_start), + ) + .route( + "/_account.reauthPasskeyFinish", + post(server::reauth_passkey_finish), + ) + .route( + "/_account.getLegacyLoginPreference", + get(server::get_legacy_login_preference), + ) + .route( + "/_account.updateLegacyLoginPreference", + post(server::update_legacy_login_preference), + ) .route("/_account.updateLocale", post(server::update_locale)) - .route("/_account.listTrustedDevices", get(server::list_trusted_devices)) - .route("/_account.revokeTrustedDevice", post(server::revoke_trusted_device)) - .route("/_account.updateTrustedDevice", post(server::update_trusted_device)) - .route("/_account.createPasskeyAccount", post(server::create_passkey_account)) - .route("/_account.startPasskeyRegistrationForSetup", post(server::start_passkey_registration_for_setup)) - .route("/_account.completePasskeySetup", post(server::complete_passkey_setup)) - .route("/_account.requestPasskeyRecovery", post(server::request_passkey_recovery)) - .route("/_account.recoverPasskeyAccount", post(server::recover_passkey_account)) - .route("/_account.updateDidDocument", post(server::update_did_document)) + .route( + "/_account.listTrustedDevices", + get(server::list_trusted_devices), + ) + .route( + "/_account.revokeTrustedDevice", + post(server::revoke_trusted_device), + ) + .route( + "/_account.updateTrustedDevice", + post(server::update_trusted_device), + ) + .route( + "/_account.createPasskeyAccount", + post(server::create_passkey_account), + ) + .route( + "/_account.startPasskeyRegistrationForSetup", + post(server::start_passkey_registration_for_setup), + ) + .route( + "/_account.completePasskeySetup", + post(server::complete_passkey_setup), + ) + .route( + "/_account.requestPasskeyRecovery", + post(server::request_passkey_recovery), + ) + .route( + "/_account.recoverPasskeyAccount", + post(server::recover_passkey_account), + ) + .route( + "/_account.updateDidDocument", + post(server::update_did_document), + ) .route("/_account.getDidDocument", get(server::get_did_document)) - .route("/com.atproto.server.requestEmailUpdate", post(server::request_email_update)) + .route( + "/com.atproto.server.requestEmailUpdate", + post(server::request_email_update), + ) .route("/_checkEmailVerified", post(server::check_email_verified)) - .route("/_checkChannelVerified", post(server::check_channel_verified)) - .route("/com.atproto.server.confirmEmail", post(server::confirm_email)) - .route("/com.atproto.server.updateEmail", post(server::update_email)) - .route("/_account.authorizeEmailUpdate", get(server::authorize_email_update)) - .route("/_account.checkEmailUpdateStatus", get(server::check_email_update_status)) - .route("/_account.checkEmailInUse", post(server::check_email_in_use)) - .route("/_account.checkCommsChannelInUse", post(server::check_comms_channel_in_use)) - .route("/com.atproto.server.reserveSigningKey", post(server::reserve_signing_key)) - .route("/com.atproto.server.verifyMigrationEmail", post(server::verify_migration_email)) - .route("/com.atproto.server.resendMigrationVerification", post(server::resend_migration_verification)) - .route("/com.atproto.identity.updateHandle", post(identity::update_handle)) - .route("/com.atproto.identity.requestPlcOperationSignature", post(identity::request_plc_operation_signature)) - .route("/com.atproto.identity.signPlcOperation", post(identity::sign_plc_operation)) - .route("/com.atproto.identity.submitPlcOperation", post(identity::submit_plc_operation)) - .route("/_identity.verifyHandleOwnership", post(identity::verify_handle_ownership)) + .route( + "/_checkChannelVerified", + post(server::check_channel_verified), + ) + .route( + "/com.atproto.server.confirmEmail", + post(server::confirm_email), + ) + .route( + "/com.atproto.server.updateEmail", + post(server::update_email), + ) + .route( + "/_account.authorizeEmailUpdate", + get(server::authorize_email_update), + ) + .route( + "/_account.checkEmailUpdateStatus", + get(server::check_email_update_status), + ) + .route( + "/_account.checkEmailInUse", + post(server::check_email_in_use), + ) + .route( + "/_account.checkCommsChannelInUse", + post(server::check_comms_channel_in_use), + ) + .route( + "/com.atproto.server.reserveSigningKey", + post(server::reserve_signing_key), + ) + .route( + "/com.atproto.server.verifyMigrationEmail", + post(server::verify_migration_email), + ) + .route( + "/com.atproto.server.resendMigrationVerification", + post(server::resend_migration_verification), + ) + .route( + "/com.atproto.identity.updateHandle", + post(identity::update_handle), + ) + .route( + "/com.atproto.identity.requestPlcOperationSignature", + post(identity::request_plc_operation_signature), + ) + .route( + "/com.atproto.identity.signPlcOperation", + post(identity::sign_plc_operation), + ) + .route( + "/com.atproto.identity.submitPlcOperation", + post(identity::submit_plc_operation), + ) + .route( + "/_identity.verifyHandleOwnership", + post(identity::verify_handle_ownership), + ) .route("/com.atproto.repo.importRepo", post(repo::import_repo)) - .route("/com.atproto.admin.deleteAccount", post(admin::delete_account)) - .route("/com.atproto.admin.updateAccountEmail", post(admin::update_account_email)) - .route("/com.atproto.admin.updateAccountHandle", post(admin::update_account_handle)) - .route("/com.atproto.admin.updateAccountPassword", post(admin::update_account_password)) - .route("/com.atproto.server.listAppPasswords", get(server::list_app_passwords)) - .route("/com.atproto.server.createAppPassword", post(server::create_app_password)) - .route("/com.atproto.server.revokeAppPassword", post(server::revoke_app_password)) - .route("/com.atproto.server.createInviteCode", post(server::create_invite_code)) - .route("/com.atproto.server.createInviteCodes", post(server::create_invite_codes)) - .route("/com.atproto.server.getAccountInviteCodes", get(server::get_account_invite_codes)) - .route("/com.atproto.server.createTotpSecret", post(server::create_totp_secret)) + .route( + "/com.atproto.admin.deleteAccount", + post(admin::delete_account), + ) + .route( + "/com.atproto.admin.updateAccountEmail", + post(admin::update_account_email), + ) + .route( + "/com.atproto.admin.updateAccountHandle", + post(admin::update_account_handle), + ) + .route( + "/com.atproto.admin.updateAccountPassword", + post(admin::update_account_password), + ) + .route( + "/com.atproto.server.listAppPasswords", + get(server::list_app_passwords), + ) + .route( + "/com.atproto.server.createAppPassword", + post(server::create_app_password), + ) + .route( + "/com.atproto.server.revokeAppPassword", + post(server::revoke_app_password), + ) + .route( + "/com.atproto.server.createInviteCode", + post(server::create_invite_code), + ) + .route( + "/com.atproto.server.createInviteCodes", + post(server::create_invite_codes), + ) + .route( + "/com.atproto.server.getAccountInviteCodes", + get(server::get_account_invite_codes), + ) + .route( + "/com.atproto.server.createTotpSecret", + post(server::create_totp_secret), + ) .route("/com.atproto.server.enableTotp", post(server::enable_totp)) - .route("/com.atproto.server.disableTotp", post(server::disable_totp)) - .route("/com.atproto.server.getTotpStatus", get(server::get_totp_status)) - .route("/com.atproto.server.regenerateBackupCodes", post(server::regenerate_backup_codes)) - .route("/com.atproto.server.startPasskeyRegistration", post(server::start_passkey_registration)) - .route("/com.atproto.server.finishPasskeyRegistration", post(server::finish_passkey_registration)) - .route("/com.atproto.server.listPasskeys", get(server::list_passkeys)) - .route("/com.atproto.server.deletePasskey", post(server::delete_passkey)) - .route("/com.atproto.server.updatePasskey", post(server::update_passkey)) - .route("/com.atproto.admin.getInviteCodes", get(admin::get_invite_codes)) + .route( + "/com.atproto.server.disableTotp", + post(server::disable_totp), + ) + .route( + "/com.atproto.server.getTotpStatus", + get(server::get_totp_status), + ) + .route( + "/com.atproto.server.regenerateBackupCodes", + post(server::regenerate_backup_codes), + ) + .route( + "/com.atproto.server.startPasskeyRegistration", + post(server::start_passkey_registration), + ) + .route( + "/com.atproto.server.finishPasskeyRegistration", + post(server::finish_passkey_registration), + ) + .route( + "/com.atproto.server.listPasskeys", + get(server::list_passkeys), + ) + .route( + "/com.atproto.server.deletePasskey", + post(server::delete_passkey), + ) + .route( + "/com.atproto.server.updatePasskey", + post(server::update_passkey), + ) + .route( + "/com.atproto.admin.getInviteCodes", + get(admin::get_invite_codes), + ) .route("/_admin.getServerStats", get(admin::get_server_stats)) .route("/_server.getConfig", get(admin::get_server_config)) - .route("/_admin.updateServerConfig", post(admin::update_server_config)) - .route("/com.atproto.admin.disableAccountInvites", post(admin::disable_account_invites)) - .route("/com.atproto.admin.enableAccountInvites", post(admin::enable_account_invites)) - .route("/com.atproto.admin.disableInviteCodes", post(admin::disable_invite_codes)) - .route("/com.atproto.admin.getSubjectStatus", get(admin::get_subject_status)) - .route("/com.atproto.admin.updateSubjectStatus", post(admin::update_subject_status)) + .route( + "/_admin.updateServerConfig", + post(admin::update_server_config), + ) + .route( + "/com.atproto.admin.disableAccountInvites", + post(admin::disable_account_invites), + ) + .route( + "/com.atproto.admin.enableAccountInvites", + post(admin::enable_account_invites), + ) + .route( + "/com.atproto.admin.disableInviteCodes", + post(admin::disable_invite_codes), + ) + .route( + "/com.atproto.admin.getSubjectStatus", + get(admin::get_subject_status), + ) + .route( + "/com.atproto.admin.updateSubjectStatus", + post(admin::update_subject_status), + ) .route("/com.atproto.admin.sendEmail", post(admin::send_email)) - .route("/app.bsky.actor.getPreferences", get(actor::get_preferences)) - .route("/app.bsky.actor.putPreferences", post(actor::put_preferences)) - .route("/com.atproto.temp.checkSignupQueue", get(temp::check_signup_queue)) - .route("/com.atproto.temp.dereferenceScope", post(temp::dereference_scope)) - .route("/_account.getNotificationPrefs", get(notification_prefs::get_notification_prefs)) - .route("/_account.updateNotificationPrefs", post(notification_prefs::update_notification_prefs)) - .route("/_account.getNotificationHistory", get(notification_prefs::get_notification_history)) - .route("/_account.confirmChannelVerification", post(verification::confirm_channel_verification)) + .route( + "/app.bsky.actor.getPreferences", + get(actor::get_preferences), + ) + .route( + "/app.bsky.actor.putPreferences", + post(actor::put_preferences), + ) + .route( + "/com.atproto.temp.checkSignupQueue", + get(temp::check_signup_queue), + ) + .route( + "/com.atproto.temp.dereferenceScope", + post(temp::dereference_scope), + ) + .route( + "/_account.getNotificationPrefs", + get(notification_prefs::get_notification_prefs), + ) + .route( + "/_account.updateNotificationPrefs", + post(notification_prefs::update_notification_prefs), + ) + .route( + "/_account.getNotificationHistory", + get(notification_prefs::get_notification_history), + ) + .route( + "/_account.confirmChannelVerification", + post(verification::confirm_channel_verification), + ) .route("/_account.verifyToken", post(server::verify_token)) - .route("/_delegation.listControllers", get(delegation::list_controllers)) - .route("/_delegation.addController", post(delegation::add_controller)) - .route("/_delegation.removeController", post(delegation::remove_controller)) - .route("/_delegation.updateControllerScopes", post(delegation::update_controller_scopes)) - .route("/_delegation.listControlledAccounts", get(delegation::list_controlled_accounts)) + .route( + "/_delegation.listControllers", + get(delegation::list_controllers), + ) + .route( + "/_delegation.addController", + post(delegation::add_controller), + ) + .route( + "/_delegation.removeController", + post(delegation::remove_controller), + ) + .route( + "/_delegation.updateControllerScopes", + post(delegation::update_controller_scopes), + ) + .route( + "/_delegation.listControlledAccounts", + get(delegation::list_controlled_accounts), + ) .route("/_delegation.getAuditLog", get(delegation::get_audit_log)) - .route("/_delegation.getScopePresets", get(delegation::get_scope_presets)) - .route("/_delegation.createDelegatedAccount", post(delegation::create_delegated_account)) - .route("/_delegation.resolveController", get(delegation::resolve_controller)) - .route("/_backup.listBackups", get(backup::list_backups)) - .route("/_backup.getBackup", get(backup::get_backup)) - .route("/_backup.createBackup", post(backup::create_backup)) - .route("/_backup.deleteBackup", post(backup::delete_backup)) - .route("/_backup.setEnabled", post(backup::set_backup_enabled)) - .route("/_backup.exportBlobs", get(backup::export_blobs)) - .route("/app.bsky.ageassurance.getState", get(age_assurance::get_state)) - .route("/app.bsky.unspecced.getAgeAssuranceState", get(age_assurance::get_age_assurance_state)) + .route( + "/_delegation.getScopePresets", + get(delegation::get_scope_presets), + ) + .route( + "/_delegation.createDelegatedAccount", + post(delegation::create_delegated_account), + ) + .route( + "/_delegation.resolveController", + get(delegation::resolve_controller), + ) + .route( + "/app.bsky.ageassurance.getState", + get(age_assurance::get_state), + ) + .route( + "/app.bsky.unspecced.getAgeAssuranceState", + get(age_assurance::get_age_assurance_state), + ) } pub fn well_known_api_routes() -> axum::Router { @@ -253,13 +450,11 @@ pub fn webhook_routes() -> axum::Router { axum::Router::new() .route( "/webhook/telegram", - post(telegram_webhook::handle_telegram_webhook) - .layer(DefaultBodyLimit::max(64 * 1024)), + post(telegram_webhook::handle_telegram_webhook).layer(DefaultBodyLimit::max(64 * 1024)), ) .route( "/webhook/discord", - post(discord_webhook::handle_discord_webhook) - .layer(DefaultBodyLimit::max(64 * 1024)), + post(discord_webhook::handle_discord_webhook).layer(DefaultBodyLimit::max(64 * 1024)), ) } diff --git a/crates/tranquil-api/src/moderation/mod.rs b/crates/tranquil-api/src/moderation/mod.rs index 8388c8c..7fba74a 100644 --- a/crates/tranquil-api/src/moderation/mod.rs +++ b/crates/tranquil-api/src/moderation/mod.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::ApiError; -use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client}; -use tranquil_pds::auth::{AnyUser, Auth}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -11,6 +7,10 @@ use axum::{ use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use tracing::{error, info}; +use tranquil_pds::api::ApiError; +use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client}; +use tranquil_pds::auth::{AnyUser, Auth}; +use tranquil_pds::state::AppState; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ReportReasonType { diff --git a/crates/tranquil-api/src/notification_prefs.rs b/crates/tranquil-api/src/notification_prefs.rs index e94a759..233de6d 100644 --- a/crates/tranquil-api/src/notification_prefs.rs +++ b/crates/tranquil-api/src/notification_prefs.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Active, Auth}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +7,9 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::info; use tranquil_db_traits::{CommsChannel, CommsStatus, CommsType}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Active, Auth}; +use tranquil_pds::state::AppState; use tranquil_types::Did; #[derive(Serialize)] @@ -141,8 +141,9 @@ pub async fn request_channel_verification( identifier: &str, handle: Option<&str>, ) -> Result { - let token = - tranquil_pds::auth::verification_token::generate_channel_update_token(did, channel, identifier); + let token = tranquil_pds::auth::verification_token::generate_channel_update_token( + did, channel, identifier, + ); let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token); match channel { diff --git a/crates/tranquil-api/src/repo/blob.rs b/crates/tranquil-api/src/repo/blob.rs index 7e4d9cf..646b1f0 100644 --- a/crates/tranquil-api/src/repo/blob.rs +++ b/crates/tranquil-api/src/repo/blob.rs @@ -1,9 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope}; -use tranquil_pds::delegation::DelegationActionType; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{CidLink, Did}; -use tranquil_pds::util::get_header_str; use axum::body::Body; use axum::{ Json, @@ -19,6 +13,12 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use std::pin::Pin; use tracing::{debug, error, info, warn}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope}; +use tranquil_pds::delegation::DelegationActionType; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{CidLink, Did}; +use tranquil_pds::util::get_header_str; fn detect_mime_type(data: &[u8], client_hint: &str) -> String { if let Some(kind) = infer::get(data) { diff --git a/crates/tranquil-api/src/repo/import.rs b/crates/tranquil-api/src/repo/import.rs index 2e6ab07..b416bc7 100644 --- a/crates/tranquil-api/src/repo/import.rs +++ b/crates/tranquil-api/src/repo/import.rs @@ -1,11 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::repo_ops::create_signed_commit; -use tranquil_pds::auth::{Auth, NotTakendown}; -use tranquil_pds::state::AppState; -use tranquil_pds::sync::import::{ImportError, apply_import, parse_car}; -use tranquil_pds::sync::verify::CarVerifier; -use tranquil_pds::types::Did; use axum::{ body::Bytes, extract::State, @@ -16,6 +8,14 @@ use jacquard_repo::storage::BlockStore; use k256::ecdsa::SigningKey; use serde_json::json; use tracing::{debug, error, info, warn}; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Auth, NotTakendown}; +use tranquil_pds::repo_ops::create_signed_commit; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::import::{ImportError, apply_import, parse_car}; +use tranquil_pds::sync::verify::CarVerifier; +use tranquil_pds::types::Did; use tranquil_types::{AtUri, CidLink}; pub async fn import_repo( @@ -266,9 +266,9 @@ pub async fn import_repo( let key_bytes = tranquil_pds::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) .map_err(|e| { - error!("Failed to decrypt signing key: {}", e); - ApiError::InternalError(None) - })?; + error!("Failed to decrypt signing key: {}", e); + ApiError::InternalError(None) + })?; let signing_key = SigningKey::from_slice(&key_bytes).map_err(|e| { error!("Invalid signing key: {:?}", e); ApiError::InternalError(None) diff --git a/crates/tranquil-api/src/repo/meta.rs b/crates/tranquil-api/src/repo/meta.rs index c8d5edd..6ced06d 100644 --- a/crates/tranquil-api/src/repo/meta.rs +++ b/crates/tranquil-api/src/repo/meta.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; -use tranquil_pds::types::AtIdentifier; use axum::{ Json, extract::{Query, State}, @@ -8,6 +5,9 @@ use axum::{ }; use serde::Deserialize; use serde_json::json; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; +use tranquil_pds::types::AtIdentifier; #[derive(Deserialize)] pub struct DescribeRepoInput { diff --git a/crates/tranquil-api/src/repo/record/batch.rs b/crates/tranquil-api/src/repo/record/batch.rs index 6431653..fecc5b2 100644 --- a/crates/tranquil-api/src/repo/record/batch.rs +++ b/crates/tranquil-api/src/repo/record/batch.rs @@ -1,17 +1,6 @@ use super::validation::validate_record_with_status; use super::validation_mode::{ValidationMode, deserialize_validation_mode}; -use tranquil_pds::api::error::ApiError; use crate::repo::record::utils::{CommitParams, RecordOp, commit_and_log, extract_blob_cids}; -use tranquil_pds::auth::{ - Active, Auth, WriteOpKind, require_not_migrated, require_verified_or_delegated, - verify_batch_write_scopes, -}; -use tranquil_pds::cid_types::CommitCid; -use tranquil_pds::delegation::DelegationActionType; -use tranquil_pds::repo::tracking::TrackingBlockStore; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey}; -use tranquil_pds::validation::ValidationStatus; use axum::{ Json, extract::State, @@ -25,6 +14,17 @@ use serde_json::json; use std::str::FromStr; use std::sync::Arc; use tracing::info; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{ + Active, Auth, WriteOpKind, require_not_migrated, require_verified_or_delegated, + verify_batch_write_scopes, +}; +use tranquil_pds::cid_types::CommitCid; +use tranquil_pds::delegation::DelegationActionType; +use tranquil_pds::repo::tracking::TrackingBlockStore; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey}; +use tranquil_pds::validation::ValidationStatus; const MAX_BATCH_WRITES: usize = 200; diff --git a/crates/tranquil-api/src/repo/record/delete.rs b/crates/tranquil-api/src/repo/record/delete.rs index 737f64b..4f6a366 100644 --- a/crates/tranquil-api/src/repo/record/delete.rs +++ b/crates/tranquil-api/src/repo/record/delete.rs @@ -1,14 +1,7 @@ -use tranquil_pds::api::error::ApiError; use crate::repo::record::utils::{ CommitError, CommitParams, RecordOp, commit_and_log, get_current_root_cid, }; use crate::repo::record::write::{CommitInfo, prepare_repo_write}; -use tranquil_pds::auth::{Active, Auth, VerifyScope}; -use tranquil_pds::cid_types::CommitCid; -use tranquil_pds::delegation::DelegationActionType; -use tranquil_pds::repo::tracking::TrackingBlockStore; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{AtIdentifier, AtUri, Nsid, Rkey}; use axum::{ Json, extract::State, @@ -22,6 +15,13 @@ use serde_json::json; use std::str::FromStr; use std::sync::Arc; use tracing::error; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Active, Auth, VerifyScope}; +use tranquil_pds::cid_types::CommitCid; +use tranquil_pds::delegation::DelegationActionType; +use tranquil_pds::repo::tracking::TrackingBlockStore; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{AtIdentifier, AtUri, Nsid, Rkey}; #[derive(Deserialize)] pub struct DeleteRecordInput { diff --git a/crates/tranquil-api/src/repo/record/read.rs b/crates/tranquil-api/src/repo/record/read.rs index f95bb9d..23abe3f 100644 --- a/crates/tranquil-api/src/repo/record/read.rs +++ b/crates/tranquil-api/src/repo/record/read.rs @@ -1,7 +1,4 @@ use super::pagination::{PaginationDirection, deserialize_pagination_direction}; -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{AtIdentifier, Nsid, Rkey}; use axum::{ Json, extract::{Query, State}, @@ -16,6 +13,9 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value, json}; use std::str::FromStr; use tracing::error; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{AtIdentifier, Nsid, Rkey}; fn ipld_to_json(ipld: Ipld) -> Value { match ipld { diff --git a/crates/tranquil-api/src/repo/record/validation.rs b/crates/tranquil-api/src/repo/record/validation.rs index 15ce69b..d7d7a1d 100644 --- a/crates/tranquil-api/src/repo/record/validation.rs +++ b/crates/tranquil-api/src/repo/record/validation.rs @@ -1,7 +1,7 @@ +use axum::response::Response; use tranquil_pds::api::error::ApiError; use tranquil_pds::types::{Nsid, Rkey}; use tranquil_pds::validation::{RecordValidator, ValidationError, ValidationStatus}; -use axum::response::Response; pub async fn validate_record_with_status( record: &serde_json::Value, diff --git a/crates/tranquil-api/src/repo/record/write.rs b/crates/tranquil-api/src/repo/record/write.rs index faaecd9..5394a68 100644 --- a/crates/tranquil-api/src/repo/record/write.rs +++ b/crates/tranquil-api/src/repo/record/write.rs @@ -1,20 +1,9 @@ use super::validation::validate_record_with_status; use super::validation_mode::{ValidationMode, deserialize_validation_mode}; -use tranquil_pds::api::error::ApiError; use crate::repo::record::utils::{ CommitParams, RecordOp, commit_and_log, extract_backlinks, extract_blob_cids, get_current_root_cid, }; -use tranquil_pds::auth::{ - Active, Auth, AuthSource, RepoScopeAction, ScopeVerified, VerifyScope, require_not_migrated, - require_verified_or_delegated, -}; -use tranquil_pds::cid_types::CommitCid; -use tranquil_pds::delegation::DelegationActionType; -use tranquil_pds::repo::tracking::TrackingBlockStore; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey}; -use tranquil_pds::validation::ValidationStatus; use axum::{ Json, extract::State, @@ -28,6 +17,17 @@ use serde_json::json; use std::str::FromStr; use std::sync::Arc; use tracing::error; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{ + Active, Auth, AuthSource, RepoScopeAction, ScopeVerified, VerifyScope, require_not_migrated, + require_verified_or_delegated, +}; +use tranquil_pds::cid_types::CommitCid; +use tranquil_pds::delegation::DelegationActionType; +use tranquil_pds::repo::tracking::TrackingBlockStore; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{AtIdentifier, AtUri, Did, Nsid, Rkey}; +use tranquil_pds::validation::ValidationStatus; use uuid::Uuid; pub struct RepoWriteAuth { diff --git a/crates/tranquil-api/src/server/account_status.rs b/crates/tranquil-api/src/server/account_status.rs index 54c14a3..08aa708 100644 --- a/crates/tranquil-api/src/server/account_status.rs +++ b/crates/tranquil-api/src/server/account_status.rs @@ -1,10 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Auth, NotTakendown, Permissive, require_legacy_session_mfa}; -use tranquil_pds::cache::Cache; -use tranquil_pds::plc::PlcClient; -use tranquil_pds::state::AppState; -use tranquil_pds::types::PlainPassword; use axum::{ Json, extract::State, @@ -23,6 +16,13 @@ use std::str::FromStr; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use tracing::{error, info, warn}; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Auth, NotTakendown, Permissive, require_legacy_session_mfa}; +use tranquil_pds::cache::Cache; +use tranquil_pds::plc::PlcClient; +use tranquil_pds::state::AppState; +use tranquil_pds::types::PlainPassword; use uuid::Uuid; #[derive(Serialize)] @@ -365,7 +365,10 @@ pub async fn activate_account( did ); if let Some(ref h) = handle { - let _ = state.cache.delete(&tranquil_pds::cache_keys::handle_key(h)).await; + let _ = state + .cache + .delete(&tranquil_pds::cache_keys::handle_key(h)) + .await; } let _ = state .cache @@ -404,12 +407,9 @@ pub async fn activate_account( did, handle ); let handle_typed = handle.clone(); - if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event( - &state, - &did, - handle_typed.as_ref(), - ) - .await + if let Err(e) = + tranquil_pds::repo_ops::sequence_identity_event(&state, &did, handle_typed.as_ref()) + .await { warn!( "[MIGRATION] activateAccount: Failed to sequence identity event for activation: {}", @@ -507,7 +507,10 @@ pub async fn deactivate_account( match result { Ok(true) => { if let Some(ref h) = handle { - let _ = state.cache.delete(&tranquil_pds::cache_keys::handle_key(h)).await; + let _ = state + .cache + .delete(&tranquil_pds::cache_keys::handle_key(h)) + .await; } if let Err(e) = tranquil_pds::repo_ops::sequence_account_event( &state, diff --git a/crates/tranquil-api/src/server/app_password.rs b/crates/tranquil-api/src/server/app_password.rs index f37dfaa..f8760ac 100644 --- a/crates/tranquil-api/src/server/app_password.rs +++ b/crates/tranquil-api/src/server/app_password.rs @@ -1,9 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Auth, NotTakendown, Permissive, generate_app_password}; -use tranquil_pds::delegation::{DelegationActionType, intersect_scopes}; -use tranquil_pds::rate_limit::{AppPasswordLimit, RateLimited}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -13,6 +7,12 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::error; use tranquil_db_traits::AppPasswordCreate; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Auth, NotTakendown, Permissive, generate_app_password}; +use tranquil_pds::delegation::{DelegationActionType, intersect_scopes}; +use tranquil_pds::rate_limit::{AppPasswordLimit, RateLimited}; +use tranquil_pds::state::AppState; #[derive(Serialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/server/email.rs b/crates/tranquil-api/src/server/email.rs index 2d943d5..d1663fe 100644 --- a/crates/tranquil-api/src/server/email.rs +++ b/crates/tranquil-api/src/server/email.rs @@ -1,8 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse}; -use tranquil_pds::auth::{Auth, NotTakendown}; -use tranquil_pds::rate_limit::{EmailUpdateLimit, RateLimited, VerificationCheckLimit}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -16,6 +11,11 @@ use std::time::Duration; use subtle::ConstantTimeEq; use tracing::{error, info, warn}; use tranquil_db_traits::CommsChannel; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::api::{EmptyResponse, TokenRequiredResponse, VerifiedResponse}; +use tranquil_pds::auth::{Auth, NotTakendown}; +use tranquil_pds::rate_limit::{EmailUpdateLimit, RateLimited, VerificationCheckLimit}; +use tranquil_pds::state::AppState; const EMAIL_UPDATE_TTL: Duration = Duration::from_secs(30 * 60); @@ -470,7 +470,9 @@ pub async fn authorize_email_update( } }; - if token_data.purpose != tranquil_pds::auth::verification_token::VerificationPurpose::ChannelUpdate { + if token_data.purpose + != tranquil_pds::auth::verification_token::VerificationPurpose::ChannelUpdate + { warn!( "authorize_email_update: wrong purpose: {:?}", token_data.purpose diff --git a/crates/tranquil-api/src/server/invite.rs b/crates/tranquil-api/src/server/invite.rs index 5e85ee0..b84919f 100644 --- a/crates/tranquil-api/src/server/invite.rs +++ b/crates/tranquil-api/src/server/invite.rs @@ -1,8 +1,3 @@ -use tranquil_pds::api::ApiError; -use tranquil_pds::api::error::DbResultExt; -use tranquil_pds::auth::{Admin, Auth, NotTakendown}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Did; use axum::{ Json, extract::State, @@ -11,6 +6,11 @@ use axum::{ use rand::Rng; use serde::{Deserialize, Serialize}; use tracing::error; +use tranquil_pds::api::ApiError; +use tranquil_pds::api::error::DbResultExt; +use tranquil_pds::auth::{Admin, Auth, NotTakendown}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; const BASE32_ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz234567"; diff --git a/crates/tranquil-api/src/server/logo.rs b/crates/tranquil-api/src/server/logo.rs index be1f4c3..4cf8d17 100644 --- a/crates/tranquil-api/src/server/logo.rs +++ b/crates/tranquil-api/src/server/logo.rs @@ -1,4 +1,3 @@ -use tranquil_pds::state::AppState; use axum::{ body::Body, extract::State, @@ -7,6 +6,7 @@ use axum::{ response::{IntoResponse, Response}, }; use tracing::error; +use tranquil_pds::state::AppState; pub async fn get_logo(State(state): State) -> Response { let logo_cid = match state.infra_repo.get_server_config("logo_cid").await { diff --git a/crates/tranquil-api/src/server/meta.rs b/crates/tranquil-api/src/server/meta.rs index 0bfa2f5..49a37ba 100644 --- a/crates/tranquil-api/src/server/meta.rs +++ b/crates/tranquil-api/src/server/meta.rs @@ -1,8 +1,8 @@ +use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use tranquil_pds::BUILD_VERSION; use tranquil_pds::state::AppState; use tranquil_pds::util::{discord_app_id, discord_bot_username, telegram_bot_username}; -use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; -use serde_json::json; fn get_available_comms_channels() -> Vec { use tranquil_db_traits::CommsChannel; diff --git a/crates/tranquil-api/src/server/migration.rs b/crates/tranquil-api/src/server/migration.rs index ef477eb..6d88d6a 100644 --- a/crates/tranquil-api/src/server/migration.rs +++ b/crates/tranquil-api/src/server/migration.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::ApiError; -use tranquil_pds::api::error::DbResultExt; -use tranquil_pds::auth::{Active, Auth}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +6,10 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use serde_json::json; +use tranquil_pds::api::ApiError; +use tranquil_pds::api::error::DbResultExt; +use tranquil_pds::auth::{Active, Auth}; +use tranquil_pds::state::AppState; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -209,11 +209,13 @@ async fn build_did_document(state: &AppState, did: &tranquil_pds::types::Did) -> .flatten(); let public_key_multibase = match key_info { - Some(info) => match tranquil_pds::config::decrypt_key(&info.key_bytes, info.encryption_version) { - Ok(key_bytes) => crate::identity::did::get_public_key_multibase(&key_bytes) - .unwrap_or_else(|_| "error".to_string()), - Err(_) => "error".to_string(), - }, + Some(info) => { + match tranquil_pds::config::decrypt_key(&info.key_bytes, info.encryption_version) { + Ok(key_bytes) => crate::identity::did::get_public_key_multibase(&key_bytes) + .unwrap_or_else(|_| "error".to_string()), + Err(_) => "error".to_string(), + } + } None => "error".to_string(), }; diff --git a/crates/tranquil-api/src/server/passkey_account.rs b/crates/tranquil-api/src/server/passkey_account.rs index 0513cc5..d57eafa 100644 --- a/crates/tranquil-api/src/server/passkey_account.rs +++ b/crates/tranquil-api/src/server/passkey_account.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::SuccessResponse; -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::NormalizedLoginIdentifier; use axum::{ Json, extract::State, @@ -17,11 +14,14 @@ use serde_json::json; use std::sync::Arc; use tracing::{debug, error, info, warn}; use tranquil_db_traits::WebauthnChallengeType; +use tranquil_pds::api::SuccessResponse; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::NormalizedLoginIdentifier; use uuid::Uuid; -use tranquil_pds::repo_ops::create_signed_commit; use tranquil_pds::auth::{ServiceTokenVerifier, generate_app_password, is_service_token}; use tranquil_pds::rate_limit::{AccountCreationLimit, PasswordResetLimit, RateLimited}; +use tranquil_pds::repo_ops::create_signed_commit; use tranquil_pds::state::AppState; use tranquil_pds::types::{Did, Handle, PlainPassword}; use tranquil_pds::validation::validate_password; @@ -324,7 +324,8 @@ pub async fn create_passkey_account( } }; - let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone())); + let plc_client = + tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone())); if let Err(e) = plc_client .send_operation(&genesis_result.did, &genesis_result.signed_operation) .await @@ -465,12 +466,9 @@ pub async fn create_passkey_account( let user_id = create_result.user_id; if !is_byod_did_web { - if let Err(e) = tranquil_pds::repo_ops::sequence_identity_event( - &state, - &did_typed, - Some(&handle_typed), - ) - .await + if let Err(e) = + tranquil_pds::repo_ops::sequence_identity_event(&state, &did_typed, Some(&handle_typed)) + .await { warn!("Failed to sequence identity event for {}: {}", did, e); } diff --git a/crates/tranquil-api/src/server/passkeys.rs b/crates/tranquil-api/src/server/passkeys.rs index 74a14e5..aebf594 100644 --- a/crates/tranquil-api/src/server/passkeys.rs +++ b/crates/tranquil-api/src/server/passkeys.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::auth::{Active, Auth, require_legacy_session_mfa, require_reauth_window}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +6,10 @@ use axum::{ use serde::{Deserialize, Serialize}; use tracing::{error, info, warn}; use tranquil_db_traits::WebauthnChallengeType; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::auth::{Active, Auth, require_legacy_session_mfa, require_reauth_window}; +use tranquil_pds::state::AppState; use webauthn_rs::prelude::*; #[derive(Deserialize)] diff --git a/crates/tranquil-api/src/server/password.rs b/crates/tranquil-api/src/server/password.rs index 92103ec..9b26613 100644 --- a/crates/tranquil-api/src/server/password.rs +++ b/crates/tranquil-api/src/server/password.rs @@ -1,3 +1,12 @@ +use axum::{ + Json, + extract::State, + response::{IntoResponse, Response}, +}; +use bcrypt::{DEFAULT_COST, hash}; +use chrono::{Duration, Utc}; +use serde::Deserialize; +use tracing::{error, info, warn}; use tranquil_pds::api::error::{ApiError, DbResultExt}; use tranquil_pds::api::{EmptyResponse, HasPasswordResponse, SuccessResponse}; use tranquil_pds::auth::{ @@ -8,15 +17,6 @@ use tranquil_pds::rate_limit::{PasswordResetLimit, RateLimited, ResetPasswordLim use tranquil_pds::state::AppState; use tranquil_pds::types::PlainPassword; use tranquil_pds::validation::validate_password; -use axum::{ - Json, - extract::State, - response::{IntoResponse, Response}, -}; -use bcrypt::{DEFAULT_COST, hash}; -use chrono::{Duration, Utc}; -use serde::Deserialize; -use tracing::{error, info, warn}; fn generate_reset_code() -> String { tranquil_pds::util::generate_token_code() diff --git a/crates/tranquil-api/src/server/reauth.rs b/crates/tranquil-api/src/server/reauth.rs index 8f89e76..5c3fc3c 100644 --- a/crates/tranquil-api/src/server/reauth.rs +++ b/crates/tranquil-api/src/server/reauth.rs @@ -1,4 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; use axum::{ Json, extract::State, @@ -9,6 +8,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use tracing::{error, info, warn}; use tranquil_db_traits::{SessionRepository, UserRepository, WebauthnChallengeType}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; use tranquil_pds::auth::{Active, Auth}; use tranquil_pds::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message}; @@ -125,8 +125,7 @@ pub async fn reauth_totp( .await?; let valid = - crate::server::totp::verify_totp_or_backup_for_user(&state, &auth.did, &input.code) - .await; + crate::server::totp::verify_totp_or_backup_for_user(&state, &auth.did, &input.code).await; if !valid { warn!(did = %&auth.did, "Re-auth failed: invalid TOTP code"); diff --git a/crates/tranquil-api/src/server/service_auth.rs b/crates/tranquil-api/src/server/service_auth.rs index 28d5cf7..995b62c 100644 --- a/crates/tranquil-api/src/server/service_auth.rs +++ b/crates/tranquil-api/src/server/service_auth.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::extractor::{Auth, Permissive}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::Did; use axum::{ Json, extract::{Query, State}, @@ -12,6 +8,10 @@ use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::sync::LazyLock; use tracing::{error, info, warn}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::extractor::{Auth, Permissive}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::Did; use tranquil_types::Nsid; static CREATE_ACCOUNT_NSID: LazyLock = @@ -75,7 +75,10 @@ pub async fn get_service_auth( match state.user_repo.get_user_info_by_did(&auth.did).await { Ok(Some(info)) => match info.key_bytes { Some(key_bytes_enc) => { - match tranquil_pds::config::decrypt_key(&key_bytes_enc, info.encryption_version) { + match tranquil_pds::config::decrypt_key( + &key_bytes_enc, + info.encryption_version, + ) { Ok(key) => key, Err(e) => { error!(error = ?e, "Failed to decrypt user key for service auth"); diff --git a/crates/tranquil-api/src/server/session.rs b/crates/tranquil-api/src/server/session.rs index f5fa84c..5445fff 100644 --- a/crates/tranquil-api/src/server/session.rs +++ b/crates/tranquil-api/src/server/session.rs @@ -1,12 +1,3 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::api::{EmptyResponse, SuccessResponse}; -use tranquil_pds::auth::{ - Active, Auth, NormalizedLoginIdentifier, Permissive, require_legacy_session_mfa, - require_reauth_window, -}; -use tranquil_pds::rate_limit::{LoginLimit, RateLimited, RefreshSessionLimit}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{AccountState, Did, Handle, PlainPassword}; use axum::{ Json, extract::State, @@ -18,6 +9,15 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{error, info, warn}; use tranquil_db_traits::{SessionId, TokenFamilyId}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::api::{EmptyResponse, SuccessResponse}; +use tranquil_pds::auth::{ + Active, Auth, NormalizedLoginIdentifier, Permissive, require_legacy_session_mfa, + require_reauth_window, +}; +use tranquil_pds::rate_limit::{LoginLimit, RateLimited, RefreshSessionLimit}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{AccountState, Did, Handle, PlainPassword}; use tranquil_types::TokenId; fn full_handle(stored_handle: &str, _pds_hostname: &str) -> String { @@ -93,7 +93,8 @@ pub async fn create_session( return ApiError::InternalError(None).into_response(); } }; - let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) { + let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) + { Ok(k) => k, Err(e) => { error!("Failed to decrypt user key: {:?}", e); @@ -203,7 +204,8 @@ pub async fn create_session( .await { error!("Failed to send 2FA code: {:?}", e); - tranquil_pds::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &row.did).await; + tranquil_pds::auth::legacy_2fa::clear_challenge(state.cache.as_ref(), &row.did) + .await; return ApiError::InternalError(Some( "Failed to send verification code. Please try again.".into(), )) @@ -261,13 +263,14 @@ pub async fn create_session( return ApiError::InternalError(None).into_response(); } }; - let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) { - Ok(m) => m, - Err(e) => { - error!("Failed to create refresh token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; + let refresh_meta = + match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) { + Ok(m) => m, + Err(e) => { + error!("Failed to create refresh token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; let did_for_doc = row.did.clone(); let did_resolver = state.did_resolver.clone(); let session_data = tranquil_db_traits::SessionTokenCreate { @@ -406,10 +409,9 @@ pub async fn delete_session( headers: axum::http::HeaderMap, _auth: Auth, ) -> Result { - let extracted = tranquil_pds::auth::extract_auth_token_from_header(tranquil_pds::util::get_header_str( - &headers, - http::header::AUTHORIZATION, - )) + let extracted = tranquil_pds::auth::extract_auth_token_from_header( + tranquil_pds::util::get_header_str(&headers, http::header::AUTHORIZATION), + ) .ok_or(ApiError::AuthenticationRequired)?; let jti = tranquil_pds::auth::get_jti_from_token(&extracted.token) .map_err(|_| ApiError::AuthenticationFailed(None))?; @@ -432,10 +434,9 @@ pub async fn refresh_session( _rate_limit: RateLimited, headers: axum::http::HeaderMap, ) -> Response { - let extracted = match tranquil_pds::auth::extract_auth_token_from_header(tranquil_pds::util::get_header_str( - &headers, - http::header::AUTHORIZATION, - )) { + let extracted = match tranquil_pds::auth::extract_auth_token_from_header( + tranquil_pds::util::get_header_str(&headers, http::header::AUTHORIZATION), + ) { Some(t) => t, None => return ApiError::AuthenticationRequired.into_response(), }; @@ -500,14 +501,16 @@ pub async fn refresh_session( return ApiError::InternalError(None).into_response(); } }; - let new_refresh_meta = - match tranquil_pds::auth::create_refresh_token_with_metadata(&session_row.did, &key_bytes) { - Ok(m) => m, - Err(e) => { - error!("Failed to create refresh token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; + let new_refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata( + &session_row.did, + &key_bytes, + ) { + Ok(m) => m, + Err(e) => { + error!("Failed to create refresh token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; let refresh_data = tranquil_db_traits::SessionRefreshData { old_refresh_jti: refresh_jti.clone(), session_id: session_row.id, @@ -668,7 +671,8 @@ pub async fn confirm_signup( } } - let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) { + let key_bytes = match tranquil_pds::config::decrypt_key(&row.key_bytes, row.encryption_version) + { Ok(k) => k, Err(e) => { error!("Failed to decrypt user key: {:?}", e); @@ -676,20 +680,22 @@ pub async fn confirm_signup( } }; - let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&row.did, &key_bytes) { - Ok(m) => m, - Err(e) => { - error!("Failed to create access token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; - let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) { - Ok(m) => m, - Err(e) => { - error!("Failed to create refresh token: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; + let access_meta = + match tranquil_pds::auth::create_access_token_with_metadata(&row.did, &key_bytes) { + Ok(m) => m, + Err(e) => { + error!("Failed to create access token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; + let refresh_meta = + match tranquil_pds::auth::create_refresh_token_with_metadata(&row.did, &key_bytes) { + Ok(m) => m, + Err(e) => { + error!("Failed to create refresh token: {:?}", e); + return ApiError::InternalError(None).into_response(); + } + }; if let Err(e) = state .user_repo @@ -855,8 +861,11 @@ pub async fn resend_verification( tranquil_db_traits::CommsChannel::Signal => row.signal_username.clone().unwrap_or_default(), }; - let verification_token = - tranquil_pds::auth::verification_token::generate_signup_token(&input.did, row.channel, &recipient); + let verification_token = tranquil_pds::auth::verification_token::generate_signup_token( + &input.did, + row.channel, + &recipient, + ); let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&verification_token); diff --git a/crates/tranquil-api/src/server/signing_key.rs b/crates/tranquil-api/src/server/signing_key.rs index f6b377a..3396abc 100644 --- a/crates/tranquil-api/src/server/signing_key.rs +++ b/crates/tranquil-api/src/server/signing_key.rs @@ -1,5 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +8,8 @@ use chrono::{Duration, Utc}; use k256::ecdsa::SigningKey; use serde::{Deserialize, Serialize}; use tracing::{error, info}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; const SECP256K1_MULTICODEC_PREFIX: [u8; 2] = [0xe7, 0x01]; diff --git a/crates/tranquil-api/src/server/totp.rs b/crates/tranquil-api/src/server/totp.rs index abbba4a..a5f0704 100644 --- a/crates/tranquil-api/src/server/totp.rs +++ b/crates/tranquil-api/src/server/totp.rs @@ -1,3 +1,10 @@ +use axum::{ + Json, + extract::State, + response::{IntoResponse, Response}, +}; +use serde::{Deserialize, Serialize}; +use tracing::{error, info, warn}; use tranquil_pds::api::EmptyResponse; use tranquil_pds::api::error::{ApiError, DbResultExt}; use tranquil_pds::auth::{ @@ -9,13 +16,6 @@ use tranquil_pds::auth::{ use tranquil_pds::rate_limit::{TotpVerifyLimit, check_user_rate_limit_with_message}; use tranquil_pds::state::AppState; use tranquil_pds::types::PlainPassword; -use axum::{ - Json, - extract::State, - response::{IntoResponse, Response}, -}; -use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; const ENCRYPTION_VERSION: i32 = 1; diff --git a/crates/tranquil-api/src/server/trusted_devices.rs b/crates/tranquil-api/src/server/trusted_devices.rs index 8a7a12a..af0d62e 100644 --- a/crates/tranquil-api/src/server/trusted_devices.rs +++ b/crates/tranquil-api/src/server/trusted_devices.rs @@ -1,5 +1,3 @@ -use tranquil_pds::api::SuccessResponse; -use tranquil_pds::api::error::{ApiError, DbResultExt}; use axum::{ Json, extract::State, @@ -9,6 +7,8 @@ use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use tracing::{error, info}; use tranquil_db_traits::OAuthRepository; +use tranquil_pds::api::SuccessResponse; +use tranquil_pds::api::error::{ApiError, DbResultExt}; use tranquil_types::DeviceId; use tranquil_pds::auth::{Active, Auth}; diff --git a/crates/tranquil-api/src/server/verify_email.rs b/crates/tranquil-api/src/server/verify_email.rs index 5b49a21..a99c688 100644 --- a/crates/tranquil-api/src/server/verify_email.rs +++ b/crates/tranquil-api/src/server/verify_email.rs @@ -1,8 +1,8 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::types::Did; use axum::{Json, extract::State}; use serde::{Deserialize, Serialize}; use tracing::{info, warn}; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::types::Did; use tranquil_pds::state::AppState; diff --git a/crates/tranquil-api/src/server/verify_token.rs b/crates/tranquil-api/src/server/verify_token.rs index 27cc20e..14fbc13 100644 --- a/crates/tranquil-api/src/server/verify_token.rs +++ b/crates/tranquil-api/src/server/verify_token.rs @@ -1,15 +1,15 @@ -use tranquil_pds::api::error::{ApiError, DbResultExt}; -use tranquil_pds::comms::comms_repo; -use tranquil_pds::types::Did; use axum::{Json, extract::State}; use serde::{Deserialize, Serialize}; use tracing::{info, warn}; +use tranquil_pds::api::error::{ApiError, DbResultExt}; +use tranquil_pds::comms::comms_repo; +use tranquil_pds::types::Did; +use tranquil_db_traits::CommsChannel; use tranquil_pds::auth::verification_token::{ VerificationPurpose, normalize_token_input, verify_token_signature, }; use tranquil_pds::state::AppState; -use tranquil_db_traits::CommsChannel; #[derive(Deserialize, Clone)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/temp.rs b/crates/tranquil-api/src/temp.rs index ef1a251..ecb025c 100644 --- a/crates/tranquil-api/src/temp.rs +++ b/crates/tranquil-api/src/temp.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::auth::{Active, Auth, Permissive}; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, @@ -10,6 +7,9 @@ use cid::Cid; use jacquard_repo::storage::BlockStore; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::auth::{Active, Auth, Permissive}; +use tranquil_pds::state::AppState; #[derive(Serialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-api/src/verification.rs b/crates/tranquil-api/src/verification.rs index b2378cb..8a396e6 100644 --- a/crates/tranquil-api/src/verification.rs +++ b/crates/tranquil-api/src/verification.rs @@ -1,11 +1,11 @@ -use tranquil_pds::api::SuccessResponse; -use tranquil_pds::state::AppState; use axum::{ Json, extract::State, response::{IntoResponse, Response}, }; use serde::Deserialize; +use tranquil_pds::api::SuccessResponse; +use tranquil_pds::state::AppState; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/tranquil-config/src/lib.rs b/crates/tranquil-config/src/lib.rs index dd6d660..d8ddade 100644 --- a/crates/tranquil-config/src/lib.rs +++ b/crates/tranquil-config/src/lib.rs @@ -108,9 +108,6 @@ pub struct TranquilConfig { #[config(nested)] pub storage: StorageConfig, - #[config(nested)] - pub backup: BackupConfig, - #[config(nested)] pub cache: CacheConfig, @@ -234,27 +231,6 @@ impl TranquilConfig { } } - // -- backup storage --------------------------------------------------- - if self.backup.enabled { - match self.backup.backend.as_str() { - "s3" => { - if self.backup.s3_bucket.is_none() { - errors.push( - "backup.backend is \"s3\" but backup.s3_bucket \ - (BACKUP_S3_BUCKET) is not set" - .to_string(), - ); - } - } - "filesystem" => {} - other => { - errors.push(format!( - "backup.backend must be \"filesystem\" or \"s3\", got \"{other}\"" - )); - } - } - } - // -- SSO providers ---------------------------------------------------- self.validate_sso_provider("sso.github", &self.sso.github, &mut errors); self.validate_sso_provider("sso.google", &self.sso.google, &mut errors); @@ -604,33 +580,6 @@ pub struct StorageConfig { pub s3_endpoint: Option, } -#[derive(Debug, Config)] -pub struct BackupConfig { - /// Enable automatic backups. - #[config(env = "BACKUP_ENABLED", default = true)] - pub enabled: bool, - - /// Backup storage backend: `filesystem` or `s3`. - #[config(env = "BACKUP_STORAGE_BACKEND", default = "filesystem")] - pub backend: String, - - /// Path on disk for the filesystem backup backend. - #[config(env = "BACKUP_STORAGE_PATH", default = "/var/lib/tranquil-pds/backups")] - pub path: String, - - /// S3 bucket name for backups. - #[config(env = "BACKUP_S3_BUCKET")] - pub s3_bucket: Option, - - /// Number of backup revisions to keep per account. - #[config(env = "BACKUP_RETENTION_COUNT", default = 7)] - pub retention_count: u32, - - /// Seconds between backup runs. - #[config(env = "BACKUP_INTERVAL_SECS", default = 86400)] - pub interval_secs: u64, -} - #[derive(Debug, Config)] pub struct CacheConfig { /// Cache backend: `ripple` (default, built-in gossip) or `valkey`. diff --git a/crates/tranquil-db-traits/src/backup.rs b/crates/tranquil-db-traits/src/backup.rs deleted file mode 100644 index 92ec911..0000000 --- a/crates/tranquil-db-traits/src/backup.rs +++ /dev/null @@ -1,108 +0,0 @@ -use async_trait::async_trait; -use chrono::{DateTime, Utc}; -use tranquil_types::Did; -use uuid::Uuid; - -use crate::DbError; - -#[derive(Debug, Clone)] -pub struct BackupRow { - pub id: Uuid, - pub repo_rev: String, - pub repo_root_cid: String, - pub block_count: i32, - pub size_bytes: i64, - pub created_at: DateTime, -} - -#[derive(Debug, Clone)] -pub struct BackupStorageInfo { - pub storage_key: String, - pub repo_rev: String, -} - -#[derive(Debug, Clone)] -pub struct BackupForDeletion { - pub id: Uuid, - pub storage_key: String, - pub deactivated_at: Option>, -} - -#[derive(Debug, Clone)] -pub struct OldBackupInfo { - pub id: Uuid, - pub storage_key: String, -} - -#[derive(Debug, Clone)] -pub struct UserBackupInfo { - pub id: Uuid, - pub did: Did, - pub backup_enabled: bool, - pub deactivated_at: Option>, - pub repo_root_cid: String, - pub repo_rev: Option, -} - -#[derive(Debug, Clone)] -pub struct BlobExportInfo { - pub cid: String, - pub storage_key: String, - pub mime_type: String, -} - -#[async_trait] -pub trait BackupRepository: Send + Sync { - async fn get_user_backup_status(&self, did: &Did) -> Result, DbError>; - - async fn list_backups_for_user(&self, user_id: Uuid) -> Result, DbError>; - - async fn get_backup_storage_info( - &self, - backup_id: Uuid, - did: &Did, - ) -> Result, DbError>; - - async fn get_user_for_backup(&self, did: &Did) -> Result, DbError>; - - async fn insert_backup( - &self, - user_id: Uuid, - storage_key: &str, - repo_root_cid: &str, - repo_rev: &str, - block_count: i32, - size_bytes: i64, - ) -> Result; - - async fn get_old_backups( - &self, - user_id: Uuid, - retention_offset: i64, - ) -> Result, DbError>; - - async fn delete_backup(&self, backup_id: Uuid) -> Result<(), DbError>; - - async fn get_backup_for_deletion( - &self, - backup_id: Uuid, - did: &Did, - ) -> Result, DbError>; - - async fn get_user_deactivated_status( - &self, - did: &Did, - ) -> Result>>, DbError>; - - async fn update_backup_enabled(&self, did: &Did, enabled: bool) -> Result<(), DbError>; - - async fn get_user_id_by_did(&self, did: &Did) -> Result, DbError>; - - async fn get_blobs_for_export(&self, user_id: Uuid) -> Result, DbError>; - - async fn get_users_needing_backup( - &self, - backup_interval_secs: i64, - limit: i64, - ) -> Result, DbError>; -} diff --git a/crates/tranquil-db-traits/src/lib.rs b/crates/tranquil-db-traits/src/lib.rs index ade5be7..8d6774d 100644 --- a/crates/tranquil-db-traits/src/lib.rs +++ b/crates/tranquil-db-traits/src/lib.rs @@ -1,5 +1,4 @@ mod backlink; -mod backup; mod blob; mod channel_verification; mod delegation; @@ -15,10 +14,6 @@ mod sso; mod user; pub use backlink::{Backlink, BacklinkPath, BacklinkRepository}; -pub use backup::{ - BackupForDeletion, BackupRepository, BackupRow, BackupStorageInfo, BlobExportInfo, - OldBackupInfo, UserBackupInfo, -}; pub use blob::{BlobForExport, BlobMetadata, BlobRepository, BlobWithTakedown, MissingBlobInfo}; pub use channel_verification::ChannelVerificationStatus; pub use delegation::{ diff --git a/crates/tranquil-db/src/postgres/backup.rs b/crates/tranquil-db/src/postgres/backup.rs deleted file mode 100644 index 91cd509..0000000 --- a/crates/tranquil-db/src/postgres/backup.rs +++ /dev/null @@ -1,300 +0,0 @@ -use async_trait::async_trait; -use chrono::{DateTime, Utc}; -use sqlx::PgPool; -use tranquil_db_traits::{ - BackupForDeletion, BackupRepository, BackupRow, BackupStorageInfo, BlobExportInfo, DbError, - OldBackupInfo, UserBackupInfo, -}; -use tranquil_types::Did; -use uuid::Uuid; - -use super::user::map_sqlx_error; - -pub struct PostgresBackupRepository { - pool: PgPool, -} - -impl PostgresBackupRepository { - pub fn new(pool: PgPool) -> Self { - Self { pool } - } -} - -#[async_trait] -impl BackupRepository for PostgresBackupRepository { - async fn get_user_backup_status(&self, did: &Did) -> Result, DbError> { - let result = sqlx::query!( - "SELECT id, backup_enabled FROM users WHERE did = $1", - did.as_str() - ) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result.map(|r| (r.id, r.backup_enabled))) - } - - async fn list_backups_for_user(&self, user_id: Uuid) -> Result, DbError> { - let results = sqlx::query_as!( - BackupRow, - r#" - SELECT id, repo_rev, repo_root_cid, block_count, size_bytes, created_at - FROM account_backups - WHERE user_id = $1 - ORDER BY created_at DESC - "#, - user_id - ) - .fetch_all(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(results) - } - - async fn get_backup_storage_info( - &self, - backup_id: Uuid, - did: &Did, - ) -> Result, DbError> { - let result = sqlx::query!( - r#" - SELECT ab.storage_key, ab.repo_rev - FROM account_backups ab - JOIN users u ON u.id = ab.user_id - WHERE ab.id = $1 AND u.did = $2 - "#, - backup_id, - did.as_str() - ) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result.map(|r| BackupStorageInfo { - storage_key: r.storage_key, - repo_rev: r.repo_rev, - })) - } - - async fn get_user_for_backup(&self, did: &Did) -> Result, DbError> { - let result = sqlx::query!( - r#" - SELECT u.id, u.did, u.backup_enabled, u.deactivated_at, r.repo_root_cid, r.repo_rev - FROM users u - JOIN repos r ON r.user_id = u.id - WHERE u.did = $1 - "#, - did.as_str() - ) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result.map(|r| UserBackupInfo { - id: r.id, - did: r.did.into(), - backup_enabled: r.backup_enabled, - deactivated_at: r.deactivated_at, - repo_root_cid: r.repo_root_cid, - repo_rev: r.repo_rev, - })) - } - - async fn insert_backup( - &self, - user_id: Uuid, - storage_key: &str, - repo_root_cid: &str, - repo_rev: &str, - block_count: i32, - size_bytes: i64, - ) -> Result { - let id = sqlx::query_scalar!( - r#" - INSERT INTO account_backups (user_id, storage_key, repo_root_cid, repo_rev, block_count, size_bytes) - VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT (storage_key) DO UPDATE SET created_at = NOW() - RETURNING id - "#, - user_id, - storage_key, - repo_root_cid, - repo_rev, - block_count, - size_bytes - ) - .fetch_one(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(id) - } - - async fn get_old_backups( - &self, - user_id: Uuid, - retention_offset: i64, - ) -> Result, DbError> { - let results = sqlx::query!( - r#" - SELECT id, storage_key - FROM account_backups - WHERE user_id = $1 - ORDER BY created_at DESC - OFFSET $2 - "#, - user_id, - retention_offset - ) - .fetch_all(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(results - .into_iter() - .map(|r| OldBackupInfo { - id: r.id, - storage_key: r.storage_key, - }) - .collect()) - } - - async fn delete_backup(&self, backup_id: Uuid) -> Result<(), DbError> { - sqlx::query!("DELETE FROM account_backups WHERE id = $1", backup_id) - .execute(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(()) - } - - async fn get_backup_for_deletion( - &self, - backup_id: Uuid, - did: &Did, - ) -> Result, DbError> { - let result = sqlx::query!( - r#" - SELECT ab.id, ab.storage_key, u.deactivated_at - FROM account_backups ab - JOIN users u ON u.id = ab.user_id - WHERE ab.id = $1 AND u.did = $2 - "#, - backup_id, - did.as_str() - ) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result.map(|r| BackupForDeletion { - id: r.id, - storage_key: r.storage_key, - deactivated_at: r.deactivated_at, - })) - } - - async fn get_user_deactivated_status( - &self, - did: &Did, - ) -> Result>>, DbError> { - let result = sqlx::query!( - "SELECT deactivated_at FROM users WHERE did = $1", - did.as_str() - ) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result.map(|r| r.deactivated_at)) - } - - async fn update_backup_enabled(&self, did: &Did, enabled: bool) -> Result<(), DbError> { - sqlx::query!( - "UPDATE users SET backup_enabled = $1 WHERE did = $2", - enabled, - did.as_str() - ) - .execute(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(()) - } - - async fn get_user_id_by_did(&self, did: &Did) -> Result, DbError> { - let result = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did.as_str()) - .fetch_optional(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(result) - } - - async fn get_blobs_for_export(&self, user_id: Uuid) -> Result, DbError> { - let results = sqlx::query!( - r#" - SELECT DISTINCT b.cid, b.storage_key, b.mime_type - FROM blobs b - JOIN record_blobs rb ON rb.blob_cid = b.cid - WHERE rb.repo_id = $1 - "#, - user_id - ) - .fetch_all(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(results - .into_iter() - .map(|r| BlobExportInfo { - cid: r.cid, - storage_key: r.storage_key, - mime_type: r.mime_type, - }) - .collect()) - } - - async fn get_users_needing_backup( - &self, - backup_interval_secs: i64, - limit: i64, - ) -> Result, DbError> { - let results = sqlx::query!( - r#" - SELECT u.id, u.did, u.backup_enabled, u.deactivated_at, r.repo_root_cid, r.repo_rev - FROM users u - JOIN repos r ON r.user_id = u.id - WHERE u.backup_enabled = true - AND u.deactivated_at IS NULL - AND ( - NOT EXISTS ( - SELECT 1 FROM account_backups ab WHERE ab.user_id = u.id - ) - OR ( - SELECT MAX(ab.created_at) FROM account_backups ab WHERE ab.user_id = u.id - ) < NOW() - make_interval(secs => $1) - ) - LIMIT $2 - "#, - backup_interval_secs as f64, - limit - ) - .fetch_all(&self.pool) - .await - .map_err(map_sqlx_error)?; - - Ok(results - .into_iter() - .map(|r| UserBackupInfo { - id: r.id, - did: r.did.into(), - backup_enabled: r.backup_enabled, - deactivated_at: r.deactivated_at, - repo_root_cid: r.repo_root_cid, - repo_rev: r.repo_rev, - }) - .collect()) - } -} diff --git a/crates/tranquil-db/src/postgres/mod.rs b/crates/tranquil-db/src/postgres/mod.rs index ba173fa..bb61a2d 100644 --- a/crates/tranquil-db/src/postgres/mod.rs +++ b/crates/tranquil-db/src/postgres/mod.rs @@ -1,5 +1,4 @@ mod backlink; -mod backup; mod blob; mod delegation; mod event_notifier; @@ -14,7 +13,6 @@ use sqlx::PgPool; use std::sync::Arc; pub use backlink::PostgresBacklinkRepository; -pub use backup::PostgresBackupRepository; pub use blob::PostgresBlobRepository; pub use delegation::PostgresDelegationRepository; pub use event_notifier::PostgresRepoEventNotifier; @@ -24,9 +22,8 @@ pub use repo::PostgresRepoRepository; pub use session::PostgresSessionRepository; pub use sso::PostgresSsoRepository; use tranquil_db_traits::{ - BacklinkRepository, BackupRepository, BlobRepository, DelegationRepository, InfraRepository, - OAuthRepository, RepoEventNotifier, RepoRepository, SessionRepository, SsoRepository, - UserRepository, + BacklinkRepository, BlobRepository, DelegationRepository, InfraRepository, OAuthRepository, + RepoEventNotifier, RepoRepository, SessionRepository, SsoRepository, UserRepository, }; pub use user::PostgresUserRepository; @@ -39,7 +36,6 @@ pub struct PostgresRepositories { pub repo: Arc, pub blob: Arc, pub infra: Arc, - pub backup: Arc, pub backlink: Arc, pub sso: Arc, pub event_notifier: Arc, @@ -56,7 +52,6 @@ impl PostgresRepositories { repo: Arc::new(PostgresRepoRepository::new(pool.clone())), blob: Arc::new(PostgresBlobRepository::new(pool.clone())), infra: Arc::new(PostgresInfraRepository::new(pool.clone())), - backup: Arc::new(PostgresBackupRepository::new(pool.clone())), backlink: Arc::new(PostgresBacklinkRepository::new(pool.clone())), sso: Arc::new(PostgresSsoRepository::new(pool.clone())), event_notifier: Arc::new(PostgresRepoEventNotifier::new(pool)), diff --git a/crates/tranquil-db/src/postgres/user.rs b/crates/tranquil-db/src/postgres/user.rs index df2f466..1e01790 100644 --- a/crates/tranquil-db/src/postgres/user.rs +++ b/crates/tranquil-db/src/postgres/user.rs @@ -2315,11 +2315,6 @@ impl UserRepository for PostgresUserRepository { .await .map_err(map_sqlx_error)?; - sqlx::query!("DELETE FROM account_backups WHERE user_id = $1", user_id) - .execute(&mut *tx) - .await - .map_err(map_sqlx_error)?; - sqlx::query!( "DELETE FROM account_deletion_requests WHERE did = $1", did.as_str() diff --git a/crates/tranquil-infra/src/lib.rs b/crates/tranquil-infra/src/lib.rs index 73e6faf..9928884 100644 --- a/crates/tranquil-infra/src/lib.rs +++ b/crates/tranquil-infra/src/lib.rs @@ -37,25 +37,6 @@ pub trait BlobStorage: Send + Sync { async fn copy(&self, src_key: &str, dst_key: &str) -> Result<(), StorageError>; } -#[async_trait] -pub trait BackupStorage: Send + Sync { - async fn put_backup(&self, did: &str, rev: &str, data: &[u8]) -> Result; - async fn get_backup(&self, storage_key: &str) -> Result; - async fn delete_backup(&self, storage_key: &str) -> Result<(), StorageError>; -} - -pub fn backup_retention_count() -> u32 { - tranquil_config::try_get() - .map(|c| c.backup.retention_count) - .unwrap_or(7) -} - -pub fn backup_interval_secs() -> u64 { - tranquil_config::try_get() - .map(|c| c.backup.interval_secs) - .unwrap_or(86400) -} - #[derive(Debug, thiserror::Error)] pub enum CacheError { #[error("Cache connection error: {0}")] diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize.rs b/crates/tranquil-oauth-server/src/endpoints/authorize.rs index 5debe8d..5f7ec5a 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize.rs @@ -1,16 +1,3 @@ -use tranquil_pds::auth::{BareLoginIdentifier, NormalizedLoginIdentifier}; -use tranquil_pds::comms::comms_repo::enqueue_2fa_code; -use tranquil_pds::oauth::{ - AuthFlow, ClientMetadataCache, Code, DeviceData, DeviceId, OAuthError, Prompt, SessionId, - db::should_show_consent, scopes::expand_include_scopes, -}; -use tranquil_pds::rate_limit::{ - OAuthAuthorizeLimit, OAuthRateLimited, OAuthRegisterCompleteLimit, TotpVerifyLimit, - check_user_rate_limit, -}; -use tranquil_pds::state::AppState; -use tranquil_pds::types::{Did, Handle, PlainPassword}; -use tranquil_pds::util::extract_client_ip; use axum::{ Json, extract::{Query, State}, @@ -24,6 +11,19 @@ use chrono::Utc; use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; use tranquil_db_traits::{ScopePreference, WebauthnChallengeType}; +use tranquil_pds::auth::{BareLoginIdentifier, NormalizedLoginIdentifier}; +use tranquil_pds::comms::comms_repo::enqueue_2fa_code; +use tranquil_pds::oauth::{ + AuthFlow, ClientMetadataCache, Code, DeviceData, DeviceId, OAuthError, Prompt, SessionId, + db::should_show_consent, scopes::expand_include_scopes, +}; +use tranquil_pds::rate_limit::{ + OAuthAuthorizeLimit, OAuthRateLimited, OAuthRegisterCompleteLimit, TotpVerifyLimit, + check_user_rate_limit, +}; +use tranquil_pds::state::AppState; +use tranquil_pds::types::{Did, Handle, PlainPassword}; +use tranquil_pds::util::extract_client_ip; use tranquil_types::{AuthorizationCode, ClientId, DeviceId as DeviceIdType, RequestId}; use urlencoding::encode as url_encode; @@ -95,7 +95,9 @@ fn extract_device_cookie(headers: &HeaderMap) -> Option {}", controller_did, diff --git a/crates/tranquil-oauth-server/src/endpoints/metadata.rs b/crates/tranquil-oauth-server/src/endpoints/metadata.rs index d441627..c4b26a4 100644 --- a/crates/tranquil-oauth-server/src/endpoints/metadata.rs +++ b/crates/tranquil-oauth-server/src/endpoints/metadata.rs @@ -1,10 +1,10 @@ use std::fmt::Debug; use crate::jwks::{JwkSet, create_jwk_set}; -use tranquil_pds::state::AppState; use axum::{Json, extract::State}; use http::{HeaderName, header}; use serde::{Deserialize, Serialize}; +use tranquil_pds::state::AppState; #[derive(Debug, Serialize, Deserialize)] pub struct ProtectedResourceMetadata { @@ -129,8 +129,8 @@ pub async fn oauth_authorization_server( } pub async fn oauth_jwks(State(_state): State) -> Json { - use tranquil_pds::config::AuthConfig; use crate::jwks::Jwk; + use tranquil_pds::config::AuthConfig; let config = AuthConfig::get(); let server_key = Jwk { kty: "EC".to_string(), diff --git a/crates/tranquil-oauth-server/src/endpoints/par.rs b/crates/tranquil-oauth-server/src/endpoints/par.rs index 623175a..93be937 100644 --- a/crates/tranquil-oauth-server/src/endpoints/par.rs +++ b/crates/tranquil-oauth-server/src/endpoints/par.rs @@ -1,3 +1,7 @@ +use axum::body::Bytes; +use axum::{Json, extract::State, http::HeaderMap}; +use chrono::{Duration, Utc}; +use serde::{Deserialize, Serialize}; use tranquil_pds::oauth::{ AuthorizationRequestParameters, ClientAuth, ClientMetadataCache, CodeChallengeMethod, OAuthError, Prompt, RequestData, RequestId, ResponseMode, ResponseType, @@ -5,10 +9,6 @@ use tranquil_pds::oauth::{ }; use tranquil_pds::rate_limit::{OAuthParLimit, OAuthRateLimited}; use tranquil_pds::state::AppState; -use axum::body::Bytes; -use axum::{Json, extract::State, http::HeaderMap}; -use chrono::{Duration, Utc}; -use serde::{Deserialize, Serialize}; use tranquil_types::RequestId as RequestIdType; const PAR_EXPIRY_SECONDS: i64 = 600; diff --git a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs index 21e12a9..25ae0f1 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/grants.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/grants.rs @@ -2,6 +2,10 @@ use super::helpers::{create_access_token_with_delegation, verify_pkce}; use super::types::{ RequestClientAuth, TokenGrant, TokenResponse, TokenType, ValidatedTokenRequest, }; +use axum::Json; +use axum::http::{HeaderMap, Method}; +use chrono::{Duration, Utc}; +use tranquil_db_traits::RefreshTokenLookup; use tranquil_pds::config::AuthConfig; use tranquil_pds::delegation::intersect_scopes; use tranquil_pds::oauth::{ @@ -12,10 +16,6 @@ use tranquil_pds::oauth::{ verify_client_auth, }; use tranquil_pds::state::AppState; -use axum::Json; -use axum::http::{HeaderMap, Method}; -use chrono::{Duration, Utc}; -use tranquil_db_traits::RefreshTokenLookup; use tranquil_types::{AuthorizationCode, Did, RefreshToken as RefreshTokenType}; const ACCESS_TOKEN_EXPIRY_SECONDS: u64 = 300; diff --git a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs index 91e811f..1ee718c 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/helpers.rs @@ -1,11 +1,11 @@ -use tranquil_pds::config::AuthConfig; -use tranquil_pds::oauth::OAuthError; use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use chrono::Utc; use hmac::Mac; use sha2::Sha256; use subtle::ConstantTimeEq; +use tranquil_pds::config::AuthConfig; +use tranquil_pds::oauth::OAuthError; const ACCESS_TOKEN_EXPIRY_SECONDS: i64 = 300; diff --git a/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs b/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs index e0a8d2c..45680f8 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/introspect.rs @@ -1,12 +1,12 @@ use super::helpers::extract_token_claims; -use tranquil_pds::oauth::OAuthError; -use tranquil_pds::rate_limit::{OAuthIntrospectLimit, OAuthRateLimited}; -use tranquil_pds::state::AppState; use axum::extract::State; use axum::http::StatusCode; use axum::{Form, Json}; use chrono::Utc; use serde::{Deserialize, Serialize}; +use tranquil_pds::oauth::OAuthError; +use tranquil_pds::rate_limit::{OAuthIntrospectLimit, OAuthRateLimited}; +use tranquil_pds::state::AppState; use tranquil_types::{RefreshToken, TokenId}; #[derive(Debug, Deserialize)] diff --git a/crates/tranquil-oauth-server/src/endpoints/token/mod.rs b/crates/tranquil-oauth-server/src/endpoints/token/mod.rs index 24c8cd1..616474c 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/mod.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/mod.rs @@ -3,11 +3,11 @@ mod helpers; mod introspect; mod types; +use axum::body::Bytes; +use axum::{Json, extract::State, http::HeaderMap}; use tranquil_pds::oauth::OAuthError; use tranquil_pds::rate_limit::{OAuthRateLimited, OAuthTokenLimit}; use tranquil_pds::state::AppState; -use axum::body::Bytes; -use axum::{Json, extract::State, http::HeaderMap}; pub use grants::{handle_authorization_code_grant, handle_refresh_token_grant}; pub use helpers::{TokenClaims, create_access_token, extract_token_claims, verify_pkce}; diff --git a/crates/tranquil-oauth-server/src/endpoints/token/types.rs b/crates/tranquil-oauth-server/src/endpoints/token/types.rs index d070a2d..8f7342f 100644 --- a/crates/tranquil-oauth-server/src/endpoints/token/types.rs +++ b/crates/tranquil-oauth-server/src/endpoints/token/types.rs @@ -1,5 +1,5 @@ -use tranquil_pds::oauth::OAuthError; use serde::{Deserialize, Serialize}; +use tranquil_pds::oauth::OAuthError; #[derive(Debug, Clone, PartialEq, Eq)] pub enum GrantType { diff --git a/crates/tranquil-oauth-server/src/lib.rs b/crates/tranquil-oauth-server/src/lib.rs index 4d85a31..82f1bbf 100644 --- a/crates/tranquil-oauth-server/src/lib.rs +++ b/crates/tranquil-oauth-server/src/lib.rs @@ -5,21 +5,18 @@ pub mod sso_endpoints; use tranquil_pds::state::AppState; pub fn oauth_routes() -> axum::Router { - use axum::{middleware, routing::{get, post}}; + use axum::{ + middleware, + routing::{get, post}, + }; axum::Router::new() .route("/jwks", get(endpoints::oauth_jwks)) .route("/par", post(endpoints::pushed_authorization_request)) .route("/authorize", get(endpoints::authorize_get)) .route("/authorize", post(endpoints::authorize_post)) - .route( - "/authorize/accounts", - get(endpoints::authorize_accounts), - ) - .route( - "/authorize/select", - post(endpoints::authorize_select), - ) + .route("/authorize/accounts", get(endpoints::authorize_accounts)) + .route("/authorize/select", post(endpoints::authorize_select)) .route("/authorize/2fa", get(endpoints::authorize_2fa_get)) .route("/authorize/2fa", post(endpoints::authorize_2fa_post)) .route( @@ -30,10 +27,7 @@ pub fn oauth_routes() -> axum::Router { "/authorize/passkey", post(endpoints::authorize_passkey_finish), ) - .route( - "/passkey/check", - get(endpoints::check_user_has_passkeys), - ) + .route("/passkey/check", get(endpoints::check_user_has_passkeys)) .route( "/security-status", get(endpoints::check_user_security_status), @@ -41,34 +35,19 @@ pub fn oauth_routes() -> axum::Router { .route("/passkey/start", post(endpoints::passkey_start)) .route("/passkey/finish", post(endpoints::passkey_finish)) .route("/authorize/deny", post(endpoints::authorize_deny)) - .route( - "/register/complete", - post(endpoints::register_complete), - ) - .route( - "/establish-session", - post(endpoints::establish_session), - ) + .route("/register/complete", post(endpoints::register_complete)) + .route("/establish-session", post(endpoints::establish_session)) .route("/authorize/consent", get(endpoints::consent_get)) .route("/authorize/consent", post(endpoints::consent_post)) .route("/authorize/renew", post(endpoints::authorize_renew)) - .route( - "/authorize/redirect", - get(endpoints::authorize_redirect), - ) + .route("/authorize/redirect", get(endpoints::authorize_redirect)) .route("/delegation/auth", post(endpoints::delegation_auth)) .route( "/delegation/auth-token", post(endpoints::delegation_auth_token), ) - .route( - "/delegation/totp", - post(endpoints::delegation_totp_verify), - ) - .route( - "/delegation/callback", - get(endpoints::delegation_callback), - ) + .route("/delegation/totp", post(endpoints::delegation_totp_verify)) + .route("/delegation/callback", get(endpoints::delegation_callback)) .route( "/delegation/client-metadata", get(endpoints::delegation_client_metadata), @@ -96,7 +75,9 @@ pub fn oauth_routes() -> axum::Router { "/sso/check-handle-available", get(sso_endpoints::check_handle_available), ) - .layer(middleware::from_fn(tranquil_pds::oauth::verify::dpop_nonce_middleware)) + .layer(middleware::from_fn( + tranquil_pds::oauth::verify::dpop_nonce_middleware, + )) } pub fn well_known_oauth_routes() -> axum::Router { @@ -116,9 +97,8 @@ pub fn well_known_oauth_routes() -> axum::Router { pub fn frontend_client_metadata_route() -> axum::Router { use axum::routing::get; - axum::Router::new() - .route( - "/oauth-client-metadata.json", - get(endpoints::frontend_client_metadata), - ) + axum::Router::new().route( + "/oauth-client-metadata.json", + get(endpoints::frontend_client_metadata), + ) } diff --git a/crates/tranquil-oauth-server/src/sso_endpoints.rs b/crates/tranquil-oauth-server/src/sso_endpoints.rs index 7d0ebb0..136ed73 100644 --- a/crates/tranquil-oauth-server/src/sso_endpoints.rs +++ b/crates/tranquil-oauth-server/src/sso_endpoints.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; use tranquil_db_traits::{SsoAction, SsoProviderType}; use tranquil_types::RequestId; -use tranquil_pds::sso::SsoConfig; use tranquil_pds::api::error::ApiError; use tranquil_pds::auth::extractor::extract_auth_token_from_header; use tranquil_pds::auth::{generate_app_password, validate_bearer_token_cached}; @@ -17,6 +16,7 @@ use tranquil_pds::rate_limit::{ AccountCreationLimit, RateLimited, SsoCallbackLimit, SsoInitiateLimit, SsoUnlinkLimit, check_user_rate_limit_with_message, }; +use tranquil_pds::sso::SsoConfig; use tranquil_pds::state::AppState; fn generate_nonce() -> String { @@ -1048,7 +1048,8 @@ pub async fn complete_registration( } }; - let plc_client = tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone())); + let plc_client = + tranquil_pds::plc::PlcClient::with_cache(None, Some(state.cache.clone())); if let Err(e) = plc_client .send_operation(&genesis_result.did, &genesis_result.signed_operation) .await @@ -1291,14 +1292,14 @@ pub async fn complete_registration( } }; - let access_meta = match tranquil_pds::auth::create_access_token_with_metadata(&did, &key_bytes) - { - Ok(m) => m, - Err(e) => { - tracing::error!("Failed to create access token: {:?}", e); - return Err(ApiError::InternalError(None)); - } - }; + let access_meta = + match tranquil_pds::auth::create_access_token_with_metadata(&did, &key_bytes) { + Ok(m) => m, + Err(e) => { + tracing::error!("Failed to create access token: {:?}", e); + return Err(ApiError::InternalError(None)); + } + }; let refresh_meta = match tranquil_pds::auth::create_refresh_token_with_metadata(&did, &key_bytes) { Ok(m) => m, diff --git a/crates/tranquil-oauth/src/lib.rs b/crates/tranquil-oauth/src/lib.rs index d12e1f1..d90d7b3 100644 --- a/crates/tranquil-oauth/src/lib.rs +++ b/crates/tranquil-oauth/src/lib.rs @@ -7,8 +7,7 @@ pub use client::{ClientMetadata, ClientMetadataCache, verify_client_auth}; pub use dpop::{ DPoPJwk, DPoPProofHeader, DPoPProofPayload, DPoPVerifier, DPoPVerifyResult, compute_access_token_hash, compute_es256_jkt, compute_jwk_thumbprint, compute_pkce_challenge, - create_dpop_proof, - es256_signing_key_to_jwk, + create_dpop_proof, es256_signing_key_to_jwk, }; pub use error::OAuthError; pub use types::{ diff --git a/crates/tranquil-pds/src/api/error.rs b/crates/tranquil-pds/src/api/error.rs index 18fec81..2a7d03e 100644 --- a/crates/tranquil-pds/src/api/error.rs +++ b/crates/tranquil-pds/src/api/error.rs @@ -72,8 +72,6 @@ pub enum ApiError { InvalidDelegation(String), DelegationNotFound, InviteCodeRequired, - BackupNotFound, - BackupsDisabled, RepoNotReady, DeviceNotFound, NoEmail, @@ -124,7 +122,7 @@ impl ApiError { Self::UpstreamFailure | Self::UpstreamUnavailable(_) | Self::UpstreamErrorMsg(_) => { StatusCode::BAD_GATEWAY } - Self::ServiceUnavailable(_) | Self::BackupsDisabled => StatusCode::SERVICE_UNAVAILABLE, + Self::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE, Self::UpstreamTimeout => StatusCode::GATEWAY_TIMEOUT, Self::UpstreamError { status, .. } => *status, Self::AuthenticationRequired @@ -155,7 +153,6 @@ impl ApiError { | Self::DeviceNotFound | Self::ControllerNotFound | Self::DelegationNotFound - | Self::BackupNotFound | Self::InvalidRecoveryLink | Self::HandleNotFound | Self::SubjectNotFound @@ -283,8 +280,6 @@ impl ApiError { Self::InvalidDelegation(_) => Cow::Borrowed("InvalidDelegation"), Self::DelegationNotFound => Cow::Borrowed("DelegationNotFound"), Self::InviteCodeRequired => Cow::Borrowed("InviteCodeRequired"), - Self::BackupNotFound => Cow::Borrowed("BackupNotFound"), - Self::BackupsDisabled => Cow::Borrowed("BackupsDisabled"), Self::RepoNotReady => Cow::Borrowed("RepoNotReady"), Self::MfaVerificationRequired => Cow::Borrowed("MfaVerificationRequired"), Self::RateLimitExceeded(_) => Cow::Borrowed("RateLimitExceeded"), @@ -373,9 +368,7 @@ impl ApiError { Self::InviteCodeRequired => { Some("An invite code is required to create an account".to_string()) } - Self::BackupNotFound => Some("Backup not found".to_string()), - Self::BackupsDisabled => Some("Backup storage not configured".to_string()), - Self::RepoNotReady => Some("Repository not ready for backup".to_string()), + Self::RepoNotReady => Some("Repository not ready".to_string()), Self::PasskeyCounterAnomaly => Some( "Authentication failed: security key counter anomaly detected. This may indicate a cloned key.".to_string(), ), diff --git a/crates/tranquil-pds/src/api/validation.rs b/crates/tranquil-pds/src/api/validation.rs index 6eb31a9..f8f5969 100644 --- a/crates/tranquil-pds/src/api/validation.rs +++ b/crates/tranquil-pds/src/api/validation.rs @@ -317,9 +317,7 @@ pub fn resolve_handle_input(input: &str) -> Result input - .strip_suffix(&format!(".{}", domain)) - .unwrap_or(input), + Some(domain) => input.strip_suffix(&format!(".{}", domain)).unwrap_or(input), None => input, }; let validated = validate_short_handle(handle_to_validate)?; diff --git a/crates/tranquil-pds/src/auth/mod.rs b/crates/tranquil-pds/src/auth/mod.rs index 84b27f8..22c2bd4 100644 --- a/crates/tranquil-pds/src/auth/mod.rs +++ b/crates/tranquil-pds/src/auth/mod.rs @@ -13,10 +13,10 @@ use tranquil_db_traits::OAuthRepository; pub mod account_verified; pub mod email_token; pub mod extractor; -pub mod reauth; pub mod legacy_2fa; pub mod login_identifier; pub mod mfa_verified; +pub mod reauth; pub mod scope_check; pub mod scope_verified; pub mod service; @@ -206,7 +206,9 @@ impl AuthenticatedUser { return ScopePermissions::from_scope_string(Some(scope)); } if !self.is_oauth() { - return ScopePermissions::from_scope_string(Some("transition:generic transition:chat.bsky")); + return ScopePermissions::from_scope_string(Some( + "transition:generic transition:chat.bsky", + )); } ScopePermissions::from_scope_string(self.scope.as_deref()) } diff --git a/crates/tranquil-pds/src/crawlers.rs b/crates/tranquil-pds/src/crawlers.rs index deff0ca..c061e12 100644 --- a/crates/tranquil-pds/src/crawlers.rs +++ b/crates/tranquil-pds/src/crawlers.rs @@ -1,5 +1,4 @@ use crate::circuit_breaker::CircuitBreaker; -use tranquil_db_traits::SequencedEvent; use reqwest::Client; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; @@ -8,6 +7,7 @@ use tokio::sync::broadcast; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, warn}; use tranquil_db_traits::RepoEventType; +use tranquil_db_traits::SequencedEvent; const NOTIFY_THRESHOLD_SECS: u64 = 20 * 60; diff --git a/crates/tranquil-pds/src/delegation/mod.rs b/crates/tranquil-pds/src/delegation/mod.rs index 43b21b6..f8d5eef 100644 --- a/crates/tranquil-pds/src/delegation/mod.rs +++ b/crates/tranquil-pds/src/delegation/mod.rs @@ -2,8 +2,7 @@ pub mod roles; pub mod scopes; pub use roles::{ - CanAddControllers, CanControlAccounts, verify_can_add_controllers, - verify_can_control_accounts, + CanAddControllers, CanControlAccounts, verify_can_add_controllers, verify_can_control_accounts, }; pub use scopes::{ InvalidDelegationScopeError, SCOPE_PRESETS, ScopePreset, ValidatedDelegationScope, diff --git a/crates/tranquil-pds/src/delegation/roles.rs b/crates/tranquil-pds/src/delegation/roles.rs index 7e91e8b..d3a5a93 100644 --- a/crates/tranquil-pds/src/delegation/roles.rs +++ b/crates/tranquil-pds/src/delegation/roles.rs @@ -81,4 +81,3 @@ pub async fn verify_can_control_accounts<'a>( _tag: PhantomData, }) } - diff --git a/crates/tranquil-pds/src/delegation/scopes.rs b/crates/tranquil-pds/src/delegation/scopes.rs index 2d223de..e9efd8c 100644 --- a/crates/tranquil-pds/src/delegation/scopes.rs +++ b/crates/tranquil-pds/src/delegation/scopes.rs @@ -214,8 +214,10 @@ mod tests { #[test] fn test_intersect_granted_with_params_covers_requested_no_params() { - let result = - intersect_scopes("repo:app.bsky.feed.post", "repo:*?action=create&action=delete"); + let result = intersect_scopes( + "repo:app.bsky.feed.post", + "repo:*?action=create&action=delete", + ); assert_eq!(result, "repo:app.bsky.feed.post"); } @@ -231,16 +233,16 @@ mod tests { #[test] fn test_scope_covers_base_only() { assert!(scope_covers("repo:*", "repo:app.bsky.feed.post")); - assert!(scope_covers("repo:*", "repo:app.bsky.feed.post?action=create")); + assert!(scope_covers( + "repo:*", + "repo:app.bsky.feed.post?action=create" + )); assert!(!scope_covers("blob:*/*", "repo:app.bsky.feed.post")); } #[test] fn test_scope_covers_params() { - assert!(scope_covers( - "repo:*?action=create", - "repo:*?action=create" - )); + assert!(scope_covers("repo:*?action=create", "repo:*?action=create")); assert!(!scope_covers( "repo:*?action=create", "repo:*?action=delete" diff --git a/crates/tranquil-pds/src/lib.rs b/crates/tranquil-pds/src/lib.rs index 0962071..d9fe982 100644 --- a/crates/tranquil-pds/src/lib.rs +++ b/crates/tranquil-pds/src/lib.rs @@ -29,13 +29,7 @@ pub mod util; pub mod validation; use api::proxy::XrpcProxyLayer; -use axum::{ - Json, Router, - extract::DefaultBodyLimit, - http::Method, - middleware, - routing::get, -}; +use axum::{Json, Router, extract::DefaultBodyLimit, http::Method, middleware, routing::get}; use http::StatusCode; use serde_json::json; use state::AppState; diff --git a/crates/tranquil-pds/src/oauth/client.rs b/crates/tranquil-pds/src/oauth/client.rs index 8a09d38..1ef482e 100644 --- a/crates/tranquil-pds/src/oauth/client.rs +++ b/crates/tranquil-pds/src/oauth/client.rs @@ -93,19 +93,18 @@ impl CrossPdsOAuthClient { state_key: &str, ) -> Result { let cache_key = format!("cross_pds_state:{}", state_key); - let encrypted_bytes = self - .cache - .get_bytes(&cache_key) - .await - .ok_or_else(|| CrossPdsError::TokenExchangeFailed("auth state expired or not found".into()))?; + let encrypted_bytes = self.cache.get_bytes(&cache_key).await.ok_or_else(|| { + CrossPdsError::TokenExchangeFailed("auth state expired or not found".into()) + })?; let _ = self.cache.delete(&cache_key).await; - let decrypted = crate::config::decrypt_key( - &encrypted_bytes, - Some(crate::config::ENCRYPTION_VERSION), - ) - .map_err(|e| CrossPdsError::TokenExchangeFailed(format!("decrypt auth state: {}", e)))?; - serde_json::from_slice(&decrypted) - .map_err(|e| CrossPdsError::TokenExchangeFailed(format!("deserialize auth state: {}", e))) + let decrypted = + crate::config::decrypt_key(&encrypted_bytes, Some(crate::config::ENCRYPTION_VERSION)) + .map_err(|e| { + CrossPdsError::TokenExchangeFailed(format!("decrypt auth state: {}", e)) + })?; + serde_json::from_slice(&decrypted).map_err(|e| { + CrossPdsError::TokenExchangeFailed(format!("deserialize auth state: {}", e)) + }) } pub async fn check_remote_is_delegated(&self, pds_url: &str, did: &str) -> Option { @@ -142,19 +141,34 @@ impl CrossPdsOAuthClient { .map_err(|e| format!("{:?}", e)) }; - let resp = self.http.post(url).header("DPoP", &make_proof(None)?).form(params) - .send().await.map_err(|e| e.to_string())?; + let resp = self + .http + .post(url) + .header("DPoP", &make_proof(None)?) + .form(params) + .send() + .await + .map_err(|e| e.to_string())?; - let nonce = resp.headers().get("dpop-nonce") - .and_then(|v| v.to_str().ok()).map(|s| s.to_string()); + let nonce = resp + .headers() + .get("dpop-nonce") + .and_then(|v| v.to_str().ok()) + .map(|s| s.to_string()); let needs_retry = matches!( resp.status(), reqwest::StatusCode::BAD_REQUEST | reqwest::StatusCode::UNAUTHORIZED ); if needs_retry && nonce.is_some() { - return self.http.post(url).header("DPoP", &make_proof(nonce.as_deref())?) - .form(params).send().await.map_err(|e| e.to_string()); + return self + .http + .post(url) + .header("DPoP", &make_proof(nonce.as_deref())?) + .form(params) + .send() + .await + .map_err(|e| e.to_string()); } Ok(resp) } diff --git a/crates/tranquil-pds/src/scheduled.rs b/crates/tranquil-pds/src/scheduled.rs index b1390f6..1e92a1f 100644 --- a/crates/tranquil-pds/src/scheduled.rs +++ b/crates/tranquil-pds/src/scheduled.rs @@ -10,13 +10,13 @@ use tokio::time::interval; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, warn}; use tranquil_db_traits::{ - BackupRepository, BlobRepository, BrokenGenesisCommit, RepoRepository, SequenceNumber, - SsoRepository, UserRepository, + BlobRepository, BrokenGenesisCommit, RepoRepository, SequenceNumber, SsoRepository, + UserRepository, }; use tranquil_types::{AtUri, CidLink, Did}; use crate::repo::PostgresBlockStore; -use crate::storage::{BackupStorage, BlobStorage, backup_interval_secs, backup_retention_count}; +use crate::storage::BlobStorage; use crate::sync::car::encode_car_header; #[derive(Debug)] @@ -571,194 +571,6 @@ async fn delete_account_data( Ok(()) } -pub async fn start_backup_tasks( - repo_repo: Arc, - backup_repo: Arc, - block_store: PostgresBlockStore, - backup_storage: Arc, - shutdown: CancellationToken, -) { - let backup_interval = Duration::from_secs(backup_interval_secs()); - - info!( - interval_secs = backup_interval.as_secs(), - retention_count = backup_retention_count(), - "Starting backup service" - ); - - let mut ticker = interval(backup_interval); - ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - - loop { - tokio::select! { - _ = shutdown.cancelled() => { - info!("Backup service shutting down"); - break; - } - _ = ticker.tick() => { - if let Err(e) = process_scheduled_backups( - repo_repo.as_ref(), - backup_repo.as_ref(), - &block_store, - backup_storage.as_ref(), - ).await { - error!("Error processing scheduled backups: {}", e); - } - } - } - } -} - -struct BackupResult { - did: Did, - repo_rev: String, - size_bytes: i64, - block_count: i32, - user_id: uuid::Uuid, -} - -enum BackupOutcome { - Success(BackupResult), - Skipped(Did, &'static str), - Failed(Did, String), -} - -#[allow(clippy::too_many_arguments)] -async fn process_single_backup( - repo_repo: &dyn RepoRepository, - backup_repo: &dyn BackupRepository, - block_store: &PostgresBlockStore, - backup_storage: &dyn BackupStorage, - user_id: uuid::Uuid, - did: Did, - repo_root_cid: String, - repo_rev: Option, -) -> BackupOutcome { - let repo_rev = match repo_rev { - Some(rev) => rev, - None => return BackupOutcome::Skipped(did, "no repo_rev"), - }; - - let head_cid = match Cid::from_str(&repo_root_cid) { - Ok(c) => c, - Err(_) => return BackupOutcome::Skipped(did, "invalid repo_root_cid"), - }; - - let car_bytes = match generate_full_backup(repo_repo, block_store, user_id, &head_cid).await { - Ok(bytes) => bytes, - Err(e) => return BackupOutcome::Failed(did, format!("CAR generation: {}", e)), - }; - - let block_count = count_car_blocks(&car_bytes); - let size_bytes = i64::try_from(car_bytes.len()).unwrap_or(i64::MAX); - - let storage_key = match backup_storage - .put_backup(did.as_str(), &repo_rev, &car_bytes) - .await - { - Ok(key) => key, - Err(e) => return BackupOutcome::Failed(did, format!("S3 upload: {}", e)), - }; - - if let Err(e) = backup_repo - .insert_backup( - user_id, - &storage_key, - &repo_root_cid, - &repo_rev, - block_count, - size_bytes, - ) - .await - { - if let Err(rollback_err) = backup_storage.delete_backup(&storage_key).await { - error!( - did = %did, - storage_key = %storage_key, - error = %rollback_err, - "Failed to rollback orphaned backup from S3" - ); - } - return BackupOutcome::Failed(did, format!("DB insert: {:?}", e)); - } - - BackupOutcome::Success(BackupResult { - did, - repo_rev, - size_bytes, - block_count, - user_id, - }) -} - -async fn process_scheduled_backups( - repo_repo: &dyn RepoRepository, - backup_repo: &dyn BackupRepository, - block_store: &PostgresBlockStore, - backup_storage: &dyn BackupStorage, -) -> anyhow::Result<()> { - let interval_secs = i64::try_from(backup_interval_secs()).unwrap_or(i64::MAX); - let retention = backup_retention_count(); - - let users_needing_backup = backup_repo - .get_users_needing_backup(interval_secs, 50) - .await - .context("DB error fetching users for backup")?; - - if users_needing_backup.is_empty() { - debug!("No accounts need backup"); - return Ok(()); - } - - info!( - count = users_needing_backup.len(), - "Processing scheduled backups" - ); - - let results = futures::future::join_all(users_needing_backup.into_iter().map(|user| { - process_single_backup( - repo_repo, - backup_repo, - block_store, - backup_storage, - user.id, - user.did, - user.repo_root_cid.to_string(), - user.repo_rev, - ) - })) - .await; - - futures::future::join_all(results.into_iter().map(|outcome| async move { - match outcome { - BackupOutcome::Success(result) => { - info!( - did = %result.did, - rev = %result.repo_rev, - size_bytes = result.size_bytes, - block_count = result.block_count, - "Created backup" - ); - if let Err(e) = - cleanup_old_backups(backup_repo, backup_storage, result.user_id, retention) - .await - { - warn!(did = %result.did, error = %e, "Failed to cleanup old backups"); - } - } - BackupOutcome::Skipped(did, reason) => { - warn!(did = %did, reason = reason, "Skipped backup"); - } - BackupOutcome::Failed(did, error) => { - warn!(did = %did, error = %error, "Failed backup"); - } - } - })) - .await; - - Ok(()) -} - pub async fn generate_repo_car( block_store: &PostgresBlockStore, head_cid: &Cid, @@ -771,7 +583,7 @@ pub async fn generate_repo_car( .filter_map(|b| match Cid::try_from(b.as_slice()) { Ok(cid) => Some(cid), Err(e) => { - tracing::warn!(error = %e, "skipping unparseable CID in backup generation"); + tracing::warn!(error = %e, "skipping unparseable CID in CAR generation"); None } }) @@ -831,105 +643,3 @@ pub async fn generate_repo_car_from_user_blocks( generate_repo_car(block_store, &actual_head_cid).await } - -pub async fn generate_full_backup( - repo_repo: &dyn tranquil_db_traits::RepoRepository, - block_store: &PostgresBlockStore, - user_id: uuid::Uuid, - head_cid: &Cid, -) -> anyhow::Result> { - generate_repo_car_from_user_blocks(repo_repo, block_store, user_id, head_cid).await -} - -pub fn count_car_blocks(car_bytes: &[u8]) -> i32 { - let mut count: i32 = 0; - let mut pos: usize = 0; - - if let Some((header_len, header_varint_len)) = read_varint(&car_bytes[pos..]) { - let Some(header_size) = usize::try_from(header_len).ok() else { - return 0; - }; - let Some(next_pos) = header_varint_len - .checked_add(header_size) - .and_then(|skip| pos.checked_add(skip)) - else { - return 0; - }; - pos = next_pos; - } else { - return 0; - } - - while pos < car_bytes.len() { - if let Some((block_len, varint_len)) = read_varint(&car_bytes[pos..]) { - let Some(block_size) = usize::try_from(block_len).ok() else { - break; - }; - let Some(next_pos) = varint_len - .checked_add(block_size) - .and_then(|skip| pos.checked_add(skip)) - else { - break; - }; - pos = next_pos; - count = count.saturating_add(1); - } else { - break; - } - } - - count -} - -fn read_varint(data: &[u8]) -> Option<(u64, usize)> { - let mut value: u64 = 0; - let mut shift = 0; - let mut pos = 0; - - while pos < data.len() && pos < 10 { - let byte = data[pos]; - value |= ((byte & 0x7f) as u64) << shift; - pos += 1; - if byte & 0x80 == 0 { - return Some((value, pos)); - } - shift += 7; - } - - None -} - -async fn cleanup_old_backups( - backup_repo: &dyn BackupRepository, - backup_storage: &dyn BackupStorage, - user_id: uuid::Uuid, - retention_count: u32, -) -> anyhow::Result<()> { - let old_backups = backup_repo - .get_old_backups(user_id, i64::from(retention_count)) - .await - .context("DB error fetching old backups")?; - - let results = futures::future::join_all(old_backups.into_iter().map(|backup| async move { - match backup_storage.delete_backup(&backup.storage_key).await { - Ok(()) => backup_repo - .delete_backup(backup.id) - .await - .with_context(|| format!("DB delete failed for {}", backup.storage_key)), - Err(e) => { - warn!( - storage_key = %backup.storage_key, - error = %e, - "Failed to delete old backup from storage, skipping DB cleanup to avoid orphan" - ); - Ok(()) - } - } - })) - .await; - - results - .into_iter() - .find_map(|r| r.err()) - .map_or(Ok(()), Err) -} diff --git a/crates/tranquil-pds/src/state.rs b/crates/tranquil-pds/src/state.rs index 85a47b8..ace7940 100644 --- a/crates/tranquil-pds/src/state.rs +++ b/crates/tranquil-pds/src/state.rs @@ -9,8 +9,7 @@ use crate::rate_limit::RateLimiters; use crate::repo::PostgresBlockStore; use crate::repo_write_lock::RepoWriteLocks; use crate::sso::{SsoConfig, SsoManager}; -use crate::storage::{BackupStorage, BlobStorage, create_backup_storage, create_blob_storage}; -use tranquil_db_traits::SequencedEvent; +use crate::storage::{BlobStorage, create_blob_storage}; use sqlx::PgPool; use std::error::Error; use std::sync::Arc; @@ -18,10 +17,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::broadcast; use tokio_util::sync::CancellationToken; use tranquil_db::{ - BacklinkRepository, BackupRepository, BlobRepository, DelegationRepository, InfraRepository, - OAuthRepository, PostgresRepositories, RepoEventNotifier, RepoRepository, SessionRepository, - SsoRepository, UserRepository, + BacklinkRepository, BlobRepository, DelegationRepository, InfraRepository, OAuthRepository, + PostgresRepositories, RepoEventNotifier, RepoRepository, SessionRepository, SsoRepository, + UserRepository, }; +use tranquil_db_traits::SequencedEvent; static RATE_LIMITING_DISABLED: AtomicBool = AtomicBool::new(false); @@ -43,12 +43,10 @@ pub struct AppState { pub repo_repo: Arc, pub blob_repo: Arc, pub infra_repo: Arc, - pub backup_repo: Arc, pub backlink_repo: Arc, pub event_notifier: Arc, pub block_store: PostgresBlockStore, pub blob_store: Arc, - pub backup_storage: Option>, pub firehose_tx: broadcast::Sender, pub rate_limiters: Arc, pub repo_write_locks: Arc, @@ -269,7 +267,6 @@ impl AppState { let repos = Arc::new(PostgresRepositories::new(db.clone())); let block_store = PostgresBlockStore::new(db); let blob_store = create_blob_storage().await; - let backup_storage = create_backup_storage().await; let firehose_buffer_size = tranquil_config::get().firehose.buffer_size; @@ -295,14 +292,12 @@ impl AppState { repo_repo: repos.repo.clone(), blob_repo: repos.blob.clone(), infra_repo: repos.infra.clone(), - backup_repo: repos.backup.clone(), backlink_repo: repos.backlink.clone(), event_notifier: repos.event_notifier.clone(), sso_repo: repos.sso.clone(), repos, block_store, blob_store, - backup_storage, firehose_tx, rate_limiters, repo_write_locks, diff --git a/crates/tranquil-pds/src/storage/mod.rs b/crates/tranquil-pds/src/storage/mod.rs index db45e73..58e25d9 100644 --- a/crates/tranquil-pds/src/storage/mod.rs +++ b/crates/tranquil-pds/src/storage/mod.rs @@ -1,8 +1,6 @@ pub use tranquil_storage::{ - BackupStorage, BlobStorage, FilesystemBackupStorage, FilesystemBlobStorage, StorageError, - StreamUploadResult, backup_interval_secs, backup_retention_count, create_backup_storage, - create_blob_storage, + BlobStorage, FilesystemBlobStorage, StorageError, StreamUploadResult, create_blob_storage, }; #[cfg(feature = "s3-storage")] -pub use tranquil_storage::{S3BackupStorage, S3BlobStorage}; +pub use tranquil_storage::S3BlobStorage; diff --git a/crates/tranquil-pds/src/sync/mod.rs b/crates/tranquil-pds/src/sync/mod.rs index 54cbf99..077c5f2 100644 --- a/crates/tranquil-pds/src/sync/mod.rs +++ b/crates/tranquil-pds/src/sync/mod.rs @@ -5,9 +5,6 @@ pub mod import; pub mod util; pub mod verify; -#[cfg(test)] -mod verify_tests; - pub use firehose::SequencedEvent; pub use util::{ RepoAccessLevel, RepoAccount, RepoAvailabilityError, assert_repo_availability, diff --git a/crates/tranquil-pds/src/sync/verify.rs b/crates/tranquil-pds/src/sync/verify.rs index 923ddaa..44f59d6 100644 --- a/crates/tranquil-pds/src/sync/verify.rs +++ b/crates/tranquil-pds/src/sync/verify.rs @@ -130,7 +130,10 @@ impl CarVerifier { .ok_or(VerifyError::NoSigningKey) } - pub(crate) async fn resolve_did_document(&self, did: &Did) -> Result, VerifyError> { + pub(crate) async fn resolve_did_document( + &self, + did: &Did, + ) -> Result, VerifyError> { let did_str = did.as_str(); if did_str.starts_with("did:plc:") { self.resolve_plc_did(did_str).await diff --git a/crates/tranquil-pds/src/util.rs b/crates/tranquil-pds/src/util.rs index 9b004d1..de09056 100644 --- a/crates/tranquil-pds/src/util.rs +++ b/crates/tranquil-pds/src/util.rs @@ -210,6 +210,27 @@ pub fn json_to_ipld(value: &JsonValue) -> Ipld { } } +pub(crate) fn gen_invite_random_token() -> String { + let mut rng = rand::thread_rng(); + let chars: Vec = BASE32_ALPHABET.chars().collect(); + let gen_segment = |rng: &mut rand::rngs::ThreadRng, len: usize| -> String { + (0..len) + .map(|_| chars[rng.gen_range(0..chars.len())]) + .collect() + }; + format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5)) +} + +pub fn gen_invite_code() -> String { + let hostname = &tranquil_config::get().server.hostname; + let hostname_prefix = hostname.replace('.', "-"); + format!("{}-{}", hostname_prefix, gen_invite_random_token()) +} + +pub fn is_self_hosted_did_web_enabled() -> bool { + tranquil_config::get().server.enable_pds_hosted_did_web +} + #[cfg(test)] mod tests { use super::*; @@ -548,24 +569,3 @@ mod tests { ); } } - -pub(crate) fn gen_invite_random_token() -> String { - let mut rng = rand::thread_rng(); - let chars: Vec = BASE32_ALPHABET.chars().collect(); - let gen_segment = |rng: &mut rand::rngs::ThreadRng, len: usize| -> String { - (0..len) - .map(|_| chars[rng.gen_range(0..chars.len())]) - .collect() - }; - format!("{}-{}", gen_segment(&mut rng, 5), gen_segment(&mut rng, 5)) -} - -pub fn gen_invite_code() -> String { - let hostname = &tranquil_config::get().server.hostname; - let hostname_prefix = hostname.replace('.', "-"); - format!("{}-{}", hostname_prefix, gen_invite_random_token()) -} - -pub fn is_self_hosted_did_web_enabled() -> bool { - tranquil_config::get().server.enable_pds_hosted_did_web -} diff --git a/crates/tranquil-pds/tests/backup.rs b/crates/tranquil-pds/tests/backup.rs deleted file mode 100644 index f9839a9..0000000 --- a/crates/tranquil-pds/tests/backup.rs +++ /dev/null @@ -1,325 +0,0 @@ -mod common; -mod helpers; - -use common::*; -use reqwest::{StatusCode, header}; -use serde_json::{Value, json}; - -#[tokio::test] -async fn test_list_backups_empty() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let res = client - .get(format!("{}/xrpc/_backup.listBackups", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("listBackups request failed"); - - assert_eq!(res.status(), StatusCode::OK); - let body: Value = res.json().await.expect("Invalid JSON"); - assert!(body["backups"].is_array()); - assert_eq!(body["backups"].as_array().unwrap().len(), 0); - assert!(body["backupEnabled"].as_bool().unwrap_or(false)); -} - -#[tokio::test] -async fn test_create_and_list_backup() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let create_res = client - .post(format!("{}/xrpc/_backup.createBackup", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("createBackup request failed"); - - assert_eq!(create_res.status(), StatusCode::OK, "createBackup failed"); - let create_body: Value = create_res.json().await.expect("Invalid JSON"); - assert!(create_body["id"].is_string()); - assert!(create_body["repoRev"].is_string()); - assert!(create_body["sizeBytes"].is_i64()); - assert!(create_body["blockCount"].is_i64()); - - let list_res = client - .get(format!("{}/xrpc/_backup.listBackups", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("listBackups request failed"); - - assert_eq!(list_res.status(), StatusCode::OK); - let list_body: Value = list_res.json().await.expect("Invalid JSON"); - let backups = list_body["backups"].as_array().unwrap(); - assert!(!backups.is_empty()); -} - -#[tokio::test] -async fn test_download_backup() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let create_res = client - .post(format!("{}/xrpc/_backup.createBackup", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("createBackup request failed"); - - assert_eq!(create_res.status(), StatusCode::OK); - let create_body: Value = create_res.json().await.expect("Invalid JSON"); - let backup_id = create_body["id"].as_str().unwrap(); - - let get_res = client - .get(format!( - "{}/xrpc/_backup.getBackup?id={}", - base_url().await, - backup_id - )) - .bearer_auth(&token) - .send() - .await - .expect("getBackup request failed"); - - assert_eq!(get_res.status(), StatusCode::OK); - let content_type = get_res.headers().get(header::CONTENT_TYPE).unwrap(); - assert_eq!(content_type, "application/vnd.ipld.car"); - - let bytes = get_res.bytes().await.expect("Failed to read body"); - assert!(bytes.len() > 100, "CAR file should have content"); - assert_eq!( - bytes[1], 0xa2, - "CAR file should have valid header structure" - ); -} - -#[tokio::test] -async fn test_delete_backup() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let create_res = client - .post(format!("{}/xrpc/_backup.createBackup", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("createBackup request failed"); - - assert_eq!(create_res.status(), StatusCode::OK); - let create_body: Value = create_res.json().await.expect("Invalid JSON"); - let backup_id = create_body["id"].as_str().unwrap(); - - let delete_res = client - .post(format!( - "{}/xrpc/_backup.deleteBackup?id={}", - base_url().await, - backup_id - )) - .bearer_auth(&token) - .send() - .await - .expect("deleteBackup request failed"); - - assert_eq!(delete_res.status(), StatusCode::OK); - - let get_res = client - .get(format!( - "{}/xrpc/_backup.getBackup?id={}", - base_url().await, - backup_id - )) - .bearer_auth(&token) - .send() - .await - .expect("getBackup request failed"); - - assert_eq!(get_res.status(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_toggle_backup_enabled() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let list_res = client - .get(format!("{}/xrpc/_backup.listBackups", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("listBackups request failed"); - - assert_eq!(list_res.status(), StatusCode::OK); - let list_body: Value = list_res.json().await.expect("Invalid JSON"); - assert!(list_body["backupEnabled"].as_bool().unwrap()); - - let disable_res = client - .post(format!("{}/xrpc/_backup.setEnabled", base_url().await)) - .bearer_auth(&token) - .json(&json!({"enabled": false})) - .send() - .await - .expect("setEnabled request failed"); - - assert_eq!(disable_res.status(), StatusCode::OK); - let disable_body: Value = disable_res.json().await.expect("Invalid JSON"); - assert!(!disable_body["enabled"].as_bool().unwrap()); - - let list_res2 = client - .get(format!("{}/xrpc/_backup.listBackups", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("listBackups request failed"); - - let list_body2: Value = list_res2.json().await.expect("Invalid JSON"); - assert!(!list_body2["backupEnabled"].as_bool().unwrap()); - - let enable_res = client - .post(format!("{}/xrpc/_backup.setEnabled", base_url().await)) - .bearer_auth(&token) - .json(&json!({"enabled": true})) - .send() - .await - .expect("setEnabled request failed"); - - assert_eq!(enable_res.status(), StatusCode::OK); -} - -#[tokio::test] -async fn test_backup_includes_blobs() { - let client = client(); - let (token, did) = create_account_and_login(&client).await; - - let blob_data = b"Hello, this is test blob data for backup testing!"; - let upload_res = client - .post(format!( - "{}/xrpc/com.atproto.repo.uploadBlob", - base_url().await - )) - .header(header::CONTENT_TYPE, "text/plain") - .bearer_auth(&token) - .body(blob_data.to_vec()) - .send() - .await - .expect("uploadBlob request failed"); - - assert_eq!(upload_res.status(), StatusCode::OK); - let upload_body: Value = upload_res.json().await.expect("Invalid JSON"); - let blob = &upload_body["blob"]; - - let record = json!({ - "$type": "app.bsky.feed.post", - "text": "Test post with blob", - "createdAt": chrono::Utc::now().to_rfc3339(), - "embed": { - "$type": "app.bsky.embed.images", - "images": [{ - "alt": "test image", - "image": blob - }] - } - }); - - let create_record_res = client - .post(format!( - "{}/xrpc/com.atproto.repo.createRecord", - base_url().await - )) - .bearer_auth(&token) - .json(&json!({ - "repo": did, - "collection": "app.bsky.feed.post", - "record": record - })) - .send() - .await - .expect("createRecord request failed"); - - assert_eq!(create_record_res.status(), StatusCode::OK); - - let create_backup_res = client - .post(format!("{}/xrpc/_backup.createBackup", base_url().await)) - .bearer_auth(&token) - .send() - .await - .expect("createBackup request failed"); - - assert_eq!(create_backup_res.status(), StatusCode::OK); - let backup_body: Value = create_backup_res.json().await.expect("Invalid JSON"); - let backup_id = backup_body["id"].as_str().unwrap(); - - let get_backup_res = client - .get(format!( - "{}/xrpc/_backup.getBackup?id={}", - base_url().await, - backup_id - )) - .bearer_auth(&token) - .send() - .await - .expect("getBackup request failed"); - - assert_eq!(get_backup_res.status(), StatusCode::OK); - let car_bytes = get_backup_res.bytes().await.expect("Failed to read body"); - - let blob_cid = blob["ref"]["$link"].as_str().unwrap(); - let blob_found = String::from_utf8_lossy(&car_bytes).contains("Hello, this is test blob data"); - assert!( - blob_found || car_bytes.len() > 500, - "Backup should contain blob data (cid: {})", - blob_cid - ); -} - -#[tokio::test] -async fn test_backup_unauthorized() { - let client = client(); - - let res = client - .get(format!("{}/xrpc/_backup.listBackups", base_url().await)) - .send() - .await - .expect("listBackups request failed"); - - assert_eq!(res.status(), StatusCode::UNAUTHORIZED); -} - -#[tokio::test] -async fn test_get_nonexistent_backup() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let fake_id = uuid::Uuid::new_v4(); - let res = client - .get(format!( - "{}/xrpc/_backup.getBackup?id={}", - base_url().await, - fake_id - )) - .bearer_auth(&token) - .send() - .await - .expect("getBackup request failed"); - - assert_eq!(res.status(), StatusCode::NOT_FOUND); -} - -#[tokio::test] -async fn test_backup_invalid_id() { - let client = client(); - let (token, _did) = create_account_and_login(&client).await; - - let res = client - .get(format!( - "{}/xrpc/_backup.getBackup?id=not-a-uuid", - base_url().await - )) - .bearer_auth(&token) - .send() - .await - .expect("getBackup request failed"); - - assert_eq!(res.status(), StatusCode::BAD_REQUEST); -} diff --git a/crates/tranquil-pds/tests/whole_story.rs b/crates/tranquil-pds/tests/whole_story.rs index 3272335..a683f69 100644 --- a/crates/tranquil-pds/tests/whole_story.rs +++ b/crates/tranquil-pds/tests/whole_story.rs @@ -134,26 +134,6 @@ async fn test_complete_user_journey_signup_to_deletion() { .expect("Edit post failed"); assert_eq!(edit_res.status(), StatusCode::OK); - let backup_res = client - .post(format!("{}/xrpc/_backup.createBackup", base)) - .bearer_auth(&jwt) - .send() - .await - .expect("Backup creation failed"); - assert_eq!(backup_res.status(), StatusCode::OK); - let backup_body: Value = backup_res.json().await.unwrap(); - let backup_id = backup_body["id"].as_str().unwrap(); - - let download_res = client - .get(format!("{}/xrpc/_backup.getBackup?id={}", base, backup_id)) - .bearer_auth(&jwt) - .send() - .await - .expect("Backup download failed"); - assert_eq!(download_res.status(), StatusCode::OK); - let backup_bytes = download_res.bytes().await.unwrap(); - assert!(backup_bytes.len() > 100, "Backup should have content"); - let delete_res = client .post(format!("{}/xrpc/com.atproto.server.deleteSession", base)) .bearer_auth(&jwt) @@ -1188,89 +1168,6 @@ async fn test_backup_restore_workflow() { .await .unwrap(); assert_eq!(profile_res.status(), StatusCode::OK); - - let backup1_res = client - .post(format!("{}/xrpc/_backup.createBackup", base)) - .bearer_auth(&jwt) - .send() - .await - .expect("Backup 1 failed"); - assert_eq!(backup1_res.status(), StatusCode::OK); - let backup1: Value = backup1_res.json().await.unwrap(); - let backup1_id = backup1["id"].as_str().unwrap(); - let backup1_rev = backup1["repoRev"].as_str().unwrap(); - - create_post(&client, &did, &jwt, "Post 4 after first backup").await; - create_post(&client, &did, &jwt, "Post 5 after first backup").await; - - let backup2_res = client - .post(format!("{}/xrpc/_backup.createBackup", base)) - .bearer_auth(&jwt) - .send() - .await - .expect("Backup 2 failed"); - assert_eq!(backup2_res.status(), StatusCode::OK); - let backup2: Value = backup2_res.json().await.unwrap(); - let backup2_id = backup2["id"].as_str().unwrap(); - let backup2_rev = backup2["repoRev"].as_str().unwrap(); - - assert_ne!( - backup1_rev, backup2_rev, - "Backups should have different revs" - ); - - let list_res = client - .get(format!("{}/xrpc/_backup.listBackups", base)) - .bearer_auth(&jwt) - .send() - .await - .expect("List backups failed"); - let list_body: Value = list_res.json().await.unwrap(); - let backups = list_body["backups"].as_array().unwrap(); - assert_eq!(backups.len(), 2, "Should have 2 backups"); - - let download1 = client - .get(format!("{}/xrpc/_backup.getBackup?id={}", base, backup1_id)) - .bearer_auth(&jwt) - .send() - .await - .expect("Download backup 1 failed"); - assert_eq!(download1.status(), StatusCode::OK); - let backup1_bytes = download1.bytes().await.unwrap(); - - let download2 = client - .get(format!("{}/xrpc/_backup.getBackup?id={}", base, backup2_id)) - .bearer_auth(&jwt) - .send() - .await - .expect("Download backup 2 failed"); - assert_eq!(download2.status(), StatusCode::OK); - let backup2_bytes = download2.bytes().await.unwrap(); - - assert!( - backup2_bytes.len() > backup1_bytes.len(), - "Second backup should be larger (more posts)" - ); - - let delete_old = client - .post(format!( - "{}/xrpc/_backup.deleteBackup?id={}", - base, backup1_id - )) - .bearer_auth(&jwt) - .send() - .await - .expect("Delete backup failed"); - assert_eq!(delete_old.status(), StatusCode::OK); - - let final_list = client - .get(format!("{}/xrpc/_backup.listBackups", base)) - .bearer_auth(&jwt) - .send() - .await - .unwrap(); - let final_body: Value = final_list.json().await.unwrap(); - assert_eq!(final_body["backups"].as_array().unwrap().len(), 1); } #[tokio::test] diff --git a/crates/tranquil-server/src/main.rs b/crates/tranquil-server/src/main.rs index 12a57da..b2fc5ea 100644 --- a/crates/tranquil-server/src/main.rs +++ b/crates/tranquil-server/src/main.rs @@ -11,7 +11,7 @@ use tranquil_pds::comms::{CommsService, DiscordSender, EmailSender, SignalSender use tranquil_pds::crawlers::{Crawlers, start_crawlers_service}; use tranquil_pds::scheduled::{ backfill_genesis_commit_blocks, backfill_record_blobs, backfill_repo_rev, backfill_user_blocks, - start_backup_tasks, start_scheduled_tasks, + start_scheduled_tasks, }; use tranquil_pds::state::AppState; @@ -233,20 +233,6 @@ async fn run() -> Result<(), Box> { None }; - let backup_handle = if let Some(backup_storage) = state.backup_storage.clone() { - info!("Backup service enabled"); - Some(tokio::spawn(start_backup_tasks( - state.repo_repo.clone(), - state.backup_repo.clone(), - state.block_store.clone(), - backup_storage, - shutdown.clone(), - ))) - } else { - warn!("Backup service disabled (BACKUP_S3_BUCKET not set or BACKUP_ENABLED=false)"); - None - }; - let scheduled_handle = tokio::spawn(start_scheduled_tasks( state.user_repo.clone(), state.blob_repo.clone(), @@ -310,10 +296,6 @@ async fn run() -> Result<(), Box> { handle.await.ok(); } - if let Some(handle) = backup_handle { - handle.await.ok(); - } - scheduled_handle.await.ok(); if let Err(e) = server_result { diff --git a/crates/tranquil-storage/src/lib.rs b/crates/tranquil-storage/src/lib.rs index 91a220b..f509354 100644 --- a/crates/tranquil-storage/src/lib.rs +++ b/crates/tranquil-storage/src/lib.rs @@ -1,7 +1,4 @@ -pub use tranquil_infra::{ - BackupStorage, BlobStorage, StorageError, StreamUploadResult, backup_interval_secs, - backup_retention_count, -}; +pub use tranquil_infra::{BlobStorage, StorageError, StreamUploadResult}; use async_trait::async_trait; use bytes::Bytes; @@ -161,70 +158,6 @@ mod s3 { ) } - pub struct S3BackupStorage { - client: Client, - bucket: String, - } - - impl S3BackupStorage { - pub async fn new() -> Option { - let bucket = tranquil_config::get().backup.s3_bucket.clone()?; - let client = create_s3_client().await; - Some(Self { client, bucket }) - } - } - - #[async_trait] - impl BackupStorage for S3BackupStorage { - async fn put_backup( - &self, - did: &str, - rev: &str, - data: &[u8], - ) -> Result { - let key = format!("{}/{}.car", did, rev); - self.client - .put_object() - .bucket(&self.bucket) - .key(&key) - .body(ByteStream::from(Bytes::copy_from_slice(data))) - .send() - .await - .map_err(|e| StorageError::Backend(e.to_string()))?; - - Ok(key) - } - - async fn get_backup(&self, storage_key: &str) -> Result { - let resp = self - .client - .get_object() - .bucket(&self.bucket) - .key(storage_key) - .send() - .await - .map_err(|e| StorageError::Backend(e.to_string()))?; - - resp.body - .collect() - .await - .map(|agg| agg.into_bytes()) - .map_err(|e| StorageError::Backend(e.to_string())) - } - - async fn delete_backup(&self, storage_key: &str) -> Result<(), StorageError> { - self.client - .delete_object() - .bucket(&self.bucket) - .key(storage_key) - .send() - .await - .map_err(|e| StorageError::Backend(e.to_string()))?; - - Ok(()) - } - } - #[async_trait] impl BlobStorage for S3BlobStorage { async fn put(&self, key: &str, data: &[u8]) -> Result<(), StorageError> { @@ -488,7 +421,7 @@ mod s3 { } #[cfg(feature = "s3")] -pub use s3::{S3BackupStorage, S3BlobStorage}; +pub use s3::S3BlobStorage; pub struct FilesystemBlobStorage { base_path: PathBuf, @@ -634,69 +567,6 @@ impl BlobStorage for FilesystemBlobStorage { } } -pub struct FilesystemBackupStorage { - base_path: PathBuf, - tmp_path: PathBuf, -} - -impl FilesystemBackupStorage { - pub async fn new(base_path: impl Into) -> Result { - let base_path = base_path.into(); - let tmp_path = base_path.join(".tmp"); - tokio::fs::create_dir_all(&base_path).await?; - tokio::fs::create_dir_all(&tmp_path).await?; - cleanup_orphaned_tmp_files(&tmp_path).await; - Ok(Self { - base_path, - tmp_path, - }) - } - - fn resolve_path(&self, key: &str) -> Result { - validate_key(key)?; - Ok(self.base_path.join(key)) - } -} - -#[async_trait] -impl BackupStorage for FilesystemBackupStorage { - async fn put_backup(&self, did: &str, rev: &str, data: &[u8]) -> Result { - use tokio::io::AsyncWriteExt; - - let key = format!("{}/{}.car", did, rev); - let final_path = self.resolve_path(&key)?; - ensure_parent_dir(&final_path).await?; - - let tmp_file_name = uuid::Uuid::new_v4().to_string(); - let tmp_path = self.tmp_path.join(&tmp_file_name); - - let mut file = tokio::fs::File::create(&tmp_path).await?; - file.write_all(data).await?; - file.sync_all().await?; - drop(file); - - rename_with_fallback(&tmp_path, &final_path).await?; - Ok(key) - } - - async fn get_backup(&self, storage_key: &str) -> Result { - let path = self.resolve_path(storage_key)?; - tokio::fs::read(&path) - .await - .map(Bytes::from) - .map_err(map_io_not_found(storage_key)) - } - - async fn delete_backup(&self, storage_key: &str) -> Result<(), StorageError> { - let path = self.resolve_path(storage_key)?; - tokio::fs::remove_file(&path).await.or_else(|e| { - (e.kind() == std::io::ErrorKind::NotFound) - .then_some(()) - .ok_or(StorageError::Io(e)) - }) - } -} - pub async fn create_blob_storage() -> Arc { let cfg = tranquil_config::get(); let backend = &cfg.storage.backend; @@ -731,60 +601,6 @@ pub async fn create_blob_storage() -> Arc { } } -pub async fn create_backup_storage() -> Option> { - let cfg = tranquil_config::get(); - - if !cfg.backup.enabled { - tracing::info!("Backup storage disabled via config"); - return None; - } - - let backend = &cfg.backup.backend; - - match backend.as_str() { - #[cfg(feature = "s3")] - "s3" => S3BackupStorage::new().await.map_or_else( - || { - tracing::error!( - "BACKUP_STORAGE_BACKEND=s3 but BACKUP_S3_BUCKET is not set. \ - Backups will be disabled." - ); - None - }, - |storage| { - tracing::info!("Initialized S3 backup storage"); - Some(Arc::new(storage) as Arc) - }, - ), - #[cfg(not(feature = "s3"))] - "s3" => { - tracing::error!( - "BACKUP_STORAGE_BACKEND=s3 but binary was compiled without s3 feature. \ - Backups will be disabled." - ); - None - } - _ => { - let path = cfg.backup.path.clone(); - FilesystemBackupStorage::new(path).await.map_or_else( - |e| { - tracing::error!( - "Failed to initialize filesystem backup storage: {}. \ - Set BACKUP_STORAGE_PATH to a valid directory path. \ - Backups will be disabled.", - e - ); - None - }, - |storage| { - tracing::info!("Initialized filesystem backup storage"); - Some(Arc::new(storage) as Arc) - }, - ) - } - } -} - trait Pipe: Sized { fn pipe(self, f: F) -> R where diff --git a/crates/tranquil-sync/src/blob.rs b/crates/tranquil-sync/src/blob.rs index d77460f..09c8a33 100644 --- a/crates/tranquil-sync/src/blob.rs +++ b/crates/tranquil-sync/src/blob.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; -use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use axum::{ Json, body::Body, @@ -11,6 +8,9 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use tracing::error; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use tranquil_types::{CidLink, Did}; #[derive(Deserialize)] diff --git a/crates/tranquil-sync/src/commit.rs b/crates/tranquil-sync/src/commit.rs index 994afce..d9c6a48 100644 --- a/crates/tranquil-sync/src/commit.rs +++ b/crates/tranquil-sync/src/commit.rs @@ -1,6 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; -use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability, get_account_with_status}; use axum::{ Json, extract::{Query, State}, @@ -14,6 +11,11 @@ use serde::{Deserialize, Serialize}; use std::str::FromStr; use tracing::error; use tranquil_db_traits::AccountStatus; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::util::{ + RepoAccessLevel, assert_repo_availability, get_account_with_status, +}; use tranquil_types::Did; async fn get_rev_from_commit(state: &AppState, cid_str: &str) -> Option { diff --git a/crates/tranquil-sync/src/crawl.rs b/crates/tranquil-sync/src/crawl.rs index 801f9c4..4c1631b 100644 --- a/crates/tranquil-sync/src/crawl.rs +++ b/crates/tranquil-sync/src/crawl.rs @@ -1,5 +1,3 @@ -use tranquil_pds::api::EmptyResponse; -use tranquil_pds::state::AppState; use axum::{ Json, extract::{Query, State}, @@ -7,6 +5,8 @@ use axum::{ }; use serde::Deserialize; use tracing::info; +use tranquil_pds::api::EmptyResponse; +use tranquil_pds::state::AppState; #[derive(Deserialize)] pub struct NotifyOfUpdateParams { diff --git a/crates/tranquil-sync/src/deprecated.rs b/crates/tranquil-sync/src/deprecated.rs index 953164d..1330e21 100644 --- a/crates/tranquil-sync/src/deprecated.rs +++ b/crates/tranquil-sync/src/deprecated.rs @@ -1,7 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::state::AppState; -use tranquil_pds::sync::car::{encode_car_block, encode_car_header}; -use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use axum::{ Json, extract::{Query, State}, @@ -13,15 +9,18 @@ use ipld_core::ipld::Ipld; use jacquard_repo::storage::BlockStore; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::car::{encode_car_block, encode_car_header}; +use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use tranquil_types::Did; const MAX_REPO_BLOCKS_TRAVERSAL: usize = 20_000; async fn check_admin_or_self(state: &AppState, headers: &HeaderMap, did: &Did) -> bool { - let extracted = match tranquil_pds::auth::extract_auth_token_from_header(tranquil_pds::util::get_header_str( - headers, - axum::http::header::AUTHORIZATION, - )) { + let extracted = match tranquil_pds::auth::extract_auth_token_from_header( + tranquil_pds::util::get_header_str(headers, axum::http::header::AUTHORIZATION), + ) { Some(t) => t, None => return false, }; diff --git a/crates/tranquil-sync/src/lib.rs b/crates/tranquil-sync/src/lib.rs index 126eecf..a3c39ba 100644 --- a/crates/tranquil-sync/src/lib.rs +++ b/crates/tranquil-sync/src/lib.rs @@ -29,10 +29,7 @@ pub fn sync_routes() -> axum::Router { .route("/com.atproto.sync.getBlocks", get(get_blocks)) .route("/com.atproto.sync.getRepo", get(get_repo)) .route("/com.atproto.sync.getRecord", get(get_record)) - .route( - "/com.atproto.sync.subscribeRepos", - get(subscribe_repos), - ) + .route("/com.atproto.sync.subscribeRepos", get(subscribe_repos)) .route("/com.atproto.sync.getHead", get(get_head)) .route("/com.atproto.sync.getCheckout", get(get_checkout)) } diff --git a/crates/tranquil-sync/src/listener.rs b/crates/tranquil-sync/src/listener.rs index 4cd7a55..5749cb3 100644 --- a/crates/tranquil-sync/src/listener.rs +++ b/crates/tranquil-sync/src/listener.rs @@ -1,8 +1,8 @@ -use tranquil_pds::state::AppState; -use tranquil_pds::sync::firehose::SequencedEvent; use std::sync::atomic::{AtomicI64, Ordering}; use tracing::{debug, error, info, warn}; use tranquil_db_traits::SequenceNumber; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::firehose::SequencedEvent; static LAST_BROADCAST_SEQ: AtomicI64 = AtomicI64::new(0); diff --git a/crates/tranquil-sync/src/repo.rs b/crates/tranquil-sync/src/repo.rs index ef710d6..efebea4 100644 --- a/crates/tranquil-sync/src/repo.rs +++ b/crates/tranquil-sync/src/repo.rs @@ -1,8 +1,3 @@ -use tranquil_pds::api::error::ApiError; -use tranquil_pds::scheduled::generate_repo_car_from_user_blocks; -use tranquil_pds::state::AppState; -use tranquil_pds::sync::car::{encode_car_block, encode_car_header}; -use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use axum::{ extract::{Query, RawQuery, State}, http::StatusCode, @@ -13,6 +8,11 @@ use jacquard_repo::storage::BlockStore; use serde::Deserialize; use std::str::FromStr; use tracing::error; +use tranquil_pds::api::error::ApiError; +use tranquil_pds::scheduled::generate_repo_car_from_user_blocks; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::car::{encode_car_block, encode_car_header}; +use tranquil_pds::sync::util::{RepoAccessLevel, assert_repo_availability}; use tranquil_types::Did; struct GetBlocksParams { diff --git a/crates/tranquil-sync/src/subscribe_repos.rs b/crates/tranquil-sync/src/subscribe_repos.rs index 93a66aa..be53718 100644 --- a/crates/tranquil-sync/src/subscribe_repos.rs +++ b/crates/tranquil-sync/src/subscribe_repos.rs @@ -1,10 +1,3 @@ -use tranquil_pds::state::AppState; -use tranquil_pds::sync::firehose::SequencedEvent; -use tranquil_pds::sync::frame::{ErrorFrameName, InfoFrameName}; -use tranquil_pds::sync::util::{ - format_error_frame, format_event_for_sending, format_event_with_prefetched_blocks, - format_info_frame, prefetch_blocks_for_events, -}; use axum::{ extract::{Query, State, ws::Message, ws::WebSocket, ws::WebSocketUpgrade}, response::Response, @@ -15,6 +8,13 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use tokio::sync::broadcast::error::RecvError; use tracing::{error, info, warn}; use tranquil_db_traits::SequenceNumber; +use tranquil_pds::state::AppState; +use tranquil_pds::sync::firehose::SequencedEvent; +use tranquil_pds::sync::frame::{ErrorFrameName, InfoFrameName}; +use tranquil_pds::sync::util::{ + format_error_frame, format_event_for_sending, format_event_with_prefetched_blocks, + format_info_frame, prefetch_blocks_for_events, +}; const BACKFILL_BATCH_SIZE: i64 = 1000; diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml index 9d75e9d..f530fe2 100644 --- a/docker-compose.prod.yaml +++ b/docker-compose.prod.yaml @@ -10,7 +10,6 @@ services: volumes: - ./config.toml:/etc/tranquil-pds/config.toml:ro - blob_data:/var/lib/tranquil/blobs - - backup_data:/var/lib/tranquil/backups depends_on: db: condition: service_healthy @@ -95,6 +94,5 @@ services: volumes: postgres_data: blob_data: - backup_data: prometheus_data: acme_challenge: diff --git a/docker-compose.yaml b/docker-compose.yaml index c2c8bf2..9b6e039 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,7 +11,6 @@ services: volumes: - ./config.toml:/etc/tranquil-pds/config.toml:ro - blob_data:/var/lib/tranquil/blobs - - backup_data:/var/lib/tranquil/backups depends_on: - db @@ -52,5 +51,4 @@ services: volumes: postgres_data: blob_data: - backup_data: prometheus_data: diff --git a/docs/install-containers.md b/docs/install-containers.md index 0c455b4..82ca517 100644 --- a/docs/install-containers.md +++ b/docs/install-containers.md @@ -110,7 +110,7 @@ apt install -y podman ```bash mkdir -p /etc/containers/systemd -mkdir -p /srv/tranquil-pds/{postgres,blobs,backups,certs,acme,config} +mkdir -p /srv/tranquil-pds/{postgres,blobs,certs,acme,config} ``` ## Create a configuration file @@ -254,7 +254,7 @@ rc-service podman start ```sh mkdir -p /srv/tranquil-pds/{data,config} -mkdir -p /srv/tranquil-pds/data/{postgres,blobs,backups,certs,acme} +mkdir -p /srv/tranquil-pds/data/{postgres,blobs,certs,acme} ``` ## Clone the repo and build images diff --git a/docs/install-debian.md b/docs/install-debian.md index 00d2400..671a82e 100644 --- a/docs/install-debian.md +++ b/docs/install-debian.md @@ -44,7 +44,7 @@ sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" ## Create blob storage directories ```bash -mkdir -p /var/lib/tranquil/blobs /var/lib/tranquil/backups +mkdir -p /var/lib/tranquil/blobs ``` We'll set ownership after creating the service user. diff --git a/docs/install-kubernetes.md b/docs/install-kubernetes.md index fc538de..a6d3df7 100644 --- a/docs/install-kubernetes.md +++ b/docs/install-kubernetes.md @@ -12,7 +12,6 @@ The container image expects: - A TOML config file mounted at `/etc/tranquil-pds/config.toml` (or passed via `--config`) - `DATABASE_URL` - postgres connection string - `BLOB_STORAGE_PATH` - path to blob storage (mount a PV here) -- `BACKUP_STORAGE_PATH` - path for repo backups (optional but recommended) - `PDS_HOSTNAME` - your PDS hostname (without protocol) - `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48` - `CRAWLERS` - typically `https://bsky.network` diff --git a/example.toml b/example.toml index 34a4078..9101427 100644 --- a/example.toml +++ b/example.toml @@ -180,47 +180,6 @@ # Can also be specified via environment variable `S3_ENDPOINT`. #s3_endpoint = -[backup] -# Enable automatic backups. -# -# Can also be specified via environment variable `BACKUP_ENABLED`. -# -# Default value: true -#enabled = true - -# Backup storage backend: `filesystem` or `s3`. -# -# Can also be specified via environment variable `BACKUP_STORAGE_BACKEND`. -# -# Default value: "filesystem" -#backend = "filesystem" - -# Path on disk for the filesystem backup backend. -# -# Can also be specified via environment variable `BACKUP_STORAGE_PATH`. -# -# Default value: "/var/lib/tranquil-pds/backups" -#path = "/var/lib/tranquil-pds/backups" - -# S3 bucket name for backups. -# -# Can also be specified via environment variable `BACKUP_S3_BUCKET`. -#s3_bucket = - -# Number of backup revisions to keep per account. -# -# Can also be specified via environment variable `BACKUP_RETENTION_COUNT`. -# -# Default value: 7 -#retention_count = 7 - -# Seconds between backup runs. -# -# Can also be specified via environment variable `BACKUP_INTERVAL_SECS`. -# -# Default value: 86400 -#interval_secs = 86400 - [cache] # Cache backend: `ripple` (default, built-in gossip) or `valkey`. # diff --git a/frontend/src/components/dashboard/SettingsContent.svelte b/frontend/src/components/dashboard/SettingsContent.svelte index 4208965..ad9f4e6 100644 --- a/frontend/src/components/dashboard/SettingsContent.svelte +++ b/frontend/src/components/dashboard/SettingsContent.svelte @@ -31,7 +31,6 @@ selectedDomain = info.availableUserDomains[0] } } catch {} - loadBackups() } init() return () => stopEmailPolling() @@ -167,7 +166,6 @@ } let exportLoading = $state(false) - let exportBlobsLoading = $state(false) async function handleExportRepo() { exportLoading = true @@ -190,136 +188,6 @@ } } - async function handleExportBlobs() { - exportBlobsLoading = true - try { - const blob = await api.exportBlobs(session.accessJwt) - if (blob.size === 0) { - toast.success($_('settings.messages.noBlobsToExport')) - return - } - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `${session.handle}-blobs.zip` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - toast.success($_('settings.messages.blobsExported')) - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.messages.exportFailed')) - } finally { - exportBlobsLoading = false - } - } - - interface BackupInfo { - id: string - repoRev: string - sizeBytes: number - createdAt: string - } - let backups = $state([]) - let backupEnabled = $state(true) - let backupsLoading = $state(false) - let createBackupLoading = $state(false) - let restoreFile = $state(null) - let restoreLoading = $state(false) - - async function loadBackups() { - backupsLoading = true - try { - const result = await api.listBackups(session.accessJwt) - backups = result.backups - backupEnabled = result.backupEnabled - } catch {} - backupsLoading = false - } - - async function handleCreateBackup() { - createBackupLoading = true - try { - await api.createBackup(session.accessJwt) - await loadBackups() - toast.success($_('settings.backups.created')) - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.backups.createFailed')) - } finally { - createBackupLoading = false - } - } - - async function handleDownloadBackup(id: string, rev: string) { - try { - const blob = await api.getBackup(session.accessJwt, id) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `${session.handle}-${rev}.car` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.backups.downloadFailed')) - } - } - - function handleRestoreFileChange(e: Event) { - const input = e.target as HTMLInputElement - if (input.files && input.files[0]) { - restoreFile = input.files[0] - } - } - - async function handleRestore() { - if (!restoreFile) return - restoreLoading = true - try { - const buffer = await restoreFile.arrayBuffer() - const car = new Uint8Array(buffer) - await api.importRepo(session.accessJwt, car) - toast.success($_('settings.backups.restored')) - restoreFile = null - await loadBackups() - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.backups.restoreFailed')) - } finally { - restoreLoading = false - } - } - - async function handleToggleBackup() { - const newEnabled = !backupEnabled - backupsLoading = true - try { - await api.setBackupEnabled(session.accessJwt, newEnabled) - backupEnabled = newEnabled - toast.success(newEnabled ? $_('settings.backups.enabled') : $_('settings.backups.disabled')) - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.backups.toggleFailed')) - } finally { - backupsLoading = false - } - } - - async function handleDeleteBackup(id: string) { - try { - await api.deleteBackup(session.accessJwt, id) - await loadBackups() - toast.success($_('settings.backups.deleted')) - } catch (e) { - toast.error(e instanceof ApiError ? e.message : $_('settings.backups.deleteFailed')) - } - } - - function formatBytes(bytes: number): string { - if (bytes < 1024) return `${bytes} B` - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` - return `${(bytes / (1024 * 1024)).toFixed(1)} MB` - } - let deleteLoading = $state(false) let deletePassword = $state('') let deleteToken = $state('') @@ -505,80 +373,9 @@

{$_('settings.exportData')}

-
- - -
-
- -
-

{$_('settings.backups.title')}

- {#if backupsLoading} -
{$_('common.loading')}
- {:else} - {#if backups.length > 0} -
    - {#each backups as backup} -
  • -
    - {formatDate(backup.createdAt)} - {formatBytes(backup.sizeBytes)} -
    -
    - - -
    -
  • - {/each} -
- {:else} -

{$_('settings.backups.noBackups')}

- {/if} -
- -
-
- -
- -
-

{$_('settings.backups.restoreTitle')}

-

{$_('settings.backups.restoreHint')}

-
- - -
- {#if restoreFile} -
- {$_('settings.backups.selectedFile')}: {restoreFile.name} - ({formatBytes(restoreFile.size)}) -
- {/if} -
- {/if} +
diff --git a/frontend/src/components/ui/Input.svelte b/frontend/src/components/ui/Input.svelte index 9eea1ff..af005f0 100644 --- a/frontend/src/components/ui/Input.svelte +++ b/frontend/src/components/ui/Input.svelte @@ -5,6 +5,7 @@ label?: string hint?: string error?: string + value?: string } let { @@ -12,6 +13,7 @@ hint, error, id, + value = $bindable(''), ...rest }: Props = $props() @@ -23,7 +25,7 @@ {#if label} {/if} - + {#if error} {error} {:else if hint} diff --git a/frontend/src/lib/api-validated.ts b/frontend/src/lib/api-validated.ts index 04d18ef..a2d797b 100644 --- a/frontend/src/lib/api-validated.ts +++ b/frontend/src/lib/api-validated.ts @@ -11,13 +11,11 @@ import type { import { accountInfoSchema, appPasswordSchema, - createBackupResponseSchema, createdAppPasswordSchema, createRecordResponseSchema, didDocumentSchema, enableTotpResponseSchema, legacyLoginPreferenceSchema, - listBackupsResponseSchema, listPasskeysResponseSchema, listRecordsResponseSchema, listSessionsResponseSchema, @@ -37,13 +35,11 @@ import { totpStatusSchema, type ValidatedAccountInfo, type ValidatedAppPassword, - type ValidatedCreateBackupResponse, type ValidatedCreatedAppPassword, type ValidatedCreateRecordResponse, type ValidatedDidDocument, type ValidatedEnableTotpResponse, type ValidatedLegacyLoginPreference, - type ValidatedListBackupsResponse, type ValidatedListPasskeysResponse, type ValidatedListRecordsResponse, type ValidatedListSessionsResponse, @@ -439,24 +435,6 @@ export const validatedApi = { ); }, - listBackups( - token: AccessToken, - ): Promise> { - return xrpcValidated("_backup.listBackups", listBackupsResponseSchema, { - token, - }); - }, - - createBackup( - token: AccessToken, - ): Promise< - Result - > { - return xrpcValidated("_backup.createBackup", createBackupResponseSchema, { - method: "POST", - token, - }); - }, }; export { ValidationError }; diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index f448e60..f934b27 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -33,7 +33,6 @@ import type { ContactState, CreateAccountParams, CreateAccountResult, - CreateBackupResponse, CreatedAppPassword, CreateRecordResponse, DelegationAuditEntry, @@ -48,7 +47,6 @@ import type { GetInviteCodesResponse, InviteCodeInfo, LegacyLoginPreference, - ListBackupsResponse, ListPasskeysResponse, ListRecordsResponse, ListReposResponse, @@ -72,7 +70,6 @@ import type { ServerDescription, ServerStats, Session, - SetBackupEnabledResponse, SsoLinkedAccount, StartPasskeyRegistrationResponse, SuccessResponse, @@ -1287,49 +1284,6 @@ export const api = { return res.arrayBuffer(); }, - listBackups(token: AccessToken): Promise { - return xrpc("_backup.listBackups", { token }); - }, - - async getBackup(token: AccessToken, id: string): Promise { - const url = `${API_BASE}/_backup.getBackup?id=${encodeURIComponent(id)}`; - const res = await authenticatedFetch(url, { token }); - if (!res.ok) { - const errData = await res.json().catch(() => ({ - error: "Unknown", - message: res.statusText, - })); - throw new ApiError(res.status, errData.error, errData.message); - } - return res.blob(); - }, - - createBackup(token: AccessToken): Promise { - return xrpc("_backup.createBackup", { - method: "POST", - token, - }); - }, - - async deleteBackup(token: AccessToken, id: string): Promise { - await xrpc("_backup.deleteBackup", { - method: "POST", - token, - params: { id }, - }); - }, - - setBackupEnabled( - token: AccessToken, - enabled: boolean, - ): Promise { - return xrpc("_backup.setEnabled", { - method: "POST", - token, - body: { enabled }, - }); - }, - async importRepo(token: AccessToken, car: Uint8Array): Promise { const res = await authenticatedFetch( `${API_BASE}/com.atproto.repo.importRepo`, @@ -1542,19 +1496,6 @@ export const api = { }); }, - async exportBlobs(token: AccessToken): Promise { - const res = await authenticatedFetch(`${API_BASE}/_backup.exportBlobs`, { - token, - }); - if (!res.ok) { - const errData = await res.json().catch(() => ({ - error: "Unknown", - message: res.statusText, - })); - throw new ApiError(res.status, errData.error, errData.message); - } - return res.blob(); - }, }; export const typedApi = { @@ -1781,21 +1722,6 @@ export const typedApi = { return xrpcResult("_admin.getServerStats", { token }); }, - listBackups( - token: AccessToken, - ): Promise> { - return xrpcResult("_backup.listBackups", { token }); - }, - - createBackup( - token: AccessToken, - ): Promise> { - return xrpcResult("_backup.createBackup", { - method: "POST", - token, - }); - }, - getDidDocument(token: AccessToken): Promise> { return xrpcResult("_account.getDidDocument", { token }); }, @@ -2050,28 +1976,6 @@ export const typedApi = { }); }, - setBackupEnabled( - token: AccessToken, - enabled: boolean, - ): Promise> { - return xrpcResult("_backup.setEnabled", { - method: "POST", - token, - body: { enabled }, - }); - }, - - deleteBackup( - token: AccessToken, - id: string, - ): Promise> { - return xrpcResult("_backup.deleteBackup", { - method: "POST", - token, - params: { id }, - }); - }, - createRecord( token: AccessToken, repo: Did, diff --git a/frontend/src/lib/authenticated-client.ts b/frontend/src/lib/authenticated-client.ts index f52f363..c042216 100644 --- a/frontend/src/lib/authenticated-client.ts +++ b/frontend/src/lib/authenticated-client.ts @@ -50,7 +50,6 @@ export interface AuthenticatedClient { Result<{ entries: DelegationAuditEntry[]; total: number }, ApiError> >; - exportBlobs(): Promise; } export function createAuthenticatedClient( @@ -76,8 +75,6 @@ export function createAuthenticatedClient( api.createDelegatedAccount(token, handle, email, controllerScopes), getDelegationAuditLog: (limit, offset) => api.getDelegationAuditLog(token, limit, offset), - - exportBlobs: () => api.exportBlobs(token), }; } diff --git a/frontend/src/lib/types/api.ts b/frontend/src/lib/types/api.ts index 6fed80f..60e3b0b 100644 --- a/frontend/src/lib/types/api.ts +++ b/frontend/src/lib/types/api.ts @@ -495,31 +495,6 @@ export interface VerifyTokenResponse { channel: VerificationChannel; } -export interface BackupInfo { - id: string; - repoRev: string; - repoRootCid: Cid; - blockCount: number; - sizeBytes: number; - createdAt: ISODateString; -} - -export interface ListBackupsResponse { - backups: BackupInfo[]; - backupEnabled: boolean; -} - -export interface CreateBackupResponse { - id: string; - repoRev: string; - sizeBytes: number; - blockCount: number; -} - -export interface SetBackupEnabledResponse { - enabled: boolean; -} - export interface EmailUpdateResponse { tokenRequired: boolean; } diff --git a/frontend/src/lib/types/schemas.ts b/frontend/src/lib/types/schemas.ts index 7c69f35..9b13c73 100644 --- a/frontend/src/lib/types/schemas.ts +++ b/frontend/src/lib/types/schemas.ts @@ -276,27 +276,6 @@ export const searchAccountsResponseSchema = z.object({ accounts: z.array(accountInfoSchema), }); -export const backupInfoSchema = z.object({ - id: z.string(), - repoRev: z.string(), - repoRootCid: cid, - blockCount: z.number(), - sizeBytes: z.number(), - createdAt: isoDate, -}); - -export const listBackupsResponseSchema = z.object({ - backups: z.array(backupInfoSchema), - backupEnabled: z.boolean(), -}); - -export const createBackupResponseSchema = z.object({ - id: z.string(), - repoRev: z.string(), - sizeBytes: z.number(), - blockCount: z.number(), -}); - export type ValidatedSession = z.infer; export type ValidatedServerDescription = z.infer< typeof serverDescriptionSchema @@ -348,10 +327,3 @@ export type ValidatedAccountInfo = z.infer; export type ValidatedSearchAccountsResponse = z.infer< typeof searchAccountsResponseSchema >; -export type ValidatedBackupInfo = z.infer; -export type ValidatedListBackupsResponse = z.infer< - typeof listBackupsResponseSchema ->; -export type ValidatedCreateBackupResponse = z.infer< - typeof createBackupResponseSchema ->; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 21fcd59..3729dec 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -213,31 +213,7 @@ "changeHandleButton": "Change Handle", "exportData": "Export Data", "downloadRepo": "Download Repository", - "downloadBlobs": "Download Media", "exporting": "Exporting...", - "backups": { - "title": "Backups", - "autoBackup": "Automatic backups", - "enabled": "Automatic backups enabled", - "disabled": "Automatic backups disabled", - "toggleFailed": "Failed to update backup setting", - "noBackups": "No backups available yet.", - "download": "Download", - "delete": "Delete", - "createNow": "Create Backup Now", - "created": "Backup created successfully", - "createFailed": "Failed to create backup", - "downloadFailed": "Failed to download backup", - "deleted": "Backup deleted", - "deleteFailed": "Failed to delete backup", - "restoreTitle": "Restore from Backup", - "restoreHint": "Upload a CAR file to restore your repository", - "selectedFile": "Selected file", - "restore": "Restore", - "restoring": "Restoring...", - "restored": "Repository restored successfully", - "restoreFailed": "Failed to restore repository" - }, "deleteAccount": "Delete Account", "deleteWarning": "This action is irreversible. All your data will be permanently deleted.", "requestDeletion": "Request Account Deletion", @@ -258,8 +234,6 @@ "deleteConfirmation": "Are you absolutely sure you want to delete your account? This cannot be undone.", "deletionFailed": "Failed to delete account", "repoExported": "Repository exported successfully", - "blobsExported": "Media files exported successfully", - "noBlobsToExport": "No media files to export", "exportFailed": "Failed to export" } }, diff --git a/frontend/src/locales/fi.json b/frontend/src/locales/fi.json index 0b68cd2..4c6a764 100644 --- a/frontend/src/locales/fi.json +++ b/frontend/src/locales/fi.json @@ -213,31 +213,7 @@ "changeHandleButton": "Vaihda käyttäjänimi", "exportData": "Vie tiedot", "downloadRepo": "Lataa tietovarasto", - "downloadBlobs": "Lataa media", "exporting": "Viedään...", - "backups": { - "title": "Varmuuskopiot", - "enabled": "Automaattiset varmuuskopiot käytössä", - "disabled": "Automaattiset varmuuskopiot pois käytöstä", - "toggleFailed": "Varmuuskopioasetuksen päivitys epäonnistui", - "noBackups": "Varmuuskopioita ei ole vielä saatavilla.", - "download": "Lataa", - "delete": "Poista", - "createNow": "Luo varmuuskopio nyt", - "created": "Varmuuskopio luotu onnistuneesti", - "createFailed": "Varmuuskopion luonti epäonnistui", - "downloadFailed": "Varmuuskopion lataus epäonnistui", - "deleted": "Varmuuskopio poistettu", - "deleteFailed": "Varmuuskopion poisto epäonnistui", - "restoreTitle": "Palauta varmuuskopiosta", - "selectedFile": "Valittu tiedosto", - "restore": "Palauta", - "restoring": "Palautetaan...", - "restored": "Tietovarasto palautettu onnistuneesti", - "restoreFailed": "Tietovaraston palautus epäonnistui", - "autoBackup": "Automaattiset varmuuskopiot", - "restoreHint": "Lataa CAR-tiedosto palauttaaksesi tietovaraston" - }, "deleteAccount": "Poista tili", "deleteWarning": "Tämä toiminto on peruuttamaton. Kaikki tietosi poistetaan pysyvästi.", "requestDeletion": "Pyydä tilin poistoa", @@ -258,8 +234,6 @@ "deleteConfirmation": "Oletko täysin varma, että haluat poistaa tilisi? Tätä ei voi perua.", "deletionFailed": "Tilin poisto epäonnistui", "repoExported": "Tietovarasto viety", - "blobsExported": "Mediatiedostot viety", - "noBlobsToExport": "Ei vietäviä mediatiedostoja", "exportFailed": "Vienti epäonnistui" } }, diff --git a/frontend/src/locales/ja.json b/frontend/src/locales/ja.json index 8d5754c..287b563 100644 --- a/frontend/src/locales/ja.json +++ b/frontend/src/locales/ja.json @@ -213,31 +213,7 @@ "changeHandleButton": "ハンドルを変更", "exportData": "データエクスポート", "downloadRepo": "リポジトリをダウンロード", - "downloadBlobs": "メディアをダウンロード", "exporting": "エクスポート中...", - "backups": { - "title": "バックアップ", - "enabled": "自動バックアップが有効です", - "disabled": "自動バックアップが無効です", - "toggleFailed": "バックアップ設定の更新に失敗しました", - "noBackups": "バックアップはまだありません。", - "download": "ダウンロード", - "delete": "削除", - "createNow": "今すぐバックアップを作成", - "created": "バックアップが正常に作成されました", - "createFailed": "バックアップの作成に失敗しました", - "downloadFailed": "バックアップのダウンロードに失敗しました", - "deleted": "バックアップが削除されました", - "deleteFailed": "バックアップの削除に失敗しました", - "restoreTitle": "バックアップから復元", - "selectedFile": "選択されたファイル", - "restore": "復元", - "restoring": "復元中...", - "restored": "リポジトリが正常に復元されました", - "restoreFailed": "リポジトリの復元に失敗しました", - "autoBackup": "自動バックアップ", - "restoreHint": "CARファイルをアップロードしてリポジトリを復元" - }, "deleteAccount": "アカウント削除", "deleteWarning": "この操作は取り消せません。すべてのデータが完全に削除されます。", "requestDeletion": "アカウント削除をリクエスト", @@ -258,8 +234,6 @@ "deleteConfirmation": "本当にアカウントを削除しますか?この操作は取り消せません。", "deletionFailed": "アカウントの削除に失敗しました", "repoExported": "リポジトリをエクスポートしました", - "blobsExported": "メディアファイルをエクスポートしました", - "noBlobsToExport": "エクスポートするメディアファイルがありません", "exportFailed": "エクスポートに失敗しました" } }, diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index 5767992..d3b06f6 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -213,31 +213,7 @@ "changeHandleButton": "핸들 변경", "exportData": "데이터 내보내기", "downloadRepo": "저장소 다운로드", - "downloadBlobs": "미디어 다운로드", "exporting": "내보내기 중...", - "backups": { - "title": "백업", - "enabled": "활성화됨", - "disabled": "비활성화됨", - "toggleFailed": "백업 설정 변경 실패", - "noBackups": "아직 백업이 없습니다", - "download": "다운로드", - "delete": "삭제", - "createNow": "지금 백업 생성", - "created": "백업이 생성되었습니다", - "createFailed": "백업 생성 실패", - "downloadFailed": "백업 다운로드 실패", - "deleted": "백업이 삭제되었습니다", - "deleteFailed": "백업 삭제 실패", - "restoreTitle": "백업에서 복원", - "selectedFile": "선택된 파일", - "restore": "백업 복원", - "restoring": "복원 중...", - "restored": "백업이 성공적으로 복원되었습니다", - "restoreFailed": "백업 복원 실패", - "autoBackup": "자동 백업", - "restoreHint": "CAR 파일을 업로드하여 저장소 복원" - }, "deleteAccount": "계정 삭제", "deleteWarning": "이 작업은 되돌릴 수 없습니다. 모든 데이터가 영구적으로 삭제됩니다.", "requestDeletion": "계정 삭제 요청", @@ -258,8 +234,6 @@ "deleteConfirmation": "정말로 계정을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", "deletionFailed": "계정 삭제에 실패했습니다", "repoExported": "저장소를 내보냈습니다", - "blobsExported": "미디어 파일을 내보냈습니다", - "noBlobsToExport": "내보낼 미디어 파일이 없습니다", "exportFailed": "내보내기에 실패했습니다" } }, diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 494d7be..904f0aa 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -213,31 +213,7 @@ "changeHandleButton": "Ändra användarnamn", "exportData": "Exportera data", "downloadRepo": "Ladda ner arkiv", - "downloadBlobs": "Ladda ner media", "exporting": "Exporterar...", - "backups": { - "title": "Säkerhetskopior", - "enabled": "Aktiverad", - "disabled": "Inaktiverad", - "toggleFailed": "Kunde inte ändra säkerhetskopieringsinställning", - "noBackups": "Inga säkerhetskopior ännu", - "download": "Ladda ner", - "delete": "Radera", - "createNow": "Skapa säkerhetskopia nu", - "created": "Säkerhetskopia skapad", - "createFailed": "Kunde inte skapa säkerhetskopia", - "downloadFailed": "Kunde inte ladda ner säkerhetskopia", - "deleted": "Säkerhetskopia raderad", - "deleteFailed": "Kunde inte radera säkerhetskopia", - "restoreTitle": "Återställ från säkerhetskopia", - "selectedFile": "Vald fil", - "restore": "Återställ säkerhetskopia", - "restoring": "Återställer...", - "restored": "Säkerhetskopia återställd", - "restoreFailed": "Kunde inte återställa säkerhetskopia", - "autoBackup": "Automatiska säkerhetskopior", - "restoreHint": "Ladda upp en CAR-fil för att återställa ditt arkiv" - }, "deleteAccount": "Radera konto", "deleteWarning": "Denna åtgärd är oåterkallelig. All din data kommer att raderas permanent.", "requestDeletion": "Begär kontoradering", @@ -258,8 +234,6 @@ "deleteConfirmation": "Är du helt säker på att du vill radera ditt konto? Detta kan inte ångras.", "deletionFailed": "Kunde inte radera kontot", "repoExported": "Arkiv exporterat", - "blobsExported": "Mediafiler exporterade", - "noBlobsToExport": "Inga mediafiler att exportera", "exportFailed": "Export misslyckades" } }, diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 352b6bc..c5b4084 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -213,31 +213,7 @@ "changeHandleButton": "更改用户名", "exportData": "导出数据", "downloadRepo": "下载数据", - "downloadBlobs": "下载媒体文件", "exporting": "导出中...", - "backups": { - "title": "备份", - "enabled": "已启用", - "disabled": "已禁用", - "toggleFailed": "更改备份设置失败", - "noBackups": "暂无备份", - "download": "下载", - "delete": "删除", - "createNow": "立即创建备份", - "created": "备份已创建", - "createFailed": "创建备份失败", - "downloadFailed": "下载备份失败", - "deleted": "备份已删除", - "deleteFailed": "删除备份失败", - "restoreTitle": "从备份恢复", - "selectedFile": "已选文件", - "restore": "恢复备份", - "restoring": "恢复中...", - "restored": "备份恢复成功", - "restoreFailed": "备份恢复失败", - "autoBackup": "自动备份", - "restoreHint": "上传 CAR 文件以恢复您的仓库" - }, "deleteAccount": "删除账户", "deleteWarning": "此操作不可逆。您的所有数据将被永久删除。", "requestDeletion": "请求删除账户", @@ -258,8 +234,6 @@ "deleteConfirmation": "您确定要删除账户吗?此操作无法撤销。", "deletionFailed": "账户删除失败", "repoExported": "数据导出成功", - "blobsExported": "媒体文件导出成功", - "noBlobsToExport": "没有可导出的媒体文件", "exportFailed": "导出失败" } }, diff --git a/frontend/src/routes/UiTest.svelte b/frontend/src/routes/UiTest.svelte index 207fd24..48b4c18 100644 --- a/frontend/src/routes/UiTest.svelte +++ b/frontend/src/routes/UiTest.svelte @@ -7,7 +7,7 @@ let inputValue = $state('') let inputError = $state('') - let inputDisabled = $state(false) + let inputDisabled = $state('') const serverConfig = getServerConfigState() diff --git a/frontend/src/styles/dashboard.css b/frontend/src/styles/dashboard.css index 48d249f..0370584 100644 --- a/frontend/src/styles/dashboard.css +++ b/frontend/src/styles/dashboard.css @@ -440,7 +440,6 @@ button.dropdown-item.logout-item { width: 100%; } -.backup-list, .passkey-list, .sso-list, .did-editor .list { @@ -452,103 +451,11 @@ button.dropdown-item.logout-item { gap: var(--space-2); } -.backup-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--space-3); - background: var(--bg-card); -} - -.backup-info { - display: flex; - gap: var(--space-3); - font-size: var(--text-sm); -} - -.backup-date { - font-weight: var(--font-medium); -} - -.backup-size { - color: var(--text-secondary); -} - -.backup-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--space-4); - gap: var(--space-3); - flex-wrap: wrap; -} - -.backup-toggle { - margin-bottom: var(--space-3); -} - -.backup-toggle .toggle-label { - display: flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; - font-size: var(--text-sm); - white-space: nowrap; -} - -.backup-toggle .toggle-label input[type="checkbox"] { - width: 16px; - height: 16px; - flex-shrink: 0; -} - -.backup-item-actions, .controllers .item-actions { display: flex; gap: var(--space-2); } -.restore-section { - margin-top: var(--space-5); - padding-top: var(--space-4); - border-top: 1px solid var(--border-color); -} - -.restore-section h4 { - margin: 0 0 var(--space-2) 0; - font-size: var(--text-sm); - font-weight: var(--font-medium); -} - -.restore-section .hint { - margin-bottom: var(--space-3); -} - -.restore-form { - display: flex; - gap: var(--space-2); - flex-wrap: wrap; -} - -.restore-form input[type="file"] { - flex: 1; - min-width: 200px; -} - -.restore-preview { - margin-top: var(--space-2); - font-size: var(--text-sm); - color: var(--text-secondary); - display: flex; - gap: var(--space-2); - flex-wrap: wrap; -} - -.restore-preview .file-name { - font-weight: var(--font-medium); - color: var(--text-primary); -} - .danger-zone h3 { color: var(--error-text); } @@ -701,11 +608,6 @@ code.record { .password-actions, .totp-actions, -.export-buttons { - display: flex; - gap: var(--space-2); - flex-wrap: wrap; -} .remove-password-form { background: var(--error-bg); diff --git a/frontend/src/tests/mocks.ts b/frontend/src/tests/mocks.ts index fdaf649..4d711d6 100644 --- a/frontend/src/tests/mocks.ts +++ b/frontend/src/tests/mocks.ts @@ -367,10 +367,6 @@ export function setupDefaultMocks(): void { "com.atproto.repo.listRecords", () => jsonResponse({ records: [] }), ); - mockEndpoint( - "_backup.listBackups", - () => jsonResponse({ backups: [] }), - ); } export function setupAuthenticatedUser( sessionOverrides?: Partial, diff --git a/migrations/20260318_drop_backups.sql b/migrations/20260318_drop_backups.sql new file mode 100644 index 0000000..ebae7b0 --- /dev/null +++ b/migrations/20260318_drop_backups.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS account_backups; +ALTER TABLE users DROP COLUMN IF EXISTS backup_enabled; diff --git a/module.nix b/module.nix index 5b1eb9a..6a34a90 100644 --- a/module.nix +++ b/module.nix @@ -37,7 +37,7 @@ in { dataDir = mkOption { type = types.str; default = "/var/lib/tranquil-pds"; - description = "Working directory for tranquil-pds. Also expected to be used for data (blobs, backups)"; + description = "Working directory for tranquil-pds. Also expected to be used for data (blobs)"; }; environmentFiles = mkOption { @@ -116,14 +116,6 @@ in { }; }; - backup = { - path = mkOption { - type = types.path; - default = "/var/lib/tranquil-pds/backups"; - description = "Directory for storing backups"; - }; - }; - email = { sendmail_path = mkOption { type = types.path; @@ -189,7 +181,6 @@ in { [ cfg.dataDir cfg.settings.storage.path - cfg.settings.backup.path ] (_: { d = { @@ -242,7 +233,6 @@ in { ReadWritePaths = [ cfg.settings.storage.path - cfg.settings.backup.path ]; }; }; diff --git a/scripts/install-debian.sh b/scripts/install-debian.sh index c67bd46..25f8524 100755 --- a/scripts/install-debian.sh +++ b/scripts/install-debian.sh @@ -72,7 +72,7 @@ if [[ -f /etc/tranquil-pds/tranquil-pds.env ]] || [[ -d /opt/tranquil-pds ]] || echo " - PostgreSQL database 'pds' and all data" echo " - All Tranquil PDS configuration and credentials" echo " - All source code in /opt/tranquil-pds" - echo " - All blobs and backups in /var/lib/tranquil/" + echo " - All blobs in /var/lib/tranquil/" echo "" read -p "Type 'NUKE' to confirm: " CONFIRM_NUKE if [[ "$CONFIRM_NUKE" == "NUKE" ]]; then @@ -195,7 +195,7 @@ sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pds TO tranquil_pds;" log_success "postgres configured" log_info "Creating blob storage directories..." -mkdir -p /var/lib/tranquil/blobs /var/lib/tranquil/backups +mkdir -p /var/lib/tranquil/blobs log_success "Blob storage directories created" log_info "Installing rust..." @@ -302,7 +302,6 @@ DATABASE_URL=postgres://tranquil_pds:${DB_PASSWORD}@localhost:5432/pds DATABASE_MAX_CONNECTIONS=100 DATABASE_MIN_CONNECTIONS=10 BLOB_STORAGE_PATH=/var/lib/tranquil/blobs -BACKUP_STORAGE_PATH=/var/lib/tranquil/backups JWT_SECRET=${JWT_SECRET} DPOP_SECRET=${DPOP_SECRET} MASTER_KEY=${MASTER_KEY} @@ -501,7 +500,6 @@ echo " DB password: ${DB_PASSWORD}" echo "" echo "Data locations:" echo " Blobs: /var/lib/tranquil/blobs" -echo " Backups: /var/lib/tranquil/backups" echo "" echo "Commands:" echo " journalctl -u tranquil-pds -f # logs" diff --git a/test.nix b/test.nix index b680f0b..c6105f5 100644 --- a/test.nix +++ b/test.nix @@ -122,7 +122,6 @@ pkgs.testers.nixosTest { with subtest("data directories exist"): server.succeed("test -d /var/lib/tranquil-pds/blobs") - server.succeed("test -d /var/lib/tranquil-pds/backups") with subtest("postgres database created"): server.succeed("sudo -u tranquil-pds psql -d tranquil-pds -c 'SELECT 1'")