feat: add email notification settings and test email functionality
- Created a new migration to add email settings to the database. - Implemented routes for managing notification settings, including retrieving and updating settings. - Added functionality to send test emails using SMTP configuration from environment variables.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
-- Add email notification settings to settings table
|
||||
ALTER TABLE settings ADD COLUMN email_enabled INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE settings ADD COLUMN notification_email TEXT;
|
||||
ALTER TABLE settings ADD COLUMN reminder_days_before INTEGER NOT NULL DEFAULT 7;
|
||||
@@ -2,6 +2,7 @@
|
||||
"entries": [
|
||||
{ "idx": 0, "version": 1, "when": 1734633120, "tag": "0000_init", "breakpoint": false },
|
||||
{ "idx": 1, "version": 1, "when": 1734633121, "tag": "0001_add_strips", "breakpoint": false },
|
||||
{ "idx": 2, "version": 1, "when": 1734633122, "tag": "0002_pack_inventory", "breakpoint": false }
|
||||
{ "idx": 2, "version": 1, "when": 1734633122, "tag": "0002_pack_inventory", "breakpoint": false },
|
||||
{ "idx": 3, "version": 1, "when": 1734710400, "tag": "0003_add_email_settings", "breakpoint": false }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -45,5 +45,9 @@ export const settings = sqliteTable("settings", {
|
||||
smtpFrom: text("smtp_from"),
|
||||
smtpSecure: integer("smtp_secure", { mode: "boolean" }).notNull().default(false),
|
||||
emailsPerDay: integer("emails_per_day").notNull().default(3),
|
||||
// Email notification settings
|
||||
emailEnabled: integer("email_enabled", { mode: "boolean" }).notNull().default(false),
|
||||
notificationEmail: text("notification_email"),
|
||||
reminderDaysBefore: integer("reminder_days_before").notNull().default(7),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { env } from "./plugins/env.js";
|
||||
import { healthRoutes } from "./routes/health.js";
|
||||
import { authRoutes } from "./routes/auth.js";
|
||||
import { medicationRoutes } from "./routes/medications.js";
|
||||
import { settingsRoutes } from "./routes/settings.js";
|
||||
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
@@ -56,6 +57,7 @@ await app.register(jwt, { secret: env.JWT_SECRET, cookie: { cookieName: "access_
|
||||
await app.register(healthRoutes);
|
||||
await app.register(authRoutes);
|
||||
await app.register(medicationRoutes);
|
||||
await app.register(settingsRoutes);
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import nodemailer from "nodemailer";
|
||||
import { readFileSync, writeFileSync, existsSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
|
||||
type SettingsBody = {
|
||||
emailEnabled: boolean;
|
||||
notificationEmail: string;
|
||||
reminderDaysBefore: number;
|
||||
};
|
||||
|
||||
type TestEmailBody = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
// Notification settings are stored in a JSON file (user-configurable)
|
||||
// SMTP settings come from .env (admin-configured)
|
||||
const notificationSettingsFile = resolve(process.cwd(), "data", "notification-settings.json");
|
||||
|
||||
type NotificationSettings = {
|
||||
emailEnabled: boolean;
|
||||
notificationEmail: string;
|
||||
reminderDaysBefore: number;
|
||||
};
|
||||
|
||||
function loadNotificationSettings(): NotificationSettings {
|
||||
try {
|
||||
if (existsSync(notificationSettingsFile)) {
|
||||
return JSON.parse(readFileSync(notificationSettingsFile, "utf-8"));
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return { emailEnabled: false, notificationEmail: "", reminderDaysBefore: 7 };
|
||||
}
|
||||
|
||||
function saveNotificationSettings(settings: NotificationSettings): void {
|
||||
writeFileSync(notificationSettingsFile, JSON.stringify(settings, null, 2));
|
||||
}
|
||||
|
||||
export async function settingsRoutes(app: FastifyInstance) {
|
||||
// Get settings - notification from JSON file, SMTP from process.env
|
||||
app.get("/settings", async (_request, reply) => {
|
||||
const notification = loadNotificationSettings();
|
||||
|
||||
return reply.send({
|
||||
// Notification settings (user-configurable, stored in JSON)
|
||||
emailEnabled: notification.emailEnabled,
|
||||
notificationEmail: notification.notificationEmail,
|
||||
reminderDaysBefore: notification.reminderDaysBefore,
|
||||
// SMTP settings (admin-configured, from .env)
|
||||
smtpHost: process.env.SMTP_HOST ?? "",
|
||||
smtpPort: parseInt(process.env.SMTP_PORT ?? "587"),
|
||||
smtpUser: process.env.SMTP_USER ?? "",
|
||||
smtpFrom: process.env.SMTP_FROM ?? "",
|
||||
smtpSecure: process.env.SMTP_SECURE === "true",
|
||||
hasSmtpPassword: !!process.env.SMTP_PASS,
|
||||
});
|
||||
});
|
||||
|
||||
// Update settings - only notification settings are saved (SMTP comes from .env)
|
||||
app.put<{ Body: SettingsBody }>("/settings", async (request, reply) => {
|
||||
const body = request.body;
|
||||
|
||||
// Save notification settings to JSON file
|
||||
saveNotificationSettings({
|
||||
emailEnabled: body.emailEnabled,
|
||||
notificationEmail: body.notificationEmail,
|
||||
reminderDaysBefore: body.reminderDaysBefore,
|
||||
});
|
||||
|
||||
return reply.send({ success: true });
|
||||
});
|
||||
|
||||
// Test email - use SMTP settings from process.env
|
||||
app.post<{ Body: TestEmailBody }>("/settings/test-email", async (request, reply) => {
|
||||
const { email } = request.body;
|
||||
|
||||
const smtpHost = process.env.SMTP_HOST;
|
||||
const smtpUser = process.env.SMTP_USER;
|
||||
const smtpPass = process.env.SMTP_PASS;
|
||||
const smtpPort = parseInt(process.env.SMTP_PORT ?? "587");
|
||||
const smtpSecure = process.env.SMTP_SECURE === "true";
|
||||
const smtpFrom = process.env.SMTP_FROM ?? smtpUser;
|
||||
|
||||
if (!smtpHost || !smtpUser) {
|
||||
return reply.status(400).send({ error: "SMTP not configured" });
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: smtpHost,
|
||||
port: smtpPort,
|
||||
secure: smtpSecure,
|
||||
auth: {
|
||||
user: smtpUser,
|
||||
pass: smtpPass ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
await transporter.sendMail({
|
||||
from: smtpFrom,
|
||||
to: email,
|
||||
subject: "MedAssist - Test Email",
|
||||
text: "This is a test email from MedAssist. If you received this, your email configuration is working correctly!",
|
||||
html: `
|
||||
<div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h2 style="color: #2563eb;">MedAssist - Test Email</h2>
|
||||
<p>This is a test email from MedAssist.</p>
|
||||
<p style="color: #10b981; font-weight: 600;">✓ If you received this, your email configuration is working correctly!</p>
|
||||
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;" />
|
||||
<p style="color: #6b7280; font-size: 14px;">Sent from MedAssist Medication Planner</p>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
return reply.send({ success: true, message: "Test email sent successfully" });
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
return reply.status(500).send({ error: `Failed to send email: ${errorMessage}` });
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user