From fcebb272a6ac7c021140c272c5fd1ca118b4738c Mon Sep 17 00:00:00 2001 From: Yi Xhan Date: Mon, 1 Dec 2025 15:51:39 -0500 Subject: [PATCH 1/2] email on cc change - i don't know if this is working but the console log seems good --- .../email/balance-change-template.ts | 68 +++++++++++++++++++ server/src/integrations/email/email.ts | 12 ++++ .../src/resolvers/currencyAccountResolver.ts | 7 ++ 3 files changed, 87 insertions(+) create mode 100644 server/src/integrations/email/balance-change-template.ts diff --git a/server/src/integrations/email/balance-change-template.ts b/server/src/integrations/email/balance-change-template.ts new file mode 100644 index 000000000..71d4e6e96 --- /dev/null +++ b/server/src/integrations/email/balance-change-template.ts @@ -0,0 +1,68 @@ +import ejs from "ejs" +import { centsToDollarString } from "../currency/currency.js" +import { getAccountOwner } from "../../repositories/Currency/CurrencyAccountsRepository.js"; + +const templateSource: string = ` + + + +
+ RIT SHED Logo +

Account Balance Modification Notice

+ + <% if (info.type == "credit") { %> +

Your balance has been credited <%= formatCents(info.amount) %>

+ <% } else { %> +

Your balance has been charged <%= formatCents(info.amount) %>

+ <% } %> + + +Reason: <%= info.desc %> + +
+` + +let template = ejs.compile(templateSource, { async: false }) + +function generateHTMLChange(desc:balChangeInfo) { + let data = { + info: desc, + formatCents: centsToDollarString, + }; + return template(data); +} + +function generateTextChange(desc: balChangeInfo) { + return desc.type +} + +export type balChangeInfo ={ + amount: number + type: "credit" | "charge" + desc: string +} + +export function generateBalanceChangeEmail(desc: balChangeInfo): {text: string, html: string} { + const text = generateTextChange(desc); + const html = generateHTMLChange(desc); + return {text, html} +} diff --git a/server/src/integrations/email/email.ts b/server/src/integrations/email/email.ts index a3c58dbe2..854ce39c3 100644 --- a/server/src/integrations/email/email.ts +++ b/server/src/integrations/email/email.ts @@ -2,6 +2,7 @@ import FormData from "form-data"; import * as Mailgun from "mailgun.js" import { generateReceiptEmail } from "./receipt-template.js" import { generateExpiryEmail, ExpiryDescription } from "./training-expiry-template.js" +import { balChangeInfo, generateBalanceChangeEmail } from "./balance-change-template.js"; const mailgun = new Mailgun.default(FormData); const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY || 'key-yourkeyhere' }); const MAIL_DOMAIN = process.env.MAIL_DOMAIN ?? ""; @@ -69,4 +70,15 @@ export async function send_training_expiry_email(email: string, desc: ExpiryDesc textContent: content.text, htmlContent: content.html, }) +} + +export async function send_cc_balance_change_email(email: string, desc: balChangeInfo) { + const content = generateBalanceChangeEmail(desc); + send_generic_email({ + fromAccount: "cc", + to: [email], + subject: "RIT SHED: Account Balance Adjusted - " + new Date().toLocaleDateString(), + textContent: content.text, + htmlContent: content.html, + }) } \ No newline at end of file diff --git a/server/src/resolvers/currencyAccountResolver.ts b/server/src/resolvers/currencyAccountResolver.ts index af8cea8e2..cd075bfce 100644 --- a/server/src/resolvers/currencyAccountResolver.ts +++ b/server/src/resolvers/currencyAccountResolver.ts @@ -1,5 +1,6 @@ import { ApolloContext } from "../context.js"; import { CurrencyAccountsRow } from "../db/tables.js"; +import { send_cc_balance_change_email } from "../integrations/email/email.js"; import * as CurrencyAccountRepo from "../repositories/Currency/CurrencyAccountsRepository.js" export const CurrencyAccountResolvers = { @@ -45,6 +46,12 @@ export const CurrencyAccountResolvers = { }, { isManager }: ApolloContext ) => { + const owner = await CurrencyAccountRepo.getAccountOwner(args.accountID); + send_cc_balance_change_email(owner?.username + "@rit.edu", { + amount: Math.abs(args.amount), + type: args.amount > 0 ? "credit" : "charge", + desc: args.description, + }); return isManager(async (user) => ( await CurrencyAccountRepo.adjustAccountBalanceCents(args.accountID, args.amount, "make-website", `adjustment by ${user.ritUsername}: ${args.description}`) )) From 95d0d96c136dce69babe26df77a59ec1a16f0fac Mon Sep 17 00:00:00 2001 From: Yi Xhan Date: Fri, 12 Dec 2025 14:22:25 -0500 Subject: [PATCH 2/2] might as well toss in another automated email --- .../email/balance-change-template.ts | 3 +- server/src/integrations/email/email.ts | 12 +++++ .../email/hold-placed-template.ts | 52 +++++++++++++++++++ server/src/resolvers/holdsResolver.ts | 2 + 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 server/src/integrations/email/hold-placed-template.ts diff --git a/server/src/integrations/email/balance-change-template.ts b/server/src/integrations/email/balance-change-template.ts index 71d4e6e96..e61567083 100644 --- a/server/src/integrations/email/balance-change-template.ts +++ b/server/src/integrations/email/balance-change-template.ts @@ -35,8 +35,7 @@ const templateSource: string = `

