this repo has no description

SQLx raw text queries to yummy query macro

lewis 04c58ef4 4825105b

Changed files
+1567 -381
.sqlx
src
api
admin
feed
identity
moderation
repo
server
repo
sync
-1
.gitignore
··· 1 /target 2 - .sqlx 3 4 .env 5
··· 1 /target 2 3 .env 4
+40
.sqlx/query-09d75b756a6bd981cf2a9e922eccc38677bee474813c66465904aec3c0da1c3e.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT u.handle, u.did, u.email, k.key_bytes\n FROM sessions s\n JOIN users u ON s.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE s.access_jwt = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "handle", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "did", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "email", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "key_bytes", 24 + "type_info": "Bytea" 25 + } 26 + ], 27 + "parameters": { 28 + "Left": [ 29 + "Text" 30 + ] 31 + }, 32 + "nullable": [ 33 + false, 34 + false, 35 + false, 36 + false 37 + ] 38 + }, 39 + "hash": "09d75b756a6bd981cf2a9e922eccc38677bee474813c66465904aec3c0da1c3e" 40 + }
+18
.sqlx/query-0f10bde03edc0233a332e210a84a4186977c71efd3be80e2508a60ea5802cb1b.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO blobs (cid, mime_type, size_bytes, created_by_user, storage_key) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (cid) DO NOTHING", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text", 10 + "Int8", 11 + "Uuid", 12 + "Text" 13 + ] 14 + }, 15 + "nullable": [] 16 + }, 17 + "hash": "0f10bde03edc0233a332e210a84a4186977c71efd3be80e2508a60ea5802cb1b" 18 + }
+29
.sqlx/query-0f8fd9cbb1ff0fd8951ce082a82cc058ec6db0dde3ab0059d6f340a1fd9ddade.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT u.did, r.repo_root_cid\n FROM repos r\n JOIN users u ON r.user_id = u.id\n WHERE u.did > $1\n ORDER BY u.did ASC\n LIMIT $2\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "repo_root_cid", 14 + "type_info": "Text" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text", 20 + "Int8" 21 + ] 22 + }, 23 + "nullable": [ 24 + false, 25 + false 26 + ] 27 + }, 28 + "hash": "0f8fd9cbb1ff0fd8951ce082a82cc058ec6db0dde3ab0059d6f340a1fd9ddade" 29 + }
+25
.sqlx/query-0fdf13907693d130babae38f4bb1df772dc11ab682f47918cacb5ae186b4eb24.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT cid FROM blobs\n WHERE created_by_user = $1 AND cid > $2 AND created_at > $3\n ORDER BY cid ASC\n LIMIT $4\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "cid", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid", 15 + "Text", 16 + "Timestamptz", 17 + "Int8" 18 + ] 19 + }, 20 + "nullable": [ 21 + false 22 + ] 23 + }, 24 + "hash": "0fdf13907693d130babae38f4bb1df772dc11ab682f47918cacb5ae186b4eb24" 25 + }
+15
.sqlx/query-14a68a119586aa980fb7b64646c1373eecd788e508246b5ad84e31b1adbdd2c1.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid", 9 + "Text" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "14a68a119586aa980fb7b64646c1373eecd788e508246b5ad84e31b1adbdd2c1" 15 + }
+18
.sqlx/query-15a3cb31c36192c76c0cfa881043d70a1cc2c212fa382f8d9efc3c35ea4e66c1.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid", 9 + "Text", 10 + "Text", 11 + "Timestamptz", 12 + "Bool" 13 + ] 14 + }, 15 + "nullable": [] 16 + }, 17 + "hash": "15a3cb31c36192c76c0cfa881043d70a1cc2c212fa382f8d9efc3c35ea4e66c1" 18 + }
+40
.sqlx/query-176d30f31356a4d128764c9c2eece81f8079a29e40b07ba58adc4380d58068c8.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT did, handle, email, created_at\n FROM users\n WHERE did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "handle", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "email", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "created_at", 24 + "type_info": "Timestamptz" 25 + } 26 + ], 27 + "parameters": { 28 + "Left": [ 29 + "Text" 30 + ] 31 + }, 32 + "nullable": [ 33 + false, 34 + false, 35 + false, 36 + false 37 + ] 38 + }, 39 + "hash": "176d30f31356a4d128764c9c2eece81f8079a29e40b07ba58adc4380d58068c8" 40 + }
+28
.sqlx/query-1d3748694f23a407e26c793cc43e91c4fa9753dc7c7fd964f6c43de27c5bac4a.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT u.did, r.repo_root_cid\n FROM users u\n LEFT JOIN repos r ON u.id = r.user_id\n WHERE u.did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "repo_root_cid", 14 + "type_info": "Text" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "1d3748694f23a407e26c793cc43e91c4fa9753dc7c7fd964f6c43de27c5bac4a" 28 + }
+14
.sqlx/query-1ee6eda3e44660e7f14fcfe56adc2d41c72901b9c701fc7b992314e5370b32dc.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "1ee6eda3e44660e7f14fcfe56adc2d41c72901b9c701fc7b992314e5370b32dc" 14 + }
+15
.sqlx/query-222699c46b99152404863463b8bdffb9452112e3d50ec352c96c1398d3e21504.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Uuid" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "222699c46b99152404863463b8bdffb9452112e3d50ec352c96c1398d3e21504" 15 + }
+46
.sqlx/query-2305db96343fcb721adc4a6a608b64678f707928d3f9395070f5e21a5ca9b601.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "did", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "handle", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "password_hash", 24 + "type_info": "Text" 25 + }, 26 + { 27 + "ordinal": 4, 28 + "name": "key_bytes", 29 + "type_info": "Bytea" 30 + } 31 + ], 32 + "parameters": { 33 + "Left": [ 34 + "Text" 35 + ] 36 + }, 37 + "nullable": [ 38 + false, 39 + false, 40 + false, 41 + false, 42 + false 43 + ] 44 + }, 45 + "hash": "2305db96343fcb721adc4a6a608b64678f707928d3f9395070f5e21a5ca9b601" 46 + }
+14
.sqlx/query-23201d4e26bc650939e30f69fb0bca00d351d057098afebc1017f70a84b4bd22.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET deactivated_at = NULL WHERE did = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "23201d4e26bc650939e30f69fb0bca00d351d057098afebc1017f70a84b4bd22" 14 + }
+23
.sqlx/query-423bbfd2ddf9b41d3bb339b8b94ac47524dc9233ec70cf2b6c5e9bc2de49b22d.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT 1 as one FROM blobs WHERE cid = $1 AND created_by_user = $2", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "one", 9 + "type_info": "Int4" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text", 15 + "Uuid" 16 + ] 17 + }, 18 + "nullable": [ 19 + null 20 + ] 21 + }, 22 + "hash": "423bbfd2ddf9b41d3bb339b8b94ac47524dc9233ec70cf2b6c5e9bc2de49b22d" 23 + }
+34
.sqlx/query-47c914ca6080c5cedf0c3f6ca7cd4cd49e8fb691b34d19511b7a1ab8b3606cdf.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT s.did, k.key_bytes, u.handle\n FROM sessions s\n JOIN users u ON s.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE s.access_jwt = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "key_bytes", 14 + "type_info": "Bytea" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "handle", 19 + "type_info": "Text" 20 + } 21 + ], 22 + "parameters": { 23 + "Left": [ 24 + "Text" 25 + ] 26 + }, 27 + "nullable": [ 28 + false, 29 + false, 30 + false 31 + ] 32 + }, 33 + "hash": "47c914ca6080c5cedf0c3f6ca7cd4cd49e8fb691b34d19511b7a1ab8b3606cdf" 34 + }
+15
.sqlx/query-48915485884524f68783351bd61e9790dd7e729e5776adc66b9d0114bb8cd73a.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET email = $1 WHERE did = $2", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "48915485884524f68783351bd61e9790dd7e729e5776adc66b9d0114bb8cd73a" 15 + }
+28
.sqlx/query-48ae289ec37b367a6ec3d74895acaf8c3dc93e65d243434b6947ead95ca8c416.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT s.did, k.key_bytes\n FROM sessions s\n JOIN users u ON s.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE s.access_jwt = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "key_bytes", 14 + "type_info": "Bytea" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "48ae289ec37b367a6ec3d74895acaf8c3dc93e65d243434b6947ead95ca8c416" 28 + }
+14
.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM users WHERE id = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7" 14 + }
+14
.sqlx/query-52437f0d7f91d29d7438263a1f658a838601038d911be8781b91ebeec8a54b89.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM sessions WHERE access_jwt = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "52437f0d7f91d29d7438263a1f658a838601038d911be8781b91ebeec8a54b89" 14 + }
+15
.sqlx/query-572b136b4196b7d755a252bc5bbf14b3765fc2e606b9512493e90efb4f1305fb.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET handle = $1 WHERE id = $2", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Uuid" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "572b136b4196b7d755a252bc5bbf14b3765fc2e606b9512493e90efb4f1305fb" 15 + }
+15
.sqlx/query-59e975c8599091b527ea2d097c68a8288360962d3e67f4db22e3dd92be924932.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET password_hash = $1 WHERE did = $2", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "59e975c8599091b527ea2d097c68a8288360962d3e67f4db22e3dd92be924932" 15 + }
+22
.sqlx/query-5b692e8f6d32dcbdcb45a3fff152a2be5672aadd807a4abab6914f80d57cba02.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT r.repo_root_cid\n FROM repos r\n JOIN users u ON r.user_id = u.id\n WHERE u.did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "repo_root_cid", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "5b692e8f6d32dcbdcb45a3fff152a2be5672aadd807a4abab6914f80d57cba02" 22 + }
+25
.sqlx/query-6c3a6dbf8d0d2a460054f093bd2ec1130ea91911d7d187cafcb4573be12bfcf4.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text", 15 + "Text", 16 + "Text", 17 + "Text" 18 + ] 19 + }, 20 + "nullable": [ 21 + false 22 + ] 23 + }, 24 + "hash": "6c3a6dbf8d0d2a460054f093bd2ec1130ea91911d7d187cafcb4573be12bfcf4" 25 + }
+23
.sqlx/query-6fd476d5640c20dc67db8e61e16b2f805e947ec404021cf93f6699f87bd7cfa5.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT id FROM users WHERE handle = $1 AND id != $2", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text", 15 + "Uuid" 16 + ] 17 + }, 18 + "nullable": [ 19 + false 20 + ] 21 + }, 22 + "hash": "6fd476d5640c20dc67db8e61e16b2f805e947ec404021cf93f6699f87bd7cfa5" 23 + }
+22
.sqlx/query-77dfbc702362db9bb87c9d17c4d985287d8faa024981ae26c995dfc53fb486fd.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT repo_root_cid FROM repos WHERE user_id = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "repo_root_cid", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "77dfbc702362db9bb87c9d17c4d985287d8faa024981ae26c995dfc53fb486fd" 22 + }
+14
.sqlx/query-7d0e11989d1b3f9c74daa3009505161369600509f103244fcd485b88869ee365.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM user_keys WHERE user_id = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "7d0e11989d1b3f9c74daa3009505161369600509f103244fcd485b88869ee365" 14 + }
+22
.sqlx/query-7d65d4608e93daa645ca0f8f6c7a23fb1bdaf50c7b0de900b1777542baf79d74.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT did FROM users WHERE handle = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "7d65d4608e93daa645ca0f8f6c7a23fb1bdaf50c7b0de900b1777542baf79d74" 22 + }
+14
.sqlx/query-824293a46fd22d2c6bf7dde85156214d561a95125ee3ba0f3bd6bb2c3a7625e5.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM repos WHERE user_id = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "824293a46fd22d2c6bf7dde85156214d561a95125ee3ba0f3bd6bb2c3a7625e5" 14 + }
+22
.sqlx/query-8e69c8aebcb15c6956b8a0b9324dcc8668b2e14731945ceaf30b756050be9882.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT id FROM users WHERE did = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "8e69c8aebcb15c6956b8a0b9324dcc8668b2e14731945ceaf30b756050be9882" 22 + }
+14
.sqlx/query-997cff04226e18bf4d5025092d0daf0450292bcbdba21b8530706797144dcafa.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM blobs WHERE created_by_user = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "997cff04226e18bf4d5025092d0daf0450292bcbdba21b8530706797144dcafa" 14 + }
+14
.sqlx/query-9c42b607a971b3a102d247def6c6fd322013f3885e9d0232d6e846220f893c49.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM sessions WHERE did = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "9c42b607a971b3a102d247def6c6fd322013f3885e9d0232d6e846220f893c49" 14 + }
+16
.sqlx/query-a5b7ceaa177ef136a0e2421eaca3f3edf283e9305bd4675d72a1b7a02c3dfc83.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text", 10 + "Text" 11 + ] 12 + }, 13 + "nullable": [] 14 + }, 15 + "hash": "a5b7ceaa177ef136a0e2421eaca3f3edf283e9305bd4675d72a1b7a02c3dfc83" 16 + }
+14
.sqlx/query-a802bf661ef9a9baa41604b31e3cca9067ca467faef9c952e98db418157c0642.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET deactivated_at = NOW() WHERE did = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "a802bf661ef9a9baa41604b31e3cca9067ca467faef9c952e98db418157c0642" 14 + }
+15
.sqlx/query-aac3acbfc4f27b98054602de28b763ce15e3a70a9e6741114d12f2d173f631fe.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "UPDATE users SET handle = $1 WHERE did = $2", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "aac3acbfc4f27b98054602de28b763ce15e3a70a9e6741114d12f2d173f631fe" 15 + }
+28
.sqlx/query-b2c53e6a278c4549c99a5b98cc7ca77fc1e9cd39a591c1d8ec1ca41adfffa3a6.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT id, did FROM users WHERE handle = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "did", 14 + "type_info": "Text" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "b2c53e6a278c4549c99a5b98cc7ca77fc1e9cd39a591c1d8ec1ca41adfffa3a6" 28 + }
+28
.sqlx/query-bb243abd63d10a43b11fa4f93644149f315894496255c5538f131b2c7aeb763b.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT storage_key, mime_type FROM blobs WHERE cid = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "storage_key", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "mime_type", 14 + "type_info": "Text" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "bb243abd63d10a43b11fa4f93644149f315894496255c5538f131b2c7aeb763b" 28 + }
+22
.sqlx/query-bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT deactivated_at FROM users WHERE did = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "deactivated_at", 9 + "type_info": "Timestamptz" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + true 19 + ] 20 + }, 21 + "hash": "bf2b237ee5cfe66d038dce3f564c05a683d391aebda7871d65d302e04b5d733f" 22 + }
+24
.sqlx/query-bf55c87dfdeb7bc18663a50d10eeee0e53fade51c1c47bed9580072435baefea.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT cid FROM blobs\n WHERE created_by_user = $1 AND cid > $2\n ORDER BY cid ASC\n LIMIT $3\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "cid", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid", 15 + "Text", 16 + "Int8" 17 + ] 18 + }, 19 + "nullable": [ 20 + false 21 + ] 22 + }, 23 + "hash": "bf55c87dfdeb7bc18663a50d10eeee0e53fade51c1c47bed9580072435baefea" 24 + }
+22
.sqlx/query-bf60faafb5c79a149ba237a984f78d068b5d691f6762641412a5aa1517605c04.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT record_cid FROM records WHERE repo_id = $1 AND collection = 'app.bsky.graph.follow'", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "record_cid", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "bf60faafb5c79a149ba237a984f78d068b5d691f6762641412a5aa1517605c04" 22 + }
+40
.sqlx/query-c2a90157c47bf1c36f08f4608932d214cc26b4794e0b922b1dae3dad18a7ddc0.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT did, handle, email, created_at\n FROM users\n WHERE did = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "handle", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "email", 19 + "type_info": "Text" 20 + }, 21 + { 22 + "ordinal": 3, 23 + "name": "created_at", 24 + "type_info": "Timestamptz" 25 + } 26 + ], 27 + "parameters": { 28 + "Left": [ 29 + "Text" 30 + ] 31 + }, 32 + "nullable": [ 33 + false, 34 + false, 35 + false, 36 + false 37 + ] 38 + }, 39 + "hash": "c2a90157c47bf1c36f08f4608932d214cc26b4794e0b922b1dae3dad18a7ddc0" 40 + }
+15
.sqlx/query-c4c9842e69c5fd4f4a2ebc176078af2a5f98beb3ea4d3c6af5b1b8fed2ec50e3.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO user_keys (user_id, key_bytes) VALUES ($1, $2)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid", 9 + "Bytea" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "c4c9842e69c5fd4f4a2ebc176078af2a5f98beb3ea4d3c6af5b1b8fed2ec50e3" 15 + }
+28
.sqlx/query-c949b23cf6d795c58e4c35907628bbb85714e9c49a569653b17acab60e1674ac.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "key_bytes", 14 + "type_info": "Bytea" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "c949b23cf6d795c58e4c35907628bbb85714e9c49a569653b17acab60e1674ac" 28 + }
+22
.sqlx/query-ce27e2da1f15cad97d2e31fda964e1d7017154fa559a8d9851728fb23af871cd.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT key_bytes FROM user_keys WHERE user_id = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "key_bytes", 9 + "type_info": "Bytea" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "ce27e2da1f15cad97d2e31fda964e1d7017154fa559a8d9851728fb23af871cd" 22 + }
+34
.sqlx/query-cec87a805457bcac6db8be601861b351a9332c649433894547176f6e4d672d01.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "name", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "created_at", 14 + "type_info": "Timestamptz" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "privileged", 19 + "type_info": "Bool" 20 + } 21 + ], 22 + "parameters": { 23 + "Left": [ 24 + "Uuid" 25 + ] 26 + }, 27 + "nullable": [ 28 + false, 29 + false, 30 + false 31 + ] 32 + }, 33 + "hash": "cec87a805457bcac6db8be601861b351a9332c649433894547176f6e4d672d01" 34 + }
+34
.sqlx/query-d31423ddcb625250d7c15581e8c9242ec6290b41507eb710744ad900d482222d.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "\n SELECT s.did, k.key_bytes, u.id as user_id\n FROM sessions s\n JOIN users u ON s.did = u.did\n JOIN user_keys k ON u.id = k.user_id\n WHERE s.access_jwt = $1\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "key_bytes", 14 + "type_info": "Bytea" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "user_id", 19 + "type_info": "Uuid" 20 + } 21 + ], 22 + "parameters": { 23 + "Left": [ 24 + "Text" 25 + ] 26 + }, 27 + "nullable": [ 28 + false, 29 + false, 30 + false 31 + ] 32 + }, 33 + "hash": "d31423ddcb625250d7c15581e8c9242ec6290b41507eb710744ad900d482222d" 34 + }
+16
.sqlx/query-db9950690548510474a2bf755b4c4c103b284e82e3cf23d17fc99cd2fc728c64.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Text", 9 + "Text", 10 + "Text" 11 + ] 12 + }, 13 + "nullable": [] 14 + }, 15 + "hash": "db9950690548510474a2bf755b4c4c103b284e82e3cf23d17fc99cd2fc728c64" 16 + }
+22
.sqlx/query-e2746221a24351293447246af539b5a847129cf93233dd98161193d4b7156b45.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "available_uses", 9 + "type_info": "Int4" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "e2746221a24351293447246af539b5a847129cf93233dd98161193d4b7156b45" 22 + }
+23
.sqlx/query-e80274b0731ed0ff9a1a1ae444eefadc193a172795b41c71445717020e6367d6.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT id FROM users WHERE handle = $1 AND did != $2", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text", 15 + "Text" 16 + ] 17 + }, 18 + "nullable": [ 19 + false 20 + ] 21 + }, 22 + "hash": "e80274b0731ed0ff9a1a1ae444eefadc193a172795b41c71445717020e6367d6" 23 + }
+22
.sqlx/query-ec5e14110d69ba47e8dd4447569c31506220df7759d0449e20d224609c7e6c4c.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT handle FROM users WHERE did = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "handle", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "ec5e14110d69ba47e8dd4447569c31506220df7759d0449e20d224609c7e6c4c" 22 + }
+22
.sqlx/query-ed34111a7f41b419a23d16ddd23cbc6aff9ab373946ff243512c52f857b7980d.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT 1 as one FROM users WHERE handle = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "one", 9 + "type_info": "Int4" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + null 19 + ] 20 + }, 21 + "hash": "ed34111a7f41b419a23d16ddd23cbc6aff9ab373946ff243512c52f857b7980d" 22 + }
+23
.sqlx/query-eecc3bc506aafe17d7804b9749dc00bd9fedcb148d2a339bbf7ffba08541e8f6.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "id", 9 + "type_info": "Uuid" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid", 15 + "Text" 16 + ] 17 + }, 18 + "nullable": [ 19 + false 20 + ] 21 + }, 22 + "hash": "eecc3bc506aafe17d7804b9749dc00bd9fedcb148d2a339bbf7ffba08541e8f6" 23 + }
+14
.sqlx/query-eed03ee02de1828a665bc268ba135511005416b122bccb79374204a8023ace41.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM records WHERE repo_id = $1", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid" 9 + ] 10 + }, 11 + "nullable": [] 12 + }, 13 + "hash": "eed03ee02de1828a665bc268ba135511005416b122bccb79374204a8023ace41" 14 + }
+22
.sqlx/query-ef55a06bcea9b1a0d744df4fe353260ae4d6d93bbf5ea73133db65e38f6241ee.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT k.key_bytes FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "key_bytes", 9 + "type_info": "Bytea" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Text" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "ef55a06bcea9b1a0d744df4fe353260ae4d6d93bbf5ea73133db65e38f6241ee" 22 + }
+15
.sqlx/query-f1d4742aaa1d48f4a944d50743a51b0938cec852d0238347883dc110050e8d3a.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "DELETE FROM app_passwords WHERE user_id = $1 AND name = $2", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Uuid", 9 + "Text" 10 + ] 11 + }, 12 + "nullable": [] 13 + }, 14 + "hash": "f1d4742aaa1d48f4a944d50743a51b0938cec852d0238347883dc110050e8d3a" 15 + }
+22
.sqlx/query-f8bee08776a6e246bdee70c0452217e8b654bf8617524edb4add139582b966ac.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT password_hash FROM app_passwords WHERE user_id = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "password_hash", 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + false 19 + ] 20 + }, 21 + "hash": "f8bee08776a6e246bdee70c0452217e8b654bf8617524edb4add139582b966ac" 22 + }
+28
.sqlx/query-f91a07e40484ade5b4c72addf62e4ad82feab312645c0b7a4ea69c0e55e17b14.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "did", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "key_bytes", 14 + "type_info": "Bytea" 15 + } 16 + ], 17 + "parameters": { 18 + "Left": [ 19 + "Text" 20 + ] 21 + }, 22 + "nullable": [ 23 + false, 24 + false 25 + ] 26 + }, 27 + "hash": "f91a07e40484ade5b4c72addf62e4ad82feab312645c0b7a4ea69c0e55e17b14" 28 + }
+22
.sqlx/query-fa09feeb85d8648a8ebf339a69114ab6b3c6336642d6907ed4549b601e401f82.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT COUNT(*) FROM records WHERE repo_id = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "count", 9 + "type_info": "Int8" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + null 19 + ] 20 + }, 21 + "hash": "fa09feeb85d8648a8ebf339a69114ab6b3c6336642d6907ed4549b601e401f82" 22 + }
+19
.sqlx/query-fc06fa0ba9ae769e68c7386a9236995fe678a15e63a692b8460d692b242ea3c7.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "INSERT INTO reports (id, reason_type, reason, subject_json, reported_by_did, created_at) VALUES ($1, $2, $3, $4, $5, $6)", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Left": [ 8 + "Int8", 9 + "Text", 10 + "Text", 11 + "Jsonb", 12 + "Text", 13 + "Timestamptz" 14 + ] 15 + }, 16 + "nullable": [] 17 + }, 18 + "hash": "fc06fa0ba9ae769e68c7386a9236995fe678a15e63a692b8460d692b242ea3c7" 19 + }
+22
.sqlx/query-fd42681b7af9c795643baf65a998a55822ea81a07b651385740401f62bf8a8ae.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT COUNT(*) FROM blobs WHERE created_by_user = $1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "count", 9 + "type_info": "Int8" 10 + } 11 + ], 12 + "parameters": { 13 + "Left": [ 14 + "Uuid" 15 + ] 16 + }, 17 + "nullable": [ 18 + null 19 + ] 20 + }, 21 + "hash": "fd42681b7af9c795643baf65a998a55822ea81a07b651385740401f62bf8a8ae" 22 + }
+37
.sqlx/query-ff7899984ea138f1e608fa862def47402369a428ac9116c653890e5fcaa0015b.json
···
··· 1 + { 2 + "db_name": "PostgreSQL", 3 + "query": "SELECT collection, rkey, record_cid FROM records WHERE repo_id = $1 AND (collection, rkey) > ($2, $3) ORDER BY collection, rkey LIMIT $4", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "ordinal": 0, 8 + "name": "collection", 9 + "type_info": "Text" 10 + }, 11 + { 12 + "ordinal": 1, 13 + "name": "rkey", 14 + "type_info": "Text" 15 + }, 16 + { 17 + "ordinal": 2, 18 + "name": "record_cid", 19 + "type_info": "Text" 20 + } 21 + ], 22 + "parameters": { 23 + "Left": [ 24 + "Uuid", 25 + "Text", 26 + "Text", 27 + "Int8" 28 + ] 29 + }, 30 + "nullable": [ 31 + false, 32 + false, 33 + false 34 + ] 35 + }, 36 + "hash": "ff7899984ea138f1e608fa862def47402369a428ac9116c653890e5fcaa0015b" 37 + }
+25 -51
src/api/admin/mod.rs
··· 7 }; 8 use serde::{Deserialize, Serialize}; 9 use serde_json::json; 10 - use sqlx::Row; 11 use tracing::error; 12 13 #[derive(Deserialize)] ··· 57 .into_response(); 58 } 59 60 - let result = sqlx::query( 61 r#" 62 SELECT did, handle, email, created_at 63 FROM users 64 WHERE did = $1 65 "#, 66 ) 67 - .bind(did) 68 .fetch_optional(&state.db) 69 .await; 70 71 match result { 72 Ok(Some(row)) => { 73 - let user_did: String = row.get("did"); 74 - let handle: String = row.get("handle"); 75 - let email: String = row.get("email"); 76 - let created_at: chrono::DateTime<chrono::Utc> = row.get("created_at"); 77 - 78 ( 79 StatusCode::OK, 80 Json(AccountInfo { 81 - did: user_did, 82 - handle, 83 - email: Some(email), 84 - indexed_at: created_at.to_rfc3339(), 85 invite_note: None, 86 invites_disabled: false, 87 email_confirmed_at: None, ··· 141 continue; 142 } 143 144 - let result = sqlx::query( 145 r#" 146 SELECT did, handle, email, created_at 147 FROM users 148 WHERE did = $1 149 "#, 150 ) 151 - .bind(did) 152 .fetch_optional(&state.db) 153 .await; 154 155 if let Ok(Some(row)) = result { 156 - let user_did: String = row.get("did"); 157 - let handle: String = row.get("handle"); 158 - let email: String = row.get("email"); 159 - let created_at: chrono::DateTime<chrono::Utc> = row.get("created_at"); 160 - 161 infos.push(AccountInfo { 162 - did: user_did, 163 - handle, 164 - email: Some(email), 165 - indexed_at: created_at.to_rfc3339(), 166 invite_note: None, 167 invites_disabled: false, 168 email_confirmed_at: None, ··· 202 .into_response(); 203 } 204 205 - let user = sqlx::query("SELECT id FROM users WHERE did = $1") 206 - .bind(did) 207 .fetch_optional(&state.db) 208 .await; 209 210 - let user_id: uuid::Uuid = match user { 211 - Ok(Some(row)) => row.get("id"), 212 Ok(None) => { 213 return ( 214 StatusCode::NOT_FOUND, ··· 226 } 227 }; 228 229 - let _ = sqlx::query("DELETE FROM sessions WHERE did = $1") 230 - .bind(did) 231 .execute(&state.db) 232 .await; 233 234 - let _ = sqlx::query("DELETE FROM records WHERE repo_id = $1") 235 - .bind(user_id) 236 .execute(&state.db) 237 .await; 238 239 - let _ = sqlx::query("DELETE FROM repos WHERE user_id = $1") 240 - .bind(user_id) 241 .execute(&state.db) 242 .await; 243 244 - let _ = sqlx::query("DELETE FROM blobs WHERE created_by_user = $1") 245 - .bind(user_id) 246 .execute(&state.db) 247 .await; 248 249 - let _ = sqlx::query("DELETE FROM user_keys WHERE user_id = $1") 250 - .bind(user_id) 251 .execute(&state.db) 252 .await; 253 254 - let result = sqlx::query("DELETE FROM users WHERE id = $1") 255 - .bind(user_id) 256 .execute(&state.db) 257 .await; 258 ··· 300 .into_response(); 301 } 302 303 - let result = sqlx::query("UPDATE users SET email = $1 WHERE did = $2") 304 - .bind(email) 305 - .bind(account) 306 .execute(&state.db) 307 .await; 308 ··· 370 .into_response(); 371 } 372 373 - let existing = sqlx::query("SELECT id FROM users WHERE handle = $1 AND did != $2") 374 - .bind(handle) 375 - .bind(did) 376 .fetch_optional(&state.db) 377 .await; 378 ··· 384 .into_response(); 385 } 386 387 - let result = sqlx::query("UPDATE users SET handle = $1 WHERE did = $2") 388 - .bind(handle) 389 - .bind(did) 390 .execute(&state.db) 391 .await; 392 ··· 455 } 456 }; 457 458 - let result = sqlx::query("UPDATE users SET password_hash = $1 WHERE did = $2") 459 - .bind(&password_hash) 460 - .bind(did) 461 .execute(&state.db) 462 .await; 463
··· 7 }; 8 use serde::{Deserialize, Serialize}; 9 use serde_json::json; 10 use tracing::error; 11 12 #[derive(Deserialize)] ··· 56 .into_response(); 57 } 58 59 + let result = sqlx::query!( 60 r#" 61 SELECT did, handle, email, created_at 62 FROM users 63 WHERE did = $1 64 "#, 65 + did 66 ) 67 .fetch_optional(&state.db) 68 .await; 69 70 match result { 71 Ok(Some(row)) => { 72 ( 73 StatusCode::OK, 74 Json(AccountInfo { 75 + did: row.did, 76 + handle: row.handle, 77 + email: Some(row.email), 78 + indexed_at: row.created_at.to_rfc3339(), 79 invite_note: None, 80 invites_disabled: false, 81 email_confirmed_at: None, ··· 135 continue; 136 } 137 138 + let result = sqlx::query!( 139 r#" 140 SELECT did, handle, email, created_at 141 FROM users 142 WHERE did = $1 143 "#, 144 + did 145 ) 146 .fetch_optional(&state.db) 147 .await; 148 149 if let Ok(Some(row)) = result { 150 infos.push(AccountInfo { 151 + did: row.did, 152 + handle: row.handle, 153 + email: Some(row.email), 154 + indexed_at: row.created_at.to_rfc3339(), 155 invite_note: None, 156 invites_disabled: false, 157 email_confirmed_at: None, ··· 191 .into_response(); 192 } 193 194 + let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 195 .fetch_optional(&state.db) 196 .await; 197 198 + let user_id = match user { 199 + Ok(Some(row)) => row.id, 200 Ok(None) => { 201 return ( 202 StatusCode::NOT_FOUND, ··· 214 } 215 }; 216 217 + let _ = sqlx::query!("DELETE FROM sessions WHERE did = $1", did) 218 .execute(&state.db) 219 .await; 220 221 + let _ = sqlx::query!("DELETE FROM records WHERE repo_id = $1", user_id) 222 .execute(&state.db) 223 .await; 224 225 + let _ = sqlx::query!("DELETE FROM repos WHERE user_id = $1", user_id) 226 .execute(&state.db) 227 .await; 228 229 + let _ = sqlx::query!("DELETE FROM blobs WHERE created_by_user = $1", user_id) 230 .execute(&state.db) 231 .await; 232 233 + let _ = sqlx::query!("DELETE FROM user_keys WHERE user_id = $1", user_id) 234 .execute(&state.db) 235 .await; 236 237 + let result = sqlx::query!("DELETE FROM users WHERE id = $1", user_id) 238 .execute(&state.db) 239 .await; 240 ··· 282 .into_response(); 283 } 284 285 + let result = sqlx::query!("UPDATE users SET email = $1 WHERE did = $2", email, account) 286 .execute(&state.db) 287 .await; 288 ··· 350 .into_response(); 351 } 352 353 + let existing = sqlx::query!("SELECT id FROM users WHERE handle = $1 AND did != $2", handle, did) 354 .fetch_optional(&state.db) 355 .await; 356 ··· 362 .into_response(); 363 } 364 365 + let result = sqlx::query!("UPDATE users SET handle = $1 WHERE did = $2", handle, did) 366 .execute(&state.db) 367 .await; 368 ··· 431 } 432 }; 433 434 + let result = sqlx::query!("UPDATE users SET password_hash = $1 WHERE did = $2", password_hash, did) 435 .execute(&state.db) 436 .await; 437
+11 -15
src/api/feed/timeline.rs
··· 59 .unwrap_or("") 60 .replace("Bearer ", ""); 61 62 - let session = sqlx::query( 63 - "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1" 64 ) 65 - .bind(&token) 66 .fetch_optional(&state.db) 67 .await 68 .unwrap_or(None); 69 70 let (did, key_bytes) = match session { 71 - Some(row) => ( 72 - row.get::<String, _>("did"), 73 - row.get::<Vec<u8>, _>("key_bytes"), 74 - ), 75 None => { 76 return ( 77 StatusCode::UNAUTHORIZED, ··· 89 .into_response(); 90 } 91 92 - let user_query = sqlx::query("SELECT id FROM users WHERE did = $1") 93 - .bind(&did) 94 .fetch_optional(&state.db) 95 .await; 96 97 - let user_id: uuid::Uuid = match user_query { 98 - Ok(Some(row)) => row.get("id"), 99 _ => { 100 return ( 101 StatusCode::INTERNAL_SERVER_ERROR, ··· 105 } 106 }; 107 108 - let follows_query = sqlx::query( 109 - "SELECT record_cid FROM records WHERE repo_id = $1 AND collection = 'app.bsky.graph.follow'" 110 ) 111 - .bind(user_id) 112 .fetch_all(&state.db) 113 .await; 114 115 let follow_cids: Vec<String> = match follows_query { 116 - Ok(rows) => rows.iter().map(|r| r.get("record_cid")).collect(), 117 Err(e) => { 118 error!("Failed to get follows: {:?}", e); 119 return (
··· 59 .unwrap_or("") 60 .replace("Bearer ", ""); 61 62 + let session = sqlx::query!( 63 + "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1", 64 + token 65 ) 66 .fetch_optional(&state.db) 67 .await 68 .unwrap_or(None); 69 70 let (did, key_bytes) = match session { 71 + Some(row) => (row.did, row.key_bytes), 72 None => { 73 return ( 74 StatusCode::UNAUTHORIZED, ··· 86 .into_response(); 87 } 88 89 + let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 90 .fetch_optional(&state.db) 91 .await; 92 93 + let user_id = match user_query { 94 + Ok(Some(row)) => row.id, 95 _ => { 96 return ( 97 StatusCode::INTERNAL_SERVER_ERROR, ··· 101 } 102 }; 103 104 + let follows_query = sqlx::query!( 105 + "SELECT record_cid FROM records WHERE repo_id = $1 AND collection = 'app.bsky.graph.follow'", 106 + user_id 107 ) 108 .fetch_all(&state.db) 109 .await; 110 111 let follow_cids: Vec<String> = match follows_query { 112 + Ok(rows) => rows.iter().map(|r| r.record_cid.clone()).collect(), 113 Err(e) => { 114 error!("Failed to get follows: {:?}", e); 115 return (
+19 -29
src/api/identity/account.rs
··· 13 use rand::rngs::OsRng; 14 use serde::{Deserialize, Serialize}; 15 use serde_json::json; 16 - use sqlx::Row; 17 use std::sync::Arc; 18 use tracing::{error, info}; 19 ··· 82 } 83 }; 84 85 - let exists_query = sqlx::query("SELECT 1 FROM users WHERE handle = $1") 86 - .bind(&input.handle) 87 .fetch_optional(&mut *tx) 88 .await; 89 ··· 108 109 if let Some(code) = &input.invite_code { 110 let invite_query = 111 - sqlx::query("SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE") 112 - .bind(code) 113 .fetch_optional(&mut *tx) 114 .await; 115 116 match invite_query { 117 Ok(Some(row)) => { 118 - let uses: i32 = row.get("available_uses"); 119 - if uses <= 0 { 120 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code exhausted"}))).into_response(); 121 } 122 123 - let update_invite = sqlx::query( 124 "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 125 ) 126 - .bind(code) 127 .execute(&mut *tx) 128 .await; 129 ··· 166 } 167 }; 168 169 - let user_insert = sqlx::query("INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id") 170 - .bind(&input.handle) 171 - .bind(&input.email) 172 - .bind(&did) 173 - .bind(&password_hash) 174 .fetch_one(&mut *tx) 175 .await; 176 177 - let user_id: uuid::Uuid = match user_insert { 178 - Ok(row) => row.get("id"), 179 Err(e) => { 180 error!("Error inserting user: {:?}", e); 181 // TODO: Check for unique constraint violation on email/did specifically ··· 190 let secret_key = SecretKey::random(&mut OsRng); 191 let secret_key_bytes = secret_key.to_bytes(); 192 193 - let key_insert = sqlx::query("INSERT INTO user_keys (user_id, key_bytes) VALUES ($1, $2)") 194 - .bind(user_id) 195 - .bind(&secret_key_bytes[..]) 196 .execute(&mut *tx) 197 .await; 198 ··· 257 } 258 }; 259 260 - let repo_insert = sqlx::query("INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)") 261 - .bind(user_id) 262 - .bind(commit_cid.to_string()) 263 .execute(&mut *tx) 264 .await; 265 ··· 274 275 if let Some(code) = &input.invite_code { 276 let use_insert = 277 - sqlx::query("INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)") 278 - .bind(code) 279 - .bind(user_id) 280 .execute(&mut *tx) 281 .await; 282 ··· 317 }; 318 319 let session_insert = 320 - sqlx::query("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)") 321 - .bind(&access_jwt) 322 - .bind(&refresh_jwt) 323 - .bind(&did) 324 .execute(&mut *tx) 325 .await; 326
··· 13 use rand::rngs::OsRng; 14 use serde::{Deserialize, Serialize}; 15 use serde_json::json; 16 use std::sync::Arc; 17 use tracing::{error, info}; 18 ··· 81 } 82 }; 83 84 + let exists_query = sqlx::query!("SELECT 1 as one FROM users WHERE handle = $1", input.handle) 85 .fetch_optional(&mut *tx) 86 .await; 87 ··· 106 107 if let Some(code) = &input.invite_code { 108 let invite_query = 109 + sqlx::query!("SELECT available_uses FROM invite_codes WHERE code = $1 FOR UPDATE", code) 110 .fetch_optional(&mut *tx) 111 .await; 112 113 match invite_query { 114 Ok(Some(row)) => { 115 + if row.available_uses <= 0 { 116 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidInviteCode", "message": "Invite code exhausted"}))).into_response(); 117 } 118 119 + let update_invite = sqlx::query!( 120 "UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1", 121 + code 122 ) 123 .execute(&mut *tx) 124 .await; 125 ··· 162 } 163 }; 164 165 + let user_insert = sqlx::query!( 166 + "INSERT INTO users (handle, email, did, password_hash) VALUES ($1, $2, $3, $4) RETURNING id", 167 + input.handle, 168 + input.email, 169 + did, 170 + password_hash 171 + ) 172 .fetch_one(&mut *tx) 173 .await; 174 175 + let user_id = match user_insert { 176 + Ok(row) => row.id, 177 Err(e) => { 178 error!("Error inserting user: {:?}", e); 179 // TODO: Check for unique constraint violation on email/did specifically ··· 188 let secret_key = SecretKey::random(&mut OsRng); 189 let secret_key_bytes = secret_key.to_bytes(); 190 191 + let key_insert = sqlx::query!("INSERT INTO user_keys (user_id, key_bytes) VALUES ($1, $2)", user_id, &secret_key_bytes[..]) 192 .execute(&mut *tx) 193 .await; 194 ··· 253 } 254 }; 255 256 + let commit_cid_str = commit_cid.to_string(); 257 + let repo_insert = sqlx::query!("INSERT INTO repos (user_id, repo_root_cid) VALUES ($1, $2)", user_id, commit_cid_str) 258 .execute(&mut *tx) 259 .await; 260 ··· 269 270 if let Some(code) = &input.invite_code { 271 let use_insert = 272 + sqlx::query!("INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)", code, user_id) 273 .execute(&mut *tx) 274 .await; 275 ··· 310 }; 311 312 let session_insert = 313 + sqlx::query!("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", access_jwt, refresh_jwt, did) 314 .execute(&mut *tx) 315 .await; 316
+14 -35
src/api/identity/did.rs
··· 11 use reqwest; 12 use serde::Deserialize; 13 use serde_json::json; 14 - use sqlx::Row; 15 use tracing::error; 16 17 #[derive(Deserialize)] ··· 33 .into_response(); 34 } 35 36 - let user = sqlx::query("SELECT did FROM users WHERE handle = $1") 37 - .bind(handle) 38 .fetch_optional(&state.db) 39 .await; 40 41 match user { 42 Ok(Some(row)) => { 43 - let did: String = row.get("did"); 44 - (StatusCode::OK, Json(json!({ "did": did }))).into_response() 45 } 46 Ok(None) => ( 47 StatusCode::NOT_FOUND, ··· 97 pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<String>) -> Response { 98 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 99 100 - let user = sqlx::query("SELECT id, did FROM users WHERE handle = $1") 101 - .bind(&handle) 102 .fetch_optional(&state.db) 103 .await; 104 105 let (user_id, did) = match user { 106 - Ok(Some(row)) => { 107 - let id: uuid::Uuid = row.get("id"); 108 - let d: String = row.get("did"); 109 - (id, d) 110 - } 111 Ok(None) => { 112 return (StatusCode::NOT_FOUND, Json(json!({"error": "NotFound"}))).into_response(); 113 } ··· 129 .into_response(); 130 } 131 132 - let key_row = sqlx::query("SELECT key_bytes FROM user_keys WHERE user_id = $1") 133 - .bind(user_id) 134 .fetch_optional(&state.db) 135 .await; 136 137 let key_bytes: Vec<u8> = match key_row { 138 - Ok(Some(row)) => row.get("key_bytes"), 139 _ => { 140 return ( 141 StatusCode::INTERNAL_SERVER_ERROR, ··· 294 .unwrap_or("") 295 .replace("Bearer ", ""); 296 297 - let session = sqlx::query( 298 r#" 299 SELECT s.did, k.key_bytes, u.handle 300 FROM sessions s ··· 302 JOIN user_keys k ON u.id = k.user_id 303 WHERE s.access_jwt = $1 304 "#, 305 ) 306 - .bind(&token) 307 .fetch_optional(&state.db) 308 .await; 309 310 let (_did, key_bytes, handle) = match session { 311 - Ok(Some(row)) => ( 312 - row.get::<String, _>("did"), 313 - row.get::<Vec<u8>, _>("key_bytes"), 314 - row.get::<String, _>("handle"), 315 - ), 316 Ok(None) => { 317 return ( 318 StatusCode::UNAUTHORIZED, ··· 404 .unwrap_or("") 405 .replace("Bearer ", ""); 406 407 - let session = sqlx::query( 408 r#" 409 SELECT s.did, k.key_bytes, u.id as user_id 410 FROM sessions s ··· 412 JOIN user_keys k ON u.id = k.user_id 413 WHERE s.access_jwt = $1 414 "#, 415 ) 416 - .bind(&token) 417 .fetch_optional(&state.db) 418 .await; 419 420 let (_did, key_bytes, user_id) = match session { 421 - Ok(Some(row)) => ( 422 - row.get::<String, _>("did"), 423 - row.get::<Vec<u8>, _>("key_bytes"), 424 - row.get::<uuid::Uuid, _>("user_id"), 425 - ), 426 Ok(None) => { 427 return ( 428 StatusCode::UNAUTHORIZED, ··· 468 .into_response(); 469 } 470 471 - let existing = sqlx::query("SELECT id FROM users WHERE handle = $1 AND id != $2") 472 - .bind(new_handle) 473 - .bind(user_id) 474 .fetch_optional(&state.db) 475 .await; 476 ··· 482 .into_response(); 483 } 484 485 - let result = sqlx::query("UPDATE users SET handle = $1 WHERE id = $2") 486 - .bind(new_handle) 487 - .bind(user_id) 488 .execute(&state.db) 489 .await; 490
··· 11 use reqwest; 12 use serde::Deserialize; 13 use serde_json::json; 14 use tracing::error; 15 16 #[derive(Deserialize)] ··· 32 .into_response(); 33 } 34 35 + let user = sqlx::query!("SELECT did FROM users WHERE handle = $1", handle) 36 .fetch_optional(&state.db) 37 .await; 38 39 match user { 40 Ok(Some(row)) => { 41 + (StatusCode::OK, Json(json!({ "did": row.did }))).into_response() 42 } 43 Ok(None) => ( 44 StatusCode::NOT_FOUND, ··· 94 pub async fn user_did_doc(State(state): State<AppState>, Path(handle): Path<String>) -> Response { 95 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 96 97 + let user = sqlx::query!("SELECT id, did FROM users WHERE handle = $1", handle) 98 .fetch_optional(&state.db) 99 .await; 100 101 let (user_id, did) = match user { 102 + Ok(Some(row)) => (row.id, row.did), 103 Ok(None) => { 104 return (StatusCode::NOT_FOUND, Json(json!({"error": "NotFound"}))).into_response(); 105 } ··· 121 .into_response(); 122 } 123 124 + let key_row = sqlx::query!("SELECT key_bytes FROM user_keys WHERE user_id = $1", user_id) 125 .fetch_optional(&state.db) 126 .await; 127 128 let key_bytes: Vec<u8> = match key_row { 129 + Ok(Some(row)) => row.key_bytes, 130 _ => { 131 return ( 132 StatusCode::INTERNAL_SERVER_ERROR, ··· 285 .unwrap_or("") 286 .replace("Bearer ", ""); 287 288 + let session = sqlx::query!( 289 r#" 290 SELECT s.did, k.key_bytes, u.handle 291 FROM sessions s ··· 293 JOIN user_keys k ON u.id = k.user_id 294 WHERE s.access_jwt = $1 295 "#, 296 + token 297 ) 298 .fetch_optional(&state.db) 299 .await; 300 301 let (_did, key_bytes, handle) = match session { 302 + Ok(Some(row)) => (row.did, row.key_bytes, row.handle), 303 Ok(None) => { 304 return ( 305 StatusCode::UNAUTHORIZED, ··· 391 .unwrap_or("") 392 .replace("Bearer ", ""); 393 394 + let session = sqlx::query!( 395 r#" 396 SELECT s.did, k.key_bytes, u.id as user_id 397 FROM sessions s ··· 399 JOIN user_keys k ON u.id = k.user_id 400 WHERE s.access_jwt = $1 401 "#, 402 + token 403 ) 404 .fetch_optional(&state.db) 405 .await; 406 407 let (_did, key_bytes, user_id) = match session { 408 + Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 409 Ok(None) => { 410 return ( 411 StatusCode::UNAUTHORIZED, ··· 451 .into_response(); 452 } 453 454 + let existing = sqlx::query!("SELECT id FROM users WHERE handle = $1 AND id != $2", new_handle, user_id) 455 .fetch_optional(&state.db) 456 .await; 457 ··· 463 .into_response(); 464 } 465 466 + let result = sqlx::query!("UPDATE users SET handle = $1 WHERE id = $2", new_handle, user_id) 467 .execute(&state.db) 468 .await; 469
+12 -15
src/api/moderation/mod.rs
··· 7 }; 8 use serde::{Deserialize, Serialize}; 9 use serde_json::{Value, json}; 10 - use sqlx::Row; 11 use tracing::error; 12 13 #[derive(Deserialize)] ··· 49 .unwrap_or("") 50 .replace("Bearer ", ""); 51 52 - let session = sqlx::query( 53 r#" 54 SELECT s.did, k.key_bytes 55 FROM sessions s ··· 57 JOIN user_keys k ON u.id = k.user_id 58 WHERE s.access_jwt = $1 59 "#, 60 ) 61 - .bind(&token) 62 .fetch_optional(&state.db) 63 .await; 64 65 let (did, key_bytes) = match session { 66 - Ok(Some(row)) => ( 67 - row.get::<String, _>("did"), 68 - row.get::<Vec<u8>, _>("key_bytes"), 69 - ), 70 Ok(None) => { 71 return ( 72 StatusCode::UNAUTHORIZED, ··· 113 let created_at = chrono::Utc::now(); 114 let report_id = created_at.timestamp_millis(); 115 116 - let insert = sqlx::query( 117 - "INSERT INTO reports (id, reason_type, reason, subject_json, reported_by_did, created_at) VALUES ($1, $2, $3, $4, $5, $6)" 118 ) 119 - .bind(report_id) 120 - .bind(&input.reason_type) 121 - .bind(&input.reason) 122 - .bind(json!(input.subject)) 123 - .bind(&did) 124 - .bind(created_at) 125 .execute(&state.db) 126 .await; 127
··· 7 }; 8 use serde::{Deserialize, Serialize}; 9 use serde_json::{Value, json}; 10 use tracing::error; 11 12 #[derive(Deserialize)] ··· 48 .unwrap_or("") 49 .replace("Bearer ", ""); 50 51 + let session = sqlx::query!( 52 r#" 53 SELECT s.did, k.key_bytes 54 FROM sessions s ··· 56 JOIN user_keys k ON u.id = k.user_id 57 WHERE s.access_jwt = $1 58 "#, 59 + token 60 ) 61 .fetch_optional(&state.db) 62 .await; 63 64 let (did, key_bytes) = match session { 65 + Ok(Some(row)) => (row.did, row.key_bytes), 66 Ok(None) => { 67 return ( 68 StatusCode::UNAUTHORIZED, ··· 109 let created_at = chrono::Utc::now(); 110 let report_id = created_at.timestamp_millis(); 111 112 + let subject_json = json!(input.subject); 113 + let insert = sqlx::query!( 114 + "INSERT INTO reports (id, reason_type, reason, subject_json, reported_by_did, created_at) VALUES ($1, $2, $3, $4, $5, $6)", 115 + report_id, 116 + input.reason_type, 117 + input.reason, 118 + subject_json, 119 + did, 120 + created_at 121 ) 122 .execute(&state.db) 123 .await; 124
+2 -5
src/api/proxy.rs
··· 6 response::{IntoResponse, Response}, 7 }; 8 use reqwest::Client; 9 - use sqlx::Row; 10 use std::collections::HashMap; 11 use tracing::{error, info}; 12 ··· 48 if let Ok(token) = auth_val.to_str() { 49 let token = token.replace("Bearer ", ""); 50 if let Ok(did) = crate::auth::get_did_from_token(&token) { 51 - let key_row = sqlx::query("SELECT k.key_bytes FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1") 52 - .bind(&did) 53 .fetch_optional(&state.db) 54 .await; 55 56 if let Ok(Some(row)) = key_row { 57 - let key_bytes: Vec<u8> = row.get("key_bytes"); 58 if let Ok(new_token) = 59 - crate::auth::create_service_token(&did, aud, &method, &key_bytes) 60 { 61 if let Ok(val) = 62 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
··· 6 response::{IntoResponse, Response}, 7 }; 8 use reqwest::Client; 9 use std::collections::HashMap; 10 use tracing::{error, info}; 11 ··· 47 if let Ok(token) = auth_val.to_str() { 48 let token = token.replace("Bearer ", ""); 49 if let Ok(did) = crate::auth::get_did_from_token(&token) { 50 + let key_row = sqlx::query!("SELECT k.key_bytes FROM user_keys k JOIN users u ON k.user_id = u.id WHERE u.did = $1", did) 51 .fetch_optional(&state.db) 52 .await; 53 54 if let Ok(Some(row)) = key_row { 55 if let Ok(new_token) = 56 + crate::auth::create_service_token(&did, aud, &method, &row.key_bytes) 57 { 58 if let Ok(val) = 59 axum::http::HeaderValue::from_str(&format!("Bearer {}", new_token))
+31 -42
src/api/repo/blob.rs
··· 12 use serde::{Deserialize, Serialize}; 13 use serde_json::json; 14 use sha2::{Digest, Sha256}; 15 - use sqlx::Row; 16 use std::str::FromStr; 17 use tracing::error; 18 ··· 35 .unwrap_or("") 36 .replace("Bearer ", ""); 37 38 - let session = sqlx::query( 39 - "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1" 40 ) 41 - .bind(&token) 42 .fetch_optional(&state.db) 43 .await 44 .unwrap_or(None); 45 46 let (did, key_bytes) = match session { 47 - Some(row) => ( 48 - row.get::<String, _>("did"), 49 - row.get::<Vec<u8>, _>("key_bytes"), 50 - ), 51 None => { 52 return ( 53 StatusCode::UNAUTHORIZED, ··· 92 .into_response(); 93 } 94 95 - let user_query = sqlx::query("SELECT id FROM users WHERE did = $1") 96 - .bind(&did) 97 .fetch_optional(&state.db) 98 .await; 99 100 - let user_id: uuid::Uuid = match user_query { 101 - Ok(Some(row)) => row.get("id"), 102 _ => { 103 return ( 104 StatusCode::INTERNAL_SERVER_ERROR, ··· 108 } 109 }; 110 111 - let insert = sqlx::query( 112 - "INSERT INTO blobs (cid, mime_type, size_bytes, created_by_user, storage_key) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (cid) DO NOTHING" 113 ) 114 - .bind(&cid_str) 115 - .bind(&mime_type) 116 - .bind(size) 117 - .bind(user_id) 118 - .bind(&storage_key) 119 .execute(&state.db) 120 .await; 121 ··· 202 .unwrap_or("") 203 .replace("Bearer ", ""); 204 205 - let session = sqlx::query( 206 - "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1" 207 ) 208 - .bind(&token) 209 .fetch_optional(&state.db) 210 .await 211 .unwrap_or(None); 212 213 let (did, key_bytes) = match session { 214 - Some(row) => ( 215 - row.get::<String, _>("did"), 216 - row.get::<Vec<u8>, _>("key_bytes"), 217 - ), 218 None => { 219 return ( 220 StatusCode::UNAUTHORIZED, ··· 232 .into_response(); 233 } 234 235 - let user_query = sqlx::query("SELECT id FROM users WHERE did = $1") 236 - .bind(&did) 237 .fetch_optional(&state.db) 238 .await; 239 240 - let user_id: uuid::Uuid = match user_query { 241 - Ok(Some(row)) => row.get("id"), 242 _ => { 243 return ( 244 StatusCode::INTERNAL_SERVER_ERROR, ··· 257 (String::new(), String::new()) 258 }; 259 260 - let records_query = sqlx::query( 261 - "SELECT collection, rkey, record_cid FROM records WHERE repo_id = $1 AND (collection, rkey) > ($2, $3) ORDER BY collection, rkey LIMIT $4" 262 ) 263 - .bind(user_id) 264 - .bind(cursor_collection) 265 - .bind(cursor_rkey) 266 - .bind(limit) 267 .fetch_all(&state.db) 268 .await; 269 ··· 283 let mut last_cursor = None; 284 285 for row in &records { 286 - let collection: String = row.get("collection"); 287 - let rkey: String = row.get("rkey"); 288 - let record_cid_str: String = row.get("record_cid"); 289 290 last_cursor = Some(format!("{}|{}", collection, rkey)); 291 ··· 308 find_blobs(&record_val, &mut blobs); 309 310 for blob_cid_str in blobs { 311 - let exists = sqlx::query("SELECT 1 FROM blobs WHERE cid = $1 AND created_by_user = $2") 312 - .bind(&blob_cid_str) 313 - .bind(user_id) 314 .fetch_optional(&state.db) 315 .await; 316
··· 12 use serde::{Deserialize, Serialize}; 13 use serde_json::json; 14 use sha2::{Digest, Sha256}; 15 use std::str::FromStr; 16 use tracing::error; 17 ··· 34 .unwrap_or("") 35 .replace("Bearer ", ""); 36 37 + let session = sqlx::query!( 38 + "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1", 39 + token 40 ) 41 .fetch_optional(&state.db) 42 .await 43 .unwrap_or(None); 44 45 let (did, key_bytes) = match session { 46 + Some(row) => (row.did, row.key_bytes), 47 None => { 48 return ( 49 StatusCode::UNAUTHORIZED, ··· 88 .into_response(); 89 } 90 91 + let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 92 .fetch_optional(&state.db) 93 .await; 94 95 + let user_id = match user_query { 96 + Ok(Some(row)) => row.id, 97 _ => { 98 return ( 99 StatusCode::INTERNAL_SERVER_ERROR, ··· 103 } 104 }; 105 106 + let insert = sqlx::query!( 107 + "INSERT INTO blobs (cid, mime_type, size_bytes, created_by_user, storage_key) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (cid) DO NOTHING", 108 + cid_str, 109 + mime_type, 110 + size, 111 + user_id, 112 + storage_key 113 ) 114 .execute(&state.db) 115 .await; 116 ··· 197 .unwrap_or("") 198 .replace("Bearer ", ""); 199 200 + let session = sqlx::query!( 201 + "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.access_jwt = $1", 202 + token 203 ) 204 .fetch_optional(&state.db) 205 .await 206 .unwrap_or(None); 207 208 let (did, key_bytes) = match session { 209 + Some(row) => (row.did, row.key_bytes), 210 None => { 211 return ( 212 StatusCode::UNAUTHORIZED, ··· 224 .into_response(); 225 } 226 227 + let user_query = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 228 .fetch_optional(&state.db) 229 .await; 230 231 + let user_id = match user_query { 232 + Ok(Some(row)) => row.id, 233 _ => { 234 return ( 235 StatusCode::INTERNAL_SERVER_ERROR, ··· 248 (String::new(), String::new()) 249 }; 250 251 + let records_query = sqlx::query!( 252 + "SELECT collection, rkey, record_cid FROM records WHERE repo_id = $1 AND (collection, rkey) > ($2, $3) ORDER BY collection, rkey LIMIT $4", 253 + user_id, 254 + cursor_collection, 255 + cursor_rkey, 256 + limit 257 ) 258 .fetch_all(&state.db) 259 .await; 260 ··· 274 let mut last_cursor = None; 275 276 for row in &records { 277 + let collection = &row.collection; 278 + let rkey = &row.rkey; 279 + let record_cid_str = &row.record_cid; 280 281 last_cursor = Some(format!("{}|{}", collection, rkey)); 282 ··· 299 find_blobs(&record_val, &mut blobs); 300 301 for blob_cid_str in blobs { 302 + let exists = sqlx::query!("SELECT 1 as one FROM blobs WHERE cid = $1 AND created_by_user = $2", blob_cid_str, user_id) 303 .fetch_optional(&state.db) 304 .await; 305
+1 -1
src/api/server/meta.rs
··· 15 } 16 17 pub async fn health(State(state): State<AppState>) -> impl IntoResponse { 18 - match sqlx::query("SELECT 1").execute(&state.db).await { 19 Ok(_) => (StatusCode::OK, "OK"), 20 Err(e) => { 21 error!("Health check failed: {:?}", e);
··· 15 } 16 17 pub async fn health(State(state): State<AppState>) -> impl IntoResponse { 18 + match sqlx::query!("SELECT 1 as one").fetch_one(&state.db).await { 19 Ok(_) => (StatusCode::OK, "OK"), 20 Err(e) => { 21 error!("Health check failed: {:?}", e);
+81 -127
src/api/server/session.rs
··· 8 use bcrypt::verify; 9 use serde::{Deserialize, Serialize}; 10 use serde_json::json; 11 - use sqlx::Row; 12 use tracing::{error, info, warn}; 13 14 #[derive(Deserialize)] ··· 43 .unwrap_or("") 44 .replace("Bearer ", ""); 45 46 - let session = sqlx::query( 47 r#" 48 SELECT s.did, k.key_bytes 49 FROM sessions s ··· 51 JOIN user_keys k ON u.id = k.user_id 52 WHERE s.access_jwt = $1 53 "#, 54 ) 55 - .bind(&token) 56 .fetch_optional(&state.db) 57 .await; 58 59 let (did, key_bytes) = match session { 60 - Ok(Some(row)) => ( 61 - row.get::<String, _>("did"), 62 - row.get::<Vec<u8>, _>("key_bytes"), 63 - ), 64 Ok(None) => { 65 return ( 66 StatusCode::UNAUTHORIZED, ··· 125 ) -> Response { 126 info!("create_session: identifier='{}'", input.identifier); 127 128 - let user_row = sqlx::query("SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1") 129 - .bind(&input.identifier) 130 .fetch_optional(&state.db) 131 .await; 132 133 match user_row { 134 Ok(Some(row)) => { 135 - let user_id: uuid::Uuid = row.get("id"); 136 - let stored_hash: String = row.get("password_hash"); 137 - let did: String = row.get("did"); 138 - let handle: String = row.get("handle"); 139 - let key_bytes: Vec<u8> = row.get("key_bytes"); 140 141 - let password_valid = if verify(&input.password, &stored_hash).unwrap_or(false) { 142 true 143 } else { 144 - let app_pass_rows = sqlx::query("SELECT password_hash FROM app_passwords WHERE user_id = $1") 145 - .bind(user_id) 146 .fetch_all(&state.db) 147 .await 148 .unwrap_or_default(); 149 150 app_pass_rows.iter().any(|row| { 151 - let hash: String = row.get("password_hash"); 152 - verify(&input.password, &hash).unwrap_or(false) 153 }) 154 }; 155 ··· 178 } 179 }; 180 181 - let session_insert = sqlx::query( 182 "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", 183 ) 184 - .bind(&access_jwt) 185 - .bind(&refresh_jwt) 186 - .bind(&did) 187 .execute(&state.db) 188 .await; 189 ··· 194 Json(CreateSessionOutput { 195 access_jwt, 196 refresh_jwt, 197 - handle, 198 - did, 199 }), 200 ) 201 .into_response(); ··· 255 .unwrap_or("") 256 .replace("Bearer ", ""); 257 258 - let result = sqlx::query( 259 r#" 260 SELECT u.handle, u.did, u.email, k.key_bytes 261 FROM sessions s ··· 263 JOIN user_keys k ON u.id = k.user_id 264 WHERE s.access_jwt = $1 265 "#, 266 ) 267 - .bind(&token) 268 .fetch_optional(&state.db) 269 .await; 270 271 match result { 272 Ok(Some(row)) => { 273 - let handle: String = row.get("handle"); 274 - let did: String = row.get("did"); 275 - let email: String = row.get("email"); 276 - let key_bytes: Vec<u8> = row.get("key_bytes"); 277 - 278 - if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 279 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response(); 280 } 281 282 return ( 283 StatusCode::OK, 284 Json(json!({ 285 - "handle": handle, 286 - "did": did, 287 - "email": email, 288 "didDoc": {} 289 })), 290 ) ··· 327 .unwrap_or("") 328 .replace("Bearer ", ""); 329 330 - let result = sqlx::query("DELETE FROM sessions WHERE access_jwt = $1") 331 - .bind(token) 332 .execute(&state.db) 333 .await; 334 ··· 369 .unwrap_or("") 370 .replace("Bearer ", ""); 371 372 - let session = sqlx::query( 373 - "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1" 374 ) 375 - .bind(&refresh_token) 376 .fetch_optional(&state.db) 377 .await; 378 379 match session { 380 Ok(Some(session_row)) => { 381 - let did: String = session_row.get("did"); 382 - let key_bytes: Vec<u8> = session_row.get("key_bytes"); 383 384 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) { 385 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response(); ··· 408 } 409 }; 410 411 - let update = sqlx::query( 412 "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3", 413 ) 414 - .bind(&new_access_jwt) 415 - .bind(&new_refresh_jwt) 416 - .bind(&refresh_token) 417 .execute(&state.db) 418 .await; 419 420 match update { 421 Ok(_) => { 422 - let user = sqlx::query("SELECT handle FROM users WHERE did = $1") 423 - .bind(&did) 424 .fetch_optional(&state.db) 425 .await; 426 427 match user { 428 Ok(Some(u)) => { 429 - let handle: String = u.get("handle"); 430 return ( 431 StatusCode::OK, 432 Json(json!({ 433 "accessJwt": new_access_jwt, 434 "refreshJwt": new_refresh_jwt, 435 - "handle": handle, 436 "did": did 437 })), 438 ) ··· 517 .unwrap_or("") 518 .replace("Bearer ", ""); 519 520 - let session = sqlx::query( 521 r#" 522 SELECT s.did, k.key_bytes, u.id as user_id 523 FROM sessions s ··· 525 JOIN user_keys k ON u.id = k.user_id 526 WHERE s.access_jwt = $1 527 "#, 528 ) 529 - .bind(&token) 530 .fetch_optional(&state.db) 531 .await; 532 533 let (did, key_bytes, user_id) = match session { 534 - Ok(Some(row)) => ( 535 - row.get::<String, _>("did"), 536 - row.get::<Vec<u8>, _>("key_bytes"), 537 - row.get::<uuid::Uuid, _>("user_id"), 538 - ), 539 Ok(None) => { 540 return ( 541 StatusCode::UNAUTHORIZED, ··· 561 .into_response(); 562 } 563 564 - let user_status = sqlx::query("SELECT deactivated_at FROM users WHERE did = $1") 565 - .bind(&did) 566 .fetch_optional(&state.db) 567 .await; 568 569 - let deactivated_at: Option<chrono::DateTime<chrono::Utc>> = match user_status { 570 - Ok(Some(row)) => row.get("deactivated_at"), 571 _ => None, 572 }; 573 574 - let repo_result = sqlx::query("SELECT repo_root_cid FROM repos WHERE user_id = $1") 575 - .bind(user_id) 576 .fetch_optional(&state.db) 577 .await; 578 579 let repo_commit = match repo_result { 580 - Ok(Some(row)) => row.get::<String, _>("repo_root_cid"), 581 _ => String::new(), 582 }; 583 584 - let record_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM records WHERE repo_id = $1") 585 - .bind(user_id) 586 .fetch_one(&state.db) 587 .await 588 .unwrap_or(0); 589 590 let blob_count: i64 = 591 - sqlx::query_scalar("SELECT COUNT(*) FROM blobs WHERE created_by_user = $1") 592 - .bind(user_id) 593 .fetch_one(&state.db) 594 .await 595 .unwrap_or(0); 596 597 let valid_did = did.starts_with("did:"); ··· 632 .unwrap_or("") 633 .replace("Bearer ", ""); 634 635 - let session = sqlx::query( 636 r#" 637 SELECT s.did, k.key_bytes 638 FROM sessions s ··· 640 JOIN user_keys k ON u.id = k.user_id 641 WHERE s.access_jwt = $1 642 "#, 643 ) 644 - .bind(&token) 645 .fetch_optional(&state.db) 646 .await; 647 648 let (did, key_bytes) = match session { 649 - Ok(Some(row)) => ( 650 - row.get::<String, _>("did"), 651 - row.get::<Vec<u8>, _>("key_bytes"), 652 - ), 653 Ok(None) => { 654 return ( 655 StatusCode::UNAUTHORIZED, ··· 675 .into_response(); 676 } 677 678 - let result = sqlx::query("UPDATE users SET deactivated_at = NULL WHERE did = $1") 679 - .bind(&did) 680 .execute(&state.db) 681 .await; 682 ··· 719 .unwrap_or("") 720 .replace("Bearer ", ""); 721 722 - let session = sqlx::query( 723 r#" 724 SELECT s.did, k.key_bytes 725 FROM sessions s ··· 727 JOIN user_keys k ON u.id = k.user_id 728 WHERE s.access_jwt = $1 729 "#, 730 ) 731 - .bind(&token) 732 .fetch_optional(&state.db) 733 .await; 734 735 let (did, key_bytes) = match session { 736 - Ok(Some(row)) => ( 737 - row.get::<String, _>("did"), 738 - row.get::<Vec<u8>, _>("key_bytes"), 739 - ), 740 Ok(None) => { 741 return ( 742 StatusCode::UNAUTHORIZED, ··· 762 .into_response(); 763 } 764 765 - let result = sqlx::query("UPDATE users SET deactivated_at = NOW() WHERE did = $1") 766 - .bind(&did) 767 .execute(&state.db) 768 .await; 769 ··· 812 .unwrap_or("") 813 .replace("Bearer ", ""); 814 815 - let session = sqlx::query( 816 r#" 817 SELECT s.did, k.key_bytes, u.id as user_id 818 FROM sessions s ··· 820 JOIN user_keys k ON u.id = k.user_id 821 WHERE s.access_jwt = $1 822 "#, 823 ) 824 - .bind(&token) 825 .fetch_optional(&state.db) 826 .await; 827 828 let (_did, key_bytes, user_id) = match session { 829 - Ok(Some(row)) => ( 830 - row.get::<String, _>("did"), 831 - row.get::<Vec<u8>, _>("key_bytes"), 832 - row.get::<uuid::Uuid, _>("user_id"), 833 - ), 834 Ok(None) => { 835 return ( 836 StatusCode::UNAUTHORIZED, ··· 856 .into_response(); 857 } 858 859 - let result = sqlx::query("SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC") 860 - .bind(user_id) 861 .fetch_all(&state.db) 862 .await; 863 ··· 866 let passwords: Vec<AppPassword> = rows 867 .iter() 868 .map(|row| { 869 - let name: String = row.get("name"); 870 - let created_at: chrono::DateTime<chrono::Utc> = row.get("created_at"); 871 - let privileged: bool = row.get("privileged"); 872 AppPassword { 873 - name, 874 - created_at: created_at.to_rfc3339(), 875 - privileged, 876 } 877 }) 878 .collect(); ··· 925 .unwrap_or("") 926 .replace("Bearer ", ""); 927 928 - let session = sqlx::query( 929 r#" 930 SELECT s.did, k.key_bytes, u.id as user_id 931 FROM sessions s ··· 933 JOIN user_keys k ON u.id = k.user_id 934 WHERE s.access_jwt = $1 935 "#, 936 ) 937 - .bind(&token) 938 .fetch_optional(&state.db) 939 .await; 940 941 let (_did, key_bytes, user_id) = match session { 942 - Ok(Some(row)) => ( 943 - row.get::<String, _>("did"), 944 - row.get::<Vec<u8>, _>("key_bytes"), 945 - row.get::<uuid::Uuid, _>("user_id"), 946 - ), 947 Ok(None) => { 948 return ( 949 StatusCode::UNAUTHORIZED, ··· 978 .into_response(); 979 } 980 981 - let existing = sqlx::query("SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2") 982 - .bind(user_id) 983 - .bind(name) 984 .fetch_optional(&state.db) 985 .await; 986 ··· 1017 let privileged = input.privileged.unwrap_or(false); 1018 let created_at = chrono::Utc::now(); 1019 1020 - let result = sqlx::query( 1021 - "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)" 1022 ) 1023 - .bind(user_id) 1024 - .bind(name) 1025 - .bind(&password_hash) 1026 - .bind(created_at) 1027 - .bind(privileged) 1028 .execute(&state.db) 1029 .await; 1030 ··· 1075 .unwrap_or("") 1076 .replace("Bearer ", ""); 1077 1078 - let session = sqlx::query( 1079 r#" 1080 SELECT s.did, k.key_bytes, u.id as user_id 1081 FROM sessions s ··· 1083 JOIN user_keys k ON u.id = k.user_id 1084 WHERE s.access_jwt = $1 1085 "#, 1086 ) 1087 - .bind(&token) 1088 .fetch_optional(&state.db) 1089 .await; 1090 1091 let (_did, key_bytes, user_id) = match session { 1092 - Ok(Some(row)) => ( 1093 - row.get::<String, _>("did"), 1094 - row.get::<Vec<u8>, _>("key_bytes"), 1095 - row.get::<uuid::Uuid, _>("user_id"), 1096 - ), 1097 Ok(None) => { 1098 return ( 1099 StatusCode::UNAUTHORIZED, ··· 1128 .into_response(); 1129 } 1130 1131 - let result = sqlx::query("DELETE FROM app_passwords WHERE user_id = $1 AND name = $2") 1132 - .bind(user_id) 1133 - .bind(name) 1134 .execute(&state.db) 1135 .await; 1136
··· 8 use bcrypt::verify; 9 use serde::{Deserialize, Serialize}; 10 use serde_json::json; 11 use tracing::{error, info, warn}; 12 13 #[derive(Deserialize)] ··· 42 .unwrap_or("") 43 .replace("Bearer ", ""); 44 45 + let session = sqlx::query!( 46 r#" 47 SELECT s.did, k.key_bytes 48 FROM sessions s ··· 50 JOIN user_keys k ON u.id = k.user_id 51 WHERE s.access_jwt = $1 52 "#, 53 + token 54 ) 55 .fetch_optional(&state.db) 56 .await; 57 58 let (did, key_bytes) = match session { 59 + Ok(Some(row)) => (row.did, row.key_bytes), 60 Ok(None) => { 61 return ( 62 StatusCode::UNAUTHORIZED, ··· 121 ) -> Response { 122 info!("create_session: identifier='{}'", input.identifier); 123 124 + let user_row = sqlx::query!( 125 + "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1", 126 + input.identifier 127 + ) 128 .fetch_optional(&state.db) 129 .await; 130 131 match user_row { 132 Ok(Some(row)) => { 133 + let user_id = row.id; 134 + let stored_hash = &row.password_hash; 135 + let did = &row.did; 136 + let handle = &row.handle; 137 + let key_bytes = &row.key_bytes; 138 139 + let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) { 140 true 141 } else { 142 + let app_pass_rows = sqlx::query!("SELECT password_hash FROM app_passwords WHERE user_id = $1", user_id) 143 .fetch_all(&state.db) 144 .await 145 .unwrap_or_default(); 146 147 app_pass_rows.iter().any(|row| { 148 + verify(&input.password, &row.password_hash).unwrap_or(false) 149 }) 150 }; 151 ··· 174 } 175 }; 176 177 + let session_insert = sqlx::query!( 178 "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", 179 + access_jwt, 180 + refresh_jwt, 181 + did 182 ) 183 .execute(&state.db) 184 .await; 185 ··· 190 Json(CreateSessionOutput { 191 access_jwt, 192 refresh_jwt, 193 + handle: handle.clone(), 194 + did: did.clone(), 195 }), 196 ) 197 .into_response(); ··· 251 .unwrap_or("") 252 .replace("Bearer ", ""); 253 254 + let result = sqlx::query!( 255 r#" 256 SELECT u.handle, u.did, u.email, k.key_bytes 257 FROM sessions s ··· 259 JOIN user_keys k ON u.id = k.user_id 260 WHERE s.access_jwt = $1 261 "#, 262 + token 263 ) 264 .fetch_optional(&state.db) 265 .await; 266 267 match result { 268 Ok(Some(row)) => { 269 + if let Err(_) = crate::auth::verify_token(&token, &row.key_bytes) { 270 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response(); 271 } 272 273 return ( 274 StatusCode::OK, 275 Json(json!({ 276 + "handle": row.handle, 277 + "did": row.did, 278 + "email": row.email, 279 "didDoc": {} 280 })), 281 ) ··· 318 .unwrap_or("") 319 .replace("Bearer ", ""); 320 321 + let result = sqlx::query!("DELETE FROM sessions WHERE access_jwt = $1", token) 322 .execute(&state.db) 323 .await; 324 ··· 359 .unwrap_or("") 360 .replace("Bearer ", ""); 361 362 + let session = sqlx::query!( 363 + "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1", 364 + refresh_token 365 ) 366 .fetch_optional(&state.db) 367 .await; 368 369 match session { 370 Ok(Some(session_row)) => { 371 + let did = &session_row.did; 372 + let key_bytes = &session_row.key_bytes; 373 374 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) { 375 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response(); ··· 398 } 399 }; 400 401 + let update = sqlx::query!( 402 "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3", 403 + new_access_jwt, 404 + new_refresh_jwt, 405 + refresh_token 406 ) 407 .execute(&state.db) 408 .await; 409 410 match update { 411 Ok(_) => { 412 + let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did) 413 .fetch_optional(&state.db) 414 .await; 415 416 match user { 417 Ok(Some(u)) => { 418 return ( 419 StatusCode::OK, 420 Json(json!({ 421 "accessJwt": new_access_jwt, 422 "refreshJwt": new_refresh_jwt, 423 + "handle": u.handle, 424 "did": did 425 })), 426 ) ··· 505 .unwrap_or("") 506 .replace("Bearer ", ""); 507 508 + let session = sqlx::query!( 509 r#" 510 SELECT s.did, k.key_bytes, u.id as user_id 511 FROM sessions s ··· 513 JOIN user_keys k ON u.id = k.user_id 514 WHERE s.access_jwt = $1 515 "#, 516 + token 517 ) 518 .fetch_optional(&state.db) 519 .await; 520 521 let (did, key_bytes, user_id) = match session { 522 + Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 523 Ok(None) => { 524 return ( 525 StatusCode::UNAUTHORIZED, ··· 545 .into_response(); 546 } 547 548 + let user_status = sqlx::query!("SELECT deactivated_at FROM users WHERE did = $1", did) 549 .fetch_optional(&state.db) 550 .await; 551 552 + let deactivated_at = match user_status { 553 + Ok(Some(row)) => row.deactivated_at, 554 _ => None, 555 }; 556 557 + let repo_result = sqlx::query!("SELECT repo_root_cid FROM repos WHERE user_id = $1", user_id) 558 .fetch_optional(&state.db) 559 .await; 560 561 let repo_commit = match repo_result { 562 + Ok(Some(row)) => row.repo_root_cid, 563 _ => String::new(), 564 }; 565 566 + let record_count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM records WHERE repo_id = $1", user_id) 567 .fetch_one(&state.db) 568 .await 569 + .unwrap_or(Some(0)) 570 .unwrap_or(0); 571 572 let blob_count: i64 = 573 + sqlx::query_scalar!("SELECT COUNT(*) FROM blobs WHERE created_by_user = $1", user_id) 574 .fetch_one(&state.db) 575 .await 576 + .unwrap_or(Some(0)) 577 .unwrap_or(0); 578 579 let valid_did = did.starts_with("did:"); ··· 614 .unwrap_or("") 615 .replace("Bearer ", ""); 616 617 + let session = sqlx::query!( 618 r#" 619 SELECT s.did, k.key_bytes 620 FROM sessions s ··· 622 JOIN user_keys k ON u.id = k.user_id 623 WHERE s.access_jwt = $1 624 "#, 625 + token 626 ) 627 .fetch_optional(&state.db) 628 .await; 629 630 let (did, key_bytes) = match session { 631 + Ok(Some(row)) => (row.did, row.key_bytes), 632 Ok(None) => { 633 return ( 634 StatusCode::UNAUTHORIZED, ··· 654 .into_response(); 655 } 656 657 + let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did) 658 .execute(&state.db) 659 .await; 660 ··· 697 .unwrap_or("") 698 .replace("Bearer ", ""); 699 700 + let session = sqlx::query!( 701 r#" 702 SELECT s.did, k.key_bytes 703 FROM sessions s ··· 705 JOIN user_keys k ON u.id = k.user_id 706 WHERE s.access_jwt = $1 707 "#, 708 + token 709 ) 710 .fetch_optional(&state.db) 711 .await; 712 713 let (did, key_bytes) = match session { 714 + Ok(Some(row)) => (row.did, row.key_bytes), 715 Ok(None) => { 716 return ( 717 StatusCode::UNAUTHORIZED, ··· 737 .into_response(); 738 } 739 740 + let result = sqlx::query!("UPDATE users SET deactivated_at = NOW() WHERE did = $1", did) 741 .execute(&state.db) 742 .await; 743 ··· 786 .unwrap_or("") 787 .replace("Bearer ", ""); 788 789 + let session = sqlx::query!( 790 r#" 791 SELECT s.did, k.key_bytes, u.id as user_id 792 FROM sessions s ··· 794 JOIN user_keys k ON u.id = k.user_id 795 WHERE s.access_jwt = $1 796 "#, 797 + token 798 ) 799 .fetch_optional(&state.db) 800 .await; 801 802 let (_did, key_bytes, user_id) = match session { 803 + Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 804 Ok(None) => { 805 return ( 806 StatusCode::UNAUTHORIZED, ··· 826 .into_response(); 827 } 828 829 + let result = sqlx::query!("SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", user_id) 830 .fetch_all(&state.db) 831 .await; 832 ··· 835 let passwords: Vec<AppPassword> = rows 836 .iter() 837 .map(|row| { 838 AppPassword { 839 + name: row.name.clone(), 840 + created_at: row.created_at.to_rfc3339(), 841 + privileged: row.privileged, 842 } 843 }) 844 .collect(); ··· 891 .unwrap_or("") 892 .replace("Bearer ", ""); 893 894 + let session = sqlx::query!( 895 r#" 896 SELECT s.did, k.key_bytes, u.id as user_id 897 FROM sessions s ··· 899 JOIN user_keys k ON u.id = k.user_id 900 WHERE s.access_jwt = $1 901 "#, 902 + token 903 ) 904 .fetch_optional(&state.db) 905 .await; 906 907 let (_did, key_bytes, user_id) = match session { 908 + Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 909 Ok(None) => { 910 return ( 911 StatusCode::UNAUTHORIZED, ··· 940 .into_response(); 941 } 942 943 + let existing = sqlx::query!("SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name) 944 .fetch_optional(&state.db) 945 .await; 946 ··· 977 let privileged = input.privileged.unwrap_or(false); 978 let created_at = chrono::Utc::now(); 979 980 + let result = sqlx::query!( 981 + "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)", 982 + user_id, 983 + name, 984 + password_hash, 985 + created_at, 986 + privileged 987 ) 988 .execute(&state.db) 989 .await; 990 ··· 1035 .unwrap_or("") 1036 .replace("Bearer ", ""); 1037 1038 + let session = sqlx::query!( 1039 r#" 1040 SELECT s.did, k.key_bytes, u.id as user_id 1041 FROM sessions s ··· 1043 JOIN user_keys k ON u.id = k.user_id 1044 WHERE s.access_jwt = $1 1045 "#, 1046 + token 1047 ) 1048 .fetch_optional(&state.db) 1049 .await; 1050 1051 let (_did, key_bytes, user_id) = match session { 1052 + Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 1053 Ok(None) => { 1054 return ( 1055 StatusCode::UNAUTHORIZED, ··· 1084 .into_response(); 1085 } 1086 1087 + let result = sqlx::query!("DELETE FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name) 1088 .execute(&state.db) 1089 .await; 1090
+9 -15
src/repo/mod.rs
··· 5 use jacquard_repo::storage::BlockStore; 6 use multihash::Multihash; 7 use sha2::{Digest, Sha256}; 8 - use sqlx::{PgPool, Row}; 9 10 #[derive(Clone)] 11 pub struct PostgresBlockStore { ··· 21 impl BlockStore for PostgresBlockStore { 22 async fn get(&self, cid: &Cid) -> Result<Option<Bytes>, RepoError> { 23 let cid_bytes = cid.to_bytes(); 24 - let row = sqlx::query("SELECT data FROM blocks WHERE cid = $1") 25 - .bind(cid_bytes) 26 .fetch_optional(&self.pool) 27 .await 28 .map_err(|e| RepoError::storage(e))?; 29 30 match row { 31 - Some(row) => { 32 - let data: Vec<u8> = row.get("data"); 33 - Ok(Some(Bytes::from(data))) 34 - } 35 None => Ok(None), 36 } 37 } ··· 44 let cid = Cid::new_v1(0x71, multihash); 45 let cid_bytes = cid.to_bytes(); 46 47 - sqlx::query("INSERT INTO blocks (cid, data) VALUES ($1, $2) ON CONFLICT (cid) DO NOTHING") 48 - .bind(cid_bytes) 49 - .bind(data) 50 .execute(&self.pool) 51 .await 52 .map_err(|e| RepoError::storage(e))?; ··· 56 57 async fn has(&self, cid: &Cid) -> Result<bool, RepoError> { 58 let cid_bytes = cid.to_bytes(); 59 - let row = sqlx::query("SELECT 1 FROM blocks WHERE cid = $1") 60 - .bind(cid_bytes) 61 .fetch_optional(&self.pool) 62 .await 63 .map_err(|e| RepoError::storage(e))?; ··· 72 let blocks: Vec<_> = blocks.into_iter().collect(); 73 for (cid, data) in blocks { 74 let cid_bytes = cid.to_bytes(); 75 - sqlx::query( 76 "INSERT INTO blocks (cid, data) VALUES ($1, $2) ON CONFLICT (cid) DO NOTHING", 77 ) 78 - .bind(cid_bytes) 79 - .bind(data.as_ref()) 80 .execute(&self.pool) 81 .await 82 .map_err(|e| RepoError::storage(e))?;
··· 5 use jacquard_repo::storage::BlockStore; 6 use multihash::Multihash; 7 use sha2::{Digest, Sha256}; 8 + use sqlx::PgPool; 9 10 #[derive(Clone)] 11 pub struct PostgresBlockStore { ··· 21 impl BlockStore for PostgresBlockStore { 22 async fn get(&self, cid: &Cid) -> Result<Option<Bytes>, RepoError> { 23 let cid_bytes = cid.to_bytes(); 24 + let row = sqlx::query!("SELECT data FROM blocks WHERE cid = $1", &cid_bytes) 25 .fetch_optional(&self.pool) 26 .await 27 .map_err(|e| RepoError::storage(e))?; 28 29 match row { 30 + Some(row) => Ok(Some(Bytes::from(row.data))), 31 None => Ok(None), 32 } 33 } ··· 40 let cid = Cid::new_v1(0x71, multihash); 41 let cid_bytes = cid.to_bytes(); 42 43 + sqlx::query!("INSERT INTO blocks (cid, data) VALUES ($1, $2) ON CONFLICT (cid) DO NOTHING", &cid_bytes, data) 44 .execute(&self.pool) 45 .await 46 .map_err(|e| RepoError::storage(e))?; ··· 50 51 async fn has(&self, cid: &Cid) -> Result<bool, RepoError> { 52 let cid_bytes = cid.to_bytes(); 53 + let row = sqlx::query!("SELECT 1 as one FROM blocks WHERE cid = $1", &cid_bytes) 54 .fetch_optional(&self.pool) 55 .await 56 .map_err(|e| RepoError::storage(e))?; ··· 65 let blocks: Vec<_> = blocks.into_iter().collect(); 66 for (cid, data) in blocks { 67 let cid_bytes = cid.to_bytes(); 68 + let data_ref = data.as_ref(); 69 + sqlx::query!( 70 "INSERT INTO blocks (cid, data) VALUES ($1, $2) ON CONFLICT (cid) DO NOTHING", 71 + &cid_bytes, 72 + data_ref 73 ) 74 .execute(&self.pool) 75 .await 76 .map_err(|e| RepoError::storage(e))?;
+39 -45
src/sync/mod.rs
··· 9 }; 10 use serde::{Deserialize, Serialize}; 11 use serde_json::json; 12 - use sqlx::Row; 13 use tracing::{error, info}; 14 15 #[derive(Deserialize)] ··· 37 .into_response(); 38 } 39 40 - let result = sqlx::query( 41 r#" 42 SELECT r.repo_root_cid 43 FROM repos r 44 JOIN users u ON r.user_id = u.id 45 WHERE u.did = $1 46 "#, 47 ) 48 - .bind(did) 49 .fetch_optional(&state.db) 50 .await; 51 52 match result { 53 Ok(Some(row)) => { 54 - let cid: String = row.get("repo_root_cid"); 55 ( 56 StatusCode::OK, 57 Json(GetLatestCommitOutput { 58 - cid, 59 rev: chrono::Utc::now().timestamp_millis().to_string(), 60 }), 61 ) ··· 105 let limit = params.limit.unwrap_or(50).min(1000); 106 let cursor_did = params.cursor.as_deref().unwrap_or(""); 107 108 - let result = sqlx::query( 109 r#" 110 SELECT u.did, r.repo_root_cid 111 FROM repos r ··· 114 ORDER BY u.did ASC 115 LIMIT $2 116 "#, 117 ) 118 - .bind(cursor_did) 119 - .bind(limit + 1) 120 .fetch_all(&state.db) 121 .await; 122 ··· 127 .iter() 128 .take(limit as usize) 129 .map(|row| { 130 - let did: String = row.get("did"); 131 - let head: String = row.get("repo_root_cid"); 132 RepoInfo { 133 - did, 134 - head, 135 rev: chrono::Utc::now().timestamp_millis().to_string(), 136 active: true, 137 } ··· 193 .into_response(); 194 } 195 196 - let user_exists = sqlx::query("SELECT id FROM users WHERE did = $1") 197 - .bind(did) 198 .fetch_optional(&state.db) 199 .await; 200 ··· 217 Ok(Some(_)) => {} 218 } 219 220 - let blob_result = sqlx::query("SELECT storage_key, mime_type FROM blobs WHERE cid = $1") 221 - .bind(cid) 222 .fetch_optional(&state.db) 223 .await; 224 225 match blob_result { 226 Ok(Some(row)) => { 227 - let storage_key: String = row.get("storage_key"); 228 - let mime_type: String = row.get("mime_type"); 229 230 match state.blob_store.get(&storage_key).await { 231 Ok(data) => Response::builder() ··· 290 let limit = params.limit.unwrap_or(500).min(1000); 291 let cursor_cid = params.cursor.as_deref().unwrap_or(""); 292 293 - let user_result = sqlx::query("SELECT id FROM users WHERE did = $1") 294 - .bind(did) 295 .fetch_optional(&state.db) 296 .await; 297 298 - let user_id: uuid::Uuid = match user_result { 299 - Ok(Some(row)) => row.get("id"), 300 Ok(None) => { 301 return ( 302 StatusCode::NOT_FOUND, ··· 314 } 315 }; 316 317 - let result = if let Some(since) = &params.since { 318 - sqlx::query( 319 r#" 320 SELECT cid FROM blobs 321 WHERE created_by_user = $1 AND cid > $2 AND created_at > $3 322 ORDER BY cid ASC 323 LIMIT $4 324 "#, 325 ) 326 - .bind(user_id) 327 - .bind(cursor_cid) 328 - .bind(since) 329 - .bind(limit + 1) 330 .fetch_all(&state.db) 331 .await 332 } else { 333 - sqlx::query( 334 r#" 335 SELECT cid FROM blobs 336 WHERE created_by_user = $1 AND cid > $2 337 ORDER BY cid ASC 338 LIMIT $3 339 "#, 340 ) 341 - .bind(user_id) 342 - .bind(cursor_cid) 343 - .bind(limit + 1) 344 .fetch_all(&state.db) 345 .await 346 }; 347 348 - match result { 349 - Ok(rows) => { 350 - let has_more = rows.len() as i64 > limit; 351 - let cids: Vec<String> = rows 352 - .iter() 353 .take(limit as usize) 354 - .map(|row| row.get("cid")) 355 .collect(); 356 357 let next_cursor = if has_more { ··· 406 .into_response(); 407 } 408 409 - let result = sqlx::query( 410 r#" 411 SELECT u.did, r.repo_root_cid 412 FROM users u 413 LEFT JOIN repos r ON u.id = r.user_id 414 WHERE u.did = $1 415 "#, 416 ) 417 - .bind(did) 418 .fetch_optional(&state.db) 419 .await; 420 421 match result { 422 Ok(Some(row)) => { 423 - let user_did: String = row.get("did"); 424 - let repo_root: Option<String> = row.get("repo_root_cid"); 425 - 426 - let rev = repo_root.map(|_| chrono::Utc::now().timestamp_millis().to_string()); 427 428 ( 429 StatusCode::OK, 430 Json(GetRepoStatusOutput { 431 - did: user_did, 432 active: true, 433 rev, 434 }),
··· 9 }; 10 use serde::{Deserialize, Serialize}; 11 use serde_json::json; 12 use tracing::{error, info}; 13 14 #[derive(Deserialize)] ··· 36 .into_response(); 37 } 38 39 + let result = sqlx::query!( 40 r#" 41 SELECT r.repo_root_cid 42 FROM repos r 43 JOIN users u ON r.user_id = u.id 44 WHERE u.did = $1 45 "#, 46 + did 47 ) 48 .fetch_optional(&state.db) 49 .await; 50 51 match result { 52 Ok(Some(row)) => { 53 ( 54 StatusCode::OK, 55 Json(GetLatestCommitOutput { 56 + cid: row.repo_root_cid, 57 rev: chrono::Utc::now().timestamp_millis().to_string(), 58 }), 59 ) ··· 103 let limit = params.limit.unwrap_or(50).min(1000); 104 let cursor_did = params.cursor.as_deref().unwrap_or(""); 105 106 + let result = sqlx::query!( 107 r#" 108 SELECT u.did, r.repo_root_cid 109 FROM repos r ··· 112 ORDER BY u.did ASC 113 LIMIT $2 114 "#, 115 + cursor_did, 116 + limit + 1 117 ) 118 .fetch_all(&state.db) 119 .await; 120 ··· 125 .iter() 126 .take(limit as usize) 127 .map(|row| { 128 RepoInfo { 129 + did: row.did.clone(), 130 + head: row.repo_root_cid.clone(), 131 rev: chrono::Utc::now().timestamp_millis().to_string(), 132 active: true, 133 } ··· 189 .into_response(); 190 } 191 192 + let user_exists = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 193 .fetch_optional(&state.db) 194 .await; 195 ··· 212 Ok(Some(_)) => {} 213 } 214 215 + let blob_result = sqlx::query!("SELECT storage_key, mime_type FROM blobs WHERE cid = $1", cid) 216 .fetch_optional(&state.db) 217 .await; 218 219 match blob_result { 220 Ok(Some(row)) => { 221 + let storage_key = &row.storage_key; 222 + let mime_type = &row.mime_type; 223 224 match state.blob_store.get(&storage_key).await { 225 Ok(data) => Response::builder() ··· 284 let limit = params.limit.unwrap_or(500).min(1000); 285 let cursor_cid = params.cursor.as_deref().unwrap_or(""); 286 287 + let user_result = sqlx::query!("SELECT id FROM users WHERE did = $1", did) 288 .fetch_optional(&state.db) 289 .await; 290 291 + let user_id = match user_result { 292 + Ok(Some(row)) => row.id, 293 Ok(None) => { 294 return ( 295 StatusCode::NOT_FOUND, ··· 307 } 308 }; 309 310 + let cids_result: Result<Vec<String>, sqlx::Error> = if let Some(since) = &params.since { 311 + let since_time = chrono::DateTime::parse_from_rfc3339(since) 312 + .map(|dt| dt.with_timezone(&chrono::Utc)) 313 + .unwrap_or_else(|_| chrono::Utc::now()); 314 + sqlx::query!( 315 r#" 316 SELECT cid FROM blobs 317 WHERE created_by_user = $1 AND cid > $2 AND created_at > $3 318 ORDER BY cid ASC 319 LIMIT $4 320 "#, 321 + user_id, 322 + cursor_cid, 323 + since_time, 324 + limit + 1 325 ) 326 .fetch_all(&state.db) 327 .await 328 + .map(|rows| rows.into_iter().map(|r| r.cid).collect()) 329 } else { 330 + sqlx::query!( 331 r#" 332 SELECT cid FROM blobs 333 WHERE created_by_user = $1 AND cid > $2 334 ORDER BY cid ASC 335 LIMIT $3 336 "#, 337 + user_id, 338 + cursor_cid, 339 + limit + 1 340 ) 341 .fetch_all(&state.db) 342 .await 343 + .map(|rows| rows.into_iter().map(|r| r.cid).collect()) 344 }; 345 346 + match cids_result { 347 + Ok(cids) => { 348 + let has_more = cids.len() as i64 > limit; 349 + let cids: Vec<String> = cids 350 + .into_iter() 351 .take(limit as usize) 352 .collect(); 353 354 let next_cursor = if has_more { ··· 403 .into_response(); 404 } 405 406 + let result = sqlx::query!( 407 r#" 408 SELECT u.did, r.repo_root_cid 409 FROM users u 410 LEFT JOIN repos r ON u.id = r.user_id 411 WHERE u.did = $1 412 "#, 413 + did 414 ) 415 .fetch_optional(&state.db) 416 .await; 417 418 match result { 419 Ok(Some(row)) => { 420 + let rev = Some(chrono::Utc::now().timestamp_millis().to_string()); 421 422 ( 423 StatusCode::OK, 424 Json(GetRepoStatusOutput { 425 + did: row.did, 426 active: true, 427 rev, 428 }),