From 2fb0e859f1f36d442506489dc0f94ab2f6b7dcd2 Mon Sep 17 00:00:00 2001 From: Aarohi Sahu Date: Wed, 15 Jul 2026 23:04:36 +0530 Subject: [PATCH 1/2] feat(api): notification created for each type --- server/src/controllers/requests.ts | 27 ++- .../src/controllers/update-collaboration.ts | 218 +++++++++++------- 2 files changed, 150 insertions(+), 95 deletions(-) diff --git a/server/src/controllers/requests.ts b/server/src/controllers/requests.ts index c0f1abb..68c8e20 100644 --- a/server/src/controllers/requests.ts +++ b/server/src/controllers/requests.ts @@ -333,8 +333,9 @@ const approveJoinRequestController = async ( }, }); } + await createNotification(tx,joinRequest.userId,NotificationType.JOIN_APPROVED,"Join Request Approved","Yay! Your request to join a collaboration has been approved",false,collaboration.chatRoom?.id || null) }); - + return res.status(200).json({ success: true, message: "Request approved successfully", @@ -379,6 +380,7 @@ const rejectJoinRequestController = async ( creatorId: true, status: true, deletedAt: true, + title: true, }, }, }, @@ -420,15 +422,18 @@ const rejectJoinRequestController = async ( }); } - await prisma.collaborationMember.update({ - where: { - id: requestId, - }, - data: { - joinStatus: JoinStatus.REJECTED, - }, + await prisma.$transaction(async(tx)=>{ + await tx.collaborationMember.update({ + where: { + id: requestId, + }, + data: { + joinStatus: JoinStatus.REJECTED, + }, + }); + await createNotification(tx,joinRequest.userId,NotificationType.JOIN_REJECTED,"Request Rejected",`Join request for ${joinRequest.collaboration.title} has been rejected.`,false,null); }); - + return res.status(200).json({ success: true, message: "Request rejected successfully", @@ -724,4 +729,6 @@ const removeMemberController = async ( } }; -export { getRequestsController, createJoinRequestController, approveJoinRequestController, rejectJoinRequestController, leaveRequestController, removeMemberController }; \ No newline at end of file +export { getRequestsController, createJoinRequestController, approveJoinRequestController, rejectJoinRequestController, leaveRequestController, removeMemberController }; + + diff --git a/server/src/controllers/update-collaboration.ts b/server/src/controllers/update-collaboration.ts index 6c73566..89b44aa 100644 --- a/server/src/controllers/update-collaboration.ts +++ b/server/src/controllers/update-collaboration.ts @@ -1,7 +1,8 @@ import { Request, Response } from 'express'; import logger from '../utils/logger'; import prisma from '../models'; -import { JoinStatus, CollaborationStatus } from '@prisma/client'; +import { JoinStatus, CollaborationStatus, NotificationType } from '@prisma/client'; +import createNotification from '../utils/createNotification'; const updateCollaborationController = async (req: Request, res: Response) => { try { @@ -179,7 +180,15 @@ const deleteCollaborationController = async (req: Request, res: Response) => { const collaboration = await prisma.collaboration.findUnique({ where: { id }, - select: { creatorId: true, status: true }, + select: { creatorId: true, status: true, title: true }, + }); + + // Get current approved members count + const approvedMembers = await prisma.collaborationMember.findMany({ + where: { + collaborationId: id, + joinStatus: JoinStatus.APPROVED, + }, }); if (!collaboration) { @@ -207,14 +216,31 @@ const deleteCollaborationController = async (req: Request, res: Response) => { }); } + // Soft-delete: update status to CANCELLED and set deletedAt - await prisma.collaboration.update({ - where: { id }, - data: { - deletedAt: new Date(), - status: CollaborationStatus.CANCELLED, - }, - }); + await prisma.$transaction(async(tx)=>{ + await tx.collaboration.update({ + where: { id }, + data: { + deletedAt: new Date(), + status: CollaborationStatus.CANCELLED, + }, + }); + await Promise.all( + approvedMembers.map((member) => + createNotification( + tx, + member.id, + NotificationType.COLLABORATION_COMPLETED, + "Collaboration Cancelled", + `Collaboration "${collaboration.title}" has been cancelled.`, + false, + null + ) + ) + ); + }) + // TODO: // When collaboration is cancelled (deleted): @@ -269,6 +295,11 @@ const completeCollaborationController = async (req: Request, res: Response) => { bio: true, }, }, + chatRoom: { + select:{ + id: true, + } + } }, }); @@ -341,88 +372,105 @@ const completeCollaborationController = async (req: Request, res: Response) => { } // Update status to COMPLETED - const updated = await prisma.collaboration.update({ - where: { id }, - data: { - status: CollaborationStatus.COMPLETED, - completedAt: new Date(), - }, - include: { - creator: { - select: { - id: true, - email: true, - name: true, - onboardingCompleted: true, - username: true, - avatarUrl: true, - bio: true, - }, + await prisma.$transaction(async(tx)=>{ + const updated = await tx.collaboration.update({ + where: { id }, + data: { + status: CollaborationStatus.COMPLETED, + completedAt: new Date(), }, - members: { - where: { joinStatus: JoinStatus.APPROVED }, - include: { - user: { - select: { - id: true, - name: true, - username: true, - avatarUrl: true, + include: { + creator: { + select: { + id: true, + email: true, + name: true, + onboardingCompleted: true, + username: true, + avatarUrl: true, + bio: true, + }, + }, + members: { + where: { joinStatus: JoinStatus.APPROVED }, + include: { + user: { + select: { + id: true, + name: true, + username: true, + avatarUrl: true, + }, }, }, }, }, - }, - }); - - // TODO: - // Ratings are available for 7 days after completedAt. - // Rating availability is computed dynamically. - // No database state is required. - - // TODO: - // Create COLLABORATION_COMPLETED notifications - // for all approved members except the creator. - - // Chat cleanup is handled by the hourly cron. - // Delete chat room, members and messages - // after completedAt + 7 days. - - // Format members to match CollaborationResponse structure - const formattedMembers = updated.members.map((m: any) => ({ - id: m.user.id, - name: m.user.name, - username: m.user.username, - avatarUrl: m.user.avatarUrl, - })); + }); - return res.status(200).json({ - success: true, - collaboration: { - id: updated.id, - title: updated.title, - description: updated.description, - fromLocation: { - name: updated.fromLocationName, - lat: updated.fromLat, - lng: updated.fromLng, - }, - toLocation: { - name: updated.toLocationName, - lat: updated.toLat, - lng: updated.toLng, - }, - scheduledAt: updated.scheduledAt.toISOString(), - maxMembers: updated.maxMembers, - status: updated.status, - creator: updated.creator, - category: updated.category, - }, - members: formattedMembers, - currentMembers: approvedMembersCount, - isCreator: true, - myJoinStatus: JoinStatus.APPROVED, - }); + // TODO: + // Ratings are available for 7 days after completedAt. + // Rating availability is computed dynamically. + // No database state is required. + + // TODO: + // Create COLLABORATION_COMPLETED notifications + // for all approved members except the creator. + + // Chat cleanup is handled by the hourly cron. + // Delete chat room, members and messages + // after completedAt + 7 days. + + // Format members to match CollaborationResponse structure + const formattedMembers = updated.members.map((m: any) => ({ + id: m.user.id, + name: m.user.name, + username: m.user.username, + avatarUrl: m.user.avatarUrl, + })); + + await Promise.all( + formattedMembers.map((member) => + createNotification( + tx, + member.id, + NotificationType.COLLABORATION_COMPLETED, + "Collaboration Completed", + `Yo! Your collaboration "${collaboration.title}" has been completed.`, + false, + collaboration.chatRoom?.id || null + ) + ) + ); + + return res.status(200).json({ + success: true, + collaboration: { + id: updated.id, + title: updated.title, + description: updated.description, + fromLocation: { + name: updated.fromLocationName, + lat: updated.fromLat, + lng: updated.fromLng, + }, + toLocation: { + name: updated.toLocationName, + lat: updated.toLat, + lng: updated.toLng, + }, + scheduledAt: updated.scheduledAt.toISOString(), + maxMembers: updated.maxMembers, + status: updated.status, + creator: updated.creator, + category: updated.category, + }, + members: formattedMembers, + currentMembers: approvedMembersCount, + isCreator: true, + myJoinStatus: JoinStatus.APPROVED, + }); + }) + } catch (error) { logger.error('Error completing collaboration', { error }); return res From 377fe024939c83849e816622f7b7edcab3117cd9 Mon Sep 17 00:00:00 2001 From: Aarohi Sahu Date: Wed, 15 Jul 2026 23:51:24 +0530 Subject: [PATCH 2/2] fix: path redirect of notification --- client/src/constants/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/constants/index.ts b/client/src/constants/index.ts index 264c9b6..0969ec4 100644 --- a/client/src/constants/index.ts +++ b/client/src/constants/index.ts @@ -122,9 +122,9 @@ export const NOTIFICATION_CONFIG: Record = iconColor: "text-emerald-600", cardBg: "bg-emerald-50/40", cardBorder: "border-emerald-200/60", - actionLabel: "View Collaboration", + actionLabel: "View Chat", getRedirectPath: (referenceId) => - `/dashboard/collaborations/${referenceId}`, + `/dashboard/chats/chat/${referenceId}`, }, JOIN_REJECTED: { icon: XCircle,