Update release notes and deploy workspace
CI - Build & Verify / Build, Typecheck & Hygiene (push) Successful in 59s
CI - Build & Verify / Deploy to award.noveria.net (push) Failing after 53s

This commit is contained in:
AzuTear
2026-06-29 17:49:54 +02:00
parent c466348d18
commit 441ef2b850
196 changed files with 9279 additions and 39292 deletions
+78 -27
View File
@@ -1,4 +1,5 @@
using Backend.Contracts;
using Backend.Common;
using Backend.Data;
using Backend.Security;
using Microsoft.EntityFrameworkCore;
@@ -20,37 +21,39 @@ public static class AdminDashboardEndpoints
return group;
}
private static async Task<IResult> GetDashboard(AwardsDbContext db, HttpContext context)
private static async Task<IResult> GetDashboard(int? seasonId, AwardsDbContext db, HttpContext context)
{
var canViewAuditIp = CanViewAuditIp(context);
var currentSeason = await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.IsCurrent);
if (currentSeason is null)
var selectedSeason = seasonId.HasValue
? await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.Id == seasonId.Value)
: await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.IsCurrent);
if (selectedSeason is null)
{
return Results.NotFound();
}
var nominationCount = await db.Nominations.CountAsync(item => item.SeasonId == currentSeason.Id);
var voteCount = await db.VoteEntries.CountAsync(item => item.Ballot.SeasonId == currentSeason.Id);
var categoryCount = await db.Categories.CountAsync(item => item.SeasonId == currentSeason.Id);
var reviewCount = await db.Nominations.CountAsync(item => item.SeasonId == currentSeason.Id && item.Status == "pending");
var riskFlagCount = await db.RiskFlags.CountAsync(item => item.Status == "open");
var selectedSeasonId = selectedSeason.Id;
var phaseKey = SeasonMappings.NormalizePhaseKey(selectedSeason.CurrentPhase);
var nominationCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId);
var voteCount = await db.VoteEntries.CountAsync(item => item.Ballot.SeasonId == selectedSeasonId);
var categoryCount = await db.Categories.CountAsync(item => item.SeasonId == selectedSeasonId);
var reviewCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId && item.Status == "pending");
var riskFlagCount = await db.RiskFlags.CountAsync(item =>
item.Status == "open" &&
(item.SeasonId == selectedSeasonId || item.SeasonId == null));
var globalRiskFlagCount = await db.RiskFlags.CountAsync(item =>
item.Status == "open" &&
item.SeasonId == null);
var topCategoryNames = await db.VoteEntries
.AsNoTracking()
.Where(item => item.Ballot.SeasonId == currentSeason.Id)
.Select(item => item.Category.Name)
.ToListAsync();
var topCategories = topCategoryNames
.GroupBy(name => name)
.Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count()))
.OrderByDescending(item => item.Votes)
.Take(5)
.ToArray();
var topCategories = phaseKey == "nomination"
? await BuildTopNominationCategoriesAsync(db, selectedSeasonId)
: await BuildTopVotingCategoriesAsync(db, selectedSeasonId);
var riskFlags = await db.RiskFlags
.AsNoTracking()
.Where(item => item.Status == "open")
.Where(item =>
item.Status == "open" &&
(item.SeasonId == selectedSeasonId || item.SeasonId == null))
.OrderByDescending(item => item.CreatedAt)
.Take(8)
.ToArrayAsync();
@@ -74,18 +77,27 @@ public static class AdminDashboardEndpoints
.ToArrayAsync();
var activityItems = auditEntries
.Take(3)
.Take(6)
.Select(item => new AdminActivityDto(item.Summary, $"{Math.Max(1, (int)Math.Round((DateTimeOffset.UtcNow - item.CreatedAt).TotalMinutes))} Min."))
.ToArray();
return Results.Ok(new AdminDashboardResponse(
selectedSeason.Id,
selectedSeason.Year,
selectedSeason.Name,
selectedSeason.IsCurrent,
new[]
{
new AdminMetricDto("Nominierungen", nominationCount, "Gespeicherte Einreichungen im aktuellen Public-Jahr"),
new AdminMetricDto("Stimmen", voteCount, "Abgegebene Stimmen im aktuellen Public-Jahr"),
new AdminMetricDto("Kategorien", categoryCount, "Aktive Kategorien im aktuellen Public-Jahr"),
new AdminMetricDto("Reviews offen", reviewCount, "Offene Nominierungen mit Review-Bedarf"),
new AdminMetricDto("Risikohinweise", riskFlagCount, "Offene Risk Flags ueber alle Quellen"),
new AdminMetricDto("Nominierungen", nominationCount, $"Gespeicherte Einreichungen im Award-Jahr {selectedSeason.Year}"),
new AdminMetricDto("Stimmen", voteCount, $"Abgegebene Stimmen im Award-Jahr {selectedSeason.Year}"),
new AdminMetricDto("Kategorien", categoryCount, $"Aktive Kategorien im Award-Jahr {selectedSeason.Year}"),
new AdminMetricDto("Reviews offen", reviewCount, "Offene Nominierungen mit Review-Bedarf in diesem Jahr"),
new AdminMetricDto(
"Risikohinweise",
riskFlagCount,
globalRiskFlagCount > 0
? $"Offene Hinweise fuer {selectedSeason.Year}, inklusive {globalRiskFlagCount} globaler Hinweise"
: $"Offene Hinweise fuer {selectedSeason.Year}"),
},
activityItems,
topCategories,
@@ -93,6 +105,45 @@ public static class AdminDashboardEndpoints
auditEntries));
}
private static async Task<AdminTopCategoryDto[]> BuildTopVotingCategoriesAsync(AwardsDbContext db, int seasonId)
{
var categoryNames = await db.VoteEntries
.AsNoTracking()
.Where(item => item.Ballot.SeasonId == seasonId)
.Select(item => item.Category.Name)
.ToListAsync();
return categoryNames
.GroupBy(name => name)
.Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Stimmen"))
.OrderByDescending(item => item.Value)
.Take(5)
.ToArray();
}
private static async Task<AdminTopCategoryDto[]> BuildTopNominationCategoriesAsync(AwardsDbContext db, int seasonId)
{
var nominationCategories = await db.Nominations
.AsNoTracking()
.Where(item => item.SeasonId == seasonId)
.Select(item => new
{
CategoryName = item.Category != null ? item.Category.Name : null,
item.CategoryGroupName,
})
.ToListAsync();
return nominationCategories
.Select(item => string.IsNullOrWhiteSpace(item.CategoryName)
? string.IsNullOrWhiteSpace(item.CategoryGroupName) ? "Ohne Kategorie" : item.CategoryGroupName
: item.CategoryName)
.GroupBy(name => name)
.Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Nominierungen"))
.OrderByDescending(item => item.Value)
.Take(5)
.ToArray();
}
private static async Task<IResult> GetAuditEntries(
int? limit,
string? query,