Merge pull request #64 from jetzig-framework/mailer-improvements

Allow mailers to modify mail template params
This commit is contained in:
bobf 2024-05-05 20:11:50 +01:00 committed by GitHub
commit bd62f75cb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 64 additions and 56 deletions

View File

@ -7,8 +7,8 @@
.hash = "1220482f07f2bbaef335f20d6890c15a1e14739950b784232bc69182423520e058a5", .hash = "1220482f07f2bbaef335f20d6890c15a1e14739950b784232bc69182423520e058a5",
}, },
.zmpl = .{ .zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/4457eba50bd2eff3743601aa00adbffebcd4207a.tar.gz", .url = "https://github.com/jetzig-framework/zmpl/archive/c14683521ca48c1de0a9b2d61dfb145e1bc4dac1.tar.gz",
.hash = "122001d661e534ef59fc20936330bb0c3068c8aaf1b7c60f9dde9e58d9a536918754", .hash = "122093b741ef4aff151e916fc6005cb0c2aed747a34b77c0d4b45099ea2b561df9c7",
}, },
.jetkv = .{ .jetkv = .{
.url = "https://github.com/jetzig-framework/jetkv/archive/edfda9108c857dd5a04f87ae48667d3ed57612d9.tar.gz", .url = "https://github.com/jetzig-framework/jetkv/archive/edfda9108c857dd5a04f87ae48667d3ed57612d9.tar.gz",

View File

@ -8,7 +8,7 @@ const util = @import("../util.zig");
/// Command line options for the `bundle` command. /// Command line options for the `bundle` command.
pub const Options = struct { pub const Options = struct {
optimize: enum { Debug, ReleaseFast, ReleaseSmall } = .ReleaseFast, 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, os: enum { linux, macos, windows, default } = .default,
pub const meta = .{ 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="); try target_buf.append("-Dtarget=");
switch (options.arch) { switch (options.arch) {
.x86 => try target_buf.append("x86"),
.x86_64 => try target_buf.append("x86_64"), .x86_64 => try target_buf.append("x86_64"),
.aarch64 => try target_buf.append("aarch64"), .aarch64 => try target_buf.append("aarch64"),
.default => try target_buf.append(@tagName(builtin.cpu.arch)), .default => try target_buf.append(@tagName(builtin.cpu.arch)),

View File

@ -51,7 +51,7 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he
\\// * env: Provides the following fields: \\// * env: Provides the following fields:
\\// - logger: Logger attached to the same stream as the Jetzig server. \\// - logger: Logger attached to the same stream as the Jetzig server.
\\// - environment: Enum of `{ production, development }`. \\// - 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; \\ _ = allocator;
\\ _ = params; \\ _ = params;
\\ // Job execution code goes here. Add any code that you would like to run in the background. \\ // Job execution code goes here. Add any code that you would like to run in the background.

View File

@ -39,45 +39,34 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he
}; };
try mailer_file.writeAll( 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. \\// 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 \\// Use this function to set default mail params (e.g. a default `from` address or
\\// to log all uses of this mailer. \\// `subject`) before the mail is delivered.
\\// \\//
\\// To use this mailer from a request:
\\// ```
\\// const mail = request.mail("<mailer-name>", .{ .to = &.{"user@example.com"} });
\\// try mail.deliver(.background, .{});
\\// ```
\\// A mailer can provide two Zmpl templates for rendering email content: \\// A mailer can provide two Zmpl templates for rendering email content:
\\// * `src/app/mailers/<mailer-name>/html.zmpl \\// * `src/app/mailers/<mailer-name>/html.zmpl
\\// * `src/app/mailers/<mailer-name>/text.zmpl \\// * `src/app/mailers/<mailer-name>/text.zmpl
\\// \\//
\\// Arguments: \\// Arguments:
\\// * allocator: Arena allocator for use during the mail delivery process. \\// * allocator: Arena allocator for use during the mail delivery process.
\\// * mail: Mail parameters. Inspect or override any values assigned when the mail was created. \\// * mail: Mail parameters (from, to, subject, etc.). Inspect or override any values
\\// * params: Params assigned to a mail (from a request, any values added to `data`). Params \\// assigned when the mail was created.
\\// can be modified before email delivery. \\// * 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: \\// * env: Provides the following fields:
\\// - logger: Logger attached to the same stream as the Jetzig server. \\// - logger: Logger attached to the same stream as the Jetzig server.
\\// - environment: Enum of `{ production, development }`. \\// - environment: Enum of `{ production, development }`.
\\pub fn deliver( \\pub fn deliver(
\\ allocator: std.mem.Allocator, \\ allocator: std.mem.Allocator,
\\ mail: *jetzig.mail.MailParams, \\ mail: *jetzig.mail.MailParams,
\\ data: *jetzig.data.Data,
\\ params: *jetzig.data.Value, \\ params: *jetzig.data.Value,
\\ env: jetzig.jobs.JobEnv, \\ env: jetzig.jobs.JobEnv,
\\) !void { \\) !void {
\\ _ = allocator; \\ _ = allocator;
\\ _ = data;
\\ _ = params; \\ _ = params;
\\
\\ try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)}); \\ try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)});
\\} \\}
\\ \\

