1use anyhow::Result;
6use std::path::PathBuf;
7use uuid::Uuid;
8
9use crate::models::{QueueEntry, Requirement, RequirementsStore, User};
10
11#[derive(Debug, Clone)]
13pub struct VersionConflict {
14 pub id: Uuid,
16 pub expected_version: i64,
18 pub current_version: i64,
20 pub display_id: String,
22}
23
24impl std::fmt::Display for VersionConflict {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(
27 f,
28 "Version conflict for {}: expected version {}, but current version is {}. \
29 Another user may have modified this record.",
30 self.display_id, self.expected_version, self.current_version
31 )
32 }
33}
34
35impl std::error::Error for VersionConflict {}
36
37#[derive(Debug)]
39pub enum UpdateResult {
40 Success,
42 Conflict(VersionConflict),
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum BackendType {
49 Yaml,
51 Sqlite,
53 Postgres,
55 Git,
57}
58
59impl std::fmt::Display for BackendType {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 match self {
62 BackendType::Yaml => write!(f, "YAML"),
63 BackendType::Sqlite => write!(f, "SQLite"),
64 BackendType::Postgres => write!(f, "PostgreSQL"),
65 BackendType::Git => write!(f, "Git"),
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct DatabaseConfig {
73 pub path: PathBuf,
75 pub backend_type: BackendType,
77 pub wal_mode: bool,
79}
80
81impl Default for DatabaseConfig {
82 fn default() -> Self {
83 Self {
84 path: PathBuf::from("requirements.yaml"),
85 backend_type: BackendType::Yaml,
86 wal_mode: true,
87 }
88 }
89}
90
91pub trait DatabaseBackend: Send + Sync {
101 fn backend_type(&self) -> BackendType;
103
104 fn path(&self) -> &std::path::Path;
106
107 fn load(&self) -> Result<RequirementsStore>;
113
114 fn save(&self, store: &RequirementsStore) -> Result<()>;
116
117 fn update_atomically<F>(&self, update_fn: F) -> Result<RequirementsStore>
120 where
121 F: FnOnce(&mut RequirementsStore),
122 Self: Sized,
123 {
124 let mut store = self.load()?;
125 update_fn(&mut store);
126 self.save(&store)?;
127 Ok(store)
128 }
129
130 fn get_requirement(&self, id: &Uuid) -> Result<Option<Requirement>> {
136 let store = self.load()?;
137 Ok(store.requirements.iter().find(|r| &r.id == id).cloned())
138 }
139
140 fn get_requirement_by_spec_id(&self, spec_id: &str) -> Result<Option<Requirement>> {
142 let store = self.load()?;
143 Ok(store
144 .requirements
145 .iter()
146 .find(|r| r.spec_id.as_deref() == Some(spec_id))
147 .cloned())
148 }
149
150 fn list_requirements(&self, include_archived: bool) -> Result<Vec<Requirement>> {
152 let store = self.load()?;
153 Ok(store
154 .requirements
155 .iter()
156 .filter(|r| include_archived || !r.archived)
157 .cloned()
158 .collect())
159 }
160
161 fn add_requirement(&self, requirement: Requirement) -> Result<Requirement> {
166 let mut store = self.load()?;
167 let mut req = requirement;
168
169 if req.spec_id.is_none() {
171 req.spec_id = Some(format!("SPEC-{:03}", store.next_spec_number));
172 store.next_spec_number += 1;
173 }
174
175 store.requirements.push(req.clone());
176 self.save(&store)?;
177 Ok(req)
178 }
179
180 fn update_requirement(&self, requirement: &Requirement) -> Result<()> {
182 let mut store = self.load()?;
183 if let Some(pos) = store
184 .requirements
185 .iter()
186 .position(|r| r.id == requirement.id)
187 {
188 store.requirements[pos] = requirement.clone();
189 self.save(&store)?;
190 Ok(())
191 } else {
192 anyhow::bail!("Requirement not found: {}", requirement.id)
193 }
194 }
195
196 fn update_requirement_versioned(&self, requirement: &Requirement) -> Result<UpdateResult> {
204 self.update_requirement(requirement)?;
206 Ok(UpdateResult::Success)
207 }
208
209 fn delete_requirement(&self, id: &Uuid) -> Result<()> {
211 let mut store = self.load()?;
212 let original_len = store.requirements.len();
213 store.requirements.retain(|r| &r.id != id);
214 if store.requirements.len() == original_len {
215 anyhow::bail!("Requirement not found: {}", id)
216 }
217 self.save(&store)
218 }
219
220 fn get_user(&self, id: &Uuid) -> Result<Option<User>> {
226 let store = self.load()?;
227 Ok(store.users.iter().find(|u| &u.id == id).cloned())
228 }
229
230 fn get_user_by_handle(&self, handle: &str) -> Result<Option<User>> {
232 let store = self.load()?;
233 Ok(store.users.iter().find(|u| u.handle == handle).cloned())
234 }
235
236 fn list_users(&self, include_archived: bool) -> Result<Vec<User>> {
238 let store = self.load()?;
239 Ok(store
240 .users
241 .iter()
242 .filter(|u| include_archived || !u.archived)
243 .cloned()
244 .collect())
245 }
246
247 fn add_user(&self, user: User) -> Result<User> {
249 let mut store = self.load()?;
250 let mut u = user;
251
252 if u.spec_id.is_none() {
254 u.spec_id = Some(store.next_meta_id(crate::models::META_PREFIX_USER));
255 }
256
257 store.users.push(u.clone());
258 self.save(&store)?;
259 Ok(u)
260 }
261
262 fn update_user(&self, user: &User) -> Result<()> {
264 let mut store = self.load()?;
265 if let Some(pos) = store.users.iter().position(|u| u.id == user.id) {
266 store.users[pos] = user.clone();
267 self.save(&store)?;
268 Ok(())
269 } else {
270 anyhow::bail!("User not found: {}", user.id)
271 }
272 }
273
274 fn delete_user(&self, id: &Uuid) -> Result<()> {
276 let mut store = self.load()?;
277 let original_len = store.users.len();
278 store.users.retain(|u| &u.id != id);
279 if store.users.len() == original_len {
280 anyhow::bail!("User not found: {}", id)
281 }
282 self.save(&store)
283 }
284
285 fn get_name(&self) -> Result<String> {
291 Ok(self.load()?.name)
292 }
293
294 fn set_name(&self, name: &str) -> Result<()> {
296 let mut store = self.load()?;
297 store.name = name.to_string();
298 self.save(&store)
299 }
300
301 fn get_title(&self) -> Result<String> {
303 Ok(self.load()?.title)
304 }
305
306 fn set_title(&self, title: &str) -> Result<()> {
308 let mut store = self.load()?;
309 store.title = title.to_string();
310 self.save(&store)
311 }
312
313 fn get_description(&self) -> Result<String> {
315 Ok(self.load()?.description)
316 }
317
318 fn set_description(&self, description: &str) -> Result<()> {
320 let mut store = self.load()?;
321 store.description = description.to_string();
322 self.save(&store)
323 }
324
325 fn create_baseline(
332 &self,
333 name: String,
334 description: Option<String>,
335 created_by: String,
336 ) -> Result<crate::models::Baseline> {
337 let mut store = self.load()?;
338 let baseline = store.create_baseline(name, description, created_by).clone();
339 self.save(&store)?;
340 Ok(baseline)
341 }
342
343 fn list_baselines(&self) -> Result<Vec<crate::models::Baseline>> {
345 let store = self.load()?;
346 Ok(store.baselines.clone())
347 }
348
349 fn get_baseline(&self, id: &Uuid) -> Result<Option<crate::models::Baseline>> {
351 let store = self.load()?;
352 Ok(store.baselines.iter().find(|b| &b.id == id).cloned())
353 }
354
355 fn delete_baseline(&self, id: &Uuid) -> Result<bool> {
357 let mut store = self.load()?;
358 let deleted = store.delete_baseline(id);
359 if deleted {
360 self.save(&store)?;
361 }
362 Ok(deleted)
363 }
364
365 fn compare_with_baseline(
367 &self,
368 baseline_id: &Uuid,
369 ) -> Result<Option<crate::models::BaselineComparison>> {
370 let store = self.load()?;
371 Ok(store.compare_with_baseline(baseline_id))
372 }
373
374 fn compare_baselines(
376 &self,
377 source_id: &Uuid,
378 target_id: &Uuid,
379 ) -> Result<Option<crate::models::BaselineComparison>> {
380 let store = self.load()?;
381 Ok(store.compare_baselines(source_id, target_id))
382 }
383
384 fn get_store_version(&self) -> Result<i64> {
393 Ok(self.load()?.store_version)
394 }
395
396 fn exists(&self) -> bool {
398 self.path().exists()
399 }
400
401 fn create_if_not_exists(&self) -> Result<()> {
403 if !self.exists() {
404 self.save(&RequirementsStore::new())?;
405 }
406 Ok(())
407 }
408
409 fn queue_list(&self, _user_id: &str, _include_completed: bool) -> Result<Vec<QueueEntry>> {
416 anyhow::bail!("Queue not supported for this backend")
417 }
418
419 fn queue_add(&self, _entry: QueueEntry) -> Result<()> {
421 anyhow::bail!("Queue not supported for this backend")
422 }
423
424 fn queue_remove(&self, _user_id: &str, _requirement_id: &Uuid) -> Result<()> {
426 anyhow::bail!("Queue not supported for this backend")
427 }
428
429 fn queue_reorder(&self, _user_id: &str, _items: &[(Uuid, i64)]) -> Result<()> {
431 anyhow::bail!("Queue not supported for this backend")
432 }
433
434 fn queue_clear(&self, _user_id: &str, _completed_only: bool) -> Result<()> {
437 anyhow::bail!("Queue not supported for this backend")
438 }
439
440 fn stats(&self) -> Result<DatabaseStats> {
446 let store = self.load()?;
447 Ok(DatabaseStats {
448 requirement_count: store.requirements.len(),
449 user_count: store.users.len(),
450 feature_count: store.features.len(),
451 backend_type: self.backend_type(),
452 })
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct DatabaseStats {
459 pub requirement_count: usize,
460 pub user_count: usize,
461 pub feature_count: usize,
462 pub backend_type: BackendType,
463}