Skip to content

Commit

Permalink
#3074-refactored notifications into pop-ups
Browse files Browse the repository at this point in the history
  • Loading branch information
caiodasilva2005 committed Dec 22, 2024
1 parent 2be6d82 commit 329b60f
Show file tree
Hide file tree
Showing 35 changed files with 479 additions and 486 deletions.
2 changes: 2 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import organizationRouter from './src/routes/organizations.routes';
import recruitmentRouter from './src/routes/recruitment.routes';
import announcementsRouter from './src/routes/announcements.routes';
import onboardingRouter from './src/routes/onboarding.routes';
import popUpsRouter from './src/routes/pop-up.routes';

const app = express();

Expand Down Expand Up @@ -70,6 +71,7 @@ app.use('/templates', workPackageTemplatesRouter);
app.use('/cars', carsRouter);
app.use('/organizations', organizationRouter);
app.use('/recruitment', recruitmentRouter);
app.use('/pop-ups', popUpsRouter);
app.use('/announcements', announcementsRouter);
app.use('/onboarding', onboardingRouter);
app.use('/', (_req, res) => {
Expand Down
30 changes: 0 additions & 30 deletions src/backend/src/controllers/notifications.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,4 @@ export default class NotificationsController {
next(error);
}
}

static async getUserUnreadNotifications(req: Request, res: Response, next: NextFunction) {
try {
const { organization, currentUser } = req;

const unreadNotifications = await NotificationsService.getUserUnreadNotifications(
currentUser.userId,
organization.organizationId
);
res.status(200).json(unreadNotifications);
} catch (error: unknown) {
next(error);
}
}

static async removeUserNotification(req: Request, res: Response, next: NextFunction) {
try {
const { notificationId } = req.params;
const { organization, currentUser } = req;

const unreadNotifications = await NotificationsService.removeUserNotification(
currentUser.userId,
notificationId,
organization.organizationId
);
res.status(200).json(unreadNotifications);
} catch (error: unknown) {
next(error);
}
}
}
27 changes: 27 additions & 0 deletions src/backend/src/controllers/popUps.controllers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NextFunction, Request, Response } from 'express';
import { PopUpService } from '../services/pop-up.services';

export default class PopUpsController {
static async getUserUnreadPopUps(req: Request, res: Response, next: NextFunction) {
try {
const { organization, currentUser } = req;

const unreadPopUps = await PopUpService.getUserUnreadPopUps(currentUser.userId, organization.organizationId);
res.status(200).json(unreadPopUps);
} catch (error: unknown) {
next(error);
}
}

static async removeUserPopUps(req: Request, res: Response, next: NextFunction) {
try {
const { popUpId } = req.params;
const { organization, currentUser } = req;

const unreadPopUps = await PopUpService.removeUserPopUp(currentUser.userId, popUpId, organization.organizationId);
res.status(200).json(unreadPopUps);
} catch (error: unknown) {
next(error);
}
}
}
11 changes: 0 additions & 11 deletions src/backend/src/prisma-query-args/notifications.query-args.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/backend/src/prisma-query-args/pop-up.query-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Prisma } from '@prisma/client';
import { getUserQueryArgs } from './user.query-args';

export type PopUpQueryArgs = ReturnType<typeof getPopUpQueryArgs>;