View File

@ -17,20 +17,23 @@ pub const defaults: jetzig.mail.DefaultMailParams = .{
// //
// Arguments: // Arguments:
// * allocator: Arena allocator for use during the mail delivery process. // * allocator: Arena allocator for use during the mail delivery process.
// * mail: Mail parameters. Inspect or override any values assigned when the mail was created. // * mail: Mail parameters (from, to, subject, etc.). Inspect or override any values
// * params: Params assigned to a mail (from a request, any values added to `data`). Params // assigned when the mail was created.
// can be modified before email delivery. // * 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: // * env: Provides the following fields:
// - logger: Logger attached to the same stream as the Jetzig server. // - logger: Logger attached to the same stream as the Jetzig server.
// - environment: Enum of `{ production, development }`. // - environment: Enum of `{ production, development }`.
pub fn deliver( pub fn deliver(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
mail: *jetzig.mail.MailParams, mail: *jetzig.mail.MailParams,
data: *jetzig.data.Data,
params: *jetzig.data.Value, params: *jetzig.data.Value,
env: jetzig.jobs.JobEnv, env: jetzig.jobs.JobEnv,
) !void { ) !void {
_ = allocator; _ = allocator;
_ = params; try params.put("email_message", data.string("Custom email message"));
try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)}); try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)});
} }

View File

@ -1 +1,2 @@
<div>{{.message}}</div> <div>{{.message}}</div>
<div>{{.email_message}}</div>

View File

@ -1 +1,3 @@
{{.message}} {{.message}}
{{.email_message}}

View File

@ -3,18 +3,14 @@ const jetzig = @import("jetzig");
/// This example demonstrates usage of Jetzig's background jobs. /// This example demonstrates usage of Jetzig's background jobs.
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
// Prepare a job using `src/app/jobs/example.zig`.
// Create a new job using `src/app/jobs/example_job.zig`.
var job = try request.job("example"); var job = try request.job("example");
// Add a param `foo` to the job. // Add a param `foo` to the job.
try job.params.put("foo", data.string("bar")); try job.params.put("foo", data.string("bar"));
try job.params.put("id", data.integer(std.crypto.random.int(u32))); 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 // Schedule the job for background processing.
// 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.
try job.schedule(); try job.schedule();
return request.render(.ok); return request.render(.ok);

View File

@ -3,7 +3,7 @@ const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
var root = try data.object(); 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); return request.render(.ok);
} }

View File

@ -1,3 +1,3 @@
<div> <div>
<span>Cached value: {{.cached_value}}</span> <span>Cached value: {{.message}}</span>
</div> </div>

View File

@ -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, // 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. // 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); try root.put("stored_string", capture);
} else { } else {
try root.put("stored_string", null); try root.put("stored_string", null);
try request.store.put("example-key", data.string("example-value")); 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. // 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| { if (try request.store.popFirst("example-array")) |value| {
try root.put("popped", value); try root.put("popped", value);
} else { } else {
try root.put("popped", null);
// Store some values in an array in the KV store. // 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("hello"));
try request.store.append("example-array", data.string("goodbye")); try request.store.append("example-array", data.string("goodbye"));
try request.store.append("example-array", data.string("hello again")); try request.store.append("example-array", data.string("hello again"));
try root.put("popped", null);
} }
return request.render(.ok); return request.render(.ok);

