diff options
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/api/auth/first-login.zig | 14 | ||||
| -rw-r--r-- | src/routes/api/auth/login.zig | 25 | ||||
| -rw-r--r-- | src/routes/api/auth/root.zig | 4 | ||||
| -rw-r--r-- | src/routes/api/root.zig | 1 | ||||
| -rw-r--r-- | src/routes/context.zig | 14 | ||||
| -rw-r--r-- | src/routes/fallback.zig | 35 | ||||
| -rw-r--r-- | src/routes/handler-info.zig | 113 | ||||
| -rw-r--r-- | src/routes/login.zig | 17 | ||||
| -rw-r--r-- | src/routes/root.zig | 55 |
9 files changed, 198 insertions, 80 deletions
diff --git a/src/routes/api/auth/first-login.zig b/src/routes/api/auth/first-login.zig new file mode 100644 index 0000000..2fb8c02 --- /dev/null +++ b/src/routes/api/auth/first-login.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const Context = @import("../../context.zig"); + +pub const needs_auth = true; +pub const method = .POST; + +const Result = struct { + is_first: bool, +}; + +pub fn handler(ctx: *Context) anyerror!Result { + _ = ctx; + return .{ .is_first = false }; +} diff --git a/src/routes/api/auth/login.zig b/src/routes/api/auth/login.zig new file mode 100644 index 0000000..c3f2bef --- /dev/null +++ b/src/routes/api/auth/login.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +const Context = @import("../../context.zig"); +const Storage = @import("../../../storage/root.zig"); + +pub const needs_auth = false; +pub const method = .POST; + +const Body = struct { + user: []const u8, + password: []const u8, +}; + +const Result = struct { + success: bool, +}; + +pub fn handler(ctx: *Context, body: Body) anyerror!Result { + var user = Storage.User.open(ctx.storage, body.user, ctx.allocator) catch return .{ + .success = false + }; + defer user.deinit(); + + return .{ .success = user.check_password(body.password) }; +} diff --git a/src/routes/api/auth/root.zig b/src/routes/api/auth/root.zig new file mode 100644 index 0000000..785271e --- /dev/null +++ b/src/routes/api/auth/root.zig @@ -0,0 +1,4 @@ +const HandlerInfo = @import("../../handler-info.zig"); + +pub const login: HandlerInfo = .from_type(@import("login.zig")); +pub const first_login: HandlerInfo = .from_type(@import("first-login.zig")); diff --git a/src/routes/api/root.zig b/src/routes/api/root.zig new file mode 100644 index 0000000..19c4074 --- /dev/null +++ b/src/routes/api/root.zig @@ -0,0 +1 @@ +pub const auth = @import("auth/root.zig"); diff --git a/src/routes/context.zig b/src/routes/context.zig new file mode 100644 index 0000000..bc3e710 --- /dev/null +++ b/src/routes/context.zig @@ -0,0 +1,14 @@ +const std = @import("std"); +const Storage = @import("../storage/root.zig"); + +const Self = @This(); + +allocator: std.mem.Allocator, +request: *std.http.Server.Request, +storage: *Storage, +user: ?[]const u8 = null, +response: struct { + headers: struct { + content_type: []const u8 = "application/json", + } = .{}, +} = .{}, diff --git a/src/routes/fallback.zig b/src/routes/fallback.zig index 78d778b..25e1927 100644 --- a/src/routes/fallback.zig +++ b/src/routes/fallback.zig @@ -1,32 +1,37 @@ const std = @import("std"); -const http = @import("../http.zig"); +const mime = @import("../mime.zig"); +const Context = @import("context.zig"); const log = std.log.scoped(.fallback); -pub fn handler( - request: *std.http.Server.Request, - allocator: std.mem.Allocator, -) anyerror!void { +pub const needs_auth = false; +pub const method = .GET; + +pub fn handler(ctx: *Context) anyerror![]const u8 { var static = try std.fs.cwd().openDir("static", .{}); defer static.close(); - if (static.openFile(request.head.target[1..], .{})) |file| { + if (static.openFile(ctx.request.head.target[1..], .{})) |file| { defer file.close(); - try http.respond_file(request, file, request.head.target[1..], allocator); + const content = file.readToEndAlloc(ctx.allocator, std.math.maxInt(usize)); + const mime_type = mime.get_type(ctx.request.head.target); + ctx.response.headers.content_type = mime_type; + return content; } else |_| { - var subdir = if (request.head.target.len == 1) static - else (static.openDir(request.head.target[1..], .{}) catch { - try request.respond("Not Found", .{ .status = .not_found }); - return; + var subdir = if (ctx.request.head.target.len == 1) static + else (static.openDir(ctx.request.head.target[1..], .{}) catch { + return error.NotFound; }); - defer if (request.head.target.len > 1) subdir.close(); + defer if (ctx.request.head.target.len > 1) subdir.close(); if (subdir.openFile("index.html", .{})) |file| { defer file.close(); - try http.respond_file(request, file, "index.html", allocator); + const content = file.readToEndAlloc(ctx.allocator, std.math.maxInt(usize)); + ctx.response.headers.content_type = "text/html"; + return content; } else |_| { - log.warn("File '{s}' Not Found", .{ request.head.target }); - try request.respond("Not Found", .{ .status = .not_found }); + log.warn("File '{s}' Not Found", .{ ctx.request.head.target }); + return error.NotFound; } } } diff --git a/src/routes/handler-info.zig b/src/routes/handler-info.zig new file mode 100644 index 0000000..bac7612 --- /dev/null +++ b/src/routes/handler-info.zig @@ -0,0 +1,113 @@ +const std = @import("std"); +const Context = @import("context.zig"); +const Storage = @import("../storage/root.zig"); + +const log = std.log.scoped(.handler_info); + +handler: *const fn (*Context) anyerror![]const u8, +needs_auth: bool, +method: std.http.Method, + +pub fn handle( + self: *const @This(), + request: *std.http.Server.Request, + storage: *Storage, + allocator: std.mem.Allocator, +) !void { + if (request.head.method != self.method) { + return request.respond( + "{ \"error\": \"Bad Request\" }", + .{ .status = .bad_request } + ); + } + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + var context: Context = .{ + .request = request, + .storage = storage, + .allocator = arena.allocator(), + }; + + const response = self.handler(&context) catch |err| { + const response, const status_code: std.http.Status = switch (err) { + error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request }, + error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized }, + error.Forbidden => .{ "{ \"error\": \"Forbidden\" }", .forbidden }, + error.NotFound => .{ "{ \"error\": \"Not Found\" }", .not_found }, + else => blk: { + log.err("handler for '{s}' returned {}", .{request.head.target, err}); + break :blk .{ "{ \"error\": \"Internal Server Error\" }", .internal_server_error }; + }, + }; + + return request.respond(response, .{ .status = status_code }); + }; + + try request.respond(response, .{ + .extra_headers = &.{ + .{ + .name = "Content-Type", + .value = context.response.headers.content_type + }, + }, + }); +} + +pub fn from_type(T: type) @This() { + const info = @typeInfo(@TypeOf(T.handler)); + const return_type = info.@"fn".return_type orelse void; + const payload_type = @typeInfo(return_type).error_union.payload; + + const Handler = type: { + break :type struct { + pub fn call(ctx: *Context) anyerror![]const u8 { + const args = args: { + const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(T.handler))); + + if (tuple.len == 1) { + break :args .{ ctx }; + } else if (tuple.len == 2) { + const Body = tuple[1].@"type"; + + var writer = std.Io.Writer.Allocating.init(ctx.allocator); + const interface = &writer.writer; + + var buffer: [1024]u8 = undefined; + const reader = try ctx.request.readerExpectContinue(&buffer); + + try reader.streamExact64(interface, ctx.request.head.content_length orelse 0); + + const body = std.json.parseFromSliceLeaky( + Body, + ctx.allocator, + writer.written(), + .{} + ) catch return error.BadRequest; + break :args .{ ctx, body }; + } else { + @compileError("invalid amount of arguments for request function"); + } + }; + + if (payload_type == []const u8) { + return @call(.auto, T.handler, args); + } else if (payload_type == void) { + try @call(.auto, T.handler, args); + return ""; + } else { + var writer = std.Io.Writer.Allocating.init(ctx.allocator); + var stringify = std.json.Stringify { .writer = &writer.writer }; + try stringify.write(try @call(.auto, T.handler, args)); + return writer.written(); + } + } + }; + }; + + return @This() { + .handler = Handler.call, + .needs_auth = T.needs_auth, + .method = T.method, + }; +} diff --git a/src/routes/login.zig b/src/routes/login.zig deleted file mode 100644 index 29bf370..0000000 --- a/src/routes/login.zig +++ /dev/null @@ -1,17 +0,0 @@ -const std = @import("std"); - -pub fn handler( - request: *std.http.Server.Request, - allocator: std.mem.Allocator, -) anyerror!void { - var output = std.Io.Writer.Allocating.init(allocator); - var stringify = std.json.Stringify { .writer = &output.writer }; - - try stringify.write(.{ - .user = "nathan.reiner", - .name = "Nathan Reiner", - }); - - try output.writer.flush(); - try request.respond(output.written(), .{}); -} diff --git a/src/routes/root.zig b/src/routes/root.zig index 01952bb..dbfce32 100644 --- a/src/routes/root.zig +++ b/src/routes/root.zig @@ -1,56 +1,15 @@ const std = @import("std"); -pub const fallback = @import("fallback.zig"); -pub const login = @import("login.zig"); +pub const HandlerInfo = @import("handler-info.zig"); -pub const HandlerInfo = struct { - handler: *const fn ( - request: *std.http.Server.Request, - allocator: std.mem.Allocator, - ) anyerror!void, - needs_auth: bool, - method: std.http.Method, - - pub fn handle( - self: *const @This(), - request: *std.http.Server.Request, - allocator: std.mem.Allocator, - ) !void { - if (request.head.method != self.method) { - try request.respond("{ \"error\": \"Bad Request\" }", .{ .status = .bad_request }); - } - - self.handler(request, allocator) catch |err| { - const response, const status_code: std.http.Status = switch (err) { - error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request }, - error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized }, - error.Forbidden => .{ "{ \"error\": \"Forbidden\" }", .forbidden }, - error.NotFound => .{ "{ \"error\": \"Not Found\" }", .not_found }, - else => .{ "{ \"error\": \"Internal Server Error\" }", .internal_server_error }, - }; - - try request.respond(response, .{ .status = status_code }); - }; - } -}; +pub const Context = @import("context.zig"); +pub const api = @import("api/root.zig"); +pub const fallback: HandlerInfo = .from_type(@import("fallback.zig")); pub const handlers = std.StaticStringMap(HandlerInfo).initComptime(.{ - .{ - "", - HandlerInfo { - .handler = fallback.handler, - .needs_auth = false, - .method = .GET, - } - }, - .{ - "/api/login", - HandlerInfo { - .handler = login.handler, - .needs_auth = false, - .method = .POST, - } - }, + .{ "", fallback }, + .{ "/api/auth/login", api.auth.login }, + .{ "/api/auth/first-login", api.auth.first_login }, }); pub fn get(path: []const u8) HandlerInfo { |