1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use serde_json::{Map, Value};
6
7use super::mongo::{MongoBackedStore, MongoConnection};
8use super::{DatabaseManager, ResourceRow};
9use super::{FilterSet, OrmResourceQuery, PageRequest, SearchParam, SortParam, SqlConnection};
10use shaperail_core::{
11 DatabaseEngine, EndpointSpec, NamedDatabaseConfig, ResourceDefinition, ShaperailError,
12};
13
14#[async_trait]
16pub trait ResourceStore: Send + Sync {
17 fn resource_name(&self) -> &str;
18
19 async fn find_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError>;
20
21 async fn find_all(
22 &self,
23 endpoint: &EndpointSpec,
24 filters: &FilterSet,
25 search: Option<&SearchParam>,
26 sort: &SortParam,
27 page: &PageRequest,
28 ) -> Result<(Vec<ResourceRow>, Value), ShaperailError>;
29
30 async fn insert(&self, data: &Map<String, Value>) -> Result<ResourceRow, ShaperailError>;
31
32 async fn update_by_id(
33 &self,
34 id: &uuid::Uuid,
35 data: &Map<String, Value>,
36 ) -> Result<ResourceRow, ShaperailError>;
37
38 async fn soft_delete_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError>;
39
40 async fn hard_delete_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError>;
41}
42
43pub type StoreRegistry = Arc<HashMap<String, Arc<dyn ResourceStore>>>;
44
45pub struct OrmBackedStore {
47 resource: Arc<ResourceDefinition>,
48 connection: SqlConnection,
49}
50
51impl OrmBackedStore {
52 pub fn new(resource: Arc<ResourceDefinition>, connection: SqlConnection) -> Self {
53 Self {
54 resource,
55 connection,
56 }
57 }
58
59 fn orm(&self) -> OrmResourceQuery<'_> {
60 OrmResourceQuery::new(self.resource.as_ref(), &self.connection)
61 }
62}
63
64#[async_trait]
65impl ResourceStore for OrmBackedStore {
66 fn resource_name(&self) -> &str {
67 &self.resource.resource
68 }
69
70 async fn find_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError> {
71 self.orm().find_by_id(id).await
72 }
73
74 async fn find_all(
75 &self,
76 _endpoint: &EndpointSpec,
77 filters: &FilterSet,
78 search: Option<&SearchParam>,
79 sort: &SortParam,
80 page: &PageRequest,
81 ) -> Result<(Vec<ResourceRow>, Value), ShaperailError> {
82 self.orm().find_all(filters, search, sort, page).await
83 }
84
85 async fn insert(&self, data: &Map<String, Value>) -> Result<ResourceRow, ShaperailError> {
86 self.orm().insert(data).await
87 }
88
89 async fn update_by_id(
90 &self,
91 id: &uuid::Uuid,
92 data: &Map<String, Value>,
93 ) -> Result<ResourceRow, ShaperailError> {
94 self.orm().update_by_id(id, data).await
95 }
96
97 async fn soft_delete_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError> {
98 self.orm().soft_delete_by_id(id).await
99 }
100
101 async fn hard_delete_by_id(&self, id: &uuid::Uuid) -> Result<ResourceRow, ShaperailError> {
102 self.orm().hard_delete_by_id(id).await
103 }
104}
105
106pub fn build_orm_store_registry(
109 manager: &DatabaseManager,
110 resources: &[ResourceDefinition],
111) -> Result<StoreRegistry, ShaperailError> {
112 let mut stores: HashMap<String, Arc<dyn ResourceStore>> = HashMap::new();
113 for resource in resources {
114 let conn = manager
115 .sql_for_resource(resource.db.as_ref())
116 .ok_or_else(|| {
117 ShaperailError::Internal(format!(
118 "No SQL connection for resource '{}' (db: {:?})",
119 resource.resource, resource.db
120 ))
121 })?;
122 let store = OrmBackedStore::new(Arc::new(resource.clone()), conn);
123 stores.insert(resource.resource.clone(), Arc::new(store));
124 }
125 Ok(Arc::new(stores))
126}
127
128pub async fn build_multi_store_registry(
133 manager: &DatabaseManager,
134 mongo_connections: &HashMap<String, MongoConnection>,
135 databases: &indexmap::IndexMap<String, NamedDatabaseConfig>,
136 resources: &[ResourceDefinition],
137) -> Result<StoreRegistry, ShaperailError> {
138 let mut stores: HashMap<String, Arc<dyn ResourceStore>> = HashMap::new();
139
140 for resource in resources {
141 let db_name = resource.db.as_deref().unwrap_or("default");
142 let engine = databases
143 .get(db_name)
144 .map(|cfg| cfg.engine)
145 .unwrap_or(DatabaseEngine::Postgres);
146
147 if engine == DatabaseEngine::MongoDB {
148 let mongo = mongo_connections.get(db_name).ok_or_else(|| {
149 ShaperailError::Internal(format!(
150 "No MongoDB connection for resource '{}' (db: '{db_name}')",
151 resource.resource
152 ))
153 })?;
154 mongo.ensure_collection(resource).await?;
156 let store = MongoBackedStore::new(Arc::new(resource.clone()), mongo.clone());
157 stores.insert(resource.resource.clone(), Arc::new(store));
158 } else {
159 let conn = manager
160 .sql_for_resource(resource.db.as_ref())
161 .ok_or_else(|| {
162 ShaperailError::Internal(format!(
163 "No SQL connection for resource '{}' (db: '{db_name}')",
164 resource.resource
165 ))
166 })?;
167 let store = OrmBackedStore::new(Arc::new(resource.clone()), conn);
168 stores.insert(resource.resource.clone(), Arc::new(store));
169 }
170 }
171 Ok(Arc::new(stores))
172}