Compare commits

...

3 Commits

5 changed files with 285 additions and 4 deletions

View File

@@ -0,0 +1,37 @@
using SDG_Backend_Barracuda.Models;
namespace SDG_Backend_Barracuda.Games;
public class Undercover : IEndpointRouteHandler
{
public static void MapEndpoints(IEndpointRouteBuilder routes)
{
var undercover = routes.MapGroup("/undercover");
undercover.MapGet("/decks", async (IDeckModel deckModel) =>
{
var decks = await deckModel.GetAll();
var undercoverDecks = decks.Where(d => d.GameList?.Any(g => g.Name == "Undercover") ?? false);
return Results.Ok(undercoverDecks.Select(d => new { id = d.Id, name = d.Name }));
});
undercover.MapGet("/deck/{deckId}", async (int deckId, IDeckModel deckModel, ICardListModel cardListModel) =>
{
var deck = await deckModel.GetById(deckId);
if (deck == null)
{
return Results.NotFound();
}
var cardList = await cardListModel.GetById(deck.CardListId);
if (cardList == null)
{
return Results.NotFound();
}
var cardValues = cardList.Cards?.Select(c => c.Value) ?? [];
return Results.Ok(new { cards = cardValues });
});
}
}

View File

@@ -0,0 +1,148 @@
using Dapper;
using Npgsql;
namespace SDG_Backend_Barracuda.Models;
public record Deck
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public int CardListId { get; init; }
public IEnumerable<Game>? GameList { get; init; }
public Deck() { }
public Deck(int id, string name, int cardListId, IEnumerable<Game>? gameList = null)
{
Id = id;
Name = name;
CardListId = cardListId;
GameList = gameList;
}
}
public record DeckInformation(string Name, int CardListId, IEnumerable<int>? GameIds = null);
public interface IDeckModel : IDatabaseModel<Deck, DeckInformation>
{
}
public class DeckModel(NpgsqlDataSource dataSource) : IDeckModel, IEndpointRouteHandler
{
public static void MapEndpoints(IEndpointRouteBuilder router)
{
var deckEndpoint = router.MapGroup("/deck");
// Create
deckEndpoint.MapPost("/", async (DeckInformation input, IDeckModel model) =>
{
var newDeck = await model.Create(input);
return Results.Created($"/deck/{newDeck.Id}", newDeck);
});
// Get all
deckEndpoint.MapGet("/", async (IDeckModel model) =>
Results.Ok(await model.GetAll()));
// Get one
deckEndpoint.MapGet("/{id}", async (int id, IDeckModel model) =>
await model.GetById(id) is { } d ? Results.Ok(d) : Results.NotFound());
// Update
deckEndpoint.MapPut("/{id}", async (int id, DeckInformation input, IDeckModel model) =>
await model.Update(id, input) is { } d ? Results.Ok(d) : Results.NotFound());
// Delete
deckEndpoint.MapDelete("/{id}", async (int id, IDeckModel model) =>
await model.Delete(id) ? Results.NoContent() : Results.NotFound());
}
public async Task<IEnumerable<Deck>> GetAll()
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """SELECT * FROM "Deck";""";
var decks = await connection.QueryAsync<Deck>(sql);
var result = new List<Deck>();
foreach (var deck in decks)
{
result.Add(await GetById(deck.Id) ?? deck);
}
return result;
}
public async Task<Deck?> GetById(int id)
{
await using var connection = await dataSource.OpenConnectionAsync();
var deckSql = """SELECT * FROM "Deck" WHERE "Id" = @Id;""";
var deck = await connection.QueryFirstOrDefaultAsync<Deck>(deckSql, new { Id = id });
if (deck == null) return null;
var gamesSql = """
SELECT g.*
FROM "Game" g
JOIN "DeckGame" dg ON g."Id" = dg."GameId"
WHERE dg."DeckId" = @Id;
""";
var games = await connection.QueryAsync<Game>(gamesSql, new { Id = id });
return deck with { GameList = games };
}
public async Task<Deck> Create(DeckInformation input)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """
INSERT INTO "Deck" ("Name", "CardListId")
VALUES (@Name, @CardListId)
RETURNING "Id", "Name", "CardListId";
""";
var newDeck = await connection.QuerySingleAsync<Deck>(sql, new { input.Name, input.CardListId });
if (input.GameIds != null)
{
foreach (var gameId in input.GameIds)
{
await connection.ExecuteAsync(
"""INSERT INTO "DeckGame" ("DeckId", "GameId") VALUES (@DeckId, @GameId)""",
new { DeckId = newDeck.Id, GameId = gameId });
}
}
return (await GetById(newDeck.Id))!;
}
public async Task<Deck?> Update(int id, DeckInformation input)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """
UPDATE "Deck"
SET "Name" = @Name, "CardListId" = @CardListId
WHERE "Id" = @Id
RETURNING "Id", "Name", "CardListId";
""";
var updatedDeck = await connection.QueryFirstOrDefaultAsync<Deck>(sql, new { Id = id, input.Name, input.CardListId });
if (updatedDeck == null) return null;
await connection.ExecuteAsync("""DELETE FROM "DeckGame" WHERE "DeckId" = @Id""", new { Id = id });
if (input.GameIds != null)
{
foreach (var gameId in input.GameIds)
{
await connection.ExecuteAsync(
"""INSERT INTO "DeckGame" ("DeckId", "GameId") VALUES (@Id, @GameId)""",
new { Id = id, GameId = gameId });
}
}
return await GetById(id);
}
public async Task<bool> Delete(int id)
{
await using var connection = await dataSource.OpenConnectionAsync();
var rowsAffected = await connection.ExecuteAsync("""DELETE FROM "Deck" WHERE "Id" = @Id""", new { Id = id });
return rowsAffected > 0;
}
}

