Refactor endpoint services to accept narrow inputs

This commit is contained in:
2026-02-07 02:17:01 +01:00
parent 5b06e279f3
commit c765dd322b
10 changed files with 179 additions and 102 deletions

View File

@@ -14,7 +14,7 @@ public static class AdminEndpoints
admin.MapPost("/results", async ([FromBody] ResultsOpenRequest request, AdminWorkflowService service) =>
{
return await service.SetResultsOpenAsync(request);
return await service.SetResultsOpenAsync(request.ResultsOpen);
});
admin.MapGet("/vote-status", async (AdminWorkflowService service) =>
@@ -24,7 +24,7 @@ public static class AdminEndpoints
admin.MapPost("/joker", async ([FromBody] GrantJokerRequest request, AdminWorkflowService service) =>
{
return await service.GrantJokerAsync(request);
return await service.GrantJokerAsync(request.PlayerId);
});
admin.MapDelete("/players/{playerId:guid}", async (Guid playerId, AdminWorkflowService service) =>
@@ -38,7 +38,7 @@ public static class AdminEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.LinkSuggestionsAsync(player, request);
return await service.LinkSuggestionsAsync(player.Id, request.SourceSuggestionId, request.TargetSuggestionId);
});
admin.MapPost("/unlink-suggestions", async ([FromBody] UnlinkSuggestionsRequest request, HttpContext ctx, AppDbContext db, AdminWorkflowService service) =>
@@ -47,7 +47,7 @@ public static class AdminEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.UnlinkSuggestionsAsync(player, request);
return await service.UnlinkSuggestionsAsync(player.Id, request.SuggestionId);
});
admin.MapPost("/reset", async (AdminWorkflowService service) =>

View File

@@ -7,15 +7,15 @@ namespace GameList.Endpoints;
internal sealed class AdminWorkflowService(AppDbContext db)
{
public async Task<IResult> SetResultsOpenAsync(ResultsOpenRequest request)
public async Task<IResult> SetResultsOpenAsync(bool resultsOpen)
{
var state = await db.AppState.FirstAsync();
state.ResultsOpen = request.ResultsOpen;
state.ResultsOpen = resultsOpen;
state.UpdatedAt = DateTimeOffset.UtcNow;
await using var tx = await db.Database.BeginTransactionAsync();
if (request.ResultsOpen)
if (resultsOpen)
{
await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Results));
}
@@ -44,9 +44,9 @@ internal sealed class AdminWorkflowService(AppDbContext db)
return Results.Ok(new VoteStatusResponse(voters, ready, waiting));
}
public async Task<IResult> GrantJokerAsync(GrantJokerRequest request)
public async Task<IResult> GrantJokerAsync(Guid playerId)
{
var player = await db.Players.FirstOrDefaultAsync(p => p.Id == request.PlayerId);
var player = await db.Players.FirstOrDefaultAsync(p => p.Id == playerId);
if (player is null)
return EndpointHelpers.NotFoundError("Player not found.");
@@ -88,18 +88,18 @@ internal sealed class AdminWorkflowService(AppDbContext db)
return Results.Ok(new AdminDeletePlayerResponse(playerId));
}
public async Task<IResult> LinkSuggestionsAsync(Player adminPlayer, LinkSuggestionsRequest request)
public async Task<IResult> LinkSuggestionsAsync(Guid adminPlayerId, int sourceSuggestionId, int targetSuggestionId)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, adminPlayer.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, adminPlayerId);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
if (request.SourceSuggestionId == request.TargetSuggestionId)
if (sourceSuggestionId == targetSuggestionId)
return EndpointHelpers.BadRequestError("Pick two different games to link.");
var suggestions = await db.Suggestions.ToListAsync();
var source = suggestions.FirstOrDefault(s => s.Id == request.SourceSuggestionId);
var target = suggestions.FirstOrDefault(s => s.Id == request.TargetSuggestionId);
var source = suggestions.FirstOrDefault(s => s.Id == sourceSuggestionId);
var target = suggestions.FirstOrDefault(s => s.Id == targetSuggestionId);
if (source is null || target is null)
return EndpointHelpers.NotFoundError("Suggestion not found.");
@@ -143,14 +143,14 @@ internal sealed class AdminWorkflowService(AppDbContext db)
return Results.Ok(new AdminLinkSuggestionsResponse(targetRoot, affectedIds, await db.Players.CountAsync()));
}
public async Task<IResult> UnlinkSuggestionsAsync(Player adminPlayer, UnlinkSuggestionsRequest request)
public async Task<IResult> UnlinkSuggestionsAsync(Guid adminPlayerId, int suggestionId)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, adminPlayer.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, adminPlayerId);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
var suggestions = await db.Suggestions.ToListAsync();
var target = suggestions.FirstOrDefault(s => s.Id == request.SuggestionId);
var target = suggestions.FirstOrDefault(s => s.Id == suggestionId);
if (target is null)
return Results.Ok(new AdminUnlinkSuggestionsResponse(Array.Empty<int>(), 0));

