Introduce typed API responses and align workflow outputs

This commit is contained in:
2026-02-07 01:19:51 +01:00
parent 35d842d6ee
commit 79dc8f899f
7 changed files with 99 additions and 77 deletions

View File

@@ -27,11 +27,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await db.SaveChangesAsync();
await tx.CommitAsync();
var currentState = await db.AppState.AsNoTracking().FirstAsync();
return Results.Ok(new
{
currentState.ResultsOpen,
currentState.UpdatedAt
});
return Results.Ok(new AdminResultsStateResponse(currentState.ResultsOpen, currentState.UpdatedAt));
}
public async Task<IResult> GetVoteStatusAsync()
@@ -45,12 +41,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
var waiting = voters.Where(v => !v.Finalized).Select(v => v.Name).ToList();
var ready = waiting.Count == 0;
return Results.Ok(new
{
voters,
ready,
waiting
});
return Results.Ok(new VoteStatusResponse(voters, ready, waiting));
}
public async Task<IResult> GrantJokerAsync(GrantJokerRequest request)
@@ -67,11 +58,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
player.VotesFinal = false;
await db.SaveChangesAsync();
return Results.Ok(new
{
player.Id,
player.HasJoker
});
return Results.Ok(new AdminGrantJokerResponse(player.Id, player.HasJoker));
}
public async Task<IResult> DeletePlayerAsync(Guid playerId)
@@ -98,7 +85,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await db.SaveChangesAsync();
await tx.CommitAsync();
return Results.Ok(new { DeletedPlayerId = playerId });
return Results.Ok(new AdminDeletePlayerResponse(playerId));
}
public async Task<IResult> LinkSuggestionsAsync(Player adminPlayer, LinkSuggestionsRequest request)
@@ -153,12 +140,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await tx.CommitAsync();
return Results.Ok(new
{
RootId = targetRoot,
LinkedSuggestionIds = affectedIds,
UnfinalizedPlayers = await db.Players.CountAsync()
});
return Results.Ok(new AdminLinkSuggestionsResponse(targetRoot, affectedIds, await db.Players.CountAsync()));
}
public async Task<IResult> UnlinkSuggestionsAsync(Player adminPlayer, UnlinkSuggestionsRequest request)
@@ -170,19 +152,11 @@ internal sealed class AdminWorkflowService(AppDbContext db)
var suggestions = await db.Suggestions.ToListAsync();
var target = suggestions.FirstOrDefault(s => s.Id == request.SuggestionId);
if (target is null)
return Results.Ok(new
{
UnlinkedSuggestionIds = Array.Empty<int>(),
UnfinalizedPlayers = 0
});
return Results.Ok(new AdminUnlinkSuggestionsResponse(Array.Empty<int>(), 0));
var rootIndex = EndpointHelpers.BuildLinkRoots(suggestions.Select(s => (s.Id, s.ParentSuggestionId)));
if (!rootIndex.TryGetValue(target.Id, out var rootId))
return Results.Ok(new
{
UnlinkedSuggestionIds = Array.Empty<int>(),
UnfinalizedPlayers = 0
});
return Results.Ok(new AdminUnlinkSuggestionsResponse(Array.Empty<int>(), 0));
var groupIds = rootIndex.Where(kv => kv.Value == rootId).Select(kv => kv.Key).ToList();
@@ -201,11 +175,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await tx.CommitAsync();
return Results.Ok(new
{
UnlinkedSuggestionIds = groupIds,
UnfinalizedPlayers = await db.Players.CountAsync()
});
return Results.Ok(new AdminUnlinkSuggestionsResponse(groupIds, await db.Players.CountAsync()));
}
public async Task<IResult> ResetAsync()
@@ -222,12 +192,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await db.SaveChangesAsync();
await tx.CommitAsync();
return Results.Ok(new
{
Phase = Phase.Suggest,
state.ResultsOpen,
state.UpdatedAt
});
return Results.Ok(new AdminResetStateResponse(Phase.Suggest, state.ResultsOpen, state.UpdatedAt));
}
public async Task<IResult> FactoryResetAsync()
@@ -245,11 +210,6 @@ internal sealed class AdminWorkflowService(AppDbContext db)
await tx.CommitAsync();
return Results.Ok(new
{
Phase = Phase.Suggest,
fresh.ResultsOpen,
fresh.UpdatedAt
});
return Results.Ok(new AdminResetStateResponse(Phase.Suggest, fresh.ResultsOpen, fresh.UpdatedAt));
}
}

View File

@@ -1,3 +1,4 @@
using GameList.Contracts;
using GameList.Data;
using GameList.Domain;
using Microsoft.EntityFrameworkCore;
@@ -54,8 +55,12 @@ internal sealed class ResultsWorkflowService(AppDbContext db)
.Where(id => id != r.Id)
.ToList();
return new
{
var linkedTitles = linkedIds
.Where(nameLookup.ContainsKey)
.Select(id => nameLookup[id])
.ToList();
return new ResultItemDto(
r.Id,
r.Name,
r.Author,
@@ -72,12 +77,9 @@ internal sealed class ResultsWorkflowService(AppDbContext db)
r.Description,
r.Genre,
r.ParentSuggestionId,
LinkedIds = linkedIds,
LinkedTitles = linkedIds
.Where(nameLookup.ContainsKey)
.Select(id => nameLookup[id])
.ToList()
};
linkedIds,
linkedTitles
);
});
return Results.Ok(shaped);

View File

@@ -80,7 +80,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
await db.SaveChangesAsync();
await tx.CommitAsync();
return Results.Created($"/api/suggestions/{suggestion.Id}", new { suggestion.Id });
return Results.Created($"/api/suggestions/{suggestion.Id}", new SuggestionCreatedResponse(suggestion.Id));
}
public async Task<IResult> DeleteAsync(Player player, bool isAdmin, int suggestionId)
@@ -150,8 +150,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
await db.SaveChangesAsync();
return Results.Ok(new
{
return Results.Ok(new SuggestionUpdatedResponse(
suggestion.Id,
suggestion.Name,
suggestion.Genre,
@@ -161,7 +160,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
suggestion.GameUrl,
suggestion.MinPlayers,
suggestion.MaxPlayers
});
));
}
public async Task<IResult> GetAllAsync(Player player)

View File

@@ -80,11 +80,7 @@ internal sealed class VoteWorkflowService(AppDbContext db)
}
await db.SaveChangesAsync();
return Results.Ok(new
{
SuggestionIds = linkedIds,
request.Score
});
return Results.Ok(new VoteUpsertResponse(linkedIds, request.Score));
}
public async Task<IResult> SetFinalizeAsync(Player player, VoteFinalizeRequest request)
@@ -95,6 +91,6 @@ internal sealed class VoteWorkflowService(AppDbContext db)
player.VotesFinal = request.Final;
await db.SaveChangesAsync();
return Results.Ok(new { player.VotesFinal });
return Results.Ok(new VoteFinalizeResponse(player.VotesFinal));
}
}