this repo has no description
1use crate::state::AppState;
2use cid::Cid;
3use jacquard::types::{did::Did, integer::LimitedU32, string::Tid};
4use jacquard_repo::commit::Commit;
5use jacquard_repo::storage::BlockStore;
6use k256::ecdsa::SigningKey;
7use serde_json::json;
8use uuid::Uuid;
9
10pub enum RecordOp {
11 Create { collection: String, rkey: String, cid: Cid },
12 Update { collection: String, rkey: String, cid: Cid },
13 Delete { collection: String, rkey: String },
14}
15
16pub struct CommitResult {
17 pub commit_cid: Cid,
18 pub rev: String,
19}
20
21pub async fn commit_and_log(
22 state: &AppState,
23 did: &str,
24 user_id: Uuid,
25 current_root_cid: Option<Cid>,
26 new_mst_root: Cid,
27 ops: Vec<RecordOp>,
28 blocks_cids: &Vec<String>,
29) -> Result<CommitResult, String> {
30 let key_row = sqlx::query!(
31 "SELECT key_bytes, encryption_version FROM user_keys WHERE user_id = $1",
32 user_id
33 )
34 .fetch_one(&state.db)
35 .await
36 .map_err(|e| format!("Failed to fetch signing key: {}", e))?;
37
38 let key_bytes = crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
39 .map_err(|e| format!("Failed to decrypt signing key: {}", e))?;
40
41 let signing_key = SigningKey::from_slice(&key_bytes)
42 .map_err(|e| format!("Invalid signing key: {}", e))?;
43
44 let did_obj = Did::new(did).map_err(|e| format!("Invalid DID: {}", e))?;
45 let rev = Tid::now(LimitedU32::MIN);
46
47 let unsigned_commit = Commit::new_unsigned(did_obj, new_mst_root, rev.clone(), current_root_cid);
48
49 let signed_commit = unsigned_commit
50 .sign(&signing_key)
51 .map_err(|e| format!("Failed to sign commit: {:?}", e))?;
52
53 let new_commit_bytes = signed_commit.to_cbor().map_err(|e| format!("Failed to serialize commit: {:?}", e))?;
54
55 let new_root_cid = state.block_store.put(&new_commit_bytes).await
56 .map_err(|e| format!("Failed to save commit block: {:?}", e))?;
57
58 sqlx::query!("UPDATE repos SET repo_root_cid = $1 WHERE user_id = $2", new_root_cid.to_string(), user_id)
59 .execute(&state.db)
60 .await
61 .map_err(|e| format!("DB Error (repos): {}", e))?;
62
63 for op in &ops {
64 match op {
65 RecordOp::Create { collection, rkey, cid } | RecordOp::Update { collection, rkey, cid } => {
66 sqlx::query!(
67 "INSERT INTO records (repo_id, collection, rkey, record_cid) VALUES ($1, $2, $3, $4)
68 ON CONFLICT (repo_id, collection, rkey) DO UPDATE SET record_cid = $4, created_at = NOW()",
69 user_id,
70 collection,
71 rkey,
72 cid.to_string()
73 )
74 .execute(&state.db)
75 .await
76 .map_err(|e| format!("DB Error (records): {}", e))?;
77 }
78 RecordOp::Delete { collection, rkey } => {
79 sqlx::query!(
80 "DELETE FROM records WHERE repo_id = $1 AND collection = $2 AND rkey = $3",
81 user_id,
82 collection,
83 rkey
84 )
85 .execute(&state.db)
86 .await
87 .map_err(|e| format!("DB Error (records): {}", e))?;
88 }
89 }
90 }
91
92 let ops_json = ops.iter().map(|op| {
93 match op {
94 RecordOp::Create { collection, rkey, cid } => json!({
95 "action": "create",
96 "path": format!("{}/{}", collection, rkey),
97 "cid": cid.to_string()
98 }),
99 RecordOp::Update { collection, rkey, cid } => json!({
100 "action": "update",
101 "path": format!("{}/{}", collection, rkey),
102 "cid": cid.to_string()
103 }),
104 RecordOp::Delete { collection, rkey } => json!({
105 "action": "delete",
106 "path": format!("{}/{}", collection, rkey),
107 "cid": null
108 }),
109 }
110 }).collect::<Vec<_>>();
111
112 let event_type = "commit";
113 let prev_cid_str = current_root_cid.map(|c| c.to_string());
114
115 let seq_row = sqlx::query!(
116 r#"
117 INSERT INTO repo_seq (did, event_type, commit_cid, prev_cid, ops, blobs, blocks_cids)
118 VALUES ($1, $2, $3, $4, $5, $6, $7)
119 RETURNING seq
120 "#,
121 did,
122 event_type,
123 new_root_cid.to_string(),
124 prev_cid_str,
125 json!(ops_json),
126 &[] as &[String],
127 blocks_cids,
128 )
129 .fetch_one(&state.db)
130 .await
131 .map_err(|e| format!("DB Error (repo_seq): {}", e))?;
132
133 sqlx::query(
134 &format!("NOTIFY repo_updates, '{}'", seq_row.seq)
135 )
136 .execute(&state.db)
137 .await
138 .map_err(|e| format!("DB Error (notify): {}", e))?;
139
140 Ok(CommitResult {
141 commit_cid: new_root_cid,
142 rev: rev.to_string(),
143 })
144}