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,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}