mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Closes #105: Configure SMTP from environment variables
Fall back to hardcoded values if each `JETZIG_SMTP_*` variable is not present.
This commit is contained in:
parent
bf6c595d64
commit
9e4a81aa19
@ -95,6 +95,7 @@ pub fn build(b: *std.Build) !void {
|
|||||||
main_tests.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
main_tests.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
||||||
main_tests.root_module.addImport("jetkv", jetkv_dep.module("jetkv"));
|
main_tests.root_module.addImport("jetkv", jetkv_dep.module("jetkv"));
|
||||||
main_tests.root_module.addImport("httpz", httpz_dep.module("httpz"));
|
main_tests.root_module.addImport("httpz", httpz_dep.module("httpz"));
|
||||||
|
main_tests.root_module.addImport("smtp", smtp_client_dep.module("smtp_client"));
|
||||||
const run_main_tests = b.addRunArtifact(main_tests);
|
const run_main_tests = b.addRunArtifact(main_tests);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
|
@ -9,6 +9,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
|||||||
|
|
||||||
const mail = jetzig.mail.Mail.init(
|
const mail = jetzig.mail.Mail.init(
|
||||||
allocator,
|
allocator,
|
||||||
|
env,
|
||||||
.{
|
.{
|
||||||
.subject = "Hello!!!",
|
.subject = "Hello!!!",
|
||||||
.from = "bob@jetzig.dev",
|
.from = "bob@jetzig.dev",
|
||||||
|
@ -116,6 +116,13 @@ pub const jetzig_options = struct {
|
|||||||
|
|
||||||
/// SMTP configuration for Jetzig Mail. It is recommended to use a local SMTP relay,
|
/// SMTP configuration for Jetzig Mail. It is recommended to use a local SMTP relay,
|
||||||
/// e.g.: https://github.com/juanluisbaptiste/docker-postfix
|
/// e.g.: https://github.com/juanluisbaptiste/docker-postfix
|
||||||
|
///
|
||||||
|
/// Each configuration option can be overridden with environment variables:
|
||||||
|
/// `JETZIG_SMTP_PORT`
|
||||||
|
/// `JETZIG_SMTP_ENCRYPTION`
|
||||||
|
/// `JETZIG_SMTP_HOST`
|
||||||
|
/// `JETZIG_SMTP_USERNAME`
|
||||||
|
/// `JETZIG_SMTP_PASSWORD`
|
||||||
// pub const smtp: jetzig.mail.SMTPConfig = .{
|
// pub const smtp: jetzig.mail.SMTPConfig = .{
|
||||||
// .port = 25,
|
// .port = 25,
|
||||||
// .encryption = .none, // .insecure, .none, .tls, .start_tls
|
// .encryption = .none, // .insecure, .none, .tls, .start_tls
|
||||||
|
@ -217,13 +217,10 @@ pub const initHook: ?*const fn (*App) anyerror!void = if (@hasDecl(root, "init")
|
|||||||
/// Initialize a new Jetzig app. Call this from `src/main.zig` and then call
|
/// Initialize a new Jetzig app. Call this from `src/main.zig` and then call
|
||||||
/// `start(@import("routes").routes)` on the returned value.
|
/// `start(@import("routes").routes)` on the returned value.
|
||||||
pub fn init(allocator: std.mem.Allocator) !App {
|
pub fn init(allocator: std.mem.Allocator) !App {
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const env = try Environment.init(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
|
||||||
|
|
||||||
const environment = Environment.init(allocator);
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.environment = environment,
|
.env = env,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.custom_routes = std.ArrayList(views.Route).init(allocator),
|
.custom_routes = std.ArrayList(views.Route).init(allocator),
|
||||||
.initHook = initHook,
|
.initHook = initHook,
|
||||||
|
@ -7,7 +7,7 @@ const mime_types = @import("mime_types").mime_types; // Generated at build time.
|
|||||||
|
|
||||||
const App = @This();
|
const App = @This();
|
||||||
|
|
||||||
environment: jetzig.Environment,
|
env: jetzig.Environment,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
custom_routes: std.ArrayList(jetzig.views.Route),
|
custom_routes: std.ArrayList(jetzig.views.Route),
|
||||||
initHook: ?*const fn (*App) anyerror!void,
|
initHook: ?*const fn (*App) anyerror!void,
|
||||||
@ -26,6 +26,8 @@ const AppOptions = struct {};
|
|||||||
pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
||||||
_ = options; // See `AppOptions`
|
_ = options; // See `AppOptions`
|
||||||
|
|
||||||
|
defer self.env.deinit();
|
||||||
|
|
||||||
if (self.initHook) |hook| try hook(@constCast(self));
|
if (self.initHook) |hook| try hook(@constCast(self));
|
||||||
|
|
||||||
var mime_map = jetzig.http.mime.MimeMap.init(self.allocator);
|
var mime_map = jetzig.http.mime.MimeMap.init(self.allocator);
|
||||||
@ -61,18 +63,14 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
);
|
);
|
||||||
defer cache.deinit();
|
defer cache.deinit();
|
||||||
|
|
||||||
const server_options = try self.environment.getServerOptions();
|
|
||||||
defer self.allocator.free(server_options.bind);
|
|
||||||
defer self.allocator.free(server_options.secret);
|
|
||||||
|
|
||||||
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,
|
||||||
.{ &server_options.log_queue.reader, .{} },
|
.{ &self.env.log_queue.reader, .{} },
|
||||||
);
|
);
|
||||||
defer log_thread.join();
|
defer log_thread.join();
|
||||||
|
|
||||||
if (server_options.detach) {
|
if (self.env.detach) {
|
||||||
const argv = try std.process.argsAlloc(self.allocator);
|
const argv = try std.process.argsAlloc(self.allocator);
|
||||||
defer std.process.argsFree(self.allocator, argv);
|
defer std.process.argsFree(self.allocator, argv);
|
||||||
var child_argv = std.ArrayList([]const u8).init(self.allocator);
|
var child_argv = std.ArrayList([]const u8).init(self.allocator);
|
||||||
@ -89,7 +87,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
|
|
||||||
var server = jetzig.http.Server.init(
|
var server = jetzig.http.Server.init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
server_options,
|
self.env,
|
||||||
routes,
|
routes,
|
||||||
self.custom_routes.items,
|
self.custom_routes.items,
|
||||||
&routes_module.jobs,
|
&routes_module.jobs,
|
||||||
@ -106,7 +104,8 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
&job_queue,
|
&job_queue,
|
||||||
.{
|
.{
|
||||||
.logger = server.logger,
|
.logger = server.logger,
|
||||||
.environment = server.options.environment,
|
.vars = self.env.vars,
|
||||||
|
.environment = self.env.environment,
|
||||||
.routes = routes,
|
.routes = routes,
|
||||||
.jobs = &routes_module.jobs,
|
.jobs = &routes_module.jobs,
|
||||||
.mailers = &routes_module.mailers,
|
.mailers = &routes_module.mailers,
|
||||||
@ -127,7 +126,7 @@ pub fn start(self: *const App, routes_module: type, options: AppOptions) !void {
|
|||||||
error.AddressInUse => {
|
error.AddressInUse => {
|
||||||
try server.logger.ERROR(
|
try server.logger.ERROR(
|
||||||
"Socket unavailable: {s}:{} - unable to start server.\n",
|
"Socket unavailable: {s}:{} - unable to start server.\n",
|
||||||
.{ server_options.bind, server_options.port },
|
.{ self.env.bind, self.env.port },
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,54 @@ const jetzig = @import("../jetzig.zig");
|
|||||||
const Environment = @This();
|
const Environment = @This();
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
logger: jetzig.loggers.Logger,
|
||||||
|
bind: []const u8,
|
||||||
|
port: u16,
|
||||||
|
secret: []const u8,
|
||||||
|
detach: bool,
|
||||||
|
environment: jetzig.Environment.EnvironmentName,
|
||||||
|
vars: jetzig.Environment.Vars,
|
||||||
|
log_queue: *jetzig.loggers.LogQueue,
|
||||||
|
|
||||||
pub const EnvironmentName = enum { development, production, testing };
|
pub const EnvironmentName = enum { development, production, testing };
|
||||||
|
pub const Vars = struct {
|
||||||
|
env_map: std.process.EnvMap,
|
||||||
|
|
||||||
|
pub fn get(self: Vars, key: []const u8) ?[]const u8 {
|
||||||
|
return self.env_map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getT(self: Vars, T: type, key: []const u8) !switch (@typeInfo(T)) {
|
||||||
|
.bool => T,
|
||||||
|
else => ?T,
|
||||||
|
} {
|
||||||
|
const value = self.env_map.get(key) orelse return if (@typeInfo(T) == .bool)
|
||||||
|
false
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.int => try std.fmt.parseInt(T, value, 10),
|
||||||
|
.bool => if (std.mem.eql(u8, value, "1"))
|
||||||
|
true
|
||||||
|
else if (std.mem.eql(u8, value, "0"))
|
||||||
|
false
|
||||||
|
else
|
||||||
|
error.JetzigInvalidEnvironmentVariableBooleanValue,
|
||||||
|
.@"enum" => parseEnum(T, value),
|
||||||
|
else => @compileError("Unsupported environment value type: `" ++ @typeName(T) ++ "`"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Vars) void {
|
||||||
|
var env_map = self.env_map;
|
||||||
|
env_map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseEnum(E: type, value: []const u8) ?E {
|
||||||
|
return std.meta.stringToEnum(E, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
help: bool = false,
|
help: bool = false,
|
||||||
@ -54,17 +100,12 @@ const Options = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Environment {
|
pub fn init(allocator: std.mem.Allocator) !Environment {
|
||||||
return .{ .allocator = allocator };
|
const options = try args.parseForCurrentProcess(Options, allocator, .print);
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate server initialization options using command line args with defaults.
|
|
||||||
pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|
||||||
const options = try args.parseForCurrentProcess(Options, self.allocator, .print);
|
|
||||||
defer options.deinit();
|
defer options.deinit();
|
||||||
|
|
||||||
const log_queue = try self.allocator.create(jetzig.loggers.LogQueue);
|
const log_queue = try allocator.create(jetzig.loggers.LogQueue);
|
||||||
log_queue.* = jetzig.loggers.LogQueue.init(self.allocator);
|
log_queue.* = jetzig.loggers.LogQueue.init(allocator);
|
||||||
try log_queue.setFiles(
|
try log_queue.setFiles(
|
||||||
try getLogFile(.stdout, options.options),
|
try getLogFile(.stdout, options.options),
|
||||||
try getLogFile(.stderr, options.options),
|
try getLogFile(.stderr, options.options),
|
||||||
@ -77,18 +118,19 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const environment = options.options.environment;
|
const environment = options.options.environment;
|
||||||
|
const vars = Vars{ .env_map = try std.process.getEnvMap(allocator) };
|
||||||
|
|
||||||
var logger = switch (options.options.@"log-format") {
|
var logger = switch (options.options.@"log-format") {
|
||||||
.development => jetzig.loggers.Logger{
|
.development => jetzig.loggers.Logger{
|
||||||
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
.development_logger = jetzig.loggers.DevelopmentLogger.init(
|
||||||
self.allocator,
|
allocator,
|
||||||
resolveLogLevel(options.options.@"log-level", environment),
|
resolveLogLevel(options.options.@"log-level", environment),
|
||||||
log_queue,
|
log_queue,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
.json => jetzig.loggers.Logger{
|
.json => jetzig.loggers.Logger{
|
||||||
.json_logger = jetzig.loggers.JsonLogger.init(
|
.json_logger = jetzig.loggers.JsonLogger.init(
|
||||||
self.allocator,
|
allocator,
|
||||||
resolveLogLevel(options.options.@"log-level", environment),
|
resolveLogLevel(options.options.@"log-level", environment),
|
||||||
log_queue,
|
log_queue,
|
||||||
),
|
),
|
||||||
@ -101,7 +143,7 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const secret_len = jetzig.http.Session.Cipher.key_length;
|
const secret_len = jetzig.http.Session.Cipher.key_length;
|
||||||
const secret = (try self.getSecret(&logger, secret_len, environment))[0..secret_len];
|
const secret = (try getSecret(allocator, &logger, secret_len, environment))[0..secret_len];
|
||||||
|
|
||||||
if (secret.len != secret_len) {
|
if (secret.len != secret_len) {
|
||||||
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
|
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
|
||||||
@ -110,16 +152,24 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
.logger = logger,
|
.logger = logger,
|
||||||
.secret = secret,
|
.secret = secret,
|
||||||
.bind = try self.allocator.dupe(u8, options.options.bind),
|
.bind = try allocator.dupe(u8, options.options.bind),
|
||||||
.port = options.options.port,
|
.port = options.options.port,
|
||||||
.detach = options.options.detach,
|
.detach = options.options.detach,
|
||||||
.environment = environment,
|
.environment = environment,
|
||||||
|
.vars = vars,
|
||||||
.log_queue = log_queue,
|
.log_queue = log_queue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Environment) void {
|
||||||
|
self.vars.deinit();
|
||||||
|
self.allocator.free(self.bind);
|
||||||
|
self.allocator.free(self.secret);
|
||||||
|
}
|
||||||
|
|
||||||
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
|
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
|
||||||
const path = switch (stream) {
|
const path = switch (stream) {
|
||||||
.stdout => options.log,
|
.stdout => options.log,
|
||||||
@ -139,10 +189,15 @@ fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10, environment: EnvironmentName) ![]const u8 {
|
fn getSecret(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
logger: *jetzig.loggers.Logger,
|
||||||
|
comptime len: u10,
|
||||||
|
environment: EnvironmentName,
|
||||||
|
) ![]const u8 {
|
||||||
const env_var = "JETZIG_SECRET";
|
const env_var = "JETZIG_SECRET";
|
||||||
|
|
||||||
return std.process.getEnvVarOwned(self.allocator, env_var) catch |err| {
|
return std.process.getEnvVarOwned(allocator, env_var) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.EnvironmentVariableNotFound => {
|
error.EnvironmentVariableNotFound => {
|
||||||
if (environment != .development) {
|
if (environment != .development) {
|
||||||
@ -151,7 +206,7 @@ fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u1
|
|||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = try jetzig.util.generateSecret(self.allocator, len);
|
const secret = try jetzig.util.generateSecret(allocator, len);
|
||||||
try logger.WARN(
|
try logger.WARN(
|
||||||
"Running in development mode, using auto-generated cookie encryption key: {s}",
|
"Running in development mode, using auto-generated cookie encryption key: {s}",
|
||||||
.{secret},
|
.{secret},
|
||||||
|
@ -393,7 +393,11 @@ pub fn session(self: *Request) !*jetzig.http.Session {
|
|||||||
if (self._session) |capture| return capture;
|
if (self._session) |capture| return capture;
|
||||||
|
|
||||||
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(self.allocator, try self.cookies(), self.server.options.secret);
|
local_session.* = jetzig.http.Session.init(
|
||||||
|
self.allocator,
|
||||||
|
try self.cookies(),
|
||||||
|
self.server.env.secret,
|
||||||
|
);
|
||||||
local_session.parse() catch |err| {
|
local_session.parse() catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.JetzigInvalidSessionCookie => {
|
error.JetzigInvalidSessionCookie => {
|
||||||
@ -477,7 +481,8 @@ const RequestMail = struct {
|
|||||||
self.request.allocator,
|
self.request.allocator,
|
||||||
mail_job.params,
|
mail_job.params,
|
||||||
jetzig.jobs.JobEnv{
|
jetzig.jobs.JobEnv{
|
||||||
.environment = self.request.server.options.environment,
|
.vars = self.request.server.env.vars,
|
||||||
|
.environment = self.request.server.env.environment,
|
||||||
.logger = self.request.server.logger,
|
.logger = self.request.server.logger,
|
||||||
.routes = self.request.server.routes,
|
.routes = self.request.server.routes,
|
||||||
.mailers = self.request.server.mailer_definitions,
|
.mailers = self.request.server.mailer_definitions,
|
||||||
|
@ -6,19 +6,9 @@ const zmpl = @import("zmpl");
|
|||||||
const zmd = @import("zmd");
|
const zmd = @import("zmd");
|
||||||
const httpz = @import("httpz");
|
const httpz = @import("httpz");
|
||||||
|
|
||||||
pub const ServerOptions = struct {
|
|
||||||
logger: jetzig.loggers.Logger,
|
|
||||||
bind: []const u8,
|
|
||||||
port: u16,
|
|
||||||
secret: []const u8,
|
|
||||||
detach: bool,
|
|
||||||
environment: jetzig.Environment.EnvironmentName,
|
|
||||||
log_queue: *jetzig.loggers.LogQueue,
|
|
||||||
};
|
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
options: ServerOptions,
|
env: jetzig.Environment,
|
||||||
routes: []*jetzig.views.Route,
|
routes: []*jetzig.views.Route,
|
||||||
custom_routes: []jetzig.views.Route,
|
custom_routes: []jetzig.views.Route,
|
||||||
job_definitions: []const jetzig.JobDefinition,
|
job_definitions: []const jetzig.JobDefinition,
|
||||||
@ -35,7 +25,7 @@ const Server = @This();
|
|||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
options: ServerOptions,
|
env: jetzig.Environment,
|
||||||
routes: []*jetzig.views.Route,
|
routes: []*jetzig.views.Route,
|
||||||
custom_routes: []jetzig.views.Route,
|
custom_routes: []jetzig.views.Route,
|
||||||
job_definitions: []const jetzig.JobDefinition,
|
job_definitions: []const jetzig.JobDefinition,
|
||||||
@ -47,8 +37,8 @@ pub fn init(
|
|||||||
) Server {
|
) Server {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.logger = options.logger,
|
.logger = env.logger,
|
||||||
.options = options,
|
.env = env,
|
||||||
.routes = routes,
|
.routes = routes,
|
||||||
.custom_routes = custom_routes,
|
.custom_routes = custom_routes,
|
||||||
.job_definitions = job_definitions,
|
.job_definitions = job_definitions,
|
||||||
@ -62,8 +52,8 @@ pub fn init(
|
|||||||
|
|
||||||
pub fn deinit(self: *Server) void {
|
pub fn deinit(self: *Server) void {
|
||||||
if (self.initialized) self.std_net_server.deinit();
|
if (self.initialized) self.std_net_server.deinit();
|
||||||
self.allocator.free(self.options.secret);
|
self.allocator.free(self.env.secret);
|
||||||
self.allocator.free(self.options.bind);
|
self.allocator.free(self.env.bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dispatcher = struct {
|
const Dispatcher = struct {
|
||||||
@ -82,8 +72,8 @@ pub fn listen(self: *Server) !void {
|
|||||||
var httpz_server = try httpz.Server(Dispatcher).init(
|
var httpz_server = try httpz.Server(Dispatcher).init(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
.{
|
.{
|
||||||
.port = self.options.port,
|
.port = self.env.port,
|
||||||
.address = self.options.bind,
|
.address = self.env.bind,
|
||||||
.thread_pool = .{
|
.thread_pool = .{
|
||||||
.count = jetzig.config.get(?u16, "thread_count") orelse @intCast(try std.Thread.getCpuCount()),
|
.count = jetzig.config.get(?u16, "thread_count") orelse @intCast(try std.Thread.getCpuCount()),
|
||||||
.buffer_size = jetzig.config.get(usize, "buffer_size"),
|
.buffer_size = jetzig.config.get(usize, "buffer_size"),
|
||||||
@ -102,9 +92,9 @@ pub fn listen(self: *Server) !void {
|
|||||||
defer httpz_server.deinit();
|
defer httpz_server.deinit();
|
||||||
|
|
||||||
try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
|
try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
|
||||||
self.options.bind,
|
self.env.bind,
|
||||||
self.options.port,
|
self.env.port,
|
||||||
@tagName(self.options.environment),
|
@tagName(self.env.environment),
|
||||||
});
|
});
|
||||||
|
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
@ -262,7 +252,7 @@ fn renderJSON(
|
|||||||
|
|
||||||
if (data.value) |_| {} else _ = try data.object();
|
if (data.value) |_| {} else _ = try data.object();
|
||||||
|
|
||||||
rendered.content = if (self.options.environment == .development)
|
rendered.content = if (self.env.environment == .development)
|
||||||
try data.toJsonOptions(.{ .pretty = true, .color = false })
|
try data.toJsonOptions(.{ .pretty = true, .color = false })
|
||||||
else
|
else
|
||||||
try data.toJson();
|
try data.toJson();
|
||||||
|
@ -13,6 +13,8 @@ pub const JobEnv = struct {
|
|||||||
logger: jetzig.loggers.Logger,
|
logger: jetzig.loggers.Logger,
|
||||||
/// The current server environment, `enum { development, production }`
|
/// The current server environment, `enum { development, production }`
|
||||||
environment: jetzig.Environment.EnvironmentName,
|
environment: jetzig.Environment.EnvironmentName,
|
||||||
|
/// Environment configured at server launch
|
||||||
|
vars: jetzig.Environment.Vars,
|
||||||
/// All routes detected by Jetzig on startup
|
/// All routes detected by Jetzig on startup
|
||||||
routes: []*const jetzig.Route,
|
routes: []*const jetzig.Route,
|
||||||
/// All mailers detected by Jetzig on startup
|
/// All mailers detected by Jetzig on startup
|
||||||
|
@ -38,7 +38,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
|||||||
|
|
||||||
try mailer.deliverFn(allocator, &mail_params, &data, params.get("params").?, env);
|
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, env, .{
|
||||||
.subject = mail_params.get(.subject) orelse "(No subject)",
|
.subject = mail_params.get(.subject) orelse "(No subject)",
|
||||||
.from = mail_params.get(.from) orelse return error.JetzigMailerMissingFromAddress,
|
.from = mail_params.get(.from) orelse return error.JetzigMailerMissingFromAddress,
|
||||||
.to = mail_params.get(.to) orelse return error.JetzigMailerMissingToAddress,
|
.to = mail_params.get(.to) orelse return error.JetzigMailerMissingToAddress,
|
||||||
|
@ -8,16 +8,19 @@ allocator: std.mem.Allocator,
|
|||||||
config: jetzig.mail.SMTPConfig,
|
config: jetzig.mail.SMTPConfig,
|
||||||
params: jetzig.mail.MailParams,
|
params: jetzig.mail.MailParams,
|
||||||
boundary: u32,
|
boundary: u32,
|
||||||
|
env: jetzig.jobs.JobEnv,
|
||||||
|
|
||||||
const Mail = @This();
|
const Mail = @This();
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
env: jetzig.jobs.JobEnv,
|
||||||
params: jetzig.mail.MailParams,
|
params: jetzig.mail.MailParams,
|
||||||
) Mail {
|
) Mail {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.config = jetzig.config.get(jetzig.mail.SMTPConfig, "smtp"),
|
.config = jetzig.config.get(jetzig.mail.SMTPConfig, "smtp"),
|
||||||
|
.env = env,
|
||||||
.params = params,
|
.params = params,
|
||||||
.boundary = std.crypto.random.int(u32),
|
.boundary = std.crypto.random.int(u32),
|
||||||
};
|
};
|
||||||
@ -31,7 +34,7 @@ pub fn deliver(self: Mail) !void {
|
|||||||
.from = self.params.from.?,
|
.from = self.params.from.?,
|
||||||
.to = self.params.to.?,
|
.to = self.params.to.?,
|
||||||
.data = data,
|
.data = data,
|
||||||
}, self.config.toSMTP(self.allocator));
|
}, try self.config.toSMTP(self.allocator, self.env));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generateData(self: Mail) ![]const u8 {
|
pub fn generateData(self: Mail) ![]const u8 {
|
||||||
@ -140,6 +143,7 @@ inline fn isEncoded(char: u8) bool {
|
|||||||
test "HTML part only" {
|
test "HTML part only" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
.config = .{},
|
.config = .{},
|
||||||
.boundary = 123456789,
|
.boundary = 123456789,
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -175,6 +179,7 @@ test "HTML part only" {
|
|||||||
test "text part only" {
|
test "text part only" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
.config = .{},
|
.config = .{},
|
||||||
.boundary = 123456789,
|
.boundary = 123456789,
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -210,6 +215,7 @@ test "text part only" {
|
|||||||
test "HTML and text parts" {
|
test "HTML and text parts" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
.config = .{},
|
.config = .{},
|
||||||
.boundary = 123456789,
|
.boundary = 123456789,
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -252,6 +258,7 @@ test "HTML and text parts" {
|
|||||||
test "long content encoding" {
|
test "long content encoding" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
.config = .{},
|
.config = .{},
|
||||||
.boundary = 123456789,
|
.boundary = 123456789,
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -301,6 +308,7 @@ test "long content encoding" {
|
|||||||
test "non-latin alphabet encoding" {
|
test "non-latin alphabet encoding" {
|
||||||
const mail = Mail{
|
const mail = Mail{
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
|
.env = undefined,
|
||||||
.config = .{},
|
.config = .{},
|
||||||
.boundary = 123456789,
|
.boundary = 123456789,
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -341,3 +349,32 @@ test "non-latin alphabet encoding" {
|
|||||||
|
|
||||||
try std.testing.expectEqualStrings(expected, actual);
|
try std.testing.expectEqualStrings(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "environment SMTP config" {
|
||||||
|
var env: jetzig.jobs.JobEnv = undefined;
|
||||||
|
var env_map = std.process.EnvMap.init(std.testing.allocator);
|
||||||
|
defer env_map.deinit();
|
||||||
|
|
||||||
|
try env_map.put("JETZIG_SMTP_PORT", "999");
|
||||||
|
try env_map.put("JETZIG_SMTP_ENCRYPTION", "start_tls");
|
||||||
|
try env_map.put("JETZIG_SMTP_HOST", "smtp.example.com");
|
||||||
|
try env_map.put("JETZIG_SMTP_USERNAME", "example-username");
|
||||||
|
try env_map.put("JETZIG_SMTP_PASSWORD", "example-password");
|
||||||
|
|
||||||
|
env.vars = jetzig.Environment.Vars{ .env_map = env_map };
|
||||||
|
|
||||||
|
const mail = Mail{
|
||||||
|
.allocator = undefined,
|
||||||
|
.env = undefined,
|
||||||
|
.config = .{},
|
||||||
|
.boundary = undefined,
|
||||||
|
.params = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = try mail.config.toSMTP(std.testing.allocator, env);
|
||||||
|
try std.testing.expect(config.port == 999);
|
||||||
|
try std.testing.expect(config.encryption == .start_tls);
|
||||||
|
try std.testing.expectEqualStrings("smtp.example.com", config.host);
|
||||||
|
try std.testing.expectEqualStrings("example-username", config.username.?);
|
||||||
|
try std.testing.expectEqualStrings("example-password", config.password.?);
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ const std = @import("std");
|
|||||||
|
|
||||||
const smtp = @import("smtp");
|
const smtp = @import("smtp");
|
||||||
|
|
||||||
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
port: u16 = 25,
|
port: u16 = 25,
|
||||||
encryption: enum { none, insecure, tls, start_tls } = .none,
|
encryption: enum { none, insecure, tls, start_tls } = .none,
|
||||||
host: []const u8 = "localhost",
|
host: []const u8 = "localhost",
|
||||||
@ -10,14 +12,19 @@ password: ?[]const u8 = null,
|
|||||||
|
|
||||||
const SMTPConfig = @This();
|
const SMTPConfig = @This();
|
||||||
|
|
||||||
pub fn toSMTP(self: SMTPConfig, allocator: std.mem.Allocator) smtp.Config {
|
pub fn toSMTP(
|
||||||
|
self: SMTPConfig,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
env: jetzig.jobs.JobEnv,
|
||||||
|
) !smtp.Config {
|
||||||
return smtp.Config{
|
return smtp.Config{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.port = self.port,
|
.port = try env.vars.getT(u16, "JETZIG_SMTP_PORT") orelse self.port,
|
||||||
.encryption = self.getEncryption(),
|
.encryption = try env.vars.getT(smtp.Encryption, "JETZIG_SMTP_ENCRYPTION") orelse
|
||||||
.host = self.host,
|
self.getEncryption(),
|
||||||
.username = self.username,
|
.host = env.vars.get("JETZIG_SMTP_HOST") orelse self.host,
|
||||||
.password = self.password,
|
.username = env.vars.get("JETZIG_SMTP_USERNAME") orelse self.username,
|
||||||
|
.password = env.vars.get("JETZIG_SMTP_PASSWORD") orelse self.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +87,16 @@ pub fn request(
|
|||||||
|
|
||||||
const logger = jetzig.loggers.Logger{ .test_logger = jetzig.loggers.TestLogger{} };
|
const logger = jetzig.loggers.Logger{ .test_logger = jetzig.loggers.TestLogger{} };
|
||||||
var log_queue = jetzig.loggers.LogQueue.init(allocator);
|
var log_queue = jetzig.loggers.LogQueue.init(allocator);
|
||||||
|
// We init the `std.process.EnvMap` directly here (instead of calling `std.process.getEnvMap`
|
||||||
|
// to ensure that tests run in a clean environment. Users can manually add items to the
|
||||||
|
// environment within a test if required.
|
||||||
|
const vars = jetzig.Environment.Vars{ .env_map = std.process.EnvMap.init(allocator) };
|
||||||
var server = jetzig.http.Server{
|
var server = jetzig.http.Server{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.logger = logger,
|
.logger = logger,
|
||||||
.options = .{
|
.env = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.vars = vars,
|
||||||
.logger = logger,
|
.logger = logger,
|
||||||
.bind = undefined,
|
.bind = undefined,
|
||||||
.port = undefined,
|
.port = undefined,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user