2025-12-11 10:20:10 +09:00
|
|
|
|
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<AppDbService>();
|
|
|
|
|
|
_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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 14:34:54 +09:00
|
|
|
|
#region Table: Configs
|
2025-12-11 10:20:10 +09:00
|
|
|
|
public async Task<Dictionary<string, string>> 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<string?> 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 14:34:54 +09:00
|
|
|
|
#endregion Table: Configs
|
2025-12-11 10:20:10 +09:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-11 14:34:54 +09:00
|
|
|
|
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<JiraIssue> 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 10:20:10 +09:00
|
|
|
|
}
|