Skip to content

Commit

Permalink
#2997-include only notification service
Browse files Browse the repository at this point in the history
  • Loading branch information
caiodasilva2005 committed Dec 11, 2024
1 parent e7df823 commit 91b5429
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 116 deletions.
11 changes: 0 additions & 11 deletions src/backend/src/controllers/users.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,4 @@ export default class UsersController {
next(error);
}
}

static async sendNotificationToUsers(req: Request, res: Response, next: NextFunction) {
try {
const { text, iconName, userIds } = req.body;

const createdNotification = await UsersService.sendNotifcationToUsers(text, iconName, userIds, req.organization);
res.status(200).json(createdNotification);
} catch (error: unknown) {
next(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,40 @@ CREATE TABLE "Notification" (
);

-- CreateTable
CREATE TABLE "_ReceivedAnnouncements" (
CREATE TABLE "_receivedAnnouncements" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);

-- CreateTable
CREATE TABLE "_UserNotifications" (
CREATE TABLE "_userNotifications" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);

-- CreateIndex
CREATE UNIQUE INDEX "_ReceivedAnnouncements_AB_unique" ON "_ReceivedAnnouncements"("A", "B");
CREATE UNIQUE INDEX "_receivedAnnouncements_AB_unique" ON "_receivedAnnouncements"("A", "B");

-- CreateIndex
CREATE INDEX "_ReceivedAnnouncements_B_index" ON "_ReceivedAnnouncements"("B");
CREATE INDEX "_receivedAnnouncements_B_index" ON "_receivedAnnouncements"("B");

-- CreateIndex
CREATE UNIQUE INDEX "_UserNotifications_AB_unique" ON "_UserNotifications"("A", "B");
CREATE UNIQUE INDEX "_userNotifications_AB_unique" ON "_userNotifications"("A", "B");

-- CreateIndex
CREATE INDEX "_UserNotifications_B_index" ON "_UserNotifications"("B");
CREATE INDEX "_userNotifications_B_index" ON "_userNotifications"("B");

-- AddForeignKey
ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_userCreatedId_fkey" FOREIGN KEY ("userCreatedId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_ReceivedAnnouncements" ADD CONSTRAINT "_ReceivedAnnouncements_A_fkey" FOREIGN KEY ("A") REFERENCES "Announcement"("announcementId") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "_receivedAnnouncements" ADD CONSTRAINT "_receivedAnnouncements_A_fkey" FOREIGN KEY ("A") REFERENCES "Announcement"("announcementId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_ReceivedAnnouncements" ADD CONSTRAINT "_ReceivedAnnouncements_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
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 "_userNotifications" ADD CONSTRAINT "_userNotifications_A_fkey" FOREIGN KEY ("A") REFERENCES "Notification"("notificationId") 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 "_userNotifications" ADD CONSTRAINT "_userNotifications_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
12 changes: 6 additions & 6 deletions src/backend/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ model User {
deletedFrequentlyAskedQuestions FrequentlyAskedQuestion[] @relation(name: "frequentlyAskedQuestionDeleter")
createdMilestones Milestone[] @relation(name: "milestoneCreator")
deletedMilestones Milestone[] @relation(name: "milestoneDeleter")
receivedAnnouncements Announcement[] @relation(name: "ReceivedAnnouncements")
createdAnnouncements Announcement[] @relation(name: "CreatedAnnouncements")
unreadNotifications Notification[] @relation(name: "UserNotifications")
receivedAnnouncements Announcement[] @relation(name: "receivedAnnouncements")
createdAnnouncements Announcement[] @relation(name: "createdAnnouncements")
unreadNotifications Notification[] @relation(name: "userNotifications")
}

model Role {
Expand Down Expand Up @@ -934,15 +934,15 @@ model Milestone {
model Announcement {
announcementId String @id @default(uuid())
text String
usersReceived User[] @relation("ReceivedAnnouncements")
usersReceived User[] @relation("receivedAnnouncements")
dateCrated DateTime
userCreatedId String
userCreated User @relation("CreatedAnnouncements", fields: [userCreatedId], references: [userId])
userCreated User @relation("createdAnnouncements", fields: [userCreatedId], references: [userId])
}

model Notification {
notificationId String @id @default(uuid())
text String
iconName String
users User[] @relation("UserNotifications")
users User[] @relation("userNotifications")
}
11 changes: 11 additions & 0 deletions src/backend/src/routes/notifications.routes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import express from 'express';
import NotificationsController from '../controllers/notifications.controllers';
import { nonEmptyString, validateInputs } from '../utils/validation.utils';
import { body } from 'express-validator';

const notificationsRouter = express.Router();

notificationsRouter.post('/task-deadlines', NotificationsController.sendDailySlackNotifications);
notificationsRouter.post(
'/send/users',
nonEmptyString(body('text')),
nonEmptyString(body('iconName')),
body('userIds').isArray(),
nonEmptyString(body('userIds.*')),
validateInputs,
NotificationsController.sendNotificationToUsers

Check failure on line 16 in src/backend/src/routes/notifications.routes.ts

View workflow job for this annotation

GitHub Actions / build

Property 'sendNotificationToUsers' does not exist on type 'typeof NotificationsController'.
);

export default notificationsRouter;
10 changes: 0 additions & 10 deletions src/backend/src/routes/users.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,4 @@ userRouter.post(
UsersController.getManyUserTasks
);

userRouter.post(
'/notifications/send/many',
nonEmptyString(body('text')),
nonEmptyString(body('iconName')),
body('userIds').isArray(),
nonEmptyString(body('userIds.*')),
validateInputs,
UsersController.sendNotificationToUsers
);

export default userRouter;
44 changes: 43 additions & 1 deletion src/backend/src/services/notifications.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { daysBetween, startOfDay, wbsPipe } from 'shared';
import { buildDueString } from '../utils/slack.utils';
import WorkPackagesService from './work-packages.services';
import { addWeeksToDate } from 'shared';
import { HttpException } from '../utils/errors.utils';
import { HttpException, NotFoundException } 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 @@ -193,4 +195,44 @@ export default class NotificationsService {

await Promise.all(promises);
}

/**
* 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
* @returns the created notification
*/
static async sendNotifcationToUsers(text: string, iconName: string, userIds: string[], organizationId: string) {
const createdNotification = await prisma.notification.create({
data: {
text,
iconName
},
...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);
}
}
42 changes: 0 additions & 42 deletions src/backend/src/services/users.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import { getAuthUserQueryArgs } from '../prisma-query-args/auth-user.query-args'
import authenticatedUserTransformer from '../transformers/auth-user.transformer';
import { getTaskQueryArgs } from '../prisma-query-args/tasks.query-args';
import taskTransformer from '../transformers/tasks.transformer';
import notificationTransformer from '../transformers/notifications.transformer';
import { getNotificationQueryArgs } from '../prisma-query-args/notifications.query-args';

export default class UsersService {
/**
Expand Down Expand Up @@ -568,44 +566,4 @@ export default class UsersService {
const resolvedTasks = await Promise.all(tasksPromises);
return resolvedTasks.flat();
}

/**
* 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 organization
* @returns the created notification
*/
static async sendNotifcationToUsers(text: string, iconName: string, userIds: string[], organization: Organization) {
const createdNotification = await prisma.notification.create({
data: {
text,
iconName
},
...getNotificationQueryArgs(organization.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);
}
}
59 changes: 59 additions & 0 deletions src/backend/tests/unmocked/notifications.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Organization } from '@prisma/client';
import { createTestOrganization, createTestUser, resetUsers } from '../test-utils';
import { batmanAppAdmin, supermanAdmin } from '../test-data/users.test-data';
import { NotFoundException } from '../../src/utils/errors.utils';
import prisma from '../../src/prisma/prisma';
import NotificationService from '../../src/services/notifications.services';

describe('Notifications Tests', () => {
let orgId: string;
let organization: Organization;
beforeEach(async () => {
organization = await createTestOrganization();
orgId = organization.organizationId;
});

afterEach(async () => {
await resetUsers();
});

describe('Send Notification', () => {
it('fails on invalid user id', async () => {
await expect(
async () =>
await NotificationService.sendNotifcationToUsers(
'test notification',
'star',
['1', '2'],
organization.organizationId
)
).rejects.toThrow(new NotFoundException('User', '1'));
});

it('Succeeds and sends notification to user', async () => {
const testBatman = await createTestUser(batmanAppAdmin, orgId);
const testSuperman = await createTestUser(supermanAdmin, orgId);
await NotificationService.sendNotifcationToUsers(
'test notification',
'star',
[testBatman.userId, testSuperman.userId],
organization.organizationId
);

const batmanWithNotifications = await prisma.user.findUnique({
where: { userId: testBatman.userId },
include: { unreadNotifications: true }
});

const supermanWithNotifications = await prisma.user.findUnique({
where: { userId: testBatman.userId },
include: { unreadNotifications: true }
});

expect(batmanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(batmanWithNotifications?.unreadNotifications[0].text).toBe('test notification');
expect(supermanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(supermanWithNotifications?.unreadNotifications[0].text).toBe('test notification');
});
});
});
37 changes: 1 addition & 36 deletions src/backend/tests/unmocked/users.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Organization } from '@prisma/client';
import { createTestOrganization, createTestTask, createTestUser, resetUsers } from '../test-utils';
import { batmanAppAdmin, supermanAdmin } from '../test-data/users.test-data';
import { batmanAppAdmin } from '../test-data/users.test-data';
import UsersService from '../../src/services/users.services';
import { NotFoundException } from '../../src/utils/errors.utils';
import prisma from '../../src/prisma/prisma';

describe('User Tests', () => {
let orgId: string;
Expand Down Expand Up @@ -49,38 +48,4 @@ describe('User Tests', () => {
expect(userTasks).toStrictEqual([batmanTask, batmanTask]);
});
});

describe('Send Notification', () => {
it('fails on invalid user id', async () => {
await expect(
async () => await UsersService.sendNotifcationToUsers('test notification', 'star', ['1', '2'], organization)
).rejects.toThrow(new NotFoundException('User', '1'));
});

it('Succeeds and sends notification to user', async () => {
const testBatman = await createTestUser(batmanAppAdmin, orgId);
const testSuperman = await createTestUser(supermanAdmin, orgId);
await UsersService.sendNotifcationToUsers(
'test notification',
'star',
[testBatman.userId, testSuperman.userId],
organization
);

const batmanWithNotifications = await prisma.user.findUnique({
where: { userId: testBatman.userId },
include: { unreadNotifications: true }
});

const supermanWithNotifications = await prisma.user.findUnique({
where: { userId: testBatman.userId },
include: { unreadNotifications: true }
});

expect(batmanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(batmanWithNotifications?.unreadNotifications[0].text).toBe('test notification');
expect(supermanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(supermanWithNotifications?.unreadNotifications[0].text).toBe('test notification');
});
});
});

0 comments on commit 91b5429

Please sign in to comment.