Your balance has been charged <%= formatCents(info.amount) %>

<% } %> - -Reason: <%= info.desc %> +

Reason: <%= info.desc %>

` diff --git a/server/src/integrations/email/email.ts b/server/src/integrations/email/email.ts index 854ce39c3..e6f9054c7 100644 --- a/server/src/integrations/email/email.ts +++ b/server/src/integrations/email/email.ts @@ -3,6 +3,7 @@ import * as Mailgun from "mailgun.js" import { generateReceiptEmail } from "./receipt-template.js" import { generateExpiryEmail, ExpiryDescription } from "./training-expiry-template.js" import { balChangeInfo, generateBalanceChangeEmail } from "./balance-change-template.js"; +import { generateHoldPlacedEmail } from "./hold-placed-template.js"; const mailgun = new Mailgun.default(FormData); const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY || 'key-yourkeyhere' }); const MAIL_DOMAIN = process.env.MAIL_DOMAIN ?? ""; @@ -81,4 +82,15 @@ export async function send_cc_balance_change_email(email: string, desc: balChang textContent: content.text, htmlContent: content.html, }) +} + +export async function send_hold_placed_email(email: string, desc: string) { + const content = generateHoldPlacedEmail(desc); + send_generic_email({ + fromAccount: "holds", + to: [email], + subject: "RIT SHED: Hold Placed On Account - " + new Date().toLocaleDateString(), + textContent: content.text, + htmlContent: content.html, + }) } \ No newline at end of file diff --git a/server/src/integrations/email/hold-placed-template.ts b/server/src/integrations/email/hold-placed-template.ts new file mode 100644 index 000000000..2237f8f04 --- /dev/null +++ b/server/src/integrations/email/hold-placed-template.ts @@ -0,0 +1,52 @@ +import ejs from "ejs" +import { centsToDollarString } from "../currency/currency.js" +import { getAccountOwner } from "../../repositories/Currency/CurrencyAccountsRepository.js"; + +const templateSource: string = ` + + + +` + +let template = ejs.compile(templateSource, { async: false }) + +function generateHTMLHold(desc: string) { + let data = { + desc: desc, + }; + return template(data); +} + +export function generateHoldPlacedEmail(desc: string): {text: string, html: string} { + const html = generateHTMLHold(desc); + return {text: html, html} +} \ No newline at end of file diff --git a/server/src/resolvers/holdsResolver.ts b/server/src/resolvers/holdsResolver.ts index 41e0e5264..31df9f4db 100644 --- a/server/src/resolvers/holdsResolver.ts +++ b/server/src/resolvers/holdsResolver.ts @@ -4,6 +4,7 @@ import * as UsersRepo from "../repositories/Users/UserRepository.js"; import { createLog } from "../repositories/AuditLogs/AuditLogRepository.js"; import { getUsersFullName } from "../repositories/Users/UserRepository.js"; import { HoldRow } from "../db/tables.js"; +import { send_hold_placed_email } from "../integrations/email/email.js"; const HoldsResolvers = { Hold: { @@ -51,6 +52,7 @@ const HoldsResolvers = { ); await UsersRepo.setActiveHold(Number(args.userID), true) + send_hold_placed_email(user.ritUsername + "@rit.edu", args.description); return HoldsRepo.createHold(user.id, Number(args.userID), args.description); }),