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::{Connection, params};
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}