Skip to main content

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