diff --git a/db/migrations/20240412003023_add_invites_table.ts b/db/migrations/20240412003023_add_invites_table.ts index af65d8f..bfd0aeb 100644 --- a/db/migrations/20240412003023_add_invites_table.ts +++ b/db/migrations/20240412003023_add_invites_table.ts @@ -21,7 +21,7 @@ export async function up(knex: Knex): Promise { table.increments('id').primary(); table.integer('workspaceId').unsigned(); table.integer('senderId').unsigned(); - table.integer('inviteeId').unsigned(); + table.string('inviteeEmail').notNullable(); table.timestamp('expiresAt').notNullable(); table @@ -33,11 +33,7 @@ export async function up(knex: Knex): Promise { .references('id') .inTable('workspaces') .onDelete('CASCADE'); - table - .foreign('inviteeId') - .references('id') - .inTable('users') - .onDelete('CASCADE'); + table .foreign('senderId') .references('id') diff --git a/db/seeds/08-inviteSeeding.ts b/db/seeds/08-inviteSeeding.ts index ddf8d0b..0fc1a66 100644 --- a/db/seeds/08-inviteSeeding.ts +++ b/db/seeds/08-inviteSeeding.ts @@ -19,12 +19,12 @@ export async function seed(knex: Knex): Promise { * */ await Promise.all([ - EntityFactory.createInvite(1, 1, 2, 1, 'accepted'), - EntityFactory.createInvite(2, 1, 3, 1, 'accepted'), - EntityFactory.createInvite(3, 1, 4, 1, 'accepted'), - EntityFactory.createInvite(4, 2, 5, 3, 'cancelled'), - EntityFactory.createInvite(5, 6, 1, 3, 'pending'), - EntityFactory.createInvite(6, 3, 1, 4, 'pending'), - EntityFactory.createInvite(7, 5, 1, 5, 'cancelled'), + EntityFactory.createInvite(1, 1, 'email2@gmail.com', 1, 'accepted'), + EntityFactory.createInvite(2, 1, 'email3@gmail.com', 1, 'accepted'), + EntityFactory.createInvite(3, 1, 'email4@gmail.com', 1, 'accepted'), + EntityFactory.createInvite(4, 2, 'email5@gmail.com', 3, 'cancelled'), + EntityFactory.createInvite(5, 6, 'email1@gmail.com', 3, 'pending'), + EntityFactory.createInvite(6, 3, 'email1@gmail.com', 4, 'pending'), + EntityFactory.createInvite(7, 5, 'email1@gmail.com', 5, 'cancelled'), ]); } diff --git a/src/api-docs/openAPIDocumentGenerator.ts b/src/api-docs/openAPIDocumentGenerator.ts index aa0d8de..32291cf 100644 --- a/src/api-docs/openAPIDocumentGenerator.ts +++ b/src/api-docs/openAPIDocumentGenerator.ts @@ -8,7 +8,7 @@ import { filesRegistry } from '@/api/files/filesRoutes'; import { healthCheckRegistry } from '@/api/healthCheck/healthCheckRouter'; import { messageRegistery } from '@/api/messages/messageApi'; import { notificationsRegistry } from '@/api/notifications/notificationsRoutes'; -import { reactionsRegistry } from '@/api/reactions/reactionsRouter'; +import { reactionsRegistry } from '@/api/reactions/reactionApi'; import { userRegistry } from '@/api/user/userApi'; import { workspaceRegistry } from '@/api/workspace/workspaceApi'; diff --git a/src/api/invites/__tests__/InviteRepository.test.ts b/src/api/invites/__tests__/InviteRepository.test.ts deleted file mode 100644 index 28c4058..0000000 --- a/src/api/invites/__tests__/InviteRepository.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { mockedTxn } from '../../../common/__tests__/mocks'; -import invitesRepository from '../invitesRepository'; - -describe('inviteRepository', () => { - const trx: any = mockedTxn; - - afterEach(() => { - vi.clearAllMocks(); - }); - - test('createInvite', async () => { - const inviteData = { - senderId: 1, - inviteeId: 2, - workspaceId: 4, - status: 'pending' as 'pending' | 'accepted' | 'cancelled', - expiresAt: new Date(), - }; - await invitesRepository.createInvite(trx, inviteData); - expect(trx.insert).toBeCalledWith(inviteData); - expect(trx.into).toBeCalledWith('invites'); - }); - - test('getInviteById', async () => { - await invitesRepository.getInviteById(trx, '1'); - expect(trx.from).toBeCalledWith('invites'); - expect(trx.where).toBeCalledWith('id', '1'); - }); - - test('getInviteByWorkspaceId', async () => { - await invitesRepository.getInviteByWorkspaceId(trx, '1'); - expect(trx.from).toBeCalledWith('invites'); - expect(trx.where).toBeCalledWith('workspaceId', '1'); - }); - - test('acceptInvite', async () => { - await invitesRepository.acceptInvite(trx, '1'); - expect(trx.update).toBeCalled(); - expect(trx.from).toBeCalledWith('invites'); - }); - - test('cancelInvite', async () => { - await invitesRepository.cancelInvite(trx, '1'); - expect(trx.update).toBeCalled(); - expect(trx.from).toBeCalledWith('invites'); - }); -}); diff --git a/src/api/invites/inviteApi.ts b/src/api/invites/inviteApi.ts new file mode 100644 index 0000000..f32bbbf --- /dev/null +++ b/src/api/invites/inviteApi.ts @@ -0,0 +1,105 @@ +import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; + +import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; +import { messageResponse } from '@/common/utils/commonResponses'; + +import { + CreateInviteSchema, + DeleteInviteSchema, + GetInviteSchema, + InviteSchema, + UpdateInviteSchema, +} from './invitesModel'; + +export const inviteRegistery = new OpenAPIRegistry(); + +const bearerAuth = inviteRegistery.registerComponent( + 'securitySchemes', + 'bearerAuth', + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + } +); + +inviteRegistery.register('Invite', InviteSchema); + +inviteRegistery.registerPath({ + method: 'post', + path: '/invites', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + body: { + content: { + 'application/json': { + schema: CreateInviteSchema.shape.body, + }, + }, + }, + }, + responses: createApiResponse(InviteSchema, 'Success'), +}); + +inviteRegistery.registerPath({ + method: 'get', + path: '/invites/{id}', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + params: GetInviteSchema.shape.params, + }, + responses: createApiResponse(InviteSchema, 'Success'), +}); + +inviteRegistery.registerPath({ + method: 'patch', + path: '/invites/{id}', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + body: { + content: { + 'application/json': { + schema: UpdateInviteSchema.shape.body, + }, + }, + }, + params: UpdateInviteSchema.shape.params, + }, + responses: createApiResponse(messageResponse, 'Success'), +}); + +inviteRegistery.registerPath({ + method: 'patch', + path: '/invites/{id}/acceptInvite', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + params: GetInviteSchema.shape.params, + }, + responses: createApiResponse(messageResponse, 'Success'), +}); + +inviteRegistery.registerPath({ + method: 'patch', + path: '/invites/{id}/cancelInvite', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + params: GetInviteSchema.shape.params, + }, + responses: createApiResponse(messageResponse, 'Success'), +}); + +inviteRegistery.registerPath({ + method: 'delete', + path: '/invites/{id}', + tags: ['Invite'], + security: [{ [bearerAuth.name]: [] }], + request: { + params: DeleteInviteSchema.shape.params, + }, + responses: createApiResponse(messageResponse, 'Success'), +}); diff --git a/src/api/invites/inviteRouter.ts b/src/api/invites/inviteRouter.ts new file mode 100644 index 0000000..1df7764 --- /dev/null +++ b/src/api/invites/inviteRouter.ts @@ -0,0 +1,45 @@ +import express, { Router } from 'express'; + +import { validateRequest } from '@/common/utils/httpHandlers'; + +import AuthController from '../auth/authController'; +import InvitesController from './invitesController'; +import { CreateInviteSchema } from './invitesModel'; + +export const inviteRouter: Router = (() => { + const router = express.Router(); + + router.post( + '/', + [AuthController.authenticate, validateRequest(CreateInviteSchema)], + InvitesController.createInvite + ); + + router.get( + '/:id', + [AuthController.authenticate], + InvitesController.getInviteById + ); + router.patch( + '/:id', + [AuthController.authenticate], + InvitesController.updateInvite + ); + router.patch( + '/:id/acceptInvite', + [AuthController.authenticate], + InvitesController.acceptInvite + ); + router.patch( + '/:id/cancelInvite', + [AuthController.authenticate], + InvitesController.cancelInvite + ); + router.delete( + '/:id', + [AuthController.authenticate], + InvitesController.deleteInvite + ); + + return router; +})(); diff --git a/src/api/invites/inviteService.ts b/src/api/invites/inviteService.ts new file mode 100644 index 0000000..1c38856 --- /dev/null +++ b/src/api/invites/inviteService.ts @@ -0,0 +1,37 @@ +import { Knex } from 'knex'; + +import { CreateInviteDto } from './invitesModel'; +import invitesRepository from './invitesRepository'; + +export function sendInvite(invite: CreateInviteDto, trx: Knex.Transaction) { + return invitesRepository.createInvite(invite, trx); +} + +export function getInviteById(id: string, trx: Knex.Transaction) { + return invitesRepository.getInviteById(id, trx); +} + +export function getInviteByWorkspaceId( + workspaceId: string, + trx: Knex.Transaction +) { + return invitesRepository.getInviteByWorkspaceId(workspaceId, trx); +} +export function updateInvite( + id: string, + status: string, + trx: Knex.Transaction +) { + return invitesRepository.updateInvite(id, status, trx); +} +export function acceptInvite(id: string, trx: Knex.Transaction) { + return invitesRepository.acceptInvite(id, trx); +} + +export function cancelInvite(id: string, trx: Knex.Transaction) { + return invitesRepository.cancelInvite(id, trx); +} + +export function deleteInvite(id: string, trx: Knex.Transaction) { + return invitesRepository.deleteInvite(id, trx); +} diff --git a/src/api/invites/invitesController.ts b/src/api/invites/invitesController.ts index 169e9d0..ebb5657 100644 --- a/src/api/invites/invitesController.ts +++ b/src/api/invites/invitesController.ts @@ -1,11 +1,15 @@ -import db from 'db/db'; import { Request, Response } from 'express'; +import { + asyncHandler, + handleServiceResponse, +} from '@/common/utils/httpHandlers'; + import { CreateInvite } from './invitesModel'; import invitesRepository from './invitesRepository'; const InvitesController = { - createInvite: async (req: Request, res: Response) => { + createInvite: asyncHandler(async (req: Request, res: Response) => { const { inviteeEmail, workspaceId } = req.body; const createInvitePayload: CreateInvite = { @@ -17,35 +21,50 @@ const InvitesController = { }; const invite = await invitesRepository.createInvite( - db, - createInvitePayload + createInvitePayload, + res.trx ); - res.json(invite); - }, - getInviteById: async (req: Request, res: Response) => { + handleServiceResponse(res, invite, 'ok'); + }), + getInviteById: asyncHandler(async (req: Request, res: Response) => { const id = req.params.id; - const invite = await invitesRepository.getInviteById(db, id); - res.json(invite); - }, + const invite = await invitesRepository.getInviteById(id, res.trx); + handleServiceResponse(res, invite, 'ok'); + }), - getWorkspaceInvites: async (req: Request, res: Response) => { + getWorkspaceInvites: asyncHandler(async (req: Request, res: Response) => { const workspaceId = req.params.id; const invites = await invitesRepository.getInviteByWorkspaceId( - db, - workspaceId + workspaceId, + res.trx ); - res.json(invites); - }, - acceptInvite: async (req: Request, res: Response) => { + handleServiceResponse(res, invites, 'ok'); + }), + updateInvite: asyncHandler(async (req: Request, res: Response) => { + const id = req.params.id; + const { status } = req.body; + const updatedInvite = await invitesRepository.updateInvite( + id, + status, + res.trx + ); + handleServiceResponse(res, updatedInvite, 'ok'); + }), + acceptInvite: asyncHandler(async (req: Request, res: Response) => { + const id = req.params.id; + await invitesRepository.acceptInvite(id, res.trx); + handleServiceResponse(res, 'Invite accepted', 'ok'); + }), + cancelInvite: asyncHandler(async (req: Request, res: Response) => { const id = req.params.id; - await invitesRepository.acceptInvite(db, id); - res.sendStatus(200); - }, - cancelInvite: async (req: Request, res: Response) => { + await invitesRepository.cancelInvite(id, res.trx); + handleServiceResponse(res, 'Invite cancelled', 'ok'); + }), + deleteInvite: asyncHandler(async (req: Request, res: Response) => { const id = req.params.id; - await invitesRepository.cancelInvite(db, id); - res.sendStatus(200); - }, + await invitesRepository.deleteInvite(id, res.trx); + handleServiceResponse(res, 'Invite deleted', 'ok'); + }), }; export default InvitesController; diff --git a/src/api/invites/invitesModel.ts b/src/api/invites/invitesModel.ts index b8cdca5..424fad7 100644 --- a/src/api/invites/invitesModel.ts +++ b/src/api/invites/invitesModel.ts @@ -22,8 +22,8 @@ export type CreateInviteDto = Omit; export const CreateInviteSchema = z.object({ body: z.object({ inviteeEmail: z.string().email(), + workspaceId: z.number(), }), - params: z.object({ id: z.number() }), }); export const GetInviteSchema = z.object({ params: z.object({ id: z.number() }), diff --git a/src/api/invites/invitesRepository.ts b/src/api/invites/invitesRepository.ts index 10ecd7b..d9130c5 100644 --- a/src/api/invites/invitesRepository.ts +++ b/src/api/invites/invitesRepository.ts @@ -1,42 +1,81 @@ +import { Knex } from 'knex'; + import { CreateInviteDto, Invite } from './invitesModel'; const invitesRepository = { createInvite: async ( - trx: any, - invite: CreateInviteDto - ): Promise => { + invite: CreateInviteDto, + trx: Knex.Transaction + ): Promise => { + //check if user is already invited to the workspace + const check = await trx('invites') + .where('inviteeEmail', invite.inviteeEmail) + .andWhere('workspaceId', invite.workspaceId) + .andWhere('status', 'pending') + .first(); + if (check) { + return check; + } + + //check if this user is already a member of the workspace + const invitee = await trx('users') + .where('email', invite.inviteeEmail) + .first(); + const coworker = await trx('coworkers') + .where('userId', invitee.id) + .andWhere('workspaceId', invite.workspaceId) + .first(); + if (coworker) { + return 'User is already a member of the workspace'; + } const ids = await trx.insert(invite).into('invites'); + console.log(ids); return await trx .select('*') .from('invites') .where('id', ids[0]) .first(); }, - getInviteById: async (trx: any, id: string): Promise => { + getInviteById: async ( + id: string, + trx: Knex.Transaction + ): Promise => { return await trx.select('*').from('invites').where('id', id).first(); }, getInviteByWorkspaceId: async ( - trx: any, - workspaceId: string + workspaceId: string, + trx: Knex.Transaction ): Promise => { return await trx .select('*') .from('invites') - .where('workspaceId', workspaceId) - .andWhere('status', 'pending'); + .where('workspaceId', workspaceId); }, - acceptInvite: async (trx: any, id: string): Promise => { - return await trx + updateInvite: async ( + id: string, + status: string, + trx: Knex.Transaction + ): Promise => { + await trx('invites').where('id', id).update({ status }); + const updatedInvite = await trx('invites').where('id', id).first(); + return updatedInvite; + }, + acceptInvite: async (id: string, trx: Knex.Transaction): Promise => { + await trx .update({ status: 'accepted' }) .from('invites') .where('id', id); + //user gets added to the workspace after accepting the invite }, - cancelInvite: async (trx: any, id: string): Promise => { - return await trx + cancelInvite: async (id: string, trx: Knex.Transaction): Promise => { + await trx .update({ status: 'cancelled' }) .from('invites') .where('id', id); }, + deleteInvite: async (id: string, trx: Knex.Transaction): Promise => { + await trx.delete().from('invites').where('id', id); + }, }; export default invitesRepository; diff --git a/src/api/messages/messageApi.ts b/src/api/messages/messageApi.ts index cb1de9e..e1b7fd3 100644 --- a/src/api/messages/messageApi.ts +++ b/src/api/messages/messageApi.ts @@ -3,6 +3,7 @@ import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; import { messageResponse } from '@/common/utils/commonResponses'; +import { ReactionSchema } from '../reactions/reactionModel'; import { CreateMessageSchema, DeleteMessageSchema, @@ -60,7 +61,7 @@ messageRegistery.registerPath({ body: { content: { 'application/json': { - schema: UpdateMessageSchema.shape.body, + schema: UpdateMessageSchema.shape.params, }, }, }, @@ -78,3 +79,12 @@ messageRegistery.registerPath({ }, responses: createApiResponse(messageResponse, 'Success'), }); + +messageRegistery.registerPath({ + method: 'get', + path: '/messages/{id}/reactions', + tags: ['Message'], + security: [{ [bearerAuth.name]: [] }], + request: { params: GetMessageSchema.shape.params }, + responses: createApiResponse(ReactionSchema, 'Success'), +}); diff --git a/src/api/messages/messageRouter.ts b/src/api/messages/messageRouter.ts index d04ee6b..db2212e 100644 --- a/src/api/messages/messageRouter.ts +++ b/src/api/messages/messageRouter.ts @@ -3,6 +3,7 @@ import express, { Router } from 'express'; import { validateRequest } from '@/common/utils/httpHandlers'; import AuthController from '../auth/authController'; +import ReactionController from '../reactions/reactionController'; import MessageController from './messageController'; import { CreateMessageSchema, @@ -25,6 +26,11 @@ export const messagesRouter: Router = (() => { [AuthController.authenticate, validateRequest(GetMessageSchema)], MessageController.getMessageById ); + router.get( + '/:id/reactions', + [AuthController.authenticate], + ReactionController.getReactionsByMessageId + ); router.patch( '/:id', diff --git a/src/api/reactions/reactionApi.ts b/src/api/reactions/reactionApi.ts new file mode 100644 index 0000000..01543a9 --- /dev/null +++ b/src/api/reactions/reactionApi.ts @@ -0,0 +1,54 @@ +import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; + +import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; +import { messageResponse } from '@/common/utils/commonResponses'; + +import { CreateReactionSchema, ReactionSchema } from './reactionModel'; +export const reactionsRegistry = new OpenAPIRegistry(); +reactionsRegistry.register('Reactions', ReactionSchema); +reactionsRegistry.registerPath({ + method: 'post', + path: '/reactions/add', + tags: ['Reactions'], + request: { + body: { + content: { + 'application/json': { + schema: CreateReactionSchema.shape.body, + }, + }, + }, + }, + responses: createApiResponse(ReactionSchema, 'Success'), +}); + +reactionsRegistry.registerPath({ + method: 'delete', + path: '/reactions/delete', + tags: ['Reactions'], + request: { + body: { + content: { + 'application/json': { + schema: CreateReactionSchema.shape.body, + }, + }, + }, + }, + responses: createApiResponse(messageResponse, 'Success'), +}); +reactionsRegistry.registerPath({ + method: 'patch', + path: '/reactions/update', + tags: ['Reactions'], + request: { + body: { + content: { + 'application/json': { + schema: CreateReactionSchema.shape.body, + }, + }, + }, + }, + responses: createApiResponse(ReactionSchema, 'Success'), +}); diff --git a/src/api/reactions/reactionController.ts b/src/api/reactions/reactionController.ts index 2469264..9cc79f9 100644 --- a/src/api/reactions/reactionController.ts +++ b/src/api/reactions/reactionController.ts @@ -1,10 +1,59 @@ import { Request, Response } from 'express'; +import { + asyncHandler, + handleServiceResponse, +} from '@/common/utils/httpHandlers'; + +import * as ReactionService from './reactionService'; const ReactionController = { - getMessageReactions: async (req: Request, res: Response) => { - const messageId = parseInt(req.params.id); - res.json({ messageId }); - }, + getReactionsByMessageId: asyncHandler( + async (req: Request, res: Response) => { + const messageId = parseInt(req.params.id); + const reactions = await ReactionService.getReactionsByMessageId( + messageId, + res.trx + ); + handleServiceResponse(res, reactions, 'ok'); + } + ), + add: asyncHandler(async (req: Request, res: Response) => { + const { userId, messageId, reaction } = req.body; + const addReactionDto = { + userId, + messageId, + reaction, + }; + const react = await ReactionService.addReaction( + addReactionDto, + res.trx + ); + handleServiceResponse(res, react, 'ok'); + }), + delete: asyncHandler(async (req: Request, res: Response) => { + const { userId, messageId, reaction } = req.body; + const deleteReactionDto = { + userId, + messageId, + reaction, + }; + await ReactionService.deleteReaction(deleteReactionDto, res.trx); + handleServiceResponse(res, '', 'ok'); + }), + update: asyncHandler(async (req: Request, res: Response) => { + const { userId, messageId, reaction } = req.body; + const updateReactionDto = { + userId, + messageId, + reaction, + }; + const updatedReaction = await ReactionService.updateReaction( + userId, + updateReactionDto, + res.trx + ); + handleServiceResponse(res, updatedReaction, 'ok'); + }), }; export default ReactionController; diff --git a/src/api/reactions/reactionModel.ts b/src/api/reactions/reactionModel.ts index 489090a..445c385 100644 --- a/src/api/reactions/reactionModel.ts +++ b/src/api/reactions/reactionModel.ts @@ -17,7 +17,7 @@ export const ReactionSchema = BaseReactionSchema.extend({ export type Reaction = z.infer; export type CreateReaction = z.infer; export type CreateReactionDto = Omit; - +export type deleteReactionDto = Omit; export const CreateReactionSchema = z.object({ body: z.object({ userId: z.number(), @@ -30,5 +30,9 @@ export const GetReactionSchema = z.object({ }); export const DeleteReactionSchema = z.object({ - params: z.object({ id: z.number() }), + body: z.object({ + userId: z.number(), + messageId: z.number(), + reaction: z.string(), + }), }); diff --git a/src/api/reactions/reactionRepository.ts b/src/api/reactions/reactionRepository.ts index a9ee432..244d2a8 100644 --- a/src/api/reactions/reactionRepository.ts +++ b/src/api/reactions/reactionRepository.ts @@ -1,22 +1,64 @@ -import { CreateReactionDto, Reaction } from '@/api/reactions/reactionModel'; +import { Knex } from 'knex'; -import db from '../../../db/db'; +import { + CreateReactionDto, + deleteReactionDto, + Reaction, +} from '@/api/reactions/reactionModel'; -export const reactionRepository = { - addReaction: async (reaction: CreateReactionDto): Promise => { - const ids = await db('reactions').insert(reaction); - const newReaction = await db('reactions').where('id', ids[0]).first(); +export const ReactionRepository = { + addReaction: async ( + reaction: CreateReactionDto, + trx: Knex.Transaction + ): Promise => { + //check if the user has already reacted to the message if yes update the reaction + const react = await trx('reactions') + .where('messageId', reaction.messageId) + .andWhere('userId', reaction.userId) + .first(); + + if (react) { + await trx('reactions') + .where('messageId', reaction.messageId) + .andWhere('userId', reaction.userId) + .update(reaction); + const updatedReaction = await trx('reactions') + .where('messageId', reaction.messageId) + .andWhere('userId', reaction.userId) + .first(); + return updatedReaction; + } + const ids = await trx('reactions').insert(reaction); + const newReaction = await trx('reactions').where('id', ids[0]).first(); return newReaction; }, + updateReaction: async ( + id: number, + reaction: CreateReactionDto, + trx: Knex.Transaction + ): Promise => { + await trx('reactions').where('id', id).update(reaction); + const updatedReaction = await trx('reactions').where('id', id).first(); + return updatedReaction; + }, - getReactionsByMessageId: async (messageId: number): Promise => { - return await db + getReactionsByMessageId: async ( + messageId: number, + trx: Knex.Transaction + ): Promise => { + return await trx .select('*') .from('reactions') .where('messageId', messageId); }, - deleteReaction: async (id: number): Promise => { - await db('reactions').where('id', id).delete(); + deleteReaction: async ( + deleteReactionDto: deleteReactionDto, + trx: Knex.Transaction + ): Promise => { + await trx('reactions') + .where('messageId', deleteReactionDto.messageId) + .andWhere('userId', deleteReactionDto.userId) + .del(); }, }; diff --git a/src/api/reactions/reactionService.ts b/src/api/reactions/reactionService.ts new file mode 100644 index 0000000..740e57d --- /dev/null +++ b/src/api/reactions/reactionService.ts @@ -0,0 +1,63 @@ +import { Knex } from 'knex'; + +import { CreateReactionDto, deleteReactionDto } from './reactionModel'; +import { ReactionRepository } from './reactionRepository'; + +export function addReaction( + reaction: CreateReactionDto, + trx: Knex.Transaction +) { + return ReactionRepository.addReaction(reaction, trx); +} +export function getReactionsByMessageId( + messageId: number, + trx: Knex.Transaction +) { + return ReactionRepository.getReactionsByMessageId(messageId, trx); +} +export function deleteReaction( + deleteReactionDto: deleteReactionDto, + trx: Knex.Transaction +) { + return ReactionRepository.deleteReaction(deleteReactionDto, trx); +} +export function updateReaction( + id: number, + reaction: CreateReactionDto, + trx: Knex.Transaction +) { + return ReactionRepository.updateReaction(id, reaction, trx); +} + +// export function getChannelMessages( +// channelId: number, +// cursor: number, +// limit: number, +// trx: Knex.Transaction +// ) { +// return messageRepository.getChannelMessages(channelId, cursor, limit, trx); +// } + +// export function getMessageById(messageId: number, trx: Knex.Transaction) { +// return messageRepository.getMessageById(messageId, trx); +// } +// export function sendMessage(message: CreateMessage, trx: Knex.Transaction) { +// return messageRepository.sendMessage(message, trx); +// } +// export function editMessage( +// messageId: number, +// message: UpdateMessage, +// trx: Knex.Transaction +// ) { +// return messageRepository.editMessage(messageId, message, trx); +// } +// export function deleteMessage(messageId: number, trx: Knex.Transaction) { +// return messageRepository.deleteMessage(messageId, trx); +// } + +// export function getChannelThreads( +// userId: number, +// channelId: number, +// trx: Knex.Transaction +// ) { +// } diff --git a/src/api/reactions/reactionsController.ts b/src/api/reactions/reactionsController.ts deleted file mode 100644 index 0712fea..0000000 --- a/src/api/reactions/reactionsController.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Request, Response } from 'express'; - -const reactionsController = { - add: async (req: Request, res: Response) => { - const { userId, messageId, reaction } = req.body; - const addReactionDto = { - userId, - messageId, - reaction, - }; - - console.log(addReactionDto); - res.send(200).json(addReactionDto); - }, - delete: async (req: Request, res: Response) => { - const { userId, messageId, reaction } = req.body; - const deleteReactionDto = { - userId, - messageId, - reaction, - }; - - console.log(deleteReactionDto); - res.send(200).json(deleteReactionDto); - }, -}; - -export default reactionsController; diff --git a/src/api/reactions/reactionsRouter.ts b/src/api/reactions/reactionsRouter.ts index de7e583..37d980e 100644 --- a/src/api/reactions/reactionsRouter.ts +++ b/src/api/reactions/reactionsRouter.ts @@ -1,65 +1,26 @@ -import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; import express, { Router } from 'express'; -import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; -import { messageResponse } from '@/common/utils/commonResponses'; - import { validateRequest } from '../../common/utils/httpHandlers'; -import { - CreateReactionSchema, - DeleteReactionSchema, - ReactionSchema, -} from './reactionModel'; -import reactionsController from './reactionsController'; - -export const reactionsRegistry = new OpenAPIRegistry(); -reactionsRegistry.register('Reactions', ReactionSchema); +import ReactionController from './reactionController'; +import { CreateReactionSchema, DeleteReactionSchema } from './reactionModel'; export const reactionsRouter: Router = (() => { const router = express.Router(); - - reactionsRegistry.registerPath({ - method: 'get', - path: '/reactions/add', - tags: ['Reactions'], - request: { - body: { - content: { - 'application/json': { - schema: CreateReactionSchema.shape.body, - }, - }, - }, - }, - responses: createApiResponse(ReactionSchema, 'Success'), - }); - router.post( '/add', validateRequest(CreateReactionSchema), - reactionsController.add + ReactionController.add ); - - reactionsRegistry.registerPath({ - method: 'delete', - path: '/reactions/delete', - tags: ['Reactions'], - request: { - body: { - content: { - 'application/json': { - schema: CreateReactionSchema.shape.body, - }, - }, - }, - }, - responses: createApiResponse(messageResponse, 'Success'), - }); - router.delete( '/delete', validateRequest(DeleteReactionSchema), - reactionsController.delete + ReactionController.delete + ); + + router.patch( + '/update', + validateRequest(CreateReactionSchema), + ReactionController.update ); return router; diff --git a/src/api/workspace/workspaceApi.ts b/src/api/workspace/workspaceApi.ts index c80a472..097f393 100644 --- a/src/api/workspace/workspaceApi.ts +++ b/src/api/workspace/workspaceApi.ts @@ -50,6 +50,14 @@ workspaceRegistry.registerPath({ request: { params: Schemas.GetWorkspaceSchema.shape.params }, responses: createApiResponse(Schemas.WorkspaceSchema, 'Success'), }); +workspaceRegistry.registerPath({ + method: 'get', + path: '/workspaces/{id}/invites', + tags: ['Workspace'], + security: [{ [bearerAuth.name]: [] }], + request: { params: Schemas.GetWorkspaceSchema.shape.params }, + responses: createApiResponse(Schemas.WorkspaceSchema, 'Success'), +}); workspaceRegistry.registerPath({ method: 'patch', diff --git a/src/api/workspace/workspaceRouter.ts b/src/api/workspace/workspaceRouter.ts index c63c8c7..c7c8835 100644 --- a/src/api/workspace/workspaceRouter.ts +++ b/src/api/workspace/workspaceRouter.ts @@ -6,6 +6,7 @@ import { validateRequest } from '@/common/utils/httpHandlers'; import AuthController from '../auth/authController'; import ChannelController from '../channels/channelController'; import CoworkersController from '../coworkers/coworkersController'; +import InvitesController from '../invites/invitesController'; import WorkspaceController from './workspaceController'; export const workspaceRouter: Router = (() => { @@ -64,6 +65,14 @@ export const workspaceRouter: Router = (() => { ], ChannelController.getWorkspaceChannels ); + router.get( + '/:id/invites', + [ + AuthController.authenticate, + validateRequest(Schemas.GetWorkspaceSchema), + ], + InvitesController.getWorkspaceInvites + ); // TODO: router.get('/:id/threads', [ diff --git a/src/common/__tests__/entityFactory.ts b/src/common/__tests__/entityFactory.ts index 5aea40e..4184a9a 100644 --- a/src/common/__tests__/entityFactory.ts +++ b/src/common/__tests__/entityFactory.ts @@ -35,14 +35,14 @@ class EntityFactory { async createInvite( id: number, senderId: number, - inviteeId: number, + inviteeEmail: string, workspaceId: number, status: 'pending' | 'accepted' | 'cancelled' = 'pending' ): Promise { const invite = { id: id, senderId: senderId, - inviteeId: inviteeId, + inviteeEmail: inviteeEmail, workspaceId: workspaceId, expiresAt: new Date(), status: status,