View File

@@ -18,7 +18,7 @@ public static class ResultsEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.GetResultsAsync(player);
return await service.GetResultsAsync(player.Id);
});
}
}

View File

@@ -7,13 +7,13 @@ namespace GameList.Endpoints;
internal sealed class ResultsWorkflowService(AppDbContext db)
{
public async Task<IResult> GetResultsAsync(Player player)
public async Task<IResult> GetResultsAsync(Guid playerId)
{
var appState = await db.AppState.AsNoTracking().FirstAsync();
if (!appState.ResultsOpen)
return EndpointHelpers.BadRequestError("Results are locked until the admin enables them.");
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase != Phase.Results)
return EndpointHelpers.PhaseMismatch(Phase.Results, phase);
@@ -33,7 +33,7 @@ internal sealed class ResultsWorkflowService(AppDbContext db)
Average = s.Votes.Count == 0 ? 0 : s.Votes.Average(v => v.Score),
Votes = s.Votes.Select(v => v.Score).ToList(),
MyVote = s.Votes
.Where(v => v.PlayerId == player.Id)
.Where(v => v.PlayerId == playerId)
.Select(v => (int?)v.Score)
.FirstOrDefault(),
s.ScreenshotUrl,

View File

@@ -17,7 +17,7 @@ public static class SuggestEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.GetMineAsync(player);
return await service.GetMineAsync(player.Id);
});
group.MapPost("/", async ([FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db, SuggestionWorkflowService service) =>
@@ -26,7 +26,19 @@ public static class SuggestEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.CreateAsync(player, request);
return await service.CreateAsync(
player.Id,
new SuggestionInput(
request.Name,
request.Genre,
request.Description,
request.ScreenshotUrl,
request.YoutubeUrl,
request.GameUrl,
request.MinPlayers,
request.MaxPlayers
)
);
}).AddEndpointFilter(new PhaseOrJokerFilter());
group.MapDelete("/{id:int}", async (int id, HttpContext ctx, AppDbContext db, SuggestionWorkflowService service) =>
@@ -35,8 +47,7 @@ public static class SuggestEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
var isAdmin = await EndpointHelpers.IsAdmin(ctx, db);
return await service.DeleteAsync(player, isAdmin, id);
return await service.DeleteAsync(player.Id, id);
});
group.MapPut("/{id:int}", async (int id, [FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db, SuggestionWorkflowService service) =>
@@ -45,8 +56,20 @@ public static class SuggestEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
var isAdmin = player.IsAdmin;
return await service.UpdateAsync(player, isAdmin, id, request);
return await service.UpdateAsync(
player.Id,
id,
new SuggestionInput(
request.Name,
request.Genre,
request.Description,
request.ScreenshotUrl,
request.YoutubeUrl,
request.GameUrl,
request.MinPlayers,
request.MaxPlayers
)
);
});
group.MapGet("/all", async (HttpContext ctx, AppDbContext db, SuggestionWorkflowService service) =>
@@ -55,7 +78,7 @@ public static class SuggestEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.GetAllAsync(player);
return await service.GetAllAsync(player.Id);
});
}
}

View File

@@ -0,0 +1,12 @@
namespace GameList.Endpoints;
internal readonly record struct SuggestionInput(
string Name,
string? Genre,
string? Description,
string? ScreenshotUrl,
string? YoutubeUrl,
string? GameUrl,
int? MinPlayers,
int? MaxPlayers
);

View File

