using MailSharp.MailClient.Services; using MailSharp.MailClient.Services.Logging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace MailSharp.MailClient.Controllers.Api; [ApiController] [Route("api/maintenance")] [Authorize(Policy = "AdminOnly")] public class MaintenanceApiController( ILogStore logStore, IAccountStore accountStore, IMessageIndexStore indexStore, IFolderSettingsStore folderSettingsStore, IMailCacheStore mailCacheStore, IBackgroundIndexingStatus indexingStatus) : ControllerBase { [HttpGet("logs")] public IActionResult Logs( LogLevel? level = null, string? category = null, int? accountId = null, DateTime? from = null, DateTime? to = null, string? search = null, int page = 1, int pageSize = 50) { var result = logStore.Query(new LogQuery { Level = level, Category = category, AccountId = accountId, From = from, To = to, Search = search, Page = page, PageSize = pageSize }); return Ok(new { items = result.Items, totalCount = result.TotalCount, page, pageSize }); } [HttpPost("logs/clear")] public IActionResult ClearLogs() { logStore.Clear(); return Ok(); } [HttpGet("status")] public IActionResult Status() { // Per-account/folder index summary (accounts, indexed folders, total messages) - the same // underlying data as the Metrics tab, but rolled up to totals here rather than shown // per-folder, since this panel is about "is the background work healthy", not the folder // browser the Metrics tab already is. var accounts = accountStore.GetAll(); var indexSummaries = accounts.Select(account => { var folders = indexStore.GetIndexedFolders(account.Id); return new { accountId = account.Id, emailAddress = account.EmailAddress, indexedFolderCount = folders.Count, indexedMessageCount = folders.Sum(f => indexStore.GetMessageCount(account.Id, f)), indexedSizeBytes = folders.Sum(f => indexStore.GetTotalSize(account.Id, f)) }; }).ToList(); var cache = mailCacheStore.GetStats(); return Ok(new { indexing = new { maxConcurrentIndexing = indexingStatus.MaxConcurrentIndexing, currentlyAvailableSlots = indexingStatus.CurrentlyAvailableSlots, inProgress = indexingStatus.InProgressKeys.Select(key => { indexingStatus.TryGetProgress(key, out var processed, out var total); return new { key, processed, total }; }) }, index = new { accounts = indexSummaries, totalIndexedFolders = indexSummaries.Sum(a => a.indexedFolderCount), totalIndexedMessages = indexSummaries.Sum(a => a.indexedMessageCount), totalIndexedSizeBytes = indexSummaries.Sum(a => a.indexedSizeBytes) }, cache = new { messageListCacheEntries = cache.MessageListCacheEntries, contactMailSearchEntries = cache.ContactMailSearchEntries, senderImagePreferences = cache.SenderImagePreferences, imageUrlMappings = cache.ImageUrlMappings, messageIdMappings = cache.MessageIdMappings, cachedMessages = new { fileCount = cache.CachedMessages.FileCount, totalBytes = cache.CachedMessages.TotalBytes }, cachedAttachments = new { fileCount = cache.CachedAttachments.FileCount, totalBytes = cache.CachedAttachments.TotalBytes }, cachedImages = new { fileCount = cache.CachedImages.FileCount, totalBytes = cache.CachedImages.TotalBytes } } }); } [HttpGet("metrics")] public IActionResult Metrics(int? accountId = null) { var accounts = accountStore.GetAll() .Where(a => accountId == null || a.Id == accountId.Value) .Select(account => { // Same display order the user set up in Settings > Mappen beheren (see // IImapService.GetFoldersAsync/ApplyDisplayOrder) - folders the user dragged to the // top there should show in that order here too, not in whatever order LiteDB happens // to return them. var savedOrder = folderSettingsStore.GetOrder(account.Id); var position = savedOrder.Select((name, i) => (name, i)).ToDictionary(x => x.name, x => x.i, StringComparer.Ordinal); var folders = indexStore.GetIndexedFolders(account.Id) .Select((folder, naturalIndex) => (folder, naturalIndex)) .OrderBy(x => position.TryGetValue(x.folder, out var idx) ? idx : int.MaxValue) .ThenBy(x => x.naturalIndex) .Select(x => new { folder = x.folder, messageCount = indexStore.GetMessageCount(account.Id, x.folder), sizeBytes = indexStore.GetTotalSize(account.Id, x.folder) }).ToList(); return new { accountId = account.Id, emailAddress = account.EmailAddress, folders, totalMessages = folders.Sum(f => f.messageCount), totalSizeBytes = folders.Sum(f => f.sizeBytes) }; }).ToList(); return Ok(new { accounts, totalMessages = accounts.Sum(a => a.totalMessages), totalSizeBytes = accounts.Sum(a => a.totalSizeBytes) }); } }