aboutsummaryrefslogtreecommitdiff
path: root/comments/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'comments/src/main.zig')
-rw-r--r--comments/src/main.zig136
1 files changed, 95 insertions, 41 deletions
diff --git a/comments/src/main.zig b/comments/src/main.zig
index 165dff0..161f250 100644
--- a/comments/src/main.zig
+++ b/comments/src/main.zig
@@ -1,6 +1,6 @@
const std = @import("std");
const zws = @import("zws");
-const pq = @import("pq");
+const pg = @import("pg");
const mustache = @import("mustache");
const smtp = @import("smtp");
@@ -8,38 +8,50 @@ const Params = zws.Params;
const Err = error{
AccessDenied,
+ AlreadyConnected,
BrokenPipe,
- ColumnNotFound,
+ Closed,
+ ConnectionBusy,
ConnectionResetByPeer,
ConnectionTimedOut,
DeviceBusy,
DiskQuota,
+ EndOfStream,
+ FieldColumnMismatch,
+ FileDescriptorNotASocket,
FileTooBig,
InputOutput,
InvalidArgument,
InvalidCharacter,
+ InvalidDataRow,
InvalidLength,
- InvalidRequestMethod,
+ InvalidProtocolOption,
+ InvalidUUID,
IsDir,
LockViolation,
Malformatted,
- NetNameDeleted,
+ NetworkSubsystemFailed,
+ NoDevice,
NoSpaceLeft,
+ NotAString,
NotOpenForReading,
NotOpenForWriting,
OperationAborted,
OutOfMemory,
- PqError,
+ Overflow,
+ PermissionDenied,
+ PG,
+ SocketNotBound,
SocketNotConnected,
- StreamTooLong,
SystemResources,
+ TimeoutTooBig,
Unexpected,
+ UnexpectedDBMessage,
WouldBlock,
- Overflow,
- EndOfStream,
};
+
const Ctx = struct {
- db: pq.Db,
+ db: *pg.Conn,
pub fn clone(self: @This()) Ctx {
return Ctx{
.db = self.db,
@@ -127,14 +139,58 @@ const router = Rtr{
.notfound = notfound,
};
+const UriParams = struct {
+ connOpts: pg.Conn.Opts,
+ authOpts: pg.Conn.AuthOpts,
+ aa: std.heap.ArenaAllocator,
+ pub fn deinit(self: *UriParams) void {
+ self.aa.deinit();
+ }
+};
+
+fn toParams(uri: std.Uri, parent_a: std.mem.Allocator) !UriParams {
+ var aa = std.heap.ArenaAllocator.init(parent_a);
+ errdefer aa.deinit();
+ const a = aa.allocator();
+ const co: pg.Conn.Opts = .{
+ .host = if (uri.host) |host| try host.toRawMaybeAlloc(a) else "localhost",
+ .port = uri.port orelse 5432,
+ };
+ const ao: pg.Conn.AuthOpts = .{
+ .username = if (uri.user) |user| try user.toRawMaybeAlloc(a) else "postgres",
+ .password = if (uri.password) |password| try password.toRawMaybeAlloc(a) else "",
+ .database = if (uri.path.isEmpty()) "postgres" else std.mem.trimLeft(u8, try uri.path.toRawMaybeAlloc(a), "/"),
+ // TODO startup parameters
+ };
+ return .{
+ .connOpts = co,
+ .authOpts = ao,
+ .aa = aa,
+ };
+}
+
/// Run as a CGI program!
pub fn main() !void {
const allocator = gpa.allocator();
const db_url = std.posix.getenv("DATABASE_URL") orelse "postgresql://comments@localhost/comments";
- var db = try pq.Db.init(db_url);
+ const uri = try std.Uri.parse(db_url);
+ var uriParams = try toParams(uri, allocator);
+ defer uriParams.deinit();
+ var db = try pg.Conn.open(allocator, uriParams.connOpts);
+ defer db.deinit();
+
+ db.auth(uriParams.authOpts) catch |e| switch (e) {
+ error.PG => {
+ std.log.err("PG error authenticating {s}", .{db.err.?.message});
+ return;
+ },
+ else => {
+ std.log.err("Error authenticating! {}", .{e});
+ return;
+ },
+ };
// 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.posix.getenv("REQUEST_METHOD") orelse "GET") orelse {
return error.InvalidRequestMethod;
@@ -148,7 +204,7 @@ pub fn main() !void {
.transfer_encoding = .none,
.headers = Headers.init(allocator),
};
- const ctx = Ctx{ .db = db };
+ const ctx = Ctx{ .db = &db };
try router.handle(&res, ctx);
}
@@ -185,16 +241,14 @@ fn get_comment(res: *Response, ctx: Ctx, _: Params) Err!void {
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);
+ var qr = try ctx.db.queryOpts(
+ \\ select author,comment,ts::text from comments where url = $1 order by ts
+ , .{url}, .{ .column_names = true });
+ defer qr.deinit();
+ var mapper = qr.mapper(Comment, .{ .allocator = res.allocator, .dupe = true });
+ while (try mapper.next()) |nxt| {
+ try comments.append(nxt);
}
-
const rr = @embedFile("templates/comments.html");
const tt = mustache.parseText(res.allocator, rr, .{}, .{ .copy_strings = false }) catch unreachable;
res.transfer_encoding = .chunked;
@@ -233,30 +287,29 @@ fn post_comment(res: *Response, ctx: Ctx, _: Params) Err!void {
// 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()) {
+ var qr = try ctx.db.query("select answer from capchas where id = $1", .{form_val.capcha_id});
+ defer qr.deinit();
+ const row: pg.Row = try qr.next() orelse {
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);
+ };
+ const ans = row.get([]const u8, 0);
if (!std.mem.eql(u8, ans, form_val.capcha_answer)) {
std.log.err("bad capcha answer {s} expected {s}", .{ form_val.capcha_answer, ans });
try constresponse(res, @embedFile("templates/capchainvalid.html"), std.http.Status.unauthorized);
return;
}
+ try qr.drain();
}
// 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();
+ _ = try ctx.db.exec("insert into comments(url,author,comment) values($1, $2, $3)", .{
+ form_val.url,
+ form_val.author,
+ form_val.comment,
+ });
}
// Send me an email
@@ -292,18 +345,19 @@ fn get_form(res: *Response, ctx: Ctx, _: Params) Err!void {
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);
+
+ var qr = try ctx.db.queryOpts("select id::text, question from capchas order by random() limit 1", .{}, .{ .column_names = true });
+ defer qr.deinit();
+ var m = qr.mapper(Capcha, .{ .allocator = res.allocator, .dupe = true });
+ const capcha = try m.next() orelse {
+ std.log.err("no capcha!", .{});
+ try badrequest(res, ctx);
+ return;
+ };
const rr = @embedFile("templates/form.html");
const tt = mustache.parseText(res.allocator, rr, .{}, .{ .copy_strings = false }) catch unreachable;