Skip to main content

max / goingson

5.2 KB · 177 lines History Blame Raw
1 use super::{DateTime, ProjectId, Result, UserId, Utc, Uuid, async_trait};
2
3 /// Type of item in search results.
4 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5 #[serde(rename_all = "lowercase")]
6 pub enum SearchResultType {
7 /// A task item.
8 Task,
9 /// An email item.
10 Email,
11 /// A project item.
12 Project,
13 /// A calendar event.
14 Event,
15 /// A contact.
16 Contact,
17 }
18
19 /// A single search result item with ranking information.
20 #[derive(Debug, Clone, serde::Serialize)]
21 pub struct SearchResultItem {
22 /// Item ID.
23 pub id: Uuid,
24 /// Type of the result.
25 pub result_type: SearchResultType,
26 /// Display title.
27 pub title: String,
28 /// Text snippet showing match context.
29 pub snippet: Option<String>,
30 /// Associated project ID if any.
31 pub project_id: Option<ProjectId>,
32 /// Associated project name if any.
33 pub project_name: Option<String>,
34 /// Relevance rank (higher is better).
35 pub rank: f64,
36 }
37
38 /// Search query with filtering and pagination options.
39 #[derive(Debug, Clone, Default)]
40 pub struct SearchQuery {
41 /// The search terms (FTS text after filters extracted).
42 pub query: String,
43 /// Filter to specific result types.
44 pub types: Option<Vec<SearchResultType>>,
45 /// Filter to a specific project by ID.
46 pub project_id: Option<ProjectId>,
47 /// Filter to a specific project by name (partial match).
48 pub project_name: Option<String>,
49 /// Filter to items on or after this date.
50 pub date_from: Option<DateTime<Utc>>,
51 /// Filter to items on or before this date.
52 pub date_to: Option<DateTime<Utc>>,
53 /// Maximum results to return.
54 pub limit: Option<i64>,
55 /// Offset for pagination.
56 pub offset: Option<i64>,
57 /// `is:` filters for time/state conditions.
58 pub is_filters: Vec<crate::search_parser::IsFilter>,
59 /// Priority filter (`priority:high` etc).
60 pub priority: Option<crate::models::Priority>,
61 /// Tags to include (`tag:name`).
62 pub tags_include: Vec<String>,
63 /// Tags to exclude (`-tag:name`).
64 pub tags_exclude: Vec<String>,
65 }
66
67 impl SearchQuery {
68 /// Creates a new search query with default options.
69 pub fn new(query: impl Into<String>) -> Self {
70 Self {
71 query: query.into(),
72 types: None,
73 project_id: None,
74 project_name: None,
75 date_from: None,
76 date_to: None,
77 limit: Some(50),
78 offset: None,
79 is_filters: Vec::new(),
80 priority: None,
81 tags_include: Vec::new(),
82 tags_exclude: Vec::new(),
83 }
84 }
85
86 /// Filters results to specific types.
87 #[must_use]
88 pub fn with_types(mut self, types: Vec<SearchResultType>) -> Self {
89 self.types = Some(types);
90 self
91 }
92
93 /// Filters results to a specific project by ID.
94 #[must_use]
95 pub fn with_project(mut self, project_id: ProjectId) -> Self {
96 self.project_id = Some(project_id);
97 self
98 }
99
100 /// Filters results to a specific project by name.
101 #[must_use]
102 pub fn with_project_name(mut self, name: impl Into<String>) -> Self {
103 self.project_name = Some(name.into());
104 self
105 }
106
107 /// Sets the maximum number of results. Negative values are clamped to 0:
108 /// SQLite treats a negative LIMIT as unbounded, so an unclamped negative
109 /// would silently disable the cap instead of enforcing it.
110 #[must_use]
111 pub fn with_limit(mut self, limit: i64) -> Self {
112 self.limit = Some(limit.max(0));
113 self
114 }
115
116 /// Sets the pagination offset. Negative values are clamped to 0.
117 #[must_use]
118 pub fn with_offset(mut self, offset: i64) -> Self {
119 self.offset = Some(offset.max(0));
120 self
121 }
122
123 /// Filters results to items on or after this date.
124 #[must_use]
125 pub fn with_date_from(mut self, date: DateTime<Utc>) -> Self {
126 self.date_from = Some(date);
127 self
128 }
129
130 /// Filters results to items on or before this date.
131 #[must_use]
132 pub fn with_date_to(mut self, date: DateTime<Utc>) -> Self {
133 self.date_to = Some(date);
134 self
135 }
136
137 /// Adds `is:` filters.
138 #[must_use]
139 pub fn with_is_filters(mut self, filters: Vec<crate::search_parser::IsFilter>) -> Self {
140 self.is_filters = filters;
141 self
142 }
143
144 /// Sets priority filter.
145 #[must_use]
146 pub fn with_priority(mut self, priority: crate::models::Priority) -> Self {
147 self.priority = Some(priority);
148 self
149 }
150
151 /// Sets tags to include.
152 #[must_use]
153 pub fn with_tags_include(mut self, tags: Vec<String>) -> Self {
154 self.tags_include = tags;
155 self
156 }
157
158 /// Sets tags to exclude.
159 #[must_use]
160 pub fn with_tags_exclude(mut self, tags: Vec<String>) -> Self {
161 self.tags_exclude = tags;
162 self
163 }
164 }
165
166 /// Repository for full-text search across all content types.
167 #[async_trait]
168 pub trait SearchRepository: Send + Sync {
169 /// Searches across all indexed content using FTS.
170 /// Returns (results, total_count) where total_count is the pre-pagination count.
171 async fn search(
172 &self,
173 user_id: UserId,
174 query: SearchQuery,
175 ) -> Result<(Vec<SearchResultItem>, usize)>;
176 }
177