View File

@@ -0,0 +1,73 @@
using Dapper;
using Npgsql;
namespace SDG_Backend_Barracuda.Models;
public record Game(int Id, string Name, string URL);
public record GameInformation(string Name, string URL);
public interface IGameModel : IDatabaseModel<Game, GameInformation>
{
}
public class GameModel(NpgsqlDataSource dataSource) : IGameModel, IEndpointRouteHandler
{
public static void MapEndpoints(IEndpointRouteBuilder router)
{
var gameEndpoint = router.MapGroup("/game");
gameEndpoint.MapPost("/", async (GameInformation input, IGameModel model) =>
{
var newGame = await model.Create(input);
return Results.Created($"/game/{newGame.Id}", newGame);
});
gameEndpoint.MapGet("/", async (IGameModel model) =>
Results.Ok(await model.GetAll()));
gameEndpoint.MapGet("/{id}", async (int id, IGameModel model) =>
await model.GetById(id) is { } g ? Results.Ok(g) : Results.NotFound());
gameEndpoint.MapPut("/{id}", async (int id, GameInformation input, IGameModel model) =>
await model.Update(id, input) is { } g ? Results.Ok(g) : Results.NotFound());
gameEndpoint.MapDelete("/{id}", async (int id, IGameModel model) =>
await model.Delete(id) ? Results.NoContent() : Results.NotFound());
}
public async Task<IEnumerable<Game>> GetAll()
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """SELECT * FROM "Game";""";
return await connection.QueryAsync<Game>(sql);
}
public async Task<Game?> GetById(int id)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """SELECT * FROM "Game" WHERE "Id" = @Id;""";
return await connection.QueryFirstOrDefaultAsync<Game>(sql, new { Id = id });
}
public async Task<Game> Create(GameInformation input)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """INSERT INTO "Game" ("Name", "URL") VALUES (@Name, @URL) RETURNING "Id", "Name", "URL";""";
return await connection.QuerySingleAsync<Game>(sql, input);
}
public async Task<Game?> Update(int id, GameInformation input)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """UPDATE "Game" SET "Name" = @Name, "URL" = @URL WHERE "Id" = @Id RETURNING "Id", "Name", "URL";""";
return await connection.QueryFirstOrDefaultAsync<Game>(sql, new { Id = id, input.Name, input.URL });
}
public async Task<bool> Delete(int id)
{
await using var connection = await dataSource.OpenConnectionAsync();
var sql = """DELETE FROM "Game" WHERE "Id" = @Id;""";
var rowsAffected = await connection.ExecuteAsync(sql, new { Id = id });
return rowsAffected > 0;
}
}

