milestone 8: web server, rest api, sse, auth, metrics and static assets

This commit is contained in:
2026-08-02 00:54:13 +02:00
parent a8092bb1b9
commit 5253c47303
59 changed files with 19640 additions and 150 deletions
+72
View File
@@ -0,0 +1,72 @@
//! `GET /api/version` — what this binary is and how long it has been running.
//!
//! Unauthenticated (ruling 18), like the other monitoring endpoints. The three
//! strings are build options, so nothing here reads the running configuration.
const std = @import("std");
const http_util = @import("../http_util.zig");
const server = @import("../server.zig");
const version = @import("../../version.zig");
pub const Body = struct {
version: []const u8,
git_commit: []const u8,
zig_version: []const u8,
/// Seconds since the process started. Zero until `started_unix` is wired,
/// and never negative: a clock stepped backwards must not report a
/// process that started in the future.
uptime_seconds: u64,
};
pub fn handle(
state: *server.WebState,
io: std.Io,
request: *http_util.Request,
) http_util.HandlerError!void {
const now = std.Io.Clock.real.now(io).toSeconds();
return http_util.respondJson(request, .ok, body(state.version, state.started_unix, now), &.{});
}
pub fn body(version_string: []const u8, started_unix: i64, now_unix: i64) Body {
return .{
.version = if (version_string.len == 0) version.string else version_string,
.git_commit = version.git_commit,
.zig_version = version.zig_version_string,
.uptime_seconds = uptime(started_unix, now_unix),
};
}
fn uptime(started_unix: i64, now_unix: i64) u64 {
if (started_unix <= 0 or now_unix <= started_unix) return 0;
return @intCast(now_unix - started_unix);
}
const testing = std.testing;
test "the body carries the build strings and the elapsed time" {
const out = body("", 1_000, 1_060);
try testing.expectEqualStrings(version.string, out.version);
try testing.expectEqualStrings(version.git_commit, out.git_commit);
try testing.expectEqualStrings(version.zig_version_string, out.zig_version);
try testing.expectEqual(@as(u64, 60), out.uptime_seconds);
}
test "the state's version string wins over the compiled-in one" {
try testing.expectEqualStrings("9.9.9", body("9.9.9", 0, 0).version);
}
test "an unset start time and a clock that stepped back both read as zero uptime" {
try testing.expectEqual(@as(u64, 0), body("", 0, 5_000).uptime_seconds);
try testing.expectEqual(@as(u64, 0), body("", 5_000, 4_000).uptime_seconds);
}
test "the body serializes with snake_case field names" {
var buffer: [256]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buffer);
try std.json.Stringify.value(body("1.2.3", 10, 20), .{}, &writer);
const text = writer.buffered();
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "\"git_commit\":"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "\"zig_version\":"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "\"uptime_seconds\":10"));
}