using Microsoft.EntityFrameworkCore; using Nuuru.Server.Data; namespace Nuuru.Server.Services { public interface IDbMaintenanceService { Task SyncPostStatsAsync(); Task SyncThreadLastPostInfoAsync(); } public class DbMaintenanceService : IDbMaintenanceService { private readonly ApplicationDbContext _context; private readonly ILogger _logger; private const int BatchSize = 500; public DbMaintenanceService(ApplicationDbContext context, ILogger logger) { _context = context; _logger = logger; } public async Task SyncPostStatsAsync() { _logger.LogInformation("Syncing Post TagCount and Score..."); // Find posts where cached stats are stale var stale = await _context.BooruPosts .Select(p => new { p.Id, p.TagCount, p.Score, ActualTagCount = p.PostTags.Count(), ActualScore = p.Votes.Sum(v => (int?)v.Value) ?? 0 }) .Where(p => p.TagCount != p.ActualTagCount || p.Score != p.ActualScore) .ToListAsync(); _logger.LogInformation("Found {Count} posts with stale stats.", stale.Count); for (int i = 0; i < stale.Count; i += BatchSize) { var batch = stale.Skip(i).Take(BatchSize).ToList(); var ids = batch.Select(b => b.Id).ToList(); // Load entities for this batch var posts = await _context.BooruPosts .Where(p => ids.Contains(p.Id)) .ToDictionaryAsync(p => p.Id); foreach (var item in batch) { if (posts.TryGetValue(item.Id, out var post)) { post.TagCount = item.ActualTagCount; post.Score = item.ActualScore; } } await _context.SaveChangesAsync(); _logger.LogInformation("Updated batch {Start}-{End} of {Total}.", i + 1, Math.Min(i + BatchSize, stale.Count), stale.Count); } _logger.LogInformation("Post stats synced."); } public async Task SyncThreadLastPostInfoAsync() { _logger.LogInformation("Syncing ForumThread LastPostId and stats..."); var stale = await _context.ForumThreads .Select(t => new { t.Id, t.LastPostAt, t.LastPostId, t.ReplyCount, ActualLastPostAt = t.Posts.Max(p => (DateTime?)p.CreatedAt) ?? t.CreatedAt, ActualLastPostId = t.Posts .OrderByDescending(p => p.CreatedAt) .ThenByDescending(p => p.Id) .Select(p => (int?)p.Id) .FirstOrDefault(), ActualReplyCount = t.Posts.Count() > 0 ? t.Posts.Count() - 1 : 0 }) .Where(t => t.LastPostAt != t.ActualLastPostAt || t.LastPostId != t.ActualLastPostId || t.ReplyCount != t.ActualReplyCount) .ToListAsync(); _logger.LogInformation("Found {Count} threads with stale stats.", stale.Count); var ids = stale.Select(s => s.Id).ToList(); var threads = await _context.ForumThreads .Where(t => ids.Contains(t.Id)) .ToDictionaryAsync(t => t.Id); foreach (var item in stale) { if (threads.TryGetValue(item.Id, out var thread)) { thread.LastPostAt = item.ActualLastPostAt; thread.LastPostId = item.ActualLastPostId; thread.ReplyCount = item.ActualReplyCount; } } await _context.SaveChangesAsync(); _logger.LogInformation("ForumThread last post info synced."); } } }