Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion Clients/src/presentation/containers/Dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import "./index.css";
import Sidebar from "../../components/Sidebar";
import { Outlet, useLocation } from "react-router";
import { useContext, useEffect, FC } from "react";
import { VerifyWiseContext } from "../../../application/contexts/VerifyWise.context";import DemoAppBanner from "../../components/DemoBanner/DemoAppBanner";
import { VerifyWiseContext } from "../../../application/contexts/VerifyWise.context";
import DemoAppBanner from "../../components/DemoBanner/DemoAppBanner";
import { getAllProjects } from "../../../application/repository/project.repository";

interface DashboardProps {
Expand Down
27 changes: 26 additions & 1 deletion Servers/controllers/project.ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import { IControl } from "../domain.layer/interfaces/i.control";
import { IControlCategory } from "../domain.layer/interfaces/i.controlCategory";
import { logProcessing, logSuccess, logFailure } from "../utils/logger/logHelper";
import { createISO27001FrameworkQuery } from "../utils/iso27001.utils";
import { sendProjectCreatedNotification } from "../services/projectCreationNotification";
import { sendProjectCreatedNotification } from "../services/projectNotification/projectCreationNotification";
import {sendUserAddedAdminNotification} from "../services/userNotification/userAddedAdminNotification"


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

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

const project = await updateProjectByIdQuery(
projectId,
updatedProject,
Expand All @@ -267,9 +273,28 @@ 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)
if (ownerChanged && currentProject) {
sendUserAddedAdminNotification({
projectId: projectId,
projectName: project.project_title,
adminId: req.userId!, // Actor who made the change
userId: updatedProject.owner! // New admin receiving notification
}).catch(async (emailError) => {
await logFailure({
eventType: "Update",
description: "Failed to send user added as admin notification email",
functionName: "updateProjectById",
fileName: "project.ctrl.ts",
error: emailError as Error,
});
});
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
return res.status(202).json(STATUS_CODE[202](project));
}


await logSuccess({
eventType: "Update",
description: `Project not found for update: ID ${projectId}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { notificationService } from "./notificationService";
import { getUserByIdQuery } from "../utils/user.utils";
import { frontEndUrl } from "../config/constants";
import { notificationService } from "../notificationService";
import { getUserByIdQuery } from "../../utils/user.utils";
import { frontEndUrl } from "../../config/constants";
import {
logProcessing,
logSuccess,
logFailure,
} from "../utils/logger/logHelper";
} from "../../utils/logger/logHelper";

export interface ProjectCreatedNotificationData {
projectId: number;
Expand Down
84 changes: 84 additions & 0 deletions Servers/services/userNotification/userAddedAdminNotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { notificationService } from "../notificationService";
import { getUserByIdQuery } from "../../utils/user.utils";
import { frontEndUrl } from "../../config/constants";
import {
logProcessing,
logSuccess,
logFailure,
} from "../../utils/logger/logHelper";


export interface UserAddedAdminNotification {
projectId: number;
projectName: string;
adminId: number;
userId: number;
}


export const sendUserAddedAdminNotification = async(
data: UserAddedAdminNotification
): Promise<void> => {
logProcessing({
description: `Sending user added as admin notification for project: ${data.projectName}`,
functionName: "sendUserAddedAdminNotification",
fileName: "userAddedAdminNotification.ts",
});

try{

// Get admin user details
const adminUser = await getUserByIdQuery(data.adminId);
if (!adminUser) {
throw new Error(`Admin user not found with ID: ${data.adminId}`);
}

// Get user details
const user = await getUserByIdQuery(data.userId);
if (!user) {
throw new Error(`User not found with ID: ${data.userId}`);
}

if(!user.email || user.email.trim() === ''){
throw new Error(`User email is missing or invalid for user ID: ${data.userId}`);
}

// Construct project URL
const projectUrl = `${frontEndUrl}/project-view?projectId=${data.projectId}`;

// Prepare template data
const templateData = {
project_name: data.projectName,
actor_name: adminUser.name,
project_url: projectUrl,
user_name: user.name
};

// Send the email using core notification service
const subject = `You are now a project admin for ${data.projectName}`;
await notificationService.sendEmailWithTemplate(
user.email,
subject,
"user-added-admin.mjml",
templateData
);

await logSuccess({
eventType: "Update",
description: `Added as Admin notification sent to ${user.email}`,
functionName: "sendUserAddedAdminNotification",
fileName: "userAddedAdminNotification.ts",
});
} catch (error) {
await logFailure({
eventType: "Update",
description: `Failed to send user added as admin notification`,
functionName: "sendUserAddedAdminNotification",
fileName: "userAddedAdminNotification.ts",
error: error as Error,
});
throw error;
}


}
6 changes: 3 additions & 3 deletions Servers/templates/project-created-admin.mjml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
</mj-attributes>
</mj-head>
<mj-body>
<mj-section>
<mj-column width="100%">
<mj-text>
<mj-body width="100%">
<mj-section text-align="left" padding="0px">
<mj-text align="left">
<h2 style="color: #2c3e50; margin-bottom: 20px;">{{project_name}} is created in VerifyWise</h2>
<p>Hi {{admin_name}},</p>
<p>Your project <strong>{{project_name}}</strong> was created in VerifyWise.</p>
Expand Down
28 changes: 28 additions & 0 deletions Servers/templates/user-added-admin.mjml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="14px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body width="100%">
<mj-section text-align="left" padding="0px">
<mj-column width="100%" vertical-align="top">
<mj-text align="left">
<h2 style="color: #2c3e50; margin-bottom: 20px;">You are now a project admin</h2>
<p>Hi {{user_name}},</p>
<p>You have been added as a project admin for <strong>{{project_name}}</strong> by {{actor_name}}.</p>
<p>As an admin, you can manage members, policies, and controls.</p>
<p style="margin: 20px 0;">
<a href="{{project_url}}" style="background-color: #3498db; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">Open project</a>
</p>
<p style="margin-top: 30px; color: #7f8c8d; font-size: 12px;">
Sent from VerifyWise
</p>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>