diff --git a/src/main/controllers.zig b/src/main/controllers.zig index 5d34b3f..fb125a5 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -6,7 +6,11 @@ const api = @import("./api.zig"); const models = @import("./models.zig"); const Uuid = @import("util").Uuid; -const utils = struct { +pub const auth = @import("./controllers/auth.zig"); +pub const notes = @import("./controllers/notes.zig"); +pub const actors = @import("./controllers/actors.zig"); + +pub const utils = struct { const json_options = if (builtin.mode == .Debug) .{ .whitespace = .{ .indent = .{ .Space = 2 }, @@ -20,7 +24,7 @@ const utils = struct { }; // Responds to a request with a json value - fn respondJson(ctx: *http.server.Context, status: http.Status, value: anytype) !void { + pub fn respondJson(ctx: *http.server.Context, status: http.Status, value: anytype) !void { var headers = http.Headers.init(ctx.alloc); defer headers.deinit(); @@ -36,11 +40,11 @@ const utils = struct { try stream.finish(); } - fn respondError(ctx: *http.server.Context, status: http.Status, err: []const u8) !void { + pub fn respondError(ctx: *http.server.Context, status: http.Status, err: []const u8) !void { return respondJson(ctx, status, .{ .@"error" = err }); } - fn parseRequestBody(comptime T: type, ctx: *http.server.Context) !T { + pub fn parseRequestBody(comptime T: type, ctx: *http.server.Context) !T { const body = ctx.request.body orelse return error.BodyRequired; var tokens = std.json.TokenStream.init(body); const parsed = try std.json.parse(T, &tokens, .{ .allocator = ctx.alloc }); @@ -48,11 +52,11 @@ const utils = struct { return parsed; } - fn freeRequestBody(value: anytype, alloc: std.mem.Allocator) void { + pub fn freeRequestBody(value: anytype, alloc: std.mem.Allocator) void { std.json.parseFree(@TypeOf(value), value, .{ .allocator = alloc }); } - fn getApiContext(srv: *RequestServer, ctx: *http.server.Context) !api.ApiContext { + pub fn getApiContext(srv: *RequestServer, ctx: *http.server.Context) !api.ApiContext { const header = ctx.request.headers.get("authorization") orelse "(null)"; const token = header[("bearer ").len..]; @@ -65,89 +69,6 @@ const utils = struct { const RequestServer = root.RequestServer; const RouteArgs = http.RouteArgs; -pub fn createNote(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { - const user_context = try utils.getApiContext(srv, ctx); - // TODO: defer free ApiContext - const info = try utils.parseRequestBody(api.NoteCreate, ctx); - defer utils.freeRequestBody(info, ctx.alloc); - - const note = try srv.api.createNoteUser(info, user_context); - - try utils.respondJson(ctx, .created, note); -} - -pub fn register(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { - const info = try utils.parseRequestBody(api.RegistrationInfo, ctx); - defer utils.freeRequestBody(info, ctx.alloc); - - const user = srv.api.register(info) catch |err| switch (err) { - error.UsernameUnavailable => return try utils.respondError(ctx, .bad_request, "Username Unavailable"), - else => return err, - }; - - try utils.respondJson(ctx, .created, user); -} - -pub fn getNote(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"); - const note = (try srv.api.getNote(id, ctx.alloc)) orelse return utils.respondError(ctx, .not_found, "Note not found"); - defer api.free(ctx.alloc, note); - - try utils.respondJson(ctx, .ok, note); -} - -pub fn getUser(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"); - const user = (try srv.api.getActor(id, ctx.alloc)) orelse return utils.respondError(ctx, .not_found, "Note not found"); - defer api.free(ctx.alloc, user); - - try utils.respondJson(ctx, .ok, user); -} - -pub fn react(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void { - const user_context = try utils.getApiContext(srv, ctx); - // TODO: defer free ApiContext - - 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 srv.api.react(id, user_context); - try utils.respondJson(ctx, .created, .{}); -} - -pub fn listReacts(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void { - const user_context = try utils.getApiContext(srv, ctx); - // TODO: defer free ApiContext - - 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 srv.api.listReacts(id, user_context); - try utils.respondJson(ctx, .ok, .{ .items = reacts }); -} - -pub fn authenticate(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { - const user_ctx = try utils.getApiContext(srv, ctx); - // TODO: defer api.free(ctx.alloc, user_ctx); - - try utils.respondJson(ctx, .ok, user_ctx.user_context); -} - -pub fn login(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); - - const actor = srv.api.login(credentials.username, credentials.password, ctx.alloc) catch |err| switch (err) { - error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"), - else => return err, - }; - defer api.free(ctx.alloc, actor); - - try utils.respondJson(ctx, .ok, actor); -} - pub fn healthcheck(_: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { try utils.respondJson(ctx, .ok, .{ .status = "ok" }); } diff --git a/src/main/controllers/actors.zig b/src/main/controllers/actors.zig new file mode 100644 index 0000000..6bfa292 --- /dev/null +++ b/src/main/controllers/actors.zig @@ -0,0 +1,18 @@ +const root = @import("root"); +const http = @import("http"); +const api = @import("../api.zig"); +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"); + const user = (try srv.api.getActor(id, ctx.alloc)) orelse return utils.respondError(ctx, .not_found, "Note not found"); + defer api.free(ctx.alloc, user); + + try utils.respondJson(ctx, .ok, user); +} diff --git a/src/main/controllers/auth.zig b/src/main/controllers/auth.zig new file mode 100644 index 0000000..7ca567d --- /dev/null +++ b/src/main/controllers/auth.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const root = @import("root"); +const builtin = @import("builtin"); +const http = @import("http"); +const api = @import("../api.zig"); +const models = @import("../models.zig"); +const Uuid = @import("util").Uuid; + +const utils = @import("../controllers.zig").utils; + +const RequestServer = root.RequestServer; +const RouteArgs = http.RouteArgs; + +pub fn register(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { + const info = try utils.parseRequestBody(api.RegistrationInfo, ctx); + defer utils.freeRequestBody(info, ctx.alloc); + + const user = srv.api.register(info) catch |err| switch (err) { + error.UsernameUnavailable => return try utils.respondError(ctx, .bad_request, "Username Unavailable"), + else => return err, + }; + + try utils.respondJson(ctx, .created, user); +} + +pub fn authenticate(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { + const user_ctx = try utils.getApiContext(srv, ctx); + // TODO: defer api.free(ctx.alloc, user_ctx); + + try utils.respondJson(ctx, .ok, user_ctx.user_context); +} + +pub fn login(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); + + const actor = srv.api.login(credentials.username, credentials.password, ctx.alloc) catch |err| switch (err) { + error.PasswordVerificationFailed => return utils.respondError(ctx, .bad_request, "Invalid Login"), + else => return err, + }; + defer api.free(ctx.alloc, actor); + + try utils.respondJson(ctx, .ok, actor); +} diff --git a/src/main/controllers/notes.zig b/src/main/controllers/notes.zig new file mode 100644 index 0000000..f83eab0 --- /dev/null +++ b/src/main/controllers/notes.zig @@ -0,0 +1,32 @@ +const root = @import("root"); +const http = @import("http"); +const api = @import("../api.zig"); +const models = @import("../models.zig"); +const Uuid = @import("util").Uuid; + +const utils = @import("../controllers.zig").utils; + +const RequestServer = root.RequestServer; +const RouteArgs = http.RouteArgs; + +pub const reacts = @import("./notes/reacts.zig"); + +pub fn create(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void { + const user_context = try utils.getApiContext(srv, ctx); + // TODO: defer free ApiContext + const info = try utils.parseRequestBody(api.NoteCreate, ctx); + defer utils.freeRequestBody(info, ctx.alloc); + + const note = try srv.api.createNoteUser(info, user_context); + + try utils.respondJson(ctx, .created, note); +} + +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"); + const note = (try srv.api.getNote(id, ctx.alloc)) orelse return utils.respondError(ctx, .not_found, "Note not found"); + defer api.free(ctx.alloc, note); + + try utils.respondJson(ctx, .ok, note); +} diff --git a/src/main/controllers/notes/reacts.zig b/src/main/controllers/notes/reacts.zig new file mode 100644 index 0000000..25f70f6 --- /dev/null +++ b/src/main/controllers/notes/reacts.zig @@ -0,0 +1,32 @@ +const root = @import("root"); +const http = @import("http"); +const api = @import("../../api.zig"); +const models = @import("../../models.zig"); +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 { + const user_context = try utils.getApiContext(srv, ctx); + // TODO: defer free ApiContext + + 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 srv.api.react(id, user_context); + try utils.respondJson(ctx, .created, .{}); +} + +pub fn list(srv: *RequestServer, ctx: *http.server.Context, args: RouteArgs) !void { + const user_context = try utils.getApiContext(srv, ctx); + // TODO: defer free ApiContext + + 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 srv.api.listReacts(id, user_context); + try utils.respondJson(ctx, .ok, .{ .items = reacts }); +} diff --git a/src/main/main.zig b/src/main/main.zig index 4cbb343..2b5d7ae 100644 --- a/src/main/main.zig +++ b/src/main/main.zig @@ -16,16 +16,17 @@ const router = Router{ .routes = &[_]Route{ Route.new(.GET, "/healthcheck", c.healthcheck), - Route.new(.GET, "/auth/authenticate", c.authenticate), - Route.new(.POST, "/auth/login", c.login), + Route.new(.POST, "/auth/register", c.auth.register), + Route.new(.POST, "/auth/login", c.auth.login), + Route.new(.GET, "/auth/authenticate", c.auth.authenticate), - Route.new(.POST, "/notes", c.createNote), - Route.new(.GET, "/notes/:id", c.getNote), - Route.new(.GET, "/notes/:id/reacts", c.listReacts), - Route.new(.POST, "/notes/:id/reacts", c.react), + Route.new(.POST, "/notes", c.notes.create), + Route.new(.GET, "/notes/:id", c.notes.get), - Route.new(.POST, "/auth/register", c.register), - Route.new(.GET, "/users/:id", c.getUser), + Route.new(.GET, "/notes/:id/reacts", c.notes.reacts.list), + Route.new(.POST, "/notes/:id/reacts", c.notes.reacts.create), + + Route.new(.GET, "/actors/:id", c.actors.get), }, };