Use new controller layout
This commit is contained in:
parent
228b9490ef
commit
add17c0a0a
|
@ -13,14 +13,12 @@ const services = struct {
|
|||
const notes = @import("./services/notes.zig");
|
||||
};
|
||||
|
||||
pub const RegistrationRequest = struct {
|
||||
username: []const u8,
|
||||
password: []const u8,
|
||||
invite_code: []const u8,
|
||||
pub const RegistrationOptions = struct {
|
||||
invite_code: ?[]const u8 = null,
|
||||
email: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const InviteRequest = struct {
|
||||
pub const InviteOptions = struct {
|
||||
pub const Kind = services.invites.Kind;
|
||||
|
||||
name: ?[]const u8 = null,
|
||||
|
@ -227,7 +225,7 @@ fn ApiConn(comptime DbConn: type) type {
|
|||
return community;
|
||||
}
|
||||
|
||||
pub fn createInvite(self: *Self, options: InviteRequest) !services.invites.Invite {
|
||||
pub fn createInvite(self: *Self, options: InviteOptions) !services.invites.Invite {
|
||||
// Only logged in users can make invites
|
||||
const user_id = self.user_id orelse return error.TokenRequired;
|
||||
|
||||
|
@ -251,26 +249,33 @@ fn ApiConn(comptime DbConn: type) type {
|
|||
return try services.invites.get(self.db, invite_id, self.arena.allocator());
|
||||
}
|
||||
|
||||
pub fn register(self: *Self, request: RegistrationRequest) !UserResponse {
|
||||
std.log.debug("registering user {s} with code {s}", .{ request.username, request.invite_code });
|
||||
const invite = try services.invites.getByCode(self.db, request.invite_code, self.community.id, self.arena.allocator());
|
||||
pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse {
|
||||
std.log.debug("registering user {s} with code {?s}", .{ username, opt.invite_code });
|
||||
const maybe_invite = if (opt.invite_code) |code|
|
||||
try services.invites.getByCode(self.db, code, self.community.id, self.arena.allocator())
|
||||
else
|
||||
null;
|
||||
|
||||
if (!Uuid.eql(invite.community_id, self.community.id)) return error.NotFound;
|
||||
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return error.InviteExpired;
|
||||
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return error.InviteExpired;
|
||||
if (maybe_invite) |invite| {
|
||||
if (!Uuid.eql(invite.community_id, self.community.id)) return error.WrongCommunity;
|
||||
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return error.InviteExpired;
|
||||
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return error.InviteExpired;
|
||||
}
|
||||
|
||||
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
|
||||
|
||||
if (self.community.kind == .admin) @panic("Unimplmented");
|
||||
|
||||
const user_id = try services.auth.register(
|
||||
self.db,
|
||||
request.username,
|
||||
request.password,
|
||||
username,
|
||||
password,
|
||||
self.community.id,
|
||||
.{ .invite_id = invite.id, .email = request.email },
|
||||
.{ .invite_id = if (maybe_invite) |inv| inv.id else null, .email = opt.email },
|
||||
self.arena.allocator(),
|
||||
);
|
||||
|
||||
switch (invite.kind) {
|
||||
switch (invite_kind) {
|
||||
.user => {},
|
||||
.system => @panic("System user invites unimplemented"),
|
||||
.community_owner => {
|
||||
|
|
|
@ -13,40 +13,26 @@ pub const invites = @import("./controllers/invites.zig");
|
|||
pub const users = @import("./controllers/users.zig");
|
||||
pub const notes = @import("./controllers/notes.zig");
|
||||
|
||||
pub fn routeRequest(api_source: anytype, request: http.Request, response: http.Response, alloc: std.mem.Allocator) void {
|
||||
pub fn routeRequest(api_source: anytype, ctx: http.server.Context, alloc: std.mem.Allocator) void {
|
||||
// TODO: hashmaps?
|
||||
inline for (routes) |route| {
|
||||
if (Context(route).matchAndHandle(api_source, request, response, alloc)) return;
|
||||
if (Context(route).matchAndHandle(api_source, ctx, alloc)) return;
|
||||
}
|
||||
|
||||
// todo 404
|
||||
var response = Response{ .headers = http.Headers.init(alloc), .ctx = ctx };
|
||||
defer response.headers.deinit();
|
||||
|
||||
response.status(.not_found) catch {};
|
||||
}
|
||||
|
||||
const routes = .{ sample_api, invites.create };
|
||||
|
||||
pub const sample_api = struct {
|
||||
const Self = @This();
|
||||
|
||||
pub const method = .POST;
|
||||
pub const path = "/notes/:id/reacts";
|
||||
pub const content_type = "application/json";
|
||||
|
||||
pub const Args = struct {
|
||||
id: []const u8,
|
||||
};
|
||||
|
||||
pub const Body = struct {
|
||||
content: util.Uuid,
|
||||
};
|
||||
|
||||
pub const Query = struct {
|
||||
arg: []const u8 = "",
|
||||
};
|
||||
|
||||
pub fn handler(ctx: Context(Self), response: *Response, _: api.ApiSource.Conn) !void {
|
||||
std.log.debug("{}", .{ctx.body.content});
|
||||
try response.writeJson(.created, ctx.query);
|
||||
}
|
||||
const routes = .{
|
||||
auth.login,
|
||||
auth.verify_login,
|
||||
communities.create,
|
||||
invites.create,
|
||||
users.create,
|
||||
notes.create,
|
||||
//notes.get,
|
||||
};
|
||||
|
||||
pub fn Context(comptime Route: type) type {
|
||||
|
@ -74,7 +60,7 @@ pub fn Context(comptime Route: type) type {
|
|||
query: Query,
|
||||
|
||||
fn parseArgs(path: []const u8) ?Args {
|
||||
var args: Route.Args = undefined;
|
||||
var args: Args = undefined;
|
||||
var path_iter = util.PathIter.from(path);
|
||||
comptime var route_iter = util.PathIter.from(Route.path);
|
||||
inline while (comptime route_iter.next()) |route_segment| {
|
||||
|
@ -93,7 +79,7 @@ pub fn Context(comptime Route: type) type {
|
|||
const req = ctx.request;
|
||||
if (req.method != Route.method) return false;
|
||||
var path = std.mem.sliceTo(std.mem.sliceTo(req.path, '#'), '?');
|
||||
var args: Route.Args = parseArgs(path) orelse return false;
|
||||
var args: Args = parseArgs(path) orelse return false;
|
||||
|
||||
var response = Response{ .headers = http.Headers.init(alloc), .ctx = ctx };
|
||||
defer response.headers.deinit();
|
||||
|
@ -116,7 +102,7 @@ pub fn Context(comptime Route: type) type {
|
|||
}
|
||||
|
||||
fn errorHandler(response: *Response, status: http.Status) void {
|
||||
response.writeStatus(status) catch unreachable;
|
||||
response.status(status) catch unreachable;
|
||||
}
|
||||
|
||||
fn prepareAndHandle(self: *Self, api_source: anytype, req: http.Request, response: *Response) void {
|
||||
|
@ -128,7 +114,7 @@ pub fn Context(comptime Route: type) type {
|
|||
var api_conn = self.getApiConn(api_source) catch return errorHandler(response, .internal_server_error); // TODO
|
||||
defer api_conn.close();
|
||||
|
||||
self.handle(response, api_conn);
|
||||
self.handle(response, &api_conn);
|
||||
}
|
||||
|
||||
fn parseBody(self: *Self, req: http.Request) !void {
|
||||
|
@ -179,16 +165,16 @@ pub const Response = struct {
|
|||
headers: http.Headers,
|
||||
ctx: http.server.Context,
|
||||
|
||||
pub fn writeStatus(self: *Self, status: http.Status) !void {
|
||||
var stream = try self.ctx.openResponse(&self.headers, status);
|
||||
pub fn status(self: *Self, status_code: http.Status) !void {
|
||||
var stream = try self.ctx.openResponse(&self.headers, status_code);
|
||||
defer stream.close();
|
||||
try stream.finish();
|
||||
}
|
||||
|
||||
pub fn writeJson(self: *Self, status: http.Status, response_body: anytype) !void {
|
||||
pub fn json(self: *Self, status_code: http.Status, response_body: anytype) !void {
|
||||
try self.headers.put("Content-Type", "application/json");
|
||||
|
||||
var stream = try self.ctx.openResponse(&self.headers, status);
|
||||
var stream = try self.ctx.openResponse(&self.headers, status_code);
|
||||
defer stream.close();
|
||||
|
||||
const writer = stream.writer();
|
||||
|
@ -196,6 +182,13 @@ pub const Response = struct {
|
|||
|
||||
try stream.finish();
|
||||
}
|
||||
|
||||
pub fn err(self: *Self, status_code: http.Status, message: []const u8, details: anytype) !void {
|
||||
return self.json(status_code, .{
|
||||
.message = message,
|
||||
.details = details,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const json_options = if (builtin.mode == .Debug)
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
const root = @import("root");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
|
||||
pub fn get(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
const id_str = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(id_str) catch return utils.respondError(ctx, .bad_request, "Invalid UUID");
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const user = (try api.getActor(id)) orelse return utils.respondError(ctx, .not_found, "Note not found");
|
||||
|
||||
try utils.respondJson(ctx, .ok, user);
|
||||
}
|
|
@ -1,42 +1,28 @@
|
|||
const std = @import("std");
|
||||
const root = @import("root");
|
||||
const builtin = @import("builtin");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
const api = @import("api");
|
||||
|
||||
pub const login = struct {
|
||||
pub const path = "/auth/login";
|
||||
pub const method = .POST;
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const credentials = try utils.parseRequestBody(struct { username: []const u8, password: []const u8 }, ctx);
|
||||
defer utils.freeRequestBody(credentials, ctx.alloc);
|
||||
pub const path = "/auth/login";
|
||||
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
pub const Body = struct {
|
||||
username: []const u8,
|
||||
password: []const u8,
|
||||
};
|
||||
|
||||
const token = try api.login(credentials.username, credentials.password);
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
const token = try srv.login(req.body.username, req.body.password);
|
||||
|
||||
try utils.respondJson(ctx, .ok, token);
|
||||
try res.json(.ok, token);
|
||||
}
|
||||
};
|
||||
|
||||
pub const verify_login = struct {
|
||||
pub const path = "/auth/login";
|
||||
pub const method = .GET;
|
||||
pub const path = "/auth/login";
|
||||
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
pub fn handler(_: anytype, res: anytype, srv: anytype) !void {
|
||||
const info = try srv.verifyAuthorization();
|
||||
|
||||
// The self-hosted compiler doesn't like inferring this error set.
|
||||
// do this for now
|
||||
const info = try api.verifyAuthorization();
|
||||
|
||||
try utils.respondJson(ctx, .ok, info);
|
||||
try res.json(.ok, info);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,33 +1,16 @@
|
|||
const root = @import("root");
|
||||
const http = @import("http");
|
||||
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
const api = @import("api");
|
||||
|
||||
pub const create = struct {
|
||||
pub const method = .POST;
|
||||
pub const path = "/communities";
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const opt = try utils.parseRequestBody(struct { origin: []const u8 }, ctx);
|
||||
defer utils.freeRequestBody(opt, ctx.alloc);
|
||||
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
pub const Body = struct {
|
||||
origin: []const u8,
|
||||
};
|
||||
|
||||
const invite = try api.createCommunity(opt.origin);
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
const invite = try srv.createCommunity(req.body.origin);
|
||||
|
||||
try utils.respondJson(ctx, .created, invite);
|
||||
try res.json(.created, invite);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn get(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
const host = args.get("host") orelse return error.NotFound;
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const invite = (try api.getCommunity(host)) orelse return utils.respondError(ctx, .not_found, "Community not found");
|
||||
|
||||
try utils.respondJson(ctx, .ok, invite);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
const api = @import("api");
|
||||
|
||||
pub const create = struct {
|
||||
pub const path = "/invites";
|
||||
pub const method = .POST;
|
||||
pub const Body = api.InviteRequest;
|
||||
pub const path = "/invites";
|
||||
|
||||
pub fn handler(req: anytype, res: anytype, srv: api.ApiSource.Conn) !void {
|
||||
pub const Body = api.InviteOptions;
|
||||
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
// No need to free because it will be freed when the api conn
|
||||
// is closed
|
||||
const invite = srv.createInvite(req.body);
|
||||
const invite = try srv.createInvite(req.body);
|
||||
|
||||
try res.writeJson(.created, invite);
|
||||
try res.json(.created, invite);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,40 +1,32 @@
|
|||
const root = @import("root");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
const NoteCreateInfo = @import("api").NoteCreateInfo;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
const api = @import("api");
|
||||
const util = @import("util");
|
||||
|
||||
pub const create = struct {
|
||||
pub const method = .POST;
|
||||
pub const path = "/notes";
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const info = try utils.parseRequestBody(struct { content: []const u8 }, ctx);
|
||||
defer utils.freeRequestBody(info, ctx.alloc);
|
||||
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
pub const Body = struct {
|
||||
content: []const u8,
|
||||
};
|
||||
|
||||
const note = try api.createNote(info.content);
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
const note = try srv.createNote(req.body.content);
|
||||
|
||||
try utils.respondJson(ctx, .created, note);
|
||||
try res.json(.created, note);
|
||||
}
|
||||
};
|
||||
|
||||
pub const get = struct {
|
||||
pub const method = .GET;
|
||||
pub const path = "/notes/:id";
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
const id_str = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(id_str) catch return utils.respondError(ctx, .bad_request, "Invalid UUID");
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const note = try api.getNote(id);
|
||||
pub const Args = struct {
|
||||
id: util.Uuid,
|
||||
};
|
||||
|
||||
try utils.respondJson(ctx, .ok, note);
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
const note = try srv.getNote(req.args.id);
|
||||
|
||||
try res.json(.ok, note);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
const root = @import("root");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const utils = @import("../../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
|
||||
pub fn create(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const note_id = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(note_id) catch return utils.respondError(ctx, .bad_request, "Invalid UUID");
|
||||
|
||||
try api.react(id);
|
||||
try utils.respondJson(ctx, .created, .{});
|
||||
}
|
||||
|
||||
pub fn list(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void {
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const note_id = args.get("id") orelse return error.NotFound;
|
||||
const id = Uuid.parse(note_id) catch return utils.respondError(ctx, .bad_request, "Invalid UUID");
|
||||
|
||||
const reacts = try api.listReacts(id);
|
||||
try utils.respondJson(ctx, .ok, .{ .items = reacts });
|
||||
}
|
|
@ -1,29 +1,26 @@
|
|||
const std = @import("std");
|
||||
const root = @import("root");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const RegistrationRequest = @import("api").RegistrationRequest;
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
const api = @import("api");
|
||||
|
||||
pub const create = struct {
|
||||
pub const method = .POST;
|
||||
pub const path = "/users";
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
const info = try utils.parseRequestBody(RegistrationRequest, ctx);
|
||||
defer utils.freeRequestBody(info, ctx.alloc);
|
||||
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
pub const Body = struct {
|
||||
username: []const u8,
|
||||
password: []const u8,
|
||||
invite_code: ?[]const u8 = null,
|
||||
email: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const user = api.register(info) catch |err| switch (err) {
|
||||
error.UsernameTaken => return utils.respondError(ctx, .bad_request, "Username Unavailable"),
|
||||
pub fn handler(req: anytype, res: anytype, srv: anytype) !void {
|
||||
const options = .{
|
||||
.invite_code = req.body.invite_code,
|
||||
.email = req.body.email,
|
||||
};
|
||||
const user = srv.register(req.body.username, req.body.password, options) catch |err| switch (err) {
|
||||
error.UsernameTaken => return res.err(.unprocessable_entity, "Username Unavailable", {}),
|
||||
else => return err,
|
||||
};
|
||||
|
||||
try utils.respondJson(ctx, .created, user);
|
||||
try res.json(.created, user);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,16 +26,13 @@ pub const RequestServer = struct {
|
|||
defer srv.shutdown();
|
||||
|
||||
while (true) {
|
||||
const buf = try self.alloc.alloc(u8, 1 << 28); // 4mb
|
||||
defer self.alloc.free(buf);
|
||||
var fba = std.heap.FixedBufferAllocator.init(buf);
|
||||
const alloc = fba.allocator();
|
||||
var arena = std.heap.ArenaAllocator.init(self.alloc);
|
||||
defer arena.deinit();
|
||||
|
||||
var ctx = try srv.accept(alloc);
|
||||
var ctx = try srv.accept(arena.allocator());
|
||||
defer ctx.close();
|
||||
|
||||
_ = c.Context(c.sample_api).matchAndHandle(self.api, ctx, self.alloc);
|
||||
if (true) continue;
|
||||
c.routeRequest(self.api, ctx, arena.allocator());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue