World microapi
Deploy Promiscuity Auth API / deploy (push) Successful in 49s
Deploy Promiscuity Character API / deploy (push) Successful in 46s
Deploy Promiscuity Crafting API / deploy (push) Successful in 47s
Deploy Promiscuity Inventory API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 48s
Deploy Promiscuity Mail API / deploy (push) Successful in 46s
k8s smoke test / test (push) Has been cancelled
Deploy Promiscuity Auth API / deploy (push) Successful in 49s
Deploy Promiscuity Character API / deploy (push) Successful in 46s
Deploy Promiscuity Crafting API / deploy (push) Successful in 47s
Deploy Promiscuity Inventory API / deploy (push) Successful in 47s
Deploy Promiscuity Locations API / deploy (push) Successful in 48s
Deploy Promiscuity Mail API / deploy (push) Successful in 46s
k8s smoke test / test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WorldApi.Services;
|
||||
|
||||
namespace WorldApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class WorldController : ControllerBase
|
||||
{
|
||||
private readonly WorldCycleService _worldCycle;
|
||||
|
||||
public WorldController(WorldCycleService worldCycle)
|
||||
{
|
||||
_worldCycle = worldCycle;
|
||||
}
|
||||
|
||||
[HttpGet("cycle")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public IActionResult GetCycle()
|
||||
{
|
||||
return Ok(_worldCycle.GetCurrentCycle());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# WorldApi document shapes
|
||||
|
||||
Outbound JSON documents
|
||||
- WorldCycleResponse (`GET /api/world/cycle`)
|
||||
```json
|
||||
{
|
||||
"currentUtc": "string (ISO-8601 datetime)",
|
||||
"dayLengthSeconds": 1200,
|
||||
"timeOfDaySeconds": 438.2,
|
||||
"timeOfDayNormalized": 0.365,
|
||||
"phase": "day",
|
||||
"isDay": true,
|
||||
"lightLevel": 0.91
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace WorldApi.Models;
|
||||
|
||||
public class WorldCycleResponse
|
||||
{
|
||||
public DateTime CurrentUtc { get; set; }
|
||||
|
||||
public double DayLengthSeconds { get; set; }
|
||||
|
||||
public double TimeOfDaySeconds { get; set; }
|
||||
|
||||
public double TimeOfDayNormalized { get; set; }
|
||||
|
||||
public string Phase { get; set; } = "day";
|
||||
|
||||
public bool IsDay { get; set; }
|
||||
|
||||
public double LightLevel { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text;
|
||||
using WorldApi.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddSingleton<WorldCycleService>();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "World API", Version = "v1" });
|
||||
c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Description = "Paste your access token here (no 'Bearer ' prefix needed)."
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{ Type = ReferenceType.SecurityScheme, Id = "bearerAuth" }
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var cfg = builder.Configuration;
|
||||
var jwtKey = cfg["Jwt:Key"] ?? throw new Exception("Jwt:Key missing");
|
||||
var issuer = cfg["Jwt:Issuer"] ?? "promiscuity";
|
||||
var aud = cfg["Jwt:Audience"] ?? issuer;
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(o =>
|
||||
{
|
||||
o.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = aud,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)),
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseExceptionHandler(errorApp =>
|
||||
{
|
||||
errorApp.Run(async context =>
|
||||
{
|
||||
var feature = context.Features.Get<IExceptionHandlerFeature>();
|
||||
var exception = feature?.Error;
|
||||
var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("GlobalException");
|
||||
var traceId = context.TraceIdentifier;
|
||||
|
||||
if (exception is not null)
|
||||
{
|
||||
logger.LogError(
|
||||
exception,
|
||||
"Unhandled exception for {Method} {Path}. TraceId={TraceId}",
|
||||
context.Request.Method,
|
||||
context.Request.Path,
|
||||
traceId
|
||||
);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
context.Response.ContentType = "application/problem+json";
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new
|
||||
{
|
||||
type = "https://httpstatuses.com/500",
|
||||
title = "Internal Server Error",
|
||||
status = 500,
|
||||
detail = exception?.Message ?? "An unexpected server error occurred.",
|
||||
traceId
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.MapGet("/healthz", () => Results.Ok("ok"));
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(o =>
|
||||
{
|
||||
o.SwaggerEndpoint("/swagger/v1/swagger.json", "World API v1");
|
||||
o.RoutePrefix = "swagger";
|
||||
});
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5185",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# WorldApi
|
||||
|
||||
Global world simulation state such as the shared day/night cycle.
|
||||
@@ -0,0 +1,56 @@
|
||||
using WorldApi.Models;
|
||||
|
||||
namespace WorldApi.Services;
|
||||
|
||||
public class WorldCycleService
|
||||
{
|
||||
private readonly double _dayLengthSeconds;
|
||||
private readonly DateTime _epochUtc;
|
||||
|
||||
public WorldCycleService(IConfiguration configuration)
|
||||
{
|
||||
_dayLengthSeconds = Math.Max(1.0, configuration.GetValue<double?>("WorldCycle:DayLengthSeconds") ?? 1200.0);
|
||||
|
||||
var configuredEpoch = configuration["WorldCycle:EpochUtc"];
|
||||
_epochUtc = DateTime.TryParse(
|
||||
configuredEpoch,
|
||||
null,
|
||||
System.Globalization.DateTimeStyles.AdjustToUniversal | System.Globalization.DateTimeStyles.AssumeUniversal,
|
||||
out var parsedEpoch)
|
||||
? parsedEpoch
|
||||
: new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
public WorldCycleResponse GetCurrentCycle()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var elapsedSeconds = Math.Max(0.0, (now - _epochUtc).TotalSeconds);
|
||||
var timeOfDaySeconds = elapsedSeconds % _dayLengthSeconds;
|
||||
var normalized = timeOfDaySeconds / _dayLengthSeconds;
|
||||
var solarCurve = Math.Sin(normalized * Math.PI);
|
||||
var lightLevel = Math.Clamp(solarCurve, 0.0, 1.0);
|
||||
|
||||
return new WorldCycleResponse
|
||||
{
|
||||
CurrentUtc = now,
|
||||
DayLengthSeconds = _dayLengthSeconds,
|
||||
TimeOfDaySeconds = timeOfDaySeconds,
|
||||
TimeOfDayNormalized = normalized,
|
||||
Phase = ResolvePhase(normalized),
|
||||
IsDay = lightLevel > 0.0,
|
||||
LightLevel = lightLevel
|
||||
};
|
||||
}
|
||||
|
||||
private static string ResolvePhase(double normalized)
|
||||
{
|
||||
return normalized switch
|
||||
{
|
||||
< 0.20 => "night",
|
||||
< 0.30 => "dawn",
|
||||
< 0.70 => "day",
|
||||
< 0.80 => "dusk",
|
||||
_ => "night"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Kestrel": { "Endpoints": { "Http": { "Url": "http://0.0.0.0:5004" } } },
|
||||
"WorldCycle": { "DayLengthSeconds": 1200, "EpochUtc": "2026-01-01T00:00:00Z" },
|
||||
"Jwt": { "Key": "SuperUltraSecureJwtKeyWithAtLeast32Chars!!", "Issuer": "promiscuity", "Audience": "promiscuity-auth-api" },
|
||||
"Logging": { "LogLevel": { "Default": "Information" } },
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MailApi", "MailApi\MailApi.
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CraftingApi", "CraftingApi\CraftingApi.csproj", "{0B5EE564-C6E1-4BA7-B0E2-B86D363A9C74}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorldApi", "WorldApi\WorldApi.csproj", "{C8F20B54-2A76-4BE0-8DA8-E146D1AF4D10}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -44,6 +46,10 @@ Global
|
||||
{0B5EE564-C6E1-4BA7-B0E2-B86D363A9C74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B5EE564-C6E1-4BA7-B0E2-B86D363A9C74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B5EE564-C6E1-4BA7-B0E2-B86D363A9C74}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C8F20B54-2A76-4BE0-8DA8-E146D1AF4D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C8F20B54-2A76-4BE0-8DA8-E146D1AF4D10}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C8F20B54-2A76-4BE0-8DA8-E146D1AF4D10}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C8F20B54-2A76-4BE0-8DA8-E146D1AF4D10}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user