using Backend.Domain; using Backend.Services; using Microsoft.EntityFrameworkCore; namespace Backend.Data; public static partial class SeedDataBootstrapper { private static async Task EnsureCategoriesAsync(AwardsDbContext db, Season season) { var categories = await db.Categories .Where(item => item.SeasonId == season.Id) .ToListAsync(); var templates = SeedCatalog.DefaultSubcategoryTemplates .Select((item, index) => item with { SortOrder = index + 1 }) .ToArray(); var usedCategories = new HashSet(); season.SubcategoryTemplatesJson = SeasonSubcategoryTemplateSettings.Serialize(templates); var nextSortOrder = 1; foreach (var award in SeedCatalog.AwardCategorySeeds.OrderBy(item => item.SortOrder)) { foreach (var template in templates) { var category = FindReusableCategory(categories, award, template, usedCategories) ?? new Category { SeasonId = season.Id }; usedCategories.Add(category); category.GroupName = award.Name; category.Name = template.Name; category.Slug = BuildCategorySlug(award.Slug, template.Slug); category.Description = award.Description; category.SortOrder = nextSortOrder++; category.MaxNomineesPerUser = 3; category.ViewerRangeMin = template.ViewerRangeMin; category.ViewerRangeMax = template.ViewerRangeMax; if (category.Id == 0 && !db.Categories.Local.Any(item => ReferenceEquals(item, category))) { db.Categories.Add(category); categories.Add(category); } } } var staleCategories = categories .Where(item => item.Id > 0 && !usedCategories.Contains(item)) .ToArray(); await RemoveStaleCategoryDataAsync(db, staleCategories); await db.SaveChangesAsync(); } private static async Task RemoveStaleCategoryDataAsync(AwardsDbContext db, Category[] staleCategories) { if (staleCategories.Length == 0) { return; } var staleCategoryIds = staleCategories.Select(item => item.Id).ToArray(); var staleCandidateIds = await db.Candidates .Where(item => staleCategoryIds.Contains(item.CategoryId)) .Select(item => item.Id) .ToArrayAsync(); var staleVoteEntries = await db.VoteEntries .Where(item => staleCategoryIds.Contains(item.CategoryId) || staleCandidateIds.Contains(item.CandidateId)) .ToArrayAsync(); db.VoteEntries.RemoveRange(staleVoteEntries); var staleResults = await db.Results .Where(item => staleCategoryIds.Contains(item.CategoryId) || staleCandidateIds.Contains(item.CandidateId)) .ToArrayAsync(); db.Results.RemoveRange(staleResults); var affectedClips = await db.ClipSubmissions .Where(item => (item.CategoryId != null && staleCategoryIds.Contains(item.CategoryId.Value)) || (item.CandidateId != null && staleCandidateIds.Contains(item.CandidateId.Value))) .ToArrayAsync(); foreach (var clip in affectedClips) { if (clip.CategoryId != null && staleCategoryIds.Contains(clip.CategoryId.Value)) { clip.CategoryId = null; } if (clip.CandidateId != null && staleCandidateIds.Contains(clip.CandidateId.Value)) { clip.CandidateId = null; } } var affectedNominations = await db.Nominations .Where(item => (item.CategoryId != null && staleCategoryIds.Contains(item.CategoryId.Value)) || (item.SuggestedCategoryId != null && staleCategoryIds.Contains(item.SuggestedCategoryId.Value)) || (item.CandidateId != null && staleCandidateIds.Contains(item.CandidateId.Value))) .ToArrayAsync(); foreach (var nomination in affectedNominations) { if (nomination.CategoryId != null && staleCategoryIds.Contains(nomination.CategoryId.Value)) { nomination.CategoryId = null; } if (nomination.SuggestedCategoryId != null && staleCategoryIds.Contains(nomination.SuggestedCategoryId.Value)) { nomination.SuggestedCategoryId = null; } if (nomination.CandidateId != null && staleCandidateIds.Contains(nomination.CandidateId.Value)) { nomination.CandidateId = null; } } var staleCandidates = await db.Candidates .Where(item => staleCandidateIds.Contains(item.Id)) .ToArrayAsync(); db.Candidates.RemoveRange(staleCandidates); db.Categories.RemoveRange(staleCategories); } private static Category? FindReusableCategory( List categories, AwardCategorySeed award, SeasonSubcategoryTemplateSetting template, HashSet usedCategories) { var targetSlug = BuildCategorySlug(award.Slug, template.Slug); return categories.FirstOrDefault(item => !usedCategories.Contains(item) && string.Equals(item.Slug, targetSlug, StringComparison.OrdinalIgnoreCase)) ?? categories.FirstOrDefault(item => !usedCategories.Contains(item) && string.Equals(item.GroupName, award.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(item.Name, template.Name, StringComparison.OrdinalIgnoreCase)) ?? categories.FirstOrDefault(item => !usedCategories.Contains(item) && string.Equals(item.GroupName, award.LegacyGroupName, StringComparison.OrdinalIgnoreCase) && string.Equals(item.Name, template.Name, StringComparison.OrdinalIgnoreCase)); } private static async Task EnsureCandidatesAsync(AwardsDbContext db, Season season, CandidateSeed[] seeds) { var categories = await db.Categories .Where(item => item.SeasonId == season.Id) .ToDictionaryAsync(item => item.Slug, StringComparer.OrdinalIgnoreCase); var existing = await db.Candidates .Where(item => item.SeasonId == season.Id) .Select(item => new { item.CategoryId, item.DisplayName, item.ChannelSlug }) .ToArrayAsync(); var existingKeys = existing .Select(item => $"{item.CategoryId}|{item.DisplayName}|{item.ChannelSlug}".ToLowerInvariant()) .ToHashSet(); foreach (var seed in seeds) { if (!categories.TryGetValue(seed.CategorySlug, out var category)) { continue; } var key = $"{category.Id}|{seed.DisplayName}|{seed.ChannelSlug}".ToLowerInvariant(); if (existingKeys.Contains(key)) { continue; } db.Candidates.Add(new Candidate { SeasonId = season.Id, CategoryId = category.Id, DisplayName = seed.DisplayName, ChannelSlug = seed.ChannelSlug, Platform = seed.Platform, }); } await db.SaveChangesAsync(); } private static async Task EnsureWinnersAsync(AwardsDbContext db, Season season, WinnerSeed[] seeds) { var categories = await db.Categories .Where(item => item.SeasonId == season.Id) .ToDictionaryAsync(item => item.Slug, StringComparer.OrdinalIgnoreCase); var candidates = await db.Candidates .Where(item => item.SeasonId == season.Id) .ToArrayAsync(); var existingResults = await db.Results .Where(item => item.SeasonId == season.Id) .ToArrayAsync(); foreach (var result in existingResults) { if (categories.Values.FirstOrDefault(item => item.Id == result.CategoryId) is { } category && result.CategoryName != category.Name) { result.CategoryName = category.Name; } } var existingResultCategoryIds = existingResults.Select(item => item.CategoryId).ToHashSet(); foreach (var seed in seeds) { if (!categories.TryGetValue(seed.CategorySlug, out var category) || existingResultCategoryIds.Contains(category.Id)) { continue; } var candidate = candidates.FirstOrDefault(item => item.CategoryId == category.Id && string.Equals(item.DisplayName, seed.DisplayName, StringComparison.OrdinalIgnoreCase)); if (candidate is null) { continue; } db.Results.Add(new AwardResult { SeasonId = season.Id, CategoryId = category.Id, CandidateId = candidate.Id, CategoryName = category.Name, }); } } private static string BuildCategorySlug(string awardSlug, string templateSlug) => $"{SeasonSubcategoryTemplateSettings.Slugify(awardSlug)}-{SeasonSubcategoryTemplateSettings.Slugify(templateSlug)}"; }