From 30b3b2dbca2a9a8ef6de120764653a5a02c90668 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 16 Aug 2024 11:12:03 +0200 Subject: [PATCH 01/10] [EN-6995] [UserProfile] Add - Post Report --- .../dto/report-abuse-user-profile.dto.ts | 12 +++++ .../dto/report-abuse-user-profile.pipe.ts | 42 +++++++++++++++ src/user-profiles/user-profiles.controller.ts | 27 ++++++++++ src/user-profiles/user-profiles.module.ts | 2 + src/user-profiles/user-profiles.service.ts | 52 ++++++++++++++++++- 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/user-profiles/dto/report-abuse-user-profile.dto.ts create mode 100644 src/user-profiles/dto/report-abuse-user-profile.pipe.ts diff --git a/src/user-profiles/dto/report-abuse-user-profile.dto.ts b/src/user-profiles/dto/report-abuse-user-profile.dto.ts new file mode 100644 index 00000000..8909963b --- /dev/null +++ b/src/user-profiles/dto/report-abuse-user-profile.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ReportAbuseUserProfileDto { + @ApiProperty() + @IsString() + reason: string; + + @ApiProperty() + @IsString() + comment: string; +} diff --git a/src/user-profiles/dto/report-abuse-user-profile.pipe.ts b/src/user-profiles/dto/report-abuse-user-profile.pipe.ts new file mode 100644 index 00000000..dca8beaf --- /dev/null +++ b/src/user-profiles/dto/report-abuse-user-profile.pipe.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { + ArgumentMetadata, + BadRequestException, + PipeTransform, +} from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { ReportAbuseUserProfileDto } from './report-abuse-user-profile.dto'; + +export class ReportAbuseUserProfilePipe + implements + PipeTransform< + ReportAbuseUserProfileDto, + Promise + > +{ + private static toValidate(metatype: Function): boolean { + const types: Function[] = [String, Boolean, Number, Array, Object]; + return !types.includes(metatype); + } + + async transform( + value: ReportAbuseUserProfileDto, + { metatype }: ArgumentMetadata + ): Promise { + if (!metatype || !ReportAbuseUserProfilePipe.toValidate(metatype)) { + return value; + } + const object = plainToInstance(metatype, value); + const errors = await validate(object, { + whitelist: true, + forbidNonWhitelisted: true, + forbidUnknownValues: true, + }); + + if (errors.length > 0) { + throw new BadRequestException(); + } + return value; + } +} diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index 40aeafff..3694e2da 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -35,6 +35,8 @@ import { } from 'src/users/users.types'; import { isRoleIncluded } from 'src/users/users.utils'; import { UpdateCoachUserProfileDto } from './dto'; +import { ReportAbuseUserProfileDto } from './dto/report-abuse-user-profile.dto'; +import { ReportAbuseUserProfilePipe } from './dto/report-abuse-user-profile.pipe'; import { UpdateCandidateUserProfileDto } from './dto/update-candidate-user-profile.dto'; import { UpdateUserProfilePipe } from './dto/update-user-profile.pipe'; import { UserProfileRecommendation } from './models/user-profile-recommendation.model'; @@ -76,6 +78,31 @@ export class UserProfilesController { return updatedUserProfile; } + @Post('/:userId/abuse') + async reportAbuse( + @UserPayload('id', new ParseUUIDPipe()) currentUserId: string, + @Param('userId', new ParseUUIDPipe()) userId: string, + @Body(new ReportAbuseUserProfilePipe()) + reportAbuseDto: ReportAbuseUserProfileDto + ): Promise { + // Set the reportedUser and reporterUser + const userReported = await this.userProfilesService.findOneUser(userId); + const userReporter = await this.userProfilesService.findOneUser( + currentUserId + ); + + // Check users exists + if (!userReported || !userReporter) { + throw new NotFoundException(); + } + + return await this.userProfilesService.reportAbuse( + reportAbuseDto, + userReporter, + userReported + ); + } + @Get() async findAll( @UserPayload('id', new ParseUUIDPipe()) userId: string, diff --git a/src/user-profiles/user-profiles.module.ts b/src/user-profiles/user-profiles.module.ts index 5b5d663c..afc1680a 100644 --- a/src/user-profiles/user-profiles.module.ts +++ b/src/user-profiles/user-profiles.module.ts @@ -3,6 +3,7 @@ import { SequelizeModule } from '@nestjs/sequelize'; import { AmbitionsModule } from 'src/common/ambitions/ambitions.module'; import { BusinessLinesModule } from 'src/common/business-lines/business-lines.module'; import { AWSModule } from 'src/external-services/aws/aws.module'; +import { SlackModule } from 'src/external-services/slack/slack.module'; import { MessagesModule } from 'src/messages/messages.module'; import { UsersModule } from 'src/users/users.module'; import { @@ -33,6 +34,7 @@ import { UserProfilesService } from './user-profiles.service'; BusinessLinesModule, AWSModule, MessagesModule, + SlackModule, ], controllers: [UserProfilesController], providers: [UserProfilesService], diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 00ee9271..9dc7eec2 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -9,6 +9,8 @@ import { BusinessLineValue } from 'src/common/business-lines/business-lines.type import { BusinessLine } from 'src/common/business-lines/models'; import { Department, Departments } from 'src/common/locations/locations.types'; import { S3Service } from 'src/external-services/aws/s3.service'; +import { SlackService } from 'src/external-services/slack/slack.service'; +import { slackChannels } from 'src/external-services/slack/slack.types'; import { MessagesService } from 'src/messages/messages.service'; import { InternalMessage } from 'src/messages/models'; import { User } from 'src/users/models'; @@ -21,6 +23,7 @@ import { UserRoles, } from 'src/users/users.types'; import { isRoleIncluded } from 'src/users/users.utils'; +import { ReportAbuseUserProfileDto } from './dto/report-abuse-user-profile.dto'; import { HelpNeed, HelpOffer, @@ -71,7 +74,8 @@ export class UserProfilesService { private s3Service: S3Service, private usersService: UsersService, private userCandidatsService: UserCandidatsService, - private messagesService: MessagesService + private messagesService: MessagesService, + private slackService: SlackService ) {} async findOne(id: string) { @@ -623,4 +627,50 @@ export class UserProfilesService { individualHooks: true, }); } + + async sendReportedUserNotification( + report: ReportAbuseUserProfileDto, + userReporter: User, + userReported: User + ) { + const message = `Le profil de ${userReported.firstName} ${userReported.lastName} a été signalé`; + + return this.slackService.sendMessage( + slackChannels.ENTOURAGE_PRO_MODERATION, + this.slackService.generateSlackBlockMsg({ + title: '🚨 Un profil a été signalé', + context: [ + { + title: 'Signalé par', + content: `\n${userReporter.firstName} ${userReporter.lastName} <${userReporter.email}>`, + }, + ], + msgParts: [ + { + content: `Profil signalé : ${userReported.firstName} ${userReported.lastName} <${userReported.email}>`, + }, + { + content: `Raison du signalement : ${report.reason}`, + }, + { + content: `Commentaire : ${report.comment}`, + }, + ], + }), + message + ); + } + + async reportAbuse( + report: ReportAbuseUserProfileDto, + userReporter: User, + userReported: User + ) { + return this.sendReportedUserNotification( + report, + userReporter, + userReported + ); + // TODO: send email to admin in charge of moderation + } } From 00a085ebe4cfafb564a411d30b0fd87d66029f69 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 16 Aug 2024 17:20:25 +0200 Subject: [PATCH 02/10] [EN-6995] [UserProfle] - Add mail on user reported --- .../mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 24 +++++++++++++++++++ src/user-profiles/user-profiles.module.ts | 2 ++ src/user-profiles/user-profiles.service.ts | 9 ++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 00ba0c53..9359c1c5 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -89,6 +89,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE: 5625323, INTERNAL_MESSAGE_CONFIRMATION: 5625495, USER_EMAIL_VERIFICATION: 5899611, + USER_REPORTED_ADMIN: 0, // TODO : Demande du template ID } as const; export type MailjetTemplateKey = keyof typeof MailjetTemplates; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 78a62a4a..b4c4cdf1 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -25,6 +25,7 @@ import { import { getMailjetVariablesForPrivateOrPublicOffer } from 'src/opportunities/opportunities.utils'; import { QueuesService } from 'src/queues/producers/queues.service'; import { Jobs } from 'src/queues/queues.types'; +import { ReportAbuseUserProfileDto } from 'src/user-profiles/dto/report-abuse-user-profile.dto'; import { User } from 'src/users/models'; import { CandidateUserRoles, @@ -723,4 +724,27 @@ export class MailsService { }, }); } + + async sendUserReportedMail( + reportAbuseUserProfileDto: ReportAbuseUserProfileDto, + reportedUser: User, + reporterUser: User + ) { + const { candidatesAdminMail } = getAdminMailsFromZone(reportedUser.zone); + + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: candidatesAdminMail, + templateId: MailjetTemplates.USER_REPORTED_ADMIN, + replyTo: candidatesAdminMail, + variables: { + reportedFirstName: reportedUser.firstName, + reportedLastName: reportedUser.lastName, + reportedEmail: reportedUser.email, + reporterFirstName: reporterUser.firstName, + reporterLastName: reporterUser.lastName, + reporterEmail: reporterUser.email, + ...reportAbuseUserProfileDto, + }, + }); + } } diff --git a/src/user-profiles/user-profiles.module.ts b/src/user-profiles/user-profiles.module.ts index afc1680a..503ef04b 100644 --- a/src/user-profiles/user-profiles.module.ts +++ b/src/user-profiles/user-profiles.module.ts @@ -4,6 +4,7 @@ import { AmbitionsModule } from 'src/common/ambitions/ambitions.module'; import { BusinessLinesModule } from 'src/common/business-lines/business-lines.module'; import { AWSModule } from 'src/external-services/aws/aws.module'; import { SlackModule } from 'src/external-services/slack/slack.module'; +import { MailsModule } from 'src/mails/mails.module'; import { MessagesModule } from 'src/messages/messages.module'; import { UsersModule } from 'src/users/users.module'; import { @@ -35,6 +36,7 @@ import { UserProfilesService } from './user-profiles.service'; AWSModule, MessagesModule, SlackModule, + MailsModule, ], controllers: [UserProfilesController], providers: [UserProfilesService], diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 9dc7eec2..172c8bf4 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -11,6 +11,7 @@ import { Department, Departments } from 'src/common/locations/locations.types'; import { S3Service } from 'src/external-services/aws/s3.service'; import { SlackService } from 'src/external-services/slack/slack.service'; import { slackChannels } from 'src/external-services/slack/slack.types'; +import { MailsService } from 'src/mails/mails.service'; import { MessagesService } from 'src/messages/messages.service'; import { InternalMessage } from 'src/messages/models'; import { User } from 'src/users/models'; @@ -75,7 +76,8 @@ export class UserProfilesService { private usersService: UsersService, private userCandidatsService: UserCandidatsService, private messagesService: MessagesService, - private slackService: SlackService + private slackService: SlackService, + private mailsService: MailsService ) {} async findOne(id: string) { @@ -671,6 +673,11 @@ export class UserProfilesService { userReporter, userReported ); + return this.mailsService.sendUserReportedMail( + report, + userReporter, + userReported + ); // TODO: send email to admin in charge of moderation } } From 2874711ada6c47dd5961251982b0466490186478 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Wed, 21 Aug 2024 11:05:49 +0200 Subject: [PATCH 03/10] [App/MJ] Update - Template id for USER_REPORTED_ADMIN --- src/external-services/mailjet/mailjet.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index fe4037fa..4a189e01 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -89,7 +89,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE: 5625323, INTERNAL_MESSAGE_CONFIRMATION: 5625495, USER_EMAIL_VERIFICATION: 5899611, - USER_REPORTED_ADMIN: 0, // TODO : Demande du template ID + USER_REPORTED_ADMIN: 12218473, USER_PROFILE_COMPLETION: 6129711, } as const; From 66b6b3807e475717315af9fb0be180c6e0f99aa4 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Wed, 21 Aug 2024 11:44:27 +0200 Subject: [PATCH 04/10] [App/MJ] update Mailjet template ID for USER_REPORTED_ADMIN --- .../mailjet/mailjet.types.ts | 2 +- src/user-profiles/user-profiles.service.ts | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 4a189e01..e5299d57 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -89,7 +89,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE: 5625323, INTERNAL_MESSAGE_CONFIRMATION: 5625495, USER_EMAIL_VERIFICATION: 5899611, - USER_REPORTED_ADMIN: 12218473, + USER_REPORTED_ADMIN: 6223181, USER_PROFILE_COMPLETION: 6129711, } as const; diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 172c8bf4..29dd7fbd 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -668,16 +668,13 @@ export class UserProfilesService { userReporter: User, userReported: User ) { - return this.sendReportedUserNotification( - report, - userReporter, - userReported - ); - return this.mailsService.sendUserReportedMail( - report, - userReporter, - userReported - ); - // TODO: send email to admin in charge of moderation + await Promise.all([ + this.sendReportedUserNotification(report, userReporter, userReported), + this.mailsService.sendUserReportedMail( + report, + userReporter, + userReported + ), + ]); } } From d1b53fb108eafa1af0efccdad8b30a8c3baf7e9f Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 30 Aug 2024 17:09:09 +0200 Subject: [PATCH 05/10] [EN-6995] Refacto - sendMessageUserReported in SlackService --- src/external-services/slack/slack.service.ts | 60 +++++++++++++++++++ src/user-profiles/user-profiles.controller.ts | 2 +- src/user-profiles/user-profiles.service.ts | 41 ++----------- tests/mocks.types.ts | 2 + 4 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/external-services/slack/slack.service.ts b/src/external-services/slack/slack.service.ts index 4781f581..631c42aa 100644 --- a/src/external-services/slack/slack.service.ts +++ b/src/external-services/slack/slack.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@nestjs/common'; import { App, Block, KnownBlock } from '@slack/bolt'; +import { User } from 'src/users/models'; import { SlackBlockConfig, + slackChannels, SlackMsgAction, SlackMsgContext, SlackMsgContextImage, @@ -43,6 +45,24 @@ export class SlackService { } }; + sendMessageUserReported = async ( + userReporter: User, + userReported: User, + reason: string, + comment: string + ): Promise => { + return this.sendMessage( + slackChannels.ENTOURAGE_PRO_MODERATION, + this.generateProfileReportedBlocks( + userReporter, + userReported, + reason, + comment + ), + `Le profil de ${userReported.firstName} ${userReported.lastName} a été signalé` + ); + }; + /** * Generate a the slack blocks for a message * @param param0 message configuration @@ -82,6 +102,46 @@ export class SlackService { return blocksBuffer; }; + /** + * Generate a slack message for a profile reported + * @param userReporter - The user who reported + * @param userReported - The user who was reported + * @param reason - The reason of the report + * @param comment - The comment of the report + * @returns blocks for the message + */ + generateProfileReportedBlocks = ( + userReporter: User, + userReported: User, + reason: string, + comment: string + ) => { + return this.generateSlackBlockMsg({ + title: '🚨 Un profil a été signalé', + context: [ + { + title: 'Signalé par', + content: `\n${userReporter.firstName} ${userReporter.lastName} <${userReporter.email}>`, + }, + ], + msgParts: [ + { + content: `Profil signalé : ${userReported.firstName} ${userReported.lastName} <${userReported.email}>`, + }, + { + content: `Raison du signalement : ${reason}`, + }, + { + content: `Commentaire : ${comment}`, + }, + ], + }); + }; + + /***************** */ + /* Private methods */ + /***************** */ + /** * Generate title block * @param title - The title diff --git a/src/user-profiles/user-profiles.controller.ts b/src/user-profiles/user-profiles.controller.ts index 9cca597b..b459ee8d 100644 --- a/src/user-profiles/user-profiles.controller.ts +++ b/src/user-profiles/user-profiles.controller.ts @@ -80,7 +80,7 @@ export class UserProfilesController { return updatedUserProfile; } - @Post('/:userId/abuse') + @Post('/:userId/report') async reportAbuse( @UserPayload('id', new ParseUUIDPipe()) currentUserId: string, @Param('userId', new ParseUUIDPipe()) userId: string, diff --git a/src/user-profiles/user-profiles.service.ts b/src/user-profiles/user-profiles.service.ts index 29dd7fbd..c61dfcd4 100644 --- a/src/user-profiles/user-profiles.service.ts +++ b/src/user-profiles/user-profiles.service.ts @@ -10,7 +10,6 @@ import { BusinessLine } from 'src/common/business-lines/models'; import { Department, Departments } from 'src/common/locations/locations.types'; import { S3Service } from 'src/external-services/aws/s3.service'; import { SlackService } from 'src/external-services/slack/slack.service'; -import { slackChannels } from 'src/external-services/slack/slack.types'; import { MailsService } from 'src/mails/mails.service'; import { MessagesService } from 'src/messages/messages.service'; import { InternalMessage } from 'src/messages/models'; @@ -630,46 +629,18 @@ export class UserProfilesService { }); } - async sendReportedUserNotification( - report: ReportAbuseUserProfileDto, - userReporter: User, - userReported: User - ) { - const message = `Le profil de ${userReported.firstName} ${userReported.lastName} a été signalé`; - - return this.slackService.sendMessage( - slackChannels.ENTOURAGE_PRO_MODERATION, - this.slackService.generateSlackBlockMsg({ - title: '🚨 Un profil a été signalé', - context: [ - { - title: 'Signalé par', - content: `\n${userReporter.firstName} ${userReporter.lastName} <${userReporter.email}>`, - }, - ], - msgParts: [ - { - content: `Profil signalé : ${userReported.firstName} ${userReported.lastName} <${userReported.email}>`, - }, - { - content: `Raison du signalement : ${report.reason}`, - }, - { - content: `Commentaire : ${report.comment}`, - }, - ], - }), - message - ); - } - async reportAbuse( report: ReportAbuseUserProfileDto, userReporter: User, userReported: User ) { await Promise.all([ - this.sendReportedUserNotification(report, userReporter, userReported), + this.slackService.sendMessageUserReported( + userReporter, + userReported, + report.reason, + report.comment + ), this.mailsService.sendUserReportedMail( report, userReporter, diff --git a/tests/mocks.types.ts b/tests/mocks.types.ts index b550640b..c86a2bc1 100644 --- a/tests/mocks.types.ts +++ b/tests/mocks.types.ts @@ -122,4 +122,6 @@ export const MailjetMock: ProviderMock = { export const SlackMocks: ProviderMock = { sendMessage: jest.fn(), generateSlackBlockMsg: jest.fn(), + sendMessageUserReported: jest.fn(), + generateProfileReportedBlocks: jest.fn(), } as const; From 3681bbf28385601b03fedf48a688db91f752ac7f Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Fri, 6 Sep 2024 11:19:35 +0200 Subject: [PATCH 06/10] fix(Messaging): change order and message content type to text --- src/db/migrations/20240807082823-create-messaging.js | 2 +- src/messaging/messaging.service.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db/migrations/20240807082823-create-messaging.js b/src/db/migrations/20240807082823-create-messaging.js index 69e6ed27..601070a5 100644 --- a/src/db/migrations/20240807082823-create-messaging.js +++ b/src/db/migrations/20240807082823-create-messaging.js @@ -41,7 +41,7 @@ module.exports = { }, content: { allowNull: false, - type: Sequelize.STRING, + type: Sequelize.TEXT, }, authorId: { type: Sequelize.UUID, diff --git a/src/messaging/messaging.service.ts b/src/messaging/messaging.service.ts index 92d08dfc..fd6c2319 100644 --- a/src/messaging/messaging.service.ts +++ b/src/messaging/messaging.service.ts @@ -57,7 +57,7 @@ export class MessagingService { { model: Message, as: 'messages', - attributes: ['id', 'content', 'createdAt'], + attributes: ['id', 'content', 'createdAt', 'authorId'], include: [ { model: User, @@ -71,12 +71,12 @@ export class MessagingService { { model: User, as: 'participants', - attributes: ['id', 'firstName', 'lastName'], + attributes: ['id', 'firstName', 'lastName', 'role'], }, ], - order: [['messages', 'createdAt', 'ASC']], }, ], + order: [['conversation', 'createdAt', 'DESC']], }); // Return the conversations return conversationParticipants.map((cp) => cp.conversation); From 11ce23e6c722c7a21dc9d20794799716e2a92e22 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 9 Sep 2024 04:53:37 +0200 Subject: [PATCH 07/10] [EN-7376] feat(Messaging): Handle read state --- src/messaging/messaging.controller.ts | 12 +++++++++++- src/messaging/messaging.service.ts | 18 ++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index 96265105..d04cb156 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -33,6 +33,7 @@ export class MessagingController { @UserPayload('id', new ParseUUIDPipe()) userId: string, @Param('conversationId', new ParseUUIDPipe()) conversationId: string ) { + await this.messagingService.setConversationHasSeen(conversationId, userId); return this.messagingService.getConversationForUser(conversationId, userId); } @@ -61,7 +62,16 @@ export class MessagingController { // Add createMessageDto properties without participantIds ...createMessageDto, }); - return createdMessage; + // Set conversation as seen because the user has sent a message + await this.messagingService.setConversationHasSeen( + createMessageDto.conversationId, + userId + ); + // Fetch the message to return it + const message = await this.messagingService.findOneMessage( + createdMessage.id + ); + return message; } catch (error) { console.error(error); } diff --git a/src/messaging/messaging.service.ts b/src/messaging/messaging.service.ts index fd6c2319..53d515e4 100644 --- a/src/messaging/messaging.service.ts +++ b/src/messaging/messaging.service.ts @@ -114,6 +114,20 @@ export class MessagingService { return this.messageModel.create(createMessageDto); } + async setConversationHasSeen(conversationId: string, userId: string) { + const conversationParticipant = + await this.conversationParticipantModel.findOne({ + where: { + conversationId, + userId, + }, + }); + if (conversationParticipant) { + conversationParticipant.seenAt = new Date(); + await conversationParticipant.save(); + } + } + /** * Get a conversation by its ID * @param conversationId - The ID of the conversation to fetch @@ -141,7 +155,7 @@ export class MessagingService { { model: User, as: 'participants', - attributes: ['id', 'firstName', 'lastName', 'gender'], + attributes: ['id', 'firstName', 'lastName', 'gender', 'role'], include: [ { model: UserProfile, @@ -207,7 +221,7 @@ export class MessagingService { ); } - private async findOneMessage(messageId: string) { + async findOneMessage(messageId: string) { return this.messageModel.findByPk(messageId, { include: [ { From 587bee228e4c6b1bb2a17adbf0f2ae03d763b922 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 9 Sep 2024 16:16:52 +0200 Subject: [PATCH 08/10] [EN-7454] fix(Messaging): ConversationReport --- .../dto/{report-abuse.dto.ts => report.dto.ts} | 6 +++++- .../dto/{report-abuse.pipe.ts => report.pipe.ts} | 8 ++++---- src/messaging/messaging.controller.ts | 11 ++++++----- src/messaging/messaging.service.ts | 2 ++ src/messaging/messaging.utils.ts | 6 +++++- 5 files changed, 22 insertions(+), 11 deletions(-) rename src/messaging/dto/{report-abuse.dto.ts => report.dto.ts} (65%) rename src/messaging/dto/{report-abuse.pipe.ts => report.pipe.ts} (83%) diff --git a/src/messaging/dto/report-abuse.dto.ts b/src/messaging/dto/report.dto.ts similarity index 65% rename from src/messaging/dto/report-abuse.dto.ts rename to src/messaging/dto/report.dto.ts index 90517a43..09594f24 100644 --- a/src/messaging/dto/report-abuse.dto.ts +++ b/src/messaging/dto/report.dto.ts @@ -1,8 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; -export class ReportAbuseDto { +export class ReportDto { @ApiProperty() @IsString() reason: string; + + @ApiProperty() + @IsString() + comment: string; } diff --git a/src/messaging/dto/report-abuse.pipe.ts b/src/messaging/dto/report.pipe.ts similarity index 83% rename from src/messaging/dto/report-abuse.pipe.ts rename to src/messaging/dto/report.pipe.ts index cf389e09..30bf537c 100644 --- a/src/messaging/dto/report-abuse.pipe.ts +++ b/src/messaging/dto/report.pipe.ts @@ -6,10 +6,10 @@ import { } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; -import { ReportAbuseDto } from './report-abuse.dto'; +import { ReportDto } from './report.dto'; export class ReportAbusePipe - implements PipeTransform> + implements PipeTransform> { private static toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object]; @@ -17,9 +17,9 @@ export class ReportAbusePipe } async transform( - value: ReportAbuseDto, + value: ReportDto, { metatype }: ArgumentMetadata - ): Promise { + ): Promise { if (!metatype || !ReportAbusePipe.toValidate(metatype)) { return value; } diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index d04cb156..c5924c79 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -10,8 +10,8 @@ import { import { ApiBearerAuth } from '@nestjs/swagger'; import { UserPayload } from 'src/auth/guards'; import { CreateMessagePipe, CreateMessageDto } from './dto'; -import { ReportAbuseDto } from './dto/report-abuse.dto'; -import { ReportAbusePipe } from './dto/report-abuse.pipe'; +import { ReportDto } from './dto/report.dto'; +import { ReportAbusePipe } from './dto/report.pipe'; import { MessagingService } from './messaging.service'; @ApiBearerAuth() @@ -77,16 +77,17 @@ export class MessagingController { } } - @Post('conversation/:conversationId/report') + @Post('conversations/:conversationId/report') async reportMessageAbuse( @UserPayload('id', new ParseUUIDPipe()) userId: string, @Param('conversationId', new ParseUUIDPipe()) conversationId: string, @Body(new ReportAbusePipe()) - reportAbuseDto: ReportAbuseDto + reportDto: ReportDto ) { return this.messagingService.reportConversation( conversationId, - reportAbuseDto.reason, + reportDto.reason, + reportDto.comment, userId ); } diff --git a/src/messaging/messaging.service.ts b/src/messaging/messaging.service.ts index 53d515e4..1e8853f5 100644 --- a/src/messaging/messaging.service.ts +++ b/src/messaging/messaging.service.ts @@ -202,6 +202,7 @@ export class MessagingService { async reportConversation( conversationId: string, reason: string, + comment: string, reporterUserId: string ) { const conversation = await this.findConversation(conversationId); @@ -210,6 +211,7 @@ export class MessagingService { generateSlackMsgConfigConversationReported( conversation, reason, + comment, reporterUser ); const slackMessage = diff --git a/src/messaging/messaging.utils.ts b/src/messaging/messaging.utils.ts index 3386fee2..d477ae83 100644 --- a/src/messaging/messaging.utils.ts +++ b/src/messaging/messaging.utils.ts @@ -5,6 +5,7 @@ import { Conversation } from './models'; export const generateSlackMsgConfigConversationReported = ( conversation: Conversation, reason: string, + comment: string, reporterUser: User ): SlackBlockConfig => { return { @@ -25,7 +26,10 @@ export const generateSlackMsgConfigConversationReported = ( .join(', ')}`, }, { - content: `*Raison du signalement* :\n${reason}`, + content: `Raison du signalement : ${reason}`, + }, + { + content: `Commentaire : ${comment}`, }, ], }; From 21903b2f62f2b5f96379b3eb53432106785e4969 Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Tue, 10 Sep 2024 11:43:16 +0200 Subject: [PATCH 09/10] [EN-7454] feat(Messaging): add mail to admin on report conversation --- .env.dist | 1 + .../mailjet/mailjet.types.ts | 1 + src/mails/mails.service.ts | 20 +++++++++++++++++++ ...port.dto.ts => report-conversation.dto.ts} | 2 +- ...rt.pipe.ts => report-conversation.pipe.ts} | 9 +++++---- src/messaging/messaging.controller.ts | 9 ++++----- src/messaging/messaging.module.ts | 2 ++ src/messaging/messaging.service.ts | 17 +++++++++++----- 8 files changed, 46 insertions(+), 15 deletions(-) rename src/messaging/dto/{report.dto.ts => report-conversation.dto.ts} (83%) rename src/messaging/dto/{report.pipe.ts => report-conversation.pipe.ts} (79%) diff --git a/.env.dist b/.env.dist index 16fb4d80..450eb830 100644 --- a/.env.dist +++ b/.env.dist @@ -43,6 +43,7 @@ USE_SMS= BITLY_TOKEN= # Admins +ADMIN_NATIONAL=contact@entourage-pro.fr ADMIN_CANDIDATES_LYON=your-email@entourage.social ADMIN_CANDIDATES_PARIS=your-email@entourage.social ADMIN_CANDIDATES_LILLE=your-email@entourage.social diff --git a/src/external-services/mailjet/mailjet.types.ts b/src/external-services/mailjet/mailjet.types.ts index 9b5ba466..d49b1e58 100644 --- a/src/external-services/mailjet/mailjet.types.ts +++ b/src/external-services/mailjet/mailjet.types.ts @@ -90,6 +90,7 @@ export const MailjetTemplates = { INTERNAL_MESSAGE_CONFIRMATION: 5625495, USER_EMAIL_VERIFICATION: 5899611, USER_REPORTED_ADMIN: 6223181, + CONVERSATION_REPORTED_ADMIN: 6276909, ONBOARDING_J1_BAO: 6129684, ONBOARDING_J3_PROFILE_COMPLETION: 6129711, } as const; diff --git a/src/mails/mails.service.ts b/src/mails/mails.service.ts index 9ddabd16..620929b6 100644 --- a/src/mails/mails.service.ts +++ b/src/mails/mails.service.ts @@ -15,6 +15,8 @@ import { } from 'src/messages/messages.types'; import { InternalMessage } from 'src/messages/models'; import { ExternalMessage } from 'src/messages/models/external-message.model'; +import { ReportConversationDto } from 'src/messaging/dto/report-conversation.dto'; +import { Conversation } from 'src/messaging/models'; import { Opportunity, OpportunityUser } from 'src/opportunities/models'; import { ContactEmployerType, @@ -766,6 +768,24 @@ export class MailsService { }, }); } + + async sendConversationReportedMail( + reportConversationDto: ReportConversationDto, + reportedConversation: Conversation, + reporterUser: User + ) { + await this.queuesService.addToWorkQueue(Jobs.SEND_MAIL, { + toEmail: process.env.ADMIN_NATIONAL || 'contact@entourage-pro.fr', + templateId: MailjetTemplates.CONVERSATION_REPORTED_ADMIN, + variables: { + reporterFirstName: reporterUser.firstName, + reporterLastName: reporterUser.lastName, + reporterEmail: reporterUser.email, + reportedConversationId: reportedConversation.id, + ...reportConversationDto, + }, + }); + } } const getRoleString = (user: User): string => { diff --git a/src/messaging/dto/report.dto.ts b/src/messaging/dto/report-conversation.dto.ts similarity index 83% rename from src/messaging/dto/report.dto.ts rename to src/messaging/dto/report-conversation.dto.ts index 09594f24..f7759b61 100644 --- a/src/messaging/dto/report.dto.ts +++ b/src/messaging/dto/report-conversation.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; -export class ReportDto { +export class ReportConversationDto { @ApiProperty() @IsString() reason: string; diff --git a/src/messaging/dto/report.pipe.ts b/src/messaging/dto/report-conversation.pipe.ts similarity index 79% rename from src/messaging/dto/report.pipe.ts rename to src/messaging/dto/report-conversation.pipe.ts index 30bf537c..6be8c932 100644 --- a/src/messaging/dto/report.pipe.ts +++ b/src/messaging/dto/report-conversation.pipe.ts @@ -6,10 +6,11 @@ import { } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; -import { ReportDto } from './report.dto'; +import { ReportConversationDto } from './report-conversation.dto'; export class ReportAbusePipe - implements PipeTransform> + implements + PipeTransform> { private static toValidate(metatype: Function): boolean { const types: Function[] = [String, Boolean, Number, Array, Object]; @@ -17,9 +18,9 @@ export class ReportAbusePipe } async transform( - value: ReportDto, + value: ReportConversationDto, { metatype }: ArgumentMetadata - ): Promise { + ): Promise { if (!metatype || !ReportAbusePipe.toValidate(metatype)) { return value; } diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index c5924c79..af89286e 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -10,8 +10,8 @@ import { import { ApiBearerAuth } from '@nestjs/swagger'; import { UserPayload } from 'src/auth/guards'; import { CreateMessagePipe, CreateMessageDto } from './dto'; -import { ReportDto } from './dto/report.dto'; -import { ReportAbusePipe } from './dto/report.pipe'; +import { ReportConversationDto } from './dto/report-conversation.dto'; +import { ReportAbusePipe } from './dto/report-conversation.pipe'; import { MessagingService } from './messaging.service'; @ApiBearerAuth() @@ -82,12 +82,11 @@ export class MessagingController { @UserPayload('id', new ParseUUIDPipe()) userId: string, @Param('conversationId', new ParseUUIDPipe()) conversationId: string, @Body(new ReportAbusePipe()) - reportDto: ReportDto + reportConversationDto: ReportConversationDto ) { return this.messagingService.reportConversation( conversationId, - reportDto.reason, - reportDto.comment, + reportConversationDto, userId ); } diff --git a/src/messaging/messaging.module.ts b/src/messaging/messaging.module.ts index 08d23dfd..948db619 100644 --- a/src/messaging/messaging.module.ts +++ b/src/messaging/messaging.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { SequelizeModule } from '@nestjs/sequelize'; import { SlackModule } from 'src/external-services/slack/slack.module'; +import { MailsModule } from 'src/mails/mails.module'; import { UsersModule } from 'src/users/users.module'; import { MessagingController } from './messaging.controller'; import { MessagingService } from './messaging.service'; @@ -14,6 +15,7 @@ import { Message, Conversation, ConversationParticipant } from './models'; ConversationParticipant, ]), SlackModule, + MailsModule, UsersModule, ], controllers: [MessagingController], diff --git a/src/messaging/messaging.service.ts b/src/messaging/messaging.service.ts index 1e8853f5..ea2322a5 100644 --- a/src/messaging/messaging.service.ts +++ b/src/messaging/messaging.service.ts @@ -6,9 +6,11 @@ import { SlackBlockConfig, slackChannels, } from 'src/external-services/slack/slack.types'; +import { MailsService } from 'src/mails/mails.service'; import { UserProfile } from 'src/user-profiles/models'; import { User } from 'src/users/models'; import { UsersService } from 'src/users/users.service'; +import { ReportConversationDto } from './dto/report-conversation.dto'; import { generateSlackMsgConfigConversationReported } from './messaging.utils'; import { ConversationParticipant } from './models'; import { Conversation } from './models/conversation.model'; @@ -24,7 +26,8 @@ export class MessagingService { @InjectModel(ConversationParticipant) private conversationParticipantModel: typeof ConversationParticipant, private slackService: SlackService, - private userService: UsersService + private userService: UsersService, + private mailsService: MailsService ) {} /** @@ -201,8 +204,7 @@ export class MessagingService { async reportConversation( conversationId: string, - reason: string, - comment: string, + reportConversationDto: ReportConversationDto, reporterUserId: string ) { const conversation = await this.findConversation(conversationId); @@ -210,8 +212,8 @@ export class MessagingService { const slackMsgConfig: SlackBlockConfig = generateSlackMsgConfigConversationReported( conversation, - reason, - comment, + reportConversationDto.reason, + reportConversationDto.comment, reporterUser ); const slackMessage = @@ -221,6 +223,11 @@ export class MessagingService { slackMessage, 'Conversation de la messagerie signalée' ); + this.mailsService.sendConversationReportedMail( + reportConversationDto, + conversation, + reporterUser + ); } async findOneMessage(messageId: string) { From dc41238cb37305c01c4d3ef6d7a85a021c298acb Mon Sep 17 00:00:00 2001 From: Guillaume Cauchois Date: Mon, 16 Sep 2024 17:16:07 +0200 Subject: [PATCH 10/10] fix(Messaging): refacto --- src/messaging/messaging.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/messaging/messaging.controller.ts b/src/messaging/messaging.controller.ts index af89286e..0e2d62d4 100644 --- a/src/messaging/messaging.controller.ts +++ b/src/messaging/messaging.controller.ts @@ -38,7 +38,7 @@ export class MessagingController { } @Post('messages') - async createInternalMessage( + async postMessage( @UserPayload('id', new ParseUUIDPipe()) userId: string, @Body(new CreateMessagePipe()) createMessageDto: CreateMessageDto