const std = @import("std"); const builtin = @import("builtin"); const sql = @import("sql"); const http = @import("http"); const util = @import("util"); const api = @import("api"); pub const migrations = @import("./migrations.zig"); // TODO const c = @import("./controllers.zig"); pub const RequestServer = struct { alloc: std.mem.Allocator, api: *api.ApiSource, config: Config, fn init(alloc: std.mem.Allocator, src: *api.ApiSource, config: Config) !RequestServer { return RequestServer{ .alloc = alloc, .api = src, .config = config, }; } fn listenAndRun(self: *RequestServer, addr: std.net.Address) !void { var srv = http.Server.listen(addr) catch unreachable; defer srv.shutdown(); while (true) { var arena = std.heap.ArenaAllocator.init(self.alloc); defer arena.deinit(); var ctx = try srv.accept(arena.allocator()); defer ctx.close(); c.routeRequest(self.api, ctx, arena.allocator()); } } }; pub const Config = struct { db: sql.Config, }; fn loadConfig(alloc: std.mem.Allocator) !Config { var file = try std.fs.cwd().openFile("config.json", .{}); defer file.close(); const bytes = try file.reader().readAllAlloc(alloc, 1 << 63); defer alloc.free(bytes); var ts = std.json.TokenStream.init(bytes); return std.json.parse(Config, &ts, .{ .allocator = alloc }); } const admin_origin_envvar = "CLUSTER_ADMIN_ORIGIN"; const admin_username_envvar = "CLUSTER_ADMIN_USERNAME"; const admin_password_envvar = "CLUSTER_ADMIN_PASSWORD"; fn runAdminSetup(db: sql.Db, alloc: std.mem.Allocator) !void { const origin = std.os.getenv(admin_origin_envvar) orelse return error.MissingArgument; const username = std.os.getenv(admin_username_envvar) orelse return error.MissingArgument; const password = std.os.getenv(admin_password_envvar) orelse return error.MissingArgument; try api.setupAdmin(db, origin, username, password, alloc); } fn prepareDb(pool: *sql.ConnPool, alloc: std.mem.Allocator) !void { const db = try pool.acquire(); defer db.releaseConnection(); try migrations.up(db); if (!try api.isAdminSetup(db)) { std.log.info("Performing first-time admin creation...", .{}); runAdminSetup(db, alloc) catch |err| switch (err) { error.MissingArgument => { std.log.err( \\First time setup required but arguments not provided. \\Please provide the following arguments via environment variable: \\- {s}: The origin to serve the cluster admin panel at (ex: https://admin.example.com) \\- {s}: The username for the initial cluster operator \\- {s}: The password for the initial cluster operator , .{ admin_origin_envvar, admin_username_envvar, admin_password_envvar }, ); std.os.exit(1); }, else => return err, }; } } pub fn main() !void { try util.seedThreadPrng(); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var cfg = try loadConfig(gpa.allocator()); var pool = try sql.ConnPool.init(cfg.db); try prepareDb(&pool, gpa.allocator()); var api_src = try api.ApiSource.init(&pool); var srv = try RequestServer.init(gpa.allocator(), &api_src, cfg); return srv.listenAndRun(std.net.Address.parseIp("0.0.0.0", 8080) catch unreachable); }