@@ -5,6 +5,7 @@ import Hackathon, {
55 IHackathon ,
66} from "../../models/hackathon.model.js" ;
77import Organization from "../../models/organization.model.js" ;
8+ import User from "../../models/user.model.js" ;
89import {
910 sendSuccess ,
1011 sendError ,
@@ -22,6 +23,10 @@ import {
2223 validatePublishRequirements ,
2324} from "./hackathon.helpers.js" ;
2425import { generateHackathonSlug } from "../../utils/hackathon.utils.js" ;
26+ import NotificationService from "../notifications/notification.service.js" ;
27+ import EmailTemplatesService from "../../services/email/email-templates.service.js" ;
28+ import { NotificationType } from "../../models/notification.model.js" ;
29+ import { config } from "../../config/main.config.js" ;
2530
2631/**
2732 * @swagger
@@ -136,6 +141,99 @@ export const publishHackathon = async (
136141 } ) ;
137142 }
138143
144+ // Send notifications
145+ try {
146+ const frontendUrl =
147+ process . env . FRONTEND_URL ||
148+ config . cors . origin ||
149+ "https://boundlessfi.xyz" ;
150+ const baseUrl = Array . isArray ( frontendUrl ) ? frontendUrl [ 0 ] : frontendUrl ;
151+ const creatorName =
152+ `${ user . profile ?. firstName || "" } ${ user . profile ?. lastName || "" } ` . trim ( ) ||
153+ user . email ;
154+
155+ // Notify organization members about hackathon publication
156+ const allMembers = [ organization . owner , ...organization . members ] . filter (
157+ ( email ) => email !== user . email ,
158+ ) ; // Don't notify the creator
159+
160+ if ( allMembers . length > 0 ) {
161+ const memberUsers = await User . find ( {
162+ email : { $in : allMembers } ,
163+ } ) . select (
164+ "email profile.firstName profile.lastName settings.notifications" ,
165+ ) ;
166+
167+ await NotificationService . notifyTeamMembers (
168+ memberUsers . map ( ( member ) => ( {
169+ userId : member . _id ,
170+ email : member . email ,
171+ name :
172+ `${ member . profile ?. firstName || "" } ${ member . profile ?. lastName || "" } ` . trim ( ) ||
173+ member . email ,
174+ } ) ) ,
175+ {
176+ type : NotificationType . HACKATHON_PUBLISHED ,
177+ title : `New hackathon published: ${ hackathon . title || "Hackathon" } ` ,
178+ message : `${ creatorName } has published a new hackathon "${ hackathon . title || "Hackathon" } "` ,
179+ data : {
180+ hackathonId : hackathon . _id ,
181+ hackathonName : hackathon . title || "Hackathon" ,
182+ hackathonSlug : hackathon . slug ,
183+ organizationId : new mongoose . Types . ObjectId ( orgId ) ,
184+ } ,
185+ emailTemplate : EmailTemplatesService . getTemplate (
186+ "hackathon-published" ,
187+ {
188+ hackathonId : hackathon . _id . toString ( ) ,
189+ hackathonName : hackathon . title || "Hackathon" ,
190+ hackathonSlug : hackathon . slug ,
191+ organizationId : orgId ,
192+ unsubscribeUrl : undefined , // Will be set per recipient
193+ } ,
194+ ) ,
195+ } ,
196+ ) ;
197+ }
198+
199+ // Notify the creator
200+ await NotificationService . sendSingleNotification (
201+ {
202+ userId : user . _id ,
203+ email : user . email ,
204+ name : creatorName ,
205+ preferences : user . settings ?. notifications ,
206+ } ,
207+ {
208+ type : NotificationType . HACKATHON_PUBLISHED ,
209+ title : `Hackathon "${ hackathon . title || "Hackathon" } " published` ,
210+ message : `Your hackathon "${ hackathon . title || "Hackathon" } " has been successfully published.` ,
211+ data : {
212+ hackathonId : hackathon . _id ,
213+ hackathonName : hackathon . title || "Hackathon" ,
214+ hackathonSlug : hackathon . slug ,
215+ organizationId : new mongoose . Types . ObjectId ( orgId ) ,
216+ } ,
217+ emailTemplate : EmailTemplatesService . getTemplate (
218+ "hackathon-published" ,
219+ {
220+ hackathonId : hackathon . _id . toString ( ) ,
221+ hackathonName : hackathon . title || "Hackathon" ,
222+ hackathonSlug : hackathon . slug ,
223+ organizationId : orgId ,
224+ unsubscribeUrl : `${ baseUrl } /unsubscribe?email=${ encodeURIComponent ( user . email ) } ` ,
225+ } ,
226+ ) ,
227+ } ,
228+ ) ;
229+ } catch ( notificationError ) {
230+ console . error (
231+ "Error sending hackathon published notifications:" ,
232+ notificationError ,
233+ ) ;
234+ // Don't fail the whole operation if notification fails
235+ }
236+
139237 sendCreated ( res , hackathon , "Hackathon published successfully" ) ;
140238 } catch ( error ) {
141239 console . error ( "Publish hackathon error:" , error ) ;
@@ -203,10 +301,118 @@ export const updateHackathon = async (
203301 }
204302
205303 const updateData = transformRequestBody ( req . body ) ;
304+ const oldStatus = hackathon . status ;
206305
207306 Object . assign ( hackathon , updateData ) ;
208307 await hackathon . save ( ) ;
209308
309+ // Send notifications
310+ try {
311+ const frontendUrl =
312+ process . env . FRONTEND_URL ||
313+ config . cors . origin ||
314+ "https://boundlessfi.xyz" ;
315+ const baseUrl = Array . isArray ( frontendUrl ) ? frontendUrl [ 0 ] : frontendUrl ;
316+ const updaterName =
317+ `${ user . profile ?. firstName || "" } ${ user . profile ?. lastName || "" } ` . trim ( ) ||
318+ user . email ;
319+ const statusChanged = oldStatus !== hackathon . status ;
320+ const changes = Object . keys ( updateData ) . join ( ", " ) ;
321+
322+ // Determine notification type based on status change
323+ let notificationType = NotificationType . HACKATHON_UPDATED ;
324+ if ( statusChanged ) {
325+ notificationType = NotificationType . HACKATHON_STATUS_CHANGED ;
326+ if ( hackathon . status === HackathonStatus . ACTIVE ) {
327+ notificationType = NotificationType . HACKATHON_ACTIVE ;
328+ } else if ( hackathon . status === HackathonStatus . COMPLETED ) {
329+ notificationType = NotificationType . HACKATHON_COMPLETED ;
330+ } else if ( hackathon . status === HackathonStatus . CANCELLED ) {
331+ notificationType = NotificationType . HACKATHON_CANCELLED ;
332+ }
333+ }
334+
335+ // Notify organization members
336+ const allMembers = [ organization . owner , ...organization . members ] . filter (
337+ ( email ) => email !== user . email ,
338+ ) ;
339+
340+ if ( allMembers . length > 0 ) {
341+ const memberUsers = await User . find ( {
342+ email : { $in : allMembers } ,
343+ } ) . select (
344+ "email profile.firstName profile.lastName settings.notifications" ,
345+ ) ;
346+
347+ await NotificationService . notifyTeamMembers (
348+ memberUsers . map ( ( member ) => ( {
349+ userId : member . _id ,
350+ email : member . email ,
351+ name :
352+ `${ member . profile ?. firstName || "" } ${ member . profile ?. lastName || "" } ` . trim ( ) ||
353+ member . email ,
354+ } ) ) ,
355+ {
356+ type : notificationType ,
357+ title : statusChanged
358+ ? `Hackathon status changed: ${ hackathon . title || "Hackathon" } `
359+ : `Hackathon updated: ${ hackathon . title || "Hackathon" } ` ,
360+ message : statusChanged
361+ ? `${ updaterName } has changed the status of "${ hackathon . title || "Hackathon" } " from ${ oldStatus } to ${ hackathon . status } `
362+ : `${ updaterName } has updated "${ hackathon . title || "Hackathon" } "` ,
363+ data : {
364+ hackathonId : hackathon . _id ,
365+ hackathonName : hackathon . title || "Hackathon" ,
366+ hackathonSlug : hackathon . slug ,
367+ organizationId : new mongoose . Types . ObjectId ( orgId ) ,
368+ oldStatus : statusChanged ? oldStatus : undefined ,
369+ newStatus : statusChanged ? hackathon . status : undefined ,
370+ changes : statusChanged ? undefined : changes ,
371+ } ,
372+ emailTemplate : statusChanged
373+ ? EmailTemplatesService . getTemplate (
374+ hackathon . status === HackathonStatus . ACTIVE
375+ ? "hackathon-active"
376+ : hackathon . status === HackathonStatus . COMPLETED
377+ ? "hackathon-completed"
378+ : hackathon . status === HackathonStatus . CANCELLED
379+ ? "hackathon-cancelled"
380+ : "hackathon-updated" ,
381+ {
382+ hackathonId : hackathon . _id . toString ( ) ,
383+ hackathonName : hackathon . title || "Hackathon" ,
384+ hackathonSlug : hackathon . slug ,
385+ organizationId : orgId ,
386+ oldStatus,
387+ newStatus : hackathon . status ,
388+ startDate : hackathon . startDate ,
389+ reason : ( hackathon as any ) . cancellationReason ,
390+ unsubscribeUrl : undefined ,
391+ } ,
392+ )
393+ : EmailTemplatesService . getTemplate ( "hackathon-updated" , {
394+ hackathonId : hackathon . _id . toString ( ) ,
395+ hackathonName : hackathon . title || "Hackathon" ,
396+ hackathonSlug : hackathon . slug ,
397+ changes,
398+ unsubscribeUrl : undefined ,
399+ } ) ,
400+ sendEmail : statusChanged , // Only send email for status changes
401+ sendInApp : true ,
402+ } ,
403+ ) ;
404+ }
405+
406+ // TODO: Notify participants if hackathon is updated (especially for status changes)
407+ // This would require querying HackathonParticipant model
408+ } catch ( notificationError ) {
409+ console . error (
410+ "Error sending hackathon update notifications:" ,
411+ notificationError ,
412+ ) ;
413+ // Don't fail the whole operation if notification fails
414+ }
415+
210416 sendSuccess ( res , hackathon , "Hackathon updated successfully" ) ;
211417 } catch ( error ) {
212418 console . error ( "Update hackathon error:" , error ) ;
0 commit comments