diff --git a/src/ClaudeDo.App/Program.cs b/src/ClaudeDo.App/Program.cs index 8d274af..1757351 100644 --- a/src/ClaudeDo.App/Program.cs +++ b/src/ClaudeDo.App/Program.cs @@ -2,6 +2,7 @@ using Avalonia; using ClaudeDo.Data; using ClaudeDo.Data.Git; using ClaudeDo.Data.Repositories; +using ClaudeDo.Data.Seeding; using ClaudeDo.Ui; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels; @@ -28,8 +29,9 @@ sealed class Program using (var scope = services.CreateScope()) { - ClaudeDoDbContext.MigrateAndConfigure( - scope.ServiceProvider.GetRequiredService()); + var db = scope.ServiceProvider.GetRequiredService(); + ClaudeDoDbContext.MigrateAndConfigure(db); + DefaultListsSeeder.SeedAsync(db).GetAwaiter().GetResult(); } try diff --git a/src/ClaudeDo.Data/Seeding/DefaultListsSeeder.cs b/src/ClaudeDo.Data/Seeding/DefaultListsSeeder.cs new file mode 100644 index 0000000..e3ec03d --- /dev/null +++ b/src/ClaudeDo.Data/Seeding/DefaultListsSeeder.cs @@ -0,0 +1,25 @@ +using ClaudeDo.Data.Models; +using Microsoft.EntityFrameworkCore; + +namespace ClaudeDo.Data.Seeding; + +public static class DefaultListsSeeder +{ + private static readonly string[] Defaults = { "My Day", "Important", "Planned" }; + + public static async Task SeedAsync(ClaudeDoDbContext ctx, CancellationToken ct = default) + { + var existing = await ctx.Lists.Select(l => l.Name).ToListAsync(ct); + var now = DateTime.UtcNow; + foreach (var name in Defaults.Where(n => !existing.Contains(n))) + { + ctx.Lists.Add(new ListEntity + { + Id = Guid.NewGuid().ToString("N"), + Name = name, + CreatedAt = now, + }); + } + await ctx.SaveChangesAsync(ct); + } +} diff --git a/src/ClaudeDo.Installer/Steps/InitDatabaseStep.cs b/src/ClaudeDo.Installer/Steps/InitDatabaseStep.cs index 007b36c..3630996 100644 --- a/src/ClaudeDo.Installer/Steps/InitDatabaseStep.cs +++ b/src/ClaudeDo.Installer/Steps/InitDatabaseStep.cs @@ -1,4 +1,5 @@ using ClaudeDo.Data; +using ClaudeDo.Data.Seeding; using ClaudeDo.Installer.Core; using Microsoft.EntityFrameworkCore; @@ -20,6 +21,7 @@ public sealed class InitDatabaseStep : IInstallStep .Options; using var context = new ClaudeDoDbContext(options); ClaudeDoDbContext.MigrateAndConfigure(context); + DefaultListsSeeder.SeedAsync(context).GetAwaiter().GetResult(); progress.Report("Schema applied successfully"); return Task.FromResult(StepResult.Ok()); diff --git a/tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs b/tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs new file mode 100644 index 0000000..2c610c5 --- /dev/null +++ b/tests/ClaudeDo.Worker.Tests/UiSchema/DefaultListSeedTests.cs @@ -0,0 +1,41 @@ +using ClaudeDo.Data; +using ClaudeDo.Data.Seeding; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace ClaudeDo.Worker.Tests.UiSchema; + +public class DefaultListSeedTests : IDisposable +{ + private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo-seed-{Guid.NewGuid():N}.db"); + + private ClaudeDoDbContext NewContext() + { + var opts = new DbContextOptionsBuilder() + .UseSqlite($"Data Source={_dbPath}").Options; + var ctx = new ClaudeDoDbContext(opts); + ctx.Database.EnsureCreated(); + return ctx; + } + + [Fact] + public async Task Seeds_MyDay_Important_Planned_Lists_Idempotently() + { + await using (var ctx = NewContext()) + { + await DefaultListsSeeder.SeedAsync(ctx); + await DefaultListsSeeder.SeedAsync(ctx); // idempotent + } + + await using var verify = NewContext(); + var names = verify.Lists.Select(l => l.Name).OrderBy(n => n).ToList(); + Assert.Equal(new[] { "Important", "My Day", "Planned" }, names); + } + + public void Dispose() + { + try { if (File.Exists(_dbPath)) File.Delete(_dbPath); } catch { } + try { if (File.Exists(_dbPath + "-wal")) File.Delete(_dbPath + "-wal"); } catch { } + try { if (File.Exists(_dbPath + "-shm")) File.Delete(_dbPath + "-shm"); } catch { } + } +}