aboutsummaryrefslogtreecommitdiffstats
path: root/src/web
diff options
context:
space:
mode:
authorsadbeast <sadbeast@sadbeast.com>2024-06-23 15:36:59 -0700
committersadbeast <sadbeast@sadbeast.com>2024-07-13 21:58:23 -0700
commit8d018d996c1eddb882dc64ebbd228bb0135944f3 (patch)
treed01956546a77dbae33357c9a5d174f511ac9b282 /src/web
downloadteamdraft-8d018d996c1eddb882dc64ebbd228bb0135944f3.tar.gz
teamdraft-8d018d996c1eddb882dc64ebbd228bb0135944f3.tar.bz2
Diffstat (limited to 'src/web')
-rw-r--r--src/web/dispatcher.zig52
-rw-r--r--src/web/drafts/pick.zig41
-rw-r--r--src/web/drafts/show.zig55
-rw-r--r--src/web/leagues/create.zig78
-rw-r--r--src/web/leagues/invite.zig78
-rw-r--r--src/web/leagues/show.zig80
-rw-r--r--src/web/picks/pick.zig37
-rw-r--r--src/web/picks/show.zig58
-rw-r--r--src/web/web.zig109
9 files changed, 588 insertions, 0 deletions
diff --git a/src/web/dispatcher.zig b/src/web/dispatcher.zig
new file mode 100644
index 0000000..77ea282
--- /dev/null
+++ b/src/web/dispatcher.zig
@@ -0,0 +1,52 @@
+pub const Dispatcher = struct {
+ app: *App,
+ // whether to try loading the user or not, this implies requires_user = false
+ load_user: bool = true,
+
+ // whether a user is required. When false, if we have a token, the user is still
+ // loaded (unless load_user = false).
+ requires_user: bool = false,
+
+ pub fn dispatch(self: *const Dispatcher, action: httpz.Action(*Env), req: *httpz.Request, res: *httpz.Response) !void {
+ const app = self.app;
+
+ var env = Env{
+ .app = app,
+ };
+ // defer env.deinit();
+ //
+ try self.doDispatch(action, req, res, &env);
+ }
+
+ fn doDispatch(_: *const Dispatcher, action: httpz.Action(*Env), req: *httpz.Request, res: *httpz.Response, env: *Env) !void {
+ const query = try req.query();
+ const user = try loadUser(env.app, query.get("user"));
+
+ if (user) |u| {
+ env.user = u;
+ std.debug.print("user: {}, {s}\n", .{ u.id, u.username });
+ }
+ try action(env, req, res);
+ }
+};
+
+fn loadUser(app: *App, optional_session_id: ?[]const u8) !?User {
+ const session_id = optional_session_id orelse return null;
+
+ var user: User = undefined;
+ const row = try app.pool.row("SELECT user_id, username FROM users WHERE user_id = $1", .{session_id});
+ if (row) |r| {
+ user = try r.to(User, .{});
+ } else {
+ return null;
+ }
+
+ return try User.init(app.allocator, user.id, user.username);
+}
+
+const httpz = @import("httpz");
+const std = @import("std");
+
+const App = @import("../app.zig").App;
+const User = @import("../User.zig");
+const Env = @import("../env.zig").Env;
diff --git a/src/web/drafts/pick.zig b/src/web/drafts/pick.zig
new file mode 100644
index 0000000..f710845
--- /dev/null
+++ b/src/web/drafts/pick.zig
@@ -0,0 +1,41 @@
+const Team = struct {
+ team_id: i16,
+ rank: ?i16,
+ name: []const u8,
+ division: []const u8,
+ league_user_id: ?i32,
+ pick_user: ?[]const u8,
+};
+
+pub fn handler(env: *Env, 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 env.app.pool.exec("CALL auto_draft($1)", .{draft_id});
+ _ = try env.app.pool.exec("INSERT INTO picks (league_user_id, draft_id, team_id) VALUES (current_picker($1), $1, $2)", .{ draft_id, team_id });
+ var result = try env.app.pool.query("SELECT cr.*, picks.league_user_id, users.name AS pick_user FROM current_rankings cr LEFT JOIN picks ON cr.team_id = picks.team_id AND picks.draft_id = $1 LEFT JOIN league_users USING(league_user_id) LEFT JOIN users USING (user_id) WHERE cr.team_id = $2", .{ draft_id, team_id });
+ defer result.deinit();
+
+ var team: Team = undefined;
+ while (try result.next()) |row| {
+ team = try row.to(Team, .{});
+ }
+ try web.renderWithDataPartials("pick", .{}, .{
+ .team_id = team.team_id,
+ .rank = team.rank,
+ .name = team.name,
+ .pick_user = team.pick_user,
+ .division = team.division,
+ .league_user_id = team.league_user_id,
+ .draft_id = draft_id,
+ }, req, res);
+ }
+ } else {
+ @panic("oh no");
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/drafts/show.zig b/src/web/drafts/show.zig
new file mode 100644
index 0000000..cd73438
--- /dev/null
+++ b/src/web/drafts/show.zig
@@ -0,0 +1,55 @@
+const Team = struct {
+ team_id: i16,
+ rank: ?i16,
+ name: []const u8,
+ division: []const u8,
+ pick_user: ?[]const u8,
+};
+
+const DraftInfo = struct {
+ draft_id: i32,
+ current_picker: i32,
+ status: []const u8,
+ league: []const u8,
+ round_time_remaining: ?i32,
+ message: []const u8,
+ can_pick: bool,
+};
+
+pub fn handler(env: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ if (req.params.get("id")) |draft_id| {
+ _ = try env.app.pool.exec("CALL auto_draft($1)", .{draft_id});
+
+ const picks_query = "SELECT * FROM current_draft_picks($1)";
+ var picks_result = try env.app.pool.query(picks_query, .{draft_id});
+ defer picks_result.deinit();
+
+ var teams: [32]Team = undefined;
+ var i: u8 = 0;
+ while (try picks_result.next()) |row| : (i += 1) {
+ const team = try row.to(Team, .{});
+ teams[i] = team;
+ }
+
+ var draft_info: DraftInfo = undefined;
+ const current_picker_query =
+ \\SELECT * FROM draft_info WHERE draft_id = $1
+ ;
+ var row = try env.app.pool.row(current_picker_query, .{draft_id});
+ if (row) |r| {
+ draft_info = try r.to(DraftInfo, .{});
+ }
+
+ try web.renderWithDataPartials("draft", .{ "pick", web.templates.get("pick").? }, .{
+ .teams = teams,
+ .draft_info = draft_info,
+ .draft_id = draft_info.draft_id,
+ }, req, res);
+ try row.?.deinit();
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/leagues/create.zig b/src/web/leagues/create.zig
new file mode 100644
index 0000000..66352cc
--- /dev/null
+++ b/src/web/leagues/create.zig
@@ -0,0 +1,78 @@
+const League = struct {
+ name: []const u8,
+};
+
+const LeaguePickScore = struct {
+ league_id: i32,
+ team: []const u8,
+ player: []const u8,
+ win: i32,
+ playoffs: ?i16,
+ divisional: ?i16,
+ conference: ?i16,
+ superbowl: ?i16,
+ champion: ?i16,
+ total: i32,
+};
+
+const PlayerScore = struct {
+ league_id: i32,
+ player: []const u8,
+ score: i32,
+};
+
+pub fn handler(env: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ if (req.params.get("id")) |league_id| {
+ var league: League = undefined;
+ const league_query =
+ \\SELECT name FROM leagues WHERE league_id = $1
+ ;
+ {
+ const row = try env.app.pool.row(league_query, .{league_id});
+ if (row) |r| {
+ league = try r.to(League, .{});
+ }
+ }
+
+ const scores_query =
+ \\SELECT * FROM league_pick_scores WHERE league_id = $1
+ ;
+ var scores_result = try env.app.pool.query(scores_query, .{league_id});
+ defer scores_result.deinit();
+
+ var scores: [32]LeaguePickScore = undefined;
+ {
+ var i: u8 = 0;
+ while (try scores_result.next()) |row| : (i += 1) {
+ const score = try row.to(LeaguePickScore, .{});
+ scores[i] = score;
+ }
+ }
+
+ const player_scores_query =
+ \\SELECT * FROM player_scores WHERE league_id = $1
+ ;
+ var player_scores_result = try env.app.pool.query(player_scores_query, .{league_id});
+ defer player_scores_result.deinit();
+
+ var player_scores: [2]PlayerScore = undefined;
+ {
+ var i: u8 = 0;
+ while (try player_scores_result.next()) |row| : (i += 1) {
+ const score = try row.to(PlayerScore, .{});
+ player_scores[i] = score;
+ }
+ }
+
+ try web.renderWithData("league", .{
+ .league = league,
+ .scores = scores,
+ .player_scores = player_scores,
+ }, req, res);
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/leagues/invite.zig b/src/web/leagues/invite.zig
new file mode 100644
index 0000000..66352cc
--- /dev/null
+++ b/src/web/leagues/invite.zig
@@ -0,0 +1,78 @@
+const League = struct {
+ name: []const u8,
+};
+
+const LeaguePickScore = struct {
+ league_id: i32,
+ team: []const u8,
+ player: []const u8,
+ win: i32,
+ playoffs: ?i16,
+ divisional: ?i16,
+ conference: ?i16,
+ superbowl: ?i16,
+ champion: ?i16,
+ total: i32,
+};
+
+const PlayerScore = struct {
+ league_id: i32,
+ player: []const u8,
+ score: i32,
+};
+
+pub fn handler(env: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ if (req.params.get("id")) |league_id| {
+ var league: League = undefined;
+ const league_query =
+ \\SELECT name FROM leagues WHERE league_id = $1
+ ;
+ {
+ const row = try env.app.pool.row(league_query, .{league_id});
+ if (row) |r| {
+ league = try r.to(League, .{});
+ }
+ }
+
+ const scores_query =
+ \\SELECT * FROM league_pick_scores WHERE league_id = $1
+ ;
+ var scores_result = try env.app.pool.query(scores_query, .{league_id});
+ defer scores_result.deinit();
+
+ var scores: [32]LeaguePickScore = undefined;
+ {
+ var i: u8 = 0;
+ while (try scores_result.next()) |row| : (i += 1) {
+ const score = try row.to(LeaguePickScore, .{});
+ scores[i] = score;
+ }
+ }
+
+ const player_scores_query =
+ \\SELECT * FROM player_scores WHERE league_id = $1
+ ;
+ var player_scores_result = try env.app.pool.query(player_scores_query, .{league_id});
+ defer player_scores_result.deinit();
+
+ var player_scores: [2]PlayerScore = undefined;
+ {
+ var i: u8 = 0;
+ while (try player_scores_result.next()) |row| : (i += 1) {
+ const score = try row.to(PlayerScore, .{});
+ player_scores[i] = score;
+ }
+ }
+
+ try web.renderWithData("league", .{
+ .league = league,
+ .scores = scores,
+ .player_scores = player_scores,
+ }, req, res);
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/leagues/show.zig b/src/web/leagues/show.zig
new file mode 100644
index 0000000..2bbdabd
--- /dev/null
+++ b/src/web/leagues/show.zig
@@ -0,0 +1,80 @@
+const League = struct {
+ name: []const u8,
+};
+
+const LeaguePickScore = struct {
+ season_id: i32,
+ league_id: i32,
+ team: []const u8,
+ player: []const u8,
+ win: i32,
+ playoffs: ?i16,
+ divisional: ?i16,
+ conference: ?i16,
+ superbowl: ?i16,
+ champion: ?i16,
+ total: i32,
+};
+
+const PlayerScore = struct {
+ player: []const u8,
+ score: i32,
+};
+
+pub fn handler(env: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ if (req.params.get("id")) |league_id| {
+ const query = try req.query();
+ var league: League = undefined;
+ const league_query =
+ \\SELECT name FROM leagues WHERE league_id = $1
+ ;
+ var league_result = (try env.app.pool.row(league_query, .{league_id})) orelse {
+ res.status = 404;
+ return;
+ };
+ defer league_result.deinit() catch {};
+
+ league = try league_result.to(League, .{});
+
+ var team_scores_result: *pg.Result = undefined;
+ var player_scores_result: *pg.Result = undefined;
+
+ if (query.get("season")) |season_id| {
+ player_scores_result = try env.app.pool.query("SELECT player, score FROM season_player_scores WHERE league_id = $1 AND season_id = season_id_by_year($2)", .{ league_id, season_id });
+ team_scores_result = try env.app.pool.query("SELECT * FROM league_scores WHERE league_id = $1 AND season_id = season_id_by_year($2)", .{ league_id, season_id });
+ } else {
+ player_scores_result = try env.app.pool.query("SELECT player, score FROM current_player_scores WHERE league_id = $1", .{league_id});
+ team_scores_result = try env.app.pool.query("SELECT * FROM league_scores WHERE league_id = $1 AND season_id = current_season()", .{league_id});
+ }
+ defer player_scores_result.deinit();
+ defer team_scores_result.deinit();
+
+ var scores: [32]LeaguePickScore = undefined;
+ var score_count: u8 = 0;
+ while (try team_scores_result.next()) |row| : (score_count += 1) {
+ const score = try row.to(LeaguePickScore, .{ .dupe = true });
+ scores[score_count] = score;
+ }
+
+ var player_scores: [2]PlayerScore = undefined;
+ {
+ var i: u8 = 0;
+ while (try player_scores_result.next()) |row| : (i += 1) {
+ const score = try row.to(PlayerScore, .{});
+ player_scores[i] = score;
+ }
+ }
+
+ try web.renderWithData("league", .{
+ .league = league,
+ .scores = scores[0..score_count],
+ .player_scores = player_scores,
+ }, req, res);
+ }
+}
+
+const httpz = @import("httpz");
+const pg = @import("pg");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/picks/pick.zig b/src/web/picks/pick.zig
new file mode 100644
index 0000000..51e05c4
--- /dev/null
+++ b/src/web/picks/pick.zig
@@ -0,0 +1,37 @@
+const Team = struct {
+ team_id: i16,
+ rank: ?i16,
+ name: []const u8,
+ division: []const u8,
+ league_user_id: ?i32,
+};
+
+pub fn handler(env: *Env, 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| {
+ var result = try env.app.pool.query("SELECT cr.*, picks.league_user_id from current_rankings cr left join picks on cr.team_id = picks.team_id AND picks.draft_id = $1 WHERE cr.team_id = $2", .{ draft_id, team_id });
+ defer result.deinit();
+
+ var team: Team = undefined;
+ while (try result.next()) |row| {
+ team = try row.to(Team, .{});
+ }
+ try web.renderWithDataPartials("pick", .{}, .{
+ .team_id = team.team_id,
+ .rank = team.rank,
+ .name = team.name,
+ .division = team.division,
+ .league_user_id = team.league_user_id,
+ .draft_id = draft_id,
+ }, req, res);
+ }
+ } else {
+ @panic("oh no");
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/picks/show.zig b/src/web/picks/show.zig
new file mode 100644
index 0000000..f802069
--- /dev/null
+++ b/src/web/picks/show.zig
@@ -0,0 +1,58 @@
+const Team = struct {
+ team_id: i16,
+ rank: ?i16,
+ name: []const u8,
+ division: []const u8,
+ username: ?[]const u8,
+};
+
+const DraftInfo = struct {
+ draft_id: i32,
+ current_picker: i32,
+ status: []const u8,
+ round_time_remaining: ?i32,
+ message: []const u8,
+};
+
+pub fn handler(env: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ if (req.params.get("id")) |draft_id| {
+ _ = try env.app.pool.exec("CALL auto_draft($1)", .{draft_id});
+
+ const picks_query =
+ \\SELECT cr.*, users.username from current_rankings cr
+ \\LEFT JOIN picks ON cr.team_id = picks.team_id AND picks.draft_id = $1
+ \\LEFT JOIN league_users ON league_users.league_user_id = picks.league_user_id
+ \\LEFT JOIN users ON users.user_id = league_users.user_id
+ ;
+ var picks_result = try env.app.pool.query(picks_query, .{draft_id});
+ defer picks_result.deinit();
+
+ var teams: [32]Team = undefined;
+ var i: u8 = 0;
+ while (try picks_result.next()) |row| : (i += 1) {
+ const team = try row.to(Team, .{});
+ teams[i] = team;
+ }
+
+ var draft_info: DraftInfo = undefined;
+ const current_picker_query =
+ \\SELECT * FROM draft_info WHERE draft_id = $1
+ ;
+ var row = try env.app.pool.row(current_picker_query, .{draft_id});
+ if (row) |r| {
+ draft_info = try r.to(DraftInfo, .{});
+ }
+
+ try web.renderWithDataPartials("draft", .{ "pick", web.pick_template }, .{
+ .teams = teams,
+ .draft_info = draft_info,
+ .can_pick = if (draft_info.round_time_remaining) |_| true else false,
+ }, req, res);
+ try row.?.deinit();
+ }
+}
+
+const httpz = @import("httpz");
+const Env = @import("../../env.zig").Env;
+const web = @import("../../web/web.zig");
+const std = @import("std");
diff --git a/src/web/web.zig b/src/web/web.zig
new file mode 100644
index 0000000..184a1a3
--- /dev/null
+++ b/src/web/web.zig
@@ -0,0 +1,109 @@
+pub const Templates = std.StringHashMap(Template);
+
+pub var templates: Templates = undefined;
+
+pub fn start(app: *App) !void {
+ const allocator = app.allocator;
+
+ var server = try httpz.ServerCtx(*const Dispatcher, *Env).init(allocator, .{
+ .address = "0.0.0.0",
+ .port = 5882,
+ }, undefined);
+ defer server.deinit();
+
+ templates = Templates.init(allocator);
+ defer {
+ var it = templates.valueIterator();
+ while (it.next()) |t| {
+ t.deinit(allocator);
+ }
+ templates.deinit();
+ }
+
+ inline for (config.templates) |template| {
+ try templates.put(template, try loadTemplate(allocator, template));
+ }
+
+ server.dispatcher(Dispatcher.dispatch);
+
+ var router = server.router();
+ {
+ var routes = router.group("", .{ .ctx = &Dispatcher{ .app = app } });
+ routes.get("/", index);
+ routes.get("/about", about);
+ routes.get("/drafts/:id", @import("drafts/show.zig").handler);
+ routes.get("/drafts/:id/pick", @import("drafts/pick.zig").handler);
+ routes.get("/leagues/:id", @import("leagues/show.zig").handler);
+ routes.post("/leagues", @import("leagues/create.zig").handler);
+ routes.get("/invites/:id", @import("leagues/invite.zig").handler);
+ }
+
+ // use get/post/put/head/patch/options/delete
+ // you can also use "all" to attach to all methods
+
+ // start the server in the current thread, blocking.
+ try server.listen();
+}
+
+fn pick(_: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ try render("pick", req, res);
+}
+
+fn about(_: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ // try render(templates.get("about").?, req, res);
+ try renderWithData("about", .{ .title = "foo" }, req, res);
+}
+
+fn index(_: *Env, req: *httpz.Request, res: *httpz.Response) !void {
+ try render("index", req, res);
+}
+
+fn loadTemplate(allocator: std.mem.Allocator, comptime template_name: []const u8) !Template {
+ return switch (try mustache.parseText(allocator, @embedFile("../views/" ++ template_name ++ ".mustache"), .{}, .{ .copy_strings = false })) {
+ .success => |t| t,
+ .parse_error => @panic("layout parse error"),
+ };
+}
+
+fn loadTemplateFile(allocator: std.mem.Allocator, template_name: []const u8) !Template {
+ const file = std.fs.cwd().openFile(try std.fmt.allocPrint(allocator, "src/views/{s}.mustache", .{template_name}), .{}) catch @panic("could not find template");
+ defer file.close();
+ const template_bytes = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch @panic("couldn't read template file ");
+
+ return switch (try mustache.parseText(allocator, template_bytes, .{}, .{ .copy_strings = false })) {
+ .success => |t| t,
+ .parse_error => @panic("layout parse error"),
+ };
+}
+
+pub fn renderWithDataPartials(template_name: []const u8, partials: anytype, data: anytype, req: *httpz.Request, res: *httpz.Response) !void {
+ var template: Template = undefined;
+ if (config.dev) {
+ template = try loadTemplateFile(res.arena, template_name);
+ } else {
+ template = templates.get(template_name).?;
+ }
+ const content = try mustache.allocRenderPartials(res.arena, template, partials, data);
+ if (req.header("hx-request")) |_| {
+ res.body = content;
+ } else {
+ res.body = try mustache.allocRender(res.arena, templates.get("layout").?, .{ .content = content });
+ }
+}
+
+pub fn renderWithData(template_name: []const u8, data: anytype, req: *httpz.Request, res: *httpz.Response) !void {
+ try renderWithDataPartials(template_name, .{}, data, req, res);
+}
+
+pub fn render(template_name: []const u8, req: *httpz.Request, res: *httpz.Response) !void {
+ try renderWithData(template_name, .{}, req, res);
+}
+
+const config = @import("config");
+const App = @import("../app.zig").App;
+const Env = @import("../env.zig").Env;
+const httpz = @import("httpz");
+const mustache = @import("mustache");
+const Template = mustache.Template;
+const std = @import("std");
+const Dispatcher = @import("dispatcher.zig").Dispatcher;