@@ -1,27 +1,25 @@
using GameList.Contracts;
namespace GameList.Endpoints;
internal static class SuggestionValidator
{
public static async Task<string?> ValidateAsync(SuggestionRequest request, IHttpClientFactory httpFactory)
public static async Task<string?> ValidateAsync(SuggestionInput input, IHttpClientFactory httpFactory)
{
if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100)
if (string.IsNullOrWhiteSpace(input.Name) || input.Name.Length > 100)
return "Name is required and must be <= 100 characters.";
if (!EndpointHelpers.IsValidImageUrl(request.ScreenshotUrl))
if (!EndpointHelpers.IsValidImageUrl(input.ScreenshotUrl))
return "Screenshot URL must be http(s) and end with an image file extension.";
if (!await EndpointHelpers.IsReachableImageAsync(request.ScreenshotUrl, httpFactory))
if (!await EndpointHelpers.IsReachableImageAsync(input.ScreenshotUrl, httpFactory))
return "Screenshot URL could not be validated as an image. Use a public image link (http/https, no redirects, max 5 MB).";
if (!EndpointHelpers.IsValidHttpUrl(request.GameUrl))
if (!EndpointHelpers.IsValidHttpUrl(input.GameUrl))
return "Game URL must be http or https.";
if (!EndpointHelpers.IsValidHttpUrl(request.YoutubeUrl))
if (!EndpointHelpers.IsValidHttpUrl(input.YoutubeUrl))
return "YouTube URL must be http or https.";
return ValidatePlayers(request.MinPlayers, request.MaxPlayers);
return ValidatePlayers(input.MinPlayers, input.MaxPlayers);
}
private static string? ValidatePlayers(int? minPlayers, int? maxPlayers)

View File

