milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+47 -89
View File
@@ -38,35 +38,23 @@ const list_clients_sql =
/// Every string in the result is a heap copy owned by `gpa`.
pub fn listClients(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(model.Client) {
var stmt = try database.prepare(list_clients_sql);
defer stmt.deinit();
return crud.listRows(model.Client, database, gpa, list_clients_sql, readClient);
}
var out: std.ArrayList(model.Client) = .empty;
// `errdefer`s run in reverse: `freeClients` is declared last so it runs
// before the backing array is released.
errdefer out.deinit(gpa);
errdefer freeClients(gpa, out.items);
while (try stmt.step()) {
const ip = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(ip);
// `clients.name` is nullable; `columnTextAlloc` reads NULL as "", which
// is exactly the model's default.
const name = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(name);
const group = try stmt.columnTextAlloc(gpa, 2);
errdefer gpa.free(group);
try out.append(gpa, .{ .ip = ip, .name = name, .group = group });
}
return out;
fn readClient(stmt: *db.Stmt, gpa: Allocator) db.Error!model.Client {
const ip = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(ip);
// `clients.name` is nullable; `columnTextAlloc` reads NULL as "", which is
// exactly the model's default.
const name = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(name);
const group = try stmt.columnTextAlloc(gpa, 2);
errdefer gpa.free(group);
return .{ .ip = ip, .name = name, .group = group };
}
pub fn freeClients(gpa: Allocator, items: []const model.Client) void {
for (items) |item| {
gpa.free(item.ip);
gpa.free(item.name);
gpa.free(item.group);
}
crud.freeRows(model.Client, gpa, items);
}
const insert_client_sql =
@@ -154,31 +142,22 @@ const list_client_prefixes_sql =
;
pub fn listClientPrefixes(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(model.ClientPrefix) {
var stmt = try database.prepare(list_client_prefixes_sql);
defer stmt.deinit();
return crud.listRows(model.ClientPrefix, database, gpa, list_client_prefixes_sql, readClientPrefix);
}
var out: std.ArrayList(model.ClientPrefix) = .empty;
errdefer out.deinit(gpa);
errdefer freeClientPrefixes(gpa, out.items);
while (try stmt.step()) {
const prefix = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(prefix);
const group = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(group);
// The column is a 64-bit integer; the model field is `i32`. A value
// outside that range means something other than nxdns wrote the row.
const priority = std.math.cast(i32, stmt.columnInt(2)) orelse return error.Mismatch;
try out.append(gpa, .{ .prefix = prefix, .group = group, .priority = priority });
}
return out;
fn readClientPrefix(stmt: *db.Stmt, gpa: Allocator) db.Error!model.ClientPrefix {
const prefix = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(prefix);
const group = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(group);
// The column is a 64-bit integer; the model field is `i32`. A value outside
// that range means something other than nxdns wrote the row.
const priority = std.math.cast(i32, stmt.columnInt(2)) orelse return error.Mismatch;
return .{ .prefix = prefix, .group = group, .priority = priority };
}
pub fn freeClientPrefixes(gpa: Allocator, items: []const model.ClientPrefix) void {
for (items) |item| {
gpa.free(item.prefix);
gpa.free(item.group);
}
crud.freeRows(model.ClientPrefix, gpa, items);
}
pub fn insertClientPrefix(database: *db.Db, item: model.ClientPrefix, ctx: InsertContext) db.Error!void {
@@ -257,29 +236,15 @@ const get_client_sql =
/// Every client, materialised ones included. Every string is a heap copy owned
/// by `gpa`.
pub fn listClientRows(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(ClientRow) {
var stmt = try database.prepare(list_client_rows_sql);
defer stmt.deinit();
var out: std.ArrayList(ClientRow) = .empty;
errdefer out.deinit(gpa);
errdefer freeClientRows(gpa, out.items);
while (try stmt.step()) {
const row = try readClientRow(&stmt, gpa);
errdefer freeClientRow(gpa, row);
try out.append(gpa, row);
}
return out;
return crud.listRows(ClientRow, database, gpa, list_client_rows_sql, readClientRow);
}
pub fn freeClientRow(gpa: Allocator, row: ClientRow) void {
gpa.free(row.ip);
gpa.free(row.name);
gpa.free(row.group);
crud.freeRow(ClientRow, gpa, row);
}
pub fn freeClientRows(gpa: Allocator, items: []const ClientRow) void {
for (items) |item| freeClientRow(gpa, item);
crud.freeRows(ClientRow, gpa, items);
}
pub fn getClient(database: *db.Db, gpa: Allocator, id: i64) db.Error!?ClientRow {
@@ -383,39 +348,32 @@ const list_client_prefix_rows_sql =
/// Same order as `listClientPrefixes`; every string is a heap copy owned by
/// `gpa`.
pub fn listClientPrefixRows(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(ClientPrefixRow) {
var stmt = try database.prepare(list_client_prefix_rows_sql);
defer stmt.deinit();
return crud.listRows(ClientPrefixRow, database, gpa, list_client_prefix_rows_sql, readClientPrefixRow);
}
var out: std.ArrayList(ClientPrefixRow) = .empty;
errdefer out.deinit(gpa);
errdefer freeClientPrefixRows(gpa, out.items);
while (try stmt.step()) {
const prefix = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(prefix);
const group = try stmt.columnTextAlloc(gpa, 3);
errdefer gpa.free(group);
// The column is a 64-bit integer; the row field is `i32`. A value
// outside that range means something other than nxdns wrote the row.
const priority = std.math.cast(i32, stmt.columnInt(4)) orelse return error.Mismatch;
try out.append(gpa, .{
.id = stmt.columnInt(0),
.prefix = prefix,
.group_id = stmt.columnInt(2),
.group = group,
.priority = priority,
});
}
return out;
fn readClientPrefixRow(stmt: *db.Stmt, gpa: Allocator) db.Error!ClientPrefixRow {
const prefix = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(prefix);
const group = try stmt.columnTextAlloc(gpa, 3);
errdefer gpa.free(group);
// The column is a 64-bit integer; the row field is `i32`. A value outside
// that range means something other than nxdns wrote the row.
const priority = std.math.cast(i32, stmt.columnInt(4)) orelse return error.Mismatch;
return .{
.id = stmt.columnInt(0),
.prefix = prefix,
.group_id = stmt.columnInt(2),
.group = group,
.priority = priority,
};
}
pub fn freeClientPrefixRow(gpa: Allocator, row: ClientPrefixRow) void {
gpa.free(row.prefix);
gpa.free(row.group);
crud.freeRow(ClientPrefixRow, gpa, row);
}
pub fn freeClientPrefixRows(gpa: Allocator, items: []const ClientPrefixRow) void {
for (items) |item| freeClientPrefixRow(gpa, item);
crud.freeRows(ClientPrefixRow, gpa, items);
}
/// Replaces the whole prefix table inside a transaction (ruling 9 makes