From f3302ecb76be3f42d782d73a58552775416b5052 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Fri, 4 Aug 2023 22:29:41 +0100 Subject: Convert comments to zig --- zig-comments/src/main.zig | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 zig-comments/src/main.zig (limited to 'zig-comments/src/main.zig') diff --git a/zig-comments/src/main.zig b/zig-comments/src/main.zig new file mode 100644 index 0000000..ffe6620 --- /dev/null +++ b/zig-comments/src/main.zig @@ -0,0 +1,163 @@ +const std = @import("std"); +const zws = @import("zws"); +const pq = @import("pq.zig"); +const mustache = @import("mustache"); + +const Params = zws.Params; + +const Err = error{ Overflow, InvalidCharacter, StreamTooLong } || pq.PqError || std.http.Server.Response.WaitError || std.http.Server.Response.DoError || std.http.Server.Response.ReadError || std.http.Server.Response.FinishError || zws.Path.ParseError; +const Ctx = struct { + db: pq.Db, + pub fn clone(self: @This()) Ctx { + return Ctx{ + .db = self.db, + }; + } + pub fn deinit(self: @This()) void { + _ = self; + } +}; + +var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +const Rtr = zws.Router(Ctx, Err); +const router = Rtr{ + .handlers = &[_]Rtr.Handler{ + .{ + .method = .GET, + .pattern = "/api/comments", + .handle_fn = get_comments, + }, + .{ + .method = .POST, + .pattern = "/api/comments", + .handle_fn = post_comments, + }, + .{ + .method = .GET, + .pattern = "/api/form/*", + .handle_fn = get_form, + }, + }, + .notfound = notfound, +}; + +pub fn main() !void { + var db = try pq.Db.init("postgresql://comments@localhost/comments"); + try db.exec(@embedFile("migrations/0_init.sql")); + try db.exec(@embedFile("migrations/1_capcha.sql")); + defer db.deinit(); + const server = zws.Server(Ctx, Rtr){ + .allocator = gpa.allocator(), + .address = std.net.Address{ .in = std.net.Ip4Address.init(.{ 127, 0, 0, 1 }, 8080) }, + .context = Ctx{ .db = db }, + .handler = router, + }; + + try server.serve(); +} + +fn notfound(res: *std.http.Server.Response, _: Ctx) Err!void { + const rr = @embedFile("templates/notfound.html"); + try constresponse(res, rr, .not_found); +} + +fn badrequest(res: *std.http.Server.Response, _: Ctx) Err!void { + const rr = @embedFile("templates/badrequest.html"); + try constresponse(res, rr, .bad_request); +} + +fn constresponse(res: *std.http.Server.Response, rr: []const u8, status: std.http.Status) Err!void { + res.status = status; + res.transfer_encoding = .{ .content_length = rr.len }; + try res.headers.append("content-type", "text/html"); + try res.do(); + try res.writeAll(rr); + try res.finish(); +} + +fn get_comments(res: *std.http.Server.Response, ctx: Ctx, _: Params) Err!void { + _ = ctx; + // Run SQL + // Render comments template + //zws.parse + + const rr = @embedFile("templates/comments.html"); + res.transfer_encoding = .{ .content_length = rr.len }; + try res.headers.append("content-type", "text/html"); + try res.do(); + try res.writeAll(rr); + try res.finish(); +} + +fn post_comments(res: *std.http.Server.Response, ctx: Ctx, _: Params) Err!void { + var body_aa = std.ArrayList(u8).init(res.allocator); + try res.reader().readAllArrayList(&body_aa, 1_000_000); + var body = try body_aa.toOwnedSlice(); + var form = try zws.Form.parse(res.allocator, body); + const Form = struct { + url: []const u8, + capcha_id: []const u8, + author: []const u8, + comment: []const u8, + capcha_answer: []const u8, + }; + const form_val = form.form_to_struct(Form) catch { + try badrequest(res, ctx); + return; + }; + // TODO validate the capcha + + var stmt = try ctx.db.prepare_statement("insert into comments(url,author,comment) values($1, $2, $3)"); + defer stmt.deinit(); + try stmt.bind(0, form_val.url); + try stmt.bind(1, form_val.author); + try stmt.bind(2, form_val.comment); + _ = try stmt.step(); + + res.transfer_encoding = .none; + res.status = .found; + try res.headers.append("location", form_val.url); + try res.do(); + try res.finish(); +} + +fn get_form(res: *std.http.Server.Response, ctx: Ctx, _: Params) Err!void { + var p = try zws.Path.parse(res.allocator, res.request.target); + defer p.deinit(); + const url: []const u8 = try p.get_query_param("url") orelse { + try badrequest(res, ctx); + return; + }; + + var stmt = try ctx.db.prepare_statement("select id, question from capchas order by random() limit 1"); + defer stmt.deinit(); + if (!try stmt.step()) { + std.log.err("no capcha!", .{}); + try badrequest(res, ctx); + return; + } + const Capcha = struct { + capcha_id: []const u8, + capcha_question: []const u8, + }; + const capcha = try stmt.read_struct(Capcha); + + const rr = @embedFile("templates/form.html"); + const tt = comptime mustache.parseComptime(rr, .{}, .{}); + + res.transfer_encoding = .chunked; + try res.headers.append("content-type", "text/html"); + try res.do(); + // For some reason, mustache.render won't work with anonymous struct + const data = struct { + capcha_id: []const u8, + capcha_question: []const u8, + url: []const u8, + }; + try mustache.render(tt, data{ + .capcha_id = capcha.capcha_id, + .capcha_question = capcha.capcha_question, + .url = url, + }, res.writer()); + try res.finish(); +} -- cgit v1.2.3-ZIG