Decouple workflow services from HTTP result types
This commit is contained in:
@@ -7,7 +7,7 @@ namespace GameList.Endpoints;
|
||||
|
||||
internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFactory httpFactory)
|
||||
{
|
||||
public async Task<IResult> GetMineAsync(Guid playerId)
|
||||
public async Task<ServiceResult<IReadOnlyList<SuggestionDto>>> GetMineAsync(Guid playerId)
|
||||
{
|
||||
var mine = await db.Suggestions
|
||||
.AsNoTracking()
|
||||
@@ -29,18 +29,19 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var ordered = mine
|
||||
IReadOnlyList<SuggestionDto> ordered = mine
|
||||
.OrderBy(s => s.CreatedAt)
|
||||
.Select(s => new SuggestionDto(s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.MinPlayers, s.MaxPlayers, s.ParentSuggestionId));
|
||||
.Select(s => new SuggestionDto(s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.MinPlayers, s.MaxPlayers, s.ParentSuggestionId))
|
||||
.ToList();
|
||||
|
||||
return Results.Ok(ordered);
|
||||
return ServiceResult<IReadOnlyList<SuggestionDto>>.Success(ordered);
|
||||
}
|
||||
|
||||
public async Task<IResult> CreateAsync(Guid playerId, SuggestionInput input)
|
||||
public async Task<ServiceResult<SuggestionCreatedResponse>> CreateAsync(Guid playerId, SuggestionInput input)
|
||||
{
|
||||
var validationError = await SuggestionValidator.ValidateAsync(input, httpFactory);
|
||||
if (validationError is not null)
|
||||
return EndpointHelpers.BadRequestError(validationError);
|
||||
return ServiceResult<SuggestionCreatedResponse>.Failure(ServiceError.BadRequest(validationError));
|
||||
|
||||
var playerState = await db.Players
|
||||
.AsNoTracking()
|
||||
@@ -55,14 +56,14 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
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);
|
||||
return ServiceResult<SuggestionCreatedResponse>.Failure(ServiceError.PhaseMismatch(Phase.Suggest, phase));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(playerState.DisplayName))
|
||||
return EndpointHelpers.BadRequestError("Set a display name before submitting suggestions.");
|
||||
return ServiceResult<SuggestionCreatedResponse>.Failure(ServiceError.BadRequest("Set a display name before submitting suggestions."));
|
||||
|
||||
var existingCount = await db.Suggestions.AsNoTracking().CountAsync(s => s.PlayerId == playerId);
|
||||
if (!usingJoker && existingCount >= 5)
|
||||
return EndpointHelpers.BadRequestError("You have reached the 5 suggestion limit.");
|
||||
return ServiceResult<SuggestionCreatedResponse>.Failure(ServiceError.BadRequest("You have reached the 5 suggestion limit."));
|
||||
|
||||
var suggestion = new Suggestion
|
||||
{
|
||||
@@ -97,13 +98,13 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
}
|
||||
catch (DbUpdateException ex) when (EndpointHelpers.IsSqliteConstraintViolation(ex, EndpointHelpers.SuggestionLimitTriggerError))
|
||||
{
|
||||
return EndpointHelpers.BadRequestError("You have reached the 5 suggestion limit.");
|
||||
return ServiceResult<SuggestionCreatedResponse>.Failure(ServiceError.BadRequest("You have reached the 5 suggestion limit."));
|
||||
}
|
||||
|
||||
return Results.Created($"/api/suggestions/{suggestion.Id}", new SuggestionCreatedResponse(suggestion.Id));
|
||||
return ServiceResult<SuggestionCreatedResponse>.Success(new SuggestionCreatedResponse(suggestion.Id));
|
||||
}
|
||||
|
||||
public async Task<IResult> DeleteAsync(Guid playerId, int suggestionId)
|
||||
public async Task<ServiceResult<Unit>> DeleteAsync(Guid playerId, int suggestionId)
|
||||
{
|
||||
var actor = await db.Players
|
||||
.AsNoTracking()
|
||||
@@ -119,14 +120,14 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
{
|
||||
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
|
||||
if (phase != Phase.Suggest)
|
||||
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
|
||||
return ServiceResult<Unit>.Failure(ServiceError.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 == playerId);
|
||||
if (suggestion == null)
|
||||
return EndpointHelpers.NotFoundError("Suggestion not found.");
|
||||
return ServiceResult<Unit>.Failure(ServiceError.NotFound("Suggestion not found."));
|
||||
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
|
||||
@@ -139,14 +140,14 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
db.Suggestions.Remove(suggestion);
|
||||
await db.SaveChangesAsync();
|
||||
await tx.CommitAsync();
|
||||
return Results.NoContent();
|
||||
return ServiceResult<Unit>.Success(default);
|
||||
}
|
||||
|
||||
public async Task<IResult> UpdateAsync(Guid playerId, int suggestionId, SuggestionInput input)
|
||||
public async Task<ServiceResult<SuggestionUpdatedResponse>> UpdateAsync(Guid playerId, int suggestionId, SuggestionInput input)
|
||||
{
|
||||
var validationError = await SuggestionValidator.ValidateAsync(input, httpFactory);
|
||||
if (validationError is not null)
|
||||
return EndpointHelpers.BadRequestError(validationError);
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Failure(ServiceError.BadRequest(validationError));
|
||||
|
||||
var actor = await db.Players
|
||||
.AsNoTracking()
|
||||
@@ -159,17 +160,17 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
|
||||
var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId);
|
||||
if (suggestion == null)
|
||||
return EndpointHelpers.NotFoundError("Suggestion not found.");
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Failure(ServiceError.NotFound("Suggestion not found."));
|
||||
|
||||
var isAdmin = actor.IsAdmin;
|
||||
if (!isAdmin)
|
||||
{
|
||||
if (suggestion.PlayerId != playerId)
|
||||
return EndpointHelpers.UnauthorizedError();
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Failure(ServiceError.Unauthorized());
|
||||
|
||||
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
|
||||
if (phase == Phase.Results)
|
||||
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Failure(ServiceError.PhaseMismatch(Phase.Suggest, phase));
|
||||
|
||||
if (phase == Phase.Suggest)
|
||||
{
|
||||
@@ -177,7 +178,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
}
|
||||
else if (phase != Phase.Vote)
|
||||
{
|
||||
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Failure(ServiceError.PhaseMismatch(Phase.Suggest, phase));
|
||||
}
|
||||
|
||||
ApplyEditableFields(suggestion, input);
|
||||
@@ -190,7 +191,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Results.Ok(new SuggestionUpdatedResponse(
|
||||
return ServiceResult<SuggestionUpdatedResponse>.Success(new SuggestionUpdatedResponse(
|
||||
suggestion.Id,
|
||||
suggestion.Name,
|
||||
suggestion.Genre,
|
||||
@@ -203,11 +204,11 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
));
|
||||
}
|
||||
|
||||
public async Task<IResult> GetAllAsync(Guid playerId)
|
||||
public async Task<ServiceResult<IReadOnlyList<SuggestionAllDto>>> GetAllAsync(Guid playerId)
|
||||
{
|
||||
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId);
|
||||
if (phase < Phase.Vote)
|
||||
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
|
||||
return ServiceResult<IReadOnlyList<SuggestionAllDto>>.Failure(ServiceError.PhaseMismatch(Phase.Vote, phase));
|
||||
|
||||
var all = await db.Suggestions
|
||||
.AsNoTracking()
|
||||
@@ -233,12 +234,11 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
var rootIndex = EndpointHelpers.BuildLinkRoots(all.Select(s => (s.Id, s.ParentSuggestionId)));
|
||||
var nameLookup = all.ToDictionary(s => s.Id, s => s.Name);
|
||||
|
||||
var ordered = all.OrderBy(s => s.CreatedAt).Select(s =>
|
||||
IReadOnlyList<SuggestionAllDto> ordered = all.OrderBy(s => s.CreatedAt).Select(s =>
|
||||
{
|
||||
var linkedIds = EndpointHelpers.LinkedIdsFor(s.Id, rootIndex).Where(id => id != s.Id).ToList();
|
||||
|
||||
return new
|
||||
{
|
||||
return new SuggestionAllDto(
|
||||
s.Id,
|
||||
s.Name,
|
||||
s.Genre,
|
||||
@@ -251,12 +251,12 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
||||
s.Author,
|
||||
s.ParentSuggestionId,
|
||||
s.IsOwner,
|
||||
LinkedIds = linkedIds,
|
||||
LinkedTitles = linkedIds.Where(nameLookup.ContainsKey).Select(id => nameLookup[id]).ToList()
|
||||
};
|
||||
});
|
||||
linkedIds,
|
||||
linkedIds.Where(nameLookup.ContainsKey).Select(id => nameLookup[id]).ToList()
|
||||
);
|
||||
}).ToList();
|
||||
|
||||
return Results.Ok(ordered);
|
||||
return ServiceResult<IReadOnlyList<SuggestionAllDto>>.Success(ordered);
|
||||
}
|
||||
|
||||
private static void ApplyEditableFields(Suggestion suggestion, SuggestionInput input)
|
||||
|
||||
Reference in New Issue
Block a user