-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/send notification #384
Changes from all commits
7b7768a
28d9376
77a9b7d
8511323
dfeea58
64acbc7
55d74fa
1605f46
05d4aea
8ba4d6a
80f363e
e290ad3
2b23b2d
9039fbc
536e91e
2ec7a87
688c2af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,6 +127,8 @@ jobs: | |
kubectl set env deployment/cc-web-deploy CHAT_PROVIDER=openai | ||
kubectl set env deployment/cc-web-deploy OPENAI_API_KEY=${{secrets.OPENAI_API_KEY}} | ||
kubectl set env deployment/cc-web-deploy HUGGINGFACE_API_KEY=${{secrets.HUGGINGFACE_API_KEY}} | ||
kubectl set env deployment/cc-web-deploy ADMIN_EMAILS="[email protected],[email protected],[email protected]" | ||
kubectl set env deployment/cc-web-deploy ADMIN_NAMES=${{secrets.ADMIN_NAMES}} | ||
kubectl set env deployment/cc-web-deploy "DEFAULT_ADMIN_EMAIL=${{secrets.DEFAULT_ADMIN_EMAIL}}" | ||
kubectl set env deployment/cc-web-deploy "DEFAULT_ADMIN_PASSWORD=${{secrets.DEFAULT_ADMIN_PASSWORD}}" | ||
kubectl create -f k8s/cc-create-admin.yml -n default | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,6 @@ VERIFICATION_TOKEN_SECRET="80c70dfdeedf2c01757b880d39c79214e915c786dd48d5473c9c0 | |
HUGGINGFACE_API_KEY=hf_MY_SECRET_KEY | ||
OPENAI_API_KEY=sk-MY_SECRET_KEY | ||
CHAT_PROVIDER=huggingface | ||
ADMIN_EMAILS="[email protected]" | ||
ADMIN_NAMES="John doe" | ||
NEXT_PUBLIC_OPENCLIMATE_API_URL="https://openclimate.openearth.dev" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,15 @@ | ||
import NotificationService from "@/backend/NotificationService"; | ||
import AdminNotificationTemplate from "@/lib/emails/AdminNotificationTemplate"; | ||
import { db } from "@/models"; | ||
import { apiHandler } from "@/util/api"; | ||
import { fileEndingToMIMEType } from "@/util/helpers"; | ||
import { bytesToMB, fileEndingToMIMEType } from "@/util/helpers"; | ||
import { createUserFileRequset } from "@/util/validation"; | ||
import { render } from "@react-email/components"; | ||
import { randomUUID } from "crypto"; | ||
import createHttpError from "http-errors"; | ||
import { Session } from "next-auth"; | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import session from "redux-persist/lib/storage/session"; | ||
|
||
// TODO: use these variables to configure file size and format | ||
const MAX_FILE_SIZE = 5000000; | ||
|
@@ -58,6 +62,19 @@ export const GET = apiHandler(async (_req: Request, context) => { | |
|
||
export const POST = apiHandler(async (req: NextRequest, context) => { | ||
const userId = context.session?.user.id; | ||
const service = new NotificationService(); | ||
const user = context.session?.user; | ||
const cityId = context.params.city; | ||
|
||
const city = await db.models.City.findOne({ | ||
where: { | ||
cityId, | ||
}, | ||
}); | ||
|
||
if (!city) { | ||
throw new createHttpError.Unauthorized("Unauthorized"); | ||
} | ||
|
||
if (!context.session) { | ||
throw new createHttpError.Unauthorized("Unauthorized"); | ||
|
@@ -104,23 +121,69 @@ export const POST = apiHandler(async (req: NextRequest, context) => { | |
throw new createHttpError.NotFound("User files not found"); | ||
} | ||
|
||
return NextResponse.json({ | ||
data: { | ||
id: userFile.id, | ||
userId: userFile.userId, | ||
cityId: userFile.cityId, | ||
fileReference: userFile.fileReference, | ||
url: userFile.url, | ||
sector: userFile.sector, | ||
fileName: userFile.fileName, | ||
lastUpdated: userFile.lastUpdated, | ||
status: userFile.status, | ||
gpcRefNo: userFile.gpcRefNo, | ||
file: { | ||
fileName: file.name, | ||
size: file.size, | ||
fileType: userFile.fileType, | ||
}, | ||
const newFileData = { | ||
id: userFile.id, | ||
userId: userFile.userId!, | ||
cityId: userFile.cityId!, | ||
fileReference: userFile.fileReference!, | ||
url: userFile.url!, | ||
sector: userFile.sector!, | ||
subsectors: userFile.subsectors!, | ||
scopes: userFile.scopes!, | ||
fileName: userFile.fileName!, | ||
lastUpdated: userFile.lastUpdated!, | ||
status: userFile.status!, | ||
gpcRefNo: userFile.gpcRefNo!, | ||
file: { | ||
fileName: file.name, | ||
size: file.size, | ||
fileType: userFile.fileType!, | ||
}, | ||
}; | ||
const host = process.env.HOST ?? "http://localhost:3000"; | ||
|
||
const emailTemplate = ` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create mock for the send email functionality There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Render template using React-Email again and move this to NotificationService. |
||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Email Notification</title> | ||
</head> | ||
<body style="background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; margin: 0; padding: 20px;"> | ||
<div style="margin: 0 auto; max-width: 580px; padding: 20px 0 48px;"> | ||
<!-- SVG Placeholder for ExcelFileIcon --> | ||
<!-- Make sure to replace with actual SVG or an <img> tag pointing to the icon --> | ||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"><!-- SVG content --></svg> | ||
<h1 style="color: #2351DC; font-size: 20px; line-height: 1.5; font-weight: 700;">CityCatalyst</h1> | ||
<h2 style="color: #484848; font-size: 24px; line-height: 1.3; font-weight: 700; margin-top: 50px;">${user?.name} From ${city.name} Uploaded New Files For Review</h2> | ||
<p style="font-size: 14px; line-height: 1.4; color: #484848;">Hi ${process.env.ADMIN_NAMES},</p> | ||
<p style="font-size: 14px; line-height: 1.4; color: #484848;">${user?.name} (${user?.email}) has uploaded files in CityCatalyst for revision and to upload to their inventories.</p> | ||
<!-- Example for file link; adjust href as needed --> | ||
<a href="${host}/api/v0/user/file/${newFileData.id}/download-file" style="text-decoration: none; color: #2351DC;"><div> | ||
<div style="flex-direction: column; padding-left: 16px; align-items: center; gap: 16px; height: 100px; border-radius: 8px; border: 1px solid #E6E7FF; margin-top: 0px"><div>${file.name}</div><br /><div style="color:a6a6a6">${bytesToMB(file.size)}</div><div style="margin-top: 20px">${newFileData.subsectors.map((item: string) => `<span key=${item} style="background-color: #e8eafb; color: #2351dc; padding: 6px 8px; border-radius: 30px; margin-right: 8px; font-size: 14px; margin-top: 20px">${item}</span>`)}</div></div> | ||
</div></a> | ||
<!-- Placeholder for tags; repeat this structure for each tag as needed --> | ||
<a href="${host}"><button style="font-size: 14px; padding: 16px; background-color: #2351DC; border-radius: 100px; line-height:1.5; color: #FFFFFF; margin-top: 20px; border:none">GOTO REVIEW</button></a> | ||
<!-- Footer --> | ||
<hr style="height: 2px; background: #EBEBEC; margin-top: 36px;" /> | ||
<p style="font-size: 12px; line-height: 16px; color: #79797A; font-weight: 400;">Open Earth Foundation is a nonprofit public benefit corporation from California, USA. EIN: 85-3261449</p> | ||
</div> | ||
</body> | ||
</html> | ||
|
||
`; | ||
|
||
if (process.env.NODE_ENV !== "test") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mock this so you don't need to check here |
||
await service.sendEmail({ | ||
to: process.env.ADMIN_EMAILS!, | ||
subject: "CityCatalyst File Upload", | ||
text: "City Catalyst", | ||
html: emailTemplate, | ||
}); | ||
} | ||
|
||
return NextResponse.json({ | ||
data: newFileData, | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { smtpOptions } from "@/lib/email"; | ||
import nodemailer, { Transporter } from "nodemailer"; | ||
|
||
interface EmailOptions { | ||
to: string; | ||
subject: string; | ||
text: string; | ||
html: string; | ||
} | ||
|
||
interface SendEmailResponse { | ||
success: boolean; | ||
messageId?: string; | ||
error?: any; | ||
} | ||
|
||
class NotificationService { | ||
private transporter: Transporter; | ||
constructor() { | ||
this.transporter = nodemailer.createTransport({ ...smtpOptions }); | ||
} | ||
Comment on lines
+18
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No state in service. Create this in sendEmail or create static helper method in NotificationService. |
||
|
||
async sendEmail({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be static so it can be called like this: Also this should handle template rendering with react-email. |
||
to, | ||
subject, | ||
text, | ||
html, | ||
}: EmailOptions): Promise<SendEmailResponse> { | ||
const mailOptions = { | ||
from: "", | ||
to, | ||
subject, | ||
text, | ||
html, | ||
}; | ||
|
||
try { | ||
const info = await this.transporter.sendMail(mailOptions); | ||
console.log("Message sent: %s", info.messageId); | ||
return { success: true, messageId: info.messageId }; | ||
} catch (error) { | ||
console.error("Error sending email:", error); | ||
return { success: false, error }; | ||
} | ||
} | ||
} | ||
|
||
export default NotificationService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use static methods and don't create an instance of NotificationService here.