Skip to content

Commit

Permalink
#2998-merged sending notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
caiodasilva2005 committed Nov 27, 2024
2 parents 6ae13e3 + 598d8fe commit 810fbe0
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 69 deletions.
12 changes: 0 additions & 12 deletions src/backend/src/controllers/users.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,4 @@ export default class UsersController {
return next(error);
}
}

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

const updatedUser = await UsersService.sendNotification(userId, text, iconName);
return res.status(200).json(updatedUser);
} catch (error: unknown) {
return next(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Warnings:
- You are about to drop the column `userId` on the `Notification` table. All the data in the column will be lost.
- Added the required column `text` to the `Announcement` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "Notification" DROP CONSTRAINT "Notification_userId_fkey";

-- AlterTable
ALTER TABLE "Announcement" ADD COLUMN "text" TEXT NOT NULL;

-- AlterTable
ALTER TABLE "Notification" DROP COLUMN "userId";

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

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

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

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

-- CreateIndex
CREATE UNIQUE INDEX "_NotificationToUser_AB_unique" ON "_NotificationToUser"("A", "B");

-- CreateIndex
CREATE INDEX "_NotificationToUser_B_index" ON "_NotificationToUser"("B");

-- AddForeignKey
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;

-- AddForeignKey
ALTER TABLE "_NotificationToUser" ADD CONSTRAINT "_NotificationToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Notification"("notificationId") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_NotificationToUser" ADD CONSTRAINT "_NotificationToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
6 changes: 0 additions & 6 deletions src/backend/src/routes/users.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,5 @@ userRouter.post(
UsersController.getManyUserTasks
);
userRouter.get('/:userId/notifications', UsersController.getUserUnreadNotifications);
userRouter.post(
`/:userId/notifications/send`,
nonEmptyString(body('text')),
nonEmptyString(body('iconName')),
UsersController.sendNotitifcation
);

export default userRouter;
23 changes: 0 additions & 23 deletions src/backend/src/services/users.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,27 +583,4 @@ export default class UsersService {

return requestedUser.unreadNotifications.map(notificationTransformer);
}

static async sendNotification(userId: string, text: string, iconName: string) {
const requestedUser = await prisma.user.findUnique({
where: { userId }
});

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

const createdNotification = await prisma.notification.create({
data: {
text,
iconName
}
});

const udaptedUser = await prisma.user.update({
where: { userId: requestedUser.userId },
data: { unreadNotifications: { connect: createdNotification } },
include: { unreadNotifications: true }
});

return udaptedUser;
}
}
1 change: 1 addition & 0 deletions src/backend/src/transformers/notification.transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Notification } from 'shared';

const notificationTransformer = (notification: Prisma.NotificationGetPayload<NotificationQueryArgs>): Notification => {
return {
notificationId: notification.notificationId,
text: notification.text,
iconName: notification.iconName
};
Expand Down
37 changes: 37 additions & 0 deletions src/backend/src/utils/homepage-notifications.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getNotificationQueryArgs } from '../prisma-query-args/notifications.query-args';
import prisma from '../prisma/prisma';
import notificationTransformer from '../transformers/notification.transformer';
import { NotFoundException } from './errors.utils';

const sendNotificationToUser = async (userId: string, notificationId: string, organizationId: string) => {
const requestedUser = await prisma.user.findUnique({
where: { userId }
});

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

const updatedUser = await prisma.user.update({
where: { userId: requestedUser.userId },
data: { unreadNotifications: { connect: { notificationId } } },
include: { unreadNotifications: getNotificationQueryArgs(organizationId) }
});

return updatedUser.unreadNotifications.map(notificationTransformer);
};

export const sendNotificationToUsers = async (userIds: string[], text: string, iconName: string, organizationId: string) => {
const createdNotification = await prisma.notification.create({
data: {
text,
iconName
},
...getNotificationQueryArgs(organizationId)
});

const notificationPromises = userIds.map(async (userId) => {
return sendNotificationToUser(userId, createdNotification.notificationId, organizationId);
});

await Promise.all(notificationPromises);
return notificationTransformer(createdNotification);
};
49 changes: 49 additions & 0 deletions src/backend/tests/unmocked/notifications.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Organization } from '@prisma/client';
import { createTestOrganization, createTestUser, resetUsers } from '../test-utils';
import { batmanAppAdmin, wonderwomanGuest } from '../test-data/users.test-data';
import { NotFoundException } from '../../src/utils/errors.utils';
import { sendNotificationToUsers } from '../../src/utils/homepage-notifications.utils';
import prisma from '../../src/prisma/prisma';

describe('Notification 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 sendNotificationToUsers(['1'], 'test', 'test', orgId)).rejects.toThrow(
new NotFoundException('User', '1')
);
});

it('Succeeds and sends notification to user', async () => {
const testBatman = await createTestUser(batmanAppAdmin, orgId);
const testWonderWoman = await createTestUser(wonderwomanGuest, orgId);

const notification = await sendNotificationToUsers([testBatman.userId, testWonderWoman.userId], 'test', 'icon', orgId);

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

expect(batmanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(batmanWithNotifications?.unreadNotifications[0]).toStrictEqual(notification);

expect(wonderWomanWithNotifications?.unreadNotifications).toHaveLength(1);
expect(wonderWomanWithNotifications?.unreadNotifications[0]).toStrictEqual(notification);
});
});
});
29 changes: 3 additions & 26 deletions src/backend/tests/unmocked/users.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createTestOrganization, createTestTask, createTestUser, resetUsers } fr
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';
import { sendNotificationToUsers } from '../../src/utils/homepage-notifications.utils';

describe('User Tests', () => {
let orgId: string;
Expand Down Expand Up @@ -50,29 +50,6 @@ describe('User Tests', () => {
});
});

describe('Send Notification', () => {
it('fails on invalid user id', async () => {
await expect(async () => await UsersService.sendNotification('1', 'test', 'test')).rejects.toThrow(
new NotFoundException('User', '1')
);
});

it('Succeeds and sends notification to user', async () => {
const testBatman = await createTestUser(batmanAppAdmin, orgId);
await UsersService.sendNotification(testBatman.userId, 'test1', 'test1');
await UsersService.sendNotification(testBatman.userId, 'test2', 'test2');

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

expect(batmanWithNotifications?.unreadNotifications).toHaveLength(2);
expect(batmanWithNotifications?.unreadNotifications[0].text).toBe('test1');
expect(batmanWithNotifications?.unreadNotifications[1].text).toBe('test2');
});
});

describe('Get Notifications', () => {
it('fails on invalid user id', async () => {
await expect(async () => await UsersService.getUserUnreadNotifications('1', organization)).rejects.toThrow(
Expand All @@ -82,8 +59,8 @@ describe('User Tests', () => {

it('Succeeds and gets user notifications', async () => {
const testBatman = await createTestUser(batmanAppAdmin, orgId);
await UsersService.sendNotification(testBatman.userId, 'test1', 'test1');
await UsersService.sendNotification(testBatman.userId, 'test2', 'test2');
await sendNotificationToUsers([testBatman.userId], 'test1', 'test1', orgId);
await sendNotificationToUsers([testBatman.userId], 'test2', 'test2', orgId);

const notifications = await UsersService.getUserUnreadNotifications(testBatman.userId, organization);

Expand Down
5 changes: 3 additions & 2 deletions src/shared/src/types/notification.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Notification {
text: String;
iconName: String;
notificationId: string;
text: string;
iconName: string;
}

0 comments on commit 810fbe0

Please sign in to comment.