export const getPopUpQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.PopUpDefaultArgs>()({
include: {
users: getUserQueryArgs(organizationId)
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ALTER TABLE "Project" ADD COLUMN "organizationId" TEXT;
CREATE TABLE "Announcement" (
"announcementId" TEXT NOT NULL,
"text" TEXT NOT NULL,
"dateCreated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"dateMessageSent" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"dateDeleted" TIMESTAMP(3),
"senderName" TEXT NOT NULL,
"slackEventId" TEXT NOT NULL,
Expand All @@ -19,13 +19,13 @@ CREATE TABLE "Announcement" (
);

-- CreateTable
CREATE TABLE "Notification" (
"notificationId" TEXT NOT NULL,
CREATE TABLE "PopUp" (
"popUpId" TEXT NOT NULL,
"text" TEXT NOT NULL,
"iconName" TEXT NOT NULL,
"eventLink" TEXT,

CONSTRAINT "Notification_pkey" PRIMARY KEY ("notificationId")
CONSTRAINT "PopUp_pkey" PRIMARY KEY ("popUpId")
);

-- CreateTable
Expand All @@ -35,7 +35,7 @@ CREATE TABLE "_receivedAnnouncements" (
);

-- CreateTable
CREATE TABLE "_userNotifications" (
CREATE TABLE "_userPopUps" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);
Expand All @@ -50,10 +50,10 @@ CREATE UNIQUE INDEX "_receivedAnnouncements_AB_unique" ON "_receivedAnnouncement
CREATE INDEX "_receivedAnnouncements_B_index" ON "_receivedAnnouncements"("B");

-- CreateIndex
CREATE UNIQUE INDEX "_userNotifications_AB_unique" ON "_userNotifications"("A", "B");
CREATE UNIQUE INDEX "_userPopUps_AB_unique" ON "_userPopUps"("A", "B");

-- CreateIndex
CREATE INDEX "_userNotifications_B_index" ON "_userNotifications"("B");
CREATE INDEX "_userPopUps_B_index" ON "_userPopUps"("B");

-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("organizationId") ON DELETE SET NULL ON UPDATE CASCADE;
Expand All @@ -65,7 +65,7 @@ ALTER TABLE "_receivedAnnouncements" ADD CONSTRAINT "_receivedAnnouncements_A_fk
ALTER TABLE "_receivedAnnouncements" ADD CONSTRAINT "_receivedAnnouncements_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_userNotifications" ADD CONSTRAINT "_userNotifications_A_fkey" FOREIGN KEY ("A") REFERENCES "Notification"("notificationId") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "_userPopUps" ADD CONSTRAINT "_userPopUps_A_fkey" FOREIGN KEY ("A") REFERENCES "PopUp"("popUpId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_userNotifications" ADD CONSTRAINT "_userNotifications_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "_userPopUps" ADD CONSTRAINT "_userPopUps_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
16 changes: 8 additions & 8 deletions src/backend/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ model User {
createdMilestones Milestone[] @relation(name: "milestoneCreator")
deletedMilestones Milestone[] @relation(name: "milestoneDeleter")
unreadAnnouncements Announcement[] @relation(name: "receivedAnnouncements")
unreadNotifications Notification[] @relation(name: "userNotifications")
unreadPopUps PopUp[] @relation(name: "userPopUps")
}

model Role {
Expand Down Expand Up @@ -935,17 +935,17 @@ model Announcement {
announcementId String @id @default(uuid())
text String
usersReceived User[] @relation("receivedAnnouncements")
dateCreated DateTime @default(now())
dateMessageSent DateTime @default(now())
dateDeleted DateTime?
senderName String
slackEventId String @unique
slackChannelName String
}

model Notification {
notificationId String @id @default(uuid())
text String
iconName String
users User[] @relation("userNotifications")
eventLink String?
model PopUp {
popUpId String @id @default(uuid())
text String
iconName String
users User[] @relation("userPopUps")
eventLink String?
}
1 change: 1 addition & 0 deletions src/backend/src/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,7 @@ const performSeed: () => Promise<void> = async () => {
await AnnouncementService.createAnnouncement(
'Welcome to Finishline!',
[regina.userId],
new Date(),
'Thomas Emrax',
'1',
'software',
Expand Down
2 changes: 0 additions & 2 deletions src/backend/src/routes/notifications.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ import NotificationsController from '../controllers/notifications.controllers';
const notificationsRouter = express.Router();

notificationsRouter.post('/task-deadlines', NotificationsController.sendDailySlackNotifications);
notificationsRouter.get('/current-user', NotificationsController.getUserUnreadNotifications);
notificationsRouter.post('/:notificationId/remove', NotificationsController.removeUserNotification);

export default notificationsRouter;
9 changes: 9 additions & 0 deletions src/backend/src/routes/pop-up.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from 'express';
import PopUpsController from '../controllers/popUps.controllers';

const popUpsRouter = express.Router();

popUpsRouter.get('/current-user', PopUpsController.getUserUnreadPopUps);
popUpsRouter.post('/:popUpId/remove', PopUpsController.removeUserPopUps);

export default popUpsRouter;
2 changes: 2 additions & 0 deletions src/backend/src/services/announcement.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class AnnouncementService {
static async createAnnouncement(
text: string,
usersReceivedIds: string[],
dateMessageSent: Date,
senderName: string,
slackEventId: string,
slackChannelName: string,
Expand All @@ -33,6 +34,7 @@ export default class AnnouncementService {
userId: id
}))
},
dateMessageSent,
senderName,
slackEventId,
slackChannelName
Expand Down
6 changes: 3 additions & 3 deletions src/backend/src/services/change-requests.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
import { ChangeRequestQueryArgs, getChangeRequestQueryArgs } from '../prisma-query-args/change-requests.query-args';
import proposedSolutionTransformer from '../transformers/proposed-solutions.transformer';
import { getProposedSolutionQueryArgs } from '../prisma-query-args/proposed-solutions.query-args';
import { sendHomeCrRequestReviewNotification, sendHomeCrReviewedNotification } from '../utils/notifications.utils';
import { sendCrRequestReviewPopUp, sendCrReviewedPopUp } from '../utils/pop-up.utils';

export default class ChangeRequestsService {
/**
Expand Down Expand Up @@ -151,7 +151,7 @@ export default class ChangeRequestsService {
// send a notification to the submitter that their change request has been reviewed
await sendCRSubmitterReviewedNotification(updated);

await sendHomeCrReviewedNotification(foundCR, updated.submitter, accepted, organization.organizationId);
await sendCrReviewedPopUp(foundCR, updated.submitter, accepted, organization.organizationId);

// send a reply to a CR's notifications of its updated status
await sendSlackCRStatusToThread(updated.notificationSlackThreads, foundCR.crId, foundCR.identifier, accepted);
Expand Down Expand Up @@ -1082,6 +1082,6 @@ export default class ChangeRequestsService {
// send slack message to CR reviewers
await sendSlackRequestedReviewNotification(newReviewers, changeRequestTransformer(foundCR));

await sendHomeCrRequestReviewNotification(foundCR, newReviewers, organization.organizationId);
await sendCrRequestReviewPopUp(foundCR, newReviewers, organization.organizationId);
}
}
4 changes: 2 additions & 2 deletions src/backend/src/services/design-reviews.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { getWorkPackageQueryArgs } from '../prisma-query-args/work-packages.quer
import { UserWithSettings } from '../utils/auth.utils';
import { getUserScheduleSettingsQueryArgs } from '../prisma-query-args/user.query-args';
import { createCalendarEvent, deleteCalendarEvent, updateCalendarEvent } from '../utils/google-integration.utils';
import { sendHomeDrNotification } from '../utils/notifications.utils';
import { sendDrPopUp } from '../utils/pop-up.utils';

export default class DesignReviewsService {
/**
Expand Down Expand Up @@ -206,7 +206,7 @@ export default class DesignReviewsService {
}
}

await sendHomeDrNotification(designReview, members, submitter, wbsElement.name, organization.organizationId);
await sendDrPopUp(designReview, members, submitter, wbsElement.name, organization.organizationId);

const project = wbsElement.workPackage?.project;
const teams = project?.teams;
Expand Down
98 changes: 1 addition & 97 deletions src/backend/src/services/notifications.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import { daysBetween, startOfDay, wbsPipe } from 'shared';
import { buildDueString } from '../utils/slack.utils';
import WorkPackagesService from './work-packages.services';
import { addWeeksToDate } from 'shared';
import { HttpException, NotFoundException } from '../utils/errors.utils';
import { HttpException } from '../utils/errors.utils';
import { meetingStartTimePipe } from '../utils/design-reviews.utils';
import { getNotificationQueryArgs } from '../prisma-query-args/notifications.query-args';
import notificationTransformer from '../transformers/notifications.transformer';

export default class NotificationsService {
static async sendDailySlackNotifications() {
Expand Down Expand Up @@ -195,98 +193,4 @@ export default class NotificationsService {

await Promise.all(promises);
}

/**
* Gets all of a user's unread notifications
* @param userId id of user to get unread notifications from
* @param organization the user's orgainzation
* @returns the unread notifications of the user
*/
static async getUserUnreadNotifications(userId: string, organizationId: string) {
const unreadNotifications = await prisma.notification.findMany({
where: {
users: {
some: { userId }
}
},
...getNotificationQueryArgs(organizationId)
});

if (!unreadNotifications) throw new HttpException(404, 'User Unread Notifications Not Found');

return unreadNotifications.map(notificationTransformer);
}

/**
* Removes a notification from the user's unread notifications
* @param userId id of the current user
* @param notificationId id of the notification to remove
* @param organization the user's organization
* @returns the user's updated unread notifications
*/
static async removeUserNotification(userId: string, notificationId: string, organizationId: string) {
const updatedUser = await prisma.user.update({
where: { userId },
data: {
unreadNotifications: {
disconnect: {
notificationId
}
}
},
include: { unreadNotifications: getNotificationQueryArgs(organizationId) }
});

if (!updatedUser) throw new HttpException(404, `Failed to remove notication: ${notificationId}`);

return updatedUser.unreadNotifications.map(notificationTransformer);
}

/**
* Creates and sends a notification to all users with the given userIds
* @param text writing in the notification
* @param iconName icon that appears in the notification
* @param userIds ids of users to send the notification to
* @param organizationId
* @param eventLink link the notification will go to when clicked
* @returns the created notification
*/
static async sendNotifcationToUsers(
text: string,
iconName: string,
userIds: string[],
organizationId: string,
eventLink?: string
) {
const createdNotification = await prisma.notification.create({
data: {
text,
iconName,
eventLink
},
...getNotificationQueryArgs(organizationId)
});

if (!createdNotification) throw new HttpException(500, 'Failed to create notification');

const notificationsPromises = userIds.map(async (userId) => {
const requestedUser = await prisma.user.findUnique({
where: { userId }
});

if (!requestedUser) throw new NotFoundException('User', userId);

return await prisma.user.update({
where: { userId: requestedUser.userId },
data: {
unreadNotifications: {
connect: { notificationId: createdNotification.notificationId }
}
}
});
});

await Promise.all(notificationsPromises);
return notificationTransformer(createdNotification);
}
}
Loading

0 comments on commit 329b60f

Please sign in to comment.