Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ const ProjectSettings = React.memo(
...prevValues,
[prop]: newValue.map((user) => user.id),
}));
setErrors((prevErrors) => ({ ...prevErrors, [prop]: "" }));
}
},
[values.monitoredRegulationsAndStandards, projectId, triggerRefresh]
Expand Down Expand Up @@ -661,6 +662,11 @@ const ProjectSettings = React.memo(
},
}).then((response) => {
if (response.status === 202) {
// Create new values reference and update both ref and form state
const newValues = { ...values };
initialValuesRef.current = newValues;
setValues(newValues);

setAlert({
variant: "success",
body: "Project updated successfully",
Expand Down
4 changes: 3 additions & 1 deletion Servers/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ dist-ssr
# *.dev
*.prod

README-Email-Notifications.md
README-Email-Notifications.md

.rate-limit-state.json
48 changes: 46 additions & 2 deletions Servers/controllers/project.ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getAllProjectsQuery,
getProjectByIdQuery,
updateProjectByIdQuery,
getCurrentProjectMembers
} from "../utils/project.utils";
import { getUserByIdQuery } from "../utils/user.utils";
import { getControlCategoryByProjectIdQuery } from "../utils/controlCategory.utils";
Expand All @@ -27,6 +28,7 @@ import { logProcessing, logSuccess, logFailure } from "../utils/logger/logHelper
import { createISO27001FrameworkQuery } from "../utils/iso27001.utils";
import { sendProjectCreatedNotification } from "../services/projectNotification/projectCreationNotification";
import {sendUserAddedAdminNotification} from "../services/userNotification/userAddedAdminNotification"
import {sendUserAddedEditorNotification} from "../services/userNotification/userAddedEditorNotification"


export async function getAllProjects(req: Request, res: Response): Promise<any> {
Expand Down Expand Up @@ -251,10 +253,13 @@ export async function updateProjectById(req: Request, res: Response): Promise<an
);
}

// Get current project to check if owner changed
// Get current project and members to check for changes
const currentProject = await getProjectByIdQuery(projectId, req.tenantId!);
const ownerChanged = currentProject && currentProject.owner !== updatedProject.owner;

// Get current members before update to identify newly added ones
const currentMembers = await getCurrentProjectMembers(projectId, req.tenantId!, transaction);

const project = await updateProjectByIdQuery(
projectId,
updatedProject,
Expand All @@ -273,7 +278,46 @@ export async function updateProjectById(req: Request, res: Response): Promise<an
fileName: "project.ctrl.ts",
});

// Send notification if owner changed (fire-and-forget, don't block response)
// Calculate which members actually got added (both new and re-added)
// This includes users who weren't in currentMembers but are now in the final project
const finalMembers = project.members || [];
const addedMembers = finalMembers.filter((m) => !currentMembers.includes(m));

// Send notification to users who were added as project editors (fire-and-forget, don't block response)
for (const memberId of addedMembers) {
try {
// Get user details to check their role
const memberUser = await getUserByIdQuery(memberId);
// Check if user has Editor role (role_id = 3)
if (memberUser && memberUser.role_id === 3) {
sendUserAddedEditorNotification({
projectId: projectId,
projectName: project.project_title,
adminId: req.userId!, // Actor who made the change
userId: memberId // Editor receiving notification
}).catch(async (emailError) => {
await logFailure({
eventType: "Update",
description: `Failed to send user added as editor notification email to user ${memberId}`,
functionName: "updateProjectById",
fileName: "project.ctrl.ts",
error: emailError as Error,
});
});
}
} catch (userLookupError) {
await logFailure({
eventType: "Update",
description: `Failed to lookup user role for member ${memberId}`,
functionName: "updateProjectById",
fileName: "project.ctrl.ts",
error: userLookupError as Error,
});
}
}


// Send notification if owner changed (fire-and-forget, don't block response)
if (ownerChanged && currentProject) {
sendUserAddedAdminNotification({
projectId: projectId,
Expand Down
32 changes: 16 additions & 16 deletions Servers/services/emailService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ const resend = new Resend(process.env.RESEND_API_KEY);

// Function to send an email
export const sendEmail = async (
to: string,
subject: string,
template: string,
data: Record<string, string>
to: string,
subject: string,
template: string,
data: Record<string, string>
) => {
// Compile MJML template to HTML
const html = compileMjmlToHtml(template, data);
// Compile MJML template to HTML
const html = compileMjmlToHtml(template, data);

if (!process.env.EMAIL_ID) {
throw new Error("Email ID is not set in environment variables");
}
if (!process.env.EMAIL_ID) {
throw new Error("Email ID is not set in environment variables");
}

const mailOptions = {
from: process.env.EMAIL_ID,
to, subject, html
};
const mailOptions = {
from: process.env.EMAIL_ID,
to, subject, html
};

// Send mail with defined transport object
return await resend.emails.send(mailOptions);
};
// Send mail with defined transport object
return await resend.emails.send(mailOptions);
};
Loading