mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-25 02:36:06 +00:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05246aec58 | ||
|
|
8d0e0c6810 | ||
|
|
1720de8650 | ||
|
|
a5c68a3506 | ||
|
|
9ea33def13 | ||
|
|
a9e3ec63e1 | ||
|
|
dbc3c23c95 | ||
|
|
531ca7c1c0 | ||
|
|
add1ff176b | ||
|
|
bdb200a30c | ||
|
|
e5faba3e73 | ||
|
|
5dc810ceef | ||
|
|
6abc0eaca2 | ||
|
|
696940f732 | ||
|
|
f777c288da | ||
|
|
9039648705 | ||
|
|
770a16b043 | ||
|
|
d37f38e496 | ||
|
|
282f08f207 | ||
|
|
f07bcb82f7 | ||
|
|
e38343ce09 | ||
|
|
e454e99b8a | ||
|
|
19b0ea19c6 | ||
|
|
318818ad51 | ||
|
|
7c55a5ceb9 | ||
|
|
9d31ee9ace | ||
|
|
e3efcb43ba | ||
|
|
eb46824220 | ||
|
|
e6c8a44a42 | ||
|
|
aac6f2818b | ||
|
|
1a80a33e12 | ||
|
|
00d71fd3d6 | ||
|
|
c8bb4c9076 | ||
|
|
d123168787 | ||
|
|
b337d2b154 | ||
|
|
1e07d674dd | ||
|
|
a3f96b6367 | ||
|
|
7b7936d539 | ||
|
|
7e3dca4026 | ||
|
|
7bc90d5e23 |
@@ -13,9 +13,22 @@ fail-fast = false
|
||||
test-threads = "num-cpus"
|
||||
slow-timeout = { period = "30s", terminate-after = 4 }
|
||||
|
||||
[profile.sim-pr]
|
||||
retries = 0
|
||||
fail-fast = true
|
||||
test-threads = "num-cpus"
|
||||
slow-timeout = { period = "60s", terminate-after = 4 }
|
||||
|
||||
[profile.sim-nightly]
|
||||
retries = 0
|
||||
fail-fast = false
|
||||
test-threads = "num-cpus"
|
||||
slow-timeout = { period = "300s", terminate-after = 2 }
|
||||
|
||||
[test-groups]
|
||||
serial-env-tests = { max-threads = 1 }
|
||||
heavy-load-tests = { max-threads = 4 }
|
||||
io-heavy-sim = { max-threads = 2 }
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "test(/import_with_verification/) | test(/plc_migration/)"
|
||||
@@ -29,6 +42,10 @@ test-group = "serial-env-tests"
|
||||
filter = "binary(ripple_cluster)"
|
||||
test-group = "serial-env-tests"
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "package(tranquil-signal)"
|
||||
test-group = "serial-env-tests"
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "binary(whole_story)"
|
||||
test-group = "heavy-load-tests"
|
||||
@@ -41,6 +58,20 @@ test-group = "heavy-load-tests"
|
||||
filter = "binary(repo_lifecycle)"
|
||||
test-group = "heavy-load-tests"
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "binary(sim_soak)"
|
||||
slow-timeout = { period = "300s", terminate-after = 10 }
|
||||
test-group = "io-heavy-sim"
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "binary(metastore_crash) | binary(sim_cross_store) | binary(sim_gc_concurrent) | binary(sim_reachability) | binary(sim_eventlog) | test(/sim_/)"
|
||||
slow-timeout = { period = "120s", terminate-after = 8 }
|
||||
test-group = "io-heavy-sim"
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = "test(/test_scale_/) | test(/full_backup_and_restore/)"
|
||||
slow-timeout = { period = "120s", terminate-after = 4 }
|
||||
|
||||
[[profile.ci.overrides]]
|
||||
filter = "test(/import_with_verification/) | test(/plc_migration/)"
|
||||
test-group = "serial-env-tests"
|
||||
@@ -53,6 +84,10 @@ test-group = "serial-env-tests"
|
||||
filter = "binary(ripple_cluster)"
|
||||
test-group = "serial-env-tests"
|
||||
|
||||
[[profile.ci.overrides]]
|
||||
filter = "package(tranquil-signal)"
|
||||
test-group = "serial-env-tests"
|
||||
|
||||
[[profile.ci.overrides]]
|
||||
filter = "binary(whole_story)"
|
||||
test-group = "heavy-load-tests"
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kyber_pre_keys",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0295414146e4ddacad638486443fd1036609ab8443390820be8696f708be3812"
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT seq, did, commit_cid\n FROM repo_seq\n WHERE event_type = 'commit'\n AND prev_cid IS NULL\n AND (blocks_cids IS NULL OR array_length(blocks_cids, 1) IS NULL OR array_length(blocks_cids, 1) = 0)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "seq",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "commit_cid",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "033ed5638ef6e30f1648c1acdbc477c173798cb57eb0127c3a582a9633fa5012"
|
||||
}
|
||||
+18
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1 AND seq < $2\n ORDER BY seq ASC",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, block_cids, block_data, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC\n LIMIT $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -50,26 +50,36 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "block_cids",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "block_data",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "handle",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "rev",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -94,8 +104,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d8524ad3f5dc03eb09ed60396a78df5003f804c43ad253d6476523eacdebf811"
|
||||
"hash": "0349b2af4d21105029ecc2bf81ad7be9987ec17ac088730596849d7a45558e57"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_sender_keys\n (address, device_id, identity, distribution_id, record)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (address, device_id, identity, distribution_id) DO UPDATE SET record = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0a3eaf432e99ef22aa6b3b43978740b085f6416a058ea2da6d639ea1cf276eda"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kv",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "166ee5a554e3ddc75496616d9fba75a15cc0c072f493f579b881a4f2300f38c8"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_signed_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1a061c261518573c29aa55c5d8e2181f1728034e723ed0bc836e928b4c69128f"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_kyber_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1aac5ab7dbc29f0d0937767ae174e8b47d236b3cdc973974cec413dc65198d64"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kyber_pre_keys (id, identity, record, is_last_resort)\n VALUES ($1, $2, $3, FALSE)\n ON CONFLICT (id, identity) DO UPDATE SET record = $3, is_last_resort = FALSE",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1d4459863eeddea9ccb5ce5c0eaffd39d56a763728ce5b1f76871265e704e241"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET is_admin = $1 WHERE did = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1e63d287c619a14e5c07d80e8e54193d2964c8b5e6a855256cb80c4d0cd2c6ea"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_sessions WHERE address = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1f464d947e93274cbc974f8a6f31d8a47eb676df857fa83f08ed1937cf8a6d9d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_base_keys_seen",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "222836500a964ee6fd238ddb128691215cd15d2ede9a1a19e7a580e4d6644ce8"
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT t.token, t.expires_at\n FROM plc_operation_tokens t\n JOIN users u ON t.user_id = u.id\n WHERE u.did = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "237c2d912e89b7e0e5baa83503a22f158ea1614b5157f6c9e2aba6017fef6b26"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_identities",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "252d42bb50ef918fe414d036979daba055dbadd3cd7e1bdcce4526867be79930"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kv (key, value) VALUES ($1, $2)\n ON CONFLICT (key) DO UPDATE SET value = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "29c834b7870cdbcdcc2d2c9259a04326e20433237ebe25e33dbd39952ae9f976"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET password_reset_code_expires_at = NOW() - INTERVAL '1 hour' WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "29ef76852bb89af1ab9e679ceaa4abcf8bc8268a348d3be0da9840d1708d20b5"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_base_keys_seen\n (kyber_pre_key_id, signed_pre_key_id, identity, base_key)\n VALUES ($1, $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "31edfe22cdd8e5f62322dad01a65165ec9546eaa92c9e372a5a4fce99d938d22"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kv WHERE key = 'registration'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "374be0bd97ed927b15929e2f86d88913b2fec45ab67e4805fb9f1e39b422ee7f"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3a3eee11be6a9c5fe012d2a634c7ef514fb695ca6a4ba7631d1c5aba87a854a5"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE repo_seq SET blocks_cids = $1 WHERE seq = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3b791fdb8e29043c980963d4d18e1e492c73c39818a8648a7af70555418fb5d1"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_sender_keys",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4011d789a6b619231f4e2b917753f0b4452f21243e1d95d47dbaa91cac019e64"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM signal_kv WHERE key = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4316de453ec1d5ba65a1dbee29599287d42bf87517936b3b61958c0675ce8115"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_sessions",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "43583f8246016d15d9875c4d33855317c41ae07af5676228c0533724590ec6ed"
|
||||
}
|
||||
+18
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC\n LIMIT $2",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, block_cids, block_data, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -50,33 +50,42 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "block_cids",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "block_data",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "handle",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "rev",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
@@ -94,8 +103,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b26bf97a27783eb7fb524a92dda3e68ef8470a9751fcaefe5fd2d7909dead54b"
|
||||
"hash": "4702a94384ce938d3f5384bc2106a48500f8749134e6bfd1c619a9151f5d3772"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO backlinks (uri, path, link_to, repo_id)\n SELECT unnest($1::text[]), unnest($2::text[]), unnest($3::text[]), $4\n ON CONFLICT (uri, path) DO NOTHING\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"TextArray",
|
||||
"TextArray",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "47149c0577ad9e9b9b089820b0c93417769a4a37affe0e3972e324ec27ec532f"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_identities WHERE address = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4bcd896340fc9178e2d97ad572269e8f57561190ec5f94ccfa06ca93f4827e79"
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_sessions (address, device_id, identity, record)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (address, device_id, identity) DO UPDATE SET record = $4",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4d22ba21356bdc84ea549799c77cab8c34ca9a5f0815150254b590fd87509eb8"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT is_last_resort FROM signal_kyber_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "is_last_resort",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "513a0d3b907df1004f2178ebf14fc182f20fc13c5691cbeb7656d428cb6437f9"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kv (key, value) VALUES ('master_key', $1)\n ON CONFLICT (key) DO UPDATE SET value = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "53814b5db4f051c30573d146007ddc487f4c12c726bb970efe17e325536629e4"
|
||||
}
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, rev)\n VALUES ($1, 'sync', $2, $3)\n RETURNING seq\n ",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, rev, block_cids, block_data)\n VALUES ($1, 'sync', $2, $3, $4, $5)\n RETURNING seq\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -13,12 +13,14 @@
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
"Text",
|
||||
"ByteaArray",
|
||||
"ByteaArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "bce9edc597592d2e7d672061883639bcdeff80be85e2ecacb6c75d8c027e6710"
|
||||
"hash": "5996774d097a484f81353d49e4a99b07c30a003377c924add576bfb48ddde29b"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kv (key, value) VALUES ('sender_certificate', $1)\n ON CONFLICT (key) DO UPDATE SET value = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5cd556968671fc8ecf57dd7fd40f65e3a168f17bd389ce2cf7ddee27d810cf1d"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT code FROM oauth_2fa_challenge WHERE request_uri = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "code",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5cee16f49a727d66b5231a8d07d7f4bcb6a1136fbf3e3d249fd33600772ac80f"
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT token, did, expires_at FROM account_deletion_requests WHERE did = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "61f489b4fc42f5b0aaea287cde4415da6f5e96b3a0f36216bdc6dea924b09abd"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_identities (address, identity, record)\n VALUES ($1, $2, $3)\n ON CONFLICT (address, identity) DO UPDATE SET record = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "62dc57cf7ad007a6458df06619fe7e0bf5ffeba195e55793f6bf6d170e053226"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) AS \"count!\" FROM signal_kyber_pre_keys\n WHERE identity = $1 AND is_last_resort = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "63ecf786e52a52d83232f6d4c8d93d5d7065f5dbfd3cd1e259d7990d02da1114"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_kyber_pre_keys\n WHERE identity = $1 AND is_last_resort = TRUE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6a91d97ed4f955ce0936eced6318c630f39ebebc52cee584f1b0dacefec984cd"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kv WHERE key = 'master_key'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6cb39ddd462ae93fe97d930ce64425ed16271adc4ca066c4d51bb742beffc22b"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "6cbfcd552a7b4eeb6a1e525f2f11e8f432405e3606e3af7d31b9eca32bc28607"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kv (key, value) VALUES ('registration', $1)\n ON CONFLICT (key) DO UPDATE SET value = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6f14e2d74ed0c1e0946c62da2a3ddd63344892bdd4022073123604ee17114ce0"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM repo_seq WHERE created_at < $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "746ca87e98f6eaa9c060770c0d1bca6f0d4da60df2dc2db088e57bdc914cf233"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO user_totp (did, secret_encrypted, encryption_version, verified, created_at)\n VALUES ($1, $2, 1, TRUE, NOW())\n ON CONFLICT (did) DO UPDATE SET secret_encrypted = $2, verified = TRUE",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7d3a9f0545943bc6a3a14fcd596aac5cc731c8177d74e504606d7e92c7d0c73f"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_pre_keys (id, identity, record)\n VALUES ($1, $2, $3)\n ON CONFLICT (id, identity) DO UPDATE SET record = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "811af305fef56437151c1cfe27eaccf6312b8234f1217a81a4d36cd5758fd863"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE users SET two_factor_enabled = $1 WHERE did = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "84a1db51a98402323cb86bc19cd2b737f908222ea3426b8bf47d735aff5b6c75"
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_profile_keys (uuid, key) VALUES ($1, $2)\n ON CONFLICT (uuid) DO UPDATE SET key = $2\n RETURNING (xmax = 0) AS \"inserted!\"",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "inserted!",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "894169c5a40acade1000869bdde61d09984e17a686c1b487004d79897ecb4e4f"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM comms_queue WHERE user_id = $1 AND comms_type = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "comms_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"welcome",
|
||||
"email_verification",
|
||||
"password_reset",
|
||||
"email_update",
|
||||
"account_deletion",
|
||||
"admin_email",
|
||||
"plc_operation",
|
||||
"two_factor_code",
|
||||
"channel_verification",
|
||||
"passkey_recovery",
|
||||
"legacy_login_alert",
|
||||
"migration_verification",
|
||||
"channel_verified"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "89b0292d8d022fad8f9cda07b9a7870ca6a7ebe904b2d580956b0816b50bcdb7"
|
||||
}
|
||||
+19
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, block_cids, block_data, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC\n LIMIT $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -50,32 +50,43 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "block_cids",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "block_data",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "handle",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "rev",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
@@ -93,8 +104,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b8101757a50075d20147014e450cb7deb7e58f84310690c7bde61e1834dc5903"
|
||||
"hash": "89c8ce80d8c52b4668c34f63e48b51cacc7e8ff300958a192378f1d72de04f9a"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, blocks_cids, prev_data_cid, rev)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n RETURNING seq\n ",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, block_cids, block_data, prev_data_cid, rev)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n RETURNING seq\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -17,7 +17,8 @@
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"TextArray",
|
||||
"TextArray",
|
||||
"ByteaArray",
|
||||
"ByteaArray",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
@@ -26,5 +27,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "40e848f48afb50f5e4820445aee874a482333e9090ef14ce44db3fd9721cfff0"
|
||||
"hash": "97dd3cfe72b74b4dac686c030a07f6eb56dabb2df72815f3248760b4e3f7950e"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) as \"count!\"\n FROM plc_operation_tokens t\n JOIN users u ON t.user_id = u.id\n WHERE u.did = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "990bf50e60fc5566639c2c12cd968d154d7b0c6863ad69141653135f98fbc998"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(id) FROM signal_kyber_pre_keys\n WHERE identity = $1 AND is_last_resort = TRUE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9da9a790efa51edd6abcb4b0991b307c3ba7f0a929e114a4e0cb2ffb88adfbbb"
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n id, user_id,\n channel as \"channel: CommsChannel\",\n comms_type as \"comms_type: CommsType\",\n status as \"status: CommsStatus\",\n recipient, subject, body, metadata,\n attempts, max_attempts, last_error,\n created_at, updated_at, scheduled_for, processed_at\n FROM comms_queue\n WHERE user_id = $1 AND comms_type = $2\n ORDER BY created_at DESC\n LIMIT $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "channel: CommsChannel",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "comms_channel",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"email",
|
||||
"discord",
|
||||
"telegram",
|
||||
"signal"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "comms_type: CommsType",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "comms_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"welcome",
|
||||
"email_verification",
|
||||
"password_reset",
|
||||
"email_update",
|
||||
"account_deletion",
|
||||
"admin_email",
|
||||
"plc_operation",
|
||||
"two_factor_code",
|
||||
"channel_verification",
|
||||
"passkey_recovery",
|
||||
"legacy_login_alert",
|
||||
"migration_verification",
|
||||
"channel_verified"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "status: CommsStatus",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
"name": "comms_status",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"pending",
|
||||
"processing",
|
||||
"sent",
|
||||
"failed"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "recipient",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "subject",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "body",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "metadata",
|
||||
"type_info": "Jsonb"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "attempts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "max_attempts",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "last_error",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "scheduled_for",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "processed_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "comms_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"welcome",
|
||||
"email_verification",
|
||||
"password_reset",
|
||||
"email_update",
|
||||
"account_deletion",
|
||||
"admin_email",
|
||||
"plc_operation",
|
||||
"two_factor_code",
|
||||
"channel_verification",
|
||||
"passkey_recovery",
|
||||
"legacy_login_alert",
|
||||
"migration_verification",
|
||||
"channel_verified"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "9f3f2b36f11e9446915d3ca29ef81e4ada0c6a6d72764116dac4f99a4e09785e"
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT device_id FROM signal_sessions\n WHERE address = $1 AND device_id != $2 AND identity = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "device_id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9fdcff515573446f0b4340bf94421bb8a425cc65304fa8c3fd153945904136f0"
|
||||
}
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, blocks_cids, rev)\n VALUES ($1, 'commit', $2, $3::TEXT, $4, $5, $6, $7)\n RETURNING seq\n ",
|
||||
"query": "\n INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, block_cids, block_data, rev)\n VALUES ($1, 'commit', $2, $3::TEXT, $4, $5, $6, $7, $8)\n RETURNING seq\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -16,7 +16,8 @@
|
||||
"Text",
|
||||
"Jsonb",
|
||||
"TextArray",
|
||||
"TextArray",
|
||||
"ByteaArray",
|
||||
"ByteaArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
@@ -24,5 +25,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "52df8ae809b990fa76e3ac45f37e352a9fc8f92f911a7b9c9be2fd9e8032b18c"
|
||||
"hash": "a325862f897484bb1a8863fd7a1bc3fd17ab20b6e30498f9900daf80609c413d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_profile_keys",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a403c2982978159b120d4bfa6d6fdca5808b667e54b0765b5ada047ce6dac9d6"
|
||||
}
|
||||
+18
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC\n LIMIT $2",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, block_cids, block_data, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1 AND seq < $2\n ORDER BY seq ASC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -50,26 +50,36 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "block_cids",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "block_data",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "handle",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "rev",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -94,8 +104,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "e7aa1080be9eb3a8ddf1f050c93dc8afd10478f41e22307014784b4ee3740b4a"
|
||||
"hash": "ad784aff9bb5c1f19f90a3b1c3b3694a3e1aa889b0fe3e55cfebb7af8c620256"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE signal_kyber_pre_keys\n SET stale_at = $1\n WHERE identity = $2 AND is_last_resort = FALSE AND stale_at IS NULL",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "adabc98c853ae77678ad6cf1747384706154334cc507eb7542a8b5490f5a1e2a"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_kyber_pre_keys (id, identity, record, is_last_resort)\n VALUES ($1, $2, $3, TRUE)\n ON CONFLICT (id, identity) DO UPDATE SET is_last_resort = TRUE, record = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b157b94e003376d827b92eab1ff55caa773ed5691e2e555ab1d1f23fc5adcf8e"
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) as \"count!\" FROM comms_queue WHERE user_id = $1 AND comms_type = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "comms_type",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"welcome",
|
||||
"email_verification",
|
||||
"password_reset",
|
||||
"email_update",
|
||||
"account_deletion",
|
||||
"admin_email",
|
||||
"plc_operation",
|
||||
"two_factor_code",
|
||||
"channel_verification",
|
||||
"passkey_recovery",
|
||||
"legacy_login_alert",
|
||||
"migration_verification",
|
||||
"channel_verified"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b364a2b202bab17c0cdc5f70d23b13841b4d9063d94cd0b09268c3dc41824fd2"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(id) FROM signal_pre_keys WHERE identity = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b558892051ad793b4ae38a76d072e4ea440092663bd0d4ed9d48f5074788157a"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_sender_keys\n WHERE address = $1 AND device_id = $2 AND identity = $3 AND distribution_id = $4",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b583ea011f17bd703a1dde860d1cc595a18c33dd0e37b575a71fa78f9fddc96f"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_signed_pre_keys",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c08770c1ca1cb553e9067c26dec9a47d40f4cd3e16461cf0edbbb49020695f18"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM signal_kv WHERE key = 'registration'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c130944c8791acb05ebf5f7b5b0f82b8c548ea9d516c0e9310fb46ffd3907977"
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT blocks_cids, commit_cid\n FROM repo_seq\n WHERE did = $1 AND rev > $2\n ORDER BY seq DESC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "commit_cid",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c15d538fece4cd7db0056f9e0374a2290e26f5766881fd9dbf4f05788845aac0"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH total AS (\n SELECT COUNT(*) AS cnt FROM signal_kyber_pre_keys\n WHERE identity = $1 AND is_last_resort = FALSE\n )\n DELETE FROM signal_kyber_pre_keys\n WHERE identity = $1 AND is_last_resort = FALSE\n AND stale_at IS NOT NULL AND stale_at < $2\n AND (SELECT cnt FROM total) > $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Timestamptz",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c30d8f2bb8a20154b565a93108d3871289a752fb8d0de35a2164619da2c03aa0"
|
||||
}
|
||||
+18
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq = $1",
|
||||
"query": "SELECT seq, did, created_at, event_type as \"event_type: RepoEventType\", commit_cid, prev_cid, prev_data_cid,\n ops, blobs, block_cids, block_data, blocks_cids, handle, active, status, rev\n FROM repo_seq\n WHERE seq > $1\n ORDER BY seq ASC",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -50,26 +50,36 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "block_cids",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "block_data",
|
||||
"type_info": "ByteaArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "blocks_cids",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 12,
|
||||
"name": "handle",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 13,
|
||||
"name": "active",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 14,
|
||||
"name": "status",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 15,
|
||||
"name": "rev",
|
||||
"type_info": "Text"
|
||||
}
|
||||
@@ -93,8 +103,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "0d32a592a97ad47c65aa37cf0d45417f2966fcbd688be7434626ae5f6971fa1f"
|
||||
"hash": "c47746c256fd8c9d386551f3bd270e9a96fecdb713642d07d60fb4aeb02c8426"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) AS \"count!\" FROM signal_signed_pre_keys WHERE identity = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c5310fca925ec4188e761f218e98c11182e5757c40a70a2aa50af0f2896d98e7"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT key FROM signal_profile_keys WHERE uuid = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "key",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c67709e3fb975148fa86d9fd5ed5e1ef805b2f57a2ce5d955629d598e1ab2b80"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kyber_pre_keys\n WHERE id = $1 AND identity = $2 AND is_last_resort = FALSE",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c749d53e4e38dd53aa2ac59ba2b15b641ace056515c34ec977bf47d74dfd7072"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM signal_kv WHERE key = 'master_key' LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c86da12578b2e53e0bff374121b22244c4663820cbb6edfd8f0b8c9aa583cad2"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM backlinks WHERE uri = ANY($1::text[])",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cffe4c37fe949fbdc3d5cd83ccec5655aae248a0a69dc260d1da9cf1d9ed2c49"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE account_deletion_requests SET expires_at = NOW() - INTERVAL '1 hour' WHERE token = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d529d6dc9858c1da360f0417e94a3b40041b043bae57e95002d4bf5df46a4ab4"
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM signal_kv WHERE key = 'sender_certificate' LIMIT 1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "dded08cb4fd0fc9bca55b3cf0c8d22f4e279bc8aedb72000f4c4bc093f61c750"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO signal_signed_pre_keys (id, identity, record)\n VALUES ($1, $2, $3)\n ON CONFLICT (id, identity) DO UPDATE SET record = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text",
|
||||
"Bytea"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "def40c1cfac196a066d81fb0efa0fa7bdf5f242bd13c8b70e47a512c5e9df5b1"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_pre_keys",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e2ed0b4bfe13a99be85311fcc6e3c2dbdb789b9a44c0122f736ead48f4713a5b"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_kyber_pre_keys WHERE id = $1 AND identity = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "eb837730e87ff990c4441d7bdab001d7542d1803702c634a6099365a9740e45a"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(id) FROM signal_kyber_pre_keys WHERE identity = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "eea566212ffce269028a8623a12b553c048ef1ea433aa5567e72b2b639f69b0d"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM signal_sessions WHERE address = $1 AND device_id = $2 AND identity = $3",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "efcf513bba65213ed4b71259a7016ffd7b1b8ff85206416a86beba19323a3be7"
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT record FROM signal_sessions\n WHERE address = $1 AND device_id = $2 AND identity = $3",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "record",
|
||||
"type_info": "Bytea"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f332e618e59a298573c4b6b6c7a12252b461a6676f2d797c5dae5a0baee016a7"
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT id, did, public_key_did_key, private_key_bytes, expires_at, used_at\n FROM reserved_signing_keys WHERE public_key_did_key = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "did",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "public_key_did_key",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "private_key_bytes",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "used_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f3b07f153284b6dd1f22c098af8628d85dcdf20dd6273443ff98d02b6f5ecbf1"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT MAX(id) FROM signal_signed_pre_keys WHERE identity = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "max",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "f41749b35c081fc9d0881c7e2fc988e7c4d164ab5996f18e9f3a29fe4d183db9"
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT password_reset_code, password_reset_code_expires_at FROM users WHERE email = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "password_reset_code",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "password_reset_code_expires_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f7af28963099aec12cf1d4f8a9a03699bb3a90f39bc9c4c0f738a37827e8f382"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
when:
|
||||
- event: []
|
||||
branch: []
|
||||
|
||||
engine: nixery
|
||||
|
||||
dependencies:
|
||||
nixpkgs:
|
||||
- podman
|
||||
|
||||
steps:
|
||||
- name: Create podman config
|
||||
command: |
|
||||
mkdir -p ~/.config/containers
|
||||
echo "unqualified-search-registries = [\"docker.io\"]" >> ~/.config/containers/registries.conf
|
||||
|
||||
- name: Build image
|
||||
command: |
|
||||
podman build . -t tranquil-pds:latest -t "tranquil-pds:$TANGLED_COMMIT_SHA"
|
||||
|
||||
- name: Publish image
|
||||
command: |
|
||||
podman push --creds "$ATCR_USERNAME:$ATCR_PASSWORD" tranquil-pds:latest "atcr.io/tranquil.farm/tranquil-pds:latest"
|
||||
podman push --creds "$ATCR_USERNAME:$ATCR_PASSWORD" "tranquil-pds:$TANGLED_COMMIT_SHA" "atcr.io/tranquil.farm/tranquil-pds:$TANGLED_COMMIT_SHA"
|
||||
Generated
+2301
-585
File diff suppressed because it is too large
Load Diff
+16
-1
@@ -21,10 +21,12 @@ members = [
|
||||
"crates/tranquil-oauth-server",
|
||||
"crates/tranquil-api",
|
||||
"crates/tranquil-lexicon",
|
||||
"crates/tranquil-signal",
|
||||
"crates/tranquil-store",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.6"
|
||||
version = "0.5.0"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
||||
@@ -49,6 +51,10 @@ tranquil-server = { path = "crates/tranquil-server" }
|
||||
tranquil-sync = { path = "crates/tranquil-sync" }
|
||||
tranquil-oauth-server = { path = "crates/tranquil-oauth-server" }
|
||||
tranquil-api = { path = "crates/tranquil-api" }
|
||||
tranquil-signal = { path = "crates/tranquil-signal", features = ["fjall-store"] }
|
||||
tranquil-store = { path = "crates/tranquil-store" }
|
||||
|
||||
presage = { git = "https://github.com/whisperfish/presage", rev = "fe3ed54c4844ae51c3a9fa49cf80a7816a31a425", default-features = false }
|
||||
|
||||
unicode-segmentation = "1"
|
||||
|
||||
@@ -81,6 +87,7 @@ hkdf = "0.12"
|
||||
hmac = "0.12"
|
||||
http = "1.4"
|
||||
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
|
||||
qrcodegen = "1.8"
|
||||
infer = "0.19"
|
||||
ipld-core = "0.4"
|
||||
iroh-car = "0.5"
|
||||
@@ -129,7 +136,15 @@ testcontainers = "0.26"
|
||||
testcontainers-modules = { version = "0.14", features = ["postgres"] }
|
||||
wiremock = "0.6"
|
||||
|
||||
[patch.crates-io]
|
||||
curve25519-dalek = { git = "https://github.com/signalapp/curve25519-dalek", tag = "signal-curve25519-4.1.3" }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.bench]
|
||||
debug = 1
|
||||
strip = false
|
||||
|
||||
+5
-13
@@ -4,7 +4,7 @@ COPY frontend/ ./
|
||||
RUN deno task build
|
||||
|
||||
FROM rust:1.92-alpine AS builder
|
||||
RUN apk add --no-cache ca-certificates musl-dev pkgconfig openssl-dev openssl-libs-static mold clang
|
||||
RUN apk add --no-cache ca-certificates musl-dev pkgconfig openssl-dev openssl-libs-static mold clang protoc
|
||||
ENV RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=mold"
|
||||
WORKDIR /app
|
||||
ARG SLIM="false"
|
||||
@@ -29,6 +29,8 @@ COPY crates/tranquil-pds ./crates/tranquil-pds
|
||||
COPY crates/tranquil-sync ./crates/tranquil-sync
|
||||
COPY crates/tranquil-api ./crates/tranquil-api
|
||||
COPY crates/tranquil-oauth-server ./crates/tranquil-oauth-server
|
||||
COPY crates/tranquil-store ./crates/tranquil-store
|
||||
COPY crates/tranquil-signal ./crates/tranquil-signal
|
||||
COPY crates/tranquil-server ./crates/tranquil-server
|
||||
COPY migrations ./crates/tranquil-pds/migrations
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
@@ -40,23 +42,13 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
fi && \
|
||||
cp target/release/tranquil-server /tmp/tranquil-pds
|
||||
|
||||
FROM alpine:3.23 AS signal-cli
|
||||
RUN apk add --no-cache curl tar
|
||||
ARG SIGNAL_CLI_VERSION=0.13.24
|
||||
RUN curl -fsSL "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux-native.tar.gz" \
|
||||
| tar xz -C /usr/local/bin
|
||||
|
||||
FROM debian:trixie-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends msmtp ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
FROM alpine:3.23
|
||||
RUN apk add --no-cache msmtp ca-certificates \
|
||||
&& ln -sf /usr/bin/msmtp /usr/sbin/sendmail
|
||||
COPY --from=signal-cli /usr/local/bin/signal-cli /usr/local/bin/signal-cli
|
||||
VOLUME /var/lib/signal-cli
|
||||
COPY --from=builder /tmp/tranquil-pds /usr/local/bin/tranquil-pds
|
||||
COPY --from=frontend /app/dist /var/lib/tranquil-pds/frontend
|
||||
COPY migrations /app/migrations
|
||||
WORKDIR /app
|
||||
ENV SIGNAL_CLI_CONFIG=/var/lib/signal-cli
|
||||
ENV SERVER_HOST=0.0.0.0
|
||||
ENV SERVER_PORT=3000
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
tranquil-store: embedded storage engine for Tranquil PDS
|
||||
RFC draft, 2026-03-22
|
||||
By Lewis!
|
||||
|
||||
-- TLDR --
|
||||
|
||||
Add an embedded storage engine as an alternative to postgres (and leapfrog SQLite-per-actor)
|
||||
that treats Tranquil's 3 types of storage workloads as 3 separate problems:
|
||||
|
||||
- BlockStore: bitcask-esque append log for immutable CID-keyed blocks [4]
|
||||
- MetaStore: Fjall LSM keyspaces for mutable metadata [5]
|
||||
- EventLog: segmented append log for the firehose
|
||||
|
||||
Group commit across users, content dedup, sub-ms firehose delivery.
|
||||
We will use deterministic simulation testing [18][19].
|
||||
Postgres will of course stay as the existing alternative backend.
|
||||
|
||||
-- Intro --
|
||||
|
||||
The ref PDS hits structural limits around 300k accounts [2].
|
||||
SQLite-per-actor means no cross-user write batching.
|
||||
|
||||
tranquil-store is an embedded rust library. It lives in-process, no external deps.
|
||||
Postgres remains supported; we plan to enable a storage transition path
|
||||
such that users can seamlessly snapshot-n-switch between the backends.
|
||||
|
||||
The BlockStore is a bitcask-style append log [4] with a Fjall key index [5] that
|
||||
maps each CID to a (file, offset, length) tuple. We use key-value separation
|
||||
as per WiscKey [6]. Because blocks are immutable and keyed by CID, the value log
|
||||
never needs compaction. An LRU hot tier keeps frequently-accessed blocks in
|
||||
mem, and hint files allow fast index reconstruction on restart [4].
|
||||
|
||||
The main throughput enabler is group commit [7]. The ref PDS fsyncs once per user
|
||||
per mutation [1], but BlockStore batches all concurrent commits into a single
|
||||
write-and-sync cycle.
|
||||
|
||||
Content dedup occurs naturally: identical MST subtrees across users share
|
||||
one CID-keyed block instead of N copies [3].
|
||||
|
||||
MetaStore uses Fjall [5] keyspaces for all mutable data. We chose Fjall over
|
||||
redb and LMDB because both of those are single-writer [8][9]. Each keyspace
|
||||
compacts independently.
|
||||
|
||||
For cross-store atomicity we use an intent log. Each mutation writes a single
|
||||
intent record containing the BlockStore refcount updates, MetaStore changes,
|
||||
and the serialized EventLog payload, fsynced via the group commit. After fsync,
|
||||
the changes are applied to MetaStore and the event is appended to the EventLog,
|
||||
then the intent is marked committed. Recovery replays any incomplete intents,
|
||||
re-applying both metadata changes and event appends. This gives us crash-atomic
|
||||
mutations across all three stores without full MVCC, since mutations
|
||||
are already serialized per-user [3].
|
||||
|
||||
EventLog stores the firehose as segmented append-only files. Live subscribers
|
||||
receive events via tokio broadcast, and consumers that are catching up will
|
||||
read from mmap'ed segments [10]. Each event receives a monotonic u64 sequence number.
|
||||
Segment headers store the base sequence number; a per-segment index maps sequence
|
||||
ranges to byte offsets. This decouples consumer cursors from physical layout,
|
||||
allowing transparent addition of per-segment zstd compression per the loom-v2
|
||||
spec [11] without invalidating checkpoints. Retention is just deleting old
|
||||
segments! :P
|
||||
|
||||
GC uses refcounted key index entries. GC is epoch-gated by the group commit
|
||||
cycle: a block is only eligible for collection if its refcount reached zero in
|
||||
a prior completed commit cycle. This prevents races between concurrent dedup
|
||||
(which skips the block write but increments the refcount in the same batch) and
|
||||
collection. Blocks past the epoch gate are collected by rewriting any data
|
||||
files that fall below a liveness threshold.
|
||||
|
||||
For serialization we use postcard on disk and rkyv [12] for in-mem caches
|
||||
only. All data files carry a version tag.
|
||||
|
||||
Memory is divided into fixed slices from a configurable total budget: Fjall
|
||||
block cache, BlockStore hot tier, and CID index each receive a configured
|
||||
percentage. Actual usage per component is exposed as metrics. The EventLog's
|
||||
mmap pages live in the OS page cache and are excluded from the budget.
|
||||
|
||||
Backup acquires the group commit lock, which quiesces all writes at the next
|
||||
commit boundary. Under the lock, the system notes the EventLog position, the
|
||||
BlockStore file list, and takes a Fjall snapshot, then releases the lock.
|
||||
Sealed data files and segments are immutable and can be copied without
|
||||
co-ordination after the snapshot. The quiesce window is bounded by one commit
|
||||
cycle. Point-in-time recovery replays the EventLog against a prior snapshot.
|
||||
For continuous replication, a background process tails the EventLog and copies
|
||||
sealed files to remote storage.
|
||||
|
||||
-- Runtime --
|
||||
|
||||
The storage core runs on tokio. It is synchronous internally, accessed through
|
||||
dedicated handler threads that communicate via async channels [13]. Requests
|
||||
are dispatched by hashing the DID, which gives us per-user write serialization
|
||||
without locks. Global operations use round-robin. All disk IO goes through
|
||||
pread/pwrite directly [13].
|
||||
|
||||
We rejected io_uring for three reasons: it creates orphan kernel operations
|
||||
when futures are cancelled [22], it is blocked by default in both Docker [16]
|
||||
and Podman [17] seccomp profiles, and it accounts for 60% of Google's kernel
|
||||
vulnerability rewards [15].
|
||||
|
||||
We also rejected thread-per-core runtimes (glommio, etc.) because they are
|
||||
incompatible with the tokio ecosystem. DID-sharded handler threads give us
|
||||
the same shared-nothing property without a runtime split.
|
||||
|
||||
-- Testing --
|
||||
|
||||
We use deterministic simulation testing, following FoundationDB [18] and
|
||||
TigerBeetle's VOPR [19]. All IO sits behind a StorageIO trait, and tests use an
|
||||
in-memory implementation that injects faults: partial writes, bit flips, sync
|
||||
failures, and misdirected writes. A single seed controls the entire fault
|
||||
schedule, so any failure reproduces exactly [20][21].
|
||||
|
||||
-- Why these choices --
|
||||
|
||||
Bitcask for blocks:
|
||||
Key-val separation [6] using Bitcask [4] for immutable CID blocks:
|
||||
O(1) writes, O(1) reads, zero write amplification, & no compaction!
|
||||
|
||||
Fjall for metadata:
|
||||
Only pure-Rust embedded engine with concurrent writers [5].
|
||||
Otherwise we'd write our own.
|
||||
|
||||
Segmented log for events:
|
||||
Write once -> scan forward -> delete by age.
|
||||
Quite straightforward!
|
||||
|
||||
Postcard on disk:
|
||||
rkyv is apparently faster [12] but couples on-disk format to library version.
|
||||
|
||||
Tokio & handler threads:
|
||||
spawn_blocking & pread matches io_uring without security/compat costs [13][14][15][16].
|
||||
|
||||
Deterministic simulation:
|
||||
Catches bug classes conventional testing can't reach [18][19].
|
||||
StorageIO trait is needed anyway; but being harness-first is a one-time cost [20][21].
|
||||
|
||||
-- References --
|
||||
|
||||
[1] Bluesky PDS SQLite migration. github.com/bluesky-social/atproto/pull/1705
|
||||
[2] G. Orosz. Building Bluesky: a Distributed Social Network. Pragmatic Engineer, April 2024.
|
||||
newsletter.pragmaticengineer.com/p/bluesky
|
||||
K. Suder. Introduction to AT Protocol. August 2025. mackuba.eu/2025/08/20/introduction-to-atproto
|
||||
Bluesky PDS "Going to Production" guide. atproto.com/guides/going-to-production
|
||||
[3] AT Protocol repository spec. atproto.com/specs/repository
|
||||
[4] Bitcask: A Log-Structured Hash Table for Fast KV Data. Riak, 2010. riak.com/assets/bitcask-intro.pdf
|
||||
[5] Fjall: LSM-based embedded storage engine. github.com/fjall-rs/fjall
|
||||
[6] Lu et al. WiscKey: Separating Keys from Values in SSD-Conscious Storage. USENIX FAST 2016.
|
||||
usenix.org/conference/fast16/technical-sessions/presentation/lu
|
||||
[7] Phil Eaton. A Write-Ahead Log Is Not a Universal Part of Durability. July 2024.
|
||||
notes.eatonphil.com/2024-07-01-a-write-ahead-log-is-not-a-universal-part-of-durability.html
|
||||
[8] redb design document. github.com/cberner/redb/blob/master/docs/design.md
|
||||
[9] LMDB source repository. github.com/LMDB/lmdb
|
||||
[10] Crotty et al. Are You Sure You Want to Use MMAP in Your DBMS? CIDR 2022.
|
||||
cs.brown.edu/people/acrotty/pubs/p13-crotty.pdf
|
||||
[11] ybzeek. RFC: com.atproto.sync.getZstdStream (zstd-compressed relay streams).
|
||||
github.com/bluesky-social/atproto/discussions/4582
|
||||
[12] rkyv: zero-copy deserialization framework for Rust. rkyv.org
|
||||
[13] Tonbo. Exploring Better Async Rust Disk IO. tonbo.io/blog/exploring-better-async-rust-disk-io
|
||||
[14] Iroh. Async Rust Challenges in Iroh. iroh.computer/blog/async-rust-challenges-in-iroh
|
||||
[15] Google restricting io_uring. phoronix.com/news/Google-Restricting-IO_uring
|
||||
[16] Docker 4.42.0 and io_uring. forums.docker.com/t/4-42-0-and-io-uring/148620
|
||||
[17] Podman io_uring discussion. github.com/containers/podman/discussions/27772
|
||||
[18] FoundationDB simulation testing. apple.github.io/foundationdb/testing.html
|
||||
[19] TigerBeetle VOPR. tigerbeetle.com/blog/2023-07-06-simulation-testing-for-liveness
|
||||
[20] DST in Rust (S2). s2.dev/blog/dst
|
||||
[21] Phil Eaton. What's the big deal about Deterministic Simulation Testing? August 2024.
|
||||
notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html
|
||||
[22] Tonbo. Async Rust Is Not Safe with io_uring. tonbo.io/blog/async-rust-is-not-safe-with-io-uring
|
||||
|
||||
Thank you for reading! Let's do some great work together.
|
||||
|
||||
@@ -12,12 +12,15 @@ tranquil-db = { workspace = true }
|
||||
tranquil-db-traits = { workspace = true }
|
||||
tranquil-lexicon = { workspace = true, features = ["resolve"] }
|
||||
tranquil-scopes = { workspace = true }
|
||||
tranquil-signal = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
backon = { workspace = true }
|
||||
base32 = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
image = { workspace = true }
|
||||
qrcodegen = { workspace = true }
|
||||
bcrypt = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
|
||||
@@ -34,13 +34,13 @@ pub struct GetPreferencesOutput {
|
||||
}
|
||||
pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Permissive>) -> Response {
|
||||
let has_full_access = auth.permissions().has_full_access();
|
||||
let user_id: uuid::Uuid = match state.user_repo.get_id_by_did(&auth.did).await {
|
||||
let user_id: uuid::Uuid = match state.repos.user.get_id_by_did(&auth.did).await {
|
||||
Ok(Some(id)) => id,
|
||||
_ => {
|
||||
return ApiError::InternalError(Some("User not found".into())).into_response();
|
||||
}
|
||||
};
|
||||
let prefs = match state.infra_repo.get_account_preferences(user_id).await {
|
||||
let prefs = match state.repos.infra.get_account_preferences(user_id).await {
|
||||
Ok(rows) => rows,
|
||||
Err(_) => {
|
||||
return ApiError::InternalError(Some("Failed to fetch preferences".into()))
|
||||
@@ -69,7 +69,7 @@ pub async fn get_preferences(State(state): State<AppState>, auth: Auth<Permissiv
|
||||
if let Some(age) = personal_details_pref
|
||||
.as_ref()
|
||||
.and_then(|pref| pref.get("birthDate"))
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(Value::as_str)
|
||||
.and_then(get_age_from_datestring)
|
||||
{
|
||||
let declared_age_pref = serde_json::json!({
|
||||
@@ -93,7 +93,7 @@ pub async fn put_preferences(
|
||||
Json(input): Json<PutPreferencesInput>,
|
||||
) -> Response {
|
||||
let has_full_access = auth.permissions().has_full_access();
|
||||
let user_id: uuid::Uuid = match state.user_repo.get_id_by_did(&auth.did).await {
|
||||
let user_id: uuid::Uuid = match state.repos.user.get_id_by_did(&auth.did).await {
|
||||
Ok(Some(id)) => id,
|
||||
_ => {
|
||||
return ApiError::InternalError(Some("User not found".into())).into_response();
|
||||
@@ -122,7 +122,7 @@ pub async fn put_preferences(
|
||||
if pref_str.len() > MAX_PREFERENCE_SIZE {
|
||||
return PrefValidation::TooLarge(pref_str.len());
|
||||
}
|
||||
let pref_type = match pref.get("$type").and_then(|t| t.as_str()) {
|
||||
let pref_type = match pref.get("$type").and_then(Value::as_str) {
|
||||
Some(t) => t,
|
||||
None => return PrefValidation::MissingType,
|
||||
};
|
||||
@@ -179,7 +179,7 @@ pub async fn put_preferences(
|
||||
.preferences
|
||||
.into_iter()
|
||||
.filter_map(|pref| {
|
||||
let pref_type = pref.get("$type").and_then(|t| t.as_str())?;
|
||||
let pref_type = pref.get("$type").and_then(Value::as_str)?;
|
||||
if pref_type == DECLARED_AGE_PREF {
|
||||
return None;
|
||||
}
|
||||
@@ -188,7 +188,8 @@ pub async fn put_preferences(
|
||||
.collect();
|
||||
|
||||
if state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.replace_namespace_preferences(user_id, APP_BSKY_NAMESPACE, prefs_to_save)
|
||||
.await
|
||||
.is_err()
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum::{Json, extract::State};
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
use tranquil_pds::api::EmptyResponse;
|
||||
@@ -20,10 +16,11 @@ pub async fn delete_account(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<DeleteAccountInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let did = &input.did;
|
||||
let (user_id, handle) = state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.get_id_and_handle_by_did(did)
|
||||
.await
|
||||
.log_db_err("in delete_account")?
|
||||
@@ -31,7 +28,8 @@ pub async fn delete_account(
|
||||
.map(|row| (row.id, row.handle))?;
|
||||
|
||||
state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.admin_delete_account_complete(user_id, did)
|
||||
.await
|
||||
.log_db_err("deleting account")?;
|
||||
@@ -52,5 +50,5 @@ pub async fn delete_account(
|
||||
.cache
|
||||
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
|
||||
.await;
|
||||
Ok(EmptyResponse::ok().into_response())
|
||||
Ok(Json(EmptyResponse {}))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum::{Json, extract::State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use tranquil_pds::api::error::{ApiError, DbResultExt};
|
||||
@@ -30,13 +25,14 @@ pub async fn send_email(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<SendEmailInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<SendEmailOutput>, ApiError> {
|
||||
let content = input.content.trim();
|
||||
if content.is_empty() {
|
||||
return Err(ApiError::InvalidRequest("content is required".into()));
|
||||
}
|
||||
let user = state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.get_by_did(&input.recipient_did)
|
||||
.await
|
||||
.log_db_err("in send_email")?
|
||||
@@ -50,7 +46,8 @@ pub async fn send_email(
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("Message from {}", hostname));
|
||||
let result = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.enqueue_comms(
|
||||
Some(user_id),
|
||||
tranquil_db_traits::CommsChannel::Email,
|
||||
@@ -68,11 +65,11 @@ pub async fn send_email(
|
||||
handle,
|
||||
input.recipient_did
|
||||
);
|
||||
Ok((StatusCode::OK, Json(SendEmailOutput { sent: true })).into_response())
|
||||
Ok(Json(SendEmailOutput { sent: true }))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to enqueue admin email: {:?}", e);
|
||||
Ok((StatusCode::OK, Json(SendEmailOutput { sent: false })).into_response())
|
||||
Ok(Json(SendEmailOutput { sent: false }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::common;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, RawQuery, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
@@ -68,9 +67,10 @@ pub async fn get_account_info(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Query(params): Query<GetAccountInfoParams>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<AccountInfo>, ApiError> {
|
||||
let account = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_admin_account_info_by_did(¶ms.did)
|
||||
.await
|
||||
.log_db_err("in get_account_info")?
|
||||
@@ -79,31 +79,28 @@ pub async fn get_account_info(
|
||||
let invited_by = get_invited_by(&state, account.id).await;
|
||||
let invites = get_invites_for_user(&state, account.id).await;
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(AccountInfo {
|
||||
did: account.did,
|
||||
handle: account.handle,
|
||||
email: account.email,
|
||||
indexed_at: account.created_at.to_rfc3339(),
|
||||
invite_note: None,
|
||||
invites_disabled: account.invites_disabled,
|
||||
email_confirmed_at: if account.email_verified {
|
||||
Some(account.created_at.to_rfc3339())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
deactivated_at: account.deactivated_at.map(|dt| dt.to_rfc3339()),
|
||||
invited_by,
|
||||
invites,
|
||||
}),
|
||||
)
|
||||
.into_response())
|
||||
Ok(Json(AccountInfo {
|
||||
did: account.did,
|
||||
handle: account.handle,
|
||||
email: account.email,
|
||||
indexed_at: account.created_at.to_rfc3339(),
|
||||
invite_note: None,
|
||||
invites_disabled: account.invites_disabled,
|
||||
email_confirmed_at: if account.email_verified {
|
||||
Some(account.created_at.to_rfc3339())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
deactivated_at: account.deactivated_at.map(|dt| dt.to_rfc3339()),
|
||||
invited_by,
|
||||
invites,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_invited_by(state: &AppState, user_id: uuid::Uuid) -> Option<InviteCodeInfo> {
|
||||
let code = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_used_by_user(user_id)
|
||||
.await
|
||||
.ok()??;
|
||||
@@ -116,7 +113,8 @@ async fn get_invites_for_user(
|
||||
user_id: uuid::Uuid,
|
||||
) -> Option<Vec<InviteCodeInfo>> {
|
||||
let invite_codes = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invites_created_by_user(user_id)
|
||||
.await
|
||||
.ok()?;
|
||||
@@ -128,21 +126,16 @@ async fn get_invites_for_user(
|
||||
let code_strings: Vec<String> = invite_codes.iter().map(|ic| ic.code.clone()).collect();
|
||||
|
||||
let uses = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_uses_batch(&code_strings)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let uses_by_code: HashMap<String, Vec<InviteCodeUseInfo>> =
|
||||
uses.into_iter().fold(HashMap::new(), |mut acc, u| {
|
||||
acc.entry(u.code.clone())
|
||||
.or_default()
|
||||
.push(InviteCodeUseInfo {
|
||||
used_by: u.used_by_did,
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
});
|
||||
acc
|
||||
});
|
||||
let uses_by_code = common::group_invite_uses_by_code(uses, |u| InviteCodeUseInfo {
|
||||
used_by: u.used_by_did,
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
});
|
||||
|
||||
let invites: Vec<InviteCodeInfo> = invite_codes
|
||||
.into_iter()
|
||||
@@ -165,10 +158,11 @@ async fn get_invites_for_user(
|
||||
}
|
||||
|
||||
async fn get_invite_code_info(state: &AppState, code: &str) -> Option<InviteCodeInfo> {
|
||||
let info = state.infra_repo.get_invite_code_info(code).await.ok()??;
|
||||
let info = state.repos.infra.get_invite_code_info(code).await.ok()??;
|
||||
|
||||
let uses = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_uses(code)
|
||||
.await
|
||||
.ok()
|
||||
@@ -195,7 +189,7 @@ pub async fn get_account_infos(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
RawQuery(raw_query): RawQuery,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<GetAccountInfosOutput>, ApiError> {
|
||||
let dids: Vec<String> =
|
||||
tranquil_pds::util::parse_repeated_query_param(raw_query.as_deref(), "dids")
|
||||
.into_iter()
|
||||
@@ -208,7 +202,8 @@ pub async fn get_account_infos(
|
||||
|
||||
let dids_typed: Vec<Did> = dids.iter().filter_map(|d| d.parse().ok()).collect();
|
||||
let accounts = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_admin_account_infos_by_dids(&dids_typed)
|
||||
.await
|
||||
.log_db_err("fetching account infos")?;
|
||||
@@ -216,7 +211,8 @@ pub async fn get_account_infos(
|
||||
let user_ids: Vec<uuid::Uuid> = accounts.iter().map(|u| u.id).collect();
|
||||
|
||||
let all_invite_codes = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_codes_by_users(&user_ids)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -228,7 +224,8 @@ pub async fn get_account_infos(
|
||||
|
||||
let all_invite_uses = if !all_codes.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_uses_batch(&all_codes)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
@@ -237,25 +234,18 @@ pub async fn get_account_infos(
|
||||
};
|
||||
|
||||
let invited_by_map: HashMap<uuid::Uuid, String> = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_uses_by_users(&user_ids)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let uses_by_code: HashMap<String, Vec<InviteCodeUseInfo>> =
|
||||
all_invite_uses
|
||||
.into_iter()
|
||||
.fold(HashMap::new(), |mut acc, u| {
|
||||
acc.entry(u.code.clone())
|
||||
.or_default()
|
||||
.push(InviteCodeUseInfo {
|
||||
used_by: u.used_by_did,
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
});
|
||||
acc
|
||||
});
|
||||
let uses_by_code = common::group_invite_uses_by_code(all_invite_uses, |u| InviteCodeUseInfo {
|
||||
used_by: u.used_by_did,
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
});
|
||||
|
||||
let (codes_by_user, code_info_map): (
|
||||
HashMap<uuid::Uuid, Vec<InviteCodeInfo>>,
|
||||
@@ -304,5 +294,5 @@ pub async fn get_account_infos(
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((StatusCode::OK, Json(GetAccountInfosOutput { infos })).into_response())
|
||||
Ok(Json(GetAccountInfosOutput { infos }))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub use info::{
|
||||
};
|
||||
pub use search::{SearchAccountsOutput, SearchAccountsParams, search_accounts};
|
||||
pub use update::{
|
||||
UpdateAccountEmailInput, UpdateAccountHandleInput, UpdateAccountPasswordInput,
|
||||
update_account_email, update_account_handle, update_account_password,
|
||||
SetAdminStatusInput, UpdateAccountEmailInput, UpdateAccountHandleInput,
|
||||
UpdateAccountPasswordInput, set_admin_status, update_account_email, update_account_handle,
|
||||
update_account_password,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tranquil_pds::api::error::{ApiError, DbResultExt};
|
||||
@@ -51,17 +49,16 @@ pub async fn search_accounts(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Query(params): Query<SearchAccountsParams>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<SearchAccountsOutput>, ApiError> {
|
||||
let limit = params.limit.clamp(1, 100);
|
||||
let email_filter = params.email.as_deref().map(|e| format!("%{}%", e));
|
||||
let handle_filter = params.handle.as_deref().map(|h| format!("%{}%", h));
|
||||
let cursor_did: Option<Did> = params.cursor.as_ref().and_then(|c| c.parse().ok());
|
||||
let rows = state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.search_accounts(
|
||||
cursor_did.as_ref(),
|
||||
email_filter.as_deref(),
|
||||
handle_filter.as_deref(),
|
||||
params.email.as_deref(),
|
||||
params.handle.as_deref(),
|
||||
limit + 1,
|
||||
)
|
||||
.await
|
||||
@@ -91,12 +88,8 @@ pub async fn search_accounts(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(SearchAccountsOutput {
|
||||
cursor: next_cursor,
|
||||
accounts,
|
||||
}),
|
||||
)
|
||||
.into_response())
|
||||
Ok(Json(SearchAccountsOutput {
|
||||
cursor: next_cursor,
|
||||
accounts,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum::{Json, extract::State};
|
||||
use serde::Deserialize;
|
||||
use tracing::{error, warn};
|
||||
use tracing::{error, info, warn};
|
||||
use tranquil_pds::api::EmptyResponse;
|
||||
use tranquil_pds::api::error::ApiError;
|
||||
use tranquil_pds::auth::{Admin, Auth};
|
||||
@@ -21,7 +17,7 @@ pub async fn update_account_email(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<UpdateAccountEmailInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let account = input.account.trim();
|
||||
let email = input.email.trim();
|
||||
if account.is_empty() || email.is_empty() {
|
||||
@@ -34,12 +30,13 @@ pub async fn update_account_email(
|
||||
.map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?;
|
||||
|
||||
match state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.admin_update_email(&account_did, email)
|
||||
.await
|
||||
{
|
||||
Ok(0) => Err(ApiError::AccountNotFound),
|
||||
Ok(_) => Ok(EmptyResponse::ok().into_response()),
|
||||
Ok(_) => Ok(Json(EmptyResponse {})),
|
||||
Err(e) => {
|
||||
error!("DB error updating email: {:?}", e);
|
||||
Err(ApiError::InternalError(None))
|
||||
@@ -57,7 +54,7 @@ pub async fn update_account_handle(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<UpdateAccountHandleInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let did = &input.did;
|
||||
let input_handle = input.handle.trim();
|
||||
if input_handle.is_empty() {
|
||||
@@ -75,9 +72,10 @@ pub async fn update_account_handle(
|
||||
} else {
|
||||
input_handle.to_string()
|
||||
};
|
||||
let old_handle = state.user_repo.get_handle_by_did(did).await.ok().flatten();
|
||||
let old_handle = state.repos.user.get_handle_by_did(did).await.ok().flatten();
|
||||
let user_id = state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.get_id_by_did(did)
|
||||
.await
|
||||
.ok()
|
||||
@@ -85,14 +83,16 @@ pub async fn update_account_handle(
|
||||
.ok_or(ApiError::AccountNotFound)?;
|
||||
let handle_for_check: Handle = handle.parse().map_err(|_| ApiError::InvalidHandle(None))?;
|
||||
if let Ok(true) = state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.check_handle_exists(&handle_for_check, user_id)
|
||||
.await
|
||||
{
|
||||
return Err(ApiError::HandleTaken);
|
||||
}
|
||||
match state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.admin_update_handle(did, &handle_for_check)
|
||||
.await
|
||||
{
|
||||
@@ -125,7 +125,7 @@ pub async fn update_account_handle(
|
||||
{
|
||||
warn!("Failed to update PLC handle for admin handle update: {}", e);
|
||||
}
|
||||
Ok(EmptyResponse::ok().into_response())
|
||||
Ok(Json(EmptyResponse {}))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("DB error updating handle: {:?}", e);
|
||||
@@ -144,27 +144,56 @@ pub async fn update_account_password(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<UpdateAccountPasswordInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let did = &input.did;
|
||||
let password = input.password.trim();
|
||||
if password.is_empty() {
|
||||
return Err(ApiError::InvalidRequest("password is required".into()));
|
||||
}
|
||||
let password_hash = bcrypt::hash(password, bcrypt::DEFAULT_COST).map_err(|e| {
|
||||
error!("Failed to hash password: {:?}", e);
|
||||
ApiError::InternalError(None)
|
||||
})?;
|
||||
let password_hash = crate::common::hash_or_internal_error(password)?;
|
||||
|
||||
match state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.admin_update_password(did, &password_hash)
|
||||
.await
|
||||
{
|
||||
Ok(0) => Err(ApiError::AccountNotFound),
|
||||
Ok(_) => Ok(EmptyResponse::ok().into_response()),
|
||||
Ok(_) => Ok(Json(EmptyResponse {})),
|
||||
Err(e) => {
|
||||
error!("DB error updating password: {:?}", e);
|
||||
Err(ApiError::InternalError(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetAdminStatusInput {
|
||||
pub did: Did,
|
||||
pub admin: bool,
|
||||
}
|
||||
|
||||
pub async fn set_admin_status(
|
||||
State(state): State<AppState>,
|
||||
auth: Auth<Admin>,
|
||||
Json(input): Json<SetAdminStatusInput>,
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
info!(
|
||||
actor = %auth.did,
|
||||
target = %input.did,
|
||||
admin = input.admin,
|
||||
"admin status change"
|
||||
);
|
||||
|
||||
state
|
||||
.repos
|
||||
.user
|
||||
.set_admin_status(&input.did, input.admin)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("DB error setting admin status: {:?}", e);
|
||||
ApiError::InternalError(None)
|
||||
})?;
|
||||
|
||||
Ok(Json(EmptyResponse {}))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use tranquil_types::CidLink;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServerConfigResponse {
|
||||
pub struct ServerConfigOutput {
|
||||
pub server_name: String,
|
||||
pub primary_color: Option<String>,
|
||||
pub primary_color_dark: Option<String>,
|
||||
@@ -29,7 +29,7 @@ pub struct UpdateServerConfigRequest {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateServerConfigResponse {
|
||||
pub struct UpdateServerConfigOutput {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fn is_valid_hex_color(s: &str) -> bool {
|
||||
|
||||
pub async fn get_server_config(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<ServerConfigResponse>, ApiError> {
|
||||
) -> Result<Json<ServerConfigOutput>, ApiError> {
|
||||
let keys = &[
|
||||
"server_name",
|
||||
"primary_color",
|
||||
@@ -53,14 +53,15 @@ pub async fn get_server_config(
|
||||
];
|
||||
|
||||
let rows = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_server_configs(keys)
|
||||
.await
|
||||
.log_db_err("fetching server config")?;
|
||||
|
||||
let config_map: std::collections::HashMap<String, String> = rows.into_iter().collect();
|
||||
|
||||
Ok(Json(ServerConfigResponse {
|
||||
Ok(Json(ServerConfigOutput {
|
||||
server_name: config_map
|
||||
.get("server_name")
|
||||
.cloned()
|
||||
@@ -77,7 +78,7 @@ pub async fn update_server_config(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(req): Json<UpdateServerConfigRequest>,
|
||||
) -> Result<Json<UpdateServerConfigResponse>, ApiError> {
|
||||
) -> Result<Json<UpdateServerConfigOutput>, ApiError> {
|
||||
if let Some(server_name) = req.server_name {
|
||||
let trimmed = server_name.trim();
|
||||
if trimmed.is_empty() || trimmed.len() > 100 {
|
||||
@@ -86,7 +87,8 @@ pub async fn update_server_config(
|
||||
));
|
||||
}
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("server_name", trimmed)
|
||||
.await
|
||||
.log_db_err("upserting server_name")?;
|
||||
@@ -95,13 +97,15 @@ pub async fn update_server_config(
|
||||
if let Some(ref color) = req.primary_color {
|
||||
if color.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.delete_server_config("primary_color")
|
||||
.await
|
||||
.log_db_err("deleting primary_color")?;
|
||||
} else if is_valid_hex_color(color) {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("primary_color", color)
|
||||
.await
|
||||
.log_db_err("upserting primary_color")?;
|
||||
@@ -115,13 +119,15 @@ pub async fn update_server_config(
|
||||
if let Some(ref color) = req.primary_color_dark {
|
||||
if color.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.delete_server_config("primary_color_dark")
|
||||
.await
|
||||
.log_db_err("deleting primary_color_dark")?;
|
||||
} else if is_valid_hex_color(color) {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("primary_color_dark", color)
|
||||
.await
|
||||
.log_db_err("upserting primary_color_dark")?;
|
||||
@@ -135,13 +141,15 @@ pub async fn update_server_config(
|
||||
if let Some(ref color) = req.secondary_color {
|
||||
if color.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.delete_server_config("secondary_color")
|
||||
.await
|
||||
.log_db_err("deleting secondary_color")?;
|
||||
} else if is_valid_hex_color(color) {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("secondary_color", color)
|
||||
.await
|
||||
.log_db_err("upserting secondary_color")?;
|
||||
@@ -155,13 +163,15 @@ pub async fn update_server_config(
|
||||
if let Some(ref color) = req.secondary_color_dark {
|
||||
if color.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.delete_server_config("secondary_color_dark")
|
||||
.await
|
||||
.log_db_err("deleting secondary_color_dark")?;
|
||||
} else if is_valid_hex_color(color) {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("secondary_color_dark", color)
|
||||
.await
|
||||
.log_db_err("upserting secondary_color_dark")?;
|
||||
@@ -174,7 +184,8 @@ pub async fn update_server_config(
|
||||
|
||||
if let Some(ref logo_cid) = req.logo_cid {
|
||||
let old_logo_cid = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_server_config("logo_cid")
|
||||
.await
|
||||
.ok()
|
||||
@@ -189,13 +200,16 @@ pub async fn update_server_config(
|
||||
if let Some(old_cid_str) = should_delete_old {
|
||||
match CidLink::new(old_cid_str) {
|
||||
Ok(old_cid) => {
|
||||
if let Ok(Some(storage_key)) =
|
||||
state.infra_repo.get_blob_storage_key_by_cid(&old_cid).await
|
||||
if let Ok(Some(storage_key)) = state
|
||||
.repos
|
||||
.infra
|
||||
.get_blob_storage_key_by_cid(&old_cid)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = state.blob_store.delete(&storage_key).await {
|
||||
error!("Failed to delete old logo blob from storage: {:?}", e);
|
||||
}
|
||||
if let Err(e) = state.infra_repo.delete_blob_by_cid(&old_cid).await {
|
||||
if let Err(e) = state.repos.infra.delete_blob_by_cid(&old_cid).await {
|
||||
error!("Failed to delete old logo blob record: {:?}", e);
|
||||
}
|
||||
}
|
||||
@@ -211,18 +225,20 @@ pub async fn update_server_config(
|
||||
|
||||
if logo_cid.is_empty() {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.delete_server_config("logo_cid")
|
||||
.await
|
||||
.log_db_err("deleting logo_cid")?;
|
||||
} else {
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.upsert_server_config("logo_cid", logo_cid)
|
||||
.await
|
||||
.log_db_err("upserting logo_cid")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(UpdateServerConfigResponse { success: true }))
|
||||
Ok(Json(UpdateServerConfigOutput { success: true }))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::common;
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
@@ -23,9 +22,9 @@ pub async fn disable_invite_codes(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<DisableInviteCodesInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
if let Some(codes) = &input.codes
|
||||
&& let Err(e) = state.infra_repo.disable_invite_codes_by_code(codes).await
|
||||
&& let Err(e) = state.repos.infra.disable_invite_codes_by_code(codes).await
|
||||
{
|
||||
error!("DB error disabling invite codes: {:?}", e);
|
||||
}
|
||||
@@ -33,14 +32,15 @@ pub async fn disable_invite_codes(
|
||||
let accounts_typed: Vec<tranquil_types::Did> =
|
||||
accounts.iter().filter_map(|a| a.parse().ok()).collect();
|
||||
if let Err(e) = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.disable_invite_codes_by_account(&accounts_typed)
|
||||
.await
|
||||
{
|
||||
error!("DB error disabling invite codes by account: {:?}", e);
|
||||
}
|
||||
}
|
||||
Ok(EmptyResponse::ok().into_response())
|
||||
Ok(Json(EmptyResponse {}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -80,7 +80,7 @@ pub async fn get_invite_codes(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Query(params): Query<GetInviteCodesParams>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<GetInviteCodesOutput>, ApiError> {
|
||||
let limit = params.limit.unwrap_or(100).clamp(1, 500);
|
||||
let sort_order = match params.sort.as_deref() {
|
||||
Some("usage") => InviteCodeSortOrder::Usage,
|
||||
@@ -88,7 +88,8 @@ pub async fn get_invite_codes(
|
||||
};
|
||||
|
||||
let codes_rows = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.list_invite_codes(params.cursor.as_deref(), limit, sort_order)
|
||||
.await
|
||||
.log_db_err("fetching invite codes")?;
|
||||
@@ -97,33 +98,30 @@ pub async fn get_invite_codes(
|
||||
let code_strings: Vec<String> = codes_rows.iter().map(|r| r.code.clone()).collect();
|
||||
|
||||
let creator_dids: std::collections::HashMap<uuid::Uuid, tranquil_types::Did> = state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_user_dids_by_ids(&user_ids)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let uses_by_code: std::collections::HashMap<String, Vec<InviteCodeUseInfo>> =
|
||||
if code_strings.is_empty() {
|
||||
std::collections::HashMap::new()
|
||||
} else {
|
||||
let uses_by_code = if code_strings.is_empty() {
|
||||
std::collections::HashMap::new()
|
||||
} else {
|
||||
common::group_invite_uses_by_code(
|
||||
state
|
||||
.infra_repo
|
||||
.repos
|
||||
.infra
|
||||
.get_invite_code_uses_batch(&code_strings)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.fold(std::collections::HashMap::new(), |mut acc, u| {
|
||||
acc.entry(u.code.clone())
|
||||
.or_default()
|
||||
.push(InviteCodeUseInfo {
|
||||
used_by: u.used_by_did.to_string(),
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
});
|
||||
acc
|
||||
})
|
||||
};
|
||||
.unwrap_or_default(),
|
||||
|u| InviteCodeUseInfo {
|
||||
used_by: u.used_by_did.to_string(),
|
||||
used_at: u.used_at.to_rfc3339(),
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let codes: Vec<InviteCodeInfo> = codes_rows
|
||||
.iter()
|
||||
@@ -149,14 +147,10 @@ pub async fn get_invite_codes(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(GetInviteCodesOutput {
|
||||
cursor: next_cursor,
|
||||
codes,
|
||||
}),
|
||||
)
|
||||
.into_response())
|
||||
Ok(Json(GetInviteCodesOutput {
|
||||
cursor: next_cursor,
|
||||
codes,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -168,7 +162,7 @@ pub async fn disable_account_invites(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<DisableAccountInvitesInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let account = input.account.trim();
|
||||
if account.is_empty() {
|
||||
return Err(ApiError::InvalidRequest("account is required".into()));
|
||||
@@ -178,11 +172,12 @@ pub async fn disable_account_invites(
|
||||
.map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?;
|
||||
|
||||
match state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.set_invites_disabled(&account_did, true)
|
||||
.await
|
||||
{
|
||||
Ok(true) => Ok(EmptyResponse::ok().into_response()),
|
||||
Ok(true) => Ok(Json(EmptyResponse {})),
|
||||
Ok(false) => Err(ApiError::AccountNotFound),
|
||||
Err(e) => {
|
||||
error!("DB error disabling account invites: {:?}", e);
|
||||
@@ -200,7 +195,7 @@ pub async fn enable_account_invites(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<EnableAccountInvitesInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<EmptyResponse>, ApiError> {
|
||||
let account = input.account.trim();
|
||||
if account.is_empty() {
|
||||
return Err(ApiError::InvalidRequest("account is required".into()));
|
||||
@@ -210,11 +205,12 @@ pub async fn enable_account_invites(
|
||||
.map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?;
|
||||
|
||||
match state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.set_invites_disabled(&account_did, false)
|
||||
.await
|
||||
{
|
||||
Ok(true) => Ok(EmptyResponse::ok().into_response()),
|
||||
Ok(true) => Ok(Json(EmptyResponse {})),
|
||||
Ok(false) => Err(ApiError::AccountNotFound),
|
||||
Err(e) => {
|
||||
error!("DB error enabling account invites: {:?}", e);
|
||||
|
||||
@@ -2,15 +2,17 @@ pub mod account;
|
||||
pub mod config;
|
||||
pub mod invite;
|
||||
pub mod server_stats;
|
||||
pub mod signal;
|
||||
pub mod status;
|
||||
|
||||
pub use account::{
|
||||
delete_account, get_account_info, get_account_infos, search_accounts, send_email,
|
||||
update_account_email, update_account_handle, update_account_password,
|
||||
set_admin_status, update_account_email, update_account_handle, update_account_password,
|
||||
};
|
||||
pub use config::{get_server_config, update_server_config};
|
||||
pub use invite::{
|
||||
disable_account_invites, disable_invite_codes, enable_account_invites, get_invite_codes,
|
||||
};
|
||||
pub use server_stats::get_server_stats;
|
||||
pub use signal::{get_signal_status, link_signal_device, unlink_signal_device};
|
||||
pub use status::{get_subject_status, update_subject_status};
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use axum::{Json, extract::State};
|
||||
use serde::Serialize;
|
||||
use tranquil_pds::api::error::ApiError;
|
||||
use tranquil_pds::auth::{Admin, Auth};
|
||||
@@ -10,7 +6,7 @@ use tranquil_pds::state::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServerStatsResponse {
|
||||
pub struct ServerStatsOutput {
|
||||
pub user_count: i64,
|
||||
pub repo_count: i64,
|
||||
pub record_count: i64,
|
||||
@@ -20,17 +16,16 @@ pub struct ServerStatsResponse {
|
||||
pub async fn get_server_stats(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
) -> Result<Response, ApiError> {
|
||||
let user_count = state.user_repo.count_users().await.unwrap_or(0);
|
||||
let repo_count = state.repo_repo.count_repos().await.unwrap_or(0);
|
||||
let record_count = state.repo_repo.count_all_records().await.unwrap_or(0);
|
||||
let blob_storage_bytes = state.blob_repo.sum_blob_storage().await.unwrap_or(0);
|
||||
) -> Result<Json<ServerStatsOutput>, ApiError> {
|
||||
let user_count = state.repos.user.count_users().await.unwrap_or(0);
|
||||
let repo_count = state.repos.repo.count_repos().await.unwrap_or(0);
|
||||
let record_count = state.repos.repo.count_all_records().await.unwrap_or(0);
|
||||
let blob_storage_bytes = state.repos.blob.sum_blob_storage().await.unwrap_or(0);
|
||||
|
||||
Ok(Json(ServerStatsResponse {
|
||||
Ok(Json(ServerStatsOutput {
|
||||
user_count,
|
||||
repo_count,
|
||||
record_count,
|
||||
blob_storage_bytes,
|
||||
})
|
||||
.into_response())
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
use axum::{Json, extract::State};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use image::{ImageBuffer, Luma};
|
||||
use serde::Serialize;
|
||||
use tranquil_pds::api::error::ApiError;
|
||||
use tranquil_pds::auth::{Admin, Auth};
|
||||
use tranquil_pds::state::AppState;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignalStatusOutput {
|
||||
pub enabled: bool,
|
||||
pub linked: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignalLinkOutput {
|
||||
pub qr_base64: String,
|
||||
}
|
||||
|
||||
pub async fn get_signal_status(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
) -> Result<Json<SignalStatusOutput>, ApiError> {
|
||||
let enabled = tranquil_config::get().signal.enabled;
|
||||
let linked = match &state.signal_sender {
|
||||
Some(slot) => slot.is_linked().await,
|
||||
None => false,
|
||||
};
|
||||
|
||||
Ok(Json(SignalStatusOutput { enabled, linked }))
|
||||
}
|
||||
|
||||
pub async fn link_signal_device(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
) -> Result<Json<SignalLinkOutput>, ApiError> {
|
||||
let slot = state
|
||||
.signal_sender
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::InvalidRequest("Signal is not enabled".into()))?;
|
||||
|
||||
if slot.is_linked().await {
|
||||
return Err(ApiError::InvalidRequest(
|
||||
"Signal device already linked".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let (generation, link_cancel) = slot.begin_link().await;
|
||||
|
||||
let device_name = tranquil_signal::DeviceName::new("tranquil-pds".to_string())
|
||||
.map_err(|e| ApiError::InternalError(Some(format!("invalid device name: {e}"))))?;
|
||||
|
||||
let signal_store = state
|
||||
.signal_store_provider
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::InternalError(Some("Signal store not configured".into())))?;
|
||||
|
||||
let link_result = signal_store
|
||||
.link_signal_device(
|
||||
device_name,
|
||||
state.shutdown.clone(),
|
||||
link_cancel,
|
||||
slot.linking_flag(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ApiError::InternalError(Some(format!("Signal linking failed: {e}"))))?;
|
||||
|
||||
let qr_base64 = url_to_qr_png_base64(link_result.url.as_str())
|
||||
.map_err(|e| ApiError::InternalError(Some(format!("QR generation failed: {e}"))))?;
|
||||
|
||||
let slot_for_task = slot.clone();
|
||||
let shutdown = state.shutdown.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.cancelled() => {
|
||||
tracing::info!("Signal linking aborted due to server shutdown");
|
||||
return;
|
||||
}
|
||||
r = link_result.completion => r,
|
||||
};
|
||||
match result {
|
||||
Ok(Ok(client)) => {
|
||||
if slot_for_task.complete_link(generation, client).await {
|
||||
tracing::info!("Signal device linked successfully");
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Signal link completed but generation mismatch or already linked; discarding"
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
tracing::error!(error = %e, "Signal device linking failed");
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!("Signal linking task dropped without completing");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Json(SignalLinkOutput { qr_base64 }))
|
||||
}
|
||||
|
||||
pub async fn unlink_signal_device(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let slot = state
|
||||
.signal_sender
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::InvalidRequest("Signal is not enabled".into()))?;
|
||||
|
||||
let signal_store = state
|
||||
.signal_store_provider
|
||||
.as_ref()
|
||||
.ok_or_else(|| ApiError::InternalError(Some("Signal store not configured".into())))?;
|
||||
|
||||
signal_store
|
||||
.clear_signal_data()
|
||||
.await
|
||||
.map_err(|e| ApiError::InternalError(Some(format!("Failed to clear signal data: {e}"))))?;
|
||||
|
||||
slot.unlink().await;
|
||||
|
||||
Ok(Json(serde_json::json!({})))
|
||||
}
|
||||
|
||||
const QR_MODULE_SCALE: u32 = 8;
|
||||
const QR_QUIET_ZONE_MODULES: u32 = 4;
|
||||
|
||||
fn url_to_qr_png_base64(url: &str) -> Result<String, String> {
|
||||
let qr = qrcodegen::QrCode::encode_text(url, qrcodegen::QrCodeEcc::Medium)
|
||||
.map_err(|e| format!("QR encode failed: {e:?}"))?;
|
||||
let size = u32::try_from(qr.size()).map_err(|_| "QR size is negative".to_string())?;
|
||||
let img_size = size
|
||||
.checked_add(
|
||||
QR_QUIET_ZONE_MODULES
|
||||
.checked_mul(2)
|
||||
.ok_or("border overflow")?,
|
||||
)
|
||||
.ok_or("image size overflow")?
|
||||
.checked_mul(QR_MODULE_SCALE)
|
||||
.ok_or("scaled size overflow")?;
|
||||
|
||||
let img: ImageBuffer<Luma<u8>, Vec<u8>> = ImageBuffer::from_fn(img_size, img_size, |x, y| {
|
||||
let module_x = x / QR_MODULE_SCALE;
|
||||
let module_y = y / QR_MODULE_SCALE;
|
||||
match (
|
||||
module_x.checked_sub(QR_QUIET_ZONE_MODULES),
|
||||
module_y.checked_sub(QR_QUIET_ZONE_MODULES),
|
||||
) {
|
||||
(Some(mx), Some(my)) if mx < size && my < size => {
|
||||
if qr.get_module(mx as i32, my as i32) {
|
||||
Luma([0u8])
|
||||
} else {
|
||||
Luma([255u8])
|
||||
}
|
||||
}
|
||||
_ => Luma([255u8]),
|
||||
}
|
||||
});
|
||||
|
||||
let mut png_bytes = Vec::new();
|
||||
let mut cursor = std::io::Cursor::new(&mut png_bytes);
|
||||
img.write_to(&mut cursor, image::ImageFormat::Png)
|
||||
.map_err(|e| format!("PNG encode failed: {e}"))?;
|
||||
|
||||
Ok(STANDARD.encode(&png_bytes))
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
use tracing::{error, warn};
|
||||
use tranquil_pds::api::error::ApiError;
|
||||
use tranquil_pds::auth::{Admin, Auth};
|
||||
@@ -37,7 +35,7 @@ pub async fn get_subject_status(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Query(params): Query<GetSubjectStatusParams>,
|
||||
) -> Result<Response, ApiError> {
|
||||
) -> Result<Json<SubjectStatus>, ApiError> {
|
||||
if params.did.is_none() && params.uri.is_none() && params.blob.is_none() {
|
||||
return Err(ApiError::InvalidRequest(
|
||||
"Must provide did, uri, or blob".into(),
|
||||
@@ -47,7 +45,7 @@ pub async fn get_subject_status(
|
||||
let did: Did = did_str
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidDid("Invalid DID format".into()))?;
|
||||
match state.user_repo.get_status_by_did(&did).await {
|
||||
match state.repos.user.get_status_by_did(&did).await {
|
||||
Ok(Some(status)) => {
|
||||
let deactivated = status.deactivated_at.map(|_| StatusAttr {
|
||||
applied: true,
|
||||
@@ -57,18 +55,14 @@ pub async fn get_subject_status(
|
||||
applied: true,
|
||||
r#ref: Some(r.clone()),
|
||||
});
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.admin.defs#repoRef",
|
||||
"did": did_str
|
||||
}),
|
||||
takedown,
|
||||
deactivated,
|
||||
return Ok(Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.admin.defs#repoRef",
|
||||
"did": did_str
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
takedown,
|
||||
deactivated,
|
||||
}));
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err(ApiError::SubjectNotFound);
|
||||
@@ -83,25 +77,21 @@ pub async fn get_subject_status(
|
||||
let cid: CidLink = uri_str
|
||||
.parse()
|
||||
.map_err(|_| ApiError::InvalidRequest("Invalid CID format".into()))?;
|
||||
match state.repo_repo.get_record_by_cid(&cid).await {
|
||||
match state.repos.repo.get_record_by_cid(&cid).await {
|
||||
Ok(Some(record)) => {
|
||||
let takedown = record.takedown_ref.as_ref().map(|r| StatusAttr {
|
||||
applied: true,
|
||||
r#ref: Some(r.clone()),
|
||||
});
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.repo.strongRef",
|
||||
"uri": uri_str,
|
||||
"cid": uri_str
|
||||
}),
|
||||
takedown,
|
||||
deactivated: None,
|
||||
return Ok(Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.repo.strongRef",
|
||||
"uri": uri_str,
|
||||
"cid": uri_str
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
takedown,
|
||||
deactivated: None,
|
||||
}));
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err(ApiError::RecordNotFound);
|
||||
@@ -119,25 +109,21 @@ pub async fn get_subject_status(
|
||||
let did = params.did.as_ref().ok_or_else(|| {
|
||||
ApiError::InvalidRequest("Must provide a did to request blob state".into())
|
||||
})?;
|
||||
match state.blob_repo.get_blob_with_takedown(&blob_cid).await {
|
||||
match state.repos.blob.get_blob_with_takedown(&blob_cid).await {
|
||||
Ok(Some(blob)) => {
|
||||
let takedown = blob.takedown_ref.as_ref().map(|r| StatusAttr {
|
||||
applied: true,
|
||||
r#ref: Some(r.clone()),
|
||||
});
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.admin.defs#repoBlobRef",
|
||||
"did": did,
|
||||
"cid": blob.cid
|
||||
}),
|
||||
takedown,
|
||||
deactivated: None,
|
||||
return Ok(Json(SubjectStatus {
|
||||
subject: json!({
|
||||
"$type": "com.atproto.admin.defs#repoBlobRef",
|
||||
"did": did,
|
||||
"cid": blob.cid
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
takedown,
|
||||
deactivated: None,
|
||||
}));
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err(ApiError::BlobNotFound(None));
|
||||
@@ -169,11 +155,11 @@ pub async fn update_subject_status(
|
||||
State(state): State<AppState>,
|
||||
_auth: Auth<Admin>,
|
||||
Json(input): Json<UpdateSubjectStatusInput>,
|
||||
) -> Result<Response, ApiError> {
|
||||
let subject_type = input.subject.get("$type").and_then(|t| t.as_str());
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let subject_type = input.subject.get("$type").and_then(Value::as_str);
|
||||
match subject_type {
|
||||
Some("com.atproto.admin.defs#repoRef") => {
|
||||
let did_str = input.subject.get("did").and_then(|d| d.as_str());
|
||||
let did_str = input.subject.get("did").and_then(Value::as_str);
|
||||
if let Some(did_str) = did_str {
|
||||
let did: Did = match did_str.parse() {
|
||||
Ok(d) => d,
|
||||
@@ -186,7 +172,8 @@ pub async fn update_subject_status(
|
||||
None
|
||||
};
|
||||
state
|
||||
.user_repo
|
||||
.repos
|
||||
.user
|
||||
.set_user_takedown(&did, takedown_ref)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -196,9 +183,9 @@ pub async fn update_subject_status(
|
||||
}
|
||||
if let Some(deactivated) = &input.deactivated {
|
||||
let result = if deactivated.applied {
|
||||
state.user_repo.deactivate_account(&did, None).await
|
||||
state.repos.user.deactivate_account(&did, None).await
|
||||
} else {
|
||||
state.user_repo.activate_account(&did).await
|
||||
state.repos.user.activate_account(&did).await
|
||||
};
|
||||
result.map_err(|e| {
|
||||
error!(
|
||||
@@ -208,11 +195,22 @@ pub async fn update_subject_status(
|
||||
ApiError::InternalError(Some("Failed to update deactivation status".into()))
|
||||
})?;
|
||||
}
|
||||
let takedown_update = input.takedown.as_ref().map(|t| t.applied);
|
||||
let takedown_ref = input.takedown.as_ref().and_then(|t| t.r#ref.as_deref());
|
||||
let deactivated_update = input.deactivated.as_ref().map(|d| d.applied);
|
||||
if (takedown_update.is_some() || deactivated_update.is_some())
|
||||
&& let Err(e) = state
|
||||
.repos
|
||||
.repo
|
||||
.update_repo_status(&did, takedown_update, takedown_ref, deactivated_update)
|
||||
.await
|
||||
{
|
||||
warn!("failed to sync status to repo backend: {e:?}");
|
||||
}
|
||||
if let Some(takedown) = &input.takedown {
|
||||
let status = if takedown.applied {
|
||||
tranquil_db_traits::AccountStatus::Takendown
|
||||
} else {
|
||||
tranquil_db_traits::AccountStatus::Active
|
||||
let status = match takedown.applied {
|
||||
true => tranquil_db_traits::AccountStatus::Takendown,
|
||||
false => tranquil_db_traits::AccountStatus::Active,
|
||||
};
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_account_event(&state, &did, status).await
|
||||
@@ -221,10 +219,9 @@ pub async fn update_subject_status(
|
||||
}
|
||||
}
|
||||
if let Some(deactivated) = &input.deactivated {
|
||||
let status = if deactivated.applied {
|
||||
tranquil_db_traits::AccountStatus::Deactivated
|
||||
} else {
|
||||
tranquil_db_traits::AccountStatus::Active
|
||||
let status = match deactivated.applied {
|
||||
true => tranquil_db_traits::AccountStatus::Deactivated,
|
||||
false => tranquil_db_traits::AccountStatus::Active,
|
||||
};
|
||||
if let Err(e) =
|
||||
tranquil_pds::repo_ops::sequence_account_event(&state, &did, status).await
|
||||
@@ -232,30 +229,26 @@ pub async fn update_subject_status(
|
||||
warn!("Failed to sequence account event for deactivation: {}", e);
|
||||
}
|
||||
}
|
||||
if let Ok(Some(handle)) = state.user_repo.get_handle_by_did(&did).await {
|
||||
if let Ok(Some(handle)) = state.repos.user.get_handle_by_did(&did).await {
|
||||
let _ = state
|
||||
.cache
|
||||
.delete(&tranquil_pds::cache_keys::handle_key(&handle))
|
||||
.await;
|
||||
}
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
})),
|
||||
"deactivated": input.deactivated.as_ref().map(|d| json!({
|
||||
"applied": d.applied
|
||||
}))
|
||||
return Ok(Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
})),
|
||||
)
|
||||
.into_response());
|
||||
"deactivated": input.deactivated.as_ref().map(|d| json!({
|
||||
"applied": d.applied
|
||||
}))
|
||||
})));
|
||||
}
|
||||
}
|
||||
Some("com.atproto.repo.strongRef") => {
|
||||
let uri_str = input.subject.get("uri").and_then(|u| u.as_str());
|
||||
let uri_str = input.subject.get("uri").and_then(Value::as_str);
|
||||
if let Some(uri_str) = uri_str {
|
||||
let cid: CidLink = uri_str
|
||||
.parse()
|
||||
@@ -267,7 +260,8 @@ pub async fn update_subject_status(
|
||||
None
|
||||
};
|
||||
state
|
||||
.repo_repo
|
||||
.repos
|
||||
.repo
|
||||
.set_record_takedown(&cid, takedown_ref)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -278,21 +272,17 @@ pub async fn update_subject_status(
|
||||
ApiError::InternalError(Some("Failed to update takedown status".into()))
|
||||
})?;
|
||||
}
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.into_response());
|
||||
return Ok(Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
}))
|
||||
})));
|
||||
}
|
||||
}
|
||||
Some("com.atproto.admin.defs#repoBlobRef") => {
|
||||
let cid_str = input.subject.get("cid").and_then(|c| c.as_str());
|
||||
let cid_str = input.subject.get("cid").and_then(Value::as_str);
|
||||
if let Some(cid_str) = cid_str {
|
||||
let cid: CidLink = cid_str
|
||||
.parse()
|
||||
@@ -304,7 +294,8 @@ pub async fn update_subject_status(
|
||||
None
|
||||
};
|
||||
state
|
||||
.blob_repo
|
||||
.repos
|
||||
.blob
|
||||
.update_blob_takedown(&cid, takedown_ref)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -315,17 +306,13 @@ pub async fn update_subject_status(
|
||||
ApiError::InternalError(Some("Failed to update takedown status".into()))
|
||||
})?;
|
||||
}
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.into_response());
|
||||
return Ok(Json(json!({
|
||||
"subject": input.subject,
|
||||
"takedown": input.takedown.as_ref().map(|t| json!({
|
||||
"applied": t.applied,
|
||||
"ref": t.r#ref
|
||||
}))
|
||||
})));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -1,37 +1,60 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::{HeaderMap, Method, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
http::{HeaderMap, Method},
|
||||
};
|
||||
use serde_json::json;
|
||||
use serde::Serialize;
|
||||
use tranquil_pds::auth::{
|
||||
AccountRequirement, extract_auth_token_from_header, validate_token_with_dpop,
|
||||
};
|
||||
use tranquil_pds::state::AppState;
|
||||
|
||||
pub async fn get_state(State(state): State<AppState>, headers: HeaderMap) -> Response {
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgeAssuranceState {
|
||||
pub status: &'static str,
|
||||
pub access: &'static str,
|
||||
pub last_initiated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AgeAssuranceMetadata {
|
||||
pub account_created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetAgeAssuranceOutput {
|
||||
pub state: AgeAssuranceState,
|
||||
pub metadata: AgeAssuranceMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AgeAssuranceStatusOutput {
|
||||
pub status: &'static str,
|
||||
}
|
||||
|
||||
pub async fn get_state(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Json<GetAgeAssuranceOutput> {
|
||||
let created_at = get_account_created_at(&state, &headers).await;
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(json!({
|
||||
"state": {
|
||||
"status": "assured",
|
||||
"access": "full",
|
||||
"lastInitiatedAt": now
|
||||
},
|
||||
"metadata": {
|
||||
"accountCreatedAt": created_at
|
||||
}
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
Json(GetAgeAssuranceOutput {
|
||||
state: AgeAssuranceState {
|
||||
status: "assured",
|
||||
access: "full",
|
||||
last_initiated_at: now,
|
||||
},
|
||||
metadata: AgeAssuranceMetadata {
|
||||
account_created_at: created_at,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_age_assurance_state() -> Response {
|
||||
(StatusCode::OK, Json(json!({"status": "assured"}))).into_response()
|
||||
pub async fn get_age_assurance_state() -> Json<AgeAssuranceStatusOutput> {
|
||||
Json(AgeAssuranceStatusOutput { status: "assured" })
|
||||
}
|
||||
|
||||
async fn get_account_created_at(state: &AppState, headers: &HeaderMap) -> Option<String> {
|
||||
@@ -45,8 +68,8 @@ async fn get_account_created_at(state: &AppState, headers: &HeaderMap) -> Option
|
||||
let http_uri = "/";
|
||||
|
||||
let auth_user = match validate_token_with_dpop(
|
||||
state.user_repo.as_ref(),
|
||||
state.oauth_repo.as_ref(),
|
||||
state.repos.user.as_ref(),
|
||||
state.repos.oauth.as_ref(),
|
||||
&extracted.token,
|
||||
extracted.scheme,
|
||||
dpop_proof,
|
||||
@@ -66,7 +89,7 @@ async fn get_account_created_at(state: &AppState, headers: &HeaderMap) -> Option
|
||||
}
|
||||
};
|
||||
|
||||
match state.user_repo.get_by_did(&auth_user.did).await {
|
||||
match state.repos.user.get_by_did(&auth_user.did).await {
|
||||
Ok(Some(user)) => {
|
||||
tracing::debug!(created_at = ?user.created_at, "age assurance: got user");
|
||||
Some(user.created_at.to_rfc3339())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user