Files
vtuber-awards/Backend/Endpoints/AdminSeasonResultsEndpoints.cs
AzuTear 441ef2b850
CI - Build & Verify / Build, Typecheck & Hygiene (push) Successful in 59s
CI - Build & Verify / Deploy to award.noveria.net (push) Failing after 53s
Update release notes and deploy workspace
2026-06-29 17:49:54 +02:00

264 lines
9.4 KiB
C#

using Backend.Contracts;
using Backend.Common;
using Backend.Data;
using Backend.Domain;
using Backend.Services;
using Microsoft.EntityFrameworkCore;
namespace Backend.Endpoints;
public static partial class AdminSeasonManagementEndpoints
{
private static async Task<IResult> SetResult(
HttpContext context,
int seasonId,
SetAwardResultRequest request,
AwardsDbContext db,
IAdminAuditService adminAuditService)
{
var session = AdminEndpointConventions.CurrentSession(context);
var category = await db.Categories
.AsNoTracking()
.FirstOrDefaultAsync(item => item.Id == request.CategoryId && item.SeasonId == seasonId);
if (category is null)
{
return Results.BadRequest(new { message = "The selected category does not exist in this season." });
}
var candidate = await db.Candidates
.AsNoTracking()
.FirstOrDefaultAsync(item =>
item.Id == request.CandidateId
&& item.SeasonId == seasonId
&& item.CategoryId == request.CategoryId);
if (candidate is null)
{
return Results.BadRequest(new { message = "The selected candidate does not belong to the selected category." });
}
var workflowRules = await LoadWorkflowRulesAsync(db, seasonId, context.RequestAborted);
var winnerRequiresClipRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.WinnerRequiresClip);
if (WorkflowRuleSettings.ShouldBlock(winnerRequiresClipRule)
&& string.IsNullOrWhiteSpace(candidate.ClipCompilationUrl))
{
return CreateWorkflowRuleError(
"Dieser Kandidat hat noch keinen gepflegten Clip-Link. Bitte zuerst die Clip-Compilation am Kandidaten hinterlegen oder die Workflow-Regel umstellen.");
}
var winnerPlacementsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.MaxWinnerPlacements);
if (WorkflowRuleSettings.ShouldBlock(winnerPlacementsRule))
{
var candidateIdentityKey = WorkflowRuleSettings.CandidateIdentityKey(candidate);
var existingWinnerIdentities = await db.Results
.AsNoTracking()
.Include(item => item.Candidate)
.Where(item => item.SeasonId == seasonId && item.CategoryId != request.CategoryId)
.Select(item => new
{
item.CategoryId,
item.Candidate.StreamerIdentityId,
item.Candidate.DisplayName,
item.Candidate.ChannelSlug,
})
.ToArrayAsync(context.RequestAborted);
var existingWinnerCount = existingWinnerIdentities.Count(item =>
candidate.StreamerIdentityId.HasValue && item.StreamerIdentityId == candidate.StreamerIdentityId
||
string.Equals(WorkflowRuleSettings.CandidateIdentityKey(item.DisplayName, item.ChannelSlug), candidateIdentityKey, StringComparison.Ordinal));
if (existingWinnerCount >= winnerPlacementsRule.Limit)
{
return CreateWorkflowRuleError(
$"Diese Person hat bereits {existingWinnerCount} Gewinnerplatz(e). Limit: {winnerPlacementsRule.Limit}.");
}
}
var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId);
var existingResult = await db.Results.FirstOrDefaultAsync(item =>
item.SeasonId == seasonId
&& item.CategoryId == request.CategoryId);
if (existingResult is null)
{
existingResult = new AwardResult
{
SeasonId = seasonId,
CategoryId = request.CategoryId,
CandidateId = request.CandidateId,
CategoryName = category.Name,
};
db.Results.Add(existingResult);
}
else
{
existingResult.CandidateId = request.CandidateId;
existingResult.CategoryName = category.Name;
}
var wasPublished = season?.WinnersPublishedAt is not null;
if (wasPublished && season is not null)
{
season.WinnersPublishedAt = null;
season.WinnersPublishedByTwitchId = null;
}
adminAuditService.AddEntry(
session.TwitchUserId,
"result.set",
"result",
$"{seasonId}:{request.CategoryId}",
$"Gewinner für {category.Name} wurde gesetzt.",
new
{
seasonId,
categoryId = request.CategoryId,
candidateId = request.CandidateId,
candidateName = candidate.DisplayName,
unpublishedWinners = wasPublished,
},
RequestMetadataReader.Read(context));
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(new
{
saved = true,
resultId = existingResult.Id,
seasonId,
categoryId = request.CategoryId,
candidateId = request.CandidateId,
});
}
private static async Task<IResult> DeleteResult(
HttpContext context,
int resultId,
AwardsDbContext db,
IAdminAuditService adminAuditService)
{
var session = AdminEndpointConventions.CurrentSession(context);
var result = await db.Results
.Include(item => item.Category)
.Include(item => item.Candidate)
.Include(item => item.Season)
.FirstOrDefaultAsync(item => item.Id == resultId);
if (result is null)
{
return Results.NotFound();
}
var wasPublished = result.Season.WinnersPublishedAt is not null;
if (wasPublished)
{
result.Season.WinnersPublishedAt = null;
result.Season.WinnersPublishedByTwitchId = null;
}
db.Results.Remove(result);
adminAuditService.AddEntry(
session.TwitchUserId,
"result.delete",
"result",
result.Id.ToString(),
$"Gewinner für {result.Category.Name} wurde entfernt.",
new
{
result.SeasonId,
result.CategoryId,
result.CandidateId,
candidateName = result.Candidate.DisplayName,
unpublishedWinners = wasPublished,
},
RequestMetadataReader.Read(context));
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(new { deleted = true, resultId });
}
private static async Task<IResult> PublishWinners(
HttpContext context,
int seasonId,
AwardsDbContext db,
IAdminAuditService adminAuditService)
{
var session = AdminEndpointConventions.CurrentSession(context);
var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted);
if (season is null)
{
return Results.NotFound();
}
var issues = await BuildWinnerPublicationIssuesAsync(db, seasonId, context.RequestAborted);
if (issues.Length > 0)
{
return CreateWinnerPublicationError(issues);
}
season.WinnersPublishedAt = DateTimeOffset.UtcNow;
season.WinnersPublishedByTwitchId = session.TwitchUserId;
adminAuditService.AddEntry(
session.TwitchUserId,
"winners.publish",
"season",
season.Id.ToString(),
$"Gewinner für {season.Year} wurden veröffentlicht.",
new
{
seasonId,
season.Year,
season.WinnersPublishedAt,
},
RequestMetadataReader.Read(context));
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(new
{
saved = true,
seasonId,
winnersPublishedAt = season.WinnersPublishedAt,
winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId,
});
}
private static async Task<IResult> UnpublishWinners(
HttpContext context,
int seasonId,
AwardsDbContext db,
IAdminAuditService adminAuditService)
{
var session = AdminEndpointConventions.CurrentSession(context);
var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted);
if (season is null)
{
return Results.NotFound();
}
var previousPublishedAt = season.WinnersPublishedAt;
season.WinnersPublishedAt = null;
season.WinnersPublishedByTwitchId = null;
adminAuditService.AddEntry(
session.TwitchUserId,
"winners.unpublish",
"season",
season.Id.ToString(),
$"Gewinner für {season.Year} wurden von der Landingpage zurückgenommen.",
new
{
seasonId,
season.Year,
previousPublishedAt,
},
RequestMetadataReader.Read(context));
await db.SaveChangesAsync(context.RequestAborted);
return Results.Ok(new
{
saved = true,
seasonId,
winnersPublishedAt = season.WinnersPublishedAt,
winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId,
});
}
}