pub fn start(app: *App) !void { const allocator = app.allocator; var server = try httpz.Server(*App).init(allocator, .{ .address = "0.0.0.0", .port = 5882, .request = .{ .max_form_count = 4, }, }, app); defer server.deinit(); const router = server.router(); { // publicly accessible var routes = router.group("/", .{}); routes.get("/", index); routes.get("/about", about); routes.get("/invite/:code", acceptInvite); routes.post("/drafts", createDraft); routes.get("/drafts/:id", showDraft); routes.post("/drafts/:id/pick", createPick); } const http_address = try std.fmt.allocPrint(allocator, "http://{s}:{d}", .{ "0.0.0.0", 5882 }); logz.info().ctx("http").string("address", http_address).log(); allocator.free(http_address); // blocks try server.listen(); } fn index(_: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { var data = Data.init(res.arena); defer data.deinit(); const dt = try zul.DateTime.fromUnix(std.time.timestamp(), .seconds); const tomorrow = try dt.add(1, .hours); var root = try data.object(); try root.put("draft_time", try std.fmt.allocPrint(req.arena, "{s}T{s}", .{ tomorrow.date(), tomorrow.time() })); try renderData("index", &data, req, res); } fn about(_: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { try render("about", req, res); } fn acceptInvite(ctx: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { var data = Data.init(res.arena); defer data.deinit(); if (req.params.get("code")) |code| { var accept_invite_result = (try ctx.app.pool.row("SELECT * FROM accept_invite($1)", .{code})) orelse { res.status = 500; return; }; defer accept_invite_result.deinit() catch {}; const session_id = accept_invite_result.get([]const u8, 0); const draft_id = accept_invite_result.get(i64, 1); res.header("Set-Cookie", try std.fmt.allocPrint(res.arena, "s={s}; Path=/", .{session_id})); res.header("Location", try std.fmt.allocPrint(res.arena, "/drafts/{}", .{draft_id})); res.status = 302; } } fn createPick(ctx: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { const query = try req.query(); if (query.get("team_id")) |team_id| { if (req.params.get("id")) |draft_id| { _ = try ctx.app.pool.exec("CALL auto_draft($1)", .{draft_id}); _ = try ctx.app.pool.exec("INSERT INTO picks (draft_user_id, draft_id, team_id) VALUES (current_picker_id($1), $1, $2)", .{ draft_id, team_id }); res.header("Location", try std.fmt.allocPrint(res.arena, "/drafts/{s}", .{draft_id})); res.header("HX-Location", try std.fmt.allocPrint(res.arena, "/drafts/{s}", .{draft_id})); res.status = 302; } } } fn createDraft(ctx: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { const formData = try req.formData(); if (formData.get("player1")) |player1| { logz.info().string("player1", player1).log(); const player2 = formData.get("player2") orelse "Player 2"; var create_draft_result: pg.QueryRow = undefined; if (formData.get("draft")) |_| { if (formData.get("draft_time")) |draft_time| { logz.info().string("draft_time", draft_time).log(); create_draft_result = (try ctx.app.pool.row("SELECT * FROM create_draft(1, $1, $2, $3)", .{ player1, player2, draft_time })) orelse { res.status = 500; return; }; } } else { create_draft_result = (try ctx.app.pool.row("SELECT * FROM create_draft(1, $1, $2, null)", .{ player1, player2 })) orelse { res.status = 500; return; }; } defer create_draft_result.deinit() catch {}; const session_id = create_draft_result.get([]const u8, 0); const draft_id = create_draft_result.get(i64, 1); res.header("Set-Cookie", try std.fmt.allocPrint(res.arena, "s={s}; Path=/", .{session_id})); res.header("Location", try std.fmt.allocPrint(res.arena, "/drafts/{}", .{draft_id})); res.status = 302; return; } var data = Data.init(res.arena); defer data.deinit(); if (formData.get("draft")) |_| { var root = try data.root(.object); try root.put("draft", true); } try renderData("index", &data, req, res); } pub const Team = struct { team_id: i32, rank: ?i16, name: []const u8, picked: bool, pick_user: ?[]const u8, }; pub const DraftInfo = struct { draft_id: i64, current_player_id: ?i64, current_player_name: ?[]const u8, draft_status_id: i16, round_time_remaining: ?i32, message: ?[]const u8, can_pick: bool, }; fn showDraft(ctx: *RequestContext, req: *httpz.Request, res: *httpz.Response) !void { if (req.params.get("id")) |draft_id| { var draft_count = (try ctx.app.pool.row("SELECT count(1) FROM drafts WHERE draft_id = $1", .{draft_id})) orelse @panic("oh no"); defer draft_count.deinit() catch {}; if (draft_count.get(i64, 0) == 0) { return ctx.app.notFound(req, res); } var data = Data.init(res.arena); var root = try data.root(.object); _ = try ctx.app.pool.exec("CALL auto_draft($1)", .{draft_id}); const picks_query = "SELECT * FROM current_draft_picks($1)"; var picks_result = try ctx.app.pool.query(picks_query, .{draft_id}); defer picks_result.deinit(); var teams = try data.array(); while (try picks_result.next()) |row| { const team = try row.to(Team, .{}); var team_data = try data.object(); try team_data.put("name", team.name); try team_data.put("pick_user", team.pick_user); try team_data.put("picked", team.picked); try team_data.put("rank", team.rank); try team_data.put("id", team.team_id); try teams.append(team_data); } try root.put("teams", teams); var draft_info: DraftInfo = undefined; const current_picker_query = \\SELECT * FROM draft_info WHERE draft_id = $1 ; var row = try ctx.app.pool.row(current_picker_query, .{draft_id}); if (row) |r| { // defer r.denit(); draft_info = try r.to(DraftInfo, .{}); var can_pick = false; if (draft_info.current_player_id) |current_player_id| { if (ctx.user) |user| { can_pick = draft_info.can_pick and current_player_id == user.id; } } try root.put("can_pick", can_pick); try root.put("running", draft_info.draft_status_id == 2); try root.put("draft_id", draft_info.draft_id); try root.put("message", draft_info.message); try root.put("round_time_remaining", draft_info.round_time_remaining); try root.put("current_picker", draft_info.current_player_name); } const code_query = \\SELECT code FROM draft_user_invites \\ JOIN draft_users USING(draft_user_id) \\ WHERE draft_id = $1 ; var code_result = (try ctx.app.pool.row(code_query, .{draft_id})) orelse @panic("oh no"); const code = code_result.get([]const u8, 0); defer code_result.deinit() catch {}; try root.put("code", code); defer data.deinit(); try renderData("draft", &data, req, res); try row.?.deinit(); } } pub fn render(template_name: []const u8, req: *httpz.Request, res: *httpz.Response) !void { var data = Data.init(res.arena); defer data.deinit(); try renderData(template_name, &data, req, res); } pub fn renderData(template_name: []const u8, data: *Data, req: *httpz.Request, res: *httpz.Response) !void { if (zmpl.find(template_name)) |template| { res.body = try template.renderWithOptions(data, .{ .layout = if (req.header("hx-request")) |_| zmpl.find("body") else zmpl.find("layout") }); } } const App = @import("../App.zig"); const RequestContext = @import("../RequestContext.zig"); const std = @import("std"); const Allocator = std.mem.Allocator; const config = @import("config"); const httpz = @import("httpz"); const logz = @import("logz"); const pg = @import("pg"); const zmpl = @import("zmpl"); const zul = @import("zul"); const Data = zmpl.Data;