fix: email
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -95,7 +95,7 @@
|
|||||||
"@orpc/zod": "catalog:",
|
"@orpc/zod": "catalog:",
|
||||||
"dotenv": "catalog:",
|
"dotenv": "catalog:",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.2",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1511,7 +1511,7 @@
|
|||||||
|
|
||||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||||
|
|
||||||
"nodemailer": ["nodemailer@8.0.1", "", {}, "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg=="],
|
"nodemailer": ["nodemailer@8.0.2", "", {}, "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw=="],
|
||||||
|
|
||||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||||
|
|
||||||
@@ -1987,8 +1987,6 @@
|
|||||||
|
|
||||||
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@kk/auth/nodemailer": ["nodemailer@8.0.2", "", {}, "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw=="],
|
|
||||||
|
|
||||||
"@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
"@noble/curves/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"@orpc/zod": "catalog:",
|
"@orpc/zod": "catalog:",
|
||||||
"dotenv": "catalog:",
|
"dotenv": "catalog:",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"nodemailer": "^8.0.1",
|
"nodemailer": "^8.0.2",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
9
packages/api/src/constants.ts
Normal file
9
packages/api/src/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Single source-of-truth for event details used across the API
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const EVENT = "Open Mic Night — vrijdag 18 april 2026";
|
||||||
|
export const OPENS = "maandag 16 maart 2026 om 19:00";
|
||||||
|
|
||||||
|
// Registration opens — used for reminder scheduling windows
|
||||||
|
export const REGISTRATION_OPENS_AT = new Date("2026-03-16T19:00:00+01:00");
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -348,7 +348,7 @@ export const drinkkaartRouter = {
|
|||||||
.then((r) => r[0]);
|
.then((r) => r[0]);
|
||||||
|
|
||||||
if (cardUser) {
|
if (cardUser) {
|
||||||
sendDeductionEmail({
|
await sendDeductionEmail({
|
||||||
to: cardUser.email,
|
to: cardUser.email,
|
||||||
firstName: cardUser.name.split(" ")[0] ?? cardUser.name,
|
firstName: cardUser.name.split(" ")[0] ?? cardUser.name,
|
||||||
amountCents: input.amountCents,
|
amountCents: input.amountCents,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
sum,
|
sum,
|
||||||
} from "drizzle-orm";
|
} from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { REGISTRATION_OPENS_AT } from "../constants";
|
||||||
import {
|
import {
|
||||||
emailLog,
|
emailLog,
|
||||||
sendCancellationEmail,
|
sendCancellationEmail,
|
||||||
@@ -32,8 +33,7 @@ import { adminProcedure, protectedProcedure, publicProcedure } from "../index";
|
|||||||
import { generateQrSecret } from "../lib/drinkkaart-utils";
|
import { generateQrSecret } from "../lib/drinkkaart-utils";
|
||||||
import { drinkkaartRouter } from "./drinkkaart";
|
import { drinkkaartRouter } from "./drinkkaart";
|
||||||
|
|
||||||
// Registration opens at this date — reminders fire 24 hours and 1 hour before
|
// Reminder windows derived from the canonical registration open date
|
||||||
const REGISTRATION_OPENS_AT = new Date("2026-03-16T19:00:00+01:00");
|
|
||||||
const REMINDER_1H_WINDOW_START = new Date(
|
const REMINDER_1H_WINDOW_START = new Date(
|
||||||
REGISTRATION_OPENS_AT.getTime() - 60 * 60 * 1000,
|
REGISTRATION_OPENS_AT.getTime() - 60 * 60 * 1000,
|
||||||
);
|
);
|
||||||
@@ -879,13 +879,15 @@ export const appRouter = {
|
|||||||
email: input.email,
|
email: input.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fire-and-forget — don't let a mail failure block the response
|
// Awaited — CF Workers abandon unawaited promises when the response
|
||||||
sendSubscriptionConfirmationEmail({ to: input.email }).catch((err) =>
|
// returns. Mail errors are caught so they don't fail the request.
|
||||||
emailLog("error", "email.catch", {
|
await sendSubscriptionConfirmationEmail({ to: input.email }).catch(
|
||||||
type: "subscription_confirmation",
|
(err) =>
|
||||||
to: input.email,
|
emailLog("error", "email.catch", {
|
||||||
error: String(err),
|
type: "subscription_confirmation",
|
||||||
}),
|
to: input.email,
|
||||||
|
error: String(err),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
|
|||||||
4
packages/env/src/server.ts
vendored
4
packages/env/src/server.ts
vendored
@@ -15,13 +15,13 @@ export const env = createEnv({
|
|||||||
server: {
|
server: {
|
||||||
DATABASE_URL: z.string().min(1),
|
DATABASE_URL: z.string().min(1),
|
||||||
BETTER_AUTH_SECRET: z.string().min(32),
|
BETTER_AUTH_SECRET: z.string().min(32),
|
||||||
BETTER_AUTH_URL: z.url(),
|
BETTER_AUTH_URL: z.url().default("https://kunstenkamp.be"),
|
||||||
CORS_ORIGIN: z.url(),
|
CORS_ORIGIN: z.url(),
|
||||||
SMTP_HOST: z.string().min(1).optional(),
|
SMTP_HOST: z.string().min(1).optional(),
|
||||||
SMTP_PORT: z.coerce.number().default(587),
|
SMTP_PORT: z.coerce.number().default(587),
|
||||||
SMTP_USER: z.string().min(1).optional(),
|
SMTP_USER: z.string().min(1).optional(),
|
||||||
SMTP_PASS: z.string().min(1).optional(),
|
SMTP_PASS: z.string().min(1).optional(),
|
||||||
SMTP_FROM: z.string().min(1).optional(),
|
SMTP_FROM: z.string().min(1).default("Kunstenkamp <info@kunstenkamp.be>"),
|
||||||
NODE_ENV: z
|
NODE_ENV: z
|
||||||
.enum(["development", "production", "test"])
|
.enum(["development", "production", "test"])
|
||||||
.default("development"),
|
.default("development"),
|
||||||
|
|||||||
Reference in New Issue
Block a user