forked from jetzig-mirror/jetzig
Compare commits
69 Commits
incrementa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1feb18fb74 | ||
|
|
86d82026ab | ||
|
|
01d0862043 | ||
|
|
95d3f10bc9 | ||
|
|
dee5701b4a | ||
|
|
b7c3c0045a | ||
|
|
6d9fa8bff4 | ||
|
|
4bd9d8b990 | ||
|
|
8171ab5b5d | ||
|
|
13907aad7c | ||
|
|
aae9fd182b | ||
|
|
4c568f2606 | ||
|
|
94cf122847 | ||
|
|
b32d24ad80 | ||
|
|
3fd42f2ebb | ||
|
|
ace82985ef | ||
|
|
e96a3d36b6 | ||
|
|
dc982cf125 | ||
|
|
6d522c07c5 | ||
|
|
c26d563898 | ||
|
|
2c52792217 | ||
|
|
327b0390de | ||
|
|
2cb1b4e959 | ||
|
|
a46e944499 | ||
|
|
79574ed861 | ||
|
|
00ed3067dd | ||
|
|
aae890998f | ||
|
|
21bf237437 | ||
|
|
b81c6dfe91 | ||
|
|
9e4e8529c5 | ||
|
|
359b4fc2eb | ||
|
|
70a8b0f698 | ||
|
|
f94db09551 | ||
|
|
46a311174f | ||
|
|
48e7139e26 | ||
|
|
c6ce935ce1 | ||
|
|
22688ccaa3 | ||
|
|
f3788567bb | ||
|
|
58f7e0e76a | ||
|
|
b070d281e3 | ||
|
|
9012ec3aaf | ||
|
|
182ceee17f | ||
|
|
034ecddfa0 | ||
|
|
19e17bc447 | ||
|
|
d8a992ce38 | ||
|
|
b8af924f8e | ||
|
|
29612e463f | ||
|
|
23d0102ae8 | ||
|
|
544b33e279 | ||
|
|
d3157e4247 | ||
|
|
501c6184a5 | ||
|
|
d6226bd4ef | ||
|
|
7eea1e3f0a | ||
|
|
3418698220 | ||
|
|
9c4b6198cd | ||
|
|
06674db51f | ||
|
|
099a2a5349 | ||
|
|
c0d6fb162b | ||
|
|
a16e1c8716 | ||
|
|
1dfe58fb64 | ||
|
|
719bd300a7 | ||
|
|
8a4f91f26a | ||
|
|
0a4690e001 | ||
|
|
da2978ed04 | ||
|
|
fb969d9b81 | ||
|
|
492c1833b2 | ||
|
|
87c7adb50f | ||
|
|
ae356c0ebf | ||
|
|
ecd66db3dc |
21
.github/workflows/CI.yml
vendored
21
.github/workflows/CI.yml
vendored
@ -18,18 +18,17 @@ jobs:
|
|||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
# Create postgres server
|
||||||
|
# https://github.com/marketplace/actions/setup-postgresql-for-linux-macos-windows
|
||||||
|
- uses: ikalnytskyi/action-setup-postgres@v7
|
||||||
|
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Zig
|
- name: Setup Zig
|
||||||
# You may pin to the exact commit or the version.
|
uses: mlugg/setup-zig@main
|
||||||
# uses: goto-bus-stop/setup-zig@41ae19e72e21b9a1380e86ff9f058db709fc8fc6
|
|
||||||
uses: goto-bus-stop/setup-zig@v2
|
|
||||||
with:
|
|
||||||
version: master
|
|
||||||
cache: true # Let's see how this behaves
|
|
||||||
|
|
||||||
- run: zig version
|
- run: zig version
|
||||||
- run: zig env
|
- run: zig env
|
||||||
@ -43,7 +42,17 @@ jobs:
|
|||||||
- name: Run App Tests
|
- name: Run App Tests
|
||||||
run: |
|
run: |
|
||||||
cd demo
|
cd demo
|
||||||
|
zig build -Denvironment=testing jetzig:database:create
|
||||||
|
zig build -Denvironment=testing jetzig:database:migrate
|
||||||
zig build -Denvironment=testing jetzig:test
|
zig build -Denvironment=testing jetzig:test
|
||||||
|
env:
|
||||||
|
JETQUERY_HOSTNAME: 'localhost'
|
||||||
|
JETQUERY_USERNAME: 'postgres'
|
||||||
|
JETQUERY_PASSWORD: 'postgres'
|
||||||
|
JETQUERY_DATABASE: 'jetzig_demo_test'
|
||||||
|
# Assume a small amount of connections are allowed
|
||||||
|
# into postgres
|
||||||
|
JETQUERY_POOL_SIZE: 2
|
||||||
|
|
||||||
- name: Build artifacts
|
- name: Build artifacts
|
||||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ _Jetzig_ is a web framework written in 100% pure [Zig](https://ziglang.org) :liz
|
|||||||
|
|
||||||
Official website: [jetzig.dev](https://www.jetzig.dev/)
|
Official website: [jetzig.dev](https://www.jetzig.dev/)
|
||||||
|
|
||||||
|
Please note that _Jetzig_'s `main` branch aims to be compatible with the latest [Zig nightly master build](https://ziglang.org/download/) and older versions of _Zig_ are not supported.
|
||||||
|
|
||||||
_Jetzig_ aims to provide a rich set of user-friendly tools for building modern web applications quickly. See the checklist below.
|
_Jetzig_ aims to provide a rich set of user-friendly tools for building modern web applications quickly. See the checklist below.
|
||||||
|
|
||||||
Join us on Discord ! [https://discord.gg/eufqssz7X6](https://discord.gg/eufqssz7X6).
|
Join us on Discord ! [https://discord.gg/eufqssz7X6](https://discord.gg/eufqssz7X6).
|
||||||
|
|||||||
37
build.zig
37
build.zig
@ -5,6 +5,9 @@ pub const GenerateMimeTypes = @import("src/GenerateMimeTypes.zig");
|
|||||||
|
|
||||||
const zmpl_build = @import("zmpl");
|
const zmpl_build = @import("zmpl");
|
||||||
const Environment = enum { development, testing, production };
|
const Environment = enum { development, testing, production };
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const use_llvm_default = builtin.os.tag != .linux;
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
@ -36,6 +39,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.{
|
.{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = b.option(bool, "use_llvm", "Use LLVM") orelse use_llvm_default,
|
||||||
.zmpl_templates_paths = templates_paths,
|
.zmpl_templates_paths = templates_paths,
|
||||||
.zmpl_auto_build = false,
|
.zmpl_auto_build = false,
|
||||||
.zmpl_markdown_fragments = try generateMarkdownFragments(b),
|
.zmpl_markdown_fragments = try generateMarkdownFragments(b),
|
||||||
@ -46,6 +50,11 @@ pub fn build(b: *std.Build) !void {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const zmpl_steps = zmpl_dep.builder.top_level_steps;
|
||||||
|
const zmpl_compile_step = zmpl_steps.get("compile").?;
|
||||||
|
const compile_step = b.step("compile", "Compile Zmpl templates");
|
||||||
|
compile_step.dependOn(&zmpl_compile_step.step);
|
||||||
|
|
||||||
const zmpl_module = zmpl_dep.module("zmpl");
|
const zmpl_module = zmpl_dep.module("zmpl");
|
||||||
|
|
||||||
const jetkv_dep = b.dependency("jetkv", .{ .target = target, .optimize = optimize });
|
const jetkv_dep = b.dependency("jetkv", .{ .target = target, .optimize = optimize });
|
||||||
@ -58,18 +67,15 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const jetcommon_dep = b.dependency("jetcommon", .{ .target = target, .optimize = optimize });
|
const jetcommon_dep = b.dependency("jetcommon", .{ .target = target, .optimize = optimize });
|
||||||
const zmd_dep = b.dependency("zmd", .{ .target = target, .optimize = optimize });
|
const zmd_dep = b.dependency("zmd", .{ .target = target, .optimize = optimize });
|
||||||
const httpz_dep = b.dependency("httpz", .{ .target = target, .optimize = optimize });
|
const httpz_dep = b.dependency("httpz", .{ .target = target, .optimize = optimize });
|
||||||
const pg_dep = b.dependency("pg", .{ .target = target, .optimize = optimize });
|
|
||||||
|
|
||||||
// This is the way to make it look nice in the zig build script
|
// This is the way to make it look nice in the zig build script
|
||||||
// If we would do it the other way around, we would have to do
|
// If we would do it the other way around, we would have to do
|
||||||
// b.dependency("jetzig",.{}).builder.dependency("zmpl",.{}).module("zmpl");
|
// b.dependency("jetzig",.{}).builder.dependency("zmpl",.{}).module("zmpl");
|
||||||
b.modules.put("zmpl", zmpl_dep.module("zmpl")) catch @panic("Out of memory");
|
b.modules.put("zmpl", zmpl_dep.module("zmpl")) catch @panic("Out of memory");
|
||||||
b.modules.put("zmd", zmd_dep.module("zmd")) catch @panic("Out of memory");
|
b.modules.put("zmd", zmd_dep.module("zmd")) catch @panic("Out of memory");
|
||||||
b.modules.put("pg", pg_dep.module("pg")) catch @panic("Out of memory");
|
|
||||||
b.modules.put("jetquery", jetquery_dep.module("jetquery")) catch @panic("Out of memory");
|
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("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_migrate", jetquery_dep.module("jetquery_migrate")) catch @panic("Out of memory");
|
||||||
jetquery_dep.module("jetquery").addImport("pg", pg_dep.module("pg"));
|
|
||||||
|
|
||||||
const smtp_client_dep = b.dependency("smtp_client", .{
|
const smtp_client_dep = b.dependency("smtp_client", .{
|
||||||
.target = target,
|
.target = target,
|
||||||
@ -132,6 +138,8 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
const target = exe.root_module.resolved_target orelse @panic("Unable to detect compile target.");
|
const target = exe.root_module.resolved_target orelse @panic("Unable to detect compile target.");
|
||||||
const optimize = exe.root_module.optimize orelse .Debug;
|
const optimize = exe.root_module.optimize orelse .Debug;
|
||||||
|
|
||||||
|
exe.use_llvm = exe.use_llvm orelse use_llvm_default;
|
||||||
|
|
||||||
if (optimize != .Debug) exe.linkLibC();
|
if (optimize != .Debug) exe.linkLibC();
|
||||||
|
|
||||||
const environment = b.option(
|
const environment = b.option(
|
||||||
@ -172,7 +180,6 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
const jetzig_module = jetzig_dep.module("jetzig");
|
const jetzig_module = jetzig_dep.module("jetzig");
|
||||||
const zmpl_module = jetzig_dep.module("zmpl");
|
const zmpl_module = jetzig_dep.module("zmpl");
|
||||||
const zmd_module = jetzig_dep.module("zmd");
|
const zmd_module = jetzig_dep.module("zmd");
|
||||||
const pg_module = jetzig_dep.module("pg");
|
|
||||||
const jetquery_module = jetzig_dep.module("jetquery");
|
const jetquery_module = jetzig_dep.module("jetquery");
|
||||||
const jetcommon_module = jetzig_dep.module("jetcommon");
|
const jetcommon_module = jetzig_dep.module("jetcommon");
|
||||||
const jetquery_migrate_module = jetzig_dep.module("jetquery_migrate");
|
const jetquery_migrate_module = jetzig_dep.module("jetquery_migrate");
|
||||||
@ -187,7 +194,6 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
exe.root_module.addImport("jetzig", jetzig_module);
|
exe.root_module.addImport("jetzig", jetzig_module);
|
||||||
exe.root_module.addImport("zmpl", zmpl_module);
|
exe.root_module.addImport("zmpl", zmpl_module);
|
||||||
exe.root_module.addImport("zmd", zmd_module);
|
exe.root_module.addImport("zmd", zmd_module);
|
||||||
exe.root_module.addImport("pg", pg_module);
|
|
||||||
|
|
||||||
if (b.option(bool, "jetzig_runner", "Used internally by `jetzig server` command.")) |jetzig_runner| {
|
if (b.option(bool, "jetzig_runner", "Used internally by `jetzig server` command.")) |jetzig_runner| {
|
||||||
if (jetzig_runner) {
|
if (jetzig_runner) {
|
||||||
@ -197,7 +203,10 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const root_path = b.build_root.path orelse try std.fs.cwd().realpathAlloc(b.allocator, ".");
|
const root_path = if (b.build_root.path) |build_root_path|
|
||||||
|
try std.fs.path.join(b.allocator, &.{ build_root_path, "." })
|
||||||
|
else
|
||||||
|
try std.fs.cwd().realpathAlloc(b.allocator, ".");
|
||||||
const templates_path: []const u8 = try std.fs.path.join(
|
const templates_path: []const u8 = try std.fs.path.join(
|
||||||
b.allocator,
|
b.allocator,
|
||||||
&[_][]const u8{ root_path, "src", "app" },
|
&[_][]const u8{ root_path, "src", "app" },
|
||||||
@ -220,6 +229,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.root_source_file = jetzig_dep.path("src/routes_file.zig"),
|
.root_source_file = jetzig_dep.path("src/routes_file.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = exe.use_llvm,
|
||||||
});
|
});
|
||||||
|
|
||||||
exe_routes_file.root_module.addImport("jetzig", jetzig_module);
|
exe_routes_file.root_module.addImport("jetzig", jetzig_module);
|
||||||
@ -247,6 +257,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.root_source_file = jetzig_dep.path("src/compile_static_routes.zig"),
|
.root_source_file = jetzig_dep.path("src/compile_static_routes.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = exe.use_llvm,
|
||||||
});
|
});
|
||||||
|
|
||||||
const main_module = b.createModule(.{ .root_source_file = b.path("src/main.zig") });
|
const main_module = b.createModule(.{ .root_source_file = b.path("src/main.zig") });
|
||||||
@ -355,6 +366,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.root_source_file = jetzig_dep.path("src/commands/routes.zig"),
|
.root_source_file = jetzig_dep.path("src/commands/routes.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = exe.use_llvm,
|
||||||
});
|
});
|
||||||
|
|
||||||
const auth_user_create_step = b.step("jetzig:auth:user:create", "List all routes in your app");
|
const auth_user_create_step = b.step("jetzig:auth:user:create", "List all routes in your app");
|
||||||
@ -363,6 +375,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.root_source_file = jetzig_dep.path("src/commands/auth.zig"),
|
.root_source_file = jetzig_dep.path("src/commands/auth.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = exe.use_llvm,
|
||||||
});
|
});
|
||||||
exe_auth.root_module.addImport("jetquery", jetquery_module);
|
exe_auth.root_module.addImport("jetquery", jetquery_module);
|
||||||
exe_auth.root_module.addImport("jetzig", jetzig_module);
|
exe_auth.root_module.addImport("jetzig", jetzig_module);
|
||||||
@ -384,6 +397,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
.root_source_file = jetzig_dep.path("src/commands/database.zig"),
|
.root_source_file = jetzig_dep.path("src/commands/database.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.use_llvm = exe.use_llvm,
|
||||||
});
|
});
|
||||||
exe_database.root_module.addImport("jetquery", jetquery_module);
|
exe_database.root_module.addImport("jetquery", jetquery_module);
|
||||||
exe_database.root_module.addImport("jetzig", jetzig_module);
|
exe_database.root_module.addImport("jetzig", jetzig_module);
|
||||||
@ -394,6 +408,9 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
|
|
||||||
registerDatabaseSteps(b, exe_database);
|
registerDatabaseSteps(b, exe_database);
|
||||||
|
|
||||||
|
const option_db_exe = b.option(bool, "database_exe", "option to install 'database' executable default is false") orelse false;
|
||||||
|
if (option_db_exe) b.installArtifact(exe_database);
|
||||||
|
|
||||||
exe_routes.root_module.addImport("jetzig", jetzig_module);
|
exe_routes.root_module.addImport("jetzig", jetzig_module);
|
||||||
exe_routes.root_module.addImport("routes", routes_module);
|
exe_routes.root_module.addImport("routes", routes_module);
|
||||||
exe_routes.root_module.addImport("app", exe.root_module);
|
exe_routes.root_module.addImport("app", exe.root_module);
|
||||||
@ -408,6 +425,8 @@ fn registerDatabaseSteps(b: *std.Build, exe_database: *std.Build.Step.Compile) v
|
|||||||
.{ "create", "Create a database for your Jetzig app." },
|
.{ "create", "Create a database for your Jetzig app." },
|
||||||
.{ "drop", "Drop your Jetzig app's database." },
|
.{ "drop", "Drop your Jetzig app's database." },
|
||||||
.{ "reflect", "Read your app's database and generate a JetQuery schema." },
|
.{ "reflect", "Read your app's database and generate a JetQuery schema." },
|
||||||
|
.{ "setup", "Create the database, run migrations, and generate schema." },
|
||||||
|
.{ "update", "Run migrations and generate schema." },
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (commands) |command| {
|
inline for (commands) |command| {
|
||||||
@ -428,7 +447,7 @@ fn generateMarkdownFragments(b: *std.Build) ![]const u8 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const stat = try file.stat();
|
const stat = try file.stat();
|
||||||
const source = try file.readToEndAllocOptions(b.allocator, @intCast(stat.size), null, @alignOf(u8), 0);
|
const source = try file.readToEndAllocOptions(b.allocator, @intCast(stat.size), null, .of(u8), 0);
|
||||||
if (try getMarkdownFragmentsSource(b.allocator, source)) |markdown_fragments_source| {
|
if (try getMarkdownFragmentsSource(b.allocator, source)) |markdown_fragments_source| {
|
||||||
return try std.fmt.allocPrint(b.allocator,
|
return try std.fmt.allocPrint(b.allocator,
|
||||||
\\const std = @import("std");
|
\\const std = @import("std");
|
||||||
@ -449,10 +468,10 @@ fn getMarkdownFragmentsSource(allocator: std.mem.Allocator, source: [:0]const u8
|
|||||||
for (ast.nodes.items(.tag), 0..) |tag, index| {
|
for (ast.nodes.items(.tag), 0..) |tag, index| {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
.simple_var_decl => {
|
.simple_var_decl => {
|
||||||
const decl = ast.simpleVarDecl(@intCast(index));
|
const decl = ast.simpleVarDecl(@enumFromInt(index));
|
||||||
const identifier = ast.tokenSlice(decl.ast.mut_token + 1);
|
const identifier = ast.tokenSlice(decl.ast.mut_token + 1);
|
||||||
if (std.mem.eql(u8, identifier, "markdown_fragments")) {
|
if (std.mem.eql(u8, identifier, "markdown_fragments")) {
|
||||||
return ast.getNodeSource(@intCast(index));
|
return ast.getNodeSource(@enumFromInt(index));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => continue,
|
else => continue,
|
||||||
|
|||||||
@ -1,42 +1,40 @@
|
|||||||
.{
|
.{
|
||||||
.name = "jetzig",
|
.name = .jetzig,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0x93ad8bfa2d209022,
|
||||||
|
.minimum_zig_version = "0.15.0-dev.355+206bd1ced",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
|
.jetcommon = .{
|
||||||
|
.url = "https://github.com/jetzig-framework/jetcommon/archive/fb4edc13759d87bfcd9b1f5fcefdf93f8c9c62dd.tar.gz",
|
||||||
|
.hash = "jetcommon-0.1.0-jPY_DS1HAAAP8xp5HSWB_ZY7m9JEYUmm8adQFrse0lwB",
|
||||||
|
},
|
||||||
.zmd = .{
|
.zmd = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmd/archive/d6c8aa9a9cde99674ccb096d8f94ed09cba8dab.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmd/archive/d6c8aa9a9cde99674ccb096d8f94ed09cba8dab.tar.gz",
|
||||||
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
||||||
},
|
},
|
||||||
.jetkv = .{
|
|
||||||
.url = "https://github.com/jetzig-framework/jetkv/archive/9d754e552e7569239a900ed9e0f313a0554ed2d3.tar.gz",
|
|
||||||
.hash = "122013f8596bc615990fd7771c833cab4d2959ecac8d05c4f6c973aa46624e43afea",
|
|
||||||
},
|
|
||||||
.args = .{
|
|
||||||
.url = "https://github.com/ikskuh/zig-args/archive/968258dc1b1230493d8f1677097c832a3d7e0bd8.tar.gz",
|
|
||||||
.hash = "1220bdedf1a993d852d8aebcd63922a8fb163fac37b9c6ff72d187b2847a4a3a4248",
|
|
||||||
},
|
|
||||||
.jetcommon = .{
|
|
||||||
.url = "https://github.com/jetzig-framework/jetcommon/archive/5be57d534b3d469f5570cd4b373b8d61032b1b8b.tar.gz",
|
|
||||||
.hash = "122079c6ceb28fa93163c2f95e2f175bb8f93f3075fa34af63045671ab7dd824e756",
|
|
||||||
},
|
|
||||||
.pg = .{
|
|
||||||
.url = "https://github.com/karlseguin/pg.zig/archive/0110cfdf387403a5a326115b5184861c4604d711.tar.gz",
|
|
||||||
.hash = "12205019ce2bc2e08c76352ea37a14600d412e5e0ecdd7ddd27b4e83a62f37d8ba94",
|
|
||||||
},
|
|
||||||
.httpz = .{
|
|
||||||
.url = "https://github.com/karlseguin/http.zig/archive/a691d731047e9a5a79d71ac594cb8f5fad1d0705.tar.gz",
|
|
||||||
.hash = "122072c92285c8c44055eb45058b834d1e7ecd46a5704d58a207103c39fb5922b8f5",
|
|
||||||
},
|
|
||||||
.smtp_client = .{
|
.smtp_client = .{
|
||||||
.url = "https://github.com/karlseguin/smtp_client.zig/archive/5163c66cc42cdd93176a6b1cad45f3db3a291a6a.tar.gz",
|
.url = "https://github.com/karlseguin/smtp_client.zig/archive/5163c66cc42cdd93176a6b1cad45f3db3a291a6a.tar.gz",
|
||||||
.hash = "1220a7807b5161550cb0cba772689c4872bfeee8305a26c3cd0e12a8ccde1d546910",
|
.hash = "smtp_client-0.0.1-AAAAAIJkAQCngHtRYVUMsMuncmicSHK_7ugwWibDzQ4S",
|
||||||
},
|
},
|
||||||
.jetquery = .{
|
.args = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetquery/archive/d4010cfd9ced2e7deb0f3a6cc64e2d32b8db95ba.tar.gz",
|
.url = "https://github.com/bobf/zig-args/archive/88cbade9a517a4014824f8f53f3c48c8a0b2ffe1.tar.gz",
|
||||||
.hash = "122069eeb0d43931e49f93419bdb5930ac3a6bc35d1e977738fe872ecaac8ff32aec",
|
.hash = "zig_args-0.0.0-jqtN6P_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH",
|
||||||
|
},
|
||||||
|
.jetkv = .{
|
||||||
|
.url = "https://github.com/jetzig-framework/jetkv/archive/5a94e3bac0a6e291efc9d6534beb2d311671ff17.tar.gz",
|
||||||
|
.hash = "jetkv-0.0.0-zCv0fmCGAgCyYqwHjk0P5KrYVRew1MJAtbtAcIO-WPpT",
|
||||||
},
|
},
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmpl/archive/b1dfca8eec73520af5b029016c5b5914da659b6d.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/c57fc9b83027e8c1459d9625c3509f59f0fb89f3.tar.gz",
|
||||||
.hash = "1220e70c218c89de219d4f9506a4ad69bd1b5257cd8c7cdc2ea823830e1d8b9dc4df",
|
.hash = "zmpl-0.0.1-SYFGBgdqAwDeA6xm4KAhpKoNrWs5CMQK6x447zhWclCs",
|
||||||
|
},
|
||||||
|
.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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
.{
|
.{
|
||||||
.name = "jetzig-cli",
|
.name = .jetzig_cli,
|
||||||
|
.fingerprint = 0x73894a3e0616c96a,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.minimum_zig_version = "0.12.0",
|
.minimum_zig_version = "0.12.0",
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.args = .{
|
.args = .{
|
||||||
.url = "https://github.com/ikskuh/zig-args/archive/968258dc1b1230493d8f1677097c832a3d7e0bd8.tar.gz",
|
.url = "https://github.com/bobf/zig-args/archive/88cbade9a517a4014824f8f53f3c48c8a0b2ffe1.tar.gz",
|
||||||
.hash = "1220bdedf1a993d852d8aebcd63922a8fb163fac37b9c6ff72d187b2847a4a3a4248",
|
.hash = "zig_args-0.0.0-jqtN6P_NAAC97fGpk9hS2K681jkiqPsWP6w3ucb_ctGH",
|
||||||
},
|
},
|
||||||
.jetquery = .{
|
.jetquery = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetquery/archive/d4010cfd9ced2e7deb0f3a6cc64e2d32b8db95ba.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetquery/archive/e1f969f2e3e0e1ad9cc30d56fde9739aa692fdc3.tar.gz",
|
||||||
.hash = "122069eeb0d43931e49f93419bdb5930ac3a6bc35d1e977738fe872ecaac8ff32aec",
|
.hash = "jetquery-0.0.0-TNf3zo2ABgBgcsIAvJ1Ud2B2zDzrBy9GQ31kKmTYZ7Ya",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
|||||||
@ -149,13 +149,13 @@ pub fn run(
|
|||||||
const tmpdir_real_path = try tmpdir.realpathAlloc(allocator, ".");
|
const tmpdir_real_path = try tmpdir.realpathAlloc(allocator, ".");
|
||||||
defer allocator.free(tmpdir_real_path);
|
defer allocator.free(tmpdir_real_path);
|
||||||
|
|
||||||
try util.runCommandInDir(allocator, tar_argv.items, .{ .path = tmpdir_real_path });
|
try util.runCommandInDir(allocator, tar_argv.items, .{ .path = tmpdir_real_path }, .{});
|
||||||
|
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
.windows => {},
|
.windows => {},
|
||||||
else => std.debug.print("Bundle `bundle.tar.gz` generated successfully.", .{}),
|
else => std.debug.print("Bundle `bundle.tar.gz` generated successfully.", .{}),
|
||||||
}
|
}
|
||||||
util.printSuccess();
|
util.printSuccess(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn locateMarkdownFiles(allocator: std.mem.Allocator, dir: std.fs.Dir, views_path: []const u8, paths: *std.ArrayList([]const u8)) !void {
|
fn locateMarkdownFiles(allocator: std.mem.Allocator, dir: std.fs.Dir, views_path: []const u8, paths: *std.ArrayList([]const u8)) !void {
|
||||||
@ -215,7 +215,7 @@ fn zig_build_install(allocator: std.mem.Allocator, path: []const u8, options: Op
|
|||||||
defer project_dir.close();
|
defer project_dir.close();
|
||||||
project_dir.makePath(".bundle") catch {};
|
project_dir.makePath(".bundle") catch {};
|
||||||
|
|
||||||
try util.runCommandInDir(allocator, install_argv.items, .{ .path = path });
|
try util.runCommandInDir(allocator, install_argv.items, .{ .path = path }, .{});
|
||||||
|
|
||||||
const install_bin_path = try std.fs.path.join(allocator, &[_][]const u8{ ".bundle", "bin" });
|
const install_bin_path = try std.fs.path.join(allocator, &[_][]const u8{ ".bundle", "bin" });
|
||||||
defer allocator.free(install_bin_path);
|
defer allocator.free(install_bin_path);
|
||||||
|
|||||||
@ -106,7 +106,7 @@ pub fn run(
|
|||||||
try copySourceFile(
|
try copySourceFile(
|
||||||
allocator,
|
allocator,
|
||||||
install_dir,
|
install_dir,
|
||||||
"demo/config/database.zig",
|
"demo/config/database_template.zig",
|
||||||
"config/database.zig",
|
"config/database.zig",
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@ -202,6 +202,7 @@ pub fn run(
|
|||||||
github_url,
|
github_url,
|
||||||
},
|
},
|
||||||
.{ .dir = install_dir },
|
.{ .dir = install_dir },
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Use arg or interactive prompt to do Git setup in net project, default to no.
|
// TODO: Use arg or interactive prompt to do Git setup in net project, default to no.
|
||||||
@ -260,7 +261,7 @@ fn copySourceFile(
|
|||||||
util.printFailure();
|
util.printFailure();
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
util.printSuccess();
|
util.printSuccess(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a file from Jetzig source code.
|
// Read a file from Jetzig source code.
|
||||||
@ -333,6 +334,7 @@ fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
|
|||||||
".",
|
".",
|
||||||
},
|
},
|
||||||
.{ .path = install_dir },
|
.{ .path = install_dir },
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try util.runCommandInDir(
|
try util.runCommandInDir(
|
||||||
@ -343,6 +345,7 @@ fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
|
|||||||
".",
|
".",
|
||||||
},
|
},
|
||||||
.{ .path = install_dir },
|
.{ .path = install_dir },
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try util.runCommandInDir(
|
try util.runCommandInDir(
|
||||||
@ -354,5 +357,6 @@ fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
|
|||||||
"Initialize Jetzig project",
|
"Initialize Jetzig project",
|
||||||
},
|
},
|
||||||
.{ .path = install_dir },
|
.{ .path = install_dir },
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,11 +78,7 @@ pub fn run(
|
|||||||
});
|
});
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
util.runCommandInDir(
|
util.runCommandInDir(allocator, argv.items, .{ .path = realpath }, .{}) catch {
|
||||||
allocator,
|
|
||||||
argv.items,
|
|
||||||
.{ .path = realpath },
|
|
||||||
) catch {
|
|
||||||
std.debug.print("Build failed, waiting for file change...\n", .{});
|
std.debug.print("Build failed, waiting for file change...\n", .{});
|
||||||
try awaitFileChange(allocator, cwd, &mtime);
|
try awaitFileChange(allocator, cwd, &mtime);
|
||||||
std.debug.print("Changes detected, restarting server...\n", .{});
|
std.debug.print("Changes detected, restarting server...\n", .{});
|
||||||
|
|||||||
@ -30,7 +30,7 @@ pub fn initDataModule(build: *std.Build) !*std.Build.Module {
|
|||||||
"demo/public/zmpl.png",
|
"demo/public/zmpl.png",
|
||||||
"demo/public/favicon.ico",
|
"demo/public/favicon.ico",
|
||||||
"demo/public/styles.css",
|
"demo/public/styles.css",
|
||||||
"demo/config/database.zig",
|
"demo/config/database_template.zig",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
116
cli/util.zig
116
cli/util.zig
@ -23,8 +23,8 @@ const icons = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Print a success confirmation.
|
/// Print a success confirmation.
|
||||||
pub fn printSuccess() void {
|
pub fn printSuccess(message: ?[]const u8) void {
|
||||||
std.debug.print(" " ++ icons.check ++ "\n", .{});
|
std.debug.print(" " ++ icons.check ++ " {s}\n", .{message orelse ""});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print a failure confirmation.
|
/// Print a failure confirmation.
|
||||||
@ -153,7 +153,7 @@ pub fn runCommandStreaming(allocator: std.mem.Allocator, install_path: []const u
|
|||||||
pub fn runCommand(allocator: std.mem.Allocator, argv: []const []const u8) !void {
|
pub fn runCommand(allocator: std.mem.Allocator, argv: []const []const u8) !void {
|
||||||
var dir = try detectJetzigProjectDir();
|
var dir = try detectJetzigProjectDir();
|
||||||
defer dir.close();
|
defer dir.close();
|
||||||
try runCommandInDir(allocator, argv, .{ .dir = dir });
|
try runCommandInDir(allocator, argv, .{ .dir = dir }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dir = union(enum) {
|
const Dir = union(enum) {
|
||||||
@ -161,34 +161,53 @@ const Dir = union(enum) {
|
|||||||
dir: std.fs.Dir,
|
dir: std.fs.Dir,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const RunOptions = struct {
|
||||||
|
output: enum { stream, capture } = .capture,
|
||||||
|
wait: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
/// Runs a command as a child process in the given directory and verifies successful exit code.
|
/// Runs a command as a child process in the given directory and verifies successful exit code.
|
||||||
pub fn runCommandInDir(allocator: std.mem.Allocator, argv: []const []const u8, dir: Dir) !void {
|
pub fn runCommandInDir(allocator: std.mem.Allocator, argv: []const []const u8, dir: Dir, options: RunOptions) !void {
|
||||||
const cwd_path = switch (dir) {
|
const cwd_path = switch (dir) {
|
||||||
.path => |capture| capture,
|
.path => |capture| capture,
|
||||||
.dir => |capture| try capture.realpathAlloc(allocator, "."),
|
.dir => |capture| try capture.realpathAlloc(allocator, "."),
|
||||||
};
|
};
|
||||||
defer if (dir == .dir) allocator.free(cwd_path);
|
defer if (dir == .dir) allocator.free(cwd_path);
|
||||||
|
|
||||||
const result = std.process.Child.run(.{
|
const output_behaviour: std.process.Child.StdIo = switch (options.output) {
|
||||||
.allocator = allocator,
|
.stream => .Inherit,
|
||||||
.argv = argv,
|
.capture => .Pipe,
|
||||||
.cwd = cwd_path,
|
};
|
||||||
}) catch |err| {
|
|
||||||
switch (err) {
|
var child = std.process.Child.init(argv, allocator);
|
||||||
error.FileNotFound => {
|
child.stdin_behavior = .Ignore;
|
||||||
printFailure();
|
child.stdout_behavior = output_behaviour;
|
||||||
const cmd_str = try std.mem.join(allocator, " ", argv);
|
child.stderr_behavior = output_behaviour;
|
||||||
defer allocator.free(cmd_str);
|
if (options.output == .stream) {
|
||||||
std.debug.print(
|
child.stdout = std.io.getStdOut();
|
||||||
\\Error: Could not execute command - executable '{s}' not found
|
child.stderr = std.io.getStdErr();
|
||||||
\\Command: {s}
|
}
|
||||||
\\Working directory: {s}
|
child.cwd = cwd_path;
|
||||||
\\
|
|
||||||
, .{ argv[0], cmd_str, cwd_path });
|
var stdout = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);
|
||||||
return error.JetzigCommandError;
|
var stderr = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0);
|
||||||
},
|
errdefer {
|
||||||
else => return err,
|
stdout.deinit(allocator);
|
||||||
}
|
stderr.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
try child.spawn();
|
||||||
|
switch (options.output) {
|
||||||
|
.capture => try collectOutput(child, allocator, &stdout, &stderr),
|
||||||
|
.stream => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.wait) return;
|
||||||
|
|
||||||
|
const result = std.process.Child.RunResult{
|
||||||
|
.term = try child.wait(),
|
||||||
|
.stdout = try stdout.toOwnedSlice(allocator),
|
||||||
|
.stderr = try stderr.toOwnedSlice(allocator),
|
||||||
};
|
};
|
||||||
defer allocator.free(result.stdout);
|
defer allocator.free(result.stdout);
|
||||||
defer allocator.free(result.stderr);
|
defer allocator.free(result.stderr);
|
||||||
@ -203,10 +222,57 @@ pub fn runCommandInDir(allocator: std.mem.Allocator, argv: []const []const u8, d
|
|||||||
}
|
}
|
||||||
return error.JetzigCommandError;
|
return error.JetzigCommandError;
|
||||||
} else {
|
} else {
|
||||||
printSuccess();
|
const message = try std.mem.join(allocator, " ", argv);
|
||||||
|
defer allocator.free(message);
|
||||||
|
printSuccess(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collectOutput(
|
||||||
|
child: std.process.Child,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
stdout: *std.ArrayListUnmanaged(u8),
|
||||||
|
stderr: *std.ArrayListUnmanaged(u8),
|
||||||
|
) !void {
|
||||||
|
const max_output_bytes = 50 * 1024;
|
||||||
|
std.debug.assert(child.stdout_behavior == .Pipe);
|
||||||
|
std.debug.assert(child.stderr_behavior == .Pipe);
|
||||||
|
var poller = std.io.poll(allocator, enum { stdout, stderr }, .{
|
||||||
|
.stdout = child.stdout.?,
|
||||||
|
.stderr = child.stderr.?,
|
||||||
|
});
|
||||||
|
defer poller.deinit();
|
||||||
|
|
||||||
|
std.debug.print("(working) .", .{});
|
||||||
|
|
||||||
|
while (try poller.poll()) {
|
||||||
|
if (poller.fifo(.stdout).count > max_output_bytes)
|
||||||
|
return error.StdoutStreamTooLong;
|
||||||
|
if (poller.fifo(.stderr).count > max_output_bytes)
|
||||||
|
return error.StderrStreamTooLong;
|
||||||
|
std.debug.print(".", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.print(" (done)\n", .{});
|
||||||
|
|
||||||
|
try writeFifoDataToArrayList(allocator, stdout, poller.fifo(.stdout));
|
||||||
|
try writeFifoDataToArrayList(allocator, stderr, poller.fifo(.stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrowed from `std.process.Child.writeFifoDataToArrayList` - non-public but needed in our
|
||||||
|
// modified `collectOutput`
|
||||||
|
fn writeFifoDataToArrayList(allocator: std.mem.Allocator, list: *std.ArrayListUnmanaged(u8), fifo: *std.io.PollFifo) !void {
|
||||||
|
if (fifo.head != 0) fifo.realign();
|
||||||
|
if (list.capacity == 0) {
|
||||||
|
list.* = .{
|
||||||
|
.items = fifo.buf[0..fifo.count],
|
||||||
|
.capacity = fifo.buf.len,
|
||||||
|
};
|
||||||
|
fifo.* = std.io.PollFifo.init(fifo.allocator);
|
||||||
|
} else {
|
||||||
|
try list.appendSlice(allocator, fifo.buf[0..fifo.count]);
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Generate a full GitHub URL for passing to `zig fetch`.
|
/// Generate a full GitHub URL for passing to `zig fetch`.
|
||||||
pub fn githubUrl(allocator: std.mem.Allocator) ![]const u8 {
|
pub fn githubUrl(allocator: std.mem.Allocator) ![]const u8 {
|
||||||
var client = std.http.Client{ .allocator = allocator };
|
var client = std.http.Client{ .allocator = allocator };
|
||||||
|
|||||||
1
demo/.gitignore
vendored
1
demo/.gitignore
vendored
@ -4,3 +4,4 @@ static/
|
|||||||
src/app/views/**/.*.zig
|
src/app/views/**/.*.zig
|
||||||
.DS_Store
|
.DS_Store
|
||||||
log/
|
log/
|
||||||
|
src/routes.zig
|
||||||
|
|||||||
44
demo/Makefile
Normal file
44
demo/Makefile
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Makefile
|
||||||
|
#
|
||||||
|
# Use this Makefile to set up a local Docker PostgreSQL database and run tests, or launch a local
|
||||||
|
# development database.
|
||||||
|
#
|
||||||
|
## Tests
|
||||||
|
#
|
||||||
|
# Set up test database and run application tests:
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# make test
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
## Development
|
||||||
|
#
|
||||||
|
# Set up development database and launch the demo Jetzig app:
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# make dev
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# TODO: Move all of this into `build.zig`
|
||||||
|
test_database=jetzig_demo_test
|
||||||
|
dev_database=jetzig_demo_dev
|
||||||
|
port=14173
|
||||||
|
|
||||||
|
export JETQUERY_HOSTNAME=localhost
|
||||||
|
export JETQUERY_USERNAME=postgres
|
||||||
|
export JETQUERY_PASSWORD=postgres
|
||||||
|
export JETQUERY_POOL_SIZE=2
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: env=JETQUERY_DATABASE=${test_database} JETQUERY_PORT=${port}
|
||||||
|
test:
|
||||||
|
docker compose up --detach --wait --renew-anon-volumes --remove-orphans --force-recreate
|
||||||
|
${env} zig build -Denvironment=testing jetzig:database:setup
|
||||||
|
${env} zig build -Denvironment=testing jetzig:test
|
||||||
|
|
||||||
|
.PHONY: dev
|
||||||
|
dev: env=JETQUERY_DATABASE=${dev_database} JETQUERY_PORT=${port}
|
||||||
|
dev:
|
||||||
|
docker compose up --detach --wait --renew-anon-volumes --remove-orphans
|
||||||
|
${env} zig build -Denvironment=testing jetzig:database:setup
|
||||||
|
${env} jetzig server
|
||||||
@ -1,7 +1,8 @@
|
|||||||
.{
|
.{
|
||||||
.name = "jetzig-demo",
|
.name = .jetzig_demo,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.minimum_zig_version = "0.12.0",
|
.minimum_zig_version = "0.12.0",
|
||||||
|
.fingerprint = 0x3877c19710a92a5c,
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.jetzig = .{
|
.jetzig = .{
|
||||||
.path = "../",
|
.path = "../",
|
||||||
|
|||||||
7
demo/compose.yml
Normal file
7
demo/compose.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
ports:
|
||||||
|
- 14173:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: 'postgres'
|
||||||
@ -1,48 +1,16 @@
|
|||||||
pub const database = .{
|
pub const database = .{
|
||||||
// Null adapter fails when a database call is invoked.
|
|
||||||
.development = .{
|
.development = .{
|
||||||
.adapter = .null,
|
.adapter = .postgresql,
|
||||||
|
.username = "postgres",
|
||||||
|
.password = "postgres",
|
||||||
|
.hostname = "localhost",
|
||||||
|
.database = "jetzig_demo_dev",
|
||||||
|
.port = 14173, // See `compose.yml`
|
||||||
},
|
},
|
||||||
|
// This configuration is used for CI
|
||||||
|
// in GitHub
|
||||||
.testing = .{
|
.testing = .{
|
||||||
.adapter = .null,
|
.adapter = .postgresql,
|
||||||
|
.database = "jetzig_demo_test",
|
||||||
},
|
},
|
||||||
.production = .{
|
|
||||||
.adapter = .null,
|
|
||||||
},
|
|
||||||
// PostgreSQL adapter configuration.
|
|
||||||
//
|
|
||||||
// All options except `adapter` can be configured using environment variables:
|
|
||||||
//
|
|
||||||
// * JETQUERY_HOSTNAME
|
|
||||||
// * JETQUERY_PORT
|
|
||||||
// * JETQUERY_USERNAME
|
|
||||||
// * JETQUERY_PASSWORD
|
|
||||||
// * JETQUERY_DATABASE
|
|
||||||
//
|
|
||||||
// .testing = .{
|
|
||||||
// .adapter = .postgresql,
|
|
||||||
// .hostname = "localhost",
|
|
||||||
// .port = 5432,
|
|
||||||
// .username = "postgres",
|
|
||||||
// .password = "password",
|
|
||||||
// .database = "myapp_testing",
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// .development = .{
|
|
||||||
// .adapter = .postgresql,
|
|
||||||
// .hostname = "localhost",
|
|
||||||
// .port = 5432,
|
|
||||||
// .username = "postgres",
|
|
||||||
// .password = "password",
|
|
||||||
// .database = "myapp_development",
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// .production = .{
|
|
||||||
// .adapter = .postgresql,
|
|
||||||
// .hostname = "localhost",
|
|
||||||
// .port = 5432,
|
|
||||||
// .username = "postgres",
|
|
||||||
// .password = "password",
|
|
||||||
// .database = "myapp_production",
|
|
||||||
// },
|
|
||||||
};
|
};
|
||||||
|
|||||||
48
demo/config/database_template.zig
Normal file
48
demo/config/database_template.zig
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
pub const database = .{
|
||||||
|
// Null adapter fails when a database call is invoked.
|
||||||
|
.development = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
.testing = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
.production = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
// PostgreSQL adapter configuration.
|
||||||
|
//
|
||||||
|
// All options except `adapter` can be configured using environment variables:
|
||||||
|
//
|
||||||
|
// * JETQUERY_HOSTNAME
|
||||||
|
// * JETQUERY_PORT
|
||||||
|
// * JETQUERY_USERNAME
|
||||||
|
// * JETQUERY_PASSWORD
|
||||||
|
// * JETQUERY_DATABASE
|
||||||
|
//
|
||||||
|
// .testing = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_testing",
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// .development = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_development",
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// .production = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_production",
|
||||||
|
// },
|
||||||
|
};
|
||||||
14
demo/src/app/database/Schema.zig
Normal file
14
demo/src/app/database/Schema.zig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const jetquery = @import("jetzig").jetquery;
|
||||||
|
|
||||||
|
pub const User = jetquery.Model(
|
||||||
|
@This(),
|
||||||
|
"users",
|
||||||
|
struct {
|
||||||
|
id: i32,
|
||||||
|
email: []const u8,
|
||||||
|
password_hash: []const u8,
|
||||||
|
created_at: jetquery.DateTime,
|
||||||
|
updated_at: jetquery.DateTime,
|
||||||
|
},
|
||||||
|
.{},
|
||||||
|
);
|
||||||
@ -1,9 +1,9 @@
|
|||||||
const jetquery = @import("jetquery");
|
const jetquery = @import("jetquery");
|
||||||
|
|
||||||
pub fn up(repo: *jetquery.Repo) !void {
|
pub fn up(repo: anytype) !void {
|
||||||
_ = repo;
|
_ = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn down(repo: *jetquery.Repo) !void {
|
pub fn down(repo: anytype) !void {
|
||||||
_ = repo;
|
_ = repo;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const jetquery = @import("jetquery");
|
||||||
|
const t = jetquery.schema.table;
|
||||||
|
|
||||||
|
pub fn up(repo: anytype) !void {
|
||||||
|
try repo.createTable(
|
||||||
|
"users",
|
||||||
|
&.{
|
||||||
|
t.primaryKey("id", .{}),
|
||||||
|
t.column("email", .string, .{ .unique = true, .index = true }),
|
||||||
|
t.column("password_hash", .string, .{}),
|
||||||
|
t.timestamps(.{}),
|
||||||
|
},
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn down(repo: anytype) !void {
|
||||||
|
try repo.dropTable("users", .{});
|
||||||
|
}
|
||||||
@ -7,4 +7,4 @@
|
|||||||
<input type="submit" value="Submit Spam" />
|
<input type="submit" value="Submit Spam" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div>Try clearing `_jetzig_session` cookie before clicking "Submit Spam"</div>
|
<div>Try clearing `_jetzig-session` cookie before clicking "Submit Spam"</div>
|
||||||
|
|||||||
3
demo/src/app/views/channels/index.zmpl
Normal file
3
demo/src/app/views/channels/index.zmpl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
<span>Content goes here</span>
|
||||||
|
</div>
|
||||||
61
demo/src/app/views/login.zig
Normal file
61
demo/src/app/views/login.zig
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const jetzig = @import("jetzig");
|
||||||
|
const auth = @import("jetzig").auth;
|
||||||
|
|
||||||
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
|
return request.render(.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||||
|
const Login = struct {
|
||||||
|
email: []const u8,
|
||||||
|
password: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = try request.expectParams(Login) orelse {
|
||||||
|
return request.fail(.forbidden);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lookup the user by email
|
||||||
|
const query = jetzig.database.Query(.User).findBy(
|
||||||
|
.{ .email = params.email },
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = try request.repo.execute(query) orelse {
|
||||||
|
return request.fail(.forbidden);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check that the password matches
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "post" {
|
||||||
|
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
|
||||||
|
defer app.deinit();
|
||||||
|
|
||||||
|
const hashed_pass = try auth.hashPassword(std.testing.allocator, "test");
|
||||||
|
defer std.testing.allocator.free(hashed_pass);
|
||||||
|
|
||||||
|
try jetzig.database.Query(.User).deleteAll().execute(app.repo);
|
||||||
|
try app.repo.insert(.User, .{
|
||||||
|
.id = 1,
|
||||||
|
.email = "test@test.com",
|
||||||
|
.password_hash = hashed_pass,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = try app.request(.POST, "/login", .{
|
||||||
|
.json = .{
|
||||||
|
.email = "test@test.com",
|
||||||
|
.password = "test",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try response.expectStatus(.found);
|
||||||
|
}
|
||||||
7
demo/src/app/views/login/index.zmpl
Normal file
7
demo/src/app/views/login/index.zmpl
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<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>
|
||||||
@ -5,8 +5,8 @@ const importedFunction = @import("../lib/example.zig").exampleFunction;
|
|||||||
|
|
||||||
pub const layout = "application";
|
pub const layout = "application";
|
||||||
|
|
||||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
var root = try data.object();
|
var root = try request.data(.object);
|
||||||
try root.put("message", "Welcome to Jetzig!");
|
try root.put("message", "Welcome to Jetzig!");
|
||||||
try root.put("custom_number", customFunction(100, 200, 300));
|
try root.put("custom_number", customFunction(100, 200, 300));
|
||||||
try root.put("imported_number", importedFunction(100, 200, 300));
|
try root.put("imported_number", importedFunction(100, 200, 300));
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<!-- Renders `src/app/views/root/_quotes.zmpl`: -->
|
<!-- Renders `src/app/views/root/_quotes.zmpl`: -->
|
||||||
<div>
|
<div>
|
||||||
@partial root/quotes(message: .message)
|
@partial root/quotes(message: $.message)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -87,7 +87,7 @@ pub const jetzig_options = struct {
|
|||||||
.path = "/",
|
.path = "/",
|
||||||
},
|
},
|
||||||
.production => .{
|
.production => .{
|
||||||
.same_site = true,
|
.same_site = .lax,
|
||||||
.secure = true,
|
.secure = true,
|
||||||
.http_only = true,
|
.http_only = true,
|
||||||
.path = "/",
|
.path = "/",
|
||||||
|
|||||||
@ -376,7 +376,7 @@ fn generateRoutesForView(self: *Routes, dir: std.fs.Dir, path: []const u8) !Rout
|
|||||||
path,
|
path,
|
||||||
@intCast(stat.size),
|
@intCast(stat.size),
|
||||||
null,
|
null,
|
||||||
@alignOf(u8),
|
.of(u8),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
defer self.allocator.free(source);
|
defer self.allocator.free(source);
|
||||||
@ -390,7 +390,7 @@ fn generateRoutesForView(self: *Routes, dir: std.fs.Dir, path: []const u8) !Rout
|
|||||||
for (self.ast.nodes.items(.tag), 0..) |tag, index| {
|
for (self.ast.nodes.items(.tag), 0..) |tag, index| {
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
.fn_proto_multi, .fn_proto_one, .fn_proto_simple => |function_tag| {
|
.fn_proto_multi, .fn_proto_one, .fn_proto_simple => |function_tag| {
|
||||||
var function = try self.parseFunction(function_tag, index, path, source);
|
var function = try self.parseFunction(function_tag, @enumFromInt(index), path, source);
|
||||||
if (function) |*capture| {
|
if (function) |*capture| {
|
||||||
if (capture.args.len == 0) {
|
if (capture.args.len == 0) {
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
@ -414,7 +414,7 @@ fn generateRoutesForView(self: *Routes, dir: std.fs.Dir, path: []const u8) !Rout
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.simple_var_decl => {
|
.simple_var_decl => {
|
||||||
const decl = self.ast.simpleVarDecl(asNodeIndex(index));
|
const decl = self.ast.simpleVarDecl(@enumFromInt(index));
|
||||||
if (self.isStaticParamsDecl(decl)) {
|
if (self.isStaticParamsDecl(decl)) {
|
||||||
self.data.reset();
|
self.data.reset();
|
||||||
const params = try self.data.root(.object);
|
const params = try self.data.root(.object);
|
||||||
@ -452,10 +452,11 @@ fn generateRoutesForView(self: *Routes, dir: std.fs.Dir, path: []const u8) !Rout
|
|||||||
|
|
||||||
// Parse the `pub const static_params` definition and into a `jetzig.data.Value`.
|
// Parse the `pub const static_params` definition and into a `jetzig.data.Value`.
|
||||||
fn parseStaticParamsDecl(self: *Routes, decl: std.zig.Ast.full.VarDecl, params: *jetzig.data.Value) !void {
|
fn parseStaticParamsDecl(self: *Routes, decl: std.zig.Ast.full.VarDecl, params: *jetzig.data.Value) !void {
|
||||||
const init_node = self.ast.nodes.items(.tag)[decl.ast.init_node];
|
const init_node = decl.ast.init_node.unwrap() orelse return;
|
||||||
switch (init_node) {
|
|
||||||
|
switch (self.ast.nodeTag(init_node)) {
|
||||||
.struct_init_dot_two, .struct_init_dot_two_comma => {
|
.struct_init_dot_two, .struct_init_dot_two_comma => {
|
||||||
try self.parseStruct(decl.ast.init_node, params);
|
try self.parseStruct(init_node, params);
|
||||||
},
|
},
|
||||||
else => return,
|
else => return,
|
||||||
}
|
}
|
||||||
@ -488,14 +489,14 @@ fn parseArray(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.
|
|||||||
|
|
||||||
const array = maybe_array.?;
|
const array = maybe_array.?;
|
||||||
|
|
||||||
const main_token = self.ast.nodes.items(.main_token)[node];
|
const main_token = self.ast.nodeMainToken(node);
|
||||||
const field_name = self.ast.tokenSlice(main_token - 3);
|
const field_name = self.ast.tokenSlice(main_token - 3);
|
||||||
|
|
||||||
const params_array = try self.data.array();
|
const params_array = try self.data.array();
|
||||||
try params.put(field_name, params_array);
|
try params.put(field_name, params_array);
|
||||||
|
|
||||||
for (array.ast.elements) |element| {
|
for (array.ast.elements) |element| {
|
||||||
const elem = self.ast.nodes.items(.tag)[element];
|
const elem = self.ast.nodeTag(element);
|
||||||
switch (elem) {
|
switch (elem) {
|
||||||
.struct_init_dot, .struct_init_dot_two, .struct_init_dot_two_comma => {
|
.struct_init_dot, .struct_init_dot_two, .struct_init_dot_two_comma => {
|
||||||
const route_params = try self.data.object();
|
const route_params = try self.data.object();
|
||||||
@ -508,20 +509,20 @@ fn parseArray(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.
|
|||||||
try self.parseField(element, route_params);
|
try self.parseField(element, route_params);
|
||||||
},
|
},
|
||||||
.string_literal => {
|
.string_literal => {
|
||||||
const string_token = self.ast.nodes.items(.main_token)[element];
|
const string_token = self.ast.nodeMainToken(element);
|
||||||
const string_value = self.ast.tokenSlice(string_token);
|
const string_value = self.ast.tokenSlice(string_token);
|
||||||
|
|
||||||
// Strip quotes: `"foo"` -> `foo`
|
// Strip quotes: `"foo"` -> `foo`
|
||||||
try params_array.append(string_value[1 .. string_value.len - 1]);
|
try params_array.append(string_value[1 .. string_value.len - 1]);
|
||||||
},
|
},
|
||||||
.number_literal => {
|
.number_literal => {
|
||||||
const number_token = self.ast.nodes.items(.main_token)[element];
|
const number_token = self.ast.nodeMainToken(element);
|
||||||
const number_value = self.ast.tokenSlice(number_token);
|
const number_value = self.ast.tokenSlice(number_token);
|
||||||
try params_array.append(try parseNumber(number_value, self.data));
|
try params_array.append(try parseNumber(number_value, self.data));
|
||||||
},
|
},
|
||||||
inline else => {
|
inline else => {
|
||||||
@setEvalBranchQuota(10_000);
|
@setEvalBranchQuota(10_000);
|
||||||
const tag = self.ast.nodes.items(.tag)[element];
|
const tag = self.ast.nodeTag(element);
|
||||||
std.debug.print("Unexpected token: {}\n", .{tag});
|
std.debug.print("Unexpected token: {}\n", .{tag});
|
||||||
return error.JetzigStaticParamsParseError;
|
return error.JetzigStaticParamsParseError;
|
||||||
},
|
},
|
||||||
@ -531,22 +532,21 @@ fn parseArray(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.
|
|||||||
|
|
||||||
// Parse the value of a param field (recursively when field is a struct/array)
|
// Parse the value of a param field (recursively when field is a struct/array)
|
||||||
fn parseField(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.Value) anyerror!void {
|
fn parseField(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.Value) anyerror!void {
|
||||||
const tag = self.ast.nodes.items(.tag)[node];
|
switch (self.ast.nodeTag(node)) {
|
||||||
switch (tag) {
|
|
||||||
// Route params, e.g. `.index = .{ ... }`
|
// Route params, e.g. `.index = .{ ... }`
|
||||||
.array_init_dot, .array_init_dot_two, .array_init_dot_comma, .array_init_dot_two_comma => {
|
.array_init_dot, .array_init_dot_two, .array_init_dot_comma, .array_init_dot_two_comma => {
|
||||||
try self.parseArray(node, params);
|
try self.parseArray(node, params);
|
||||||
},
|
},
|
||||||
.struct_init_dot, .struct_init_dot_two, .struct_init_dot_two_comma => {
|
.struct_init_dot, .struct_init_dot_two, .struct_init_dot_two_comma => {
|
||||||
const nested_params = try self.data.object();
|
const nested_params = try self.data.object();
|
||||||
const main_token = self.ast.nodes.items(.main_token)[node];
|
const main_token = self.ast.nodeMainToken(node);
|
||||||
const field_name = self.ast.tokenSlice(main_token - 3);
|
const field_name = self.ast.tokenSlice(main_token - 3);
|
||||||
try params.put(field_name, nested_params);
|
try params.put(field_name, nested_params);
|
||||||
try self.parseStruct(node, nested_params);
|
try self.parseStruct(node, nested_params);
|
||||||
},
|
},
|
||||||
// Individual param in a params struct, e.g. `.foo = "bar"`
|
// Individual param in a params struct, e.g. `.foo = "bar"`
|
||||||
.string_literal => {
|
.string_literal => {
|
||||||
const main_token = self.ast.nodes.items(.main_token)[node];
|
const main_token = self.ast.nodeMainToken(node);
|
||||||
const field_name = self.ast.tokenSlice(main_token - 2);
|
const field_name = self.ast.tokenSlice(main_token - 2);
|
||||||
const field_value = self.ast.tokenSlice(main_token);
|
const field_value = self.ast.tokenSlice(main_token);
|
||||||
|
|
||||||
@ -557,13 +557,13 @@ fn parseField(self: *Routes, node: std.zig.Ast.Node.Index, params: *jetzig.data.
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
.number_literal => {
|
.number_literal => {
|
||||||
const main_token = self.ast.nodes.items(.main_token)[node];
|
const main_token = self.ast.nodeMainToken(node);
|
||||||
const field_name = self.ast.tokenSlice(main_token - 2);
|
const field_name = self.ast.tokenSlice(main_token - 2);
|
||||||
const field_value = self.ast.tokenSlice(main_token);
|
const field_value = self.ast.tokenSlice(main_token);
|
||||||
|
|
||||||
try params.put(field_name, try parseNumber(field_value, self.data));
|
try params.put(field_name, try parseNumber(field_value, self.data));
|
||||||
},
|
},
|
||||||
else => {
|
else => |tag| {
|
||||||
std.debug.print("Unexpected token: {}\n", .{tag});
|
std.debug.print("Unexpected token: {}\n", .{tag});
|
||||||
return error.JetzigStaticParamsParseError;
|
return error.JetzigStaticParamsParseError;
|
||||||
},
|
},
|
||||||
@ -594,16 +594,16 @@ fn isStaticParamsDecl(self: *Routes, decl: std.zig.Ast.full.VarDecl) bool {
|
|||||||
fn parseFunction(
|
fn parseFunction(
|
||||||
self: *Routes,
|
self: *Routes,
|
||||||
function_type: std.zig.Ast.Node.Tag,
|
function_type: std.zig.Ast.Node.Tag,
|
||||||
index: usize,
|
index: std.zig.Ast.Node.Index,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
source: []const u8,
|
source: []const u8,
|
||||||
) !?Function {
|
) !?Function {
|
||||||
var buf: [1]std.zig.Ast.Node.Index = undefined;
|
var buf: [1]std.zig.Ast.Node.Index = undefined;
|
||||||
|
|
||||||
const fn_proto = switch (function_type) {
|
const fn_proto = switch (function_type) {
|
||||||
.fn_proto_multi => self.ast.fnProtoMulti(@as(u32, @intCast(index))),
|
.fn_proto_multi => self.ast.fnProtoMulti(index),
|
||||||
.fn_proto_one => self.ast.fnProtoOne(&buf, @as(u32, @intCast(index))),
|
.fn_proto_one => self.ast.fnProtoOne(&buf, index),
|
||||||
.fn_proto_simple => self.ast.fnProtoSimple(&buf, @as(u32, @intCast(index))),
|
.fn_proto_simple => self.ast.fnProtoSimple(&buf, index),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
if (fn_proto.name_token) |token| {
|
if (fn_proto.name_token) |token| {
|
||||||
@ -620,7 +620,7 @@ fn parseFunction(
|
|||||||
while (it.next()) |arg| {
|
while (it.next()) |arg| {
|
||||||
if (arg.name_token) |arg_token| {
|
if (arg.name_token) |arg_token| {
|
||||||
const arg_name = self.ast.tokenSlice(arg_token);
|
const arg_name = self.ast.tokenSlice(arg_token);
|
||||||
const node = self.ast.nodes.get(arg.type_expr);
|
const node = self.ast.nodes.get(@intFromEnum(arg.type_expr.?));
|
||||||
const type_name = try self.parseTypeExpr(node);
|
const type_name = try self.parseTypeExpr(node);
|
||||||
try args.append(.{ .name = arg_name, .type_name = type_name });
|
try args.append(.{ .name = arg_name, .type_name = type_name });
|
||||||
}
|
}
|
||||||
@ -666,10 +666,6 @@ fn parseTypeExpr(self: *Routes, node: std.zig.Ast.Node) ![]const u8 {
|
|||||||
return error.JetzigAstParserError;
|
return error.JetzigAstParserError;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn asNodeIndex(index: usize) std.zig.Ast.Node.Index {
|
|
||||||
return @as(std.zig.Ast.Node.Index, @intCast(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isActionFunctionName(name: []const u8) bool {
|
fn isActionFunctionName(name: []const u8) bool {
|
||||||
inline for (@typeInfo(jetzig.views.Route.Action).@"enum".fields) |field| {
|
inline for (@typeInfo(jetzig.views.Route.Action).@"enum".fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, name)) return true;
|
if (std.mem.eql(u8, field.name, name)) return true;
|
||||||
|
|||||||
@ -33,7 +33,7 @@ pub fn main() !void {
|
|||||||
const Repo = jetzig.jetquery.Repo(jetzig.database.adapter, Schema);
|
const Repo = jetzig.jetquery.Repo(jetzig.database.adapter, Schema);
|
||||||
var repo = try Repo.loadConfig(
|
var repo = try Repo.loadConfig(
|
||||||
allocator,
|
allocator,
|
||||||
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
|
@field(jetzig.jetquery.Environment, @tagName(jetzig.environment)),
|
||||||
.{ .env = try jetzig.database.repoEnv(env), .context = .cli },
|
.{ .env = try jetzig.database.repoEnv(env), .context = .cli },
|
||||||
);
|
);
|
||||||
defer repo.deinit();
|
defer repo.deinit();
|
||||||
@ -60,7 +60,7 @@ pub fn main() !void {
|
|||||||
|
|
||||||
const email = args[2];
|
const email = args[2];
|
||||||
|
|
||||||
try repo.insert(std.enums.nameCast(std.meta.DeclEnum(Schema), model), .{
|
try repo.insert(@field(std.meta.DeclEnum(Schema), model), .{
|
||||||
.email = email,
|
.email = email,
|
||||||
.password_hash = try hashPassword(allocator, password),
|
.password_hash = try hashPassword(allocator, password),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const production_drop_failure_message = "To drop a production database, " ++
|
|||||||
|
|
||||||
const environment = jetzig.build_options.environment;
|
const environment = jetzig.build_options.environment;
|
||||||
const config = @field(jetquery.config.database, @tagName(environment));
|
const config = @field(jetquery.config.database, @tagName(environment));
|
||||||
const Action = enum { migrate, rollback, create, drop, reflect };
|
const Action = enum { migrate, rollback, create, drop, reflect, setup, update };
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
@ -46,6 +46,8 @@ pub fn main() !void {
|
|||||||
.{ "create", .create },
|
.{ "create", .create },
|
||||||
.{ "drop", .drop },
|
.{ "drop", .drop },
|
||||||
.{ "reflect", .reflect },
|
.{ "reflect", .reflect },
|
||||||
|
.{ "setup", .setup },
|
||||||
|
.{ "update", .update },
|
||||||
});
|
});
|
||||||
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedArgument;
|
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedArgument;
|
||||||
|
|
||||||
@ -77,6 +79,25 @@ pub fn main() !void {
|
|||||||
defer repo.deinit();
|
defer repo.deinit();
|
||||||
try repo.createDatabase(database, .{});
|
try repo.createDatabase(database, .{});
|
||||||
},
|
},
|
||||||
|
.setup => {
|
||||||
|
{
|
||||||
|
var repo = try migrationsRepo(.create, allocator, repo_env);
|
||||||
|
defer repo.deinit();
|
||||||
|
try repo.createDatabase(database, .{});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var repo = try migrationsRepo(.update, allocator, repo_env);
|
||||||
|
defer repo.deinit();
|
||||||
|
try Migrate(config.adapter).init(&repo).migrate();
|
||||||
|
try reflectSchema(allocator, repo_env);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.update => {
|
||||||
|
var repo = try migrationsRepo(action, allocator, repo_env);
|
||||||
|
defer repo.deinit();
|
||||||
|
try Migrate(config.adapter).init(&repo).migrate();
|
||||||
|
try reflectSchema(allocator, repo_env);
|
||||||
|
},
|
||||||
.drop => {
|
.drop => {
|
||||||
if (environment == .production) {
|
if (environment == .production) {
|
||||||
const confirm = std.process.getEnvVarOwned(allocator, confirm_drop_env) catch |err| {
|
const confirm = std.process.getEnvVarOwned(allocator, confirm_drop_env) catch |err| {
|
||||||
@ -103,33 +124,7 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.reflect => {
|
.reflect => {
|
||||||
var cwd = try jetzig.util.detectJetzigProjectDir();
|
try reflectSchema(allocator, repo_env);
|
||||||
defer cwd.close();
|
|
||||||
|
|
||||||
const Repo = jetquery.Repo(config.adapter, Schema);
|
|
||||||
var repo = try Repo.loadConfig(
|
|
||||||
allocator,
|
|
||||||
std.enums.nameCast(jetquery.Environment, environment),
|
|
||||||
.{ .context = .migration, .env = repo_env },
|
|
||||||
);
|
|
||||||
const reflect = @import("jetquery_reflect").Reflect(config.adapter, Schema).init(
|
|
||||||
allocator,
|
|
||||||
&repo,
|
|
||||||
.{
|
|
||||||
.import_jetquery =
|
|
||||||
\\@import("jetzig").jetquery
|
|
||||||
,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const schema = try reflect.generateSchema();
|
|
||||||
const project_dir = try jetzig.util.detectJetzigProjectDir();
|
|
||||||
const project_dir_realpath = try project_dir.realpathAlloc(allocator, ".");
|
|
||||||
const path = try std.fs.path.join(
|
|
||||||
allocator,
|
|
||||||
&.{ project_dir_realpath, "src", "app", "database", "Schema.zig" },
|
|
||||||
);
|
|
||||||
try jetzig.util.createFile(path, schema);
|
|
||||||
std.log.info("Database schema written to `{s}`.", .{path});
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,15 +133,46 @@ const MigrationsRepo = jetquery.Repo(config.adapter, MigrateSchema);
|
|||||||
fn migrationsRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytype) !MigrationsRepo {
|
fn migrationsRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytype) !MigrationsRepo {
|
||||||
return try MigrationsRepo.loadConfig(
|
return try MigrationsRepo.loadConfig(
|
||||||
allocator,
|
allocator,
|
||||||
std.enums.nameCast(jetquery.Environment, environment),
|
@field(jetquery.Environment, @tagName(environment)),
|
||||||
.{
|
.{
|
||||||
.admin = switch (action) {
|
.admin = switch (action) {
|
||||||
.migrate, .rollback => false,
|
.migrate, .rollback, .update => false,
|
||||||
.create, .drop => true,
|
.create, .drop => true,
|
||||||
.reflect => unreachable, // We use a separate repo for schema reflection.
|
.reflect => unreachable, // We use a separate repo for schema reflection.
|
||||||
|
.setup => unreachable, // Setup uses `create` and then `update`
|
||||||
},
|
},
|
||||||
.context = .migration,
|
.context = .migration,
|
||||||
.env = repo_env,
|
.env = repo_env,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reflectSchema(allocator: std.mem.Allocator, repo_env: anytype) !void {
|
||||||
|
var cwd = try jetzig.util.detectJetzigProjectDir();
|
||||||
|
defer cwd.close();
|
||||||
|
|
||||||
|
const Repo = jetquery.Repo(config.adapter, Schema);
|
||||||
|
var repo = try Repo.loadConfig(
|
||||||
|
allocator,
|
||||||
|
@field(jetquery.Environment, @tagName(environment)),
|
||||||
|
.{ .context = .migration, .env = repo_env },
|
||||||
|
);
|
||||||
|
const reflect = @import("jetquery_reflect").Reflect(config.adapter, Schema).init(
|
||||||
|
allocator,
|
||||||
|
&repo,
|
||||||
|
.{
|
||||||
|
.import_jetquery =
|
||||||
|
\\@import("jetzig").jetquery
|
||||||
|
,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const schema = try reflect.generateSchema();
|
||||||
|
const project_dir = try jetzig.util.detectJetzigProjectDir();
|
||||||
|
const project_dir_realpath = try project_dir.realpathAlloc(allocator, ".");
|
||||||
|
const path = try std.fs.path.join(
|
||||||
|
allocator,
|
||||||
|
&.{ project_dir_realpath, "src", "app", "database", "Schema.zig" },
|
||||||
|
);
|
||||||
|
try jetzig.util.createFile(path, schema);
|
||||||
|
std.log.info("Database schema written to `{s}`.", .{path});
|
||||||
|
}
|
||||||
|
|||||||
@ -33,7 +33,10 @@ pub const Date = jetcommon.types.Date;
|
|||||||
pub const authenticity_token_name = config.get([]const u8, "authenticity_token_name");
|
pub const authenticity_token_name = config.get([]const u8, "authenticity_token_name");
|
||||||
|
|
||||||
pub const build_options = @import("build_options");
|
pub const build_options = @import("build_options");
|
||||||
pub const environment = std.enums.nameCast(Environment.EnvironmentName, build_options.environment);
|
pub const environment = @field(
|
||||||
|
Environment.EnvironmentName,
|
||||||
|
@tagName(build_options.environment),
|
||||||
|
);
|
||||||
|
|
||||||
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
/// The primary interface for a Jetzig application. Create an `App` in your application's
|
||||||
/// `src/main.zig` and call `start` to launch the application.
|
/// `src/main.zig` and call `start` to launch the application.
|
||||||
|
|||||||
@ -24,8 +24,7 @@ const AppOptions = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Starts an application. `routes` should be `@import("routes").routes`, a generated file
|
/// Starts an application. `routes` should be `@import("routes").routes`, a generated file
|
||||||
/// automatically created at build time. `templates` should be
|
/// automatically created at build time.
|
||||||
/// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time.
|
|
||||||
pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
||||||
defer self.env.deinit();
|
defer self.env.deinit();
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
defer mime_map.deinit();
|
defer mime_map.deinit();
|
||||||
try mime_map.build();
|
try mime_map.build();
|
||||||
|
|
||||||
const routes = try createRoutes(self.allocator, &routes_module.routes);
|
const routes = try createRoutes(self.allocator, if (@hasDecl(routes_module, "routes")) &routes_module.routes else &.{});
|
||||||
defer {
|
defer {
|
||||||
for (routes) |var_route| {
|
for (routes) |var_route| {
|
||||||
var_route.deinitParams();
|
var_route.deinitParams();
|
||||||
@ -63,7 +62,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
var log_thread = try std.Thread.spawn(
|
var log_thread = try std.Thread.spawn(
|
||||||
.{ .allocator = self.allocator },
|
.{ .allocator = self.allocator },
|
||||||
jetzig.loggers.LogQueue.Reader.publish,
|
jetzig.loggers.LogQueue.Reader.publish,
|
||||||
.{ &self.env.log_queue.reader, .{} },
|
.{ &self.env.log_queue.reader, jetzig.loggers.LogQueue.Reader.PublishOptions{} },
|
||||||
);
|
);
|
||||||
defer log_thread.join();
|
defer log_thread.join();
|
||||||
|
|
||||||
@ -87,8 +86,8 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
self.env,
|
self.env,
|
||||||
routes,
|
routes,
|
||||||
self.custom_routes.items,
|
self.custom_routes.items,
|
||||||
&routes_module.jobs,
|
if (@hasDecl(routes_module, "jobs")) &routes_module.jobs else &.{},
|
||||||
&routes_module.mailers,
|
if (@hasDecl(routes_module, "jobs")) &routes_module.mailers else &.{},
|
||||||
&mime_map,
|
&mime_map,
|
||||||
&store,
|
&store,
|
||||||
&job_queue,
|
&job_queue,
|
||||||
@ -107,8 +106,8 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
.vars = self.env.vars,
|
.vars = self.env.vars,
|
||||||
.environment = self.env.environment,
|
.environment = self.env.environment,
|
||||||
.routes = routes,
|
.routes = routes,
|
||||||
.jobs = &routes_module.jobs,
|
.jobs = if (@hasDecl(routes_module, "jobs")) &routes_module.jobs else &.{},
|
||||||
.mailers = &routes_module.mailers,
|
.mailers = if (@hasDecl(routes_module, "jobs")) &routes_module.mailers else &.{},
|
||||||
.store = &store,
|
.store = &store,
|
||||||
.cache = &cache,
|
.cache = &cache,
|
||||||
.repo = &repo,
|
.repo = &repo,
|
||||||
@ -132,7 +131,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
try server.logger.ERROR("Encountered error: {}\nExiting.\n", .{err});
|
try server.logger.ERROR("Encountered error at server launch: {}\nExiting.\n", .{err});
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,15 +196,15 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
|
|||||||
|
|
||||||
const env_file = std.fs.cwd().openFile(options.options.@"env-file", .{}) catch |err|
|
const env_file = std.fs.cwd().openFile(options.options.@"env-file", .{}) catch |err|
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.FileNotFound => null,
|
error.FileNotFound => null,
|
||||||
else => return err,
|
else => return err,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vars = try Vars.init(allocator, env_file);
|
const vars = try Vars.init(allocator, env_file);
|
||||||
|
|
||||||
var launch_logger = LaunchLogger{
|
var launch_logger = LaunchLogger{
|
||||||
.stdout = stdout,
|
.stdout = stdout.file,
|
||||||
.stderr = stderr,
|
.stderr = stderr.file,
|
||||||
.silent = env_options.silent,
|
.silent = env_options.silent,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -213,8 +213,8 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
|
|||||||
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
||||||
allocator,
|
allocator,
|
||||||
resolveLogLevel(options.options.@"log-level", jetzig.environment),
|
resolveLogLevel(options.options.@"log-level", jetzig.environment),
|
||||||
stdout,
|
stdout.file,
|
||||||
stderr,
|
stderr.file,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
.production => jetzig.loggers.Logger{
|
.production => jetzig.loggers.Logger{
|
||||||
@ -304,23 +304,26 @@ pub fn deinit(self: Environment) void {
|
|||||||
self.parent_allocator.destroy(self.arena);
|
self.parent_allocator.destroy(self.arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
|
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !jetzig.loggers.LogFile {
|
||||||
const path = switch (stream) {
|
const path = switch (stream) {
|
||||||
.stdout => options.log,
|
.stdout => options.log,
|
||||||
.stderr => options.@"log-error",
|
.stderr => options.@"log-error",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.eql(u8, path, "-")) return switch (stream) {
|
if (std.mem.eql(u8, path, "-")) return switch (stream) {
|
||||||
.stdout => std.io.getStdOut(),
|
.stdout => .{ .file = std.io.getStdOut(), .sync = false },
|
||||||
.stderr => if (std.mem.eql(u8, options.log, "-"))
|
.stderr => if (std.mem.eql(u8, options.log, "-"))
|
||||||
std.io.getStdErr()
|
.{ .file = std.io.getStdErr(), .sync = false }
|
||||||
else
|
else
|
||||||
try std.fs.createFileAbsolute(options.log, .{ .truncate = false }),
|
.{
|
||||||
|
.file = try std.fs.createFileAbsolute(options.log, .{ .truncate = false }),
|
||||||
|
.sync = true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const file = try std.fs.createFileAbsolute(path, .{ .truncate = false });
|
const file = try std.fs.createFileAbsolute(path, .{ .truncate = false });
|
||||||
try file.seekFromEnd(0);
|
try file.seekFromEnd(0);
|
||||||
return file;
|
return .{ .file = file, .sync = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSecret(
|
fn getSecret(
|
||||||
|
|||||||
@ -14,7 +14,7 @@ pub fn getUserId(comptime id_type: IdType, request: *jetzig.Request) !?switch (i
|
|||||||
} {
|
} {
|
||||||
const session = try request.session();
|
const session = try request.session();
|
||||||
|
|
||||||
return session.getT(std.enums.nameCast(jetzig.data.ValueType, id_type), "_jetzig_user_id");
|
return session.getT(@field(jetzig.data.ValueType, @tagName(id_type)), "_jetzig_user_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signIn(request: *jetzig.Request, user_id: anytype) !void {
|
pub fn signIn(request: *jetzig.Request, user_id: anytype) !void {
|
||||||
@ -42,13 +42,16 @@ pub fn verifyPassword(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
|
pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]const u8 {
|
||||||
const buf = try allocator.alloc(u8, 128);
|
var buf: [128]u8 = undefined;
|
||||||
return try std.crypto.pwhash.argon2.strHash(
|
const hash = try std.crypto.pwhash.argon2.strHash(
|
||||||
password,
|
password,
|
||||||
.{
|
.{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.params = .{ .t = 3, .m = 32, .p = 4 },
|
.params = .{ .t = 3, .m = 32, .p = 4 },
|
||||||
},
|
},
|
||||||
buf,
|
&buf,
|
||||||
);
|
);
|
||||||
|
const result = try allocator.alloc(u8, hash.len);
|
||||||
|
@memcpy(result, hash);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,10 @@ pub const mail = @import("mail.zig");
|
|||||||
pub const kv = @import("kv.zig");
|
pub const kv = @import("kv.zig");
|
||||||
pub const db = @import("database.zig");
|
pub const db = @import("database.zig");
|
||||||
pub const Environment = @import("Environment.zig");
|
pub const Environment = @import("Environment.zig");
|
||||||
pub const environment = std.enums.nameCast(Environment.EnvironmentName, build_options.environment);
|
pub const environment = @field(
|
||||||
|
Environment.EnvironmentName,
|
||||||
|
@tagName(build_options.environment),
|
||||||
|
);
|
||||||
pub const build_options = @import("build_options");
|
pub const build_options = @import("build_options");
|
||||||
|
|
||||||
const root = @import("root");
|
const root = @import("root");
|
||||||
|
|||||||
@ -2,9 +2,9 @@ const std = @import("std");
|
|||||||
|
|
||||||
const jetzig = @import("../jetzig.zig");
|
const jetzig = @import("../jetzig.zig");
|
||||||
|
|
||||||
pub const adapter = std.enums.nameCast(
|
pub const adapter = @field(
|
||||||
jetzig.jetquery.adapters.Name,
|
jetzig.jetquery.adapters.Name,
|
||||||
@field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter,
|
@tagName(@field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter),
|
||||||
);
|
);
|
||||||
|
|
||||||
pub const Schema = jetzig.config.get(type, "Schema");
|
pub const Schema = jetzig.config.get(type, "Schema");
|
||||||
@ -25,7 +25,7 @@ pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
|
|||||||
|
|
||||||
return try Repo.loadConfig(
|
return try Repo.loadConfig(
|
||||||
allocator,
|
allocator,
|
||||||
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
|
@field(jetzig.jetquery.Environment, @tagName(jetzig.environment)),
|
||||||
.{
|
.{
|
||||||
.eventCallback = Callback.callbackFn,
|
.eventCallback = Callback.callbackFn,
|
||||||
.lazy_connect = switch (jetzig.environment) {
|
.lazy_connect = switch (jetzig.environment) {
|
||||||
|
|||||||
@ -98,7 +98,7 @@ const SourceLine = struct { content: []const u8, line: usize };
|
|||||||
|
|
||||||
pub fn readLineFromFile(allocator: std.mem.Allocator, path: []const u8, line: usize) !SourceLine {
|
pub fn readLineFromFile(allocator: std.mem.Allocator, path: []const u8, line: usize) !SourceLine {
|
||||||
const file = try std.fs.openFileAbsolute(path, .{});
|
const file = try std.fs.openFileAbsolute(path, .{});
|
||||||
var buf: [std.mem.page_size]u8 = undefined;
|
var buf: [std.heap.pageSize()]u8 = undefined;
|
||||||
|
|
||||||
var count: usize = 1;
|
var count: usize = 1;
|
||||||
var cursor: usize = 0;
|
var cursor: usize = 0;
|
||||||
|
|||||||
@ -497,12 +497,14 @@ pub fn cookies(self: *Request) !*jetzig.http.Cookies {
|
|||||||
/// `jetzig.http.Session`.
|
/// `jetzig.http.Session`.
|
||||||
pub fn session(self: *Request) !*jetzig.http.Session {
|
pub fn session(self: *Request) !*jetzig.http.Session {
|
||||||
if (self._session) |capture| return capture;
|
if (self._session) |capture| return capture;
|
||||||
|
const cookie_name = self.server.env.vars.get("JETZIG_SESSION_COOKIE") orelse
|
||||||
|
jetzig.http.Session.default_cookie_name;
|
||||||
const local_session = try self.allocator.create(jetzig.http.Session);
|
const local_session = try self.allocator.create(jetzig.http.Session);
|
||||||
local_session.* = jetzig.http.Session.init(
|
local_session.* = jetzig.http.Session.init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
try self.cookies(),
|
try self.cookies(),
|
||||||
self.server.env.secret,
|
self.server.env.secret,
|
||||||
|
.{ .cookie_name = cookie_name },
|
||||||
);
|
);
|
||||||
local_session.parse() catch |err| {
|
local_session.parse() catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
@ -582,17 +584,17 @@ const RequestMail = struct {
|
|||||||
var mail_job = try self.request.job("__jetzig_mail");
|
var mail_job = try self.request.job("__jetzig_mail");
|
||||||
|
|
||||||
try mail_job.params.put("mailer_name", self.name);
|
try mail_job.params.put("mailer_name", self.name);
|
||||||
try mail_job.params.put("from", self.mail_params.from);
|
try mail_job.params.put("from", self.mail_params.get(.from));
|
||||||
|
|
||||||
var to_array = try mail_job.data.array();
|
var to_array = try mail_job.data.array();
|
||||||
if (self.mail_params.to) |to| {
|
if (self.mail_params.get(.to)) |to| {
|
||||||
for (to) |each| try to_array.append(each);
|
for (to) |each| try to_array.append(.{ .email = each.email, .name = each.name });
|
||||||
}
|
}
|
||||||
try mail_job.params.put("to", to_array);
|
try mail_job.params.put("to", to_array);
|
||||||
|
|
||||||
try mail_job.params.put("subject", self.mail_params.subject);
|
try mail_job.params.put("subject", self.mail_params.get(.subject));
|
||||||
try mail_job.params.put("html", self.mail_params.html);
|
try mail_job.params.put("html", self.mail_params.get(.html));
|
||||||
try mail_job.params.put("text", self.mail_params.text);
|
try mail_job.params.put("text", self.mail_params.get(.text));
|
||||||
|
|
||||||
if (self.request.response_data.value) |value| try mail_job.params.put(
|
if (self.request.response_data.value) |value| try mail_job.params.put(
|
||||||
"params",
|
"params",
|
||||||
|
|||||||
@ -110,6 +110,8 @@ pub fn listen(self: *Server) !void {
|
|||||||
|
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
|
|
||||||
|
try jetzig.http.middleware.afterLaunch(self);
|
||||||
|
|
||||||
return try httpz_server.listen();
|
return try httpz_server.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +153,23 @@ pub fn processNextRequest(
|
|||||||
try request.process();
|
try request.process();
|
||||||
|
|
||||||
var middleware_data = try jetzig.http.middleware.afterRequest(&request);
|
var middleware_data = try jetzig.http.middleware.afterRequest(&request);
|
||||||
|
if (try maybeMiddlewareRender(&request, &response)) {
|
||||||
|
try self.logger.logRequest(&request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.renderResponse(&request);
|
||||||
|
try request.response.headers.append("Content-Type", response.content_type);
|
||||||
|
|
||||||
|
try jetzig.http.middleware.beforeResponse(&middleware_data, &request);
|
||||||
|
try request.respond();
|
||||||
|
try jetzig.http.middleware.afterResponse(&middleware_data, &request);
|
||||||
|
jetzig.http.middleware.deinit(&middleware_data, &request);
|
||||||
|
|
||||||
|
try self.logger.logRequest(&request);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybeMiddlewareRender(request: *jetzig.http.Request, response: *const jetzig.http.Response) !bool {
|
||||||
if (request.middleware_rendered) |_| {
|
if (request.middleware_rendered) |_| {
|
||||||
// Request processing ends when a middleware renders or redirects.
|
// Request processing ends when a middleware renders or redirects.
|
||||||
if (request.redirect_state) |state| {
|
if (request.redirect_state) |state| {
|
||||||
@ -162,17 +180,8 @@ pub fn processNextRequest(
|
|||||||
}
|
}
|
||||||
try request.response.headers.append("Content-Type", response.content_type);
|
try request.response.headers.append("Content-Type", response.content_type);
|
||||||
try request.respond();
|
try request.respond();
|
||||||
} else {
|
return true;
|
||||||
try self.renderResponse(&request);
|
} else return false;
|
||||||
try request.response.headers.append("Content-Type", response.content_type);
|
|
||||||
|
|
||||||
try jetzig.http.middleware.beforeResponse(&middleware_data, &request);
|
|
||||||
try request.respond();
|
|
||||||
try jetzig.http.middleware.afterResponse(&middleware_data, &request);
|
|
||||||
jetzig.http.middleware.deinit(&middleware_data, &request);
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.logger.logRequest(&request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
|
fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
|
||||||
|
|||||||
@ -2,12 +2,12 @@ const std = @import("std");
|
|||||||
|
|
||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const cookie_name = "_jetzig-session";
|
|
||||||
pub const Cipher = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
|
pub const Cipher = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
encryption_key: []const u8,
|
encryption_key: []const u8,
|
||||||
cookies: *jetzig.http.Cookies,
|
cookies: *jetzig.http.Cookies,
|
||||||
|
cookie_name: []const u8,
|
||||||
|
|
||||||
initialized: bool = false,
|
initialized: bool = false,
|
||||||
data: jetzig.data.Data,
|
data: jetzig.data.Data,
|
||||||
@ -15,22 +15,30 @@ state: enum { parsed, pending } = .pending,
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const default_cookie_name = "_jetzig-session";
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
cookie_name: []const u8 = default_cookie_name,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
cookies: *jetzig.http.Cookies,
|
cookies: *jetzig.http.Cookies,
|
||||||
encryption_key: []const u8,
|
encryption_key: []const u8,
|
||||||
|
options: Options,
|
||||||
) Self {
|
) Self {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.data = jetzig.data.Data.init(allocator),
|
.data = jetzig.data.Data.init(allocator),
|
||||||
.cookies = cookies,
|
.cookies = cookies,
|
||||||
|
.cookie_name = options.cookie_name,
|
||||||
.encryption_key = encryption_key,
|
.encryption_key = encryption_key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse session cookie.
|
/// Parse session cookie.
|
||||||
pub fn parse(self: *Self) !void {
|
pub fn parse(self: *Self) !void {
|
||||||
if (self.cookies.get(cookie_name)) |cookie| {
|
if (self.cookies.get(self.cookie_name)) |cookie| {
|
||||||
try self.parseSessionCookie(cookie.value);
|
try self.parseSessionCookie(cookie.value);
|
||||||
} else {
|
} else {
|
||||||
try self.reset();
|
try self.reset();
|
||||||
@ -111,7 +119,7 @@ fn save(self: *Self) !void {
|
|||||||
defer self.allocator.free(encrypted);
|
defer self.allocator.free(encrypted);
|
||||||
const encoded = try jetzig.util.base64Encode(self.allocator, encrypted);
|
const encoded = try jetzig.util.base64Encode(self.allocator, encrypted);
|
||||||
defer self.allocator.free(encoded);
|
defer self.allocator.free(encoded);
|
||||||
try self.cookies.put(.{ .name = cookie_name, .value = encoded });
|
try self.cookies.put(.{ .name = self.cookie_name, .value = encoded });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
|
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
|
||||||
@ -180,7 +188,7 @@ test "put and get session key/value" {
|
|||||||
try cookies.parse();
|
try cookies.parse();
|
||||||
|
|
||||||
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
||||||
var session = Self.init(allocator, &cookies, &secret);
|
var session = Self.init(allocator, &cookies, &secret, .{});
|
||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
var data = jetzig.data.Data.init(allocator);
|
var data = jetzig.data.Data.init(allocator);
|
||||||
@ -199,7 +207,7 @@ test "remove session key/value" {
|
|||||||
try cookies.parse();
|
try cookies.parse();
|
||||||
|
|
||||||
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
||||||
var session = Self.init(allocator, &cookies, &secret);
|
var session = Self.init(allocator, &cookies, &secret, .{});
|
||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
var data = jetzig.data.Data.init(allocator);
|
var data = jetzig.data.Data.init(allocator);
|
||||||
@ -224,7 +232,7 @@ test "get value from parsed/decrypted cookie" {
|
|||||||
try cookies.parse();
|
try cookies.parse();
|
||||||
|
|
||||||
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
||||||
var session = Self.init(allocator, &cookies, &secret);
|
var session = Self.init(allocator, &cookies, &secret, .{});
|
||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
try session.parse();
|
try session.parse();
|
||||||
@ -233,17 +241,32 @@ test "get value from parsed/decrypted cookie" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "invalid cookie value - too short" {
|
test "invalid cookie value - too short" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
var cookies = jetzig.http.Cookies.init(allocator, "_jetzig-session=abc");
|
||||||
|
defer cookies.deinit();
|
||||||
|
try cookies.parse();
|
||||||
|
|
||||||
|
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
||||||
|
var session = Self.init(allocator, &cookies, &secret, .{});
|
||||||
|
defer session.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectError(error.JetzigInvalidSessionCookie, session.parse());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "custom session cookie name" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
var cookies = jetzig.http.Cookies.init(
|
var cookies = jetzig.http.Cookies.init(
|
||||||
allocator,
|
allocator,
|
||||||
"_jetzig-session=abc",
|
"custom-cookie-name=fPCFwZHvPDT-XCVcsQUSspDLchS3tRuJDqPpB2v3127VXpRP_bPcPLgpHK6RiVkfcP1bMtU",
|
||||||
);
|
);
|
||||||
defer cookies.deinit();
|
defer cookies.deinit();
|
||||||
try cookies.parse();
|
try cookies.parse();
|
||||||
|
|
||||||
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
|
||||||
var session = Self.init(allocator, &cookies, &secret);
|
var session = Self.init(allocator, &cookies, &secret, .{ .cookie_name = "custom-cookie-name" });
|
||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
try std.testing.expectError(error.JetzigInvalidSessionCookie, session.parse());
|
try session.parse();
|
||||||
|
var value = (session.get("foo")).?;
|
||||||
|
try std.testing.expectEqualStrings("bar", try value.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,14 @@ pub fn Type(comptime name: MiddlewareEnum()) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn afterLaunch(server: *jetzig.http.Server) !void {
|
||||||
|
inline for (middlewares) |middleware| {
|
||||||
|
if (comptime @hasDecl(middleware, "afterLaunch")) {
|
||||||
|
try middleware.afterLaunch(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData {
|
pub fn afterRequest(request: *jetzig.http.Request) !MiddlewareData {
|
||||||
var middleware_data = MiddlewareData.init(0) catch unreachable;
|
var middleware_data = MiddlewareData.init(0) catch unreachable;
|
||||||
|
|
||||||
|
|||||||
@ -41,9 +41,9 @@ fn jetKVOptions(options: KVOptions) jetzig.jetkv.Options {
|
|||||||
.port = options.valkey_options.port,
|
.port = options.valkey_options.port,
|
||||||
.connect_timeout = options.valkey_options.connect_timeout * std.time.ms_per_s,
|
.connect_timeout = options.valkey_options.connect_timeout * std.time.ms_per_s,
|
||||||
.read_timeout = options.valkey_options.read_timeout * std.time.ms_per_s,
|
.read_timeout = options.valkey_options.read_timeout * std.time.ms_per_s,
|
||||||
.connect = std.enums.nameCast(
|
.connect = @field(
|
||||||
jetzig.jetkv.ValkeyBackendOptions.ConnectMode,
|
jetzig.jetkv.ValkeyBackendOptions.ConnectMode,
|
||||||
options.valkey_options.connect,
|
@tagName(options.valkey_options.connect),
|
||||||
),
|
),
|
||||||
.buffer_size = options.valkey_options.buffer_size,
|
.buffer_size = options.valkey_options.buffer_size,
|
||||||
.pool_size = options.valkey_options.pool_size,
|
.pool_size = options.valkey_options.pool_size,
|
||||||
|
|||||||
@ -12,6 +12,11 @@ pub const NullLogger = @import("loggers/NullLogger.zig");
|
|||||||
|
|
||||||
pub const LogQueue = @import("loggers/LogQueue.zig");
|
pub const LogQueue = @import("loggers/LogQueue.zig");
|
||||||
|
|
||||||
|
pub const LogFile = struct {
|
||||||
|
file: std.fs.File,
|
||||||
|
sync: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
|
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
|
||||||
pub const LogFormat = enum { development, production, json, null };
|
pub const LogFormat = enum { development, production, json, null };
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,15 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
const buffer_size = jetzig.config.get(usize, "log_message_buffer_len");
|
const buffer_size = jetzig.config.get(usize, "log_message_buffer_len");
|
||||||
const max_pool_len = jetzig.config.get(usize, "max_log_pool_len");
|
const max_pool_len = jetzig.config.get(usize, "max_log_pool_len");
|
||||||
|
|
||||||
const List = std.DoublyLinkedList(Event);
|
const List = std.DoublyLinkedList;
|
||||||
|
const ListNode = struct {
|
||||||
|
event: Event,
|
||||||
|
node: std.DoublyLinkedList.Node = .{},
|
||||||
|
};
|
||||||
const Buffer = [buffer_size]u8;
|
const Buffer = [buffer_size]u8;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
node_allocator: std.heap.MemoryPool(List.Node),
|
node_allocator: std.heap.MemoryPool(ListNode),
|
||||||
buffer_allocator: std.heap.MemoryPool(Buffer),
|
buffer_allocator: std.heap.MemoryPool(Buffer),
|
||||||
list: List,
|
list: List,
|
||||||
read_write_mutex: std.Thread.Mutex,
|
read_write_mutex: std.Thread.Mutex,
|
||||||
@ -18,7 +22,7 @@ condition: std.Thread.Condition,
|
|||||||
condition_mutex: std.Thread.Mutex,
|
condition_mutex: std.Thread.Mutex,
|
||||||
writer: Writer = undefined,
|
writer: Writer = undefined,
|
||||||
reader: Reader = undefined,
|
reader: Reader = undefined,
|
||||||
node_pool: std.ArrayList(*List.Node),
|
node_pool: std.ArrayList(*ListNode),
|
||||||
buffer_pool: std.ArrayList(*Buffer),
|
buffer_pool: std.ArrayList(*Buffer),
|
||||||
position: usize,
|
position: usize,
|
||||||
stdout_is_tty: bool = undefined,
|
stdout_is_tty: bool = undefined,
|
||||||
@ -42,13 +46,13 @@ const Event = struct {
|
|||||||
pub fn init(allocator: std.mem.Allocator) LogQueue {
|
pub fn init(allocator: std.mem.Allocator) LogQueue {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.node_allocator = initPool(allocator, List.Node),
|
.node_allocator = initPool(allocator, ListNode),
|
||||||
.buffer_allocator = initPool(allocator, Buffer),
|
.buffer_allocator = initPool(allocator, Buffer),
|
||||||
.list = List{},
|
.list = List{},
|
||||||
.condition = std.Thread.Condition{},
|
.condition = std.Thread.Condition{},
|
||||||
.condition_mutex = std.Thread.Mutex{},
|
.condition_mutex = std.Thread.Mutex{},
|
||||||
.read_write_mutex = std.Thread.Mutex{},
|
.read_write_mutex = std.Thread.Mutex{},
|
||||||
.node_pool = std.ArrayList(*List.Node).init(allocator),
|
.node_pool = std.ArrayList(*ListNode).init(allocator),
|
||||||
.buffer_pool = std.ArrayList(*Buffer).init(allocator),
|
.buffer_pool = std.ArrayList(*Buffer).init(allocator),
|
||||||
.position = 0,
|
.position = 0,
|
||||||
};
|
};
|
||||||
@ -66,7 +70,11 @@ pub fn deinit(self: *LogQueue) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the stdout and stderr outputs. Must be called before `print`.
|
/// Set the stdout and stderr outputs. Must be called before `print`.
|
||||||
pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.File) !void {
|
pub fn setFiles(
|
||||||
|
self: *LogQueue,
|
||||||
|
stdout_file: jetzig.loggers.LogFile,
|
||||||
|
stderr_file: jetzig.loggers.LogFile,
|
||||||
|
) !void {
|
||||||
self.writer = Writer{
|
self.writer = Writer{
|
||||||
.queue = self,
|
.queue = self,
|
||||||
.mutex = std.Thread.Mutex{},
|
.mutex = std.Thread.Mutex{},
|
||||||
@ -76,11 +84,11 @@ pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.F
|
|||||||
.stderr_file = stderr_file,
|
.stderr_file = stderr_file,
|
||||||
.queue = self,
|
.queue = self,
|
||||||
};
|
};
|
||||||
self.stdout_is_tty = stdout_file.isTty();
|
self.stdout_is_tty = stdout_file.file.isTty();
|
||||||
self.stderr_is_tty = stderr_file.isTty();
|
self.stderr_is_tty = stderr_file.file.isTty();
|
||||||
|
|
||||||
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color;
|
self.stdout_colorize = std.io.tty.detectConfig(stdout_file.file) != .no_color;
|
||||||
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color;
|
self.stderr_colorize = std.io.tty.detectConfig(stderr_file.file) != .no_color;
|
||||||
|
|
||||||
self.state = .ready;
|
self.state = .ready;
|
||||||
}
|
}
|
||||||
@ -143,17 +151,21 @@ pub const Writer = struct {
|
|||||||
/// Reader for `LogQueue`. Reads log events from the queue and writes them to the designated
|
/// Reader for `LogQueue`. Reads log events from the queue and writes them to the designated
|
||||||
/// target (stdout or stderr).
|
/// target (stdout or stderr).
|
||||||
pub const Reader = struct {
|
pub const Reader = struct {
|
||||||
stdout_file: std.fs.File,
|
stdout_file: jetzig.loggers.LogFile,
|
||||||
stderr_file: std.fs.File,
|
stderr_file: jetzig.loggers.LogFile,
|
||||||
queue: *LogQueue,
|
queue: *LogQueue,
|
||||||
|
|
||||||
|
pub const PublishOptions = struct {
|
||||||
|
oneshot: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
/// Publish log events from the queue. Invoke from a dedicated thread. Sleeps when log queue
|
/// Publish log events from the queue. Invoke from a dedicated thread. Sleeps when log queue
|
||||||
/// is empty, wakes up when a new event is published.
|
/// is empty, wakes up when a new event is published.
|
||||||
pub fn publish(self: *Reader, options: struct { oneshot: bool = false }) !void {
|
pub fn publish(self: *Reader, options: PublishOptions) !void {
|
||||||
std.debug.assert(self.queue.state == .ready);
|
std.debug.assert(self.queue.state == .ready);
|
||||||
|
|
||||||
const stdout_writer = self.stdout_file.writer();
|
const stdout_writer = self.stdout_file.file.writer();
|
||||||
const stderr_writer = self.stderr_file.writer();
|
const stderr_writer = self.stderr_file.file.writer();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
self.queue.condition_mutex.lock();
|
self.queue.condition_mutex.lock();
|
||||||
@ -173,13 +185,13 @@ pub const Reader = struct {
|
|||||||
.stdout => {
|
.stdout => {
|
||||||
stdout_written = true;
|
stdout_written = true;
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
file = self.stdout_file;
|
file = self.stdout_file.file;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.stderr => {
|
.stderr => {
|
||||||
stderr_written = true;
|
stderr_written = true;
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
file = self.stderr_file;
|
file = self.stderr_file.file;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -212,8 +224,8 @@ pub const Reader = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout_written and !self.queue.stdout_is_tty) try self.stdout_file.sync();
|
if (stdout_written and self.stdout_file.sync) try self.stdout_file.file.sync();
|
||||||
if (stderr_written and !self.queue.stderr_is_tty) try self.stderr_file.sync();
|
if (stderr_written and self.stderr_file.sync) try self.stderr_file.file.sync();
|
||||||
|
|
||||||
if (options.oneshot) break;
|
if (options.oneshot) break;
|
||||||
}
|
}
|
||||||
@ -233,8 +245,8 @@ fn append(self: *LogQueue, event: Event) !void {
|
|||||||
|
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
|
|
||||||
node.* = .{ .data = event };
|
node.* = .{ .event = event };
|
||||||
self.list.append(node);
|
self.list.append(&node.node);
|
||||||
|
|
||||||
self.condition.signal();
|
self.condition.signal();
|
||||||
}
|
}
|
||||||
@ -245,16 +257,17 @@ fn popFirst(self: *LogQueue) !?Event {
|
|||||||
defer self.read_write_mutex.unlock();
|
defer self.read_write_mutex.unlock();
|
||||||
|
|
||||||
if (self.list.popFirst()) |node| {
|
if (self.list.popFirst()) |node| {
|
||||||
const value = node.data;
|
const list_node: *ListNode = @fieldParentPtr("node", node);
|
||||||
|
const value = list_node.event;
|
||||||
self.position -= 1;
|
self.position -= 1;
|
||||||
if (self.position < self.node_pool.items.len) {
|
if (self.position < self.node_pool.items.len) {
|
||||||
self.node_pool.items[self.position] = node;
|
self.node_pool.items[self.position] = list_node;
|
||||||
} else {
|
} else {
|
||||||
if (self.node_pool.items.len >= max_pool_len) {
|
if (self.node_pool.items.len >= max_pool_len) {
|
||||||
self.node_allocator.destroy(node);
|
self.node_allocator.destroy(list_node);
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
} else {
|
} else {
|
||||||
try self.node_pool.append(node);
|
try self.node_pool.append(list_node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@ -280,7 +293,7 @@ test "print to stdout and stderr" {
|
|||||||
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
|
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
|
||||||
defer stderr.close();
|
defer stderr.close();
|
||||||
|
|
||||||
try log_queue.setFiles(stdout, stderr);
|
try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
|
||||||
try log_queue.print("foo {s}\n", .{"bar"}, .stdout);
|
try log_queue.print("foo {s}\n", .{"bar"}, .stdout);
|
||||||
try log_queue.print("baz {s}\n", .{"qux"}, .stderr);
|
try log_queue.print("baz {s}\n", .{"qux"}, .stderr);
|
||||||
try log_queue.print("quux {s}\n", .{"corge"}, .stdout);
|
try log_queue.print("quux {s}\n", .{"corge"}, .stdout);
|
||||||
@ -324,7 +337,7 @@ test "long messages" {
|
|||||||
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
|
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
|
||||||
defer stderr.close();
|
defer stderr.close();
|
||||||
|
|
||||||
try log_queue.setFiles(stdout, stderr);
|
try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
|
||||||
try log_queue.print("foo" ** buffer_size, .{}, .stdout);
|
try log_queue.print("foo" ** buffer_size, .{}, .stdout);
|
||||||
|
|
||||||
try log_queue.reader.publish(.{ .oneshot = true });
|
try log_queue.reader.publish(.{ .oneshot = true });
|
||||||
|
|||||||
@ -46,8 +46,11 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
|||||||
|
|
||||||
if (env.environment == .development and !jetzig.config.get(bool, "force_development_email_delivery")) {
|
if (env.environment == .development and !jetzig.config.get(bool, "force_development_email_delivery")) {
|
||||||
try env.logger.INFO(
|
try env.logger.INFO(
|
||||||
"Skipping mail delivery in development environment:\n{s}",
|
\\Skipping mail delivery in development environment:
|
||||||
.{try mail.generateData()},
|
\\To: {?s}
|
||||||
|
\\{s}
|
||||||
|
,
|
||||||
|
.{ mail.params.get(.to), try mail.generateData() },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
try mail.deliver();
|
try mail.deliver();
|
||||||
@ -87,8 +90,8 @@ fn resolveTo(allocator: std.mem.Allocator, params: *const jetzig.data.Value) !?[
|
|||||||
.null => null,
|
.null => null,
|
||||||
.string => |string| .{ .email = string.value },
|
.string => |string| .{ .email = string.value },
|
||||||
.object => |object| .{
|
.object => |object| .{
|
||||||
.email = object.getT(.string, "email") orelse return null,
|
.email = object.getT(.string, "email") orelse return error.JetzigMissingEmailField,
|
||||||
.name = object.getT(.string, "name") orelse return null,
|
.name = object.getT(.string, "name"),
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -165,7 +165,7 @@ test "HTML part only" {
|
|||||||
defer std.testing.allocator.free(actual);
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
\\From: <Bob> user@example.com
|
\\From: Bob <user@example.com>
|
||||||
\\Subject: Test subject
|
\\Subject: Test subject
|
||||||
\\MIME-Version: 1.0
|
\\MIME-Version: 1.0
|
||||||
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
@ -201,7 +201,7 @@ test "text part only" {
|
|||||||
defer std.testing.allocator.free(actual);
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
\\From: <Bob> user@example.com
|
\\From: Bob <user@example.com>
|
||||||
\\Subject: Test subject
|
\\Subject: Test subject
|
||||||
\\MIME-Version: 1.0
|
\\MIME-Version: 1.0
|
||||||
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
@ -238,7 +238,7 @@ test "HTML and text parts" {
|
|||||||
defer std.testing.allocator.free(actual);
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
\\From: <Bob> user@example.com
|
\\From: Bob <user@example.com>
|
||||||
\\Subject: Test subject
|
\\Subject: Test subject
|
||||||
\\MIME-Version: 1.0
|
\\MIME-Version: 1.0
|
||||||
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
@ -262,6 +262,41 @@ test "HTML and text parts" {
|
|||||||
try std.testing.expectEqualStrings(expected, actual);
|
try std.testing.expectEqualStrings(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "default email address name" {
|
||||||
|
const mail = Mail{
|
||||||
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
|
.config = .{},
|
||||||
|
.boundary = 123456789,
|
||||||
|
.params = .{
|
||||||
|
.from = .{ .email = "user@example.com" },
|
||||||
|
.to = &.{.{ .email = "user@example.com" }},
|
||||||
|
.subject = "Test subject",
|
||||||
|
.text = "Hello",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const actual = try generateData(mail);
|
||||||
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
|
\\From: user@example.com <user@example.com>
|
||||||
|
\\Subject: Test subject
|
||||||
|
\\MIME-Version: 1.0
|
||||||
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
|
\\--=_alternative_123456789
|
||||||
|
\\Content-Type: text/plain; charset="UTF-8"
|
||||||
|
\\Content-Transfer-Encoding: quoted-printable
|
||||||
|
\\
|
||||||
|
\\Hello
|
||||||
|
\\
|
||||||
|
\\.
|
||||||
|
\\
|
||||||
|
, "\n", "\r\n");
|
||||||
|
defer std.testing.allocator.free(expected);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(expected, actual);
|
||||||
|
}
|
||||||
test "long content encoding" {
|
test "long content encoding" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
@ -281,7 +316,7 @@ test "long content encoding" {
|
|||||||
defer std.testing.allocator.free(actual);
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
\\From: <Bob> user@example.com
|
\\From: Bob <user@example.com>
|
||||||
\\Subject: Test subject
|
\\Subject: Test subject
|
||||||
\\MIME-Version: 1.0
|
\\MIME-Version: 1.0
|
||||||
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
@ -332,7 +367,7 @@ test "non-latin alphabet encoding" {
|
|||||||
defer std.testing.allocator.free(actual);
|
defer std.testing.allocator.free(actual);
|
||||||
|
|
||||||
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
|
||||||
\\From: <Bob> user@example.com
|
\\From: Bob <user@example.com>
|
||||||
\\Subject: Test subject
|
\\Subject: Test subject
|
||||||
\\MIME-Version: 1.0
|
\\MIME-Version: 1.0
|
||||||
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
|
||||||
|
|||||||
@ -22,7 +22,7 @@ pub const Address = struct {
|
|||||||
email: []const u8,
|
email: []const u8,
|
||||||
|
|
||||||
pub fn format(address: Address, _: anytype, _: anytype, writer: anytype) !void {
|
pub fn format(address: Address, _: anytype, _: anytype, writer: anytype) !void {
|
||||||
try writer.print("<{?s}> {s}", .{ address.name, address.email });
|
try writer.print("{s} <{s}>", .{ address.name orelse address.email, address.email });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,17 +28,17 @@ pub fn beforeResponse(request: *jetzig.http.Request, response: *jetzig.http.Resp
|
|||||||
|
|
||||||
const compressed = switch (encoding) {
|
const compressed = switch (encoding) {
|
||||||
.gzip => jetzig.util.gzip(request.allocator, response.content, .{}) catch |err|
|
.gzip => jetzig.util.gzip(request.allocator, response.content, .{}) catch |err|
|
||||||
return request.server.logger.logError(err),
|
return request.server.logger.logError(@errorReturnTrace(), err),
|
||||||
.deflate => jetzig.util.deflate(request.allocator, response.content, .{}) catch |err|
|
.deflate => jetzig.util.deflate(request.allocator, response.content, .{}) catch |err|
|
||||||
return request.server.logger.logError(err),
|
return request.server.logger.logError(@errorReturnTrace(), err),
|
||||||
};
|
};
|
||||||
|
|
||||||
response.headers.append("Content-Encoding", @tagName(encoding)) catch |err|
|
response.headers.append("Content-Encoding", @tagName(encoding)) catch |err|
|
||||||
return request.server.logger.logError(err);
|
return request.server.logger.logError(@errorReturnTrace(), err);
|
||||||
|
|
||||||
// Make caching work
|
// Make caching work
|
||||||
response.headers.append("Vary", "Accept-Encoding") catch |err|
|
response.headers.append("Vary", "Accept-Encoding") catch |err|
|
||||||
return request.server.logger.logError(err);
|
return request.server.logger.logError(@errorReturnTrace(), err);
|
||||||
|
|
||||||
response.content = compressed;
|
response.content = compressed;
|
||||||
}
|
}
|
||||||
@ -48,9 +48,9 @@ fn detectEncoding(request: *const jetzig.http.Request) ?Encoding {
|
|||||||
while (headers_it.next()) |header| {
|
while (headers_it.next()) |header| {
|
||||||
var it = std.mem.tokenizeScalar(u8, header.value, ',');
|
var it = std.mem.tokenizeScalar(u8, header.value, ',');
|
||||||
while (it.next()) |param| {
|
while (it.next()) |param| {
|
||||||
inline for (@typeInfo(Encoding).Enum.fields) |field| {
|
inline for (@typeInfo(Encoding).@"enum".fields) |field| {
|
||||||
if (std.mem.eql(u8, field.name, jetzig.util.strip(param))) {
|
if (std.mem.eql(u8, field.name, jetzig.util.strip(param))) {
|
||||||
return std.enums.nameCast(Encoding, field.name);
|
return @field(Encoding, field.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,10 +8,10 @@ const HtmxMiddleware = @This();
|
|||||||
/// request doesn't come via htmx and, when the request does come from htmx, only return the
|
/// request doesn't come via htmx and, when the request does come from htmx, only return the
|
||||||
/// content rendered directly by the view function.
|
/// content rendered directly by the view function.
|
||||||
pub fn afterRequest(request: *jetzig.http.Request) !void {
|
pub fn afterRequest(request: *jetzig.http.Request) !void {
|
||||||
if (request.headers.get("HX-Target")) |target| {
|
if (request.headers.get("HX-Request")) |_| {
|
||||||
try request.server.logger.DEBUG(
|
try request.server.logger.DEBUG(
|
||||||
"[middleware-htmx] htmx request detected, disabling layout. (#{s})",
|
"[middleware-htmx] HX-Request header, disabling layout.",
|
||||||
.{target},
|
.{},
|
||||||
);
|
);
|
||||||
request.setLayout(null);
|
request.setLayout(null);
|
||||||
}
|
}
|
||||||
@ -19,17 +19,21 @@ pub fn afterRequest(request: *jetzig.http.Request) !void {
|
|||||||
|
|
||||||
/// If a redirect was issued during request processing, reset any response data, set response
|
/// If a redirect was issued during request processing, reset any response data, set response
|
||||||
/// status to `200 OK` and replace the `Location` header with a `HX-Redirect` header.
|
/// status to `200 OK` and replace the `Location` header with a `HX-Redirect` header.
|
||||||
|
/// Add Vary response header to prevent caching the page without layout for requests not coming
|
||||||
|
/// from htmx.
|
||||||
pub fn beforeResponse(request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
pub fn beforeResponse(request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
|
||||||
switch (response.status_code) {
|
|
||||||
.moved_permanently, .found => {},
|
|
||||||
else => return,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.headers.get("HX-Request") == null) return;
|
if (request.headers.get("HX-Request") == null) return;
|
||||||
|
|
||||||
if (response.headers.get("Location")) |location| {
|
switch (response.status_code) {
|
||||||
response.status_code = .ok;
|
.moved_permanently, .found => {
|
||||||
request.response_data.reset();
|
if (response.headers.get("Location")) |location| {
|
||||||
try response.headers.append("HX-Redirect", location);
|
response.status_code = .ok;
|
||||||
|
request.response_data.reset();
|
||||||
|
try response.headers.append("HX-Redirect", location);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
try response.headers.append("Vary", "HX-Request");
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
|
|||||||
try cookies.parse();
|
try cookies.parse();
|
||||||
|
|
||||||
const session = try alloc.create(jetzig.http.Session);
|
const session = try alloc.create(jetzig.http.Session);
|
||||||
session.* = jetzig.http.Session.init(alloc, cookies, jetzig.testing.secret);
|
session.* = jetzig.http.Session.init(alloc, cookies, jetzig.testing.secret, .{});
|
||||||
|
|
||||||
app.* = App{
|
app.* = App{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
@ -78,6 +78,7 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
|
|||||||
|
|
||||||
/// Free allocated resources for test app.
|
/// Free allocated resources for test app.
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
|
self.repo.deinit();
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.allocator.destroy(self.arena);
|
self.allocator.destroy(self.arena);
|
||||||
if (self.logger.test_logger.file) |file| file.close();
|
if (self.logger.test_logger.file) |file| file.close();
|
||||||
@ -236,7 +237,7 @@ pub fn initSession(self: *App) !void {
|
|||||||
const allocator = self.arena.allocator();
|
const allocator = self.arena.allocator();
|
||||||
|
|
||||||
var local_session = try allocator.create(jetzig.http.Session);
|
var local_session = try allocator.create(jetzig.http.Session);
|
||||||
local_session.* = jetzig.http.Session.init(allocator, self.cookies, jetzig.testing.secret);
|
local_session.* = jetzig.http.Session.init(allocator, self.cookies, jetzig.testing.secret, .{});
|
||||||
try local_session.parse();
|
try local_session.parse();
|
||||||
|
|
||||||
self.session = local_session;
|
self.session = local_session;
|
||||||
@ -346,7 +347,7 @@ fn stubbedRequest(
|
|||||||
.route_data = null,
|
.route_data = null,
|
||||||
.middlewares = undefined,
|
.middlewares = undefined,
|
||||||
.address = undefined,
|
.address = undefined,
|
||||||
.method = std.enums.nameCast(httpz.Method, @tagName(method)),
|
.method = @field(httpz.Method, @tagName(method)),
|
||||||
.protocol = .HTTP11,
|
.protocol = .HTTP11,
|
||||||
.params = undefined,
|
.params = undefined,
|
||||||
.conn = undefined,
|
.conn = undefined,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user