diff --git a/src/ClaudeDo.Data/Repositories/DailyNoteRepository.cs b/src/ClaudeDo.Data/Repositories/DailyNoteRepository.cs new file mode 100644 index 0000000..2d1d94f --- /dev/null +++ b/src/ClaudeDo.Data/Repositories/DailyNoteRepository.cs @@ -0,0 +1,59 @@ +using ClaudeDo.Data.Models; +using Microsoft.EntityFrameworkCore; + +namespace ClaudeDo.Data.Repositories; + +public sealed class DailyNoteRepository +{ + private readonly ClaudeDoDbContext _context; + + public DailyNoteRepository(ClaudeDoDbContext context) => _context = context; + + public async Task> ListByDayAsync(DateOnly day, CancellationToken ct = default) => + await _context.DailyNotes.AsNoTracking() + .Where(n => n.Date == day) + .OrderBy(n => n.SortOrder) + .ToListAsync(ct); + + public async Task> ListBetweenAsync( + DateOnly start, DateOnly end, CancellationToken ct = default) => + await _context.DailyNotes.AsNoTracking() + .Where(n => n.Date >= start && n.Date <= end) + .OrderBy(n => n.Date).ThenBy(n => n.SortOrder) + .ToListAsync(ct); + + public async Task AddAsync(DateOnly day, string text, CancellationToken ct = default) + { + var nextOrder = await _context.DailyNotes + .Where(n => n.Date == day) + .Select(n => (int?)n.SortOrder) + .MaxAsync(ct) ?? -1; + + var note = new DailyNoteEntity + { + Date = day, + Text = text, + SortOrder = nextOrder + 1, + CreatedAt = DateTime.UtcNow, + }; + _context.DailyNotes.Add(note); + await _context.SaveChangesAsync(ct); + return note; + } + + public async Task UpdateAsync(string id, string text, CancellationToken ct = default) + { + var row = await _context.DailyNotes.FirstOrDefaultAsync(n => n.Id == id, ct); + if (row is null) return; + row.Text = text; + await _context.SaveChangesAsync(ct); + } + + public async Task DeleteAsync(string id, CancellationToken ct = default) + { + var row = await _context.DailyNotes.FirstOrDefaultAsync(n => n.Id == id, ct); + if (row is null) return; + _context.DailyNotes.Remove(row); + await _context.SaveChangesAsync(ct); + } +} diff --git a/tests/ClaudeDo.Worker.Tests/Repositories/DailyNoteRepositoryTests.cs b/tests/ClaudeDo.Worker.Tests/Repositories/DailyNoteRepositoryTests.cs new file mode 100644 index 0000000..a067a31 --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/Repositories/DailyNoteRepositoryTests.cs @@ -0,0 +1,67 @@ +using ClaudeDo.Data.Models; +using ClaudeDo.Data.Repositories; +using ClaudeDo.Worker.Tests.Infrastructure; + +namespace ClaudeDo.Worker.Tests.Repositories; + +public class DailyNoteRepositoryTests : IDisposable +{ + private readonly DbFixture _db = new(); + public void Dispose() => _db.Dispose(); + + [Fact] + public async Task Add_AssignsIncrementingSortOrder_WithinDay() + { + var day = new DateOnly(2026, 6, 1); + using (var ctx = _db.CreateContext()) + { + var repo = new DailyNoteRepository(ctx); + await repo.AddAsync(day, "first"); + await repo.AddAsync(day, "second"); + } + using var read = _db.CreateContext(); + var notes = await new DailyNoteRepository(read).ListByDayAsync(day); + Assert.Equal(new[] { "first", "second" }, notes.Select(n => n.Text)); + Assert.Equal(new[] { 0, 1 }, notes.Select(n => n.SortOrder)); + } + + [Fact] + public async Task ListBetween_ReturnsOnlyInRange_OrderedByDateThenSort() + { + using (var ctx = _db.CreateContext()) + { + var repo = new DailyNoteRepository(ctx); + await repo.AddAsync(new DateOnly(2026, 5, 31), "before"); + await repo.AddAsync(new DateOnly(2026, 6, 1), "in-a"); + await repo.AddAsync(new DateOnly(2026, 6, 2), "in-b"); + await repo.AddAsync(new DateOnly(2026, 6, 5), "after"); + } + using var read = _db.CreateContext(); + var notes = await new DailyNoteRepository(read) + .ListBetweenAsync(new DateOnly(2026, 6, 1), new DateOnly(2026, 6, 2)); + Assert.Equal(new[] { "in-a", "in-b" }, notes.Select(n => n.Text)); + } + + [Fact] + public async Task Update_And_Delete_Work() + { + var day = new DateOnly(2026, 6, 1); + string id; + using (var ctx = _db.CreateContext()) + { + var n = await new DailyNoteRepository(ctx).AddAsync(day, "orig"); + id = n.Id; + } + using (var ctx = _db.CreateContext()) + await new DailyNoteRepository(ctx).UpdateAsync(id, "edited"); + using (var ctx = _db.CreateContext()) + { + var notes = await new DailyNoteRepository(ctx).ListByDayAsync(day); + Assert.Equal("edited", notes.Single().Text); + } + using (var ctx = _db.CreateContext()) + await new DailyNoteRepository(ctx).DeleteAsync(id); + using var read = _db.CreateContext(); + Assert.Empty(await new DailyNoteRepository(read).ListByDayAsync(day)); + } +}