1use anyhow::{Result, bail};
2use chrono::{DateTime, Utc};
3
4use crate::db::Database;
5use crate::ids::{TaskId, WorkspaceId};
6use crate::recurrence::RecurrenceSeriesId;
7use crate::refs::DisplayRefContext;
8use crate::types::Task;
9
10mod database_stats;
11mod dependencies;
12mod details;
13mod doctor;
14pub mod fragments;
15mod hydration;
16mod projects;
17mod recent_actions;
18mod recurrence;
19mod search;
20mod sidebar;
21mod sorting;
22mod sync_history;
23mod tasks;
24#[cfg(test)]
25mod test_support;
26mod types;
27
28pub use database_stats::{DatabaseStats, DatabaseStatsPriorityCounts, DatabaseStatsStatusCounts};
29pub use dependencies::{TaskDependencyItem, TaskDependencySummary};
30pub(crate) use dependencies::{task_dependency_summary, task_dependency_summary_with_display_refs};
31pub use details::{TaskDetail, TaskDetailConflict};
32pub(crate) use details::{conflict_display_value, task_detail, task_detail_with_display_refs};
33pub use doctor::WorkspaceTaskCounts;
34pub(crate) use doctor::{unresolved_conflict_count, workspace_task_counts};
35pub(crate) use projects::list_project_items_in_workspace;
36pub(crate) use recent_actions::list_recent_actions_in_workspace;
37pub(crate) use recurrence::task_recurrence_summaries;
38pub use search::{
39 SearchMatchedField, TaskSearchPreviewResultSet, TaskSearchQuery, TaskSearchResult,
40};
41pub(crate) use search::{
42 search_task_items_in_workspace, search_task_occurrence_items_in_workspace,
43 search_task_preview_set_in_workspace,
44};
45pub(crate) use sidebar::sidebar_counts_for_scope_in_workspace;
46pub use sync_history::SyncHistoryStats;
47pub(crate) use sync_history::sync_history_stats;
48pub(crate) use tasks::{
49 list_task_items_in_workspace, list_task_items_with_display_refs,
50 list_task_summary_items_in_workspace,
51};
52pub use types::RecentActionTarget;
53pub use types::{
54 AttachmentMetadata, EpicRollup, MetadataFilter, ProjectListItem, RecentActionItem,
55 RecurrenceCounts, RecurrenceHistoryEntry, RecurrenceHistoryKind, RecurrenceHistoryPage,
56 RecurrenceOccurrenceLink, RecurrenceReconciliation, RecurrenceSeriesConflict,
57 RecurrenceSeriesDetail, RecurrenceSeriesLifecycleFilter, RecurrenceSeriesListItem,
58 RecurrenceSeriesListQuery, RecurrenceSeriesSummary, RecurrenceTaskGroup, SidebarCounts,
59 SortDirection, TaskAvailabilityFilter, TaskDependencyLink, TaskFilters, TaskIdFilter,
60 TaskListItem, TaskNote, TaskQueryMode, TaskRecurrenceSummary, TaskSort,
61};
62
63pub const MAX_RECURRENCE_HISTORY_LIMIT: usize = 500;
64
65fn validate_search_limit(limit: usize) -> Result<()> {
66 if limit == 0 {
67 bail!("error search-limit-invalid limit=0 min=1 hint=\"pass a limit of 1 or greater\"");
68 }
69 Ok(())
70}
71
72fn validate_recurrence_history_limit(limit: usize) -> Result<()> {
73 if !(1..=MAX_RECURRENCE_HISTORY_LIMIT).contains(&limit) {
74 bail!(
75 "error recurrence-history-limit-invalid limit={} min=1 max={} hint=\"pass a limit between 1 and {}\"",
76 limit,
77 MAX_RECURRENCE_HISTORY_LIMIT,
78 MAX_RECURRENCE_HISTORY_LIMIT
79 );
80 }
81 Ok(())
82}
83
84impl Database {
85 pub async fn reconcile_recurrence_reports_at(
90 &self,
91 workspace_id: &WorkspaceId,
92 at: DateTime<Utc>,
93 ) -> Result<RecurrenceReconciliation> {
94 let (workspace, candidates, incomplete) = {
95 let mut conn = self.acquire_reader().await?;
96 let workspace = crate::workspaces::workspace_for_id(&mut conn, workspace_id).await?;
97 let (candidates, incomplete) =
98 recurrence::recurrence_reconciliation_candidates(&mut conn, workspace_id, at)
99 .await?;
100 (workspace, candidates, incomplete)
101 };
102 let mut report = RecurrenceReconciliation {
103 workspace_id: Some(workspace_id.clone()),
104 examined: candidates.len(),
105 incomplete,
106 ..RecurrenceReconciliation::default()
107 };
108 for series_id in candidates {
109 let result = self
110 .reconcile_recurrence_series(&workspace, &series_id, at)
111 .await?;
112 report.changed += usize::from(result.changed);
113 report.lifecycle_blocked += usize::from(result.lifecycle_blocked);
114 }
115 recurrence::validate_reconciliation(&report)?;
116 Ok(report)
117 }
118
119 pub async fn reconcile_recurrence_reports(
120 &self,
121 workspace_id: &WorkspaceId,
122 ) -> Result<RecurrenceReconciliation> {
123 let at = crate::ids::now_utc();
124 self.reconcile_recurrence_reports_at(workspace_id, at).await
125 }
126
127 pub async fn list_recurrence_series_view_at(
128 &self,
129 workspace_id: &WorkspaceId,
130 at: DateTime<Utc>,
131 query: RecurrenceSeriesListQuery,
132 ) -> Result<Vec<RecurrenceSeriesListItem>> {
133 self.reconcile_recurrence_reports_at(workspace_id, at)
134 .await?;
135 self.list_recurrence_series_view_from_current_projection(workspace_id, query)
136 .await
137 }
138
139 #[doc(hidden)]
143 pub async fn list_recurrence_series_view_from_current_projection(
144 &self,
145 workspace_id: &WorkspaceId,
146 query: RecurrenceSeriesListQuery,
147 ) -> Result<Vec<RecurrenceSeriesListItem>> {
148 let mut conn = self.acquire_reader().await?;
149 recurrence::list_recurrence_series_view(&mut conn, workspace_id, &query).await
150 }
151
152 pub async fn list_recurrence_series_view(
153 &self,
154 workspace_id: &WorkspaceId,
155 query: RecurrenceSeriesListQuery,
156 ) -> Result<Vec<RecurrenceSeriesListItem>> {
157 let at = crate::ids::now_utc();
158 self.list_recurrence_series_view_at(workspace_id, at, query)
159 .await
160 }
161
162 pub async fn list_recurrence_series_at(
163 &self,
164 workspace_id: &WorkspaceId,
165 at: DateTime<Utc>,
166 ) -> Result<Vec<RecurrenceSeriesSummary>> {
167 self.reconcile_recurrence_reports_at(workspace_id, at)
168 .await?;
169 let mut conn = self.acquire_reader().await?;
170 recurrence::list_recurrence_series(&mut conn, workspace_id, at).await
171 }
172
173 pub async fn recurrence_series_detail_at(
174 &self,
175 workspace_id: &WorkspaceId,
176 series_id: &RecurrenceSeriesId,
177 at: DateTime<Utc>,
178 ) -> Result<RecurrenceSeriesDetail> {
179 self.reconcile_recurrence_reports_at(workspace_id, at)
180 .await?;
181 self.recurrence_series_detail_at_from_current_projection(workspace_id, series_id, at)
182 .await
183 }
184
185 #[doc(hidden)]
189 pub async fn recurrence_series_detail_at_from_current_projection(
190 &self,
191 workspace_id: &WorkspaceId,
192 series_id: &RecurrenceSeriesId,
193 at: DateTime<Utc>,
194 ) -> Result<RecurrenceSeriesDetail> {
195 let mut conn = self.acquire_reader().await?;
196 recurrence::recurrence_series_detail(&mut conn, workspace_id, series_id, at).await
197 }
198
199 pub async fn recurrence_history_at(
200 &self,
201 workspace_id: &WorkspaceId,
202 series_id: &RecurrenceSeriesId,
203 at: DateTime<Utc>,
204 offset: usize,
205 limit: usize,
206 ) -> Result<RecurrenceHistoryPage> {
207 validate_recurrence_history_limit(limit)?;
208 self.reconcile_recurrence_reports_at(workspace_id, at)
209 .await?;
210 let mut conn = self.acquire_reader().await?;
211 recurrence::recurrence_history(&mut conn, workspace_id, series_id, at, offset, limit).await
212 }
213
214 pub async fn list_recurrence_series(
215 &self,
216 workspace_id: &WorkspaceId,
217 ) -> Result<Vec<RecurrenceSeriesSummary>> {
218 let at = crate::ids::now_utc();
219 self.list_recurrence_series_at(workspace_id, at).await
220 }
221
222 pub async fn recurrence_series_detail(
223 &self,
224 workspace_id: &WorkspaceId,
225 series_id: &RecurrenceSeriesId,
226 ) -> Result<RecurrenceSeriesDetail> {
227 let at = crate::ids::now_utc();
228 self.recurrence_series_detail_at(workspace_id, series_id, at)
229 .await
230 }
231
232 #[doc(hidden)]
236 pub async fn recurrence_series_detail_from_current_projection(
237 &self,
238 workspace_id: &WorkspaceId,
239 series_id: &RecurrenceSeriesId,
240 ) -> Result<RecurrenceSeriesDetail> {
241 let at = crate::ids::now_utc();
242 self.recurrence_series_detail_at_from_current_projection(workspace_id, series_id, at)
243 .await
244 }
245
246 pub async fn recurrence_history(
247 &self,
248 workspace_id: &WorkspaceId,
249 series_id: &RecurrenceSeriesId,
250 offset: usize,
251 limit: usize,
252 ) -> Result<RecurrenceHistoryPage> {
253 let at = crate::ids::now_utc();
254 self.recurrence_history_at(workspace_id, series_id, at, offset, limit)
255 .await
256 }
257
258 pub async fn list_project_items(
259 &self,
260 workspace_id: &WorkspaceId,
261 ) -> Result<Vec<ProjectListItem>> {
262 self.reconcile_recurrence_reports(workspace_id).await?;
263 self.list_project_items_from_current_projection(workspace_id)
264 .await
265 }
266
267 #[doc(hidden)]
271 pub async fn list_project_items_from_current_projection(
272 &self,
273 workspace_id: &WorkspaceId,
274 ) -> Result<Vec<ProjectListItem>> {
275 let mut conn = self.acquire_reader().await?;
276 list_project_items_in_workspace(&mut conn, workspace_id).await
277 }
278
279 pub async fn list_task_items(
280 &self,
281 workspace_id: &WorkspaceId,
282 filters: TaskFilters,
283 mode: TaskQueryMode,
284 sort: TaskSort,
285 direction: SortDirection,
286 ) -> Result<Vec<TaskListItem>> {
287 self.reconcile_recurrence_reports(workspace_id).await?;
288 self.list_task_items_from_current_projection(workspace_id, filters, mode, sort, direction)
289 .await
290 }
291
292 #[doc(hidden)]
296 pub async fn list_task_items_from_current_projection(
297 &self,
298 workspace_id: &WorkspaceId,
299 filters: TaskFilters,
300 mode: TaskQueryMode,
301 sort: TaskSort,
302 direction: SortDirection,
303 ) -> Result<Vec<TaskListItem>> {
304 let mut conn = self.acquire_reader().await?;
305 list_task_items_in_workspace(&mut conn, workspace_id, filters, mode, sort, direction).await
306 }
307
308 pub async fn list_task_summary_items(
309 &self,
310 workspace_id: &WorkspaceId,
311 filters: TaskFilters,
312 mode: TaskQueryMode,
313 sort: TaskSort,
314 direction: SortDirection,
315 limit: Option<usize>,
316 ) -> Result<Vec<TaskListItem>> {
317 self.reconcile_recurrence_reports(workspace_id).await?;
318 let mut conn = self.acquire_reader().await?;
319 list_task_summary_items_in_workspace(
320 &mut conn,
321 workspace_id,
322 filters,
323 mode,
324 sort,
325 direction,
326 limit,
327 )
328 .await
329 }
330
331 pub async fn sidebar_counts_for_scope(
332 &self,
333 workspace_id: &WorkspaceId,
334 project_key: Option<&str>,
335 ) -> Result<SidebarCounts> {
336 self.reconcile_recurrence_reports(workspace_id).await?;
337 self.sidebar_counts_for_scope_from_current_projection(workspace_id, project_key)
338 .await
339 }
340
341 #[doc(hidden)]
345 pub async fn sidebar_counts_for_scope_from_current_projection(
346 &self,
347 workspace_id: &WorkspaceId,
348 project_key: Option<&str>,
349 ) -> Result<SidebarCounts> {
350 let mut conn = self.acquire_reader().await?;
351 sidebar_counts_for_scope_in_workspace(&mut conn, workspace_id, project_key).await
352 }
353
354 pub async fn list_recent_actions(
355 &self,
356 workspace_id: &WorkspaceId,
357 project_scope: Option<&str>,
358 ) -> Result<Vec<RecentActionItem>> {
359 self.reconcile_recurrence_reports(workspace_id).await?;
360 self.list_recent_actions_from_current_projection(workspace_id, project_scope)
361 .await
362 }
363
364 #[doc(hidden)]
368 pub async fn list_recent_actions_from_current_projection(
369 &self,
370 workspace_id: &WorkspaceId,
371 project_scope: Option<&str>,
372 ) -> Result<Vec<RecentActionItem>> {
373 let mut conn = self.acquire_reader().await?;
374 list_recent_actions_in_workspace(&mut conn, workspace_id, project_scope).await
375 }
376
377 pub async fn search_task_items(
378 &self,
379 workspace_id: &WorkspaceId,
380 query: TaskSearchQuery,
381 ) -> Result<Vec<TaskSearchResult>> {
382 validate_search_limit(query.limit)?;
383 self.reconcile_recurrence_reports(workspace_id).await?;
384 let mut conn = self.acquire_reader().await?;
385 search_task_items_in_workspace(&mut conn, workspace_id, query).await
386 }
387
388 pub async fn search_task_occurrence_items(
389 &self,
390 workspace_id: &WorkspaceId,
391 query: TaskSearchQuery,
392 ) -> Result<Vec<TaskSearchResult>> {
393 validate_search_limit(query.limit)?;
394 self.reconcile_recurrence_reports(workspace_id).await?;
395 let mut conn = self.acquire_reader().await?;
396 search_task_occurrence_items_in_workspace(&mut conn, workspace_id, query).await
397 }
398
399 pub async fn search_task_preview_set(
400 &self,
401 workspace_id: &WorkspaceId,
402 query: TaskSearchQuery,
403 ) -> Result<TaskSearchPreviewResultSet> {
404 validate_search_limit(query.limit)?;
405 self.reconcile_recurrence_reports(workspace_id).await?;
406 self.search_task_preview_set_from_current_projection(workspace_id, query)
407 .await
408 }
409
410 #[doc(hidden)]
414 pub async fn search_task_preview_set_from_current_projection(
415 &self,
416 workspace_id: &WorkspaceId,
417 query: TaskSearchQuery,
418 ) -> Result<TaskSearchPreviewResultSet> {
419 validate_search_limit(query.limit)?;
420 let mut conn = self.acquire_reader().await?;
421 search_task_preview_set_in_workspace(&mut conn, workspace_id, query).await
422 }
423
424 pub async fn task_detail(&self, task: &Task) -> Result<TaskDetail> {
425 self.reconcile_recurrence_reports(&task.workspace_id)
426 .await?;
427 let mut conn = self.acquire_reader().await?;
428 task_detail(&mut conn, task).await
429 }
430
431 pub async fn task_detail_with_display_refs(
432 &self,
433 task: &Task,
434 display_refs: &DisplayRefContext,
435 ) -> Result<TaskDetail> {
436 self.reconcile_recurrence_reports(&task.workspace_id)
437 .await?;
438 let mut conn = self.acquire_reader().await?;
439 task_detail_with_display_refs(&mut conn, task, display_refs).await
440 }
441
442 pub async fn conflict_display_value(
443 &self,
444 workspace_id: &WorkspaceId,
445 field: &str,
446 value: &str,
447 ) -> Result<String> {
448 let mut conn = self.acquire_reader().await?;
449 conflict_display_value(&mut conn, workspace_id, field, value).await
450 }
451
452 pub async fn task_dependency_summary(
453 &self,
454 workspace_id: &WorkspaceId,
455 task_id: &TaskId,
456 ) -> Result<TaskDependencySummary> {
457 let mut conn = self.acquire_reader().await?;
458 task_dependency_summary(&mut conn, workspace_id, task_id).await
459 }
460
461 pub async fn workspace_task_counts(
462 &self,
463 workspace_id: &WorkspaceId,
464 ) -> Result<WorkspaceTaskCounts> {
465 let mut conn = self.acquire_reader().await?;
466 workspace_task_counts(&mut conn, workspace_id).await
467 }
468
469 pub async fn unresolved_conflict_count(&self) -> Result<i64> {
470 let mut conn = self.acquire_reader().await?;
471 unresolved_conflict_count(&mut conn).await
472 }
473
474 pub async fn sync_history_stats(&self) -> Result<SyncHistoryStats> {
475 let mut conn = self.acquire_reader().await?;
476 sync_history_stats(&mut conn).await
477 }
478
479 pub async fn database_stats(
480 &self,
481 workspace: &crate::workspaces::Workspace,
482 ) -> Result<DatabaseStats> {
483 let mut conn = self.acquire_reader().await?;
484 database_stats::database_stats(&mut conn, workspace).await
485 }
486}