Skip to content

Ks 033 oct 03 email notification 1959#2296

Merged
MuhammadKhalilzadeh merged 9 commits intodevelopfrom
ks-033-Oct-03-email-notification-1959
Oct 5, 2025
Merged

Ks 033 oct 03 email notification 1959#2296
MuhammadKhalilzadeh merged 9 commits intodevelopfrom
ks-033-Oct-03-email-notification-1959

Conversation

@solan117
Copy link
Copy Markdown
Contributor

@solan117 solan117 commented Oct 4, 2025

Describe your changes

feat(notifications): add email notification for editor-to-admin role change

  • Implemented sendMemberRoleChangedEditorToAdminNotification to notify users when their role changes from editor to admin.
  • Integrated email template MEMBER_ROLE_CHANGED_EDITOR_TO_ADMIN with dynamic data for notification personalization.
  • Merged sendUserAddedToProjectNotification, sendMemberRoleChangedEditorToAdminNotification, and sendProjectCreatedNotification into a unified projectNotifications.ts module.
  • Centralized notification configurations and streamlined template handling for role-based and project events.
  • Removed deprecated individual notification handlers for improved maintainability and consistency.

Write your issue number after "Fixes "

Fixes #1959

Please ensure all items are checked off before requesting a review:

  • I deployed the code locally.
  • I have performed a self-review of my code.
  • I have included the issue # in the PR.
  • I have labelled the PR correctly.
  • The issue I am working on is assigned to me.
  • I have avoided using hardcoded values to ensure scalability and maintain consistency across the application.
  • I have ensured that font sizes, color choices, and other UI elements are referenced from the theme.
  • My pull request is focused and addresses a single, specific feature.
  • If there are UI changes, I have attached a screenshot or video to this PR.
Screenshot 2025-10-03 232824

…change

- Implemented `sendMemberRoleChangedEditorToAdminNotification` to notify users when their role changes from editor to admin.
- Integrated email template `MEMBER_ROLE_CHANGED_EDITOR_TO_ADMIN` with dynamic data for notification personalization.
- Improved `getUserByIdQuery` to validate user existence and convert plain objects to `UserModel` instances.
- Enhanced `updateUserById` and `updateUserRole` functions to notify admins when a user's role changes from Editor (role_id = 3) to Admin (role_id = 1).
- Added detailed error logging for failed project retrieval and notification sending during role updates.
- Updated `getUserProjects` to include tenant-specific queries with joined data for project members.
…sable module

- Merged `sendUserAddedToProjectNotification`, `sendMemberRoleChangedEditorToAdminNotification`, and `sendProjectCreatedNotification` into a unified `projectNotifications.ts` module.
- Centralized notification configurations and streamlined template handling for role-based and project events.
- Removed deprecated individual notification handlers for improved maintainability and consistency.
@solan117 solan117 added this to the 1.5 milestone Oct 4, 2025
@solan117 solan117 self-assigned this Oct 4, 2025
@solan117 solan117 added the backend Backend related tasks/issues label Oct 4, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 4, 2025

Walkthrough

Centralizes project-related email notifications into Servers/services/userNotification/projectNotifications.ts, removes legacy per-event notification modules, updates controllers to use the unified API, adds Editor→Admin per-project notifications (fire-and-forget) on user updates, and scopes user project queries to tenant context.

Changes

