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",
},
.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",

View File

@ -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)),

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:
\\// - 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.

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(
\\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("<mailer-name>", .{ .to = &.{"user@example.com"} });
\\// try mail.deliver(.background, .{});
\\// ```
\\// A mailer can provide two Zmpl templates for rendering email content:
\\// * `src/app/mailers/<mailer-name>/html.zmpl
\\// * `src/app/mailers/<mailer-name>/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)});
\\}
\\

View File

@ -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)});
}

View File

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

View File

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

View File

@ -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);

View File

@ -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);
}

View File

@ -1,3 +1,3 @@
<div>
<span>Cached value: {{.cached_value}}</span>
<span>Cached value: {{.message}}</span>
</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,
// 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);

View File

@ -1,3 +1,4 @@
<div>
<span>Your email has been sent!</span>
<div>{{.message}}</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_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(),

View File

@ -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,
};

View File

@ -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)",

View File

@ -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;