Files
RpgRoller/RpgRoller/Program.cs

291 lines
8.5 KiB
C#

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using RpgRoller.Contracts;
using RpgRoller.Domain;
using RpgRoller.Services;
const string SessionCookieName = "rpgroller_session";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IPasswordHasher<UserAccount>, PasswordHasher<UserAccount>>();
builder.Services.AddSingleton<IDiceRoller, RandomDiceRoller>();
builder.Services.AddSingleton<IGameService, GameService>();
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapGet("/api/health", () => TypedResults.Ok(new HealthResponse("ok")));
app.MapGet("/api/rulesets", (IGameService game) => TypedResults.Ok(game.GetRulesets()));
app.MapPost("/api/auth/register", Results<Ok<UserSummary>, BadRequest<ApiError>> (RegisterRequest request, IGameService game) =>
{
var result = game.Register(request);
if (!result.Succeeded)
{
return ToBadRequest(result.Error!);
}
return TypedResults.Ok(result.Value!);
});
app.MapPost("/api/auth/login", Results<Ok<UserSummary>, BadRequest<ApiError>> (LoginRequest request, HttpContext context, IGameService game) =>
{
var result = game.Login(request);
if (!result.Succeeded)
{
return ToBadRequest(result.Error!);
}
context.Response.Cookies.Append(SessionCookieName, result.Value.SessionToken, new CookieOptions
{
HttpOnly = true,
SameSite = SameSiteMode.Strict,
IsEssential = true,
Secure = false
});
return TypedResults.Ok(result.Value.User);
});
app.MapPost("/api/auth/logout", (HttpContext context, IGameService game) =>
{
if (TryGetSessionToken(context, out var sessionToken))
{
game.Logout(sessionToken);
}
context.Response.Cookies.Delete(SessionCookieName);
return TypedResults.NoContent();
});
app.MapGet("/api/me", Results<Ok<MeResponse>, BadRequest<ApiError>, UnauthorizedHttpResult> (HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.GetMe(sessionToken);
if (!result.Succeeded)
{
return result.Error!.Code == "unauthorized"
? TypedResults.Unauthorized()
: TypedResults.BadRequest(new ApiError(result.Error.Message));
}
return TypedResults.Ok(result.Value!);
});
app.MapPost("/api/campaigns", (CreateCampaignRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.CreateCampaign(sessionToken, request);
return ToApiResult(result);
});
app.MapGet("/api/campaigns", (HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.GetCampaigns(sessionToken);
return ToApiResult(result);
});
app.MapGet("/api/campaigns/{campaignId:guid}", (Guid campaignId, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.GetCampaign(sessionToken, campaignId);
return ToApiResult(result);
});
app.MapGet("/api/campaigns/{campaignId:guid}/log", (Guid campaignId, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.GetCampaignLog(sessionToken, campaignId);
return ToApiResult(result);
});
app.MapPost("/api/characters", (CreateCharacterRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.CreateCharacter(sessionToken, request);
return ToApiResult(result);
});
app.MapPut("/api/characters/{characterId:guid}", (Guid characterId, UpdateCharacterRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.UpdateCharacter(sessionToken, characterId, request);
return ToApiResult(result);
});
app.MapPost("/api/characters/{characterId:guid}/activate", (Guid characterId, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.ActivateCharacter(sessionToken, characterId);
return ToApiResult(result);
});
app.MapGet("/api/characters/current-campaign", (HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.GetCurrentCampaignCharacters(sessionToken);
return ToApiResult(result);
});
app.MapPost("/api/characters/{characterId:guid}/skills", (Guid characterId, CreateSkillRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.CreateSkill(sessionToken, characterId, request);
return ToApiResult(result);
});
app.MapPut("/api/skills/{skillId:guid}", (Guid skillId, UpdateSkillRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.UpdateSkill(sessionToken, skillId, request);
return ToApiResult(result);
});
app.MapPost("/api/skills/{skillId:guid}/roll", (Guid skillId, RollSkillRequest request, HttpContext context, IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var result = game.RollSkill(sessionToken, skillId, request);
return ToApiResult(result);
});
app.MapGet("/api/events/state", async Task<IResult> (
Guid campaignId,
HttpContext context,
IGameService game) =>
{
if (!TryGetSessionToken(context, out var sessionToken))
{
return TypedResults.Unauthorized();
}
var versionResult = game.GetCampaignVersion(sessionToken, campaignId);
if (!versionResult.Succeeded)
{
return versionResult.Error!.Code == "unauthorized"
? TypedResults.Unauthorized()
: TypedResults.BadRequest(new ApiError(versionResult.Error.Message));
}
context.Response.Headers.CacheControl = "no-cache";
context.Response.Headers.Connection = "keep-alive";
context.Response.ContentType = "text/event-stream";
var lastVersion = versionResult.Value;
await context.Response.WriteAsync($"event: state\ndata: {{\"campaignId\":\"{campaignId}\",\"version\":{lastVersion}}}\n\n");
await context.Response.Body.FlushAsync();
try
{
while (!context.RequestAborted.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), context.RequestAborted);
var currentVersionResult = game.GetCampaignVersion(sessionToken, campaignId);
if (!currentVersionResult.Succeeded)
{
break;
}
if (currentVersionResult.Value != lastVersion)
{
lastVersion = currentVersionResult.Value;
await context.Response.WriteAsync($"event: state\ndata: {{\"campaignId\":\"{campaignId}\",\"version\":{lastVersion}}}\n\n");
}
else
{
await context.Response.WriteAsync(": heartbeat\n\n");
}
await context.Response.Body.FlushAsync();
}
}
catch (OperationCanceledException)
{
}
return TypedResults.Empty;
});
app.Run();
return;
static bool TryGetSessionToken(HttpContext context, out string sessionToken)
{
sessionToken = context.Request.Cookies[SessionCookieName] ?? string.Empty;
return !string.IsNullOrWhiteSpace(sessionToken);
}
static Results<Ok<T>, BadRequest<ApiError>, UnauthorizedHttpResult> ToApiResult<T>(ServiceResult<T> result)
{
if (result.Succeeded)
{
return TypedResults.Ok(result.Value!);
}
if (result.Error!.Code == "unauthorized")
{
return TypedResults.Unauthorized();
}
return TypedResults.BadRequest(new ApiError(result.Error.Message));
}
static BadRequest<ApiError> ToBadRequest(ServiceError error)
{
return TypedResults.BadRequest(new ApiError(error.Message));
}
public partial class Program;