aboutsummaryrefslogtreecommitdiff
path: root/zig-comments/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'zig-comments/src/main.zig')
-rw-r--r--zig-comments/src/main.zig163
1 files changed, 163 insertions, 0 deletions
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();
+}