Cohort / File(s) Summary
Controllers
Servers/controllers/project.ctrl.ts, Servers/controllers/user.ctrl.ts
Replaced legacy imports with unified userNotification/projectNotifications. user.ctrl.ts normalizes role IDs (string→number), captures oldRoleId, scopes project queries by tenant, and triggers Editor→Admin per-project notifications (fire-and-forget) in updateUserById and updateUserRole, logging notification failures.
New unified notification service
Servers/services/userNotification/projectNotifications.ts
New module defining notification types/roles, template/config maps, core sendProjectNotification flow, logging, and exported helpers: sendUserAddedToProjectNotification, sendMemberRoleChangedEditorToAdminNotification, sendProjectCreatedNotification.
Removed legacy notification modules
Servers/services/projectNotification/projectCreationNotification.ts, Servers/services/userNotification/userAddedToProjectNotification.ts
Deleted old per-event notification files and their exported types/functions; behavior consolidated into the new projectNotifications.ts.
User utilities
Servers/utils/user.utils.ts
getUserByIdQuery signature/return adjusted to return UserModel and getUserByIdOrThrow added. getUserProjects signature changed to (userId: number, tenant: string) and SQL updated to scope projects per tenant.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Admin as Admin (actor)
  participant API as User Controller
  participant DB as DB
  participant Notif as projectNotifications
  participant Mail as Email Service

  Admin->>API: PATCH /users/:id (change role)
  API->>DB: fetch old user
  DB-->>API: oldUser (oldRoleId)
  API->>DB: update user role
  DB-->>API: updated user
  alt oldRole = Editor && newRole = Admin
    API->>DB: getUserProjects(userId, tenant)
    DB-->>API: [projects...]
    loop per project (fire-and-forget)
      API-)Notif: sendMemberRoleChangedEditorToAdminNotification({projectId,projectName,actorId,userId})
      Note right of Notif: Asynchronous, errors logged via logFailure
      Notif->>DB: fetch actor & user
      DB-->>Notif: actor/user
      Notif->>Mail: sendEmailWithTemplate(...)
      Mail-->>Notif: result
    end
  end
  API-->>Admin: 200 OK
Loading
sequenceDiagram
  autonumber
  participant Ctrl as Project Controller
  participant Notif as projectNotifications
  participant DB as DB
  participant Mail as Email Service

  Ctrl-)Notif: sendProjectCreatedNotification({projectId, projectName, adminId})
  Notif->>DB: fetch admin
  DB-->>Notif: admin record
  Notif->>Mail: sendEmailWithTemplate(project.created.admin,...)
  Mail-->>Notif: result
  Notif-->>Ctrl: resolve / throw
Loading
sequenceDiagram
  autonumber
  participant Ctrl as Membership Controller
  participant Notif as projectNotifications
  participant DB as DB
  participant Mail as Email Service

  Ctrl-)Notif: sendUserAddedToProjectNotification({projectId, projectName, adminId, userId, role})
  Notif->>DB: fetch actor & user
  DB-->>Notif: records
  Notif->>Mail: sendEmailWithTemplate(templateByRole,...)
  Mail-->>Notif: result
  Notif-->>Ctrl: resolve / throw
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • MuhammadKhalilzadeh
  • rachanabasnet

Poem

🐰 I stitched the notes into one tidy den,
Old burrows closed, messages hop to ten.
Roles leap up, tenant fences aligned,
Emails flutter — neatly combined.
Carrots in inboxes, joy by design.

Pre-merge checks and finishing touches

