diff options
author | sadbeast <sadbeast@sadbeast.com> | 2024-07-16 18:16:29 -0700 |
---|---|---|
committer | sadbeast <sadbeast@sadbeast.com> | 2024-10-05 16:40:55 -0700 |
commit | 6bd24af2ffbea91db1b10a5d5258980ce2068c7f (patch) | |
tree | 66634833f2d45260be5fcaf9111400eda12f03cc /src/web/web.zig | |
download | teamdraft-6bd24af2ffbea91db1b10a5d5258980ce2068c7f.tar.gz teamdraft-6bd24af2ffbea91db1b10a5d5258980ce2068c7f.tar.bz2 |
Diffstat (limited to 'src/web/web.zig')
-rw-r--r-- | src/web/web.zig | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/src/web/web.zig b/src/web/web.zig new file mode 100644 index 0000000..e0882a0 --- /dev/null +++ b/src/web/web.zig @@ -0,0 +1,241 @@ +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; |