@@ -96,6 +96,21 @@ export default async function assignRoleAdminRoute(server: FastifyInstance) {
9696 const { id } = request . params as ParamsWithId ;
9797 const { role_id } = request . body as AssignRoleRequest ;
9898
99+ // Get user's current role before making changes
100+ const userBeforeChange = await userService . getUserById ( id ) ;
101+ if ( ! userBeforeChange ) {
102+ const errorResponse : ErrorResponse = {
103+ success : false ,
104+ error : 'User not found'
105+ } ;
106+ const jsonString = JSON . stringify ( errorResponse ) ;
107+ return reply . status ( 404 ) . type ( 'application/json' ) . send ( jsonString ) ;
108+ }
109+
110+ const oldRoleId = userBeforeChange . role_id ;
111+ const isPromotionToGlobalAdmin = role_id === 'global_admin' && oldRoleId !== 'global_admin' ;
112+ const isDemotionFromGlobalAdmin = oldRoleId === 'global_admin' && role_id !== 'global_admin' ;
113+
99114 // Prevent users from changing their own role
100115 if ( request . user ?. id === id ) {
101116 const errorResponse : ErrorResponse = {
@@ -120,6 +135,96 @@ export default async function assignRoleAdminRoute(server: FastifyInstance) {
120135 // Get updated user data
121136 const user = await userService . getUserById ( id ) ;
122137
138+ // Send email notifications for global_admin role changes
139+ if ( isPromotionToGlobalAdmin || isDemotionFromGlobalAdmin ) {
140+ try {
141+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142+ const jobQueueService = ( server as any ) . jobQueueService ;
143+ if ( jobQueueService && userBeforeChange . email && request . user ) {
144+ const currentUser = request . user as { id : string ; username ?: string ; email ?: string } ;
145+ const actorName = currentUser . username || currentUser . email || 'Administrator' ;
146+ const targetUserName = userBeforeChange . username || userBeforeChange . email ;
147+
148+ // Email 1: Notify the user whose role changed
149+ if ( isPromotionToGlobalAdmin ) {
150+ await jobQueueService . createJob ( 'send_email' , {
151+ to : userBeforeChange . email ,
152+ subject : 'You\'ve been promoted to Global Administrator' ,
153+ template : 'global-admin-promoted' ,
154+ variables : {
155+ userName : targetUserName ,
156+ promotedByName : actorName ,
157+ dashboardUrl : process . env . FRONTEND_URL || undefined
158+ }
159+ } ) ;
160+ server . log . info ( { operation : 'global_admin_promoted' , userId : id } , 'Global admin promotion email queued' ) ;
161+ } else if ( isDemotionFromGlobalAdmin ) {
162+ const newRole = await userService . getUserById ( id ) ;
163+ const newRoleName = newRole ?. role ?. name || 'Standard User' ;
164+
165+ await jobQueueService . createJob ( 'send_email' , {
166+ to : userBeforeChange . email ,
167+ subject : 'Your role has been updated' ,
168+ template : 'global-admin-demoted' ,
169+ variables : {
170+ userName : targetUserName ,
171+ newRoleName : newRoleName ,
172+ changedByName : actorName ,
173+ supportEmail : process . env . SUPPORT_EMAIL || undefined
174+ }
175+ } ) ;
176+ server . log . info ( { operation : 'global_admin_demoted' , userId : id } , 'Global admin demotion email queued' ) ;
177+ }
178+
179+ // Email 2: Notify OTHER global admins (exclude actor and target user)
180+ const allGlobalAdmins = await userService . getUsersByRole ( 'global_admin' ) ;
181+ const otherAdmins = allGlobalAdmins . filter ( admin =>
182+ admin . id !== request . user ! . id && admin . email && admin . id !== id
183+ ) ;
184+
185+ if ( otherAdmins . length > 0 ) {
186+ for ( const admin of otherAdmins ) {
187+ const adminDisplayName = admin . username || admin . email ;
188+
189+ if ( isPromotionToGlobalAdmin ) {
190+ await jobQueueService . createJob ( 'send_email' , {
191+ to : admin . email ,
192+ subject : 'New Global Administrator Added' ,
193+ template : 'global-admin-user-promoted-notification' ,
194+ variables : {
195+ adminName : adminDisplayName ,
196+ promotedUserName : targetUserName ,
197+ promotedByName : actorName ,
198+ dashboardUrl : process . env . FRONTEND_URL || undefined
199+ }
200+ } ) ;
201+ } else if ( isDemotionFromGlobalAdmin ) {
202+ const newRole = await userService . getUserById ( id ) ;
203+ const newRoleName = newRole ?. role ?. name || 'Standard User' ;
204+
205+ await jobQueueService . createJob ( 'send_email' , {
206+ to : admin . email ,
207+ subject : 'Global Administrator Removed' ,
208+ template : 'global-admin-user-demoted-notification' ,
209+ variables : {
210+ adminName : adminDisplayName ,
211+ demotedUserName : targetUserName ,
212+ newRoleName : newRoleName ,
213+ changedByName : actorName ,
214+ dashboardUrl : process . env . FRONTEND_URL || undefined
215+ }
216+ } ) ;
217+ }
218+ }
219+ server . log . info ( { notifiedCount : otherAdmins . length } , 'Notified other global admins' ) ;
220+ }
221+ }
222+ } catch ( emailError ) {
223+ server . log . error ( emailError , 'Failed to queue emails - role change succeeded' ) ;
224+ // Don't fail role assignment if email queueing fails
225+ }
226+ }
227+
123228 const successResponse : AssignRoleSuccessResponse = {
124229 success : true ,
125230 data : user ,
0 commit comments