Skip to main content

veta_core/
service.rs

1use crate::{CreateNote, Database, Error, Note, NoteQuery, NoteSummary, TagCount, UpdateNote};
2
3/// The main service that contains all business logic.
4/// Generic over the database implementation.
5pub struct VetaService<D: Database> {
6    db: D,
7}
8
9impl<D: Database> VetaService<D> {
10    pub fn new(db: D) -> Self {
11        Self { db }
12    }
13
14    /// Add a new note.
15    pub async fn add_note(
16        &self,
17        title: String,
18        body: String,
19        tags: Vec<String>,
20        references: Vec<String>,
21    ) -> Result<i64, Error> {
22        // Validation
23        let title = title.trim().to_string();
24        if title.is_empty() {
25            return Err(Error::Validation("title cannot be empty".into()));
26        }
27
28        // Normalize tags: lowercase, trim, deduplicate, remove empty
29        let mut tags: Vec<String> = tags
30            .into_iter()
31            .map(|t| t.trim().to_lowercase())
32            .filter(|t| !t.is_empty())
33            .collect();
34        tags.sort();
35        tags.dedup();
36
37        // Normalize references: trim, deduplicate, remove empty
38        let mut references: Vec<String> = references
39            .into_iter()
40            .map(|r| r.trim().to_string())
41            .filter(|r| !r.is_empty())
42            .collect();
43        references.dedup();
44
45        self.db
46            .add_note(CreateNote {
47                title,
48                body,
49                tags,
50                references,
51            })
52            .await
53    }
54
55    /// Get a note by ID.
56    pub async fn get_note(&self, id: i64) -> Result<Option<Note>, Error> {
57        self.db.get_note(id).await
58    }
59
60    /// List notes with optional filters.
61    pub async fn list_notes(&self, query: NoteQuery) -> Result<Vec<NoteSummary>, Error> {
62        // Apply default limit if not specified (0 means no limit)
63        let query = NoteQuery {
64            limit: match query.limit {
65                Some(0) => None,
66                Some(n) => Some(n),
67                None => Some(100),
68            },
69            ..query
70        };
71        let notes = self.db.list_notes(query).await?;
72        Ok(notes.into_iter().map(|n| n.to_summary(60)).collect())
73    }
74
75    /// Count notes matching the query (ignores limit).
76    pub async fn count_notes(&self, query: NoteQuery) -> Result<i64, Error> {
77        self.db.count_notes(query).await
78    }
79
80    /// Update an existing note.
81    pub async fn update_note(&self, id: i64, update: UpdateNote) -> Result<bool, Error> {
82        // Validate title if provided
83        if let Some(ref title) = update.title {
84            if title.trim().is_empty() {
85                return Err(Error::Validation("title cannot be empty".into()));
86            }
87        }
88
89        // Normalize tags if provided
90        let update = UpdateNote {
91            title: update.title.map(|t| t.trim().to_string()),
92            body: update.body,
93            tags: update.tags.map(|tags| {
94                let mut tags: Vec<String> = tags
95                    .into_iter()
96                    .map(|t| t.trim().to_lowercase())
97                    .filter(|t| !t.is_empty())
98                    .collect();
99                tags.sort();
100                tags.dedup();
101                tags
102            }),
103            references: update.references.map(|refs| {
104                let mut refs: Vec<String> = refs
105                    .into_iter()
106                    .map(|r| r.trim().to_string())
107                    .filter(|r| !r.is_empty())
108                    .collect();
109                refs.dedup();
110                refs
111            }),
112        };
113
114        self.db.update_note(id, update).await
115    }
116
117    /// Delete a note by ID.
118    pub async fn delete_note(&self, id: i64) -> Result<bool, Error> {
119        self.db.delete_note(id).await
120    }
121
122    /// List all tags with counts.
123    pub async fn list_tags(&self) -> Result<Vec<TagCount>, Error> {
124        self.db.list_tags().await
125    }
126
127    /// Search notes by pattern.
128    pub async fn grep(
129        &self,
130        pattern: &str,
131        tags: Option<Vec<String>>,
132        case_sensitive: bool,
133    ) -> Result<Vec<NoteSummary>, Error> {
134        let notes = self
135            .db
136            .grep(pattern, tags.as_deref(), case_sensitive)
137            .await?;
138        Ok(notes.into_iter().map(|n| n.to_summary(60)).collect())
139    }
140}