feat(data): rewrite all repositories to use EF Core ClaudeDoDbContext
Replace raw ADO.NET implementations with EF Core LINQ queries and ExecuteUpdate/ExecuteDelete for bulk operations. TaskRepository preserves FlipAllRunningToFailedAsync(reason) signature and keeps raw SQL for the atomic queue claim (UPDATE...RETURNING). GetByListAsync alias kept for backwards compat. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,157 +1,89 @@
|
||||
using ClaudeDo.Data.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ClaudeDo.Data.Repositories;
|
||||
|
||||
public sealed class ListRepository
|
||||
{
|
||||
private readonly SqliteConnectionFactory _factory;
|
||||
private readonly ClaudeDoDbContext _context;
|
||||
|
||||
public ListRepository(SqliteConnectionFactory factory) => _factory = factory;
|
||||
public ListRepository(ClaudeDoDbContext context) => _context = context;
|
||||
|
||||
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);
|
||||
_context.Lists.Add(entity);
|
||||
await _context.SaveChangesAsync(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);
|
||||
_context.Lists.Update(entity);
|
||||
await _context.SaveChangesAsync(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);
|
||||
await _context.Lists.Where(l => l.Id == listId).ExecuteDeleteAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<ListEntity?> 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);
|
||||
return await _context.Lists.FirstOrDefaultAsync(l => l.Id == listId, ct);
|
||||
}
|
||||
|
||||
public async Task<List<ListEntity>> 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<ListEntity>();
|
||||
while (await reader.ReadAsync(ct))
|
||||
result.Add(ReadList(reader));
|
||||
return result;
|
||||
return await _context.Lists.OrderBy(l => l.CreatedAt).ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<List<TagEntity>> 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<TagEntity>();
|
||||
while (await reader.ReadAsync(ct))
|
||||
result.Add(new TagEntity { Id = reader.GetInt64(0), Name = reader.GetString(1) });
|
||||
return result;
|
||||
return await _context.Lists
|
||||
.Where(l => l.Id == listId)
|
||||
.SelectMany(l => l.Tags)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
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);
|
||||
var list = await _context.Lists.Include(l => l.Tags).FirstAsync(l => l.Id == listId, ct);
|
||||
var tag = await _context.Tags.FindAsync([tagId], ct);
|
||||
if (tag is not null && !list.Tags.Any(t => t.Id == tagId))
|
||||
{
|
||||
list.Tags.Add(tag);
|
||||
await _context.SaveChangesAsync(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);
|
||||
var list = await _context.Lists.Include(l => l.Tags).FirstAsync(l => l.Id == listId, ct);
|
||||
var tag = list.Tags.FirstOrDefault(t => t.Id == tagId);
|
||||
if (tag is not null)
|
||||
{
|
||||
list.Tags.Remove(tag);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ListConfigEntity?> 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);
|
||||
return await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == listId, ct);
|
||||
}
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync(ct);
|
||||
if (!await reader.ReadAsync(ct)) return null;
|
||||
return new ListConfigEntity
|
||||
public async Task SetConfigAsync(ListConfigEntity config, CancellationToken ct = default)
|
||||
{
|
||||
var existing = await _context.ListConfigs.FirstOrDefaultAsync(c => c.ListId == config.ListId, ct);
|
||||
if (existing is null)
|
||||
{
|
||||
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),
|
||||
};
|
||||
_context.ListConfigs.Add(config);
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.Model = config.Model;
|
||||
existing.SystemPrompt = config.SystemPrompt;
|
||||
existing.AgentPath = config.AgentPath;
|
||||
}
|
||||
await _context.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user