using ClaudeDo.Data.Models; using Microsoft.Data.Sqlite; namespace ClaudeDo.Data.Repositories; public sealed class ListRepository { private readonly SqliteConnectionFactory _factory; public ListRepository(SqliteConnectionFactory factory) => _factory = factory; public async Task AddAsync(ListEntity entity, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ INSERT INTO lists (id, name, created_at, working_dir, default_commit_type) VALUES (@id, @name, @created_at, @working_dir, @default_commit_type) """; cmd.Parameters.AddWithValue("@id", entity.Id); cmd.Parameters.AddWithValue("@name", entity.Name); cmd.Parameters.AddWithValue("@created_at", entity.CreatedAt.ToString("o")); cmd.Parameters.AddWithValue("@working_dir", (object?)entity.WorkingDir ?? DBNull.Value); cmd.Parameters.AddWithValue("@default_commit_type", entity.DefaultCommitType); await cmd.ExecuteNonQueryAsync(ct); } public async Task UpdateAsync(ListEntity entity, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ UPDATE lists SET name = @name, working_dir = @working_dir, default_commit_type = @default_commit_type WHERE id = @id """; cmd.Parameters.AddWithValue("@id", entity.Id); cmd.Parameters.AddWithValue("@name", entity.Name); cmd.Parameters.AddWithValue("@working_dir", (object?)entity.WorkingDir ?? DBNull.Value); cmd.Parameters.AddWithValue("@default_commit_type", entity.DefaultCommitType); await cmd.ExecuteNonQueryAsync(ct); } public async Task DeleteAsync(string listId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "DELETE FROM lists WHERE id = @id"; cmd.Parameters.AddWithValue("@id", listId); await cmd.ExecuteNonQueryAsync(ct); } public async Task GetByIdAsync(string listId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT id, name, created_at, working_dir, default_commit_type FROM lists WHERE id = @id"; cmd.Parameters.AddWithValue("@id", listId); await using var reader = await cmd.ExecuteReaderAsync(ct); if (!await reader.ReadAsync(ct)) return null; return ReadList(reader); } public async Task> GetAllAsync(CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT id, name, created_at, working_dir, default_commit_type FROM lists ORDER BY created_at"; await using var reader = await cmd.ExecuteReaderAsync(ct); var result = new List(); while (await reader.ReadAsync(ct)) result.Add(ReadList(reader)); return result; } public async Task> GetTagsAsync(string listId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ SELECT t.id, t.name FROM tags t JOIN list_tags lt ON lt.tag_id = t.id WHERE lt.list_id = @list_id """; cmd.Parameters.AddWithValue("@list_id", listId); await using var reader = await cmd.ExecuteReaderAsync(ct); var result = new List(); while (await reader.ReadAsync(ct)) result.Add(new TagEntity { Id = reader.GetInt64(0), Name = reader.GetString(1) }); return result; } public async Task AddTagAsync(string listId, long tagId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "INSERT OR IGNORE INTO list_tags (list_id, tag_id) VALUES (@list_id, @tag_id)"; cmd.Parameters.AddWithValue("@list_id", listId); cmd.Parameters.AddWithValue("@tag_id", tagId); await cmd.ExecuteNonQueryAsync(ct); } public async Task RemoveTagAsync(string listId, long tagId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "DELETE FROM list_tags WHERE list_id = @list_id AND tag_id = @tag_id"; cmd.Parameters.AddWithValue("@list_id", listId); cmd.Parameters.AddWithValue("@tag_id", tagId); await cmd.ExecuteNonQueryAsync(ct); } public async Task GetConfigAsync(string listId, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT list_id, model, system_prompt, agent_path FROM list_config WHERE list_id = @list_id"; cmd.Parameters.AddWithValue("@list_id", listId); await using var reader = await cmd.ExecuteReaderAsync(ct); if (!await reader.ReadAsync(ct)) return null; return new ListConfigEntity { ListId = reader.GetString(0), Model = reader.IsDBNull(1) ? null : reader.GetString(1), SystemPrompt = reader.IsDBNull(2) ? null : reader.GetString(2), AgentPath = reader.IsDBNull(3) ? null : reader.GetString(3), }; } public async Task SetConfigAsync(ListConfigEntity entity, CancellationToken ct = default) { await using var conn = _factory.Open(); await using var cmd = conn.CreateCommand(); cmd.CommandText = """ INSERT OR REPLACE INTO list_config (list_id, model, system_prompt, agent_path) VALUES (@list_id, @model, @system_prompt, @agent_path) """; cmd.Parameters.AddWithValue("@list_id", entity.ListId); cmd.Parameters.AddWithValue("@model", (object?)entity.Model ?? DBNull.Value); cmd.Parameters.AddWithValue("@system_prompt", (object?)entity.SystemPrompt ?? DBNull.Value); cmd.Parameters.AddWithValue("@agent_path", (object?)entity.AgentPath ?? DBNull.Value); await cmd.ExecuteNonQueryAsync(ct); } private static ListEntity ReadList(SqliteDataReader reader) => new() { Id = reader.GetString(0), Name = reader.GetString(1), CreatedAt = DateTime.Parse(reader.GetString(2)), WorkingDir = reader.IsDBNull(3) ? null : reader.GetString(3), DefaultCommitType = reader.GetString(4), }; }