View File

@ -1,3 +1,4 @@
<div> <div>
<span>Your email has been sent!</span> <span>Your email has been sent!</span>
<div>{{.message}}</div>
</div> </div>

View File

@ -1 +0,0 @@
<div>{{.data.test}}</div>

View File

@ -1 +0,0 @@
{{.data.test}}

View File

@ -1 +0,0 @@
<div>Hello</div>

View File

@ -31,55 +31,55 @@ redirected: bool = false,
rendered_multiple: bool = false, rendered_multiple: bool = false,
rendered_view: ?jetzig.views.View = null, rendered_view: ?jetzig.views.View = null,
start_time: i128, start_time: i128,
store: ArenaStore, store: RequestStore,
cache: ArenaStore, cache: RequestStore,
/// Wrapper for KV store that uses the request's arena allocator for fetching values. /// 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, allocator: std.mem.Allocator,
store: *jetzig.kv.Store, store: *jetzig.kv.Store,
/// Put a String or into the key-value 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); return try self.store.get(try self.data(), key);
} }
/// Get a String from the store. /// 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); try self.store.put(key, value);
} }
/// Remove a String to from the key-value store and return it if found. /// 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); return try self.store.fetchRemove(try self.data(), key);
} }
/// Remove a String to from the key-value store. /// 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); try self.store.remove(key);
} }
/// Append a Value to the end of an Array in the key-value store. /// 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); try self.store.append(key, value);
} }
/// Prepend a Value to the start of an Array in the key-value store. /// 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); try self.store.prepend(key, value);
} }
/// Pop a String from an Array in the key-value store. /// 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); return try self.store.pop(try self.data(), key);
} }
/// Left-pop a String from an Array in the key-value store. /// 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); 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); const arena_data = try self.allocator.create(jetzig.data.Data);
arena_data.* = jetzig.data.Data.init(self.allocator); arena_data.* = jetzig.data.Data.init(self.allocator);
return arena_data; 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; const text = if (self.mail_params.text) |text| mail_job.data.string(text) else null;
try mail_job.params.put("text", text); 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) { switch (strategy) {
.background => try mail_job.schedule(), .background => try mail_job.schedule(),

View File

@ -7,14 +7,24 @@ pub const JobDefinition = struct {
runFn: *const fn (std.mem.Allocator, *jetzig.data.Value, JobEnv) anyerror!void, runFn: *const fn (std.mem.Allocator, *jetzig.data.Value, JobEnv) anyerror!void,
}; };
/// Environment passed to all jobs.
pub const JobEnv = struct { pub const JobEnv = struct {
/// The Jetzig server logger
logger: jetzig.loggers.Logger, logger: jetzig.loggers.Logger,
/// The current server environment, `enum { development, production }`
environment: jetzig.Environment.EnvironmentName, environment: jetzig.Environment.EnvironmentName,
/// All routes detected by Jetzig on startup
routes: []*const jetzig.Route, routes: []*const jetzig.Route,
/// All mailers detected by Jetzig on startup
mailers: []const jetzig.MailerDefinition, mailers: []const jetzig.MailerDefinition,
/// All jobs detected by Jetzig on startup
jobs: []const jetzig.JobDefinition, jobs: []const jetzig.JobDefinition,
/// Global key-value store
store: *jetzig.kv.Store, store: *jetzig.kv.Store,
/// Global cache
cache: *jetzig.kv.Store, 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, mutex: *std.Thread.Mutex,
}; };

View File

@ -34,7 +34,9 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
.defaults = mailer.defaults, .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, .{ const mail = jetzig.mail.Mail.init(allocator, .{
.subject = mail_params.get(.subject) orelse "(No subject)", .subject = mail_params.get(.subject) orelse "(No subject)",

View File

@ -4,6 +4,7 @@ const jetzig = @import("../../jetzig.zig");
pub const DeliverFn = *const fn ( pub const DeliverFn = *const fn (
std.mem.Allocator, std.mem.Allocator,
*jetzig.mail.MailParams, *jetzig.mail.MailParams,
*jetzig.data.Data,
*jetzig.data.Value, *jetzig.data.Value,
jetzig.jobs.JobEnv, jetzig.jobs.JobEnv,
) anyerror!void; ) anyerror!void;