2022-11-18 04:12:49 +00:00
|
|
|
const std = @import("std");
|
2022-12-06 10:53:41 +00:00
|
|
|
const util = @import("util");
|
2022-12-10 09:42:07 +00:00
|
|
|
const http = @import("http");
|
2022-11-24 11:50:25 +00:00
|
|
|
const controllers = @import("../controllers.zig");
|
2022-11-18 04:12:49 +00:00
|
|
|
|
|
|
|
pub const routes = .{
|
2022-11-27 01:33:46 +00:00
|
|
|
controllers.apiEndpoint(index),
|
|
|
|
controllers.apiEndpoint(about),
|
|
|
|
controllers.apiEndpoint(login),
|
|
|
|
controllers.apiEndpoint(global_timeline),
|
|
|
|
controllers.apiEndpoint(cluster.overview),
|
2022-12-06 10:53:41 +00:00
|
|
|
controllers.apiEndpoint(media),
|
2022-12-09 12:31:23 +00:00
|
|
|
controllers.apiEndpoint(static),
|
2022-12-10 09:42:07 +00:00
|
|
|
controllers.apiEndpoint(signup.page),
|
|
|
|
controllers.apiEndpoint(signup.with_invite),
|
|
|
|
controllers.apiEndpoint(signup.submit),
|
2022-12-09 12:31:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const static = struct {
|
|
|
|
pub const path = "/static/:path*";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub const Args = struct {
|
|
|
|
path: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, _: anytype) !void {
|
|
|
|
if (std.mem.indexOf(u8, req.args.path, "..") != null) return error.NotFound;
|
|
|
|
std.log.debug("opening {s}", .{req.args.path});
|
|
|
|
var dir = try std.fs.cwd().openDir("static", .{});
|
|
|
|
defer dir.close();
|
|
|
|
var file = dir.openFile(req.args.path, .{}) catch |err| switch (err) {
|
|
|
|
error.FileNotFound => return error.NotFound,
|
|
|
|
else => |e| return e,
|
|
|
|
};
|
|
|
|
defer file.close();
|
|
|
|
|
|
|
|
var stream = try res.open(.ok);
|
|
|
|
defer stream.close();
|
|
|
|
|
|
|
|
var buf: [1 << 16]u8 = undefined;
|
|
|
|
while (true) {
|
|
|
|
const count = try file.reader().readAll(&buf);
|
|
|
|
if (count == 0) break;
|
|
|
|
|
|
|
|
std.log.debug("writing {} bytes from {s}", .{ count, req.args.path });
|
|
|
|
try stream.writer().writeAll(buf[0..count]);
|
|
|
|
}
|
|
|
|
|
|
|
|
try stream.finish();
|
|
|
|
}
|
2022-11-18 04:12:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const index = struct {
|
|
|
|
pub const path = "/";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub fn handler(_: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
if (srv.user_id == null) {
|
|
|
|
try res.headers.put("Location", about.path);
|
|
|
|
return res.status(.see_other);
|
|
|
|
}
|
|
|
|
|
2022-12-09 12:31:23 +00:00
|
|
|
try res.template(.ok, srv, "Hello", .{});
|
2022-11-18 04:12:49 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const about = struct {
|
|
|
|
pub const path = "/about";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub fn handler(_: anytype, res: anytype, srv: anytype) !void {
|
2022-12-09 12:31:23 +00:00
|
|
|
try res.template(.ok, srv, tmpl, .{});
|
2022-11-18 04:12:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-09 12:31:23 +00:00
|
|
|
const tmpl = @embedFile("./web/about.tmpl.html");
|
2022-11-18 04:12:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const login = struct {
|
|
|
|
pub const path = "/login";
|
|
|
|
pub const method = .POST;
|
|
|
|
|
|
|
|
pub const Body = struct {
|
|
|
|
username: []const u8,
|
|
|
|
password: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
const token = try srv.login(req.body.username, req.body.password);
|
|
|
|
|
|
|
|
try res.headers.put("Location", index.path);
|
|
|
|
var buf: [64]u8 = undefined;
|
|
|
|
const cookie_name = try std.fmt.bufPrint(&buf, "token.{s}", .{req.body.username});
|
|
|
|
try res.headers.setCookie(cookie_name, token.token, .{});
|
|
|
|
try res.headers.setCookie("active_account", req.body.username, .{ .HttpOnly = false });
|
|
|
|
|
|
|
|
try res.status(.see_other);
|
|
|
|
}
|
|
|
|
};
|
2022-11-18 12:07:58 +00:00
|
|
|
|
2022-12-10 09:42:07 +00:00
|
|
|
const signup = struct {
|
|
|
|
const tmpl = @embedFile("./web/signup.tmpl.html");
|
|
|
|
|
|
|
|
fn servePage(
|
|
|
|
invite_code: ?[]const u8,
|
|
|
|
error_msg: ?[]const u8,
|
|
|
|
status: http.Status,
|
|
|
|
res: anytype,
|
|
|
|
srv: anytype,
|
|
|
|
) !void {
|
|
|
|
const invite = if (invite_code) |code| srv.validateInvite(code) catch |err| switch (err) {
|
|
|
|
error.InvalidInvite => return servePage(null, "Invite is not valid", .bad_request, res, srv),
|
|
|
|
else => |e| return e,
|
|
|
|
} else null;
|
|
|
|
defer util.deepFree(srv.allocator, invite);
|
|
|
|
|
|
|
|
try res.template(status, srv, tmpl, .{
|
|
|
|
.error_msg = error_msg,
|
|
|
|
.invite = invite,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const page = struct {
|
|
|
|
pub const path = "/signup";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub fn handler(_: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
try servePage(null, null, .ok, res, srv);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const with_invite = struct {
|
|
|
|
pub const path = "/invite/:code";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub const Args = struct {
|
|
|
|
code: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
std.log.debug("{s}", .{req.args.code});
|
|
|
|
try servePage(req.args.code, null, .ok, res, srv);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const submit = struct {
|
|
|
|
pub const path = "/signup";
|
|
|
|
pub const method = .POST;
|
|
|
|
|
|
|
|
pub const Body = struct {
|
|
|
|
username: []const u8,
|
|
|
|
password: []const u8,
|
|
|
|
email: ?[]const u8 = null,
|
|
|
|
invite_code: ?[]const u8 = null,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
const user = srv.register(req.body.username, req.body.password, .{
|
|
|
|
.email = req.body.email,
|
|
|
|
.invite_code = req.body.invite_code,
|
|
|
|
}) catch |err| {
|
|
|
|
var status: http.Status = .bad_request;
|
|
|
|
const err_msg = switch (err) {
|
|
|
|
error.UsernameEmpty => "Username cannot be empty",
|
|
|
|
error.UsernameContainsInvalidChar => "Username must be composed of alphanumeric characters and underscore",
|
|
|
|
error.UsernameTooLong => "Username too long",
|
|
|
|
error.PasswordTooShort => "Password too short, must be at least 12 chars",
|
|
|
|
|
|
|
|
error.UsernameTaken => blk: {
|
|
|
|
status = .unprocessable_entity;
|
|
|
|
break :blk "Username is already registered";
|
|
|
|
},
|
|
|
|
else => blk: {
|
|
|
|
status = .internal_server_error;
|
|
|
|
break :blk "an internal error occurred";
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
return servePage(req.body.invite_code, err_msg, status, res, srv);
|
|
|
|
};
|
|
|
|
defer util.deepFree(srv.allocator, user);
|
|
|
|
|
|
|
|
const token = try srv.login(req.body.username, req.body.password);
|
|
|
|
|
|
|
|
try res.headers.put("Location", index.path);
|
|
|
|
var buf: [64]u8 = undefined;
|
|
|
|
const cookie_name = try std.fmt.bufPrint(&buf, "token.{s}", .{req.body.username});
|
|
|
|
try res.headers.setCookie(cookie_name, token.token, .{});
|
|
|
|
try res.headers.setCookie("active_account", req.body.username, .{ .HttpOnly = false });
|
|
|
|
|
|
|
|
try res.status(.see_other);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-11-18 12:07:58 +00:00
|
|
|
const global_timeline = struct {
|
|
|
|
pub const path = "/timelines/global";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
_ = req;
|
|
|
|
const timeline = try srv.globalTimeline(.{});
|
|
|
|
|
2022-12-09 12:31:23 +00:00
|
|
|
try res.template(.ok, srv, @embedFile("./web/timelines/global.tmpl.html"), .{
|
2022-11-18 12:07:58 +00:00
|
|
|
.notes = timeline.items,
|
|
|
|
.community = srv.community,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2022-11-19 11:13:05 +00:00
|
|
|
|
|
|
|
const cluster = struct {
|
|
|
|
const overview = struct {
|
|
|
|
pub const path = "/cluster/overview";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub fn handler(_: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
const meta = try srv.getClusterMeta();
|
2022-12-09 12:31:23 +00:00
|
|
|
try res.template(.ok, srv, @embedFile("./web/cluster/overview.tmpl.html"), .{
|
2022-11-19 11:13:05 +00:00
|
|
|
.community = srv.community,
|
|
|
|
.meta = meta,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
2022-12-06 10:53:41 +00:00
|
|
|
|
|
|
|
const media = struct {
|
|
|
|
pub const path = "/media/:id";
|
|
|
|
pub const method = .GET;
|
|
|
|
|
|
|
|
pub const Args = struct {
|
|
|
|
id: util.Uuid,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
|
|
|
const result = try srv.fileDereference(req.args.id);
|
|
|
|
defer util.deepFree(srv.allocator, result);
|
|
|
|
|
|
|
|
try res.headers.put("Content-Type", result.meta.content_type orelse "application/octet-stream");
|
|
|
|
var stream = try res.open(.ok);
|
|
|
|
defer stream.close();
|
|
|
|
|
|
|
|
try stream.writer().writeAll(result.data);
|
|
|
|
try stream.finish();
|
|
|
|
}
|
|
|
|
};
|