Files
SafelyYouCodingChallenge/dotnet/SafelyYou/Program.cs
T
2026-08-06 13:40:07 -05:00

94 lines
2.9 KiB
C#

using SafelyYou;
using SafelyYou.Api;
using SafelyYou.Config;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddSingleton<KnownDevices>(_ =>
{
var deviceIds = File.ReadAllLines("/devices.csv").Skip(1).Select(id => new DeviceId(id)).ToHashSet();
return new KnownDevices(deviceIds);
});
builder.Services.AddSingleton<HeartBeatHistoryConfig>(_ =>
{
var millis = builder.Configuration.GetSection("HeartBeatHistory").GetValue<int>("WindowMillis");
return new HeartBeatHistoryConfig() { Window = TimeSpan.FromMilliseconds(millis) };
});
builder.Services.AddSingleton<HeartBeatHistories>();
builder.Services.AddSingleton<UploadTimes>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
// heartbeat history
// upload stats
app.MapPost("/devices/{deviceId}/heartbeat", (string deviceId, HeartbeatRequest request,
KnownDevices knownDevices, HeartBeatHistories histories) =>
{
var device = new DeviceId(deviceId);
if (!knownDevices.Contains(device))
{
return Results.NotFound(new NotFoundResponse($"Device ID {deviceId} not found"));
}
var addResult = histories.Add(device, request.SentAt);
return addResult switch
{
DeviceHeartBeatHistory.AddResult.Added => Results.NoContent(),
DeviceHeartBeatHistory.AddResult.OutOfSequence => Results.InternalServerError(
new ErrorResponse("Attempted to add a timestamp that was out of sequence or invalid.")),
_ => throw new ArgumentOutOfRangeException()
};
});
app.MapPost("/devices/{deviceId}/stats", (string deviceId, UploadStatsRequest request, KnownDevices knownDevices, UploadTimes uploadTimes) =>
{
var device = new DeviceId(deviceId);
if (!knownDevices.Contains(device))
{
return Results.NotFound(new NotFoundResponse($"Device ID {deviceId} not found"));
}
uploadTimes.Add(device, request.UploadTime);
return Results.NoContent();
// not sure where the 500 would go here beyond doing a try/catch
});
app.MapGet("/devices/{deviceId}/stats", (string deviceId, KnownDevices knownDevices, HeartBeatHistories histories, UploadTimes uploadTimes) =>
{
var device = new DeviceId(deviceId);
if (!knownDevices.Contains(device))
{
return Results.NotFound(new NotFoundResponse($"Device ID {deviceId} not found"));
}
var averageUploadTimeNanos = uploadTimes.AverageUploadTimeNanoseconds(device);
var timeSpanText = TimeSpan.FromTicks((long)(averageUploadTimeNanos / 100)).ToString(@"m\ms\s"); // loses accuracy on the Go nanos for this text conversion
var uptime = histories.Uptime(device);
return Results.Ok(new GetDeviceStatsResponse(timeSpanText, uptime));
// no 500 here
});
app.Run();