using System.ComponentModel; using trakker.Models; namespace trakker.Data { /// /// Provides data access methods for the entity. /// This class encapsulates database operations such as upsert, delete and ad-hoc /// SQL execution for tasks. It inherits from which /// provides connection management. /// internal class TaskData(string connectionString) : DataAccess(connectionString) { public trakker.Models.Task Get(string? taskId = null) { var results = new List(); string whereClause = "1 = 1"; if (taskId != null) { whereClause = "task_id = $task_id"; } string sql = $@" SELECT task_id, project_id, title, description, status, priority, due_date, estimated_hours, actual_hours, hourly_rate, parent_task_id, created_at, updated_at FROM tasks WHERE {whereClause} ORDER BY priority DESC, due_date ASC, created_at DESC ; "; using var conn = OpenConnection(); using var cmd = conn.CreateCommand(); cmd.CommandText = sql; if (taskId != null) { cmd.Parameters.AddWithValue("$task_id", taskId); } using var reader = cmd.ExecuteReader(); var _var1 = reader.GetOrdinal("task_id"); var _var2 = reader.GetOrdinal("project_id"); var _var3 = reader.GetOrdinal("title"); var _var4 = reader.GetOrdinal("description"); var _var5 = reader.GetOrdinal("status"); var _var6 = reader.GetOrdinal("priority"); var _var7 = reader.GetOrdinal("due_date"); var _var8 = reader.GetOrdinal("estimated_hours"); var _var9 = reader.GetOrdinal("actual_hours"); var _var10 = reader.GetOrdinal("hourly_rate"); var _var11 = reader.GetOrdinal("parent_task_id"); var _var12 = reader.GetOrdinal("created_at"); var _var13 = reader.GetOrdinal("updated_at"); while (reader.Read()) { results.Add(new trakker.Models.Task { TaskId = reader.GetString(_var1), ProjectId = reader.GetString(_var2), Title = reader.GetString(_var3), Description = reader.GetString(_var4), Status = reader.GetString(_var5), Priority = reader.GetString(_var6), DueDate = reader.IsDBNull(_var7) ? null : reader.GetDateTime(_var7), EstimatedHours = reader.IsDBNull(_var8) ? null : reader.GetDouble(_var8), ActualHours = reader.IsDBNull(_var9) ? null : reader.GetDouble(_var9), HourlyRate = reader.IsDBNull(_var10) ? null : reader.GetDouble(_var10), ParentTaskId = reader.IsDBNull(_var11) ? null : reader.GetString(_var11), CreatedAt = reader.IsDBNull(_var12) ? null : reader.GetDateTime(_var12), UpdatedAt = reader.IsDBNull(_var13) ? null : reader.GetDateTime(_var13), }); } return results.FirstOrDefault() ?? new trakker.Models.Task(); } /// /// Inserts a new task record or updates an existing one (upsert) using /// the provided model. This method executes /// a single SQL statement inside a transaction and will commit on /// success or roll back on failure. /// /// The model to insert or update. Must not be null. /// /// The SQL statement uses an ON CONFLICT clause to perform the update when a /// matching task_id already exists. Parameter names correspond to the /// task model property names. /// public void Upsert(trakker.Models.Task task) { const string sql = @" INSERT INTO tasks ( task_id, project_id, title, description, status, priority, due_date, estimated_hours, actual_hours, hourly_rate, parent_task_id ) VALUES ( $task_id, $project_id, $title, $description, $status, $priority, $due_date, $estimated_hours, $actual_hours, $hourly_rate, $parent_task_id ) ON CONFLICT(task_id) DO UPDATE SET project_id = excluded.project_id, title = excluded.title, description = excluded.description, status = excluded.status, priority = excluded.priority, due_date = excluded.due_date, estimated_hours = excluded.estimated_hours, actual_hours = excluded.actual_hours, hourly_rate = excluded.hourly_rate, parent_task_id = excluded.parent_task_id, updated_at = CURRENT_TIMESTAMP ; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$task_id", task.TaskId); cmd.Parameters.AddWithValue("$project_id", task.ProjectId); cmd.Parameters.AddWithValue("$title", task.Title); cmd.Parameters.AddWithValue("$description", task.Description); cmd.Parameters.AddWithValue("$status", task.Status); cmd.Parameters.AddWithValue("$priority", task.Priority); cmd.Parameters.AddWithValue("$due_date", task.DueDate); cmd.Parameters.AddWithValue("$estimated_hours", task.EstimatedHours); cmd.Parameters.AddWithValue("$actual_hours", task.ActualHours); cmd.Parameters.AddWithValue("$hourly_rate", task.HourlyRate); cmd.Parameters.AddWithValue("$parent_task_id", task.ParentTaskId); cmd.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; } } /// /// Deletes the task with the specified from the /// database. /// /// The identifier of the task to delete. /// An optional integer representing any scalar value returned by the /// command executed after deletion (if applicable). May be null. /// /// The method executes within a transaction. The current implementation attempts /// to read a scalar value after the delete; that value depends on surrounding /// database triggers or commands and may be null. /// public int? Delete(string taskId) { const string sql = @" DELETE FROM tasks WHERE task_id = $task_id ; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); int? result = 0; try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$task_id", taskId); cmd.ExecuteNonQuery(); } using var idCmd = conn.CreateCommand(); idCmd.Transaction = tx; result = (int?)idCmd.ExecuteScalar() ; tx.Commit(); } catch { tx.Rollback(); throw; } return result; } public List GetFS() { var results = new List(); string sql = $@" SELECT a.project_id AS guid, a.name AS node, '/' AS parent, a.hourly_rate, a.project_id FROM projects a UNION SELECT b.task_id AS guid, b.title AS node, b.parent_task_id AS parent, b.hourly_rate, b.project_id FROM tasks b ; "; using var conn = OpenConnection(); using var cmd = conn.CreateCommand(); cmd.CommandText = sql; using var reader = cmd.ExecuteReader(); var _var1 = reader.GetOrdinal("guid"); var _var2 = reader.GetOrdinal("node"); var _var3 = reader.GetOrdinal("parent"); var _var4 = reader.GetOrdinal("hourly_rate"); var _var5 = reader.GetOrdinal("project_id"); while (reader.Read()) { results.Add(new TaskFS { GUID = reader.GetString(_var1), Node = reader.GetString(_var2), Parent = reader.GetString(_var3), HourlyRate = reader.IsDBNull(_var4) ? (double?)null : reader.GetDouble(_var4), ProjectId = reader.GetString(_var5) }); } return results; } public void SaveComment(TaskComment taskComment) { const string sql = @" INSERT INTO task_comments ( task_comment_id, task_id, comment ) VALUES ( $task_comment_id, $task_id, $comment ) ON CONFLICT(task_comment_id) DO UPDATE SET task_id = excluded.task_id, comment = excluded.comment ; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$task_comment_id", taskComment.TaskCommentId); cmd.Parameters.AddWithValue("$task_id", taskComment.TaskId); cmd.Parameters.AddWithValue("$comment", taskComment.Comment); cmd.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; } } public List GetComments(string taskId) { var results = new List(); string whereClause = "1 = 1"; if (taskId != null) { whereClause = "task_id = $task_id"; } string sql = $@" SELECT task_comment_id, task_id, comment, created_at FROM task_comments WHERE {whereClause} ORDER BY created_at DESC ; "; using var conn = OpenConnection(); using var cmd = conn.CreateCommand(); cmd.CommandText = sql; using var reader = cmd.ExecuteReader(); var _var1 = reader.GetOrdinal("task_comment_id"); var _var2 = reader.GetOrdinal("task_id"); var _var3 = reader.GetOrdinal("comment"); var _var4 = reader.GetOrdinal("created_at"); while (reader.Read()) { results.Add(new TaskComment { TaskCommentId = reader.GetString(_var1), TaskId = reader.GetString(_var2), Comment = reader.GetString(_var3), CreatedAt = reader.IsDBNull(_var4) ? (DateTime?)null : reader.GetDateTime(_var4) }); } return results; } } }