|
| 1 | +const std = @import("std"); |
| 2 | +const dusty = @import("dusty"); |
| 3 | +const pg = @import("pg"); |
| 4 | +const datetimez = @import("datetimez"); |
| 5 | + |
| 6 | +pub var date_str: [29]u8 = undefined; |
| 7 | + |
| 8 | +pub const Global = struct { |
| 9 | + pool: *pg.Pool, |
| 10 | + rand: *std.Random, |
| 11 | +}; |
| 12 | + |
| 13 | +const World = struct { |
| 14 | + id: i32, |
| 15 | + randomNumber: i32, |
| 16 | +}; |
| 17 | + |
| 18 | +const Fortune = struct { |
| 19 | + id: i32, |
| 20 | + message: []const u8, |
| 21 | +}; |
| 22 | + |
| 23 | +pub fn plaintext(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { |
| 24 | + try setHeaders(res); |
| 25 | + |
| 26 | + try res.header("Content-Type", "text/plain"); |
| 27 | + res.body = "Hello, World!"; |
| 28 | +} |
| 29 | + |
| 30 | +pub fn json(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { |
| 31 | + try setHeaders(res); |
| 32 | + |
| 33 | + try res.json(.{ .message = "Hello, World!" }, .{}); |
| 34 | +} |
| 35 | + |
| 36 | +pub fn db(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { |
| 37 | + try setHeaders(res); |
| 38 | + |
| 39 | + const random_number = 1 + (global.rand.uintAtMostBiased(u32, 9999)); |
| 40 | + |
| 41 | + const world = getWorld(global.pool, random_number) catch |err| { |
| 42 | + std.debug.print("Error querying database: {}\n", .{err}); |
| 43 | + return; |
| 44 | + }; |
| 45 | + |
| 46 | + try res.json(world, .{}); |
| 47 | +} |
| 48 | + |
| 49 | +pub fn fortune(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { |
| 50 | + try setHeaders(res); |
| 51 | + |
| 52 | + const fortunes_html = try getFortunesHtml(res.arena, global.pool); |
| 53 | + |
| 54 | + try res.header("Content-Type", "text/html; charset=utf-8"); |
| 55 | + res.body = fortunes_html; |
| 56 | +} |
| 57 | + |
| 58 | +fn getWorld(pool: *pg.Pool, random_number: u32) !World { |
| 59 | + var conn = try pool.acquire(); |
| 60 | + defer conn.release(); |
| 61 | + |
| 62 | + const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number}); |
| 63 | + |
| 64 | + var row = row_result.?; |
| 65 | + defer row.deinit() catch {}; |
| 66 | + |
| 67 | + return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) }; |
| 68 | +} |
| 69 | + |
| 70 | +fn setHeaders(res: *dusty.Response) !void { |
| 71 | + try res.header("Server", "Dusty"); |
| 72 | + try res.header("Date", try res.arena.dupe(u8, &date_str)); |
| 73 | +} |
| 74 | + |
| 75 | +fn getFortunesHtml(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const u8 { |
| 76 | + const fortunes = try getFortunes(allocator, pool); |
| 77 | + |
| 78 | + var sb = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); |
| 79 | + |
| 80 | + const writer = sb.writer(allocator); |
| 81 | + try sb.appendSlice(allocator, "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"); |
| 82 | + |
| 83 | + for (fortunes) |ft| { |
| 84 | + try writer.print("<tr><td>{d}</td><td>{s}</td></tr>", .{ |
| 85 | + ft.id, |
| 86 | + try escape_html(allocator, ft.message), |
| 87 | + }); |
| 88 | + } |
| 89 | + |
| 90 | + try sb.appendSlice(allocator, "</table></body></html>"); |
| 91 | + |
| 92 | + return sb.toOwnedSlice(allocator); |
| 93 | +} |
| 94 | + |
| 95 | +fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune { |
| 96 | + var conn = try pool.acquire(); |
| 97 | + defer conn.release(); |
| 98 | + |
| 99 | + var rows = try conn.query("SELECT id, message FROM Fortune", .{}); |
| 100 | + defer rows.deinit(); |
| 101 | + |
| 102 | + var fortunes = try std.ArrayListUnmanaged(Fortune).initCapacity(allocator, 0); |
| 103 | + defer fortunes.deinit(allocator); |
| 104 | + |
| 105 | + while (try rows.next()) |row| { |
| 106 | + const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) }; |
| 107 | + try fortunes.append(allocator, current_fortune); |
| 108 | + } |
| 109 | + |
| 110 | + const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." }; |
| 111 | + try fortunes.append(allocator, zero_fortune); |
| 112 | + |
| 113 | + const fortunes_slice = try fortunes.toOwnedSlice(allocator); |
| 114 | + std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage); |
| 115 | + |
| 116 | + return fortunes_slice; |
| 117 | +} |
| 118 | + |
| 119 | +fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool { |
| 120 | + return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt); |
| 121 | +} |
| 122 | + |
| 123 | +fn escape_html(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { |
| 124 | + var output = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); |
| 125 | + defer output.deinit(allocator); |
| 126 | + |
| 127 | + for (input) |char| { |
| 128 | + switch (char) { |
| 129 | + '<' => try output.appendSlice(allocator, "<"), |
| 130 | + '>' => try output.appendSlice(allocator, ">"), |
| 131 | + else => try output.append(allocator, char), |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + return output.toOwnedSlice(allocator); |
| 136 | +} |
0 commit comments