@@ -7,11 +7,11 @@ namespace GameList.Endpoints;
internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFactory httpFactory)
{
public async Task<IResult> GetMineAsync(Player player)
public async Task<IResult> GetMineAsync(Guid playerId)
{
var mine = await db.Suggestions
.AsNoTracking()
.Where(s => s.PlayerId == player.Id)
.Where(s => s.PlayerId == playerId)
.Select(s => new
{
s.Id,
@@ -36,35 +36,45 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
return Results.Ok(ordered);
}
public async Task<IResult> CreateAsync(Player player, SuggestionRequest request)
public async Task<IResult> CreateAsync(Guid playerId, SuggestionInput input)
{
var validationError = await SuggestionValidator.ValidateAsync(request, httpFactory);
var validationError = await SuggestionValidator.ValidateAsync(input, httpFactory);
if (validationError is not null)
return EndpointHelpers.BadRequestError(validationError);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var usingJoker = phase == Phase.Vote && player.HasJoker;
var playerState = await db.Players
.AsNoTracking()
.Where(p => p.Id == playerId)
.Select(p => new
{
p.DisplayName,
p.HasJoker
})
.FirstAsync();
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
var usingJoker = phase == Phase.Vote && playerState.HasJoker;
if (phase != Phase.Suggest && !usingJoker)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
if (string.IsNullOrWhiteSpace(player.DisplayName))
if (string.IsNullOrWhiteSpace(playerState.DisplayName))
return EndpointHelpers.BadRequestError("Set a display name before submitting suggestions.");
var existingCount = await db.Suggestions.CountAsync(s => s.PlayerId == player.Id);
var existingCount = await db.Suggestions.CountAsync(s => s.PlayerId == playerId);
if (!usingJoker && existingCount >= 5)
return EndpointHelpers.BadRequestError("You have reached the 5 suggestion limit.");
var suggestion = new Suggestion
{
PlayerId = player.Id,
Name = request.Name.Trim(),
Genre = EndpointHelpers.TrimTo(request.Genre, 50),
Description = EndpointHelpers.TrimTo(request.Description, 500),
ScreenshotUrl = EndpointHelpers.TrimTo(request.ScreenshotUrl, 2048),
YoutubeUrl = EndpointHelpers.TrimTo(request.YoutubeUrl, 2048),
GameUrl = EndpointHelpers.TrimTo(request.GameUrl, 2048),
MinPlayers = request.MinPlayers,
MaxPlayers = request.MaxPlayers
PlayerId = playerId,
Name = input.Name.Trim(),
Genre = EndpointHelpers.TrimTo(input.Genre, 50),
Description = EndpointHelpers.TrimTo(input.Description, 500),
ScreenshotUrl = EndpointHelpers.TrimTo(input.ScreenshotUrl, 2048),
YoutubeUrl = EndpointHelpers.TrimTo(input.YoutubeUrl, 2048),
GameUrl = EndpointHelpers.TrimTo(input.GameUrl, 2048),
MinPlayers = input.MinPlayers,
MaxPlayers = input.MaxPlayers
};
await using var tx = await db.Database.BeginTransactionAsync();
@@ -73,7 +83,9 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
if (usingJoker)
{
player.HasJoker = false;
await db.Players
.Where(p => p.Id == playerId)
.ExecuteUpdateAsync(p => p.SetProperty(x => x.HasJoker, false));
await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
}
@@ -83,18 +95,28 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
return Results.Created($"/api/suggestions/{suggestion.Id}", new SuggestionCreatedResponse(suggestion.Id));
}
public async Task<IResult> DeleteAsync(Player player, bool isAdmin, int suggestionId)
public async Task<IResult> DeleteAsync(Guid playerId, int suggestionId)
{
var actor = await db.Players
.AsNoTracking()
.Where(p => p.Id == playerId)
.Select(p => new
{
p.IsAdmin
})
.FirstAsync();
var isAdmin = actor.IsAdmin;
if (!isAdmin)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase != Phase.Suggest)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
}
var suggestion = isAdmin
? await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId)
: await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId && s.PlayerId == player.Id);
: await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId && s.PlayerId == playerId);
if (suggestion == null)
return EndpointHelpers.NotFoundError("Suggestion not found.");
@@ -112,40 +134,50 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
return Results.NoContent();
}
public async Task<IResult> UpdateAsync(Player player, bool isAdmin, int suggestionId, SuggestionRequest request)
public async Task<IResult> UpdateAsync(Guid playerId, int suggestionId, SuggestionInput input)
{
var validationError = await SuggestionValidator.ValidateAsync(request, httpFactory);
var validationError = await SuggestionValidator.ValidateAsync(input, httpFactory);
if (validationError is not null)
return EndpointHelpers.BadRequestError(validationError);
var actor = await db.Players
.AsNoTracking()
.Where(p => p.Id == playerId)
.Select(p => new
{
p.IsAdmin
})
.FirstAsync();
var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId);
if (suggestion == null)
return EndpointHelpers.NotFoundError("Suggestion not found.");
var isAdmin = actor.IsAdmin;
if (!isAdmin)
{
if (suggestion.PlayerId != player.Id)
if (suggestion.PlayerId != playerId)
return EndpointHelpers.UnauthorizedError();
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase == Phase.Results)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
if (phase == Phase.Suggest)
{
suggestion.Name = request.Name.Trim();
suggestion.Name = input.Name.Trim();
}
else if (phase != Phase.Vote)
{
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
}
ApplyEditableFields(suggestion, request);
ApplyEditableFields(suggestion, input);
}
else
{
suggestion.Name = request.Name.Trim();
ApplyEditableFields(suggestion, request);
suggestion.Name = input.Name.Trim();
ApplyEditableFields(suggestion, input);
}
await db.SaveChangesAsync();
@@ -163,9 +195,9 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
));
}
public async Task<IResult> GetAllAsync(Player player)
public async Task<IResult> GetAllAsync(Guid playerId)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase < Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -186,7 +218,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
Author = s.Player!.DisplayName,
s.CreatedAt,
s.ParentSuggestionId,
IsOwner = s.PlayerId == player.Id
IsOwner = s.PlayerId == playerId
})
.ToListAsync();
@@ -219,14 +251,14 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
return Results.Ok(ordered);
}
private static void ApplyEditableFields(Suggestion suggestion, SuggestionRequest request)
private static void ApplyEditableFields(Suggestion suggestion, SuggestionInput input)
{
suggestion.Genre = EndpointHelpers.TrimTo(request.Genre, 50);
suggestion.Description = EndpointHelpers.TrimTo(request.Description, 500);
suggestion.ScreenshotUrl = EndpointHelpers.TrimTo(request.ScreenshotUrl, 2048);
suggestion.YoutubeUrl = EndpointHelpers.TrimTo(request.YoutubeUrl, 2048);
suggestion.GameUrl = EndpointHelpers.TrimTo(request.GameUrl, 2048);
suggestion.MinPlayers = request.MinPlayers;
suggestion.MaxPlayers = request.MaxPlayers;
suggestion.Genre = EndpointHelpers.TrimTo(input.Genre, 50);
suggestion.Description = EndpointHelpers.TrimTo(input.Description, 500);
suggestion.ScreenshotUrl = EndpointHelpers.TrimTo(input.ScreenshotUrl, 2048);
suggestion.YoutubeUrl = EndpointHelpers.TrimTo(input.YoutubeUrl, 2048);
suggestion.GameUrl = EndpointHelpers.TrimTo(input.GameUrl, 2048);
suggestion.MinPlayers = input.MinPlayers;
suggestion.MaxPlayers = input.MaxPlayers;
}
}

View File

