using System.Text.Json; using ClaudeDo.Data.Models; using Microsoft.EntityFrameworkCore; namespace ClaudeDo.Data.Repositories; public sealed class AppSettingsRepository { private readonly ClaudeDoDbContext _context; public AppSettingsRepository(ClaudeDoDbContext context) => _context = context; public async Task GetAsync(CancellationToken ct = default) { var row = await _context.AppSettings.AsNoTracking() .FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct); if (row is not null) return row; row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId }; _context.AppSettings.Add(row); await _context.SaveChangesAsync(ct); _context.Entry(row).State = EntityState.Detached; return row; } private async Task GetOrCreateTrackedRowAsync(CancellationToken ct) { var row = await _context.AppSettings .FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct); if (row is null) { row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId }; _context.AppSettings.Add(row); } return row; } public async Task UpdateAsync(AppSettingsEntity updated, CancellationToken ct = default) { var row = await GetOrCreateTrackedRowAsync(ct); row.DefaultClaudeInstructions = updated.DefaultClaudeInstructions ?? string.Empty; row.DefaultModel = string.IsNullOrWhiteSpace(updated.DefaultModel) ? "sonnet" : updated.DefaultModel; row.DefaultMaxTurns = updated.DefaultMaxTurns; row.DefaultPermissionMode = string.IsNullOrWhiteSpace(updated.DefaultPermissionMode) ? "auto" : updated.DefaultPermissionMode; row.MaxParallelExecutions = updated.MaxParallelExecutions < 1 ? 1 : updated.MaxParallelExecutions; row.WorktreeStrategy = string.IsNullOrWhiteSpace(updated.WorktreeStrategy) ? "sibling" : updated.WorktreeStrategy; row.CentralWorktreeRoot = string.IsNullOrWhiteSpace(updated.CentralWorktreeRoot) ? null : updated.CentralWorktreeRoot; row.WorktreeAutoCleanupEnabled = updated.WorktreeAutoCleanupEnabled; row.WorktreeAutoCleanupDays = updated.WorktreeAutoCleanupDays; row.ReportExcludedPaths = string.IsNullOrWhiteSpace(updated.ReportExcludedPaths) ? null : updated.ReportExcludedPaths; row.StandupWeekday = updated.StandupWeekday; row.DailyPrepMaxTasks = updated.DailyPrepMaxTasks < 1 ? 1 : updated.DailyPrepMaxTasks; await _context.SaveChangesAsync(ct); } public async Task> GetRepoImportFoldersAsync(CancellationToken ct = default) { var json = await _context.AppSettings.AsNoTracking() .Where(s => s.Id == AppSettingsEntity.SingletonId) .Select(s => s.RepoImportFolders) .FirstOrDefaultAsync(ct); if (string.IsNullOrWhiteSpace(json)) return new List(); try { return JsonSerializer.Deserialize>(json) ?? new List(); } catch (JsonException) { return new List(); } } public async Task SetRepoImportFoldersAsync(IEnumerable folders, CancellationToken ct = default) { var list = folders.ToList(); var row = await GetOrCreateTrackedRowAsync(ct); row.RepoImportFolders = list.Count == 0 ? null : JsonSerializer.Serialize(list); await _context.SaveChangesAsync(ct); } }