1use mnm_core::types::{Node, NodeKind};
5use serde::Serialize;
6use sqlx::PgPool;
7use time::OffsetDateTime;
8use uuid::Uuid;
9
10use crate::error::Result;
11
12pub async fn insert(
19 pool: &PgPool,
20 source_version_id: Uuid,
21 parent_node_id: Option<Uuid>,
22 kind: NodeKind,
23 name: &str,
24 order_index: i32,
25) -> Result<Uuid> {
26 let kind_str = match kind {
27 NodeKind::Root => "root",
28 NodeKind::Group => "group",
29 NodeKind::Document => "document",
30 NodeKind::Chunk => "chunk",
31 };
32 let row: (Uuid,) = sqlx::query_as(
33 "INSERT INTO node (source_version_id, parent_node_id, kind, name, order_index) \
34 VALUES ($1, $2, $3, $4, $5) RETURNING id",
35 )
36 .bind(source_version_id)
37 .bind(parent_node_id)
38 .bind(kind_str)
39 .bind(name)
40 .bind(order_index)
41 .fetch_one(pool)
42 .await?;
43 Ok(row.0)
44}
45
46pub async fn parent_chain(pool: &PgPool, node_id: Uuid) -> Result<Vec<Node>> {
56 let rows = sqlx::query_as::<_, NodeRow>(
57 "WITH RECURSIVE chain AS ( \
58 SELECT id, source_version_id, parent_node_id, kind, name, order_index, created_at, 0 AS depth \
59 FROM node WHERE id = $1 \
60 UNION ALL \
61 SELECT n.id, n.source_version_id, n.parent_node_id, n.kind, n.name, n.order_index, n.created_at, c.depth + 1 \
62 FROM node n JOIN chain c ON n.id = c.parent_node_id \
63 ) \
64 SELECT id, source_version_id, parent_node_id, kind, name, order_index, created_at FROM chain \
65 WHERE depth > 0 ORDER BY depth",
66 )
67 .bind(node_id)
68 .fetch_all(pool)
69 .await?;
70 rows.into_iter().map(TryInto::try_into).collect()
71}
72
73#[derive(Debug, Clone, Serialize)]
76pub struct ParentNode {
77 pub id: Uuid,
79 pub source_version_id: Uuid,
81 pub parent_node_id: Option<Uuid>,
83 pub kind: NodeKind,
85 pub name: String,
87 pub order_index: i32,
89 pub document_id: Option<Uuid>,
91}
92
93pub async fn parent_chain_with_documents(pool: &PgPool, node_id: Uuid) -> Result<Vec<ParentNode>> {
102 #[derive(sqlx::FromRow)]
103 struct Row {
104 id: Uuid,
105 source_version_id: Uuid,
106 parent_node_id: Option<Uuid>,
107 kind: String,
108 name: String,
109 order_index: i32,
110 document_id: Option<Uuid>,
111 }
112 let rows = sqlx::query_as::<_, Row>(
113 "WITH RECURSIVE chain AS ( \
114 SELECT id, source_version_id, parent_node_id, kind, name, order_index, 0 AS depth \
115 FROM node WHERE id = $1 \
116 UNION ALL \
117 SELECT n.id, n.source_version_id, n.parent_node_id, n.kind, n.name, n.order_index, c.depth + 1 \
118 FROM node n JOIN chain c ON n.id = c.parent_node_id \
119 ) \
120 SELECT chain.id, chain.source_version_id, chain.parent_node_id, chain.kind, chain.name, \
121 chain.order_index, d.id AS document_id \
122 FROM chain LEFT JOIN document d ON d.node_id = chain.id \
123 WHERE chain.depth > 0 ORDER BY chain.depth",
124 )
125 .bind(node_id)
126 .fetch_all(pool)
127 .await?;
128 rows.into_iter()
129 .map(|r| {
130 let kind: NodeKind = serde_json::from_value(serde_json::Value::String(r.kind))
131 .map_err(|e| crate::error::StoreError::Json(e.to_string()))?;
132 Ok(ParentNode {
133 id: r.id,
134 source_version_id: r.source_version_id,
135 parent_node_id: r.parent_node_id,
136 kind,
137 name: r.name,
138 order_index: r.order_index,
139 document_id: r.document_id,
140 })
141 })
142 .collect()
143}
144
145pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result<Node> {
151 let row = sqlx::query_as::<_, NodeRow>(
152 "SELECT id, source_version_id, parent_node_id, kind, name, order_index, created_at \
153 FROM node WHERE id = $1",
154 )
155 .bind(id)
156 .fetch_one(pool)
157 .await?;
158 row.try_into()
159}
160
161#[derive(sqlx::FromRow)]
162struct NodeRow {
163 id: Uuid,
164 source_version_id: Uuid,
165 parent_node_id: Option<Uuid>,
166 kind: String,
167 name: String,
168 order_index: i32,
169 created_at: OffsetDateTime,
170}
171
172impl TryFrom<NodeRow> for Node {
173 type Error = crate::error::StoreError;
174
175 fn try_from(r: NodeRow) -> std::result::Result<Self, Self::Error> {
176 let kind: NodeKind = serde_json::from_value(serde_json::Value::String(r.kind))
177 .map_err(|e| crate::error::StoreError::Json(e.to_string()))?;
178 Ok(Self {
179 id: r.id,
180 source_version_id: r.source_version_id,
181 parent_node_id: r.parent_node_id,
182 kind,
183 name: r.name,
184 order_index: r.order_index,
185 created_at: r.created_at,
186 })
187 }
188}