mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-07-01 05:26:07 +00:00
Merge branch 'main' into websockets
This commit is contained in:
commit
69fe4e0bb1
17
build.zig
17
build.zig
@ -7,7 +7,10 @@ const zmpl_build = @import("zmpl");
|
||||
const Environment = enum { development, testing, production };
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const use_llvm_default = builtin.os.tag != .linux;
|
||||
const use_llvm_default = switch (builtin.cpu.arch) {
|
||||
.x86, .x86_64 => builtin.os.tag != .linux,
|
||||
else => true,
|
||||
};
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
@ -73,6 +76,7 @@ pub fn build(b: *std.Build) !void {
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.jetquery_migrations_path = @as([]const u8, "src/app/database/migrations"),
|
||||
.jetquery_seeders_path = @as([]const u8, "src/app/database/seeders"),
|
||||
.jetquery_config_path = @as([]const u8, "config/database.zig"),
|
||||
});
|
||||
const jetcommon_dep = b.dependency("jetcommon", .{ .target = target, .optimize = optimize });
|
||||
@ -87,6 +91,10 @@ pub fn build(b: *std.Build) !void {
|
||||
b.modules.put("jetquery", jetquery_dep.module("jetquery")) catch @panic("Out of memory");
|
||||
b.modules.put("jetcommon", jetcommon_dep.module("jetcommon")) catch @panic("Out of memory");
|
||||
b.modules.put("jetquery_migrate", jetquery_dep.module("jetquery_migrate")) catch @panic("Out of memory");
|
||||
b.modules.put("jetquery_seeder", jetquery_dep.module("jetquery_seeder")) catch @panic("Out of memory");
|
||||
|
||||
jetquery_dep.module("jetquery_seeder").addImport("jetzig", jetzig_module);
|
||||
jetquery_dep.module("jetquery_migrate").addImport("jetzig", jetzig_module);
|
||||
|
||||
const smtp_client_dep = b.dependency("smtp_client", .{
|
||||
.target = target,
|
||||
@ -185,6 +193,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.jetquery_migrations_path = @as([]const u8, "src/app/database/migrations"),
|
||||
.jetquery_seeders_path = @as([]const u8, "src/app/database/seeders"),
|
||||
.jetquery_config_path = @as([]const u8, "config/database.zig"),
|
||||
});
|
||||
|
||||
@ -194,8 +203,12 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
||||
const jetquery_module = jetzig_dep.module("jetquery");
|
||||
const jetcommon_module = jetzig_dep.module("jetcommon");
|
||||
const jetquery_migrate_module = jetzig_dep.module("jetquery_migrate");
|
||||
const jetquery_seeder_module = jetzig_dep.module("jetquery_seeder");
|
||||
const jetquery_reflect_module = jetquery_dep.module("jetquery_reflect");
|
||||
|
||||
jetquery_dep.module("jetquery_seeders").addImport("jetzig", jetzig_module);
|
||||
jetquery_dep.module("jetquery_migrations").addImport("jetzig", jetzig_module);
|
||||
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption(Environment, "environment", environment);
|
||||
build_options.addOption(bool, "build_static", build_static);
|
||||
@ -414,6 +427,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
||||
exe_database.root_module.addImport("jetzig", jetzig_module);
|
||||
exe_database.root_module.addImport("jetcommon", jetcommon_module);
|
||||
exe_database.root_module.addImport("jetquery_migrate", jetquery_migrate_module);
|
||||
exe_database.root_module.addImport("jetquery_seeder", jetquery_seeder_module);
|
||||
exe_database.root_module.addImport("jetquery_reflect", jetquery_reflect_module);
|
||||
exe_database.root_module.addImport("Schema", schema_module);
|
||||
|
||||
@ -432,6 +446,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
||||
fn registerDatabaseSteps(b: *std.Build, exe_database: *std.Build.Step.Compile) void {
|
||||
const commands = .{
|
||||
.{ "migrate", "Migrate your Jetzig app's database." },
|
||||
.{ "seed", "Run seeds and set up initial data." },
|
||||
.{ "rollback", "Roll back a migration in your Jetzig app's database." },
|
||||
.{ "create", "Create a database for your Jetzig app." },
|
||||
.{ "drop", "Drop your Jetzig app's database." },
|
||||
|
@ -25,17 +25,16 @@
|
||||
.hash = "jetkv-0.0.0-zCv0fmCGAgCyYqwHjk0P5KrYVRew1MJAtbtAcIO-WPpT",
|
||||
},
|
||||
.zmpl = .{
|
||||
// .url = "https://github.com/jetzig-framework/zmpl/archive/89ee0ce9b4c96c316cc0575266fb66c864f24a49.tar.gz",
|
||||
// .hash = "zmpl-0.0.1-SYFGBtuNAwCj2YbqnoEJt3bk1iFIZjGK6JwMc72toZBR",
|
||||
.path = "../zmpl",
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/febec2dd477adadf09c67676ac4bf2079046b1d6.tar.gz",
|
||||
.hash = "zmpl-0.0.1-SYFGBhilAwAoY1evzcCHqpNFZf1zuB6IhY0P2w-bgM3t",
|
||||
},
|
||||
.httpz = .{
|
||||
.url = "https://github.com/karlseguin/http.zig/archive/37d7cb9819b804ade5f4b974b82f8dd0622225ed.tar.gz",
|
||||
.hash = "httpz-0.0.0-PNVzrEK4BgBpHQGA2m0RPqPGEjnTdDXHodBwzjYDrmps",
|
||||
},
|
||||
.jetquery = .{
|
||||
.url = "https://github.com/jetzig-framework/jetquery/archive/e1f969f2e3e0e1ad9cc30d56fde9739aa692fdc3.tar.gz",
|
||||
.hash = "jetquery-0.0.0-TNf3zo2ABgBgcsIAvJ1Ud2B2zDzrBy9GQ31kKmTYZ7Ya",
|
||||
.url = "https://github.com/jetzig-framework/jetquery/archive/907acae15dd36834dbdab06b17c3ba8f576d77cb.tar.gz",
|
||||
.hash = "jetquery-0.0.0-TNf3zm-5BgDOtCpRuVLEZQMWjkgKWRe1pNlGhrdoyvYE",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -18,6 +18,7 @@ pub fn build(b: *std.Build) !void {
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.jetquery_migrations_path = @as([]const u8, "src/app/database/migrations"),
|
||||
.jetquery_seeders_path = @as([]const u8, "src/app/database/seeders"),
|
||||
});
|
||||
exe.root_module.addImport("jetquery", jetquery_dep.module("jetquery"));
|
||||
exe.root_module.addImport("args", zig_args_dep.module("args"));
|
||||
|
@ -10,8 +10,8 @@
|
||||
.hash = "zig_args-0.0.0-jqtN6P_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH",
|
||||
},
|
||||
.jetquery = .{
|
||||
.url = "https://github.com/jetzig-framework/jetquery/archive/e1f969f2e3e0e1ad9cc30d56fde9739aa692fdc3.tar.gz",
|
||||
.hash = "jetquery-0.0.0-TNf3zo2ABgBgcsIAvJ1Ud2B2zDzrBy9GQ31kKmTYZ7Ya",
|
||||
.url = "https://github.com/jetzig-framework/jetquery/archive/907acae15dd36834dbdab06b17c3ba8f576d77cb.tar.gz",
|
||||
.hash = "jetquery-0.0.0-TNf3zm-5BgDOtCpRuVLEZQMWjkgKWRe1pNlGhrdoyvYE",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
|
@ -5,6 +5,7 @@ const args = @import("args");
|
||||
const util = @import("../util.zig");
|
||||
const cli = @import("../cli.zig");
|
||||
const migrate = @import("database/migrate.zig");
|
||||
const seed = @import("database/seed.zig");
|
||||
const rollback = @import("database/rollback.zig");
|
||||
const create = @import("database/create.zig");
|
||||
const drop = @import("database/drop.zig");
|
||||
@ -41,9 +42,19 @@ pub fn run(
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const Action = enum { migrate, rollback, create, drop, reflect, update, setup };
|
||||
const Action = enum {
|
||||
migrate,
|
||||
seed,
|
||||
rollback,
|
||||
create,
|
||||
drop,
|
||||
reflect,
|
||||
update,
|
||||
setup,
|
||||
};
|
||||
const map = std.StaticStringMap(Action).initComptime(.{
|
||||
.{ "migrate", .migrate },
|
||||
.{ "seed", .seed },
|
||||
.{ "rollback", .rollback },
|
||||
.{ "create", .create },
|
||||
.{ "drop", .drop },
|
||||
@ -74,6 +85,7 @@ pub fn run(
|
||||
|
||||
break :blk switch (capture) {
|
||||
.migrate => migrate.run(alloc, cwd, sub_args, options, T, main_options),
|
||||
.seed => seed.run(alloc, cwd, sub_args, options, T, main_options),
|
||||
.rollback => rollback.run(alloc, cwd, sub_args, options, T, main_options),
|
||||
.create => create.run(alloc, cwd, sub_args, options, T, main_options),
|
||||
.drop => drop.run(alloc, cwd, sub_args, options, T, main_options),
|
||||
|
36
cli/commands/database/seed.zig
Normal file
36
cli/commands/database/seed.zig
Normal file
@ -0,0 +1,36 @@
|
||||
const std = @import("std");
|
||||
|
||||
const cli = @import("../../cli.zig");
|
||||
const util = @import("../../util.zig");
|
||||
|
||||
pub fn run(
|
||||
allocator: std.mem.Allocator,
|
||||
cwd: std.fs.Dir,
|
||||
args: []const []const u8,
|
||||
options: cli.database.Options,
|
||||
T: type,
|
||||
main_options: T,
|
||||
) !void {
|
||||
_ = cwd;
|
||||
_ = options;
|
||||
if (main_options.options.help or args.len != 0) {
|
||||
std.debug.print(
|
||||
\\Run database seeders.
|
||||
\\
|
||||
\\Example:
|
||||
\\
|
||||
\\ jetzig database seed
|
||||
\\ jetzig --environment=testing database seed
|
||||
\\
|
||||
, .{});
|
||||
|
||||
return if (main_options.options.help) {} else error.JetzigCommandError;
|
||||
}
|
||||
|
||||
try util.execCommand(allocator, &.{
|
||||
"zig",
|
||||
"build",
|
||||
util.environmentBuildOption(main_options.options.environment),
|
||||
"jetzig:database:seed",
|
||||
});
|
||||
}
|
@ -10,11 +10,12 @@ const middleware = @import("generate/middleware.zig");
|
||||
const job = @import("generate/job.zig");
|
||||
const mailer = @import("generate/mailer.zig");
|
||||
const migration = @import("generate/migration.zig");
|
||||
const seeder = @import("generate/seeder.zig");
|
||||
|
||||
/// Command line options for the `generate` command.
|
||||
pub const Options = struct {
|
||||
pub const meta = .{
|
||||
.usage_summary = "[view|partial|layout|mailer|middleware|job|secret|migration] [options]",
|
||||
.usage_summary = "[view|partial|layout|mailer|middleware|job|secret|migration|seeder] [options]",
|
||||
.full_text =
|
||||
\\Generate scaffolding for views, middleware, and other objects.
|
||||
\\
|
||||
@ -39,7 +40,17 @@ pub fn run(
|
||||
|
||||
_ = options;
|
||||
|
||||
const Generator = enum { view, partial, layout, mailer, middleware, job, secret, migration };
|
||||
const Generator = enum {
|
||||
view,
|
||||
partial,
|
||||
layout,
|
||||
mailer,
|
||||
middleware,
|
||||
job,
|
||||
secret,
|
||||
migration,
|
||||
seeder,
|
||||
};
|
||||
var sub_args = std.ArrayList([]const u8).init(allocator);
|
||||
defer sub_args.deinit();
|
||||
|
||||
@ -55,6 +66,7 @@ pub fn run(
|
||||
.{ "middleware", .middleware },
|
||||
.{ "secret", .secret },
|
||||
.{ "migration", .migration },
|
||||
.{ "seeder", .seeder },
|
||||
});
|
||||
for (map.keys()) |key| try available_buf.append(key);
|
||||
|
||||
@ -92,6 +104,7 @@ pub fn run(
|
||||
.middleware => middleware.run(arena, cwd, sub_args.items, main_options.options.help),
|
||||
.secret => secret.run(arena, cwd, sub_args.items, main_options.options.help),
|
||||
.migration => migration.run(arena, cwd, sub_args.items, main_options.options.help),
|
||||
.seeder => seeder.run(arena, cwd, sub_args.items, main_options.options.help),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
40
cli/commands/generate/seeder.zig
Normal file
40
cli/commands/generate/seeder.zig
Normal file
@ -0,0 +1,40 @@
|
||||
const std = @import("std");
|
||||
|
||||
const jetquery = @import("jetquery");
|
||||
|
||||
/// Run the seeder generator. Create a seed in `src/app/database/seeders/`
|
||||
pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, help: bool) !void {
|
||||
if (help or args.len < 1) {
|
||||
std.debug.print(
|
||||
\\Generate a new Seeder. Seeders is a way to set up some inital data for your application.
|
||||
\\
|
||||
\\Example:
|
||||
\\
|
||||
\\ jetzig generate seeder iguana
|
||||
\\
|
||||
\\ More information: https://www.jetzig.dev/documentation/sections/database/command_line_tools
|
||||
\\
|
||||
, .{});
|
||||
|
||||
if (help) return;
|
||||
|
||||
return error.JetzigCommandError;
|
||||
}
|
||||
|
||||
const name = args[0];
|
||||
|
||||
const seeders_dir = try cwd.makeOpenPath(
|
||||
try std.fs.path.join(allocator, &.{ "src", "app", "database", "seeders" }),
|
||||
.{},
|
||||
);
|
||||
const seed = jetquery.Seeder.init(
|
||||
allocator,
|
||||
name,
|
||||
.{
|
||||
.seeders_path = try seeders_dir.realpathAlloc(allocator, "."),
|
||||
},
|
||||
);
|
||||
const path = try seed.save();
|
||||
|
||||
std.log.info("Saved seed: {s}", .{path});
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const jetquery = @import("jetquery");
|
||||
const t = jetquery.schema.table;
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn up(repo: anytype) !void {
|
||||
try repo.createTable(
|
||||
|
@ -0,0 +1,21 @@
|
||||
const std = @import("std");
|
||||
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn run(repo: anytype) !void {
|
||||
try repo.insert(
|
||||
.User,
|
||||
.{
|
||||
.email = "iguana@jetzig.dev",
|
||||
.password_hash = try jetzig.auth.hashPassword(repo.allocator, "password"),
|
||||
},
|
||||
);
|
||||
|
||||
try repo.insert(
|
||||
.User,
|
||||
.{
|
||||
.email = "admin@jetzig.dev",
|
||||
.password_hash = try jetzig.auth.hashPassword(repo.allocator, "admin"),
|
||||
},
|
||||
);
|
||||
}
|
@ -2,16 +2,24 @@ const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
/// This example demonstrates usage of Jetzig's background jobs.
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
// Prepare a job using `src/app/jobs/example.zig`.
|
||||
var job = try request.job("example");
|
||||
|
||||
// Add a param `foo` to the job.
|
||||
try job.params.put("foo", data.string("bar"));
|
||||
try job.params.put("id", data.integer(std.crypto.random.int(u32)));
|
||||
try job.params.put("foo", "bar");
|
||||
try job.params.put("id", std.crypto.random.int(u32));
|
||||
|
||||
// Schedule the job for background processing.
|
||||
try job.schedule();
|
||||
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
test "index" {
|
||||
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
|
||||
defer app.deinit();
|
||||
|
||||
const response = try app.request(.GET, "/background_jobs", .{});
|
||||
try response.expectJob("example", .{ .foo = "bar" });
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
try root.put("message", try request.cache.get("message"));
|
||||
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
|
||||
const params = try request.params();
|
||||
|
||||
@ -17,7 +17,7 @@ pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
try request.cache.put("message", message);
|
||||
try root.put("message", message);
|
||||
} else {
|
||||
try root.put("message", data.string("[no message param detected]"));
|
||||
try root.put("message", "[no message param detected]");
|
||||
}
|
||||
|
||||
return request.render(.ok);
|
||||
|
@ -4,16 +4,14 @@ const jetzig = @import("jetzig");
|
||||
// Generic handler for all errors.
|
||||
// Use `jetzig.http.status_codes.get(request.status_code)` to get a value that provides string
|
||||
// versions of the error code and message for use in templates.
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
var error_info = try data.object();
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
var error_info = try root.put("error", .object);
|
||||
|
||||
const status = jetzig.http.status_codes.get(request.status_code);
|
||||
|
||||
try error_info.put("code", data.string(status.getCode()));
|
||||
try error_info.put("message", data.string(status.getMessage()));
|
||||
|
||||
try root.put("error", error_info);
|
||||
try error_info.put("code", status.getCode());
|
||||
try error_info.put("message", status.getMessage());
|
||||
|
||||
// Render with the original error status code, or override if preferred.
|
||||
return request.render(request.status_code);
|
||||
|
@ -9,13 +9,11 @@ pub const formats: jetzig.Route.Formats = .{
|
||||
.get = &.{.html},
|
||||
};
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||
_ = id;
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ const jetzig = @import("jetzig");
|
||||
/// PUT /1234 => put(id, request, data)
|
||||
/// PATCH /1234 => patch(id, request, data)
|
||||
/// DELETE /1234 => delete(id, request, data)
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
// The first call to `data.object()` or `data.array()` sets the root response data value.
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
// Sets the root response data value.
|
||||
// JSON requests return a JSON string representation of the root data value.
|
||||
// Zmpl templates can access all values in the root data value.
|
||||
var root = try data.object();
|
||||
var root = try request.data(.object);
|
||||
|
||||
// Add a string to the root object.
|
||||
try root.put("welcome_message", data.string("Welcome to Jetzig!"));
|
||||
try root.put("welcome_message", "Welcome to Jetzig!");
|
||||
|
||||
// Request params have the same type as a `data.object()` so they can be inserted them
|
||||
// directly into the response data. Fetch `http://localhost:8080/?message=hello` to set the
|
||||
|
@ -3,38 +3,44 @@ const jetzig = @import("jetzig");
|
||||
const auth = @import("jetzig").auth;
|
||||
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
|
||||
if (request.middleware(.auth).user) |user| {
|
||||
try root.put("user", .{ .email = user.email });
|
||||
}
|
||||
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||
const Login = struct {
|
||||
email: []const u8,
|
||||
password: []const u8,
|
||||
};
|
||||
const Logout = struct { logout: []const u8 };
|
||||
const Login = struct { email: []const u8, password: []const u8 };
|
||||
|
||||
if (try request.expectParams(Logout)) |_| {
|
||||
try auth.signOut(request);
|
||||
return request.redirect("/login", .found);
|
||||
}
|
||||
|
||||
const params = try request.expectParams(Login) orelse {
|
||||
return request.fail(.forbidden);
|
||||
};
|
||||
|
||||
// Lookup the user by email
|
||||
const query = jetzig.database.Query(.User).findBy(
|
||||
const user = try jetzig.database.Query(.User).findBy(
|
||||
.{ .email = params.email },
|
||||
);
|
||||
|
||||
const user = try request.repo.execute(query) orelse {
|
||||
).execute(request.repo) orelse {
|
||||
return request.fail(.forbidden);
|
||||
};
|
||||
|
||||
// Check that the password matches
|
||||
if (try auth.verifyPassword(
|
||||
if (!try auth.verifyPassword(
|
||||
request.allocator,
|
||||
user.password_hash,
|
||||
params.password,
|
||||
)) {
|
||||
try auth.signIn(request, user.id);
|
||||
return request.redirect("/", .found);
|
||||
}
|
||||
return request.fail(.forbidden);
|
||||
)) return request.fail(.forbidden);
|
||||
|
||||
try auth.signIn(request, user.id);
|
||||
return request.redirect("/login", .found);
|
||||
}
|
||||
|
||||
test "post" {
|
||||
|
@ -1,7 +1,16 @@
|
||||
<form method="post" id="login">
|
||||
<input type="email" name="email" placeholder="name@example.com">
|
||||
<label for="email">Email address</label>
|
||||
<input type="password" name="password" placeholder="Password">
|
||||
<label for="password">Password</label>
|
||||
<button type="submit" form="login">Sign in</button>
|
||||
</form>
|
||||
@if ($.user) |user|
|
||||
<div>Logged in as {{user.email}}</div>
|
||||
|
||||
<form method="post" id="logout">
|
||||
<input type="hidden" name="logout" value="1" />
|
||||
<button type="submit" form="logout">Sign Out</button>
|
||||
</form>
|
||||
@else
|
||||
<form method="post" id="login">
|
||||
<input type="email" name="email" placeholder="name@example.com">
|
||||
<label for="email">Email address</label>
|
||||
<input type="password" name="password" placeholder="Password">
|
||||
<label for="password">Password</label>
|
||||
<button type="submit" form="login">Sign in</button>
|
||||
</form>
|
||||
@end
|
||||
|
@ -1,9 +1,9 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
try root.put("message", data.string("Welcome to Jetzig!"));
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
try root.put("message", "Welcome to Jetzig!");
|
||||
|
||||
// Create a new mail using `src/app/mailers/welcome.zig`.
|
||||
// HTML and text parts are rendered using Zmpl templates:
|
||||
|
@ -3,8 +3,7 @@ const jetzig = @import("jetzig");
|
||||
|
||||
pub const layout = "application";
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
|
17
demo/src/app/views/render_text.zig
Normal file
17
demo/src/app/views/render_text.zig
Normal file
@ -0,0 +1,17 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
request.response.content_type = "text/xml";
|
||||
return request.renderText("<foo><bar>baz</bar></foo>", .ok);
|
||||
}
|
||||
|
||||
test "index" {
|
||||
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
|
||||
defer app.deinit();
|
||||
|
||||
const response = try app.request(.GET, "/render_text", .{});
|
||||
try response.expectStatus(.ok);
|
||||
try response.expectBodyContains("<foo><bar>baz</bar></foo>");
|
||||
try response.expectHeader("content-type", "text/xml");
|
||||
}
|
3
demo/src/app/views/render_text/index.zmpl
Normal file
3
demo/src/app/views/render_text/index.zmpl
Normal file
@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<span>Content goes here</span>
|
||||
</div>
|
@ -1,15 +1,15 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
var root = try data.object();
|
||||
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||
var root = try request.data(.object);
|
||||
|
||||
const session = try request.session();
|
||||
|
||||
if (session.get("message")) |message| {
|
||||
try root.put("message", message);
|
||||
} else {
|
||||
try root.put("message", data.string("No message saved yet"));
|
||||
try root.put("message", "No message saved yet");
|
||||
}
|
||||
|
||||
return request.render(.ok);
|
||||
@ -20,8 +20,7 @@ pub fn edit(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
||||
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
_ = data;
|
||||
pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||
const params = try request.params();
|
||||
var session = try request.session();
|
||||
|
||||
|
@ -18,7 +18,10 @@ pub const jetzig_options = struct {
|
||||
// jetzig.middleware.AntiCsrfMiddleware,
|
||||
jetzig.middleware.HtmxMiddleware,
|
||||
jetzig.middleware.ChannelsMiddleware,
|
||||
// jetzig.middleware.InertiaMiddleware,
|
||||
// jetzig.middleware.InertiaMiddleware,
|
||||
jetzig.middleware.AuthMiddleware,
|
||||
// jetzig.middleware.AntiCsrfMiddleware,
|
||||
// jetzig.middleware.HtmxMiddleware,
|
||||
// jetzig.middleware.CompressionMiddleware,
|
||||
// @import("app/middleware/DemoMiddleware.zig"),
|
||||
};
|
||||
@ -70,6 +73,10 @@ pub const jetzig_options = struct {
|
||||
// Path relative to cwd() to serve public content from. Symlinks are not followed.
|
||||
pub const public_content_path = "public";
|
||||
|
||||
/// Request path to map to public directory, e.g. if `public_routing_path` is `"/foo"` then a
|
||||
/// request to `/foo/bar.png` will serve static content found in `public/bar.png`
|
||||
pub const public_routing_path = "/";
|
||||
|
||||
// HTTP buffer. Must be large enough to store all headers. This should typically not be modified.
|
||||
pub const http_buffer_size: usize = std.math.pow(usize, 2, 16);
|
||||
|
||||
|
@ -6,6 +6,8 @@ const jetquery = @import("jetquery");
|
||||
const jetzig = @import("jetzig");
|
||||
const Migrate = @import("jetquery_migrate").Migrate;
|
||||
const MigrateSchema = @import("jetquery_migrate").MigrateSchema;
|
||||
const Seeder = @import("jetquery_seeder").Seed;
|
||||
const SeederSchema = @import("jetquery_seeder").SeederSchema;
|
||||
const Schema = @import("Schema");
|
||||
const util = @import("util.zig");
|
||||
|
||||
@ -15,7 +17,7 @@ const production_drop_failure_message = "To drop a production database, " ++
|
||||
|
||||
const environment = jetzig.build_options.environment;
|
||||
const config = @field(jetquery.config.database, @tagName(environment));
|
||||
const Action = enum { migrate, rollback, create, drop, reflect, setup, update };
|
||||
const Action = enum { migrate, rollback, create, drop, reflect, setup, update, seed };
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
@ -42,6 +44,7 @@ pub fn main() !void {
|
||||
|
||||
const map = std.StaticStringMap(Action).initComptime(.{
|
||||
.{ "migrate", .migrate },
|
||||
.{ "seed", .seed },
|
||||
.{ "rollback", .rollback },
|
||||
.{ "create", .create },
|
||||
.{ "drop", .drop },
|
||||
@ -69,6 +72,11 @@ pub fn main() !void {
|
||||
defer repo.deinit();
|
||||
try Migrate(config.adapter).init(&repo).migrate();
|
||||
},
|
||||
.seed => {
|
||||
var repo = try seedersRepo(action, allocator, repo_env);
|
||||
defer repo.deinit();
|
||||
try Seeder(config.adapter, Schema).init(&repo).seed();
|
||||
},
|
||||
.rollback => {
|
||||
var repo = try migrationsRepo(action, allocator, repo_env);
|
||||
defer repo.deinit();
|
||||
@ -136,7 +144,7 @@ fn migrationsRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytyp
|
||||
@field(jetquery.Environment, @tagName(environment)),
|
||||
.{
|
||||
.admin = switch (action) {
|
||||
.migrate, .rollback, .update => false,
|
||||
.migrate, .seed, .rollback, .update => false,
|
||||
.create, .drop => true,
|
||||
.reflect => unreachable, // We use a separate repo for schema reflection.
|
||||
.setup => unreachable, // Setup uses `create` and then `update`
|
||||
@ -147,6 +155,24 @@ fn migrationsRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytyp
|
||||
);
|
||||
}
|
||||
|
||||
const SeedersRepo = jetquery.Repo(config.adapter, Schema);
|
||||
fn seedersRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytype) !SeedersRepo {
|
||||
return try SeedersRepo.loadConfig(
|
||||
allocator,
|
||||
@field(jetquery.Environment, @tagName(environment)),
|
||||
.{
|
||||
.admin = switch (action) {
|
||||
.migrate, .seed, .rollback, .update => false,
|
||||
.create, .drop => false,
|
||||
.reflect => unreachable, // We use a separate repo for schema reflection.
|
||||
.setup => unreachable, // Setup uses `create` and then `update`
|
||||
},
|
||||
.context = .seed,
|
||||
.env = repo_env,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn reflectSchema(allocator: std.mem.Allocator, repo_env: anytype) !void {
|
||||
var cwd = try jetzig.util.detectJetzigProjectDir();
|
||||
defer cwd.close();
|
||||
|
@ -31,7 +31,7 @@ pub fn main() !void {
|
||||
var index: usize = 0;
|
||||
while (it.next()) |arg| : (index += 1) {
|
||||
if (index == 0) continue;
|
||||
const file = try std.fs.createFileAbsolute(arg, .{});
|
||||
const file = try std.fs.cwd().createFile(arg, .{});
|
||||
const writer = file.writer();
|
||||
try compileStaticRoutes(allocator, writer);
|
||||
file.close();
|
||||
|
@ -22,6 +22,11 @@ pub fn signIn(request: *jetzig.Request, user_id: anytype) !void {
|
||||
try session.put("_jetzig_user_id", user_id);
|
||||
}
|
||||
|
||||
pub fn signOut(request: *jetzig.Request) !void {
|
||||
var session = try request.session();
|
||||
_ = try session.remove("_jetzig_user_id");
|
||||
}
|
||||
|
||||
pub fn verifyPassword(
|
||||
allocator: std.mem.Allocator,
|
||||
hash: []const u8,
|
||||
|
@ -84,6 +84,10 @@ pub const max_connections: u16 = 512;
|
||||
/// Path relative to cwd() to serve public content from. Symlinks are not followed.
|
||||
pub const public_content_path = "public";
|
||||
|
||||
/// Request path to map to public directory, e.g. if `public_routing_path` is `"/foo"` then a
|
||||
/// request to `/foo/bar.png` will serve static content found in `public/bar.png`
|
||||
pub const public_routing_path = "/";
|
||||
|
||||
/// Middleware chain. Add any custom middleware here, or use middleware provided in
|
||||
/// `jetzig.middleware` (e.g. `jetzig.middleware.HtmxMiddleware`).
|
||||
pub const middleware = &.{};
|
||||
|
@ -26,3 +26,5 @@ pub const params = @import("http/params.zig");
|
||||
pub const SimplifiedRequest = struct {
|
||||
location: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const default_content_type = "application/octet-stream";
|
||||
|
@ -107,10 +107,12 @@ pub fn deinit(self: *Cookies) void {
|
||||
self.arena.deinit();
|
||||
}
|
||||
|
||||
/// Fetch a cookie by name.
|
||||
pub fn get(self: *Cookies, key: []const u8) ?*Cookie {
|
||||
return self.cookies.get(key);
|
||||
}
|
||||
|
||||
/// Put a cookie into the cookie store.
|
||||
pub fn put(self: *Cookies, cookie: Cookie) !void {
|
||||
self.modified = true;
|
||||
|
||||
@ -127,6 +129,18 @@ pub fn put(self: *Cookies, cookie: Cookie) !void {
|
||||
try self.cookies.put(key, ptr);
|
||||
}
|
||||
|
||||
/// Overwrite a cookie with an empty string and expiry of 0. The browser should then no longer
|
||||
/// send the specified cookie value.
|
||||
///
|
||||
/// > Notice that servers can delete cookies by sending the user agent a new cookie with an
|
||||
/// > Expires attribute with a value in the past.
|
||||
/// - https://www.rfc-editor.org/rfc/rfc6265.html
|
||||
pub fn delete(self: *Cookies, key: []const u8) !void {
|
||||
self.modified = true;
|
||||
|
||||
try self.put(.{ .name = key, .value = "", .expires = 0 });
|
||||
}
|
||||
|
||||
pub const HeaderIterator = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
cookies_iterator: std.StringArrayHashMap(*Cookie).Iterator,
|
||||
@ -434,3 +448,17 @@ test "default flags" {
|
||||
try std.testing.expect(cookie.expires == null);
|
||||
try std.testing.expect(cookie.max_age == null);
|
||||
}
|
||||
|
||||
test "delete" {
|
||||
const allocator = std.testing.allocator;
|
||||
var cookies = Cookies.init(allocator, "foo=bar;");
|
||||
defer cookies.deinit();
|
||||
|
||||
try cookies.parse();
|
||||
|
||||
try cookies.delete("foo");
|
||||
const cookie = cookies.get("foo").?;
|
||||
|
||||
try std.testing.expectEqualStrings(cookie.value, "");
|
||||
try std.testing.expectEqual(cookie.expires.?, 0);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ pub const RequestState = enum {
|
||||
after_request, // Initial middleware processing
|
||||
after_view, // View returned, response data ready for full response render
|
||||
rendered, // Rendered by middleware or view
|
||||
rendered_content, // Rendered a plain string by middleware or view
|
||||
rendered_text, // Rendered text by middleware or view
|
||||
redirected, // Redirected by middleware or view
|
||||
failed, // Failed by middleware or view
|
||||
before_response, // Post middleware processing
|
||||
@ -251,7 +251,7 @@ pub fn renderContent(
|
||||
if (self.isRendered()) self.rendered_multiple = true;
|
||||
|
||||
self.rendered_view = .{ .data = self.response_data, .status_code = status_code, .content = content };
|
||||
self.state = .rendered_content;
|
||||
self.state = .rendered_text;
|
||||
return self.rendered_view.?;
|
||||
}
|
||||
|
||||
@ -268,7 +268,7 @@ pub fn fail(self: *Request, status_code: jetzig.http.status_codes.StatusCode) je
|
||||
pub inline fn isRendered(self: *const Request) bool {
|
||||
return switch (self.state) {
|
||||
.initial, .processed, .after_request, .before_response => false,
|
||||
.after_view, .rendered, .rendered_content, .redirected, .failed, .finalized => true,
|
||||
.after_view, .rendered, .rendered_text, .redirected, .failed, .finalized => true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -778,6 +778,18 @@ pub fn joinPath(self: *const Request, args: anytype) ![]const u8 {
|
||||
return try std.mem.join(self.allocator, "/", buf[0..]);
|
||||
}
|
||||
|
||||
pub fn renderText(
|
||||
self: *Request,
|
||||
text: []const u8,
|
||||
status_code: jetzig.http.StatusCode,
|
||||
) jetzig.views.View {
|
||||
self.state = .rendered_text;
|
||||
self.rendered_view = .{ .data = self.response_data, .status_code = status_code };
|
||||
self.setResponse(.{ .view = self.rendered_view.?, .content = text }, .{});
|
||||
|
||||
return self.rendered_view.?;
|
||||
}
|
||||
|
||||
pub fn joinPaths(self: *const Request, paths: []const []const []const u8) ![]const u8 {
|
||||
var buf = std.ArrayList([]const u8).init(self.allocator);
|
||||
defer buf.deinit();
|
||||
@ -795,10 +807,12 @@ pub fn setResponse(
|
||||
) void {
|
||||
self.response.content = rendered_view.content;
|
||||
self.response.status_code = rendered_view.view.status_code;
|
||||
self.response.content_type = options.content_type orelse switch (self.requestFormat()) {
|
||||
.HTML, .UNKNOWN => "text/html",
|
||||
.JSON => "application/json",
|
||||
};
|
||||
if (self.response.content_type == null) {
|
||||
self.response.content_type = options.content_type orelse switch (self.requestFormat()) {
|
||||
.HTML, .UNKNOWN => "text/html",
|
||||
.JSON => "application/json",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn setCookieHeaders(self: *Request) !void {
|
||||
|
@ -11,7 +11,7 @@ allocator: std.mem.Allocator,
|
||||
headers: jetzig.http.Headers,
|
||||
content: []const u8,
|
||||
status_code: http.status_codes.StatusCode,
|
||||
content_type: []const u8,
|
||||
content_type: ?[]const u8 = null,
|
||||
httpz_response: *httpz.Response,
|
||||
|
||||
pub fn init(
|
||||
@ -22,8 +22,11 @@ pub fn init(
|
||||
.allocator = allocator,
|
||||
.httpz_response = httpz_response,
|
||||
.status_code = .no_content,
|
||||
.content_type = "application/octet-stream",
|
||||
.content = "",
|
||||
.headers = jetzig.http.Headers.init(allocator, &httpz_response.headers),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn contentType(self: *const jetzig.http.Response) []const u8 {
|
||||
return self.content_type orelse jetzig.http.default_content_type;
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
}
|
||||
|
||||
try self.renderResponse(&request);
|
||||
try request.response.headers.append("Content-Type", response.content_type);
|
||||
try request.response.headers.append("Content-Type", response.contentType());
|
||||
|
||||
try jetzig.http.middleware.beforeResponse(&middleware_data, &request);
|
||||
try request.respond();
|
||||
@ -244,7 +244,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
// TODO: Allow middleware to set content
|
||||
request.setResponse(.{ .view = rendered, .content = "" }, .{});
|
||||
}
|
||||
try request.response.headers.append("Content-Type", response.content_type);
|
||||
try request.response.headers.append("Content-Type", response.contentType());
|
||||
try request.respond();
|
||||
return true;
|
||||
} else return false;
|
||||
@ -418,12 +418,16 @@ pub fn RoutedServer(Routes: type) type {
|
||||
return try self.renderInternalServerError(request, @errorReturnTrace(), err);
|
||||
};
|
||||
|
||||
if (request.state == .failed) {
|
||||
const view: jetzig.views.View = request.rendered_view orelse .{
|
||||
.data = request.response_data,
|
||||
.status_code = .internal_server_error,
|
||||
};
|
||||
return try self.renderError(request, view.status_code, .{});
|
||||
switch (request.state) {
|
||||
.failed => {
|
||||
const status_code = request.rendered_view.?.status_code;
|
||||
return try self.renderError(request, status_code, .{});
|
||||
},
|
||||
.rendered_text => {
|
||||
const view = request.rendered_view.?; // a panic here is a bug.
|
||||
return .{ .view = view, .content = request.response.content };
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
const template: ?zmpl.Template = if (request.dynamic_assigned_template) |request_template|
|
||||
@ -435,7 +439,7 @@ pub fn RoutedServer(Routes: type) type {
|
||||
|
||||
if (request.rendered_view) |rendered_view| {
|
||||
if (request.state == .redirected) return .{ .view = rendered_view, .content = "" };
|
||||
if (request.state == .rendered_content) return .{
|
||||
if (request.state == .rendered_text) return .{
|
||||
.view = rendered_view,
|
||||
.content = rendered_view.content.?,
|
||||
};
|
||||
|
@ -66,7 +66,7 @@ pub const TestResponse = struct {
|
||||
jobs: []Job,
|
||||
|
||||
pub const Header = struct { name: []const u8, value: []const u8 };
|
||||
pub const Job = struct { name: []const u8, params: ?[]const u8 = null };
|
||||
pub const Job = struct { name: []const u8, params: ?*jetzig.data.Value = null };
|
||||
|
||||
pub fn expectStatus(self: TestResponse, comptime expected: jetzig.http.status_codes.StatusCode) !void {
|
||||
try testing.expectStatus(expected, self);
|
||||
@ -120,14 +120,31 @@ pub fn expectBodyContains(expected: []const u8, response: TestResponse) !void {
|
||||
}
|
||||
|
||||
pub fn expectHeader(expected_name: []const u8, expected_value: ?[]const u8, response: TestResponse) !void {
|
||||
var mismatches = std.ArrayList([]const u8).init(response.allocator);
|
||||
defer mismatches.deinit();
|
||||
|
||||
for (response.headers) |header| {
|
||||
if (!std.ascii.eqlIgnoreCase(header.name, expected_name)) continue;
|
||||
if (expected_value) |value| {
|
||||
if (std.mem.eql(u8, header.value, value)) return;
|
||||
if (std.mem.eql(u8, header.value, value)) {
|
||||
return;
|
||||
} else {
|
||||
try mismatches.append(header.value);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logFailure(
|
||||
"Expected header " ++
|
||||
jetzig.colors.cyan("{s}") ++
|
||||
": " ++
|
||||
jetzig.colors.green("{?s}") ++
|
||||
", found: " ++
|
||||
jetzig.colors.red("{s}"),
|
||||
.{ expected_name, expected_value, mismatches.items },
|
||||
);
|
||||
return error.JetzigExpectHeaderError;
|
||||
}
|
||||
|
||||
@ -282,13 +299,54 @@ pub fn expectJson(expected_path: []const u8, expected_value: anytype, response:
|
||||
}
|
||||
|
||||
pub fn expectJob(job_name: []const u8, job_params: anytype, response: TestResponse) !void {
|
||||
var actual_params_buf = std.ArrayList([]const u8).init(response.allocator);
|
||||
defer actual_params_buf.deinit();
|
||||
|
||||
const value: ?*jetzig.data.Value = if (@TypeOf(job_params) == @TypeOf(null))
|
||||
null
|
||||
else
|
||||
try jetzig.Data.zmplValue(job_params, response.allocator);
|
||||
for (response.jobs) |job| {
|
||||
comptime var has_args = false;
|
||||
inline for (@typeInfo(@TypeOf(job_params)).Struct.fields) |field| {
|
||||
has_args = true;
|
||||
_ = field;
|
||||
if (std.mem.eql(u8, job_name, job.name)) {
|
||||
if (value != null and job.params != null) {
|
||||
if (job.params.?.includes(value.?.*)) {
|
||||
return;
|
||||
} else {
|
||||
try actual_params_buf.append(try job.params.?.toJson());
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!has_args and std.mem.eql(u8, job_name, job.name)) return;
|
||||
}
|
||||
|
||||
if (actual_params_buf.items.len == 0) {
|
||||
logFailure(
|
||||
"Expected job " ++
|
||||
jetzig.colors.cyan("{s}") ++
|
||||
" to have been scheduled but job was not found in the queue.",
|
||||
.{job_name},
|
||||
);
|
||||
} else {
|
||||
const actual_params_formatted = try std.mem.join(
|
||||
response.allocator,
|
||||
"\n",
|
||||
actual_params_buf.items,
|
||||
);
|
||||
defer response.allocator.free(actual_params_formatted);
|
||||
logFailure(
|
||||
"Expected params for job " ++
|
||||
jetzig.colors.cyan("{s}") ++
|
||||
":\n" ++
|
||||
jetzig.colors.red("{s}\n") ++
|
||||
"Actual job params:\n" ++
|
||||
jetzig.colors.green("{s}"),
|
||||
.{
|
||||
job_name,
|
||||
if (value) |v| try v.toJson() else "null",
|
||||
actual_params_formatted,
|
||||
},
|
||||
);
|
||||
}
|
||||
return error.JetzigExpectJobError;
|
||||
}
|
||||
|
@ -234,6 +234,7 @@ pub fn request(
|
||||
while (try self.job_queue.popFirst(&data, "__jetzig_jobs")) |value| {
|
||||
if (value.getT(.string, "__jetzig_job_name")) |job_name| try jobs.append(.{
|
||||
.name = try allocator.dupe(u8, job_name),
|
||||
.params = value,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ pub fn main() !void {
|
||||
mailers_path,
|
||||
);
|
||||
const generated_routes = try routes.generateRoutes();
|
||||
var src_dir = try std.fs.openDirAbsolute(src_path, .{ .iterate = true });
|
||||
var src_dir = try std.fs.cwd().openDir(src_path, .{ .iterate = true });
|
||||
defer src_dir.close();
|
||||
var walker = try src_dir.walk(allocator);
|
||||
defer walker.deinit();
|
||||
@ -39,7 +39,7 @@ pub fn main() !void {
|
||||
const stat = try src_dir.statFile(entry.path);
|
||||
const src_data = try src_dir.readFileAlloc(allocator, entry.path, @intCast(stat.size));
|
||||
const relpath = try std.fs.path.join(allocator, &[_][]const u8{ "src", entry.path });
|
||||
var dir = try std.fs.openDirAbsolute(std.fs.path.dirname(output_path).?, .{});
|
||||
var dir = try std.fs.cwd().openDir(std.fs.path.dirname(output_path).?, .{});
|
||||
const dest_dir = try dir.makeOpenPath(std.fs.path.dirname(relpath).?, .{});
|
||||
const src_file = try dest_dir.createFile(std.fs.path.basename(relpath), .{});
|
||||
try src_file.writeAll(src_data);
|
||||
@ -47,7 +47,7 @@ pub fn main() !void {
|
||||
}
|
||||
}
|
||||
|
||||
const file = try std.fs.createFileAbsolute(output_path, .{ .truncate = true });
|
||||
const file = try std.fs.cwd().createFile(output_path, .{ .truncate = true });
|
||||
try file.writeAll(generated_routes);
|
||||
file.close();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user