catcolab_backend/
document.rs1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use ts_rs::TS;
6use uuid::Uuid;
7
8use super::app::{AppCtx, AppError, AppState};
9
10pub async fn new_ref(ctx: AppCtx, content: Value) -> Result<Uuid, AppError> {
12 let ref_id = Uuid::now_v7();
13
14 let mut transaction = ctx.state.db.begin().await?;
15
16 let user_id = ctx.user.map(|user| user.user_id);
17 let insert_ref = sqlx::query!(
18 "
19 WITH snapshot AS (
20 INSERT INTO snapshots(for_ref, content, last_updated)
21 VALUES ($1, $2, NOW())
22 RETURNING id
23 )
24 INSERT INTO refs(id, head, created)
25 VALUES ($1, (SELECT id FROM snapshot), NOW())
26 ",
27 ref_id,
28 content
29 );
30 insert_ref.execute(&mut *transaction).await?;
31
32 let insert_permission = sqlx::query!(
33 "
34 INSERT INTO permissions(subject, object, level)
35 VALUES ($1, $2, 'own')
36 ",
37 user_id,
38 ref_id,
39 );
40 insert_permission.execute(&mut *transaction).await?;
41
42 transaction.commit().await?;
43 Ok(ref_id)
44}
45
46pub async fn head_snapshot(state: AppState, ref_id: Uuid) -> Result<Value, AppError> {
48 let query = sqlx::query!(
49 "
50 SELECT content FROM snapshots
51 WHERE id = (SELECT head FROM refs WHERE id = $1)
52 ",
53 ref_id
54 );
55 Ok(query.fetch_one(&state.db).await?.content)
56}
57
58pub async fn autosave(state: AppState, data: RefContent) -> Result<(), AppError> {
60 let RefContent { ref_id, content } = data;
61 let query = sqlx::query!(
62 "
63 UPDATE snapshots
64 SET content = $2, last_updated = NOW()
65 WHERE id = (SELECT head FROM refs WHERE id = $1)
66 ",
67 ref_id,
68 content
69 );
70 query.execute(&state.db).await?;
71 Ok(())
72}
73
74pub async fn save_snapshot(state: AppState, data: RefContent) -> Result<(), AppError> {
79 let RefContent { ref_id, content } = data;
80 let query = sqlx::query!(
81 "
82 WITH snapshot AS (
83 INSERT INTO snapshots(for_ref, content, last_updated)
84 VALUES ($1, $2, NOW())
85 RETURNING id
86 )
87 UPDATE refs
88 SET head = (SELECT id FROM snapshot)
89 WHERE id = $1
90 ",
91 ref_id,
92 content
93 );
94 query.execute(&state.db).await?;
95 Ok(())
96}
97
98pub async fn doc_id(state: AppState, ref_id: Uuid) -> Result<String, AppError> {
100 let automerge_io = &state.automerge_io;
101 let ack = automerge_io.emit_with_ack::<Vec<Option<String>>>("get_doc", ref_id).unwrap();
102 let mut response = ack.await?;
103
104 let maybe_doc_id = response.data.pop().flatten();
105 if let Some(doc_id) = maybe_doc_id {
106 Ok(doc_id)
108 } else {
109 let content = head_snapshot(state.clone(), ref_id).await?;
112 let data = RefContent { ref_id, content };
113 let ack = automerge_io.emit_with_ack::<Vec<String>>("create_doc", data).unwrap();
114 let response = ack.await?;
115 Ok(response.data[0].to_string())
116 }
117}
118
119#[derive(Debug, Serialize, Deserialize, TS)]
121pub struct RefContent {
122 #[serde(rename = "refId")]
123 pub ref_id: Uuid,
124 pub content: Value,
125}