b53c7fb736
Features: - Category viewer ranges + subcategory templates (admin group modal, tree workspace) - Nomination enrichment via TwitchTracker API (NominationEnrichmentService, TwitchTrackerViewerStatsProvider) with admin tracking rules editor - Nomination group tracker: CategoryGroupName as primary identifier, CategoryId stays as nullable legacy field; StreamerIdentity table - Dynamic showact application form builder (AdminShowactFormBuilder, ShowactApplicationSchedule) - Session idle timeout setting (AdminSessionTimeoutCard) - Share URLs for X and Discord (SiteSettings, public extras) - Workflow rules now stored per season (falls back to global SiteSettings) - New admin routes: settings/access, settings/workflows, tracking-rules - New admin review workspace with subcategory tabs - AdminCategoriesView rebuilt with group/subcategory modals Migrations (all additive): - AddShareUrls, AddShowactDynamicForm, AddCategoryViewerRanges, AddSessionIdleTimeoutSettings, AddSeasonSubcategoryTemplates, AddNominationGroupTrackerIdentity, AddShowactApplicationSchedule, AddSeasonWorkflowRulesJson Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
92 lines
3.1 KiB
C#
92 lines
3.1 KiB
C#
using Backend.Contracts;
|
|
using Backend.Data;
|
|
using Backend.Services;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Backend.Endpoints;
|
|
|
|
public static partial class PublicEndpoints
|
|
{
|
|
private static async Task<IResult> GetUserParticipation(
|
|
HttpContext context,
|
|
int year,
|
|
AwardsDbContext db,
|
|
IUserSessionService userSessionService)
|
|
{
|
|
var session = await userSessionService.ResolveSessionAsync(context, context.RequestAborted);
|
|
if (session is null)
|
|
{
|
|
return Results.Unauthorized();
|
|
}
|
|
|
|
var season = await db.Seasons
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(item => item.Year == year);
|
|
|
|
if (season is null)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
var nominations = await db.Nominations
|
|
.AsNoTracking()
|
|
.Where(item => item.SeasonId == season.Id && item.SubmittedByTwitchId == session.TwitchUserId)
|
|
.OrderBy(item => item.CategoryId)
|
|
.ThenBy(item => item.Id)
|
|
.Select(item => new
|
|
{
|
|
item.CategoryId,
|
|
item.CategoryGroupName,
|
|
item.Status,
|
|
Nominee = item.CandidateId != null
|
|
? item.Candidate!.DisplayName
|
|
: item.CandidateText ?? item.StreamUrl,
|
|
})
|
|
.ToArrayAsync();
|
|
|
|
var groupedNominations = nominations
|
|
.Where(item => item.Status != "rejected" && item.Status != "superseded")
|
|
.Where(item => !string.IsNullOrWhiteSpace(item.Nominee))
|
|
.GroupBy(item => new { item.CategoryId, item.CategoryGroupName })
|
|
.Select(group => new UserNominationStateDto(
|
|
group.Key.CategoryId,
|
|
group.Key.CategoryGroupName,
|
|
group.Select(item => item.Nominee!)
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
.ToArray()))
|
|
.ToArray();
|
|
|
|
var votes = await db.VoteEntries
|
|
.AsNoTracking()
|
|
.Where(item => item.Ballot.SeasonId == season.Id && item.Ballot.SubmittedByTwitchId == session.TwitchUserId)
|
|
.OrderBy(item => item.CategoryId)
|
|
.Select(item => new UserVoteStateDto(item.CategoryId, item.CandidateId))
|
|
.ToArrayAsync();
|
|
|
|
var clips = await db.ClipSubmissions
|
|
.AsNoTracking()
|
|
.Where(item => item.SeasonId == season.Id && item.SubmittedByTwitchId == session.TwitchUserId)
|
|
.OrderByDescending(item => item.CreatedAt)
|
|
.Take(12)
|
|
.Select(item => new UserClipSubmissionStateDto(
|
|
item.Id,
|
|
item.CategoryId,
|
|
item.ClipUrl,
|
|
item.Title,
|
|
item.Creator,
|
|
item.Platform,
|
|
item.Status,
|
|
item.CreatedAt,
|
|
item.ReviewNote,
|
|
item.ReviewedAt))
|
|
.ToArrayAsync();
|
|
|
|
return Results.Ok(new UserParticipationResponse(
|
|
season.Id,
|
|
season.Year,
|
|
groupedNominations,
|
|
votes,
|
|
clips));
|
|
}
|
|
}
|