- Fix YAML tags in auth config struct (json -> yaml) - Update CLI configs to use pre-hashed API keys - Remove double hashing in WebSocket client - Fix port mapping (9102 -> 9103) in CLI commands - Update permission keys to use jobs:read, jobs:create, etc. - Clean up all debug logging from CLI and server - All user roles now authenticate correctly: * Admin: Can queue jobs and see all jobs * Researcher: Can queue jobs and see own jobs * Analyst: Can see status (read-only access) Multi-user authentication is now fully functional.
101 lines
3 KiB
Zig
101 lines
3 KiB
Zig
const std = @import("std");
|
|
|
|
pub const Entry = struct {
|
|
job_name: []const u8,
|
|
commit_id: []const u8,
|
|
queued_at: i64,
|
|
};
|
|
|
|
fn historyDir(allocator: std.mem.Allocator) ![]const u8 {
|
|
const home = std.posix.getenv("HOME") orelse return error.NoHomeDir;
|
|
return std.fmt.allocPrint(allocator, "{s}/.ml", .{home});
|
|
}
|
|
|
|
fn historyPath(allocator: std.mem.Allocator) ![]const u8 {
|
|
const dir = try historyDir(allocator);
|
|
defer allocator.free(dir);
|
|
return std.fmt.allocPrint(allocator, "{s}/history.log", .{dir});
|
|
}
|
|
|
|
pub fn record(allocator: std.mem.Allocator, job_name: []const u8, commit_id: []const u8) !void {
|
|
const dir = try historyDir(allocator);
|
|
defer allocator.free(dir);
|
|
std.fs.makeDirAbsolute(dir) catch |err| {
|
|
if (err != error.PathAlreadyExists) return err;
|
|
};
|
|
|
|
const path = try historyPath(allocator);
|
|
defer allocator.free(path);
|
|
|
|
var file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
|
|
error.FileNotFound => try std.fs.createFileAbsolute(path, .{}),
|
|
else => return err,
|
|
};
|
|
defer file.close();
|
|
|
|
// Append at end of file
|
|
try file.seekFromEnd(0);
|
|
|
|
const ts = std.time.timestamp();
|
|
|
|
// Format one line into a temporary buffer
|
|
const line = try std.fmt.allocPrint(
|
|
allocator,
|
|
"{d}\t{s}\t{s}\n",
|
|
.{ ts, job_name, commit_id },
|
|
);
|
|
defer allocator.free(line);
|
|
|
|
try file.writeAll(line);
|
|
}
|
|
|
|
pub fn loadEntries(allocator: std.mem.Allocator) ![]Entry {
|
|
const path = historyPath(allocator) catch |err| switch (err) {
|
|
error.NoHomeDir => return error.NoHomeDir,
|
|
else => return err,
|
|
};
|
|
defer allocator.free(path);
|
|
|
|
const file = std.fs.openFileAbsolute(path, .{}) catch |err| switch (err) {
|
|
error.FileNotFound => return &.{},
|
|
else => return err,
|
|
};
|
|
defer file.close();
|
|
|
|
const contents = try file.readToEndAlloc(allocator, 1024 * 1024);
|
|
defer allocator.free(contents);
|
|
|
|
var entries = std.ArrayListUnmanaged(Entry){};
|
|
defer entries.deinit(allocator);
|
|
|
|
var it = std.mem.splitScalar(u8, contents, '\n');
|
|
while (it.next()) |line_full| {
|
|
const line = std.mem.trim(u8, line_full, " \t\r");
|
|
if (line.len == 0) continue;
|
|
|
|
var parts = std.mem.splitScalar(u8, line, '\t');
|
|
const ts_str = parts.next() orelse continue;
|
|
const job = parts.next() orelse continue;
|
|
const commit = parts.next() orelse continue;
|
|
|
|
const ts = std.fmt.parseInt(i64, ts_str, 10) catch continue;
|
|
const job_dup = try allocator.dupe(u8, job);
|
|
const commit_dup = try allocator.dupe(u8, commit);
|
|
|
|
try entries.append(allocator, Entry{
|
|
.job_name = job_dup,
|
|
.commit_id = commit_dup,
|
|
.queued_at = ts,
|
|
});
|
|
}
|
|
|
|
return try entries.toOwnedSlice(allocator);
|
|
}
|
|
|
|
pub fn freeEntries(allocator: std.mem.Allocator, entries: []Entry) void {
|
|
for (entries) |entry| {
|
|
allocator.free(entry.job_name);
|
|
allocator.free(entry.commit_id);
|
|
}
|
|
allocator.free(entries);
|
|
}
|