using Microsoft.EntityFrameworkCore; using Serilog; using TriliumMind.Data; using TriliumMind.Data.Entities; namespace TriliumMind.Services; public class AppDbService { private readonly Serilog.ILogger _log; private readonly IAppDbContext _db; public AppDbService(IAppDbContext dbContext) { _log = Log.ForContext(); _db = dbContext; } public async Task InitializeDatabaseAsync() { try { _log.Information("Checking database..."); // Create db if not exists var created = await _db.Database.EnsureCreatedAsync(); if (created) { _log.Information("Database created successfully"); } else { _log.Information("Database already exists"); } } catch (Exception ex) { _log.Error(ex, "Failed to initialize database"); throw; } } #region Table: Configs public async Task> LoadConfigAsync() { try { _log.Information("Loading configuration from database..."); var configs = await _db.Configs.ToDictionaryAsync(c => c.Key, c => c.Value); _log.Information("Loaded {count} configuration entries", configs.Count); return configs; } catch (Exception ex) { _log.Error(ex, "Failed to load configuration from database"); throw; } } public async Task GetConfigAsync(string key) { try { var config = await _db.Configs.FindAsync(key); return config?.Value; } catch (Exception ex) { _log.Error(ex, "Failed to get config value for key: {key}", key); throw; } } public async Task SetConfigAsync(string key, string value) { try { var config = await _db.Configs.FindAsync(key); if (config == null) { config = new Config { Key = key, Value = value }; _db.Configs.Add(config); } else { config.Value = value; _db.Configs.Update(config); } await _db.SaveChangesAsync(); _log.Debug("Config saved: {key} = {value}", key, value); } catch (Exception ex) { _log.Error(ex, "Failed to set config value for key: {key}", key); throw; } } #endregion Table: Configs public async Task UpsertJiraIssueAsync(JiraIssue issue, CancellationToken ct = default) { try { var existing = await _db.JiraIssues.FindAsync([issue.Key], ct); if (existing == null) { _db.JiraIssues.Add(issue); _log.Debug("Inserting new Jira issue: {key}", issue.Key); } else { if (issue.Updated > existing.Updated) { existing.Summary = issue.Summary; existing.Parent = issue.Parent; existing.Type = issue.Type; existing.Status = issue.Status; existing.Assignee = issue.Assignee; existing.Manager = issue.Manager; existing.Due = issue.Due; existing.Updated = issue.Updated; existing.ObjectId = issue.ObjectId; existing.NeedNotify = issue.NeedNotify; _db.JiraIssues.Update(existing); _log.Debug("Updating existing Jira issue: {key}", issue.Key); } else { _log.Debug("Skipping Jira issue (not newer): {key}", issue.Key); } } await _db.SaveChangesAsync(ct); } catch (Exception ex) { _log.Error(ex, "Failed to upsert Jira issue: {key}", issue.Key); throw; } } public async Task UpsertJiraIssuesBatchAsync(IEnumerable issues, CancellationToken ct = default) { try { var issueList = issues.ToList(); if (!issueList.Any()) return; var keys = issueList.Select(i => i.Key).ToList(); var existingIssues = await _db.JiraIssues .Where(ji => keys.Contains(ji.Key)) .ToDictionaryAsync(ji => ji.Key, ct); int insertCount = 0, updateCount = 0, skipCount = 0; foreach (var issue in issueList) { if (!existingIssues.TryGetValue(issue.Key, out var existing)) { _db.JiraIssues.Add(issue); insertCount++; } else if (issue.Updated > existing.Updated) { existing.Summary = issue.Summary; existing.Parent = issue.Parent; existing.Type = issue.Type; existing.Status = issue.Status; existing.Assignee = issue.Assignee; existing.Manager = issue.Manager; existing.Due = issue.Due; existing.Updated = issue.Updated; existing.ObjectId = issue.ObjectId; existing.NeedNotify = issue.NeedNotify; _db.JiraIssues.Update(existing); updateCount++; } else { skipCount++; } } await _db.SaveChangesAsync(ct); _log.Information("Batch completed: {insert} inserted, {update} updated, {skip} skipped out of {total} issues", insertCount, updateCount, skipCount, issueList.Count); } catch (Exception ex) { _log.Error(ex, "Failed to batch upsert Jira issues"); throw; } } }