kasl/db/templates.rs
1//! Task template system for efficient task creation and workflow standardization.
2//!
3//! Provides functionality for managing reusable task templates that streamline
4//! the creation of frequently used tasks. Templates store predefined task
5//! configurations including names, descriptions, and completion status.
6//!
7//! ## Features
8//!
9//! - **Template Management**: Create, update, delete, and query template definitions
10//! - **Search Capabilities**: Find templates by name or task content with fuzzy matching
11//! - **Workflow Standardization**: Consistent task creation for repetitive workflows
12//! - **Content Reuse**: Store commonly used task patterns for rapid deployment
13//! - **Validation Support**: Ensure template uniqueness and data integrity
14//!
15//! ## Usage
16//!
17//! ```rust,no_run
18//! # fn main() -> anyhow::Result<()> {
19//! use kasl::db::templates::{Templates, TaskTemplate};
20//!
21//! let mut templates = Templates::new()?;
22//! let template = TaskTemplate::new(
23//! "daily-standup".to_string(),
24//! "Prepare for daily standup".to_string(),
25//! "Review yesterday's work and plan today".to_string(),
26//! 50
27//! );
28//! templates.create(&template)?;
29//! # Ok(())
30//! # }
31//! ```
32
33use crate::db::db::Db;
34use crate::libs::messages::Message;
35use crate::msg_error_anyhow;
36use anyhow::Result;
37use rusqlite::{Connection, params};
38use serde::{Deserialize, Serialize};
39
40/// SQL schema for the task templates table.
41///
42/// Defines the structure for storing reusable task templates with unique
43/// naming, content specifications, and automatic timestamp tracking.
44/// The schema supports efficient lookups and ensures template uniqueness.
45const SCHEMA_TEMPLATES: &str = "CREATE TABLE IF NOT EXISTS task_templates (
46 id INTEGER PRIMARY KEY,
47 name TEXT NOT NULL UNIQUE,
48 task_name TEXT NOT NULL,
49 comment TEXT,
50 completeness INTEGER DEFAULT 100,
51 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
52)";
53
54/// Insert a new template with complete field specification.
55///
56/// Creates a template record with automatic ID assignment and timestamp
57/// generation. Template names must be unique across the system.
58const INSERT_TEMPLATE: &str = "INSERT INTO task_templates (name, task_name, comment, completeness) VALUES (?1, ?2, ?3, ?4)";
59
60/// Update an existing template's content while preserving metadata.
61///
62/// Modifies template properties (task_name, comment, completeness) while
63/// maintaining the unique template name and preserving creation timestamp.
64const UPDATE_TEMPLATE: &str = "UPDATE task_templates SET task_name = ?2, comment = ?3, completeness = ?4 WHERE name = ?1";
65
66/// Delete a template record by its unique name identifier.
67///
68/// Permanently removes a template definition from the system. Template
69/// deletion does not affect tasks that were previously created from the template.
70const DELETE_TEMPLATE: &str = "DELETE FROM task_templates WHERE name = ?1";
71
72/// Retrieve all templates ordered alphabetically by template name.
73///
74/// Provides a complete list of available templates sorted for consistent
75/// display in user interfaces and selection dialogs.
76const SELECT_ALL_TEMPLATES: &str = "SELECT * FROM task_templates ORDER BY name";
77
78/// Find a specific template by its unique name identifier.
79///
80/// Enables direct template lookup for validation, editing, and task
81/// creation operations using the human-readable template name.
82const SELECT_TEMPLATE_BY_NAME: &str = "SELECT * FROM task_templates WHERE name = ?1";
83
84/// Search templates by name or task content with fuzzy matching.
85///
86/// Supports partial matching across both template names and task names
87/// to help users discover relevant templates quickly. Uses SQL LIKE
88/// operator for flexible pattern matching.
89const SEARCH_TEMPLATES: &str = "SELECT * FROM task_templates WHERE name LIKE ?1 OR task_name LIKE ?1 ORDER BY name";
90
91/// Represents a reusable task template with predefined values.
92///
93/// A task template serves as a blueprint for creating tasks with consistent
94/// properties. Templates encapsulate common task patterns and provide
95/// default values that can be used directly or modified during task creation.
96///
97/// ## Design Philosophy
98///
99/// Templates separate the template identity (name) from the actual task
100/// content (task_name), allowing for readable template names while
101/// maintaining flexible task naming. This enables templates like
102/// "daily-standup" to create tasks named "Prepare for daily standup meeting".
103///
104/// ## Field Relationships
105///
106/// - **name**: Identifies the template itself (e.g., "code-review-template")
107/// - **task_name**: The actual task title when created (e.g., "Review PR #123")
108/// - **comment**: Default description for context and instructions
109/// - **completeness**: Default progress state (useful for different task types)
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct TaskTemplate {
112 /// Database-assigned unique identifier.
113 ///
114 /// Automatically set when the template is saved to the database.
115 /// Used for internal references and database operations.
116 pub id: Option<i32>,
117
118 /// Unique template identifier for human-readable reference.
119 ///
120 /// This is the name users will use to identify and select templates.
121 /// Must be unique across all templates and should be descriptive
122 /// of the template's purpose (e.g., "daily-standup", "code-review").
123 pub name: String,
124
125 /// The actual task name that will be used when creating tasks.
126 ///
127 /// This becomes the task title when a task is created from this template.
128 /// Can contain placeholders or generic descriptions that users can
129 /// customize during task creation.
130 pub task_name: String,
131
132 /// Default description or notes for tasks created from this template.
133 ///
134 /// Provides context, instructions, or checklist items that help
135 /// users understand what the task involves. Can include formatting
136 /// and detailed guidance for task execution.
137 pub comment: String,
138
139 /// Default completion percentage when tasks are created from this template.
140 ///
141 /// Allows templates to specify different starting completion states:
142 /// - 0: For tasks that start completely unfinished
143 /// - 50: For tasks that are partially pre-completed
144 /// - 100: For tasks that are considered complete when created (e.g., automated tasks)
145 pub completeness: i32,
146
147 /// Timestamp when the template was created.
148 ///
149 /// Automatically managed by the database for audit trails and
150 /// chronological sorting. Used for template management and history.
151 pub created_at: Option<String>,
152}
153
154impl TaskTemplate {
155 /// Creates a new task template with the specified properties.
156 ///
157 /// This constructor creates a template object ready for database insertion.
158 /// The ID and creation timestamp are automatically assigned when the
159 /// template is saved using the `Templates::create()` method.
160 ///
161 /// # Arguments
162 ///
163 /// * `name` - Unique identifier for the template (user-facing name)
164 /// * `task_name` - Default task title for tasks created from this template
165 /// * `comment` - Default description/instructions for template-based tasks
166 /// * `completeness` - Default completion percentage (0-100)
167 ///
168 /// # Returns
169 ///
170 /// Returns a new `TaskTemplate` instance ready for database operations.
171 ///
172 /// # Example
173 ///
174 /// ```rust
175 /// use kasl::db::templates::TaskTemplate;
176 ///
177 /// let template = TaskTemplate::new(
178 /// "morning-routine".to_string(),
179 /// "Complete morning routine".to_string(),
180 /// "Check emails, review calendar, plan day".to_string(),
181 /// 25 // Start at 25% since some prep is already done
182 /// );
183 /// # let _ = template;
184 /// ```
185 ///
186 /// # Design Considerations
187 ///
188 /// - Template names should be URL-safe and easy to type
189 /// - Task names can be more descriptive and user-friendly
190 /// - Comments should provide actionable guidance
191 /// - Completion values should reflect realistic starting states
192 pub fn new(name: String, task_name: String, comment: String, completeness: i32) -> Self {
193 Self {
194 id: None,
195 name,
196 task_name,
197 comment,
198 completeness,
199 created_at: None,
200 }
201 }
202}
203
204/// Database manager for task template operations and lifecycle management.
205///
206/// The `Templates` struct provides a comprehensive interface for managing
207/// task templates, including creation, modification, deletion, and discovery
208/// operations. It handles database connections and ensures data integrity
209/// for all template-related operations.
210///
211/// ## Functionality Overview
212///
213/// - **CRUD Operations**: Complete Create, Read, Update, Delete support
214/// - **Search and Discovery**: Flexible template finding and filtering
215/// - **Validation**: Ensures template uniqueness and data consistency
216/// - **Batch Operations**: Efficient handling of multiple template operations
217///
218/// ## Database Integration
219///
220/// The struct automatically manages database schema initialization and
221/// provides transaction support for complex operations. It's designed
222/// to work seamlessly with the broader kasl database ecosystem.
223pub struct Templates {
224 /// Direct database connection for template operations.
225 ///
226 /// Provides optimized access to the task_templates table with
227 /// proper transaction support and connection management.
228 conn: Connection,
229}
230
231impl Templates {
232 /// Creates a new Templates manager and initializes the database schema.
233 ///
234 /// This constructor establishes a database connection, ensures the
235 /// task_templates table exists with the proper schema, and prepares
236 /// the manager for template operations. Schema creation is idempotent
237 /// and integrates with the migration system.
238 ///
239 /// # Returns
240 ///
241 /// Returns a new `Templates` instance ready for template management,
242 /// or an error if database initialization fails.
243 ///
244 /// # Example
245 ///
246 /// ```rust,no_run
247 /// # fn main() -> anyhow::Result<()> {
248 /// use kasl::db::templates::Templates;
249 ///
250 /// let mut templates = Templates::new()?;
251 /// // Ready for template operations
252 /// # Ok(())
253 /// # }
254 /// ```
255 ///
256 /// # Database Integration
257 ///
258 /// The templates table is officially created by migration v2, but this
259 /// method ensures the table exists even in non-standard initialization
260 /// scenarios. This provides robustness across different deployment patterns.
261 ///
262 /// # Errors
263 ///
264 /// Returns an error if:
265 /// - Database connection cannot be established
266 /// - Schema creation fails due to permissions or corruption
267 /// - Migration system encounters errors during initialization
268 pub fn new() -> Result<Self> {
269 let db = Db::new()?;
270
271 // Ensure the templates table exists (migration v2 creates it officially)
272 db.conn.execute(SCHEMA_TEMPLATES, [])?;
273
274 Ok(Templates { conn: db.conn })
275 }
276
277 /// Creates a new template in the database and validates uniqueness.
278 ///
279 /// This method inserts a new template record with the provided properties,
280 /// automatically assigning a unique ID and creation timestamp. Template
281 /// names must be unique across the entire system.
282 ///
283 /// ## Validation Process
284 ///
285 /// - **Uniqueness Check**: Ensures template name doesn't already exist
286 /// - **Data Validation**: Validates required fields and constraints
287 /// - **Integrity Enforcement**: Maintains database consistency rules
288 /// - **Automatic Fields**: Sets ID and timestamp automatically
289 ///
290 /// # Arguments
291 ///
292 /// * `template` - Template object containing the properties to store
293 ///
294 /// # Returns
295 ///
296 /// Returns `Ok(())` if the template is created successfully, or an error
297 /// if creation fails due to uniqueness violations or database issues.
298 ///
299 /// # Example
300 ///
301 /// ```rust,no_run
302 /// # fn main() -> anyhow::Result<()> {
303 /// use kasl::db::templates::{Templates, TaskTemplate};
304 ///
305 /// let mut templates = Templates::new()?;
306 /// let template = TaskTemplate::new(
307 /// "weekly-review".to_string(),
308 /// "Weekly review and planning".to_string(),
309 /// "Review accomplishments and plan next week".to_string(),
310 /// 0
311 /// );
312 /// templates.create(&template)?;
313 /// # Ok(())
314 /// # }
315 /// ```
316 ///
317 /// # Errors
318 ///
319 /// Returns an error if:
320 /// - A template with the same name already exists
321 /// - Required fields are missing or invalid
322 /// - Database constraints are violated
323 /// - Connection or transaction failures occur
324 pub fn create(&mut self, template: &TaskTemplate) -> Result<()> {
325 let affected = self.conn.execute(
326 INSERT_TEMPLATE,
327 params![template.name, template.task_name, template.comment, template.completeness],
328 )?;
329
330 if affected == 0 {
331 return Err(msg_error_anyhow!(Message::TemplateCreateFailed));
332 }
333
334 Ok(())
335 }
336
337 /// Update an existing template
338 pub fn update(&mut self, template: &TaskTemplate) -> Result<()> {
339 let affected = self.conn.execute(
340 UPDATE_TEMPLATE,
341 params![template.name, template.task_name, template.comment, template.completeness],
342 )?;
343
344 if affected == 0 {
345 return Err(msg_error_anyhow!(Message::TemplateNotFound(template.name.clone())));
346 }
347
348 Ok(())
349 }
350
351 /// Deletes a template permanently from the database.
352 ///
353 /// This method removes a template definition from the system. Note that
354 /// deleting a template does not affect tasks that were previously created
355 /// from that template - those tasks remain independent and unchanged.
356 ///
357 /// ## Impact Scope
358 ///
359 /// - **Template Removal**: Permanently removes the template definition
360 /// - **Task Preservation**: Existing tasks created from template are unaffected
361 /// - **Reference Cleanup**: Removes template from discovery and selection
362 /// - **Audit Trail**: Operation can be tracked through database logs
363 ///
364 /// # Arguments
365 ///
366 /// * `name` - Unique name identifier of the template to delete
367 ///
368 /// # Returns
369 ///
370 /// Returns `Ok(())` if deletion succeeds, or an error if the operation
371 /// fails. Deleting a non-existent template is not considered an error.
372 ///
373 /// # Example
374 ///
375 /// ```rust,no_run
376 /// # use kasl::db::templates::Templates;
377 /// # fn main() -> anyhow::Result<()> {
378 /// let mut templates = Templates::new()?;
379 /// templates.delete("obsolete-template")?;
380 /// # Ok(())
381 /// # }
382 /// ```
383 ///
384 /// # Safety Considerations
385 ///
386 /// - Deletion is immediate and permanent
387 /// - No confirmation prompts at this level
388 /// - Callers should implement appropriate confirmation workflows
389 /// - Consider exporting template definitions before deletion
390 pub fn delete(&mut self, name: &str) -> Result<()> {
391 let affected = self.conn.execute(DELETE_TEMPLATE, params![name])?;
392
393 if affected == 0 {
394 return Err(msg_error_anyhow!(Message::TemplateNotFound(name.to_string())));
395 }
396
397 Ok(())
398 }
399
400 /// Retrieves all templates from the database ordered alphabetically.
401 ///
402 /// This method returns a complete list of all template definitions
403 /// sorted by name for consistent display in user interfaces and
404 /// selection menus. The list includes all template properties and
405 /// metadata for comprehensive template management.
406 ///
407 /// # Returns
408 ///
409 /// Returns a vector of all template records ordered by name, or an
410 /// error if the database query fails.
411 ///
412 /// # Example
413 ///
414 /// ```rust,no_run
415 /// # use kasl::db::templates::Templates;
416 /// # fn main() -> anyhow::Result<()> {
417 /// let mut templates = Templates::new()?;
418 /// let all_templates = templates.get_all()?;
419 /// for template in all_templates {
420 /// println!("Template: {} -> {}", template.name, template.task_name);
421 /// }
422 /// # Ok(())
423 /// # }
424 /// ```
425 ///
426 /// # Performance Considerations
427 ///
428 /// This method loads all templates into memory, which is efficient for
429 /// typical template collections but may need pagination for very large
430 /// numbers of templates (hundreds or thousands).
431 ///
432 /// # Use Cases
433 ///
434 /// - Template selection interfaces
435 /// - Administrative template management
436 /// - Template export and backup operations
437 /// - System configuration displays
438 pub fn get_all(&mut self) -> Result<Vec<TaskTemplate>> {
439 let mut stmt = self.conn.prepare(SELECT_ALL_TEMPLATES)?;
440 let template_iter = stmt.query_map([], |row| {
441 Ok(TaskTemplate {
442 id: row.get(0)?,
443 name: row.get(1)?,
444 task_name: row.get(2)?,
445 comment: row.get(3)?,
446 completeness: row.get(4)?,
447 created_at: row.get(5)?,
448 })
449 })?;
450
451 let mut templates = Vec::new();
452 for template in template_iter {
453 templates.push(template?);
454 }
455 Ok(templates)
456 }
457
458 /// Finds a specific template by its unique name identifier.
459 ///
460 /// This method performs exact name matching to retrieve a specific
461 /// template definition. It's commonly used for template editing,
462 /// validation, and task creation operations that reference templates
463 /// by their user-friendly names.
464 ///
465 /// # Arguments
466 ///
467 /// * `name` - Exact name of the template to retrieve (case-sensitive)
468 ///
469 /// # Returns
470 ///
471 /// Returns `Some(TaskTemplate)` if found, `None` if no matching template
472 /// exists, or an error if the database query fails.
473 ///
474 /// # Example
475 ///
476 /// ```rust,no_run
477 /// # use kasl::db::templates::Templates;
478 /// # fn main() -> anyhow::Result<()> {
479 /// let mut templates = Templates::new()?;
480 /// if let Some(template) = templates.get("daily-standup")? {
481 /// println!("Found template: {}", template.task_name);
482 /// // Use template to create a task with predefined values
483 /// } else {
484 /// println!("Template 'daily-standup' not found");
485 /// }
486 /// # Ok(())
487 /// # }
488 /// ```
489 ///
490 /// # Name Matching
491 ///
492 /// - Performs exact, case-sensitive string matching
493 /// - Does not support wildcards or partial matching (use search() for that)
494 /// - Whitespace and special characters must match exactly
495 /// - Template names are typically lowercase with hyphens for readability
496 pub fn get(&mut self, name: &str) -> Result<Option<TaskTemplate>> {
497 let mut stmt = self.conn.prepare(SELECT_TEMPLATE_BY_NAME)?;
498 let mut template_iter = stmt.query_map(params![name], |row| {
499 Ok(TaskTemplate {
500 id: row.get(0)?,
501 name: row.get(1)?,
502 task_name: row.get(2)?,
503 comment: row.get(3)?,
504 completeness: row.get(4)?,
505 created_at: row.get(5)?,
506 })
507 })?;
508
509 // Extract the first (and only) result from the iterator
510 match template_iter.next() {
511 Some(Ok(template)) => Ok(Some(template)),
512 Some(Err(e)) => Err(e.into()),
513 None => Ok(None),
514 }
515 }
516
517 /// Searches templates by name or task content with flexible pattern matching.
518 ///
519 /// This method provides fuzzy search capabilities across both template names
520 /// and task names, enabling users to discover relevant templates when they
521 /// don't remember exact names. It's particularly useful for interactive
522 /// template selection and discovery workflows.
523 ///
524 /// ## Search Behavior
525 ///
526 /// - **Partial Matching**: Matches substrings within template or task names
527 /// - **Case Insensitive**: Search is case-insensitive for user convenience
528 /// - **Multiple Fields**: Searches both template name and task_name fields
529 /// - **Sorted Results**: Returns results ordered alphabetically by template name
530 ///
531 /// ## Search Algorithm
532 ///
533 /// Uses SQL LIKE operator with wildcard patterns, which provides:
534 /// - Substring matching anywhere in the field
535 /// - Efficient execution using database indices
536 /// - Consistent behavior across different database backends
537 ///
538 /// # Arguments
539 ///
540 /// * `query` - Search term to match against template and task names
541 ///
542 /// # Returns
543 ///
544 /// Returns a vector of matching templates ordered by name, or an error
545 /// if the database query fails. Empty vector if no matches found.
546 ///
547 /// # Example
548 ///
549 /// ```rust,no_run
550 /// # use kasl::db::templates::Templates;
551 /// # fn main() -> anyhow::Result<()> {
552 /// let mut templates = Templates::new()?;
553 ///
554 /// // Find all templates related to "review"
555 /// let review_templates = templates.search("review")?;
556 ///
557 /// // Find templates containing "standup" anywhere
558 /// let standup_templates = templates.search("standup")?;
559 ///
560 /// for template in review_templates {
561 /// println!("Found: {} -> {}", template.name, template.task_name);
562 /// }
563 /// # let _ = standup_templates;
564 /// # Ok(())
565 /// # }
566 /// ```
567 ///
568 /// # Performance Notes
569 ///
570 /// - LIKE queries may be slower on very large template collections
571 /// - Consider indexing if template search becomes a performance bottleneck
572 /// - Results are limited by available memory for the returned vector
573 ///
574 /// # Use Cases
575 ///
576 /// - Interactive template selection interfaces
577 /// - Command-line template discovery
578 /// - Autocomplete and suggestion systems
579 /// - Template organization and categorization
580 pub fn search(&mut self, query: &str) -> Result<Vec<TaskTemplate>> {
581 // Prepare search pattern with wildcard matching
582 let search_pattern = format!("%{}%", query);
583 let mut stmt = self.conn.prepare(SEARCH_TEMPLATES)?;
584
585 let template_iter = stmt.query_map(params![search_pattern], |row| {
586 Ok(TaskTemplate {
587 id: row.get(0)?,
588 name: row.get(1)?,
589 task_name: row.get(2)?,
590 comment: row.get(3)?,
591 completeness: row.get(4)?,
592 created_at: row.get(5)?,
593 })
594 })?;
595
596 // Collect all matching templates
597 let mut templates = Vec::new();
598 for template in template_iter {
599 templates.push(template?);
600 }
601
602 Ok(templates)
603 }
604
605 /// Checks if a template with the specified name exists in the database.
606 ///
607 /// This convenience method efficiently determines template existence
608 /// without retrieving the full template data. It's useful for validation
609 /// before performing operations that require existing templates or for
610 /// preventing duplicate template creation.
611 ///
612 /// # Arguments
613 ///
614 /// * `name` - Unique name identifier of the template to check
615 ///
616 /// # Returns
617 ///
618 /// Returns `true` if the template exists, `false` otherwise, or an error
619 /// if the database query fails.
620 ///
621 /// # Example
622 ///
623 /// ```rust,no_run
624 /// # use kasl::db::templates::{Templates, TaskTemplate};
625 /// # fn main() -> anyhow::Result<()> {
626 /// let mut templates = Templates::new()?;
627 /// if templates.exists("daily-standup")? {
628 /// println!("Template already exists");
629 /// } else {
630 /// // Safe to create new template with this name
631 /// let template = TaskTemplate::new(
632 /// "daily-standup".to_string(),
633 /// "Prepare for daily standup".to_string(),
634 /// "Review progress and plan".to_string(),
635 /// 0
636 /// );
637 /// templates.create(&template)?;
638 /// }
639 /// # Ok(())
640 /// # }
641 /// ```
642 ///
643 /// # Performance
644 ///
645 /// This method is more efficient than retrieving the full template when
646 /// only existence verification is needed. It internally uses the `get()`
647 /// method but only checks the result without processing template data.
648 ///
649 /// # Use Cases
650 ///
651 /// - Template creation validation
652 /// - User interface state management
653 /// - Batch operation preprocessing
654 /// - Configuration file validation
655 pub fn exists(&mut self, name: &str) -> Result<bool> {
656 Ok(self.get(name)?.is_some())
657 }
658}