using GameList.Contracts; using GameList.Data; using GameList.Domain; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace GameList.Endpoints; public static class SuggestEndpoints { public static void MapSuggestEndpoints(this IEndpointRouteBuilder app) { app.MapGet("/api/suggestions/mine", async (HttpContext ctx, AppDbContext db) => { var phase = await EndpointHelpers.GetPhase(db); if (phase != Phase.Suggest) return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase); var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return Results.Unauthorized(); var mine = await db.Suggestions.AsNoTracking() .Where(s => s.PlayerId == player.Id) .Select(s => new { s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.CreatedAt }) .ToListAsync(); var ordered = mine .OrderBy(s => s.CreatedAt) .Select(s => new SuggestionDto(s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl)); return Results.Ok(ordered); }); app.MapPost("/api/suggestions", async ([FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db) => { var phase = await EndpointHelpers.GetPhase(db); if (phase != Phase.Suggest) return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase); if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100) { return Results.BadRequest(new { error = "Name is required and must be <= 100 characters." }); } var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return Results.Unauthorized(); if (string.IsNullOrWhiteSpace(player.DisplayName)) { return Results.BadRequest(new { error = "Set a display name before submitting suggestions." }); } var existingCount = await db.Suggestions.CountAsync(s => s.PlayerId == player.Id); if (existingCount >= 3) { return Results.BadRequest(new { error = "You have reached the 3 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) }; db.Suggestions.Add(suggestion); await db.SaveChangesAsync(); return Results.Created($"/api/suggestions/{suggestion.Id}", new { suggestion.Id }); }); app.MapDelete("/api/suggestions/{id:int}", async (int id, HttpContext ctx, AppDbContext db) => { var phase = await EndpointHelpers.GetPhase(db); if (phase != Phase.Suggest) return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase); var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return Results.Unauthorized(); var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id && s.PlayerId == player.Id); if (suggestion == null) return Results.NotFound(new { error = "Suggestion not found." }); db.Suggestions.Remove(suggestion); await db.SaveChangesAsync(); return Results.NoContent(); }); app.MapPut("/api/suggestions/{id:int}", async (int id, [FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db, IConfiguration config) => { var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); var isAdmin = await EndpointHelpers.IsAdmin(ctx, db, config); if (!isAdmin) { if (player is null) return Results.Unauthorized(); var phase = await EndpointHelpers.GetPhase(db); if (phase != Phase.Suggest) return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase); } if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100) { return Results.BadRequest(new { error = "Name is required and must be <= 100 characters." }); } var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id); if (suggestion == null) return Results.NotFound(new { error = "Suggestion not found." }); if (!isAdmin) { if (suggestion.PlayerId != player!.Id) return Results.Unauthorized(); } suggestion.Name = request.Name.Trim(); 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); await db.SaveChangesAsync(); return Results.Ok(new { suggestion.Id, suggestion.Name, suggestion.Genre, suggestion.Description, suggestion.ScreenshotUrl, suggestion.YoutubeUrl, suggestion.GameUrl }); }); app.MapGet("/api/suggestions/all", async (HttpContext ctx, AppDbContext db) => { var phase = await EndpointHelpers.GetPhase(db); if (phase < Phase.Reveal) return EndpointHelpers.PhaseMismatch(Phase.Reveal, phase); var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return Results.Unauthorized(); var all = await db.Suggestions.AsNoTracking() .Include(s => s.Player) .Select(s => new { s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, Author = s.Player!.DisplayName, s.CreatedAt }) .ToListAsync(); var ordered = all .OrderBy(s => s.CreatedAt) .Select(s => new { s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.Author }); return Results.Ok(ordered); }); } }