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

@@ -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;
}
}