View File

@@ -1,6 +1,7 @@
using Dapper; using Dapper;
using Npgsql; using Npgsql;
using SDG_Backend_Barracuda.Models; using SDG_Backend_Barracuda.Models;
using SDG_Backend_Barracuda.Games;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -15,6 +16,8 @@ builder.Services.AddNpgsqlDataSource(connectionString!);
// Register Data Tables // Register Data Tables
builder.Services.AddScoped<ICardModel, CardModel>(); builder.Services.AddScoped<ICardModel, CardModel>();
builder.Services.AddScoped<ICardListModel, CardListModel>(); builder.Services.AddScoped<ICardListModel, CardListModel>();
builder.Services.AddScoped<IGameModel, GameModel>();
builder.Services.AddScoped<IDeckModel, DeckModel>();
var app = builder.Build(); var app = builder.Build();
@@ -40,6 +43,21 @@ using (var scope = app.Services.CreateScope())
""CardListId"" INTEGER NOT NULL REFERENCES ""CardList""(""Id"") ON DELETE CASCADE, ""CardListId"" INTEGER NOT NULL REFERENCES ""CardList""(""Id"") ON DELETE CASCADE,
""SubCardListId"" INTEGER NOT NULL REFERENCES ""CardList""(""Id"") ON DELETE CASCADE, ""SubCardListId"" INTEGER NOT NULL REFERENCES ""CardList""(""Id"") ON DELETE CASCADE,
PRIMARY KEY (""CardListId"", ""SubCardListId"") PRIMARY KEY (""CardListId"", ""SubCardListId"")
);
CREATE TABLE IF NOT EXISTS ""Game"" (
""Id"" SERIAL PRIMARY KEY,
""Name"" VARCHAR(64) NOT NULL,
""URL"" TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS ""Deck"" (
""Id"" SERIAL PRIMARY KEY,
""Name"" TEXT NOT NULL,
""CardListId"" INTEGER NOT NULL REFERENCES ""CardList""(""Id"") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ""DeckGame"" (
""DeckId"" INTEGER NOT NULL REFERENCES ""Deck""(""Id"") ON DELETE CASCADE,
""GameId"" INTEGER NOT NULL REFERENCES ""Game""(""Id"") ON DELETE CASCADE,
PRIMARY KEY (""DeckId"", ""GameId"")
);"); );");
} }
@@ -48,10 +66,15 @@ if (app.Environment.IsDevelopment())
{ {
app.MapOpenApi(); app.MapOpenApi();
} }
else
app.UseHttpsRedirection(); {
app.UseHttpsRedirection();
}
CardModel.MapEndpoints(app); CardModel.MapEndpoints(app);
CardListModel.MapEndpoints(app); CardListModel.MapEndpoints(app);
GameModel.MapEndpoints(app);
DeckModel.MapEndpoints(app);
Undercover.MapEndpoints(app);
app.Run(); app.Run();

View File

@@ -5,7 +5,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "http://localhost:5164", "applicationUrl": "http://0.0.0.0:5164",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@@ -14,7 +14,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "https://localhost:7075;http://localhost:5164", "applicationUrl": "https://0.0.0.0:7075;http://0.0.0.0:5164",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }