Test
This commit is contained in:
7
app/api/health/route.ts
Normal file
7
app/api/health/route.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { logLine } from "@/lib/logger";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
logLine("health.hit", { ok: true });
|
||||||
|
return NextResponse.json({ ok: true });
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import { prisma } from "@/lib/prisma";
|
|||||||
import { DEFAULT_ALERTS, DEFAULT_DEFAULTS, DEFAULT_SHIFT } from "@/lib/settings";
|
import { DEFAULT_ALERTS, DEFAULT_DEFAULTS, DEFAULT_SHIFT } from "@/lib/settings";
|
||||||
import { buildVerifyEmail, sendEmail } from "@/lib/email";
|
import { buildVerifyEmail, sendEmail } from "@/lib/email";
|
||||||
import { getBaseUrl } from "@/lib/appUrl";
|
import { getBaseUrl } from "@/lib/appUrl";
|
||||||
|
import { logLine } from "@/lib/logger";
|
||||||
|
|
||||||
|
|
||||||
function slugify(input: string) {
|
function slugify(input: string) {
|
||||||
const trimmed = input.trim().toLowerCase();
|
const trimmed = input.trim().toLowerCase();
|
||||||
@@ -120,10 +122,16 @@ export async function POST(req: Request) {
|
|||||||
text: emailContent.text,
|
text: emailContent.text,
|
||||||
html: emailContent.html,
|
html: emailContent.html,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (err: any) {
|
||||||
emailSent = false;
|
emailSent = false;
|
||||||
|
logLine("signup.verify_email.failed", {
|
||||||
|
email,
|
||||||
|
message: err?.message,
|
||||||
|
code: err?.code,
|
||||||
|
response: err?.response,
|
||||||
|
responseCode: err?.responseCode,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
verificationRequired: true,
|
verificationRequired: true,
|
||||||
|
|||||||
66
lib/email.ts
66
lib/email.ts
@@ -1,4 +1,6 @@
|
|||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
|
import { logLine } from "@/lib/logger";
|
||||||
|
|
||||||
|
|
||||||
type EmailPayload = {
|
type EmailPayload = {
|
||||||
to: string;
|
to: string;
|
||||||
@@ -25,11 +27,23 @@ function getTransporter() {
|
|||||||
throw new Error("SMTP not configured");
|
throw new Error("SMTP not configured");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const smtpDebug = process.env.SMTP_DEBUG === "true";
|
||||||
|
logLine("smtp.config", {
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
secure,
|
||||||
|
user,
|
||||||
|
from: process.env.SMTP_FROM,
|
||||||
|
smtpDebug,
|
||||||
|
});
|
||||||
|
|
||||||
cachedTransport = nodemailer.createTransport({
|
cachedTransport = nodemailer.createTransport({
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
secure,
|
secure,
|
||||||
auth: { user, pass },
|
auth: { user, pass },
|
||||||
|
logger: smtpDebug,
|
||||||
|
debug: smtpDebug,
|
||||||
});
|
});
|
||||||
|
|
||||||
return cachedTransport;
|
return cachedTransport;
|
||||||
@@ -40,15 +54,55 @@ export async function sendEmail(payload: EmailPayload) {
|
|||||||
if (!from) {
|
if (!from) {
|
||||||
throw new Error("SMTP_FROM not configured");
|
throw new Error("SMTP_FROM not configured");
|
||||||
}
|
}
|
||||||
|
logLine("email.send.start", {
|
||||||
const transporter = getTransporter();
|
|
||||||
return transporter.sendMail({
|
|
||||||
from,
|
|
||||||
to: payload.to,
|
to: payload.to,
|
||||||
subject: payload.subject,
|
subject: payload.subject,
|
||||||
text: payload.text,
|
from,
|
||||||
html: payload.html,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const transporter = getTransporter();
|
||||||
|
try {
|
||||||
|
const info = await transporter.sendMail({
|
||||||
|
from,
|
||||||
|
to: payload.to,
|
||||||
|
subject: payload.subject,
|
||||||
|
text: payload.text,
|
||||||
|
html: payload.html,
|
||||||
|
headers: {
|
||||||
|
"X-Mailer": "MIS Control Tower",
|
||||||
|
},
|
||||||
|
|
||||||
|
replyTo: from,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nodemailer response details:
|
||||||
|
logLine("email.send.ok", {
|
||||||
|
to: payload.to,
|
||||||
|
from,
|
||||||
|
messageId: info.messageId,
|
||||||
|
response: info.response,
|
||||||
|
accepted: info.accepted,
|
||||||
|
rejected: info.rejected,
|
||||||
|
pending: (info as any).pending,
|
||||||
|
});
|
||||||
|
|
||||||
|
return info;
|
||||||
|
} catch (err: any) {
|
||||||
|
logLine("email.send.err", {
|
||||||
|
to: payload.to,
|
||||||
|
from,
|
||||||
|
name: err?.name,
|
||||||
|
message: err?.message,
|
||||||
|
code: err?.code,
|
||||||
|
command: err?.command,
|
||||||
|
response: err?.response,
|
||||||
|
responseCode: err?.responseCode,
|
||||||
|
stack: err?.stack,
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildVerifyEmail(params: { appName: string; verifyUrl: string }) {
|
export function buildVerifyEmail(params: { appName: string; verifyUrl: string }) {
|
||||||
|
|||||||
20
lib/logger.ts
Normal file
20
lib/logger.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const LOG_PATH = process.env.LOG_FILE || "/tmp/mis-control-tower.log";
|
||||||
|
|
||||||
|
export function logLine(event: string, data: Record<string, any> = {}) {
|
||||||
|
const line = JSON.stringify({
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
event,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
console.log(line);
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
|
||||||
|
fs.appendFileSync(LOG_PATH, line + "\n", { encoding: "utf8" });
|
||||||
|
} catch {
|
||||||
|
// If file logging fails, we still want something:
|
||||||
|
console.error("[logLine-failed]", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user