@@ -17,7 +17,7 @@ public static class VoteEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.GetMineAsync(player);
return await service.GetMineAsync(player.Id);
});
group.MapPost("/", async (VoteRequest request, HttpContext ctx, AppDbContext db, VoteWorkflowService service) =>
@@ -25,7 +25,7 @@ public static class VoteEndpoints
var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.UpsertAsync(player, request);
return await service.UpsertAsync(player.Id, request.SuggestionId, request.Score);
});
group.MapPost("/finalize", async (VoteFinalizeRequest request, HttpContext ctx, AppDbContext db, VoteWorkflowService service) =>
@@ -34,7 +34,7 @@ public static class VoteEndpoints
if (player is null)
return EndpointHelpers.UnauthorizedError();
return await service.SetFinalizeAsync(player, request);
return await service.SetFinalizeAsync(player.Id, request.Final);
});
}
}

View File

@@ -7,15 +7,15 @@ namespace GameList.Endpoints;
internal sealed class VoteWorkflowService(AppDbContext db)
{
public async Task<IResult> GetMineAsync(Player player)
public async Task<IResult> GetMineAsync(Guid playerId)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
var votes = await db.Votes
.AsNoTracking()
.Where(v => v.PlayerId == player.Id)
.Where(v => v.PlayerId == playerId)
.Select(v => new
{
v.SuggestionId,
@@ -26,19 +26,29 @@ internal sealed class VoteWorkflowService(AppDbContext db)
return Results.Ok(votes);
}
public async Task<IResult> UpsertAsync(Player player, VoteRequest request)
public async Task<IResult> UpsertAsync(Guid playerId, int suggestionId, int score)
{
if (request.Score is < 0 or > 10)
if (score is < 0 or > 10)
return EndpointHelpers.BadRequestError("Score must be between 0 and 10.");
if (player.VotesFinal)
var playerState = await db.Players
.AsNoTracking()
.Where(p => p.Id == playerId)
.Select(p => new
{
p.VotesFinal,
p.DisplayName
})
.FirstAsync();
if (playerState.VotesFinal)
return EndpointHelpers.BadRequestError("Votes are finalized. Unfinalize before changing scores.");
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
if (string.IsNullOrWhiteSpace(player.DisplayName))
if (string.IsNullOrWhiteSpace(playerState.DisplayName))
return EndpointHelpers.BadRequestError("Set a display name before voting.");
var linkMap = await db.Suggestions
@@ -50,46 +60,48 @@ internal sealed class VoteWorkflowService(AppDbContext db)
})
.ToListAsync();
var rootIndex = EndpointHelpers.BuildLinkRoots(linkMap.Select(s => (s.Id, s.ParentSuggestionId)));
if (!rootIndex.ContainsKey(request.SuggestionId))
if (!rootIndex.ContainsKey(suggestionId))
return EndpointHelpers.BadRequestError("Suggestion not found.");
var linkedIds = EndpointHelpers.LinkedIdsFor(request.SuggestionId, rootIndex);
var linkedIds = EndpointHelpers.LinkedIdsFor(suggestionId, rootIndex);
if (linkedIds.Count == 0)
linkedIds.Add(request.SuggestionId);
linkedIds.Add(suggestionId);
var existingVotes = await db.Votes
.Where(v => v.PlayerId == player.Id && linkedIds.Contains(v.SuggestionId))
.Where(v => v.PlayerId == playerId && linkedIds.Contains(v.SuggestionId))
.ToListAsync();
foreach (var suggestionId in linkedIds)
foreach (var linkedSuggestionId in linkedIds)
{
var vote = existingVotes.FirstOrDefault(v => v.SuggestionId == suggestionId);
var vote = existingVotes.FirstOrDefault(v => v.SuggestionId == linkedSuggestionId);
if (vote == null)
{
db.Votes.Add(new Vote
{
PlayerId = player.Id,
SuggestionId = suggestionId,
Score = request.Score
PlayerId = playerId,
SuggestionId = linkedSuggestionId,
Score = score
});
}
else
{
vote.Score = request.Score;
vote.Score = score;
}
}
await db.SaveChangesAsync();
return Results.Ok(new VoteUpsertResponse(linkedIds, request.Score));
return Results.Ok(new VoteUpsertResponse(linkedIds, score));
}
public async Task<IResult> SetFinalizeAsync(Player player, VoteFinalizeRequest request)
public async Task<IResult> SetFinalizeAsync(Guid playerId, bool final)
{
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
player.VotesFinal = request.Final;
var player = await db.Players.FirstAsync(p => p.Id == playerId);
player.VotesFinal = final;
await db.SaveChangesAsync();
return Results.Ok(new VoteFinalizeResponse(player.VotesFinal));
}