From 65edd20092b858bfe12bcfcacd616bf4896e9370 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 5 May 2024 20:04:57 +0100 Subject: [PATCH] Allow mailers to modify mail template params Remove need to add all mail template params to view response data. This was originally intended functionality but the wrong value was passed to the `deliver` function, and no `data` argument was given, making it cumbersome to create new values. --- build.zig.zon | 4 +-- cli/commands/bundle.zig | 3 +- cli/commands/generate/job.zig | 2 +- cli/commands/generate/mailer.zig | 29 ++++++-------------- demo/src/app/mailers/welcome.zig | 11 +++++--- demo/src/app/mailers/welcome/html.zmpl | 1 + demo/src/app/mailers/welcome/text.zmpl | 2 ++ demo/src/app/views/background_jobs.zig | 8 ++---- demo/src/app/views/cache.zig | 2 +- demo/src/app/views/cache/index.zmpl | 2 +- demo/src/app/views/kvstore.zig | 8 ++++-- demo/src/app/views/mail/index.zmpl | 1 + demo/src/app/views/mailers/welcome.html.zmpl | 1 - demo/src/app/views/mailers/welcome.text.zmpl | 1 - demo/src/app/views/welcome.html.zmpl | 1 - src/jetzig/http/Request.zig | 29 +++++++++++--------- src/jetzig/jobs/Job.zig | 10 +++++++ src/jetzig/mail/Job.zig | 4 ++- src/jetzig/mail/MailerDefinition.zig | 1 + 19 files changed, 64 insertions(+), 56 deletions(-) delete mode 100644 demo/src/app/views/mailers/welcome.html.zmpl delete mode 100644 demo/src/app/views/mailers/welcome.text.zmpl delete mode 100644 demo/src/app/views/welcome.html.zmpl diff --git a/build.zig.zon b/build.zig.zon index e809431..0ac5933 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,8 +7,8 @@ .hash = "1220482f07f2bbaef335f20d6890c15a1e14739950b784232bc69182423520e058a5", }, .zmpl = .{ - .url = "https://github.com/jetzig-framework/zmpl/archive/4457eba50bd2eff3743601aa00adbffebcd4207a.tar.gz", - .hash = "122001d661e534ef59fc20936330bb0c3068c8aaf1b7c60f9dde9e58d9a536918754", + .url = "https://github.com/jetzig-framework/zmpl/archive/c14683521ca48c1de0a9b2d61dfb145e1bc4dac1.tar.gz", + .hash = "122093b741ef4aff151e916fc6005cb0c2aed747a34b77c0d4b45099ea2b561df9c7", }, .jetkv = .{ .url = "https://github.com/jetzig-framework/jetkv/archive/edfda9108c857dd5a04f87ae48667d3ed57612d9.tar.gz", diff --git a/cli/commands/bundle.zig b/cli/commands/bundle.zig index 7447bf6..87448ae 100644 --- a/cli/commands/bundle.zig +++ b/cli/commands/bundle.zig @@ -8,7 +8,7 @@ const util = @import("../util.zig"); /// Command line options for the `bundle` command. pub const Options = struct { optimize: enum { Debug, ReleaseFast, ReleaseSmall } = .ReleaseFast, - arch: enum { x86_64, aarch64, default } = .default, + arch: enum { x86, x86_64, aarch64, default } = .default, os: enum { linux, macos, windows, default } = .default, pub const meta = .{ @@ -190,6 +190,7 @@ fn zig_build_install(allocator: std.mem.Allocator, path: []const u8, options: Op try target_buf.append("-Dtarget="); switch (options.arch) { + .x86 => try target_buf.append("x86"), .x86_64 => try target_buf.append("x86_64"), .aarch64 => try target_buf.append("aarch64"), .default => try target_buf.append(@tagName(builtin.cpu.arch)), diff --git a/cli/commands/generate/job.zig b/cli/commands/generate/job.zig index a58bcbe..5272109 100644 --- a/cli/commands/generate/job.zig +++ b/cli/commands/generate/job.zig @@ -51,7 +51,7 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he \\// * env: Provides the following fields: \\// - logger: Logger attached to the same stream as the Jetzig server. \\// - environment: Enum of `{ production, development }`. - \\pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, logger: jetzig.Logger) !void { + \\pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void { \\ _ = allocator; \\ _ = params; \\ // Job execution code goes here. Add any code that you would like to run in the background. diff --git a/cli/commands/generate/mailer.zig b/cli/commands/generate/mailer.zig index aa963aa..2d50e00 100644 --- a/cli/commands/generate/mailer.zig +++ b/cli/commands/generate/mailer.zig @@ -39,45 +39,34 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he }; try mailer_file.writeAll( - \\const std = @import("std"); - \\const jetzig = @import("jetzig"); - \\ - \\// Default values for this mailer. - \\pub const defaults: jetzig.mail.DefaultMailParams = .{ - \\ .from = "no-reply@example.com", - \\ .subject = "Default subject", - \\}; - \\ \\// The `deliver` function is invoked every time this mailer is used to send an email. - \\// Use this function to modify mail parameters before the mail is delivered, or simply - \\// to log all uses of this mailer. + \\// Use this function to set default mail params (e.g. a default `from` address or + \\// `subject`) before the mail is delivered. \\// - \\// To use this mailer from a request: - \\// ``` - \\// const mail = request.mail("", .{ .to = &.{"user@example.com"} }); - \\// try mail.deliver(.background, .{}); - \\// ``` \\// A mailer can provide two Zmpl templates for rendering email content: \\// * `src/app/mailers//html.zmpl \\// * `src/app/mailers//text.zmpl \\// \\// Arguments: \\// * allocator: Arena allocator for use during the mail delivery process. - \\// * mail: Mail parameters. Inspect or override any values assigned when the mail was created. - \\// * params: Params assigned to a mail (from a request, any values added to `data`). Params - \\// can be modified before email delivery. + \\// * mail: Mail parameters (from, to, subject, etc.). Inspect or override any values + \\// assigned when the mail was created. + \\// * data: Provides `data.string()` etc. for generating Jetzig Values. + \\// * params: Template data for `text.zmpl` and `html.zmpl`. Inherits all response data + \\// assigned in a view function and can be modified for email-specific content. \\// * env: Provides the following fields: \\// - logger: Logger attached to the same stream as the Jetzig server. \\// - environment: Enum of `{ production, development }`. \\pub fn deliver( \\ allocator: std.mem.Allocator, \\ mail: *jetzig.mail.MailParams, + \\ data: *jetzig.data.Data, \\ params: *jetzig.data.Value, \\ env: jetzig.jobs.JobEnv, \\) !void { \\ _ = allocator; + \\ _ = data; \\ _ = params; - \\ \\ try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)}); \\} \\ diff --git a/demo/src/app/mailers/welcome.zig b/demo/src/app/mailers/welcome.zig index e4dc1d0..87e0d83 100644 --- a/demo/src/app/mailers/welcome.zig +++ b/demo/src/app/mailers/welcome.zig @@ -17,20 +17,23 @@ pub const defaults: jetzig.mail.DefaultMailParams = .{ // // Arguments: // * allocator: Arena allocator for use during the mail delivery process. -// * mail: Mail parameters. Inspect or override any values assigned when the mail was created. -// * params: Params assigned to a mail (from a request, any values added to `data`). Params -// can be modified before email delivery. +// * mail: Mail parameters (from, to, subject, etc.). Inspect or override any values +// assigned when the mail was created. +// * data: Provides `data.string()` etc. for generating Jetzig Values. +// * params: Template data for `text.zmpl` and `html.zmpl`. Inherits all response data +// assigned in a view function and can be modified for email-specific content. // * env: Provides the following fields: // - logger: Logger attached to the same stream as the Jetzig server. // - environment: Enum of `{ production, development }`. pub fn deliver( allocator: std.mem.Allocator, mail: *jetzig.mail.MailParams, + data: *jetzig.data.Data, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv, ) !void { _ = allocator; - _ = params; + try params.put("email_message", data.string("Custom email message")); try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)}); } diff --git a/demo/src/app/mailers/welcome/html.zmpl b/demo/src/app/mailers/welcome/html.zmpl index dc4dd40..671b21d 100644 --- a/demo/src/app/mailers/welcome/html.zmpl +++ b/demo/src/app/mailers/welcome/html.zmpl @@ -1 +1,2 @@
{{.message}}
+
{{.email_message}}
diff --git a/demo/src/app/mailers/welcome/text.zmpl b/demo/src/app/mailers/welcome/text.zmpl index f9f2e47..02af048 100644 --- a/demo/src/app/mailers/welcome/text.zmpl +++ b/demo/src/app/mailers/welcome/text.zmpl @@ -1 +1,3 @@ {{.message}} + +{{.email_message}} diff --git a/demo/src/app/views/background_jobs.zig b/demo/src/app/views/background_jobs.zig index 9707dc3..d73a53e 100644 --- a/demo/src/app/views/background_jobs.zig +++ b/demo/src/app/views/background_jobs.zig @@ -3,18 +3,14 @@ const jetzig = @import("jetzig"); /// This example demonstrates usage of Jetzig's background jobs. pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { - - // Create a new job using `src/app/jobs/example_job.zig`. + // Prepare a job using `src/app/jobs/example.zig`. var job = try request.job("example"); // Add a param `foo` to the job. try job.params.put("foo", data.string("bar")); try job.params.put("id", data.integer(std.crypto.random.int(u32))); - // Schedule the job for background processing. The job is added to the queue. When the job is - // processed a new instance of `example_job` is created and its `run` function is invoked. - // All params are added above are available to the job by calling `job.params()` inside the - // `run` function. + // Schedule the job for background processing. try job.schedule(); return request.render(.ok); diff --git a/demo/src/app/views/cache.zig b/demo/src/app/views/cache.zig index 5041502..c966b17 100644 --- a/demo/src/app/views/cache.zig +++ b/demo/src/app/views/cache.zig @@ -3,7 +3,7 @@ const jetzig = @import("jetzig"); pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { var root = try data.object(); - try root.put("cached_value", try request.cache.get("example")); + try root.put("message", try request.cache.get("message")); return request.render(.ok); } diff --git a/demo/src/app/views/cache/index.zmpl b/demo/src/app/views/cache/index.zmpl index a74c6ac..4a0aae4 100644 --- a/demo/src/app/views/cache/index.zmpl +++ b/demo/src/app/views/cache/index.zmpl @@ -1,3 +1,3 @@
- Cached value: {{.cached_value}} + Cached value: {{.message}}
diff --git a/demo/src/app/views/kvstore.zig b/demo/src/app/views/kvstore.zig index b86a024..cf9d6cd 100644 --- a/demo/src/app/views/kvstore.zig +++ b/demo/src/app/views/kvstore.zig @@ -6,23 +6,25 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { // Fetch a string from the KV store. If it exists, store it in the root data object, // otherwise store a string value to be picked up by the next request. - if (try request.store.fetchRemove("example-key")) |capture| { + if (try request.store.get("example-key")) |capture| { try root.put("stored_string", capture); } else { try root.put("stored_string", null); try request.store.put("example-key", data.string("example-value")); } - // Left-pop an item from the array and store it in the root data object. This will empty the + // Left-pop an item from an array and store it in the root data object. This will empty the // array after multiple requests. + // If the array is empty or not found, append some new values to the array. if (try request.store.popFirst("example-array")) |value| { try root.put("popped", value); } else { - try root.put("popped", null); // Store some values in an array in the KV store. try request.store.append("example-array", data.string("hello")); try request.store.append("example-array", data.string("goodbye")); try request.store.append("example-array", data.string("hello again")); + + try root.put("popped", null); } return request.render(.ok); diff --git a/demo/src/app/views/mail/index.zmpl b/demo/src/app/views/mail/index.zmpl index 9483c0e..a25202b 100644 --- a/demo/src/app/views/mail/index.zmpl +++ b/demo/src/app/views/mail/index.zmpl @@ -1,3 +1,4 @@
Your email has been sent! +
{{.message}}
diff --git a/demo/src/app/views/mailers/welcome.html.zmpl b/demo/src/app/views/mailers/welcome.html.zmpl deleted file mode 100644 index 0ca781b..0000000 --- a/demo/src/app/views/mailers/welcome.html.zmpl +++ /dev/null @@ -1 +0,0 @@ -
{{.data.test}}
diff --git a/demo/src/app/views/mailers/welcome.text.zmpl b/demo/src/app/views/mailers/welcome.text.zmpl deleted file mode 100644 index d0bbaba..0000000 --- a/demo/src/app/views/mailers/welcome.text.zmpl +++ /dev/null @@ -1 +0,0 @@ -{{.data.test}} diff --git a/demo/src/app/views/welcome.html.zmpl b/demo/src/app/views/welcome.html.zmpl deleted file mode 100644 index 0fd9beb..0000000 --- a/demo/src/app/views/welcome.html.zmpl +++ /dev/null @@ -1 +0,0 @@ -
Hello
diff --git a/src/jetzig/http/Request.zig b/src/jetzig/http/Request.zig index a0f7b77..e260a06 100644 --- a/src/jetzig/http/Request.zig +++ b/src/jetzig/http/Request.zig @@ -31,55 +31,55 @@ redirected: bool = false, rendered_multiple: bool = false, rendered_view: ?jetzig.views.View = null, start_time: i128, -store: ArenaStore, -cache: ArenaStore, +store: RequestStore, +cache: RequestStore, /// Wrapper for KV store that uses the request's arena allocator for fetching values. -pub const ArenaStore = struct { +pub const RequestStore = struct { allocator: std.mem.Allocator, store: *jetzig.kv.Store, /// Put a String or into the key-value store. - pub fn get(self: ArenaStore, key: []const u8) !?*jetzig.data.Value { + pub fn get(self: RequestStore, key: []const u8) !?*jetzig.data.Value { return try self.store.get(try self.data(), key); } /// Get a String from the store. - pub fn put(self: ArenaStore, key: []const u8, value: *jetzig.data.Value) !void { + pub fn put(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void { try self.store.put(key, value); } /// Remove a String to from the key-value store and return it if found. - pub fn fetchRemove(self: ArenaStore, key: []const u8) !?*jetzig.data.Value { + pub fn fetchRemove(self: RequestStore, key: []const u8) !?*jetzig.data.Value { return try self.store.fetchRemove(try self.data(), key); } /// Remove a String to from the key-value store. - pub fn remove(self: ArenaStore, key: []const u8) !void { + pub fn remove(self: RequestStore, key: []const u8) !void { try self.store.remove(key); } /// Append a Value to the end of an Array in the key-value store. - pub fn append(self: ArenaStore, key: []const u8, value: *jetzig.data.Value) !void { + pub fn append(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void { try self.store.append(key, value); } /// Prepend a Value to the start of an Array in the key-value store. - pub fn prepend(self: ArenaStore, key: []const u8, value: *jetzig.data.Value) !void { + pub fn prepend(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void { try self.store.prepend(key, value); } /// Pop a String from an Array in the key-value store. - pub fn pop(self: ArenaStore, key: []const u8) !?*jetzig.data.Value { + pub fn pop(self: RequestStore, key: []const u8) !?*jetzig.data.Value { return try self.store.pop(try self.data(), key); } /// Left-pop a String from an Array in the key-value store. - pub fn popFirst(self: ArenaStore, key: []const u8) !?*jetzig.data.Value { + pub fn popFirst(self: RequestStore, key: []const u8) !?*jetzig.data.Value { return try self.store.popFirst(try self.data(), key); } - fn data(self: ArenaStore) !*jetzig.data.Data { + fn data(self: RequestStore) !*jetzig.data.Data { const arena_data = try self.allocator.create(jetzig.data.Data); arena_data.* = jetzig.data.Data.init(self.allocator); return arena_data; @@ -390,7 +390,10 @@ const RequestMail = struct { const text = if (self.mail_params.text) |text| mail_job.data.string(text) else null; try mail_job.params.put("text", text); - if (self.request.response_data.value) |value| try mail_job.params.put("params", value); + if (self.request.response_data.value) |value| try mail_job.params.put( + "params", + if (strategy == .now) try value.clone(self.request.allocator) else value, + ); switch (strategy) { .background => try mail_job.schedule(), diff --git a/src/jetzig/jobs/Job.zig b/src/jetzig/jobs/Job.zig index f6f9b08..1c0e80a 100644 --- a/src/jetzig/jobs/Job.zig +++ b/src/jetzig/jobs/Job.zig @@ -7,14 +7,24 @@ pub const JobDefinition = struct { runFn: *const fn (std.mem.Allocator, *jetzig.data.Value, JobEnv) anyerror!void, }; +/// Environment passed to all jobs. pub const JobEnv = struct { + /// The Jetzig server logger logger: jetzig.loggers.Logger, + /// The current server environment, `enum { development, production }` environment: jetzig.Environment.EnvironmentName, + /// All routes detected by Jetzig on startup routes: []*const jetzig.Route, + /// All mailers detected by Jetzig on startup mailers: []const jetzig.MailerDefinition, + /// All jobs detected by Jetzig on startup jobs: []const jetzig.JobDefinition, + /// Global key-value store store: *jetzig.kv.Store, + /// Global cache cache: *jetzig.kv.Store, + /// Global mutex - use with caution if it is necessary to guarantee thread safety/consistency + /// between concurrent job workers mutex: *std.Thread.Mutex, }; diff --git a/src/jetzig/mail/Job.zig b/src/jetzig/mail/Job.zig index 820b26b..88c03fa 100644 --- a/src/jetzig/mail/Job.zig +++ b/src/jetzig/mail/Job.zig @@ -34,7 +34,9 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig .defaults = mailer.defaults, }; - try mailer.deliverFn(allocator, &mail_params, params, env); + var data = jetzig.data.Data.init(allocator); + + try mailer.deliverFn(allocator, &mail_params, &data, params.get("params").?, env); const mail = jetzig.mail.Mail.init(allocator, .{ .subject = mail_params.get(.subject) orelse "(No subject)", diff --git a/src/jetzig/mail/MailerDefinition.zig b/src/jetzig/mail/MailerDefinition.zig index 2331428..923d997 100644 --- a/src/jetzig/mail/MailerDefinition.zig +++ b/src/jetzig/mail/MailerDefinition.zig @@ -4,6 +4,7 @@ const jetzig = @import("../../jetzig.zig"); pub const DeliverFn = *const fn ( std.mem.Allocator, *jetzig.mail.MailParams, + *jetzig.data.Data, *jetzig.data.Value, jetzig.jobs.JobEnv, ) anyerror!void;