milestone 8: web server, rest api, sse, auth, metrics and static assets
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
//! `model.fromSettings` speak, so the scalar sections cross the storage boundary
|
||||
//! without a second shape.
|
||||
//!
|
||||
//! Only list / insert / deleteAll / count exist.
|
||||
//! The import path is list / insert / deleteAll / count; `putSetting` is Phase
|
||||
//! 8's single-key write. `settings` has no row ids — the key is the identity —
|
||||
//! so it gains no by-id surface.
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -64,6 +66,32 @@ pub fn countSettings(database: *db.Db) db.Error!i64 {
|
||||
return database.queryInt("SELECT count(*) FROM settings");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// REST surface (milestone 8)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const put_setting_sql =
|
||||
\\INSERT INTO settings (key, value) VALUES (?1, ?2)
|
||||
\\ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
||||
;
|
||||
|
||||
/// Writes one key, whether or not it is already stored.
|
||||
///
|
||||
/// `PUT /api/settings` is a partial update over a table whose rows the import
|
||||
/// path writes once and never revisits, so a plain `INSERT` would fail on every
|
||||
/// key the config already carries and a plain `UPDATE` would drop every key it
|
||||
/// does not. The conflict target is `settings.key`, the table's PRIMARY KEY.
|
||||
///
|
||||
/// No constraint can fire: the table has one key column and one `NOT NULL`
|
||||
/// value, and both are bound.
|
||||
pub fn putSetting(database: *db.Db, key: []const u8, value: []const u8) db.Error!void {
|
||||
var stmt = try database.prepare(put_setting_sql);
|
||||
defer stmt.deinit();
|
||||
try stmt.bindText(1, key);
|
||||
try stmt.bindText(2, value);
|
||||
try stmt.exec();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// tests
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -143,3 +171,41 @@ fn listSettingsUnderFailure(gpa: Allocator) !void {
|
||||
test "listSettings is leak-safe under allocation failure" {
|
||||
try testing.checkAllAllocationFailures(testing.allocator, listSettingsUnderFailure, .{});
|
||||
}
|
||||
|
||||
// --- REST surface ----------------------------------------------------------
|
||||
|
||||
test "putSetting writes a key that is absent and overwrites one that is present" {
|
||||
var database = try openMigrated();
|
||||
defer database.close();
|
||||
try seedSettings(&database);
|
||||
|
||||
try putSetting(&database, "web.port", "9090");
|
||||
try putSetting(&database, "cache.max_entries", "20000");
|
||||
|
||||
var items = try listSettings(&database, testing.allocator);
|
||||
defer items.deinit(testing.allocator);
|
||||
defer freeSettings(testing.allocator, items.items);
|
||||
|
||||
try testing.expectEqual(@as(usize, 4), items.items.len);
|
||||
try testing.expectEqualStrings("cache.max_entries", items.items[0].key);
|
||||
try testing.expectEqualStrings("20000", items.items[0].value);
|
||||
try testing.expectEqualStrings("web.port", items.items[3].key);
|
||||
try testing.expectEqualStrings("9090", items.items[3].value);
|
||||
// The keys it did not name are untouched.
|
||||
try testing.expectEqualStrings("dns.port", items.items[1].key);
|
||||
try testing.expectEqualStrings("53", items.items[1].value);
|
||||
}
|
||||
|
||||
test "putSetting is idempotent and leaves the row count alone" {
|
||||
var database = try openMigrated();
|
||||
defer database.close();
|
||||
|
||||
try putSetting(&database, "web.password_hash", "$argon2id$v=19$m=19456,t=2,p=1$c2FsdA$aGFzaA");
|
||||
try putSetting(&database, "web.password_hash", "$argon2id$v=19$m=19456,t=2,p=1$c2FsdA$aGFzaA");
|
||||
try testing.expectEqual(@as(i64, 1), try countSettings(&database));
|
||||
|
||||
var items = try listSettings(&database, testing.allocator);
|
||||
defer items.deinit(testing.allocator);
|
||||
defer freeSettings(testing.allocator, items.items);
|
||||
try testing.expectEqualStrings("$argon2id$v=19$m=19456,t=2,p=1$c2FsdA$aGFzaA", items.items[0].value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user