atproto pds in zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164const std = @import("std");const config = @import("config.zig");const log = @import("log.zig");const zat = @import("zat");
pub fn send( io: std.Io, allocator: std.mem.Allocator, to: []const u8, handle: []const u8, subject: []const u8, text: []const u8,) !void { return switch (config.mailProvider()) { .comail => sendComail(io, allocator, to, handle, subject, text), .resend => sendResend(io, allocator, to, handle, subject, text), };}
fn sendComail( io: std.Io, allocator: std.mem.Allocator, to: []const u8, handle: []const u8, subject: []const u8, text: []const u8,) !void { const maybe_api_key = config.comailApiKey(); const maybe_did = config.comailDid(); const maybe_from = config.emailFrom(); if (maybe_api_key == null or maybe_did == null or maybe_from == null) { return missingConfig( "comail", subject, to, handle, text, "missing_comail_api_key={} missing_comail_did={} missing_email_from={}", .{ maybe_api_key == null, maybe_did == null, maybe_from == null }, ); }
const payload = try std.fmt.allocPrint( allocator, "{{\"from\":{f},\"to\":{f},\"subject\":{f},\"text\":{f},\"category\":\"verification\"}}", .{ std.json.fmt(maybe_from.?, .{}), std.json.fmt(to, .{}), std.json.fmt(subject, .{}), std.json.fmt(text, .{}) }, ); const authorization = try std.fmt.allocPrint(allocator, "Bearer {s}", .{maybe_api_key.?}); const headers = [_]std.http.Header{ .{ .name = "X-Atmos-DID", .value = maybe_did.? }, }; var transport = zat.HttpTransport.init(io, allocator); defer transport.deinit(); const result = try transport.fetch(.{ .url = "https://smtp.atmos.email/v1/send", .method = .POST, .payload = payload, .authorization = authorization, .content_type = "application/json", .extra_headers = &headers, .max_response_size = 64 * 1024, }); defer allocator.free(result.body); return requireOk("comail", result.status, result.body);}
fn sendResend( io: std.Io, allocator: std.mem.Allocator, to: []const u8, handle: []const u8, subject: []const u8, text: []const u8,) !void { const maybe_api_key = config.resendApiKey(); const maybe_from = config.emailFrom(); if (maybe_api_key == null or maybe_from == null) { return missingConfig( "resend", subject, to, handle, text, "missing_resend_api_key={} missing_email_from={}", .{ maybe_api_key == null, maybe_from == null }, ); }
const payload = try std.fmt.allocPrint( allocator, "{{\"from\":{f},\"to\":[{f}],\"subject\":{f},\"text\":{f}}}", .{ std.json.fmt(maybe_from.?, .{}), std.json.fmt(to, .{}), std.json.fmt(subject, .{}), std.json.fmt(text, .{}) }, ); const authorization = try std.fmt.allocPrint(allocator, "Bearer {s}", .{maybe_api_key.?}); var transport = zat.HttpTransport.init(io, allocator); defer transport.deinit(); const result = try transport.fetch(.{ .url = "https://api.resend.com/emails", .method = .POST, .payload = payload, .authorization = authorization, .content_type = "application/json", .max_response_size = 64 * 1024, }); defer allocator.free(result.body); return requireOk("resend", result.status, result.body);}
pub fn sendCode( io: std.Io, allocator: std.mem.Allocator, to: []const u8, handle: []const u8, subject: []const u8, label: []const u8, code: []const u8,) !void { const text = try std.fmt.allocPrint( allocator, "Hello {s}. Your {s} code is {s}. This code will expire in ten minutes.", .{ handle, label, code }, ); return send(io, allocator, to, handle, subject, text);}
fn logMail(subject: []const u8, email: []const u8, handle: []const u8, text: []const u8) void { log.info("[zds mail] {s} for {s} <{s}>: {s}\n", .{ subject, handle, email, text });}
fn missingConfig( comptime provider: []const u8, subject: []const u8, to: []const u8, handle: []const u8, text: []const u8, comptime fields_fmt: []const u8, fields: anytype,) !void { if (isLocalDelivery()) { logMail(subject, to, handle, text); return; } log.err("mail delivery disabled provider=" ++ provider ++ " " ++ fields_fmt ++ " subject={s} to={s}\n", fields ++ .{ subject, to }); return error.EmailDeliveryFailed;}
fn requireOk(comptime provider: []const u8, status: std.http.Status, body: []const u8) !void { const code = @intFromEnum(status); if (code >= 200 and code < 300) return; log.err("mail delivery failed provider=" ++ provider ++ " status={d} body={s}\n", .{ code, bodyExcerpt(body) }); return error.EmailDeliveryFailed;}
fn bodyExcerpt(body: []const u8) []const u8 { return body[0..@min(body.len, 512)];}
fn isLocalDelivery() bool { const public_url = config.publicUrl(); return std.mem.startsWith(u8, public_url, "http://localhost") or std.mem.startsWith(u8, public_url, "http://127.0.0.1") or std.mem.startsWith(u8, public_url, "http://[::1]");}