❌ Failed checks (4 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Ks 033 oct 03 email notification 1959” is unclear and includes branch identifiers, a date, and an issue number rather than summarizing the core change, making it difficult for teammates to understand the primary feature added. Please replace the title with a concise sentence that highlights the main change, for example “Unify project notification handlers and add editor-to-admin email notification.”
Linked Issues Check ⚠️ Warning The implementation covers project‐created, user‐added (all role variants), and editor→admin role change notifications per issue #1959, but it omits the “policy.due_soon” reminder feature specified for policy due date reminders, so not all required notifications are implemented. Add the policy due date reminder notification (using the policy.due_soon template and required variables) to fully satisfy the linked issue’s objectives.
Out of Scope Changes Check ⚠️ Warning Several changes, such as updating calculateProgress to pass tenant context and modifying getUserByIdQuery signatures, are unrelated to the email notification objectives and introduce behavior outside the scope of issue #1959. Separate or justify non-notification changes in a different PR, ensuring this pull request focuses solely on implementing the email notification features.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The description follows the repository template by including a clear “Describe your changes” section, listing the issue number under “Fixes,” and completing the required checklist with a screenshot, thereby satisfying all mandatory template sections.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ks-033-Oct-03-email-notification-1959

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b0b9be and b6f315a.

📒 Files selected for processing (6)
  • Servers/controllers/project.ctrl.ts (1 hunks)
  • Servers/controllers/user.ctrl.ts (7 hunks)
  • Servers/services/projectNotification/projectCreationNotification.ts (0 hunks)
  • Servers/services/userNotification/projectNotifications.ts (1 hunks)
  • Servers/services/userNotification/userAddedToProjectNotification.ts (0 hunks)
  • Servers/utils/user.utils.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • Servers/services/userNotification/userAddedToProjectNotification.ts
  • Servers/services/projectNotification/projectCreationNotification.ts
🧰 Additional context used
🧬 Code graph analysis (3)
Servers/controllers/user.ctrl.ts (4)
Servers/utils/user.utils.ts (1)
  • getUserProjects (416-428)
Servers/utils/project.utils.ts (1)
  • getUserProjects (21-59)
Servers/services/userNotification/projectNotifications.ts (1)
  • sendMemberRoleChangedEditorToAdminNotification (246-259)
Servers/utils/logger/logHelper.ts (1)
  • logFailure (51-68)
Servers/utils/user.utils.ts (2)
Clients/src/domain/models/Common/user/user.model.ts (1)
  • UserModel (1-25)
Servers/utils/project.utils.ts (1)
  • getUserProjects (21-59)
Servers/services/userNotification/projectNotifications.ts (4)
Servers/constants/emailTemplates.ts (1)
  • EMAIL_TEMPLATES (14-33)
Servers/utils/logger/logHelper.ts (3)
  • logProcessing (22-30)
  • logSuccess (33-49)
  • logFailure (51-68)
Servers/utils/user.utils.ts (1)
  • getUserByIdQuery (137-154)
Servers/services/notificationService.ts (1)
  • notificationService (321-321)

Comment thread Servers/controllers/user.ctrl.ts
Comment thread Servers/utils/user.utils.ts
…ateUserRole logic

- Removed unnecessary debug log statement in `updateUserById`.
- Added normalization for `newRoleId` to handle string inputs gracefully in `updateUserRole`.
…uery

- Updated `getUserByIdQuery` to return `null` if the user is not found, instead of throwing an error.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Servers/utils/user.utils.ts (1)

137-154: Restore null return for missing users to preserve 404 handling.

This change breaks existing error handling. Throwing when a user is absent forces every caller that previously checked for a falsy return into catch blocks that emit 500 responses instead of the intended 404. The past review comment on this function remains unaddressed.

Apply this diff to restore the original behavior:

-export const getUserByIdQuery = async (id: number): Promise<UserModel> => {
+export const getUserByIdQuery = async (id: number): Promise<UserModel | null> => {
   const [userObj] = await sequelize.query<any>(
     "SELECT * FROM public.users WHERE id = :id",
     {
       replacements: { id },
       type: QueryTypes.SELECT,
     }
   );

   if (!userObj) {
-    throw new Error(`User not found with ID: ${id}`);
+    return null;
   }

   // Convert plain object to UserModel instance
   const user = new UserModel();
   Object.assign(user, userObj);
   return user;
 };
🧹 Nitpick comments (1)
Servers/utils/user.utils.ts (1)

416-428: Consider consolidating duplicate getUserProjects functions.

Two getUserProjects functions exist with different signatures and logic:

  • Servers/utils/user.utils.ts (this file): (userId: number, tenant: string)
  • Servers/utils/project.utils.ts: ({ userId, role, transaction }, tenant)

The project.utils.ts version includes role-based filtering and ownership checks that this version lacks. This naming collision creates confusion and maintenance burden.

Consider:

  1. Renaming this function to getUserProjectsByMembership to clarify scope, or
  2. Consolidating into a single function with optional parameters for role/transaction if both are needed.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cca1919 and bc7f45e.

📒 Files selected for processing (1)
  • Servers/utils/user.utils.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Servers/utils/user.utils.ts (2)
Clients/src/domain/models/Common/user/user.model.ts (1)
  • UserModel (1-25)
Servers/utils/project.utils.ts (1)
  • getUserProjects (21-59)
🔇 Additional comments (1)
Servers/utils/user.utils.ts (1)

416-428: Ignore missing ownership concern; function scope matches usage.
user.utils.getUserProjects is explicitly scoped to membership-only queries (controllers’ comments confirm this) and all invocations now supply the tenant parameter.

Likely an incorrect or invalid review comment.

- Updated `getUserByIdQuery` to return a `UserModel` instance or `null` if the user is not found.
- Introduced `getUserByIdOrThrow` for mandatory user retrieval, throwing an error if the user is not found.
- Updated method signatures, examples, and comments to align with the new logic.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Servers/utils/user.utils.ts (1)

436-448: Handle project owners in getUserProjects
The query in Servers/utils/user.utils.ts (l. 436–448) only returns projects where the user is in projects_members. It must also include projects they own. Change to:

  • LEFT JOIN "${tenant}".projects_members pm ON p.id = pm.project_id
  • WHERE p.owner = :user_id OR pm.user_id = :user_id
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc7f45e and 386fbe1.

📒 Files selected for processing (1)
  • Servers/utils/user.utils.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Servers/utils/user.utils.ts (2)
Clients/src/domain/models/Common/user/user.model.ts (1)
  • UserModel (1-25)
Servers/utils/project.utils.ts (1)
  • getUserProjects (21-59)
🔇 Additional comments (1)
Servers/utils/user.utils.ts (1)

152-174: LGTM! Clean separation of concerns.

The getUserByIdOrThrow wrapper provides a clear API for cases where the user must exist, eliminating the need for null checks at call sites. The documentation and implementation are both clear.

Comment on lines 137 to +150
export const getUserByIdQuery = async (id: number): Promise<UserModel> => {
const user = await sequelize.query("SELECT * FROM public.users WHERE id = :id", {
replacements: { id },
mapToModel: true,
model: UserModel,
});
return user[0];
const users = await sequelize.query<UserModel>(
"SELECT * FROM public.users WHERE id = :id",
{
replacements: { id },
model: UserModel,
mapToModel: true, // converts results into UserModel instances
}
);

// users will be an array. Return first element or null if not found
return users[0];
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Return null instead of undefined and update the return type.

The current implementation returns users[0], which evaluates to undefined when no user is found, but the past review comment specifically requested returning null to preserve 404 handling in callers. Additionally, the return type is still Promise<UserModel> instead of Promise<UserModel | null>, which creates a type mismatch.

Apply this diff to address the past review comment:

-export const getUserByIdQuery = async (id: number): Promise<UserModel> => {
+export const getUserByIdQuery = async (id: number): Promise<UserModel | null> => {
     const users = await sequelize.query<UserModel>(
         "SELECT * FROM public.users WHERE id = :id",
         {
             replacements: { id },
             model: UserModel,
             mapToModel: true, // converts results into UserModel instances
         }
     );

     // users will be an array. Return first element or null if not found
-    return users[0];
+    return users[0] || null;
 };

This ensures callers can safely check for null (e.g., if (!user)) and the type signature accurately reflects the function's behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getUserByIdQuery = async (id: number): Promise<UserModel> => {
const user = await sequelize.query("SELECT * FROM public.users WHERE id = :id", {
replacements: { id },
mapToModel: true,
model: UserModel,
});
return user[0];
const users = await sequelize.query<UserModel>(
"SELECT * FROM public.users WHERE id = :id",
{
replacements: { id },
model: UserModel,
mapToModel: true, // converts results into UserModel instances
}
);
// users will be an array. Return first element or null if not found
return users[0];
};
export const getUserByIdQuery = async (id: number): Promise<UserModel | null> => {
const users = await sequelize.query<UserModel>(
"SELECT * FROM public.users WHERE id = :id",
{
replacements: { id },
model: UserModel,
mapToModel: true, // converts results into UserModel instances
}
);
// users will be an array. Return first element or null if not found
return users[0] || null;
};
🤖 Prompt for AI Agents
In Servers/utils/user.utils.ts around lines 138 to 150, the function currently
returns users[0] which becomes undefined when no record is found and its
signature is Promise<UserModel>; change the return type to Promise<UserModel |
null> and ensure the function returns null instead of undefined when no user is
found (e.g., return users.length ? users[0] : null), keeping the existing
sequelize.query call and mapToModel behavior so callers can reliably check for
null.

# Conflicts:
#	Servers/controllers/project.ctrl.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Servers/controllers/user.ctrl.ts (1)

687-727: Notification logic is correct, but consider variable naming.

The role-change detection and per-project notification flow work correctly. The fire-and-forget pattern with comprehensive error logging is appropriate. However, the variable names newRoleIdNum and oldRoleIdNum could be clearer since roleId (not newRoleId) is used here.

Consider this small clarity improvement:

-      // Convert to numbers explicitly for comparison
-      const oldRoleIdNum = Number(oldRoleId);
-      const newRoleIdNum = Number(roleId);
-
-      if (newRoleIdNum === 1 && oldRoleIdNum === 3) {
+      // Check if role changed from Editor (3) to Admin (1)
+      if (Number(roleId) === 1 && Number(oldRoleId) === 3) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 386fbe1 and 052d2c0.

📒 Files selected for processing (2)
  • Servers/controllers/project.ctrl.ts (1 hunks)
  • Servers/controllers/user.ctrl.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Servers/controllers/project.ctrl.ts
🧰 Additional context used
🧬 Code graph analysis (1)
Servers/controllers/user.ctrl.ts (3)
Servers/utils/user.utils.ts (1)
  • getUserProjects (436-448)
Servers/services/userNotification/projectNotifications.ts (1)
  • sendMemberRoleChangedEditorToAdminNotification (246-259)
Servers/utils/logger/logHelper.ts (1)
  • logFailure (51-68)
🔇 Additional comments (7)
Servers/controllers/user.ctrl.ts (7)

46-47: LGTM!

The new imports for sendMemberRoleChangedEditorToAdminNotification and logFailure are correctly placed and align with the PR's goal of centralizing project notifications.


613-616: LGTM!

The roleId normalization correctly handles string-to-number conversion, ensuring type consistency for subsequent comparisons.


652-654: LGTM!

Capturing oldRoleId before the user update is the correct approach for detecting role changes.


857-857: LGTM!

The tenant context is correctly passed to getUserProjects, ensuring proper multi-tenant isolation.


1003-1006: Normalization fixes the previous issue.

The newRoleId normalization correctly addresses the previous review comment about the short-circuit when the client sends a string. This ensures the role comparison at line 1078 works as intended.

Based on past review comments.


1062-1063: LGTM!

Capturing oldRoleId before updating the role is the correct approach for detecting role transitions.


1077-1110: LGTM!

The role-change detection (Editor → Admin) and per-project notification logic are correctly implemented. The fire-and-forget pattern with comprehensive error logging is appropriate, and the normalization ensures the comparison at line 1078 works as expected.

@MuhammadKhalilzadeh
Copy link
Copy Markdown
Collaborator

@Aryanak47 and @swaleha456 , would you please provide your reviews here?


// Send notification for each project (fire-and-forget)
for (const project of userProjects) {
sendMemberRoleChangedEditorToAdminNotification({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For functions like this, its better to name them like this:
sendMemberRoleChangedNotification, and then you have two props, oldRole, newRole.
Please modify it, so the logic is handled like that

@MuhammadKhalilzadeh MuhammadKhalilzadeh merged commit df8f736 into develop Oct 5, 2025
2 checks passed
@MuhammadKhalilzadeh MuhammadKhalilzadeh deleted the ks-033-Oct-03-email-notification-1959 branch October 5, 2025 05:23
@MuhammadKhalilzadeh
Copy link
Copy Markdown
Collaborator

@solan117 Please address my comments in a next pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend related tasks/issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implementing email notifications

2 participants