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.zig294
1 files changed, 0 insertions, 294 deletions
diff --git a/zig-comments/src/main.zig b/zig-comments/src/main.zig
deleted file mode 100644
index 898b689..0000000
--- a/zig-comments/src/main.zig
+++ /dev/null
@@ -1,294 +0,0 @@
-const std = @import("std");
-const zws = @import("zws");
-const pq = @import("pq.zig");
-const mustache = @import("mustache");
-
-const Params = zws.Params;
-
-const Err = error{
- Unexpected,
- AccessDenied,
- OutOfMemory,
- InputOutput,
- SystemResources,
- IsDir,
- OperationAborted,
- BrokenPipe,
- ConnectionResetByPeer,
- ConnectionTimedOut,
- NotOpenForReading,
- NetNameDeleted,
- WouldBlock,
- StreamTooLong,
- Malformatted,
- InvalidLength,
- InvalidCharacter,
- NoSpaceLeft,
- PqError,
- ColumnNotFound,
- DiskQuota,
- FileTooBig,
- DeviceBusy,
- InvalidArgument,
- NotOpenForWriting,
- LockViolation,
- InvalidRequestMethod,
-};
-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 Request = struct {
- method: std.http.Method,
- target: []const u8,
-};
-const ResponseTransfer = union(enum) {
- content_length: u64,
- chunked: void,
- none: void,
-};
-const Headers = struct {
- const Entry = struct { key: []const u8, val: []const u8 };
- _internal: std.ArrayList(Entry),
-
- fn init(allocator: std.mem.Allocator) Headers {
- return .{ ._internal = std.ArrayList(Entry).init(allocator) };
- }
- fn append(self: *@This(), key: []const u8, val: []const u8) !void {
- try self._internal.append(.{ .key = key, .val = val });
- }
-};
-const Response = struct {
- allocator: std.mem.Allocator,
- request: Request,
- // TODO other fields and writer function to write headers and body to stdout
- status: std.http.Status,
- transfer_encoding: ResponseTransfer,
- headers: Headers,
- fn reader(_: @This()) std.fs.File.Reader {
- return std.io.getStdIn().reader();
- }
- fn do(self: @This()) !void {
- for (self.headers._internal.items) |tup| {
- try std.io.getStdOut().writeAll(tup.key);
- try std.io.getStdOut().writeAll(": ");
- try std.io.getStdOut().writeAll(tup.val);
- try std.io.getStdOut().writeAll("\r\n");
- }
- try std.io.getStdOut().writeAll("\r\n");
- }
- fn writer(_: @This()) std.fs.File.Writer {
- return std.io.getStdOut().writer();
- }
- fn finish(_: @This()) !void {
- // TODO Write empty lines? or just do nothing
- }
-};
-
-const Rtr = zws.Router(*Response, Ctx, Err);
-const router = Rtr{
- .allocator = gpa.allocator(),
- .handlers = &[_]Rtr.Handler{
- .{
- .method = .GET,
- .pattern = "/api/comment",
- .handle_fn = get_comment,
- },
- .{
- .method = .POST,
- .pattern = "/api/comment",
- .handle_fn = post_comment,
- },
- .{
- .method = .GET,
- .pattern = "/api/form",
- .handle_fn = get_form,
- },
- },
- .notfound = notfound,
-};
-
-/// Run as a CGI program!
-pub fn main() !void {
- const allocator = gpa.allocator();
- const db_url = std.os.getenv("DATABASE_URL") orelse "postgresql://comments@localhost/comments";
- var db = try pq.Db.init(db_url);
- // try db.exec(@embedFile("migrations/0_init.sql"));
- // try db.exec(@embedFile("migrations/1_capcha.sql"));
- defer db.deinit();
- const req = Request{
- .method = std.meta.stringToEnum(std.http.Method, std.os.getenv("REQUEST_METHOD") orelse "GET") orelse {
- return error.InvalidRequestMethod;
- },
- .target = std.os.getenv("REQUEST_URI") orelse "/",
- };
- var res = Response{
- .allocator = allocator,
- .request = req,
- .status = .bad_request,
- .transfer_encoding = .none,
- .headers = Headers.init(allocator),
- };
- const ctx = Ctx{ .db = db };
- try router.handle(&res, ctx);
-}
-
-fn notfound(res: *Response, _: Ctx) Err!void {
- const rr = @embedFile("templates/notfound.html");
- try constresponse(res, rr, .not_found);
-}
-
-fn badrequest(res: *Response, _: Ctx) Err!void {
- const rr = @embedFile("templates/badrequest.html");
- try constresponse(res, rr, .bad_request);
-}
-
-fn constresponse(res: *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.writer().writeAll(rr);
- try res.finish();
-}
-
-fn get_comment(res: *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;
- };
-
- const Comment = struct {
- author: []const u8,
- comment: []const u8,
- ts: []const u8,
- };
- var comments = std.ArrayList(Comment).init(res.allocator);
- var stmt = try ctx.db.prepare_statement(res.allocator,
- \\ select author,comment,ts from comments where url = $1 order by ts
- );
- defer stmt.deinit();
- try stmt.bind(0, url);
- while (try stmt.step()) {
- const cmt = try stmt.read_struct(Comment);
- try comments.append(cmt);
- }
-
- const rr = @embedFile("templates/comments.html");
- const tt = comptime mustache.parseComptime(rr, .{}, .{});
- res.transfer_encoding = .chunked;
- try res.headers.append("content-type", "text/html");
- try res.do();
-
- const data = struct {
- comments: []const Comment,
- };
- try mustache.render(tt, data{
- .comments = comments.items,
- }, res.writer());
- try res.finish();
-}
-
-fn post_comment(res: *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;
- };
-
- // Validate the capcha
- {
- var stmt = try ctx.db.prepare_statement(res.allocator, "select answer from capchas where id = $1");
- defer stmt.deinit();
- try stmt.bind(0, form_val.capcha_id);
- if (!try stmt.step()) {
- std.log.err("missing capcha_id {s}", .{form_val.capcha_id});
- try badrequest(res, ctx);
- return;
- }
- const ans = try stmt.read_column(0, []const u8);
- if (!std.mem.eql(u8, ans, form_val.capcha_answer)) {
- try constresponse(res, @embedFile("templates/capchainvalid.html"), std.http.Status.unauthorized);
- return;
- }
- }
-
- // Add the comment...
- {
- var stmt = try ctx.db.prepare_statement(res.allocator, "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();
- }
-
- // And redirect!
- 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: *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(res.allocator, "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 {
- id: []const u8,
- 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.id,
- .capcha_question = capcha.question,
- .url = url,
- }, res.writer());
- try res.finish();
-}