From a60711c8082dd5f5c4cca8167d044790dafa3012 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Mon, 5 May 2025 17:23:27 +0200 Subject: [PATCH] Ej/fix message visibility (#11874) Screenshot 2025-05-05 at 15 30 09 Screenshot 2025-05-05 at 15 29 05 Screenshot 2025-05-05 at 15 33 06 related to PR https://github.com/twentyhq/twenty/pull/11840 and issue https://github.com/twentyhq/twenty/issues/9826 --- .../message/components/EventCardMessage.tsx | 22 +- .../EventCardMessageBodyNotShared.tsx | 51 ++++ .../components/EventCardMessageForbidden.tsx | 43 +++ .../components/EventCardMessageNotShared.tsx | 86 ------ .../services/timeline-messaging.service.ts | 6 +- .../messaging/utils/format-threads.util.ts | 27 +- ...calendar-event-find-many.pre-query.hook.ts | 62 ---- .../calendar-event-find-one.pre-query-hook.ts | 62 ---- ...-events-visibility-restrictions.service.ts | 2 +- .../can-access-calendar-events.service.ts | 70 ----- .../query-hooks/calendar-query-hook.module.ts | 6 - ...es-visibility-restrictions.service.spec.ts | 269 ++++++++++++++++++ ...essages-visibility-restrictions.service.ts | 98 +++++++ .../can-access-message-thread.service.ts | 62 ---- .../message-find-many.post-query.hook.ts | 35 +++ .../message-find-many.pre-query.hook.ts | 58 ---- .../message-find-one.post-query.hook.ts | 35 +++ .../message-find-one.pre-query-hook.ts | 53 ---- .../messaging-query-hook.module.ts | 12 +- 19 files changed, 578 insertions(+), 481 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared.tsx create mode 100644 packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageForbidden.tsx delete mode 100644 packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageNotShared.tsx delete mode 100644 packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook.ts delete mode 100644 packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts delete mode 100644 packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service.ts create mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts create mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts delete mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts create mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts delete mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook.ts create mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts delete mode 100644 packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook.ts diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx index b549003f0..a5c84bc88 100644 --- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessage.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled'; -import { isUndefined } from '@sniptt/guards'; import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage'; -import { EventCardMessageNotShared } from '@/activities/timeline-activities/rows/message/components/EventCardMessageNotShared'; +import { EventCardMessageBodyNotShared } from '@/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared'; +import { EventCardMessageForbidden } from '@/activities/timeline-activities/rows/message/components/EventCardMessageForbidden'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; +import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; import { OverflowingTextWithTooltip } from 'twenty-ui/display'; @@ -85,7 +86,7 @@ export const EventCardMessage = ({ ); if (shouldHideMessageContent) { - return ; + return ; } const shouldHandleNotFound = error.graphQLErrors.some( @@ -99,7 +100,7 @@ export const EventCardMessage = ({ return
Error loading message
; } - if (loading || isUndefined(message)) { + if (loading || !isDefined(message)) { return
Loading...
; } @@ -112,12 +113,21 @@ export const EventCardMessage = ({ - {message.subject} + + {message.subject !== + FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED + ? message.subject + : 'Subject not shared'} + - {message.text} + {message.text !== FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED ? ( + {message.text} + ) : ( + + )} ); diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared.tsx new file mode 100644 index 000000000..b8f1cb5a9 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared.tsx @@ -0,0 +1,51 @@ +import styled from '@emotion/styled'; +import { IconLock } from 'twenty-ui/display'; + +const StyledEmailBodyNotSharedContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: ${({ theme }) => theme.spacing(1)}; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(3)}; + + height: 80px; + justify-content: center; + + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + width: 100%; +`; + +const StyledEmailBodyNotSharedIconContainer = styled.div` + display: flex; + width: ${({ theme }) => theme.icon.size.sm}px; + height: ${({ theme }) => theme.icon.size.sm}px; + justify-content: center; + align-items: center; +`; + +const StyledEmailBodyNotShared = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +export const EventCardMessageBodyNotShared = ({ + notSharedByFullName, +}: { + notSharedByFullName: string; +}) => { + return ( + + + + + + Not shared by {notSharedByFullName} + + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageForbidden.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageForbidden.tsx new file mode 100644 index 000000000..60e658036 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageForbidden.tsx @@ -0,0 +1,43 @@ +import { EventCardMessageBodyNotShared } from '@/activities/timeline-activities/rows/message/components/EventCardMessageBodyNotShared'; +import styled from '@emotion/styled'; + +const StyledEventCardMessageContainer = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const StyledEmailContent = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(4)}; + justify-content: center; + width: 100%; +`; + +const StyledEmailTitle = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + display: flex; + flex-direction: column; + margin-top: ${({ theme }) => theme.spacing(2)}; +`; + +export const EventCardMessageForbidden = ({ + notSharedByFullName, +}: { + notSharedByFullName: string; +}) => { + return ( + + + + Subject not shared + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageNotShared.tsx b/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageNotShared.tsx deleted file mode 100644 index 18ae476e8..000000000 --- a/packages/twenty-front/src/modules/activities/timeline-activities/rows/message/components/EventCardMessageNotShared.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import styled from '@emotion/styled'; -import { IconLock } from 'twenty-ui/display'; - -const StyledEventCardMessageContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const StyledEmailContent = styled.div` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(4)}; - justify-content: center; -`; - -const StyledEmailTop = styled.div` - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledEmailTitle = styled.div` - color: ${({ theme }) => theme.font.color.primary}; - font-weight: ${({ theme }) => theme.font.weight.medium}; - display: flex; -`; - -const StyledEmailBodyNotShareContainer = styled.div` - align-items: center; - align-self: stretch; - background: ${({ theme }) => theme.background.transparent.lighter}; - border: 1px solid ${({ theme }) => theme.border.color.light}; - border-radius: ${({ theme }) => theme.spacing(1)}; - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(3)}; - - height: 80px; - justify-content: center; - padding: 0 ${({ theme }) => theme.spacing(1)}; - - color: ${({ theme }) => theme.font.color.light}; - font-size: ${({ theme }) => theme.font.size.sm}; - font-weight: ${({ theme }) => theme.font.weight.medium}; -`; - -const StyledEmailBodyNotSharedIconContainer = styled.div` - display: flex; - width: 14px; - height: 14px; - justify-content: center; - align-items: center; -`; - -const StyledEmailBodyNotShare = styled.div` - align-items: center; - display: flex; - gap: ${({ theme }) => theme.spacing(1)}; - padding: 0 ${({ theme }) => theme.spacing(1)}; -`; - -export const EventCardMessageNotShared = ({ - sharedByFullName, -}: { - sharedByFullName: string; -}) => { - return ( - - - - - Subject not shared - - - - - - - - Not shared by {sharedByFullName} - - - - - ); -}; diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts index cc1d15e4e..993f1df97 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts @@ -222,7 +222,7 @@ export class TimelineMessagingService { const visibilityValues = Object.values(MessageChannelVisibility); - const threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants: + const threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotOwner: | { [key: string]: MessageChannelVisibility; } @@ -247,10 +247,10 @@ export class TimelineMessagingService { const threadVisibilityByThreadId: { [key: string]: MessageChannelVisibility; } = messageThreadIds.reduce((threadVisibilityAcc, messageThreadId) => { - // If the workspace member is not in the participants of the thread, use the visibility value from the query + // If the workspace member is not the owner of the thread, use the visibility value from the query threadVisibilityAcc[messageThreadId] = threadIdsWithoutWorkspaceMember.includes(messageThreadId) - ? (threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotInParticipants?.[ + ? (threadVisibilityByThreadIdForWhichWorkspaceMemberIsNotOwner?.[ messageThreadId ] ?? MessageChannelVisibility.METADATA) : MessageChannelVisibility.SHARE_EVERYTHING; diff --git a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts index a141cbca1..230e1420f 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts @@ -1,3 +1,5 @@ +import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; + import { TimelineThread } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto'; import { extractParticipantSummary } from 'src/engine/core-modules/messaging/utils/extract-participant-summary.util'; import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; @@ -19,10 +21,23 @@ export const formatThreads = ( [key: string]: MessageChannelVisibility; }, ): TimelineThread[] => { - return threads.map((thread) => ({ - ...thread, - ...extractParticipantSummary(threadParticipantsByThreadId[thread.id]), - visibility: threadVisibilityByThreadId[thread.id], - read: true, - })); + return threads.map((thread) => { + const visibility = threadVisibilityByThreadId[thread.id]; + + return { + ...thread, + subject: + visibility === MessageChannelVisibility.SHARE_EVERYTHING || + visibility === MessageChannelVisibility.SUBJECT + ? thread.subject + : FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + lastMessageBody: + visibility === MessageChannelVisibility.SHARE_EVERYTHING + ? thread.lastMessageBody + : FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + ...extractParticipantSummary(threadParticipantsByThreadId[thread.id]), + visibility, + read: true, + }; + }); }; diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook.ts deleted file mode 100644 index e4b68f726..000000000 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { BadRequestException, NotFoundException, Scope } from '@nestjs/common'; - -import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { CanAccessCalendarEventsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service'; -import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; - -@WorkspaceQueryHook({ - key: `calendarEvent.findMany`, - scope: Scope.REQUEST, -}) -export class CalendarEventFindManyPreQueryHook - implements WorkspaceQueryHookInstance -{ - constructor( - private readonly twentyORMManager: TwentyORMManager, - private readonly canAccessCalendarEventsService: CanAccessCalendarEventsService, - ) {} - - async execute( - authContext: AuthContext, - objectName: string, - payload: FindManyResolverArgs, - ): Promise { - if (!payload?.filter?.id?.eq) { - throw new BadRequestException('id filter is required'); - } - - if (!authContext.user?.id) { - throw new BadRequestException('User id is required'); - } - - const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( - 'calendarChannelEventAssociation', - ); - - const calendarChannelCalendarEventAssociations = - await calendarChannelEventAssociationRepository.find({ - where: { - calendarEventId: payload?.filter?.id?.eq, - }, - relations: ['calendarChannel.connectedAccount'], - }); - - if (calendarChannelCalendarEventAssociations.length === 0) { - throw new NotFoundException(); - } - - await this.canAccessCalendarEventsService.canAccessCalendarEvents( - authContext.user.id, - authContext.workspace.id, - calendarChannelCalendarEventAssociations, - ); - - return payload; - } -} diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts deleted file mode 100644 index 359721b9b..000000000 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { BadRequestException, NotFoundException, Scope } from '@nestjs/common'; - -import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { CanAccessCalendarEventsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service'; -import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; - -@WorkspaceQueryHook({ - key: `calendarEvent.findOne`, - scope: Scope.REQUEST, -}) -export class CalendarEventFindOnePreQueryHook - implements WorkspaceQueryHookInstance -{ - constructor( - private readonly twentyORMManager: TwentyORMManager, - private readonly canAccessCalendarEventsService: CanAccessCalendarEventsService, - ) {} - - async execute( - authContext: AuthContext, - objectName: string, - payload: FindOneResolverArgs, - ): Promise { - if (!payload?.filter?.id?.eq) { - throw new BadRequestException('id filter is required'); - } - - if (!authContext.user?.id) { - throw new BadRequestException('User id is required'); - } - - const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( - 'calendarChannelEventAssociation', - ); - - const calendarChannelCalendarEventAssociations = - await calendarChannelEventAssociationRepository.find({ - where: { - calendarEventId: payload?.filter?.id?.eq, - }, - relations: ['calendarChannel', 'calendarChannel.connectedAccount'], - }); - - if (calendarChannelCalendarEventAssociations.length === 0) { - throw new NotFoundException(); - } - - await this.canAccessCalendarEventsService.canAccessCalendarEvents( - authContext.user.id, - authContext.workspace.id, - calendarChannelCalendarEventAssociations, - ); - - return payload; - } -} diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts index 4ff0e03aa..c19278fb3 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts @@ -28,7 +28,7 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { where: { calendarEventId: In(calendarEvents.map((event) => event.id)), }, - relations: ['calendarChannel', 'calendarChannel.connectedAccount'], + relations: ['calendarChannel'], }); const connectedAccountRepository = diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service.ts deleted file mode 100644 index e58e3118e..000000000 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import groupBy from 'lodash.groupby'; -import { In } from 'typeorm'; - -import { ForbiddenError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; - -@Injectable() -export class CanAccessCalendarEventsService { - constructor( - private readonly twentyORMManager: TwentyORMManager, - @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) - private readonly workspaceMemberService: WorkspaceMemberRepository, - ) {} - - public async canAccessCalendarEvents( - userId: string, - workspaceId: string, - calendarChannelCalendarEventAssociations: CalendarChannelEventAssociationWorkspaceEntity[], - ) { - const calendarChannels = calendarChannelCalendarEventAssociations.map( - (association) => association.calendarChannel, - ); - - const calendarChannelsGroupByVisibility = groupBy( - calendarChannels, - (channel) => channel.visibility, - ); - - if ( - calendarChannelsGroupByVisibility[ - CalendarChannelVisibility.SHARE_EVERYTHING - ] || - calendarChannelsGroupByVisibility[CalendarChannelVisibility.METADATA] - ) { - return; - } - - const currentWorkspaceMember = - await this.workspaceMemberService.getByIdOrFail(userId, workspaceId); - - const connectedAccountRepository = - await this.twentyORMManager.getRepository( - 'connectedAccount', - ); - - const connectedAccounts = await connectedAccountRepository.find({ - select: ['id'], - where: { - calendarChannels: { - id: In(calendarChannels.map((channel) => channel.id)), - }, - accountOwnerId: currentWorkspaceMember.id, - }, - }); - - if (connectedAccounts.length > 0) { - return; - } - - throw new ForbiddenError('Calendar events not shared'); - } -} diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts index 1b21ef65d..727a85699 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts @@ -2,11 +2,8 @@ import { Module } from '@nestjs/common'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { CalendarEventFindManyPostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook'; -import { CalendarEventFindManyPreQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook'; import { CalendarEventFindOnePostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook'; -import { CalendarEventFindOnePreQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook'; import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service'; -import { CanAccessCalendarEventsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/can-access-calendar-events.service'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Module({ @@ -14,10 +11,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), ], providers: [ - CanAccessCalendarEventsService, ApplyCalendarEventsVisibilityRestrictionsService, - CalendarEventFindOnePreQueryHook, - CalendarEventFindManyPreQueryHook, CalendarEventFindOnePostQueryHook, CalendarEventFindManyPostQueryHook, ], diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts new file mode 100644 index 000000000..345e6dde0 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts @@ -0,0 +1,269 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; + +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; + +import { ApplyMessagesVisibilityRestrictionsService } from './apply-messages-visibility-restrictions.service'; + +const createMockMessage = ( + id: string, + subject: string, + text: string, +): MessageWorkspaceEntity => ({ + id, + subject, + text, + headerMessageId: '', + receivedAt: new Date('2024-03-20T10:00:00Z'), + messageThreadId: '', + messageThread: null, + messageChannelMessageAssociations: [], + messageParticipants: [], + deletedAt: null, + createdAt: '2024-03-20T09:00:00Z', + updatedAt: '2024-03-20T09:00:00Z', +}); + +describe('ApplyMessagesVisibilityRestrictionsService', () => { + let service: ApplyMessagesVisibilityRestrictionsService; + + const mockMessageChannelMessageAssociationRepository = { + find: jest.fn(), + }; + + const mockConnectedAccountRepository = { + find: jest.fn(), + }; + + const mockTwentyORMManager = { + getRepository: jest.fn().mockImplementation((name) => { + if (name === 'messageChannelMessageAssociation') { + return mockMessageChannelMessageAssociationRepository; + } + if (name === 'connectedAccount') { + return mockConnectedAccountRepository; + } + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ApplyMessagesVisibilityRestrictionsService, + { + provide: TwentyORMManager, + useValue: mockTwentyORMManager, + }, + ], + }).compile(); + + service = module.get( + ApplyMessagesVisibilityRestrictionsService, + ); + + jest.clearAllMocks(); + }); + + it('should return message without obfuscated subject and text if the visibility is SHARE_EVERYTHING', async () => { + const messages = [ + createMockMessage('messageId', 'Test Subject', 'Test Message'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: 'messageId', + messageChannel: { + id: 'messageChannelId', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, + }, + }, + ]); + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual(messages); + expect( + result.every( + (item) => + item.subject === 'Test Subject' && item.text === 'Test Message', + ), + ).toBe(true); + expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled(); + }); + + it('should return message without obfuscated subject and with obfuscated text if the visibility is SUBJECT', async () => { + const messages = [ + createMockMessage('messageId', 'Test Subject', 'Test Message'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: 'messageId', + messageChannel: { + id: 'messageChannelId', + visibility: MessageChannelVisibility.SUBJECT, + }, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([]); + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual([ + { + ...messages[0], + text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + }, + ]); + }); + + it('should return message with obfuscated subject and text if the visibility is METADATA', async () => { + const messages = [ + createMockMessage('messageId', 'Test Subject', 'Test Message'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: 'messageId', + messageChannel: { + id: 'messageChannelId', + visibility: MessageChannelVisibility.METADATA, + }, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([]); + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual([ + { + ...messages[0], + subject: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + }, + ]); + }); + + it('should return message without obfuscated subject and text if the visibility is METADATA and the workspace member is the channel owner', async () => { + const messages = [ + createMockMessage('messageId', 'Test Subject', 'Test Message'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: 'messageId', + messageChannel: { + id: 'messageChannelId', + visibility: MessageChannelVisibility.METADATA, + }, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]); + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual(messages); + expect( + result.every( + (item) => + item.subject === 'Test Subject' && item.text === 'Test Message', + ), + ).toBe(true); + }); + + it('should not return message if visibility is not SHARE_EVERYTHING, SUBJECT or METADATA and the workspace member is not the channel owner', async () => { + const messages = [ + createMockMessage('messageId', 'Test Subject', 'Test Message'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: 'messageId', + messageChannel: { + id: 'messageChannelId', + }, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([]); + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual([]); + }); + + it('should return all messages with the right visibility', async () => { + const messages = [ + createMockMessage('1', 'Subject 1', 'Message 1'), + createMockMessage('2', 'Subject 2', 'Message 2'), + createMockMessage('3', 'Subject 3', 'Message 3'), + ]; + + mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ + { + messageId: '1', + messageChannel: { + id: '1', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, + }, + }, + { + messageId: '2', + messageChannel: { + id: '2', + visibility: MessageChannelVisibility.SUBJECT, + }, + }, + { + messageId: '3', + messageChannel: { + id: '3', + visibility: MessageChannelVisibility.METADATA, + }, + }, + ]); + + mockConnectedAccountRepository.find + .mockResolvedValueOnce([]) // request for message 3 + .mockResolvedValueOnce([]); // request for message 2 + + const result = await service.applyMessagesVisibilityRestrictions( + 'workspace-member-id', + messages, + ); + + expect(result).toEqual([ + messages[0], + { + ...messages[1], + text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + }, + { + ...messages[2], + subject: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + text: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + }, + ]); + }); +}); diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts new file mode 100644 index 000000000..e87e3857d --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common'; + +import groupBy from 'lodash.groupby'; +import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; +import { In } from 'typeorm'; + +import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; +import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; + +@Injectable() +export class ApplyMessagesVisibilityRestrictionsService { + constructor(private readonly twentyORMManager: TwentyORMManager) {} + + public async applyMessagesVisibilityRestrictions( + workspaceMemberId: string, + messages: MessageWorkspaceEntity[], + ) { + const messageChannelMessageAssociationRepository = + await this.twentyORMManager.getRepository( + 'messageChannelMessageAssociation', + ); + + const messageChannelMessagesAssociations = + await messageChannelMessageAssociationRepository.find({ + where: { + messageId: In(messages.map((message) => message.id)), + }, + relations: ['messageChannel'], + }); + + const connectedAccountRepository = + await this.twentyORMManager.getRepository( + 'connectedAccount', + ); + + for (let i = messages.length - 1; i >= 0; i--) { + const messageChannelMessageAssociations = + messageChannelMessagesAssociations.filter( + (association) => association.messageId === messages[i].id, + ); + + const messageChannels = messageChannelMessageAssociations + .map((association) => association.messageChannel) + .filter( + (channel): channel is NonNullable => channel !== null, + ); + + if (messageChannels.length === 0) { + throw new NotFoundError('Associated message channels not found'); + } + + const messageChannelsGroupByVisibility = groupBy( + messageChannels, + (channel) => channel.visibility, + ); + + if ( + messageChannelsGroupByVisibility[ + MessageChannelVisibility.SHARE_EVERYTHING + ] + ) { + continue; + } + + const connectedAccounts = await connectedAccountRepository.find({ + select: ['id'], + where: { + messageChannels: { + id: In(messageChannels.map((channel) => channel.id)), + }, + accountOwnerId: workspaceMemberId, + }, + }); + + if (connectedAccounts.length > 0) { + continue; + } + if (messageChannelsGroupByVisibility[MessageChannelVisibility.SUBJECT]) { + messages[i].text = FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED; + continue; + } + + if (messageChannelsGroupByVisibility[MessageChannelVisibility.METADATA]) { + messages[i].subject = FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED; + messages[i].text = FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED; + continue; + } + + messages.splice(i, 1); + } + + return messages; + } +} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts deleted file mode 100644 index 19fdb153f..000000000 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/can-access-message-thread.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { ForbiddenException } from '@nestjs/common'; - -import { In } from 'typeorm'; - -import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; - -export class CanAccessMessageThreadService { - constructor( - @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) - private readonly workspaceMemberRepository: WorkspaceMemberRepository, - private readonly twentyORMManager: TwentyORMManager, - ) {} - - public async canAccessMessageThread( - userId: string, - workspaceId: string, - messageChannelMessageAssociations: MessageChannelMessageAssociationWorkspaceEntity[], - ) { - const messageChannelIds = messageChannelMessageAssociations.map( - (association) => association.messageChannelId, - ); - - const currentWorkspaceMember = - await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId); - - const connectedAccountRepository = - await this.twentyORMManager.getRepository( - 'connectedAccount', - ); - - const connectedAccounts = await connectedAccountRepository.find({ - select: { - id: true, - }, - where: [ - { - messageChannels: { - id: In(messageChannelIds), - visibility: MessageChannelVisibility.SHARE_EVERYTHING, - }, - }, - { - messageChannels: { - id: In(messageChannelIds), - }, - accountOwnerId: currentWorkspaceMember.id, - }, - ], - take: 1, - }); - - if (connectedAccounts.length === 0) { - throw new ForbiddenException(); - } - } -} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts new file mode 100644 index 000000000..63b3bcc4a --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts @@ -0,0 +1,35 @@ +import { WorkspaceQueryPostHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; +import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; + +@WorkspaceQueryHook({ + key: `message.findMany`, + type: WorkspaceQueryHookType.PostHook, +}) +export class MessageFindManyPostQueryHook + implements WorkspaceQueryPostHookInstance +{ + constructor( + private readonly applyMessagesVisibilityRestrictionsService: ApplyMessagesVisibilityRestrictionsService, + ) {} + + async execute( + authContext: AuthContext, + _objectName: string, + payload: MessageWorkspaceEntity[], + ): Promise { + if (!authContext.workspaceMemberId) { + throw new UserInputError('Workspace member id is required'); + } + + await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( + authContext.workspaceMemberId, + payload, + ); + } +} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook.ts deleted file mode 100644 index 14fa417cf..000000000 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { BadRequestException, NotFoundException } from '@nestjs/common'; - -import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service'; -import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; - -@WorkspaceQueryHook(`message.findMany`) -export class MessageFindManyPreQueryHook implements WorkspaceQueryHookInstance { - constructor( - private readonly canAccessMessageThreadService: CanAccessMessageThreadService, - private readonly twentyORMManager: TwentyORMManager, - ) {} - - async execute( - authContext: AuthContext, - objectName: string, - payload: FindManyResolverArgs, - ): Promise { - if (!payload?.filter?.messageThreadId?.eq) { - throw new BadRequestException('messageThreadId filter is required'); - } - - if (!authContext.user?.id) { - throw new BadRequestException('User id is required'); - } - - const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( - 'messageChannelMessageAssociation', - ); - - const messageChannelMessageAssociations = - await messageChannelMessageAssociationRepository.find({ - where: { - message: { - messageThreadId: payload.filter.messageThreadId.eq, - }, - }, - }); - - if (messageChannelMessageAssociations.length === 0) { - throw new NotFoundException(); - } - - await this.canAccessMessageThreadService.canAccessMessageThread( - authContext.user.id, - authContext.workspace.id, - messageChannelMessageAssociations, - ); - - return payload; - } -} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts new file mode 100644 index 000000000..33e345503 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts @@ -0,0 +1,35 @@ +import { WorkspaceQueryPostHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; + +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; +import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; + +@WorkspaceQueryHook({ + key: `message.findOne`, + type: WorkspaceQueryHookType.PostHook, +}) +export class MessageFindOnePostQueryHook + implements WorkspaceQueryPostHookInstance +{ + constructor( + private readonly applyMessagesVisibilityRestrictionsService: ApplyMessagesVisibilityRestrictionsService, + ) {} + + async execute( + authContext: AuthContext, + _objectName: string, + payload: MessageWorkspaceEntity[], + ): Promise { + if (!authContext.workspaceMemberId) { + throw new UserInputError('Workspace member id is required'); + } + + await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( + authContext.workspaceMemberId, + payload, + ); + } +} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook.ts deleted file mode 100644 index f06f4b09f..000000000 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { NotFoundException } from '@nestjs/common'; - -import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service'; -import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; - -@WorkspaceQueryHook(`message.findOne`) -export class MessageFindOnePreQueryHook implements WorkspaceQueryHookInstance { - constructor( - private readonly canAccessMessageThreadService: CanAccessMessageThreadService, - private readonly twentyORMManager: TwentyORMManager, - ) {} - - async execute( - authContext: AuthContext, - objectName: string, - payload: FindOneResolverArgs, - ): Promise { - if (!authContext.user?.id) { - throw new NotFoundException('User id is required'); - } - - const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( - 'messageChannelMessageAssociation', - ); - - const messageChannelMessageAssociations = - await messageChannelMessageAssociationRepository.find({ - where: { - messageId: payload?.filter?.id?.eq, - }, - }); - - if (messageChannelMessageAssociations.length === 0) { - throw new NotFoundException(); - } - - await this.canAccessMessageThreadService.canAccessMessageThread( - authContext.user.id, - authContext.workspace.id, - messageChannelMessageAssociations, - ); - - return payload; - } -} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts index 1222865f6..99f703b19 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; -import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service'; -import { MessageFindManyPreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook'; -import { MessageFindOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook'; +import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; +import { MessageFindManyPostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook'; +import { MessageFindOnePostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Module({ @@ -11,9 +11,9 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]), ], providers: [ - CanAccessMessageThreadService, - MessageFindOnePreQueryHook, - MessageFindManyPreQueryHook, + ApplyMessagesVisibilityRestrictionsService, + MessageFindOnePostQueryHook, + MessageFindManyPostQueryHook, ], }) export class MessagingQueryHookModule {}