Add WorkspaceAuthContextMiddleware (#17487)
## Context
Introduces a middleware that automatically sets the workspace auth
context in AsyncLocalStorage for HTTP requests, making it available
throughout the request lifecycle without explicit parameter passing.
The motivation behind this change is to reduce boilerplate and simplify
the developer experience when working with workspace data in HTTP
request handlers.
The Problem (Before)
Every HTTP request handler that needed to access workspace data had to:
- Extract auth-related info from decorators (@AuthWorkspace(),
@AuthUserWorkspaceId(), etc.) in controller/resolver and pass down to
services
- Build or pass the authContext explicitly (sometimes with type
assertion which was flaky)
Then call executeInWorkspaceContext(authContext, async () => { ... })
## Changes
- Add WorkspaceAuthContextMiddleware that extracts auth context from the
request and stores it in AsyncLocalStorage
- Register middleware for GraphQL, metadata, and REST routes (runs after
hydration middlewares)
- Simplify executeInWorkspaceContext signature: fn is now the first
parameter, authContext is optional second
- If authContext is not provided, it's automatically retrieved from the
storage (set by middleware)
- Update all callers (~120 files) to use the new parameter order
- Fixes a bug in search where system auth context was used, bypassing
RLS feature.
This commit is contained in:
+2
-2
@@ -88,7 +88,6 @@ export class BlocklistValidationService {
|
||||
|
||||
const currentWorkspaceMember =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -100,6 +99,7 @@ export class BlocklistValidationService {
|
||||
userId,
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
const currentBlocklist =
|
||||
@@ -145,7 +145,6 @@ export class BlocklistValidationService {
|
||||
|
||||
const currentWorkspaceMember =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -157,6 +156,7 @@ export class BlocklistValidationService {
|
||||
userId,
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
const currentBlocklist =
|
||||
|
||||
@@ -17,7 +17,6 @@ export class BlocklistRepository {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const blockListRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -32,6 +31,7 @@ export class BlocklistRepository {
|
||||
id,
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export class BlocklistRepository {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const blockListRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -56,6 +55,7 @@ export class BlocklistRepository {
|
||||
},
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+102
-105
@@ -35,126 +35,123 @@ export class BlocklistItemDeleteCalendarEventsJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const blocklistItemIds = data.events.map(
|
||||
(eventPayload) => eventPayload.recordId,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const blocklistItemIds = data.events.map(
|
||||
(eventPayload) => eventPayload.recordId,
|
||||
);
|
||||
|
||||
const blocklistRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<BlocklistWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'blocklist',
|
||||
);
|
||||
|
||||
const blocklistRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<BlocklistWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'blocklist',
|
||||
);
|
||||
const blocklist = await blocklistRepository.find({
|
||||
where: {
|
||||
id: Any(blocklistItemIds),
|
||||
},
|
||||
});
|
||||
|
||||
const blocklist = await blocklistRepository.find({
|
||||
where: {
|
||||
id: Any(blocklistItemIds),
|
||||
const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce(
|
||||
(acc, blocklistItem) => {
|
||||
const { handle, workspaceMemberId } = blocklistItem;
|
||||
|
||||
if (!acc.has(workspaceMemberId)) {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
|
||||
return acc;
|
||||
},
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
|
||||
for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) {
|
||||
const handles =
|
||||
handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId);
|
||||
|
||||
if (!handles) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce(
|
||||
(acc, blocklistItem) => {
|
||||
const { handle, workspaceMemberId } = blocklistItem;
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
const calendarChannelHandles = [calendarChannel.handle];
|
||||
|
||||
if (!acc.has(workspaceMemberId)) {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
if (calendarChannel.connectedAccount.handleAliases) {
|
||||
calendarChannelHandles.push(
|
||||
...calendarChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
const handleConditions = handles.map((handle) => {
|
||||
const isHandleDomain = handle.startsWith('@');
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
return isHandleDomain
|
||||
? {
|
||||
handle: And(
|
||||
Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)),
|
||||
Not(In(calendarChannelHandles)),
|
||||
),
|
||||
}
|
||||
: { handle };
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
const calendarEventsAssociationsToDelete =
|
||||
await calendarChannelEventAssociationRepository.find({
|
||||
where: {
|
||||
calendarChannelId: calendarChannel.id,
|
||||
calendarEvent: {
|
||||
calendarEventParticipants: handleConditions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
|
||||
for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) {
|
||||
const handles =
|
||||
handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId);
|
||||
|
||||
if (!handles) {
|
||||
if (calendarEventsAssociationsToDelete.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
const calendarChannelHandles = [calendarChannel.handle];
|
||||
|
||||
if (calendarChannel.connectedAccount.handleAliases) {
|
||||
calendarChannelHandles.push(
|
||||
...calendarChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
}
|
||||
|
||||
const handleConditions = handles.map((handle) => {
|
||||
const isHandleDomain = handle.startsWith('@');
|
||||
|
||||
return isHandleDomain
|
||||
? {
|
||||
handle: And(
|
||||
Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)),
|
||||
Not(In(calendarChannelHandles)),
|
||||
),
|
||||
}
|
||||
: { handle };
|
||||
});
|
||||
|
||||
const calendarEventsAssociationsToDelete =
|
||||
await calendarChannelEventAssociationRepository.find({
|
||||
where: {
|
||||
calendarChannelId: calendarChannel.id,
|
||||
calendarEvent: {
|
||||
calendarEventParticipants: handleConditions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (calendarEventsAssociationsToDelete.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await calendarChannelEventAssociationRepository.delete(
|
||||
calendarEventsAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
}
|
||||
await calendarChannelEventAssociationRepository.delete(
|
||||
calendarEventsAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
|
||||
workspaceId,
|
||||
);
|
||||
},
|
||||
);
|
||||
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-28
@@ -36,37 +36,34 @@ export class BlocklistReimportCalendarEventsJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
syncStage: Not(
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
),
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
});
|
||||
syncStage: Not(
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending(
|
||||
calendarChannels.map((calendarChannel) => calendarChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending(
|
||||
calendarChannels.map((calendarChannel) => calendarChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-30
@@ -15,37 +15,34 @@ export class CalendarEventCleanerService {
|
||||
public async cleanWorkspaceCalendarEvents(workspaceId: string) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarEventRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
'calendarEvent',
|
||||
);
|
||||
|
||||
await deleteUsingPagination(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarEventRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
500,
|
||||
async (limit, offset) => {
|
||||
const nonAssociatedCalendarEvents =
|
||||
await calendarEventRepository.find({
|
||||
where: {
|
||||
calendarChannelEventAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
return nonAssociatedCalendarEvents.map(({ id }) => id);
|
||||
},
|
||||
async (ids) => {
|
||||
await calendarEventRepository.delete({ id: Any(ids) });
|
||||
},
|
||||
'calendarEvent',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
async (limit, offset) => {
|
||||
const nonAssociatedCalendarEvents =
|
||||
await calendarEventRepository.find({
|
||||
where: {
|
||||
calendarChannelEventAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
return nonAssociatedCalendarEvents.map(({ id }) => id);
|
||||
},
|
||||
async (ids) => {
|
||||
await calendarEventRepository.delete({ id: Any(ids) });
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-53
@@ -51,64 +51,61 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (calendarChannelId) {
|
||||
whereCondition.id = calendarChannelId;
|
||||
}
|
||||
|
||||
const calendarChannels =
|
||||
await calendarChannelRepository.find(whereCondition);
|
||||
|
||||
if (calendarChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
'No calendar channels found with CALENDAR_EVENT_LIST_FETCH_PENDING status',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${calendarChannels.length} calendar channel(s) to process`,
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{
|
||||
calendarChannelId: calendarChannel.id,
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (calendarChannelId) {
|
||||
whereCondition.id = calendarChannelId;
|
||||
}
|
||||
|
||||
const calendarChannels =
|
||||
await calendarChannelRepository.find(whereCondition);
|
||||
|
||||
if (calendarChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
'No calendar channels found with CALENDAR_EVENT_LIST_FETCH_PENDING status',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${calendarChannels.length} calendar channel(s) to process`,
|
||||
},
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{
|
||||
calendarChannelId: calendarChannel.id,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Triggered fetch for calendar channel ${calendarChannel.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully triggered ${calendarChannels.length} calendar event list fetch job(s)`,
|
||||
`Triggered fetch for calendar channel ${calendarChannel.id}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully triggered ${calendarChannels.length} calendar event list fetch job(s)`,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@Option({
|
||||
|
||||
+45
-48
@@ -35,55 +35,52 @@ export class CalendarEventListFetchJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
calendarChannel.syncStage !==
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarFetchEventsService.fetchCalendarEvents(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
calendarChannel.syncStage !==
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarFetchEventsService.fetchCalendarEvents(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+44
-47
@@ -35,54 +35,51 @@ export class CalendarEventsImportJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel?.isSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
calendarChannel.syncStage !==
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarEventsImportService.processCalendarEventsImport(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel?.isSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
calendarChannel.syncStage !==
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarEventsImportService.processCalendarEventsImport(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-53
@@ -35,63 +35,60 @@ export class CalendarOngoingStaleJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
if (
|
||||
calendarChannel.syncStageStartedAt &&
|
||||
isSyncStale(calendarChannel.syncStageStartedAt)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
if (
|
||||
calendarChannel.syncStageStartedAt &&
|
||||
isSyncStale(calendarChannel.syncStageStartedAt)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
switch (calendarChannel.syncStage) {
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING:
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENT_LIST_FETCH_PENDING`,
|
||||
);
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING:
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENTS_IMPORT_PENDING`,
|
||||
);
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (calendarChannel.syncStage) {
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING:
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENT_LIST_FETCH_PENDING`,
|
||||
);
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING:
|
||||
case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENTS_IMPORT_PENDING`,
|
||||
);
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-32
@@ -31,41 +31,37 @@ export class CalendarRelaunchFailedCalendarChannelJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!calendarChannel ||
|
||||
calendarChannel.syncStage !== CalendarChannelSyncStage.FAILED ||
|
||||
calendarChannel.syncStatus !==
|
||||
CalendarChannelSyncStatus.FAILED_UNKNOWN
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!calendarChannel ||
|
||||
calendarChannel.syncStage !== CalendarChannelSyncStage.FAILED ||
|
||||
calendarChannel.syncStatus !== CalendarChannelSyncStatus.FAILED_UNKNOWN
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelId, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelId, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-19
@@ -133,26 +133,23 @@ export class CalendarEventImportErrorHandlerService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.increment(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await calendarChannelRepository.increment(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
switch (syncStep) {
|
||||
case CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH:
|
||||
|
||||
+96
-100
@@ -58,121 +58,117 @@ export class CalendarEventsImportService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
let calendarEvents: FetchedCalendarEvent[] = [];
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
let calendarEvents: FetchedCalendarEvent[] = [];
|
||||
|
||||
try {
|
||||
if (fetchedCalendarEvents) {
|
||||
calendarEvents = fetchedCalendarEvents;
|
||||
} else {
|
||||
const eventIdsToFetch: string[] = await this.cacheStorage.setPop(
|
||||
`calendar-events-to-import:${workspaceId}:${calendarChannel.id}`,
|
||||
CALENDAR_EVENT_IMPORT_BATCH_SIZE,
|
||||
);
|
||||
try {
|
||||
if (fetchedCalendarEvents) {
|
||||
calendarEvents = fetchedCalendarEvents;
|
||||
} else {
|
||||
const eventIdsToFetch: string[] = await this.cacheStorage.setPop(
|
||||
`calendar-events-to-import:${workspaceId}:${calendarChannel.id}`,
|
||||
CALENDAR_EVENT_IMPORT_BATCH_SIZE,
|
||||
);
|
||||
|
||||
if (!eventIdsToFetch || eventIdsToFetch.length === 0) {
|
||||
await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectedAccount.provider) {
|
||||
case 'microsoft':
|
||||
calendarEvents =
|
||||
await this.microsoftCalendarImportEventService.getCalendarEvents(
|
||||
connectedAccount,
|
||||
eventIdsToFetch,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!calendarEvents || calendarEvents?.length === 0) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
if (!eventIdsToFetch || eventIdsToFetch.length === 0) {
|
||||
await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const blocklist =
|
||||
await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
connectedAccount.accountOwnerId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(connectedAccount.handleAliases) ||
|
||||
!isDefined(calendarChannel.handle)
|
||||
) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Calendar channel handle or Handle aliases are required',
|
||||
CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
switch (connectedAccount.provider) {
|
||||
case 'microsoft':
|
||||
calendarEvents =
|
||||
await this.microsoftCalendarImportEventService.getCalendarEvents(
|
||||
connectedAccount,
|
||||
eventIdsToFetch,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const { filteredEvents, cancelledEvents } =
|
||||
filterEventsAndReturnCancelledEvents(
|
||||
[
|
||||
calendarChannel.handle,
|
||||
...connectedAccount.handleAliases.split(','),
|
||||
],
|
||||
calendarEvents,
|
||||
blocklist.map((blocklist) => blocklist.handle ?? ''),
|
||||
);
|
||||
|
||||
const cancelledEventExternalIds = cancelledEvents.map(
|
||||
(event) => event.id,
|
||||
);
|
||||
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
for (let i = 0; i < filteredEvents.length; i = i + BATCH_SIZE) {
|
||||
const eventsBatch = filteredEvents.slice(i, i + BATCH_SIZE);
|
||||
|
||||
await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob(
|
||||
eventsBatch,
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
|
||||
await calendarChannelEventAssociationRepository.delete({
|
||||
eventExternalId: Any(cancelledEventExternalIds),
|
||||
calendarChannel: {
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
});
|
||||
|
||||
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending(
|
||||
if (!calendarEvents || calendarEvents?.length === 0) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
await this.calendarEventImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
CalendarEventImportSyncStep.CALENDAR_EVENTS_IMPORT,
|
||||
}
|
||||
|
||||
const blocklist = await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
connectedAccount.accountOwnerId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(connectedAccount.handleAliases) ||
|
||||
!isDefined(calendarChannel.handle)
|
||||
) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Calendar channel handle or Handle aliases are required',
|
||||
CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const { filteredEvents, cancelledEvents } =
|
||||
filterEventsAndReturnCancelledEvents(
|
||||
[
|
||||
calendarChannel.handle,
|
||||
...connectedAccount.handleAliases.split(','),
|
||||
],
|
||||
calendarEvents,
|
||||
blocklist.map((blocklist) => blocklist.handle ?? ''),
|
||||
);
|
||||
|
||||
const cancelledEventExternalIds = cancelledEvents.map(
|
||||
(event) => event.id,
|
||||
);
|
||||
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
for (let i = 0; i < filteredEvents.length; i = i + BATCH_SIZE) {
|
||||
const eventsBatch = filteredEvents.slice(i, i + BATCH_SIZE);
|
||||
|
||||
await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob(
|
||||
eventsBatch,
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
|
||||
await calendarChannelEventAssociationRepository.delete({
|
||||
eventExternalId: Any(cancelledEventExternalIds),
|
||||
calendarChannel: {
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
});
|
||||
|
||||
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
await this.calendarEventImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
CalendarEventImportSyncStep.CALENDAR_EVENTS_IMPORT,
|
||||
calendarChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+89
-92
@@ -48,68 +48,51 @@ export class CalendarFetchEventsService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
const { accessToken, refreshToken } =
|
||||
await this.calendarAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
);
|
||||
|
||||
const connectedAccountWithFreshTokens = {
|
||||
...connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
if (!isDefined(calendarChannel.syncCursor)) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Sync cursor is required',
|
||||
CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const getCalendarEventsResponse =
|
||||
await this.getCalendarEventsService.getCalendarEvents(
|
||||
connectedAccountWithFreshTokens,
|
||||
calendarChannel.syncCursor,
|
||||
);
|
||||
|
||||
const hasFullEvents = getCalendarEventsResponse.fullEvents;
|
||||
|
||||
const calendarEvents = hasFullEvents
|
||||
? getCalendarEventsResponse.calendarEvents
|
||||
: null;
|
||||
const calendarEventIds = getCalendarEventsResponse.calendarEventIds;
|
||||
const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor;
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
const { accessToken, refreshToken } =
|
||||
await this.calendarAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
);
|
||||
|
||||
if (!calendarEvents || calendarEvents?.length === 0) {
|
||||
await calendarChannelRepository.update(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
{
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
const connectedAccountWithFreshTokens = {
|
||||
...connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
if (!isDefined(calendarChannel.syncCursor)) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Sync cursor is required',
|
||||
CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const getCalendarEventsResponse =
|
||||
await this.getCalendarEventsService.getCalendarEvents(
|
||||
connectedAccountWithFreshTokens,
|
||||
calendarChannel.syncCursor,
|
||||
);
|
||||
|
||||
const hasFullEvents = getCalendarEventsResponse.fullEvents;
|
||||
|
||||
const calendarEvents = hasFullEvents
|
||||
? getCalendarEventsResponse.calendarEvents
|
||||
: null;
|
||||
const calendarEventIds = getCalendarEventsResponse.calendarEventIds;
|
||||
const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor;
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
if (!calendarEvents || calendarEvents?.length === 0) {
|
||||
await calendarChannelRepository.update(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
@@ -119,42 +102,56 @@ export class CalendarFetchEventsService {
|
||||
},
|
||||
);
|
||||
|
||||
if (hasFullEvents && calendarEvents) {
|
||||
await this.calendarEventsImportService.processCalendarEventsImport(
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
calendarEvents,
|
||||
);
|
||||
} else if (!hasFullEvents && calendarEventIds) {
|
||||
await this.cacheStorage.setAdd(
|
||||
`calendar-events-to-import:${workspaceId}:${calendarChannel.id}`,
|
||||
calendarEventIds,
|
||||
);
|
||||
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} else {
|
||||
throw new CalendarEventImportDriverException(
|
||||
"Expected 'calendarEvents' or 'calendarEventIds' to be present",
|
||||
CalendarEventImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
`Calendar event fetch error for workspace ${workspaceId} and calendar channel ${calendarChannel.id}`,
|
||||
);
|
||||
this.logger.error(error);
|
||||
await this.calendarEventImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH,
|
||||
calendarChannel,
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
{
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
|
||||
if (hasFullEvents && calendarEvents) {
|
||||
await this.calendarEventsImportService.processCalendarEventsImport(
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
calendarEvents,
|
||||
);
|
||||
} else if (!hasFullEvents && calendarEventIds) {
|
||||
await this.cacheStorage.setAdd(
|
||||
`calendar-events-to-import:${workspaceId}:${calendarChannel.id}`,
|
||||
calendarEventIds,
|
||||
);
|
||||
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} else {
|
||||
throw new CalendarEventImportDriverException(
|
||||
"Expected 'calendarEvents' or 'calendarEventIds' to be present",
|
||||
CalendarEventImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
`Calendar event fetch error for workspace ${workspaceId} and calendar channel ${calendarChannel.id}`,
|
||||
);
|
||||
this.logger.error(error);
|
||||
await this.calendarEventImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH,
|
||||
calendarChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+208
-217
@@ -34,248 +34,239 @@ export class CalendarSaveEventsService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarEventRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarEventWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEvent',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarEventRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarEventWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEvent',
|
||||
);
|
||||
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannelEventAssociation',
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
await workspaceDataSource.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
const existingCalendarEvents = await calendarEventRepository.find(
|
||||
{
|
||||
where: {
|
||||
iCalUid: Any(
|
||||
fetchedCalendarEvents.map(
|
||||
(event) => event.iCalUid as string,
|
||||
),
|
||||
),
|
||||
},
|
||||
await workspaceDataSource.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
const existingCalendarEvents = await calendarEventRepository.find(
|
||||
{
|
||||
where: {
|
||||
iCalUid: Any(
|
||||
fetchedCalendarEvents.map((event) => event.iCalUid as string),
|
||||
),
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const fetchedCalendarEventsWithDBEvents: FetchedCalendarEventWithDBEvent[] =
|
||||
fetchedCalendarEvents.map(
|
||||
(event): FetchedCalendarEventWithDBEvent => {
|
||||
const existingEventWithSameiCalUid =
|
||||
existingCalendarEvents.find(
|
||||
(existingEvent) =>
|
||||
existingEvent.iCalUid === event.iCalUid,
|
||||
);
|
||||
|
||||
return {
|
||||
fetchedCalendarEvent: event,
|
||||
existingCalendarEvent: existingEventWithSameiCalUid ?? null,
|
||||
newlyCreatedCalendarEvent: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const newCalendarEventsToInsert = fetchedCalendarEventsWithDBEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent === null,
|
||||
)
|
||||
.map(({ fetchedCalendarEvent }) => ({
|
||||
id: uuid(),
|
||||
iCalUid: fetchedCalendarEvent.iCalUid,
|
||||
title: fetchedCalendarEvent.title,
|
||||
description: fetchedCalendarEvent.description,
|
||||
startsAt: fetchedCalendarEvent.startsAt,
|
||||
endsAt: fetchedCalendarEvent.endsAt,
|
||||
location: fetchedCalendarEvent.location,
|
||||
isFullDay: fetchedCalendarEvent.isFullDay,
|
||||
isCanceled: fetchedCalendarEvent.isCanceled,
|
||||
conferenceSolution: fetchedCalendarEvent.conferenceSolution,
|
||||
conferenceLink: {
|
||||
primaryLinkLabel: fetchedCalendarEvent.conferenceLinkLabel,
|
||||
primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl,
|
||||
secondaryLinks: [],
|
||||
},
|
||||
externalCreatedAt: fetchedCalendarEvent.externalCreatedAt,
|
||||
externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt,
|
||||
}));
|
||||
|
||||
if (newCalendarEventsToInsert.length > 0) {
|
||||
await calendarEventRepository.insert(
|
||||
newCalendarEventsToInsert,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents: FetchedCalendarEventWithDBEvent[] =
|
||||
fetchedCalendarEventsWithDBEvents.map(
|
||||
({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
const savedCalendarEvent = newCalendarEventsToInsert.find(
|
||||
(inserted) =>
|
||||
inserted.iCalUid === fetchedCalendarEvent.iCalUid,
|
||||
const fetchedCalendarEventsWithDBEvents: FetchedCalendarEventWithDBEvent[] =
|
||||
fetchedCalendarEvents.map(
|
||||
(event): FetchedCalendarEventWithDBEvent => {
|
||||
const existingEventWithSameiCalUid =
|
||||
existingCalendarEvents.find(
|
||||
(existingEvent) => existingEvent.iCalUid === event.iCalUid,
|
||||
);
|
||||
|
||||
return {
|
||||
fetchedCalendarEvent,
|
||||
existingCalendarEvent: existingCalendarEvent,
|
||||
newlyCreatedCalendarEvent: savedCalendarEvent
|
||||
? ({
|
||||
id: savedCalendarEvent.id,
|
||||
iCalUid: savedCalendarEvent.iCalUid,
|
||||
} as CalendarEventWorkspaceEntity)
|
||||
: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const existingEventsToUpdate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent !== null,
|
||||
)
|
||||
.map(({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
if (!existingCalendarEvent) {
|
||||
throw new Error(
|
||||
`Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
criteria: existingCalendarEvent.id,
|
||||
partialEntity: {
|
||||
iCalUid: fetchedCalendarEvent.iCalUid,
|
||||
title: fetchedCalendarEvent.title,
|
||||
description: fetchedCalendarEvent.description,
|
||||
startsAt: fetchedCalendarEvent.startsAt,
|
||||
endsAt: fetchedCalendarEvent.endsAt,
|
||||
location: fetchedCalendarEvent.location,
|
||||
isFullDay: fetchedCalendarEvent.isFullDay,
|
||||
isCanceled: fetchedCalendarEvent.isCanceled,
|
||||
conferenceSolution:
|
||||
fetchedCalendarEvent.conferenceSolution,
|
||||
conferenceLink: {
|
||||
primaryLinkLabel:
|
||||
fetchedCalendarEvent.conferenceLinkLabel,
|
||||
primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl,
|
||||
secondaryLinks: [],
|
||||
},
|
||||
externalCreatedAt: fetchedCalendarEvent.externalCreatedAt,
|
||||
externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (existingEventsToUpdate.length > 0) {
|
||||
await calendarEventRepository.updateMany(
|
||||
existingEventsToUpdate,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const calendarChannelEventAssociationsToSave: Pick<
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
| 'calendarEventId'
|
||||
| 'eventExternalId'
|
||||
| 'calendarChannelId'
|
||||
| 'recurringEventExternalId'
|
||||
>[] = fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents.map(
|
||||
({
|
||||
fetchedCalendarEvent,
|
||||
existingCalendarEvent,
|
||||
newlyCreatedCalendarEvent,
|
||||
}) => {
|
||||
const calendarEventId =
|
||||
existingCalendarEvent?.id ?? newlyCreatedCalendarEvent?.id;
|
||||
|
||||
if (!calendarEventId) {
|
||||
throw new Error(
|
||||
`Calendar event id not found for event with iCalUid ${fetchedCalendarEvent.iCalUid} - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
calendarEventId,
|
||||
eventExternalId: fetchedCalendarEvent.id,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
recurringEventExternalId:
|
||||
fetchedCalendarEvent.recurringEventExternalId ?? '',
|
||||
fetchedCalendarEvent: event,
|
||||
existingCalendarEvent: existingEventWithSameiCalUid ?? null,
|
||||
newlyCreatedCalendarEvent: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (calendarChannelEventAssociationsToSave.length > 0) {
|
||||
await calendarChannelEventAssociationRepository.insert(
|
||||
calendarChannelEventAssociationsToSave,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
const newCalendarEventsToInsert = fetchedCalendarEventsWithDBEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent === null,
|
||||
)
|
||||
.map(({ fetchedCalendarEvent }) => ({
|
||||
id: uuid(),
|
||||
iCalUid: fetchedCalendarEvent.iCalUid,
|
||||
title: fetchedCalendarEvent.title,
|
||||
description: fetchedCalendarEvent.description,
|
||||
startsAt: fetchedCalendarEvent.startsAt,
|
||||
endsAt: fetchedCalendarEvent.endsAt,
|
||||
location: fetchedCalendarEvent.location,
|
||||
isFullDay: fetchedCalendarEvent.isFullDay,
|
||||
isCanceled: fetchedCalendarEvent.isCanceled,
|
||||
conferenceSolution: fetchedCalendarEvent.conferenceSolution,
|
||||
conferenceLink: {
|
||||
primaryLinkLabel: fetchedCalendarEvent.conferenceLinkLabel,
|
||||
primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl,
|
||||
secondaryLinks: [],
|
||||
},
|
||||
externalCreatedAt: fetchedCalendarEvent.externalCreatedAt,
|
||||
externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt,
|
||||
}));
|
||||
|
||||
const participantsToCreate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ newlyCreatedCalendarEvent }) =>
|
||||
newlyCreatedCalendarEvent !== null,
|
||||
)
|
||||
.flatMap(
|
||||
({ newlyCreatedCalendarEvent, fetchedCalendarEvent }) => {
|
||||
if (!newlyCreatedCalendarEvent?.id) {
|
||||
throw new Error(
|
||||
`Newly created calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
);
|
||||
}
|
||||
if (newCalendarEventsToInsert.length > 0) {
|
||||
await calendarEventRepository.insert(
|
||||
newCalendarEventsToInsert,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
return fetchedCalendarEvent.participants.map(
|
||||
(participant) => ({
|
||||
...participant,
|
||||
calendarEventId: newlyCreatedCalendarEvent.id,
|
||||
}),
|
||||
);
|
||||
},
|
||||
const fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents: FetchedCalendarEventWithDBEvent[] =
|
||||
fetchedCalendarEventsWithDBEvents.map(
|
||||
({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
const savedCalendarEvent = newCalendarEventsToInsert.find(
|
||||
(inserted) =>
|
||||
inserted.iCalUid === fetchedCalendarEvent.iCalUid,
|
||||
);
|
||||
|
||||
// todo: we should prevent duplicate rows on calendarEventAssociation by creating
|
||||
// an index on calendarChannelId and calendarEventId
|
||||
const participantsToUpdate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent !== null,
|
||||
)
|
||||
.flatMap(({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
if (!existingCalendarEvent?.id) {
|
||||
return {
|
||||
fetchedCalendarEvent,
|
||||
existingCalendarEvent: existingCalendarEvent,
|
||||
newlyCreatedCalendarEvent: savedCalendarEvent
|
||||
? ({
|
||||
id: savedCalendarEvent.id,
|
||||
iCalUid: savedCalendarEvent.iCalUid,
|
||||
} as CalendarEventWorkspaceEntity)
|
||||
: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const existingEventsToUpdate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent !== null,
|
||||
)
|
||||
.map(({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
if (!existingCalendarEvent) {
|
||||
throw new Error(
|
||||
`Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
criteria: existingCalendarEvent.id,
|
||||
partialEntity: {
|
||||
iCalUid: fetchedCalendarEvent.iCalUid,
|
||||
title: fetchedCalendarEvent.title,
|
||||
description: fetchedCalendarEvent.description,
|
||||
startsAt: fetchedCalendarEvent.startsAt,
|
||||
endsAt: fetchedCalendarEvent.endsAt,
|
||||
location: fetchedCalendarEvent.location,
|
||||
isFullDay: fetchedCalendarEvent.isFullDay,
|
||||
isCanceled: fetchedCalendarEvent.isCanceled,
|
||||
conferenceSolution: fetchedCalendarEvent.conferenceSolution,
|
||||
conferenceLink: {
|
||||
primaryLinkLabel:
|
||||
fetchedCalendarEvent.conferenceLinkLabel,
|
||||
primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl,
|
||||
secondaryLinks: [],
|
||||
},
|
||||
externalCreatedAt: fetchedCalendarEvent.externalCreatedAt,
|
||||
externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (existingEventsToUpdate.length > 0) {
|
||||
await calendarEventRepository.updateMany(
|
||||
existingEventsToUpdate,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const calendarChannelEventAssociationsToSave: Pick<
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
| 'calendarEventId'
|
||||
| 'eventExternalId'
|
||||
| 'calendarChannelId'
|
||||
| 'recurringEventExternalId'
|
||||
>[] = fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents.map(
|
||||
({
|
||||
fetchedCalendarEvent,
|
||||
existingCalendarEvent,
|
||||
newlyCreatedCalendarEvent,
|
||||
}) => {
|
||||
const calendarEventId =
|
||||
existingCalendarEvent?.id ?? newlyCreatedCalendarEvent?.id;
|
||||
|
||||
if (!calendarEventId) {
|
||||
throw new Error(
|
||||
`Calendar event id not found for event with iCalUid ${fetchedCalendarEvent.iCalUid} - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
calendarEventId,
|
||||
eventExternalId: fetchedCalendarEvent.id,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
recurringEventExternalId:
|
||||
fetchedCalendarEvent.recurringEventExternalId ?? '',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
if (calendarChannelEventAssociationsToSave.length > 0) {
|
||||
await calendarChannelEventAssociationRepository.insert(
|
||||
calendarChannelEventAssociationsToSave,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const participantsToCreate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ newlyCreatedCalendarEvent }) =>
|
||||
newlyCreatedCalendarEvent !== null,
|
||||
)
|
||||
.flatMap(
|
||||
({ newlyCreatedCalendarEvent, fetchedCalendarEvent }) => {
|
||||
if (!newlyCreatedCalendarEvent?.id) {
|
||||
throw new Error(
|
||||
`Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
`Newly created calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return fetchedCalendarEvent.participants.map(
|
||||
(participant) => ({
|
||||
...participant,
|
||||
calendarEventId: existingCalendarEvent.id,
|
||||
calendarEventId: newlyCreatedCalendarEvent.id,
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants(
|
||||
{
|
||||
participantsToCreate,
|
||||
participantsToUpdate,
|
||||
transactionManager,
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
// todo: we should prevent duplicate rows on calendarEventAssociation by creating
|
||||
// an index on calendarChannelId and calendarEventId
|
||||
const participantsToUpdate =
|
||||
fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents
|
||||
.filter(
|
||||
({ existingCalendarEvent }) => existingCalendarEvent !== null,
|
||||
)
|
||||
.flatMap(({ fetchedCalendarEvent, existingCalendarEvent }) => {
|
||||
if (!existingCalendarEvent?.id) {
|
||||
throw new Error(
|
||||
`Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`,
|
||||
);
|
||||
}
|
||||
|
||||
return fetchedCalendarEvent.participants.map((participant) => ({
|
||||
...participant,
|
||||
calendarEventId: existingCalendarEvent.id,
|
||||
}));
|
||||
});
|
||||
|
||||
await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants(
|
||||
{
|
||||
participantsToCreate,
|
||||
participantsToUpdate,
|
||||
transactionManager,
|
||||
calendarChannel,
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+112
-116
@@ -58,131 +58,127 @@ export class CalendarEventParticipantService {
|
||||
}): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const chunkedParticipantsToUpdate = chunk(participantsToUpdate, 200);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const chunkedParticipantsToUpdate = chunk(participantsToUpdate, 200);
|
||||
|
||||
const calendarEventParticipantRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarEventParticipantWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEventParticipant',
|
||||
);
|
||||
const calendarEventParticipantRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarEventParticipantWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarEventParticipant',
|
||||
);
|
||||
|
||||
for (const participantsToUpdateChunk of chunkedParticipantsToUpdate) {
|
||||
const existingCalendarEventParticipants =
|
||||
await calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
calendarEventId: Any(
|
||||
participantsToUpdateChunk
|
||||
.map((participant) => participant.calendarEventId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
calendarEventParticipantsToUpdate,
|
||||
newCalendarEventParticipants,
|
||||
} = participantsToUpdateChunk.reduce<{
|
||||
calendarEventParticipantsToUpdate: FetchedCalendarEventParticipantWithCalendarEventIdAndExistingId[];
|
||||
newCalendarEventParticipants: FetchedCalendarEventParticipantWithCalendarEventId[];
|
||||
}>(
|
||||
(acc, calendarEventParticipant) => {
|
||||
const existingCalendarEventParticipant =
|
||||
existingCalendarEventParticipants.find(
|
||||
(existingCalendarEventParticipant) =>
|
||||
existingCalendarEventParticipant.handle ===
|
||||
calendarEventParticipant.handle &&
|
||||
existingCalendarEventParticipant.calendarEventId ===
|
||||
calendarEventParticipant.calendarEventId,
|
||||
);
|
||||
|
||||
if (existingCalendarEventParticipant) {
|
||||
acc.calendarEventParticipantsToUpdate.push({
|
||||
...calendarEventParticipant,
|
||||
id: existingCalendarEventParticipant.id,
|
||||
});
|
||||
} else {
|
||||
acc.newCalendarEventParticipants.push(calendarEventParticipant);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
calendarEventParticipantsToUpdate: [],
|
||||
newCalendarEventParticipants: [],
|
||||
},
|
||||
);
|
||||
|
||||
const calendarEventParticipantsToDelete = differenceWith(
|
||||
existingCalendarEventParticipants,
|
||||
participantsToUpdateChunk,
|
||||
(existingCalendarEventParticipant, participantToUpdate) =>
|
||||
existingCalendarEventParticipant.handle ===
|
||||
participantToUpdate.handle &&
|
||||
existingCalendarEventParticipant.calendarEventId ===
|
||||
participantToUpdate.calendarEventId,
|
||||
);
|
||||
|
||||
await calendarEventParticipantRepository.delete(
|
||||
{
|
||||
id: Any(
|
||||
calendarEventParticipantsToDelete.map(
|
||||
(calendarEventParticipant) => calendarEventParticipant.id,
|
||||
),
|
||||
for (const participantsToUpdateChunk of chunkedParticipantsToUpdate) {
|
||||
const existingCalendarEventParticipants =
|
||||
await calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
calendarEventId: Any(
|
||||
participantsToUpdateChunk
|
||||
.map((participant) => participant.calendarEventId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
});
|
||||
|
||||
await calendarEventParticipantRepository.updateMany(
|
||||
calendarEventParticipantsToUpdate.map((participant) => ({
|
||||
criteria: participant.id,
|
||||
partialEntity: participant,
|
||||
})),
|
||||
transactionManager,
|
||||
);
|
||||
participantsToCreate.push(...newCalendarEventParticipants);
|
||||
}
|
||||
const {
|
||||
calendarEventParticipantsToUpdate,
|
||||
newCalendarEventParticipants,
|
||||
} = participantsToUpdateChunk.reduce<{
|
||||
calendarEventParticipantsToUpdate: FetchedCalendarEventParticipantWithCalendarEventIdAndExistingId[];
|
||||
newCalendarEventParticipants: FetchedCalendarEventParticipantWithCalendarEventId[];
|
||||
}>(
|
||||
(acc, calendarEventParticipant) => {
|
||||
const existingCalendarEventParticipant =
|
||||
existingCalendarEventParticipants.find(
|
||||
(existingCalendarEventParticipant) =>
|
||||
existingCalendarEventParticipant.handle ===
|
||||
calendarEventParticipant.handle &&
|
||||
existingCalendarEventParticipant.calendarEventId ===
|
||||
calendarEventParticipant.calendarEventId,
|
||||
);
|
||||
|
||||
const chunkedParticipantsToCreate = chunk(participantsToCreate, 200);
|
||||
const savedParticipants: CalendarEventParticipantWorkspaceEntity[] = [];
|
||||
if (existingCalendarEventParticipant) {
|
||||
acc.calendarEventParticipantsToUpdate.push({
|
||||
...calendarEventParticipant,
|
||||
id: existingCalendarEventParticipant.id,
|
||||
});
|
||||
} else {
|
||||
acc.newCalendarEventParticipants.push(calendarEventParticipant);
|
||||
}
|
||||
|
||||
for (const participantsToCreateChunk of chunkedParticipantsToCreate) {
|
||||
const savedParticipantsChunk =
|
||||
await calendarEventParticipantRepository.insert(
|
||||
participantsToCreateChunk,
|
||||
transactionManager,
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
calendarEventParticipantsToUpdate: [],
|
||||
newCalendarEventParticipants: [],
|
||||
},
|
||||
);
|
||||
|
||||
savedParticipants.push(...savedParticipantsChunk.raw);
|
||||
}
|
||||
const calendarEventParticipantsToDelete = differenceWith(
|
||||
existingCalendarEventParticipants,
|
||||
participantsToUpdateChunk,
|
||||
(existingCalendarEventParticipant, participantToUpdate) =>
|
||||
existingCalendarEventParticipant.handle ===
|
||||
participantToUpdate.handle &&
|
||||
existingCalendarEventParticipant.calendarEventId ===
|
||||
participantToUpdate.calendarEventId,
|
||||
);
|
||||
|
||||
if (calendarChannel.isContactAutoCreationEnabled) {
|
||||
await this.messageQueueService.add<CreateCompanyAndContactJobData>(
|
||||
CreateCompanyAndContactJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccount,
|
||||
contactsToCreate: savedParticipants.map((participant) => ({
|
||||
handle: participant.handle ?? '',
|
||||
displayName:
|
||||
participant.displayName ?? participant.handle ?? '',
|
||||
})),
|
||||
source: FieldActorSource.CALENDAR,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await this.matchParticipantService.matchParticipants({
|
||||
participants: savedParticipants,
|
||||
objectMetadataName: 'calendarEventParticipant',
|
||||
await calendarEventParticipantRepository.delete(
|
||||
{
|
||||
id: Any(
|
||||
calendarEventParticipantsToDelete.map(
|
||||
(calendarEventParticipant) => calendarEventParticipant.id,
|
||||
),
|
||||
),
|
||||
},
|
||||
transactionManager,
|
||||
matchWith: 'workspaceMemberAndPerson',
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
await calendarEventParticipantRepository.updateMany(
|
||||
calendarEventParticipantsToUpdate.map((participant) => ({
|
||||
criteria: participant.id,
|
||||
partialEntity: participant,
|
||||
})),
|
||||
transactionManager,
|
||||
);
|
||||
participantsToCreate.push(...newCalendarEventParticipants);
|
||||
}
|
||||
|
||||
const chunkedParticipantsToCreate = chunk(participantsToCreate, 200);
|
||||
const savedParticipants: CalendarEventParticipantWorkspaceEntity[] = [];
|
||||
|
||||
for (const participantsToCreateChunk of chunkedParticipantsToCreate) {
|
||||
const savedParticipantsChunk =
|
||||
await calendarEventParticipantRepository.insert(
|
||||
participantsToCreateChunk,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
savedParticipants.push(...savedParticipantsChunk.raw);
|
||||
}
|
||||
|
||||
if (calendarChannel.isContactAutoCreationEnabled) {
|
||||
await this.messageQueueService.add<CreateCompanyAndContactJobData>(
|
||||
CreateCompanyAndContactJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccount,
|
||||
contactsToCreate: savedParticipants.map((participant) => ({
|
||||
handle: participant.handle ?? '',
|
||||
displayName: participant.displayName ?? participant.handle ?? '',
|
||||
})),
|
||||
source: FieldActorSource.CALENDAR,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await this.matchParticipantService.matchParticipants({
|
||||
participants: savedParticipants,
|
||||
objectMetadataName: 'calendarEventParticipant',
|
||||
transactionManager,
|
||||
matchWith: 'workspaceMemberAndPerson',
|
||||
workspaceId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
}),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
+1
-1
@@ -27,7 +27,6 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
@@ -117,6 +116,7 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
|
||||
|
||||
return calendarEvents;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+129
-156
@@ -39,21 +39,18 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsCalendarEventListFetchOngoing(
|
||||
@@ -66,22 +63,19 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async resetAndMarkAsCalendarEventListFetchPending(
|
||||
@@ -100,22 +94,19 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
await this.markAsCalendarEventListFetchPending(
|
||||
calendarChannelIds,
|
||||
@@ -133,20 +124,17 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsCalendarEventsImportPending(
|
||||
@@ -160,21 +148,18 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsCalendarEventsImportOngoing(
|
||||
@@ -187,21 +172,18 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsCompletedAndMarkAsCalendarEventListFetchPending(
|
||||
@@ -214,24 +196,21 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
await this.markAsCalendarEventListFetchPending(
|
||||
calendarChannelIds,
|
||||
@@ -260,21 +239,18 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
},
|
||||
);
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
key: MetricsKeys.CalendarEventSyncJobFailedUnknown,
|
||||
@@ -298,48 +274,45 @@ export class CalendarChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(calendarChannelIds) },
|
||||
});
|
||||
|
||||
const connectedAccountIds = calendarChannels.map(
|
||||
(calendarChannel) => calendarChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
},
|
||||
);
|
||||
|
||||
await this.addToAccountsToReconnect(
|
||||
calendarChannels.map((calendarChannel) => calendarChannel.id),
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(calendarChannelIds) },
|
||||
});
|
||||
|
||||
const connectedAccountIds = calendarChannels.map(
|
||||
(calendarChannel) => calendarChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
},
|
||||
);
|
||||
|
||||
await this.addToAccountsToReconnect(
|
||||
calendarChannels.map((calendarChannel) => calendarChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
key: MetricsKeys.CalendarEventSyncJobFailedInsufficientPermissions,
|
||||
|
||||
+54
-60
@@ -53,38 +53,35 @@ export class ChannelSyncService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION,
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async startCalendarChannelSync(
|
||||
@@ -93,38 +90,35 @@ export class ChannelSyncService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
|
||||
await this.calendarQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.calendarQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ describe('Email Alias Manager Service', () => {
|
||||
.mockResolvedValue(connectedAccountRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
EmailAliasManagerService,
|
||||
|
||||
+13
-16
@@ -49,22 +49,19 @@ export class EmailAliasManagerService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array
|
||||
},
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-13
@@ -24,19 +24,16 @@ export class DeleteWorkspaceMemberConnectedAccountsCleanupJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.delete({
|
||||
accountOwnerId: workspaceMemberId,
|
||||
});
|
||||
},
|
||||
);
|
||||
await connectedAccountRepository.delete({
|
||||
accountOwnerId: workspaceMemberId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-28
@@ -27,36 +27,30 @@ export class ConnectedAccountListener {
|
||||
const workspaceId = payload.workspaceId;
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
for (const eventPayload of payload.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.accountOwnerId;
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
for (const eventPayload of payload.events) {
|
||||
const workspaceMemberId = eventPayload.properties.before.accountOwnerId;
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
const workspaceMember = await workspaceMemberRepository.findOneOrFail(
|
||||
{
|
||||
where: { id: workspaceMemberId },
|
||||
},
|
||||
);
|
||||
|
||||
const userId = workspaceMember.userId;
|
||||
|
||||
const connectedAccountId = eventPayload.properties.before.id;
|
||||
|
||||
await this.accountsToReconnectService.removeAccountToReconnect(
|
||||
userId,
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
'workspaceMember',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
const workspaceMember = await workspaceMemberRepository.findOneOrFail({
|
||||
where: { id: workspaceMemberId },
|
||||
});
|
||||
|
||||
const userId = workspaceMember.userId;
|
||||
|
||||
const connectedAccountId = eventPayload.properties.before.id;
|
||||
|
||||
await this.accountsToReconnectService.removeAccountToReconnect(
|
||||
userId,
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,6 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
|
||||
const messageChannels =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
@@ -53,6 +52,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
connectedAccountId,
|
||||
});
|
||||
},
|
||||
authContext as WorkspaceAuthContext,
|
||||
);
|
||||
|
||||
const objectMetadataEntity =
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+14
-17
@@ -77,24 +77,21 @@ export class ConnectedAccountRefreshTokensService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
...connectedAccountTokens,
|
||||
lastCredentialsRefreshedAt: new Date(),
|
||||
},
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
...connectedAccountTokens,
|
||||
lastCredentialsRefreshedAt: new Date(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
return connectedAccountTokens;
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -29,7 +29,6 @@ export class ImapSmtpCalDavAPIService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
@@ -41,6 +40,7 @@ export class ImapSmtpCalDavAPIService {
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ export class ImapSmtpCalDavAPIService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
@@ -147,6 +146,7 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
return newOrExistingAccountId;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ describe('CreateCompanyService', () => {
|
||||
getRepository: jest.fn().mockResolvedValue(mockCompanyRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+21
-24
@@ -51,7 +51,6 @@ export class CreateCompanyAndPersonService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const personRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -144,6 +143,7 @@ export class CreateCompanyAndPersonService {
|
||||
|
||||
return { ...createdPeople, ...restoredPeople };
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -160,32 +160,29 @@ export class CreateCompanyAndPersonService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
if (!connectedAccount.accountOwner) {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
if (!connectedAccount.accountOwner) {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
);
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: {
|
||||
id: connectedAccount.accountOwnerId,
|
||||
},
|
||||
});
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: {
|
||||
id: connectedAccount.accountOwnerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
throw new Error(
|
||||
`Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
connectedAccount.accountOwner = workspaceMember;
|
||||
if (!workspaceMember) {
|
||||
throw new Error(
|
||||
`Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
connectedAccount.accountOwner = workspaceMember;
|
||||
}
|
||||
}, authContext);
|
||||
|
||||
for (const contactsBatch of contactsBatches) {
|
||||
try {
|
||||
|
||||
+1
-1
@@ -56,7 +56,6 @@ export class CreateCompanyService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const companyRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -157,6 +156,7 @@ export class CreateCompanyService {
|
||||
: {}),
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -22,7 +22,6 @@ export class CreatePersonService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const personRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -47,6 +46,7 @@ export class CreatePersonService {
|
||||
|
||||
return createdPeople.raw;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ export class CreatePersonService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const personRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -86,6 +85,7 @@ export class CreatePersonService {
|
||||
|
||||
return restoredPeople.raw;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,6 @@ export class DashboardSyncService {
|
||||
|
||||
try {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -71,6 +70,7 @@ export class DashboardSyncService {
|
||||
|
||||
await dashboardRepository.update({ pageLayoutId }, { updatedAt });
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
|
||||
+1
-1
@@ -44,7 +44,6 @@ export class DashboardDuplicationService {
|
||||
const workspaceId = workspace.id;
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<DashboardWorkspaceEntity>(
|
||||
@@ -108,6 +107,7 @@ export class DashboardDuplicationService {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
authContext as WorkspaceAuthContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+23
-26
@@ -52,32 +52,29 @@ export class DashboardToPageLayoutSyncService {
|
||||
}): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<DashboardWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const dashboards = await dashboardRepository.find({
|
||||
where: {
|
||||
id: In(dashboardIds),
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const pageLayoutIds = dashboards
|
||||
.map((dashboard) => dashboard.pageLayoutId)
|
||||
.filter(isDefined);
|
||||
|
||||
await this.pageLayoutService.destroyMany({
|
||||
ids: pageLayoutIds,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const dashboardRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<DashboardWorkspaceEntity>(
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const dashboards = await dashboardRepository.find({
|
||||
where: {
|
||||
id: In(dashboardIds),
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
const pageLayoutIds = dashboards
|
||||
.map((dashboard) => dashboard.pageLayoutId)
|
||||
.filter(isDefined);
|
||||
|
||||
await this.pageLayoutService.destroyMany({
|
||||
ids: pageLayoutIds,
|
||||
workspaceId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-19
@@ -206,27 +206,24 @@ const createDashboardRecord = async (
|
||||
): Promise<string> => {
|
||||
const authContext = buildSystemAuthContext(context.workspaceId);
|
||||
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const dashboardRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const dashboardRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const position = await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: { isCustom: false, nameSingular: 'dashboard' },
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
const position = await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: { isCustom: false, nameSingular: 'dashboard' },
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const dashboard = { id: uuidv4(), title, pageLayoutId, position };
|
||||
const dashboard = { id: uuidv4(), title, pageLayoutId, position };
|
||||
|
||||
await dashboardRepository.insert(dashboard);
|
||||
await dashboardRepository.insert(dashboard);
|
||||
|
||||
return dashboard.id;
|
||||
},
|
||||
);
|
||||
return dashboard.id;
|
||||
}, authContext);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,6 @@ export const createGetDashboardTool = (
|
||||
|
||||
const dashboard =
|
||||
await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const repo = await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
@@ -37,6 +36,7 @@ export const createGetDashboardTool = (
|
||||
|
||||
return repo.findOne({ where: { id: parameters.dashboardId } });
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
if (!isDefined(dashboard)) {
|
||||
|
||||
@@ -30,7 +30,6 @@ export const createListDashboardsTool = (
|
||||
|
||||
const dashboards =
|
||||
await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const repo = await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
@@ -40,6 +39,7 @@ export const createListDashboardsTool = (
|
||||
|
||||
return repo.find({ take: limit, order: { position: 'ASC' } });
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
const dashboardList = dashboards.map((d) => ({
|
||||
|
||||
+13
-16
@@ -25,22 +25,19 @@ export class FavoriteFolderDeletionListener {
|
||||
const workspaceId = payload.workspaceId;
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
for (const eventPayload of payload.events) {
|
||||
const favoriteRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<FavoriteWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'favorite',
|
||||
);
|
||||
|
||||
await favoriteRepository.update(
|
||||
{ favoriteFolderId: eventPayload.recordId },
|
||||
{ deletedAt: new Date().toISOString() },
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
for (const eventPayload of payload.events) {
|
||||
const favoriteRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<FavoriteWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'favorite',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await favoriteRepository.update(
|
||||
{ favoriteFolderId: eventPayload.recordId },
|
||||
{ deletedAt: new Date().toISOString() },
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,68 +28,65 @@ export class FavoriteDeletionService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const favoriteRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<FavoriteWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'favorite',
|
||||
);
|
||||
|
||||
const favoriteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
nameSingular: 'favorite',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!favoriteObjectMetadata) {
|
||||
throw new Error('Favorite object metadata not found');
|
||||
}
|
||||
|
||||
const favoriteFields = await this.fieldMetadataRepository.find({
|
||||
where: {
|
||||
objectMetadataId: favoriteObjectMetadata.id,
|
||||
type: FieldMetadataType.RELATION,
|
||||
},
|
||||
});
|
||||
|
||||
const favoritesToDelete = await favoriteRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: favoriteFields.map((field) => ({
|
||||
[`${field.name}Id`]: In(deletedRecordIds),
|
||||
})),
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (favoritesToDelete.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const favoriteIdsToDelete = favoritesToDelete.map(
|
||||
(favorite) => favorite.id,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const favoriteRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<FavoriteWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'favorite',
|
||||
);
|
||||
|
||||
const batches: string[][] = [];
|
||||
const favoriteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
nameSingular: 'favorite',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < favoriteIdsToDelete.length;
|
||||
i += FAVORITE_DELETION_BATCH_SIZE
|
||||
) {
|
||||
batches.push(
|
||||
favoriteIdsToDelete.slice(i, i + FAVORITE_DELETION_BATCH_SIZE),
|
||||
);
|
||||
}
|
||||
if (!favoriteObjectMetadata) {
|
||||
throw new Error('Favorite object metadata not found');
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
await favoriteRepository.delete(batch);
|
||||
}
|
||||
},
|
||||
);
|
||||
const favoriteFields = await this.fieldMetadataRepository.find({
|
||||
where: {
|
||||
objectMetadataId: favoriteObjectMetadata.id,
|
||||
type: FieldMetadataType.RELATION,
|
||||
},
|
||||
});
|
||||
|
||||
const favoritesToDelete = await favoriteRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: favoriteFields.map((field) => ({
|
||||
[`${field.name}Id`]: In(deletedRecordIds),
|
||||
})),
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (favoritesToDelete.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const favoriteIdsToDelete = favoritesToDelete.map(
|
||||
(favorite) => favorite.id,
|
||||
);
|
||||
|
||||
const batches: string[][] = [];
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < favoriteIdsToDelete.length;
|
||||
i += FAVORITE_DELETION_BATCH_SIZE
|
||||
) {
|
||||
batches.push(
|
||||
favoriteIdsToDelete.slice(i, i + FAVORITE_DELETION_BATCH_SIZE),
|
||||
);
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
await favoriteRepository.delete(batch);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,36 +210,32 @@ export class MatchParticipantService<
|
||||
}: MatchParticipantsForWorkspaceMembersArgs) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const participantRepository = await this.getParticipantRepository(
|
||||
workspaceId,
|
||||
objectMetadataName,
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const participantRepository = await this.getParticipantRepository(
|
||||
workspaceId,
|
||||
objectMetadataName,
|
||||
);
|
||||
|
||||
const participants = await participantRepository.find({
|
||||
where: {
|
||||
workspaceMemberId: In(participantMatching.workspaceMemberIds),
|
||||
},
|
||||
});
|
||||
const participants = await participantRepository.find({
|
||||
where: {
|
||||
workspaceMemberId: In(participantMatching.workspaceMemberIds),
|
||||
},
|
||||
});
|
||||
|
||||
const tobeRematchedParticipants = participants.map((participant) => {
|
||||
return {
|
||||
...participant,
|
||||
workspaceMemberId: null,
|
||||
};
|
||||
});
|
||||
const tobeRematchedParticipants = participants.map((participant) => {
|
||||
return {
|
||||
...participant,
|
||||
workspaceMemberId: null,
|
||||
};
|
||||
});
|
||||
|
||||
await this.matchParticipants({
|
||||
matchWith: 'workspaceMemberOnly',
|
||||
participants:
|
||||
tobeRematchedParticipants as ParticipantWorkspaceEntity[],
|
||||
objectMetadataName,
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
await this.matchParticipants({
|
||||
matchWith: 'workspaceMemberOnly',
|
||||
participants: tobeRematchedParticipants as ParticipantWorkspaceEntity[],
|
||||
objectMetadataName,
|
||||
workspaceId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async matchParticipantsForPeople({
|
||||
@@ -249,56 +245,53 @@ export class MatchParticipantService<
|
||||
}: MatchParticipantsForPeopleArgs) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const participantRepository = await this.getParticipantRepository(
|
||||
workspaceId,
|
||||
objectMetadataName,
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const participantRepository = await this.getParticipantRepository(
|
||||
workspaceId,
|
||||
objectMetadataName,
|
||||
);
|
||||
|
||||
let participantsMatchingPersonEmails: ParticipantWorkspaceEntity[] = [];
|
||||
let participantsMatchingPersonId: ParticipantWorkspaceEntity[] = [];
|
||||
let participantsMatchingPersonEmails: ParticipantWorkspaceEntity[] = [];
|
||||
let participantsMatchingPersonId: ParticipantWorkspaceEntity[] = [];
|
||||
|
||||
if (participantMatching.personIds.length > 0) {
|
||||
participantsMatchingPersonId = (await participantRepository.find({
|
||||
where: {
|
||||
personId: In(participantMatching.personIds),
|
||||
},
|
||||
})) as ParticipantWorkspaceEntity[];
|
||||
}
|
||||
|
||||
if (participantMatching.personEmails.length > 0) {
|
||||
participantsMatchingPersonEmails = (await participantRepository.find({
|
||||
where: {
|
||||
handle: In(participantMatching.personEmails),
|
||||
},
|
||||
})) as ParticipantWorkspaceEntity[];
|
||||
}
|
||||
|
||||
const uniqueParticipants = [
|
||||
...new Set([
|
||||
...participantsMatchingPersonId,
|
||||
...participantsMatchingPersonEmails,
|
||||
]),
|
||||
];
|
||||
|
||||
const tobeRematchedParticipants = uniqueParticipants.map(
|
||||
(participant) => {
|
||||
return {
|
||||
...participant,
|
||||
personId: null,
|
||||
};
|
||||
if (participantMatching.personIds.length > 0) {
|
||||
participantsMatchingPersonId = (await participantRepository.find({
|
||||
where: {
|
||||
personId: In(participantMatching.personIds),
|
||||
},
|
||||
);
|
||||
})) as ParticipantWorkspaceEntity[];
|
||||
}
|
||||
|
||||
await this.matchParticipants({
|
||||
matchWith: 'personOnly',
|
||||
participants: tobeRematchedParticipants,
|
||||
objectMetadataName,
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
if (participantMatching.personEmails.length > 0) {
|
||||
participantsMatchingPersonEmails = (await participantRepository.find({
|
||||
where: {
|
||||
handle: In(participantMatching.personEmails),
|
||||
},
|
||||
})) as ParticipantWorkspaceEntity[];
|
||||
}
|
||||
|
||||
const uniqueParticipants = [
|
||||
...new Set([
|
||||
...participantsMatchingPersonId,
|
||||
...participantsMatchingPersonEmails,
|
||||
]),
|
||||
];
|
||||
|
||||
const tobeRematchedParticipants = uniqueParticipants.map(
|
||||
(participant) => {
|
||||
return {
|
||||
...participant,
|
||||
personId: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
await this.matchParticipants({
|
||||
matchWith: 'personOnly',
|
||||
participants: tobeRematchedParticipants,
|
||||
objectMetadataName,
|
||||
workspaceId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+108
-111
@@ -36,132 +36,129 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const blocklistItemIds = data.events.map(
|
||||
(eventPayload) => eventPayload.recordId,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const blocklistItemIds = data.events.map(
|
||||
(eventPayload) => eventPayload.recordId,
|
||||
);
|
||||
|
||||
const blocklistRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<BlocklistWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'blocklist',
|
||||
);
|
||||
|
||||
const blocklistRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<BlocklistWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'blocklist',
|
||||
);
|
||||
const blocklist = await blocklistRepository.find({
|
||||
where: {
|
||||
id: Any(blocklistItemIds),
|
||||
},
|
||||
});
|
||||
|
||||
const blocklist = await blocklistRepository.find({
|
||||
where: {
|
||||
id: Any(blocklistItemIds),
|
||||
const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce(
|
||||
(acc, blocklistItem) => {
|
||||
const { handle, workspaceMemberId } = blocklistItem;
|
||||
|
||||
if (!acc.has(workspaceMemberId)) {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
|
||||
return acc;
|
||||
},
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) {
|
||||
const handles =
|
||||
handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId);
|
||||
|
||||
if (!handles) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rolesToDelete = [
|
||||
MessageParticipantRole.FROM,
|
||||
MessageParticipantRole.TO,
|
||||
] as const;
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce(
|
||||
(acc, blocklistItem) => {
|
||||
const { handle, workspaceMemberId } = blocklistItem;
|
||||
for (const messageChannel of messageChannels) {
|
||||
const messageChannelHandles = [messageChannel.handle];
|
||||
|
||||
if (!acc.has(workspaceMemberId)) {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
if (messageChannel.connectedAccount.handleAliases) {
|
||||
messageChannelHandles.push(
|
||||
...messageChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
const handleConditions = handles.map((handle) => {
|
||||
const isHandleDomain = handle.startsWith('@');
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
return isHandleDomain
|
||||
? {
|
||||
handle: And(
|
||||
Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)),
|
||||
Not(In(messageChannelHandles)),
|
||||
),
|
||||
role: In(rolesToDelete),
|
||||
}
|
||||
: { handle, role: In(rolesToDelete) };
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
const messageChannelMessageAssociationsToDelete =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
messageChannelId: messageChannel.id,
|
||||
message: {
|
||||
messageParticipants: handleConditions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) {
|
||||
const handles =
|
||||
handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId);
|
||||
|
||||
if (!handles) {
|
||||
if (messageChannelMessageAssociationsToDelete.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rolesToDelete = [
|
||||
MessageParticipantRole.FROM,
|
||||
MessageParticipantRole.TO,
|
||||
] as const;
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
const messageChannelHandles = [messageChannel.handle];
|
||||
|
||||
if (messageChannel.connectedAccount.handleAliases) {
|
||||
messageChannelHandles.push(
|
||||
...messageChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
}
|
||||
|
||||
const handleConditions = handles.map((handle) => {
|
||||
const isHandleDomain = handle.startsWith('@');
|
||||
|
||||
return isHandleDomain
|
||||
? {
|
||||
handle: And(
|
||||
Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)),
|
||||
Not(In(messageChannelHandles)),
|
||||
),
|
||||
role: In(rolesToDelete),
|
||||
}
|
||||
: { handle, role: In(rolesToDelete) };
|
||||
});
|
||||
|
||||
const messageChannelMessageAssociationsToDelete =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
messageChannelId: messageChannel.id,
|
||||
message: {
|
||||
messageParticipants: handleConditions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (messageChannelMessageAssociationsToDelete.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await messageChannelMessageAssociationRepository.delete(
|
||||
messageChannelMessageAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
}
|
||||
await messageChannelMessageAssociationRepository.delete(
|
||||
messageChannelMessageAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await this.threadCleanerService.cleanOrphanMessagesAndThreads(
|
||||
workspaceId,
|
||||
);
|
||||
},
|
||||
);
|
||||
await this.threadCleanerService.cleanOrphanMessagesAndThreads(
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-28
@@ -36,37 +36,32 @@ export class BlocklistReimportMessagesJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
syncStage: Not(
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
),
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
});
|
||||
syncStage: Not(MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING),
|
||||
},
|
||||
});
|
||||
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
}),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
+1
-1
@@ -28,7 +28,6 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
@@ -128,6 +127,7 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
|
||||
return messages;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,6 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
const systemAuthContext = buildSystemAuthContext(workspace.id);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
systemAuthContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
@@ -144,6 +143,7 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
|
||||
return payload;
|
||||
},
|
||||
systemAuthContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+163
-195
@@ -44,21 +44,18 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsMessagesImportPending(
|
||||
@@ -72,21 +69,18 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async resetAndMarkAsMessagesListFetchPending(
|
||||
@@ -105,37 +99,34 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
});
|
||||
|
||||
await messageFolderRepository.update(
|
||||
{ messageChannelId: In(messageChannelIds) },
|
||||
{
|
||||
syncCursor: '',
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.NONE,
|
||||
},
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
});
|
||||
|
||||
await messageFolderRepository.update(
|
||||
{ messageChannelId: In(messageChannelIds) },
|
||||
{
|
||||
syncCursor: '',
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.NONE,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.markAsMessagesListFetchPending(messageChannelIds, workspaceId);
|
||||
}
|
||||
@@ -150,20 +141,17 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsMessagesListFetchScheduled(
|
||||
@@ -176,22 +164,19 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsMessagesListFetchOngoing(
|
||||
@@ -204,21 +189,18 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
@@ -231,24 +213,21 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
key: MetricsKeys.MessageChannelSyncJobActive,
|
||||
@@ -266,20 +245,17 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsMessagesImportOngoing(
|
||||
@@ -292,21 +268,18 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async markAsFailed(
|
||||
@@ -322,64 +295,59 @@ export class MessageChannelSyncStatusService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.FAILED,
|
||||
syncStatus: syncStatus,
|
||||
});
|
||||
|
||||
const metricsKey =
|
||||
syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
? MetricsKeys.MessageChannelSyncJobFailedInsufficientPermissions
|
||||
: MetricsKeys.MessageChannelSyncJobFailedUnknown;
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
key: metricsKey,
|
||||
eventIds: messageChannelIds,
|
||||
});
|
||||
|
||||
if (
|
||||
syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
) {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.FAILED,
|
||||
syncStatus: syncStatus,
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(messageChannelIds) },
|
||||
});
|
||||
|
||||
const metricsKey =
|
||||
syncStatus ===
|
||||
MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
? MetricsKeys.MessageChannelSyncJobFailedInsufficientPermissions
|
||||
: MetricsKeys.MessageChannelSyncJobFailedUnknown;
|
||||
const connectedAccountIds = messageChannels.map(
|
||||
(messageChannel) => messageChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
key: metricsKey,
|
||||
eventIds: messageChannelIds,
|
||||
});
|
||||
await connectedAccountRepository.update(
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
syncStatus ===
|
||||
MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
) {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(messageChannelIds) },
|
||||
});
|
||||
|
||||
const connectedAccountIds = messageChannels.map(
|
||||
(messageChannel) => messageChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
},
|
||||
);
|
||||
|
||||
await this.addToAccountsToReconnect(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.addToAccountsToReconnect(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async addToAccountsToReconnect(
|
||||
|
||||
+34
-37
@@ -38,52 +38,49 @@ export class MessagingResetChannelCommand extends CommandRunner {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`No message channel ID provided, resetting all message channels in workspace ${workspaceId}`,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
...(isDefined(messageChannelId) ? { id: messageChannelId } : {}),
|
||||
},
|
||||
});
|
||||
this.logger.log(
|
||||
`No message channel ID provided, resetting all message channels in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.log(
|
||||
`No message channels found in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
...(isDefined(messageChannelId) ? { id: messageChannelId } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.log(
|
||||
`Found ${messageChannels.length} message channels to reset`,
|
||||
`No message channels found in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
await this.messagingMessageCleanerService.cleanOrphanMessagesAndThreads(
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully reset all ${messageChannels.length} message channels in workspace ${workspaceId}`,
|
||||
this.logger.log(
|
||||
`Found ${messageChannels.length} message channels to reset`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
},
|
||||
);
|
||||
await this.messagingMessageCleanerService.cleanOrphanMessagesAndThreads(
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully reset all ${messageChannels.length} message channels in workspace ${workspaceId}`,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@Option({
|
||||
|
||||
+171
-177
@@ -29,197 +29,191 @@ export class MessagingMessageCleanerService {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'message',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'message',
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
const messageThreadRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageThreadWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageThread',
|
||||
);
|
||||
const messageThreadRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageThreadWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageThread',
|
||||
);
|
||||
|
||||
const messageExternalIdsChunks = chunk(messageExternalIds, 500);
|
||||
const messageExternalIdsChunks = chunk(messageExternalIds, 500);
|
||||
|
||||
for (const messageExternalIdsChunk of messageExternalIdsChunks) {
|
||||
const messageChannelMessageAssociationsToDelete =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
messageExternalId: In(messageExternalIdsChunk),
|
||||
messageChannelId,
|
||||
},
|
||||
});
|
||||
|
||||
if (messageChannelMessageAssociationsToDelete.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await messageChannelMessageAssociationRepository.delete(
|
||||
messageChannelMessageAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${messageChannelMessageAssociationsToDelete.length} message channel message associations`,
|
||||
);
|
||||
|
||||
const orphanMessages = await messageRepository.find({
|
||||
for (const messageExternalIdsChunk of messageExternalIdsChunks) {
|
||||
const messageChannelMessageAssociationsToDelete =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
id: In(
|
||||
messageChannelMessageAssociationsToDelete.map(
|
||||
({ messageId }) => messageId,
|
||||
),
|
||||
),
|
||||
messageChannelMessageAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
messageExternalId: In(messageExternalIdsChunk),
|
||||
messageChannelId,
|
||||
},
|
||||
});
|
||||
|
||||
if (orphanMessages.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${orphanMessages.length} orphan messages`,
|
||||
);
|
||||
|
||||
await messageRepository.delete(orphanMessages.map(({ id }) => id));
|
||||
|
||||
const orphanMessageThreads = await messageThreadRepository.find({
|
||||
where: {
|
||||
id: In(
|
||||
orphanMessages.map(({ messageThreadId }) => messageThreadId),
|
||||
),
|
||||
messages: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (orphanMessageThreads.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${orphanMessageThreads.length} orphan message threads`,
|
||||
);
|
||||
|
||||
await messageThreadRepository.delete(
|
||||
orphanMessageThreads.map(({ id }) => id),
|
||||
);
|
||||
if (messageChannelMessageAssociationsToDelete.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await messageChannelMessageAssociationRepository.delete(
|
||||
messageChannelMessageAssociationsToDelete.map(({ id }) => id),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${messageChannelMessageAssociationsToDelete.length} message channel message associations`,
|
||||
);
|
||||
|
||||
const orphanMessages = await messageRepository.find({
|
||||
where: {
|
||||
id: In(
|
||||
messageChannelMessageAssociationsToDelete.map(
|
||||
({ messageId }) => messageId,
|
||||
),
|
||||
),
|
||||
messageChannelMessageAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (orphanMessages.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${orphanMessages.length} orphan messages`,
|
||||
);
|
||||
|
||||
await messageRepository.delete(orphanMessages.map(({ id }) => id));
|
||||
|
||||
const orphanMessageThreads = await messageThreadRepository.find({
|
||||
where: {
|
||||
id: In(
|
||||
orphanMessages.map(({ messageThreadId }) => messageThreadId),
|
||||
),
|
||||
messages: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (orphanMessageThreads.length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${orphanMessageThreads.length} orphan message threads`,
|
||||
);
|
||||
|
||||
await messageThreadRepository.delete(
|
||||
orphanMessageThreads.map(({ id }) => id),
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
public async cleanOrphanMessagesAndThreads(workspaceId: string) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageThreadRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageThreadWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageThread',
|
||||
);
|
||||
|
||||
const messageRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'message',
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
await workspaceDataSource.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
async (
|
||||
limit: number,
|
||||
offset: number,
|
||||
_workspaceId: string,
|
||||
transactionManager: WorkspaceEntityManager,
|
||||
) => {
|
||||
const nonAssociatedMessages = await messageRepository.find(
|
||||
{
|
||||
where: {
|
||||
messageChannelMessageAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
relations: ['messageChannelMessageAssociations'],
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return nonAssociatedMessages.map(({ id }) => id);
|
||||
},
|
||||
async (
|
||||
ids: string[],
|
||||
workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${ids.length} messages from message cleaner`,
|
||||
);
|
||||
await messageRepository.delete(ids, transactionManager);
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
async (
|
||||
limit: number,
|
||||
offset: number,
|
||||
_workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
const orphanThreads = await messageThreadRepository.find(
|
||||
{
|
||||
where: {
|
||||
messages: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return orphanThreads.map(({ id }) => id);
|
||||
},
|
||||
async (
|
||||
ids: string[],
|
||||
_workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
await messageThreadRepository.delete(ids, transactionManager);
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
},
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageThreadRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageThreadWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageThread',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const messageRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'message',
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
await workspaceDataSource.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
async (
|
||||
limit: number,
|
||||
offset: number,
|
||||
_workspaceId: string,
|
||||
transactionManager: WorkspaceEntityManager,
|
||||
) => {
|
||||
const nonAssociatedMessages = await messageRepository.find(
|
||||
{
|
||||
where: {
|
||||
messageChannelMessageAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
relations: ['messageChannelMessageAssociations'],
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return nonAssociatedMessages.map(({ id }) => id);
|
||||
},
|
||||
async (
|
||||
ids: string[],
|
||||
workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId} Deleting ${ids.length} messages from message cleaner`,
|
||||
);
|
||||
await messageRepository.delete(ids, transactionManager);
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
async (
|
||||
limit: number,
|
||||
offset: number,
|
||||
_workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
const orphanThreads = await messageThreadRepository.find(
|
||||
{
|
||||
where: {
|
||||
messages: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return orphanThreads.map(({ id }) => id);
|
||||
},
|
||||
async (
|
||||
ids: string[],
|
||||
_workspaceId: string,
|
||||
transactionManager?: WorkspaceEntityManager,
|
||||
) => {
|
||||
await messageThreadRepository.delete(ids, transactionManager);
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -126,7 +126,9 @@ describe('SyncMessageFoldersService', () => {
|
||||
useValue: {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_, callback) => callback()),
|
||||
.mockImplementation((callback: () => any, _authContext?: any) =>
|
||||
callback(),
|
||||
),
|
||||
getRepository: jest.fn().mockResolvedValue(mockRepository),
|
||||
getDataSourceForWorkspace: jest
|
||||
.fn()
|
||||
|
||||
+1
-1
@@ -133,7 +133,6 @@ export class SyncMessageFoldersService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
@@ -188,6 +187,7 @@ export class SyncMessageFoldersService {
|
||||
},
|
||||
);
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-52
@@ -51,63 +51,60 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (messageChannelId) {
|
||||
whereCondition.id = messageChannelId;
|
||||
}
|
||||
|
||||
const messageChannels =
|
||||
await messageChannelRepository.find(whereCondition);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
'No message channels found with MESSAGE_LIST_FETCH_PENDING status',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${messageChannels.length} message channel(s) to process`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await messageChannelRepository.update(messageChannel.id, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
messageChannelId: messageChannel.id,
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (messageChannelId) {
|
||||
whereCondition.id = messageChannelId;
|
||||
}
|
||||
|
||||
const messageChannels =
|
||||
await messageChannelRepository.find(whereCondition);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
'No message channels found with MESSAGE_LIST_FETCH_PENDING status',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${messageChannels.length} message channel(s) to process`,
|
||||
},
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await messageChannelRepository.update(messageChannel.id, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
messageChannelId: messageChannel.id,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Triggered fetch for message channel ${messageChannel.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully triggered ${messageChannels.length} message list fetch job(s)`,
|
||||
`Triggered fetch for message channel ${messageChannel.id}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully triggered ${messageChannels.length} message list fetch job(s)`,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@Option({
|
||||
|
||||
+62
-65
@@ -48,82 +48,79 @@ export class MessagingMessageListFetchJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch_job.error.message_channel_not_found',
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch_job.error.message_channel_not_found',
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
messageChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
messageChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch.started',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccount.id,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
await this.messagingMessageListFetchService.processMessageListFetch(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch.started',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccount.id,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
|
||||
await this.messagingMessageListFetchService.processMessageListFetch(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch.completed',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccount.id,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'message_list_fetch.completed',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccount.id,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
} catch (error) {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+48
-51
@@ -43,64 +43,61 @@ export class MessagingMessagesImportJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'messages_import.error.message_channel_not_found',
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'messages_import.error.message_channel_not_found',
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if (!messageChannel?.isSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!messageChannel?.isSyncEnabled) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
messageChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
messageChannel,
|
||||
messageChannel.connectedAccount,
|
||||
if (
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
messageChannel.throttleFailureCount,
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
true,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
messageChannel,
|
||||
messageChannel.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+50
-53
@@ -35,63 +35,60 @@ export class MessagingOngoingStaleJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (
|
||||
messageChannel.syncStageStartedAt &&
|
||||
isSyncStale(messageChannel.syncStageStartedAt)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (
|
||||
messageChannel.syncStageStartedAt &&
|
||||
isSyncStale(messageChannel.syncStageStartedAt)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
switch (messageChannel.syncStage) {
|
||||
case MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING:
|
||||
case MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGE_LIST_FETCH_PENDING`,
|
||||
);
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING:
|
||||
case MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGES_IMPORT_PENDING`,
|
||||
);
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (messageChannel.syncStage) {
|
||||
case MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING:
|
||||
case MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGE_LIST_FETCH_PENDING`,
|
||||
);
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING:
|
||||
case MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED:
|
||||
this.logger.log(
|
||||
`Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGES_IMPORT_PENDING`,
|
||||
);
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-31
@@ -31,40 +31,37 @@ export class MessagingRelaunchFailedMessageChannelJob {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!messageChannel ||
|
||||
messageChannel.syncStage !== MessageChannelSyncStage.FAILED ||
|
||||
messageChannel.syncStatus !== MessageChannelSyncStatus.FAILED_UNKNOWN
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!messageChannel ||
|
||||
messageChannel.syncStage !== MessageChannelSyncStage.FAILED ||
|
||||
messageChannel.syncStatus !== MessageChannelSyncStatus.FAILED_UNKNOWN
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await messageChannelRepository.update(messageChannelId, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(messageChannelId, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -213,7 +213,7 @@ describe('MessagingMessageListFetchService', () => {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
getRepository: jest.fn().mockImplementation((workspaceId, name) => {
|
||||
if (name === 'messageChannel') {
|
||||
return {
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
}),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+46
-49
@@ -19,55 +19,52 @@ export class MessagingCursorService {
|
||||
) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
const folderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
const folderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
if (!folderId) {
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncCursor:
|
||||
!messageChannel.syncCursor ||
|
||||
nextSyncCursor > messageChannel.syncCursor
|
||||
? nextSyncCursor
|
||||
: messageChannel.syncCursor,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await folderRepository.update(
|
||||
{
|
||||
id: folderId,
|
||||
},
|
||||
{
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!folderId) {
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncCursor:
|
||||
!messageChannel.syncCursor ||
|
||||
nextSyncCursor > messageChannel.syncCursor
|
||||
? nextSyncCursor
|
||||
: messageChannel.syncCursor,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await folderRepository.update(
|
||||
{
|
||||
id: folderId,
|
||||
},
|
||||
{
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,6 @@ export class MessagingDeleteGroupEmailMessagesService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
@@ -146,6 +145,7 @@ export class MessagingDeleteGroupEmailMessagesService {
|
||||
|
||||
return totalDeletedCount;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-17
@@ -150,24 +150,21 @@ export class MessageImportExceptionHandlerService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.increment(
|
||||
{ id: messageChannel.id },
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await messageChannelRepository.increment(
|
||||
{ id: messageChannel.id },
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
switch (syncStep) {
|
||||
case MessageImportSyncStep.MESSAGE_LIST_FETCH:
|
||||
|
||||
+212
-216
@@ -58,254 +58,250 @@ export class MessagingMessageListFetchService {
|
||||
) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
const pendingGroupEmailActionsProcessed =
|
||||
await this.processPendingGroupEmailActions(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const pendingFolderActionsProcessed =
|
||||
await this.processPendingFolderActions(messageChannel, workspaceId);
|
||||
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchOngoing(
|
||||
[messageChannel.id],
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
const pendingGroupEmailActionsProcessed =
|
||||
await this.processPendingGroupEmailActions(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${messageChannel.id} Processing message list fetch`,
|
||||
const pendingFolderActionsProcessed =
|
||||
await this.processPendingFolderActions(messageChannel, workspaceId);
|
||||
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchOngoing(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${messageChannel.id} Processing message list fetch`,
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
const freshMessageChannel =
|
||||
pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed
|
||||
? await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannel.id,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
})
|
||||
: messageChannel;
|
||||
|
||||
if (!isDefined(freshMessageChannel)) {
|
||||
this.logger.error(
|
||||
`error processing message list fetch: messageChannelId: ${messageChannel.id} Message channel not found`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken } =
|
||||
await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount: freshMessageChannel.connectedAccount,
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const freshMessageChannel =
|
||||
pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed
|
||||
? await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannel.id,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
})
|
||||
: messageChannel;
|
||||
|
||||
if (!isDefined(freshMessageChannel)) {
|
||||
this.logger.error(
|
||||
`error processing message list fetch: messageChannelId: ${messageChannel.id} Message channel not found`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken } =
|
||||
await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount: freshMessageChannel.connectedAccount,
|
||||
workspaceId,
|
||||
messageChannelId: freshMessageChannel.id,
|
||||
},
|
||||
);
|
||||
|
||||
const messageChannelWithFreshTokens = {
|
||||
...freshMessageChannel,
|
||||
connectedAccount: {
|
||||
...freshMessageChannel.connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
messageChannelId: freshMessageChannel.id,
|
||||
},
|
||||
};
|
||||
);
|
||||
|
||||
const messageFolders =
|
||||
await this.syncMessageFoldersService.syncMessageFolders({
|
||||
messageChannel: messageChannelWithFreshTokens,
|
||||
workspaceId,
|
||||
const messageChannelWithFreshTokens = {
|
||||
...freshMessageChannel,
|
||||
connectedAccount: {
|
||||
...freshMessageChannel.connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
},
|
||||
};
|
||||
|
||||
const messageFolders =
|
||||
await this.syncMessageFoldersService.syncMessageFolders({
|
||||
messageChannel: messageChannelWithFreshTokens,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const messageFoldersToSync = messageFolders.filter(
|
||||
(folder) =>
|
||||
folder.pendingSyncAction === MessageFolderPendingSyncAction.NONE,
|
||||
);
|
||||
|
||||
const messageLists =
|
||||
await this.messagingGetMessageListService.getMessageLists(
|
||||
messageChannelWithFreshTokens,
|
||||
messageFoldersToSync,
|
||||
);
|
||||
|
||||
await this.cacheStorage.del(
|
||||
`messages-to-import:${workspaceId}:${freshMessageChannel.id}`,
|
||||
);
|
||||
|
||||
const messageExternalIds = messageLists.flatMap(
|
||||
(messageList) => messageList.messageExternalIds,
|
||||
);
|
||||
|
||||
const messageExternalIdsToDelete = messageLists.flatMap(
|
||||
(messageList) => messageList.messageExternalIdsToDelete,
|
||||
);
|
||||
|
||||
const isFullSync =
|
||||
messageLists.every(
|
||||
(messageList) => !isNonEmptyString(messageList.previousSyncCursor),
|
||||
) && !isNonEmptyString(freshMessageChannel.syncCursor);
|
||||
|
||||
let totalMessagesToImportCount = 0;
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Is full sync: ${isFullSync} and toImportCount: ${messageExternalIds.length}, toDeleteCount: ${messageExternalIdsToDelete.length}, cursors: ${messageLists.map(
|
||||
(messageList) => {
|
||||
messageList.nextSyncCursor;
|
||||
},
|
||||
)}`,
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
const messageExternalIdsChunks = chunk(messageExternalIds, 200);
|
||||
|
||||
for (const [
|
||||
index,
|
||||
messageExternalIdsChunk,
|
||||
] of messageExternalIdsChunks.entries()) {
|
||||
const existingMessageChannelMessageAssociations =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
messageChannelId: freshMessageChannel.id,
|
||||
messageExternalId: In(messageExternalIdsChunk),
|
||||
},
|
||||
});
|
||||
|
||||
const messageFoldersToSync = messageFolders.filter(
|
||||
(folder) =>
|
||||
folder.pendingSyncAction === MessageFolderPendingSyncAction.NONE,
|
||||
);
|
||||
|
||||
const messageLists =
|
||||
await this.messagingGetMessageListService.getMessageLists(
|
||||
messageChannelWithFreshTokens,
|
||||
messageFoldersToSync,
|
||||
);
|
||||
|
||||
await this.cacheStorage.del(
|
||||
`messages-to-import:${workspaceId}:${freshMessageChannel.id}`,
|
||||
);
|
||||
|
||||
const messageExternalIds = messageLists.flatMap(
|
||||
(messageList) => messageList.messageExternalIds,
|
||||
);
|
||||
|
||||
const messageExternalIdsToDelete = messageLists.flatMap(
|
||||
(messageList) => messageList.messageExternalIdsToDelete,
|
||||
);
|
||||
|
||||
const isFullSync =
|
||||
messageLists.every(
|
||||
(messageList) =>
|
||||
!isNonEmptyString(messageList.previousSyncCursor),
|
||||
) && !isNonEmptyString(freshMessageChannel.syncCursor);
|
||||
|
||||
let totalMessagesToImportCount = 0;
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Is full sync: ${isFullSync} and toImportCount: ${messageExternalIds.length}, toDeleteCount: ${messageExternalIdsToDelete.length}, cursors: ${messageLists.map(
|
||||
(messageList) => {
|
||||
messageList.nextSyncCursor;
|
||||
},
|
||||
)}`,
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
const messageExternalIdsChunks = chunk(messageExternalIds, 200);
|
||||
|
||||
for (const [
|
||||
index,
|
||||
messageExternalIdsChunk,
|
||||
] of messageExternalIdsChunks.entries()) {
|
||||
const existingMessageChannelMessageAssociations =
|
||||
await messageChannelMessageAssociationRepository.find({
|
||||
where: {
|
||||
messageChannelId: freshMessageChannel.id,
|
||||
messageExternalId: In(messageExternalIdsChunk),
|
||||
},
|
||||
});
|
||||
|
||||
const existingMessageChannelMessageAssociationsExternalIds =
|
||||
existingMessageChannelMessageAssociations.map(
|
||||
(messageChannelMessageAssociation) =>
|
||||
messageChannelMessageAssociation.messageExternalId,
|
||||
);
|
||||
|
||||
const messageExternalIdsToImport = messageExternalIdsChunk.filter(
|
||||
(messageExternalId) =>
|
||||
!existingMessageChannelMessageAssociationsExternalIds.includes(
|
||||
messageExternalId,
|
||||
),
|
||||
);
|
||||
|
||||
if (messageExternalIdsToImport.length) {
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Adding ${messageExternalIdsToImport.length} message external ids to import in batch ${index + 1}`,
|
||||
);
|
||||
|
||||
totalMessagesToImportCount += messageExternalIdsToImport.length;
|
||||
|
||||
await this.cacheStorage.setAdd(
|
||||
`messages-to-import:${workspaceId}:${messageChannelWithFreshTokens.id}`,
|
||||
messageExternalIdsToImport,
|
||||
ONE_WEEK_IN_MILLISECONDS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const messageList of messageLists) {
|
||||
const { nextSyncCursor, folderId } = messageList;
|
||||
|
||||
await this.messagingCursorService.updateCursor(
|
||||
messageChannelWithFreshTokens,
|
||||
nextSyncCursor,
|
||||
workspaceId,
|
||||
folderId,
|
||||
);
|
||||
}
|
||||
|
||||
const fullSyncMessageChannelMessageAssociationsToDelete = isFullSync
|
||||
? await this.computeFullSyncMessageChannelMessageAssociationsToDelete(
|
||||
freshMessageChannel,
|
||||
messageExternalIds,
|
||||
workspaceId,
|
||||
)
|
||||
: [];
|
||||
|
||||
const allMessageExternalIdsToDelete = [
|
||||
...messageExternalIdsToDelete,
|
||||
...fullSyncMessageChannelMessageAssociationsToDelete.map(
|
||||
const existingMessageChannelMessageAssociationsExternalIds =
|
||||
existingMessageChannelMessageAssociations.map(
|
||||
(messageChannelMessageAssociation) =>
|
||||
messageChannelMessageAssociation.messageExternalId,
|
||||
),
|
||||
];
|
||||
);
|
||||
|
||||
if (allMessageExternalIdsToDelete.length) {
|
||||
const messageExternalIdsToImport = messageExternalIdsChunk.filter(
|
||||
(messageExternalId) =>
|
||||
!existingMessageChannelMessageAssociationsExternalIds.includes(
|
||||
messageExternalId,
|
||||
),
|
||||
);
|
||||
|
||||
if (messageExternalIdsToImport.length) {
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Deleting ${allMessageExternalIdsToDelete.length} message channel message associations`,
|
||||
`messageChannelId: ${freshMessageChannel.id} Adding ${messageExternalIdsToImport.length} message external ids to import in batch ${index + 1}`,
|
||||
);
|
||||
|
||||
const toDeleteChunks = chunk(allMessageExternalIdsToDelete, 200);
|
||||
totalMessagesToImportCount += messageExternalIdsToImport.length;
|
||||
|
||||
for (const [index, toDeleteChunk] of toDeleteChunks.entries()) {
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Deleting ${toDeleteChunk.length} message channel message associations in batch ${index + 1}`,
|
||||
);
|
||||
|
||||
await this.messagingMessageCleanerService.deleteMessagesChannelMessageAssociationsAndRelatedOrphans(
|
||||
{
|
||||
workspaceId,
|
||||
messageExternalIds: toDeleteChunk.filter(
|
||||
(messageExternalId) => isNonEmptyString(messageExternalId),
|
||||
),
|
||||
messageChannelId: messageChannelWithFreshTokens.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
await this.cacheStorage.setAdd(
|
||||
`messages-to-import:${workspaceId}:${messageChannelWithFreshTokens.id}`,
|
||||
messageExternalIdsToImport,
|
||||
ONE_WEEK_IN_MILLISECONDS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Total messages to import count: ${totalMessagesToImportCount}`,
|
||||
for (const messageList of messageLists) {
|
||||
const { nextSyncCursor, folderId } = messageList;
|
||||
|
||||
await this.messagingCursorService.updateCursor(
|
||||
messageChannelWithFreshTokens,
|
||||
nextSyncCursor,
|
||||
workspaceId,
|
||||
folderId,
|
||||
);
|
||||
}
|
||||
|
||||
if (totalMessagesToImportCount === 0) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannelWithFreshTokens.id],
|
||||
const fullSyncMessageChannelMessageAssociationsToDelete = isFullSync
|
||||
? await this.computeFullSyncMessageChannelMessageAssociationsToDelete(
|
||||
freshMessageChannel,
|
||||
messageExternalIds,
|
||||
workspaceId,
|
||||
);
|
||||
)
|
||||
: [];
|
||||
|
||||
return;
|
||||
}
|
||||
const allMessageExternalIdsToDelete = [
|
||||
...messageExternalIdsToDelete,
|
||||
...fullSyncMessageChannelMessageAssociationsToDelete.map(
|
||||
(messageChannelMessageAssociation) =>
|
||||
messageChannelMessageAssociation.messageExternalId,
|
||||
),
|
||||
];
|
||||
|
||||
if (allMessageExternalIdsToDelete.length) {
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Scheduling direct messages import`,
|
||||
`messageChannelId: ${freshMessageChannel.id} Deleting ${allMessageExternalIdsToDelete.length} message channel message associations`,
|
||||
);
|
||||
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportScheduled(
|
||||
const toDeleteChunks = chunk(allMessageExternalIdsToDelete, 200);
|
||||
|
||||
for (const [index, toDeleteChunk] of toDeleteChunks.entries()) {
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Deleting ${toDeleteChunk.length} message channel message associations in batch ${index + 1}`,
|
||||
);
|
||||
|
||||
await this.messagingMessageCleanerService.deleteMessagesChannelMessageAssociationsAndRelatedOrphans(
|
||||
{
|
||||
workspaceId,
|
||||
messageExternalIds: toDeleteChunk.filter((messageExternalId) =>
|
||||
isNonEmptyString(messageExternalId),
|
||||
),
|
||||
messageChannelId: messageChannelWithFreshTokens.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Total messages to import count: ${totalMessagesToImportCount}`,
|
||||
);
|
||||
|
||||
if (totalMessagesToImportCount === 0) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannelWithFreshTokens.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
{
|
||||
...messageChannelWithFreshTokens,
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
},
|
||||
messageChannelWithFreshTokens.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`messageChannelId: ${freshMessageChannel.id} Scheduling direct messages import`,
|
||||
);
|
||||
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportScheduled(
|
||||
[messageChannelWithFreshTokens.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
{
|
||||
...messageChannelWithFreshTokens,
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
},
|
||||
messageChannelWithFreshTokens.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async processPendingGroupEmailActions(
|
||||
|
||||
+1
-1
@@ -55,7 +55,6 @@ export class MessagingMessageService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
@@ -245,6 +244,7 @@ export class MessagingMessageService {
|
||||
messageExternalIdsAndIdsMap,
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+147
-152
@@ -60,169 +60,164 @@ export class MessagingMessagesImportService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
if (
|
||||
messageChannel.syncStage !==
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'messages_import.started',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccountId,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
await this.messagingMonitoringService.track({
|
||||
eventName: 'messages_import.started',
|
||||
workspaceId,
|
||||
connectedAccountId: messageChannel.connectedAccountId,
|
||||
messageChannelId: messageChannel.id,
|
||||
});
|
||||
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportOngoing(
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportOngoing(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { accessToken, refreshToken } =
|
||||
await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
|
||||
const connectedAccountWithFreshTokens = {
|
||||
...connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
await this.emailAliasManagerService.refreshHandleAliases(
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
messageIdsToFetch = await this.cacheStorage.setPop(
|
||||
`messages-to-import:${workspaceId}:${messageChannel.id}`,
|
||||
MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE,
|
||||
);
|
||||
|
||||
if (!messageIdsToFetch?.length) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { accessToken, refreshToken } =
|
||||
await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication(
|
||||
{
|
||||
connectedAccount,
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
|
||||
const connectedAccountWithFreshTokens = {
|
||||
...connectedAccount,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
await this.emailAliasManagerService.refreshHandleAliases(
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
messageIdsToFetch = await this.cacheStorage.setPop(
|
||||
`messages-to-import:${workspaceId}:${messageChannel.id}`,
|
||||
MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE,
|
||||
);
|
||||
|
||||
if (!messageIdsToFetch?.length) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return await this.trackMessageImportCompleted(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const allMessages =
|
||||
await this.messagingGetMessagesService.getMessages(
|
||||
messageIdsToFetch,
|
||||
connectedAccountWithFreshTokens,
|
||||
);
|
||||
|
||||
const blocklist =
|
||||
await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
connectedAccountWithFreshTokens.accountOwnerId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(messageChannel.handle)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(connectedAccountWithFreshTokens.handleAliases)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const messagesToSave = filterEmails(
|
||||
messageChannel.handle,
|
||||
[...connectedAccountWithFreshTokens.handleAliases.split(',')],
|
||||
allMessages,
|
||||
blocklist
|
||||
.map((blocklistItem) => blocklistItem.handle)
|
||||
.filter(isDefined),
|
||||
messageChannel.excludeGroupEmails,
|
||||
);
|
||||
|
||||
if (messagesToSave.length > 0) {
|
||||
await this.saveMessagesAndEnqueueContactCreationService.saveMessagesAndEnqueueContactCreation(
|
||||
messagesToSave,
|
||||
messageChannel,
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
messageIdsToFetch.length <
|
||||
MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} else {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
);
|
||||
|
||||
return await this.trackMessageImportCompleted(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error (${error.code}) importing messages for workspace ${workspaceId.slice(0, 8)} and account ${connectedAccount.id.slice(0, 8)}: ${error.message} - ${error.body}`,
|
||||
);
|
||||
await this.cacheStorage.setAdd(
|
||||
`messages-to-import:${workspaceId}:${messageChannel.id}`,
|
||||
messageIdsToFetch,
|
||||
);
|
||||
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGES_IMPORT_ONGOING,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return await this.trackMessageImportCompleted(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const allMessages = await this.messagingGetMessagesService.getMessages(
|
||||
messageIdsToFetch,
|
||||
connectedAccountWithFreshTokens,
|
||||
);
|
||||
|
||||
const blocklist = await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
connectedAccountWithFreshTokens.accountOwnerId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(messageChannel.handle)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(connectedAccountWithFreshTokens.handleAliases)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const messagesToSave = filterEmails(
|
||||
messageChannel.handle,
|
||||
[...connectedAccountWithFreshTokens.handleAliases.split(',')],
|
||||
allMessages,
|
||||
blocklist
|
||||
.map((blocklistItem) => blocklistItem.handle)
|
||||
.filter(isDefined),
|
||||
messageChannel.excludeGroupEmails,
|
||||
);
|
||||
|
||||
if (messagesToSave.length > 0) {
|
||||
await this.saveMessagesAndEnqueueContactCreationService.saveMessagesAndEnqueueContactCreation(
|
||||
messagesToSave,
|
||||
messageChannel,
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
messageIdsToFetch.length <
|
||||
MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
} else {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
);
|
||||
|
||||
return await this.trackMessageImportCompleted(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error (${error.code}) importing messages for workspace ${workspaceId.slice(0, 8)} and account ${connectedAccount.id.slice(0, 8)}: ${error.message} - ${error.body}`,
|
||||
);
|
||||
await this.cacheStorage.setAdd(
|
||||
`messages-to-import:${workspaceId}:${messageChannel.id}`,
|
||||
messageIdsToFetch,
|
||||
);
|
||||
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGES_IMPORT_ONGOING,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return await this.trackMessageImportCompleted(
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async trackMessageImportCompleted(
|
||||
|
||||
+1
-1
@@ -90,7 +90,6 @@ export class MessagingProcessFolderActionsService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
@@ -128,6 +127,7 @@ export class MessagingProcessFolderActionsService {
|
||||
},
|
||||
);
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+62
-68
@@ -30,25 +30,22 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{ pendingGroupEmailsAction },
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{ pendingGroupEmailsAction },
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
async processGroupEmailActions(
|
||||
@@ -70,61 +67,58 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
try {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
try {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
switch (pendingGroupEmailsAction) {
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION:
|
||||
await this.handleGroupEmailsDeletion(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
switch (pendingGroupEmailsAction) {
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION:
|
||||
await this.handleGroupEmailsDeletion(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
break;
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT:
|
||||
await this.handleGroupEmailsImport(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
pendingGroupEmailsAction:
|
||||
MessageChannelPendingGroupEmailsAction.NONE,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw error;
|
||||
break;
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT:
|
||||
await this.handleGroupEmailsImport(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
pendingGroupEmailsAction:
|
||||
MessageChannelPendingGroupEmailsAction.NONE,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async handleGroupEmailsDeletion(
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
|
||||
.mockResolvedValue(datasourceInstance),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
+1
-1
@@ -47,7 +47,6 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
|
||||
const participantsWithMessageId =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
@@ -117,6 +116,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
},
|
||||
);
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
if (
|
||||
|
||||
+49
-52
@@ -23,60 +23,57 @@ export class MessagingMessageParticipantService {
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageParticipantRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageParticipantWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageParticipant',
|
||||
);
|
||||
|
||||
const existingParticipantsBasedOnMessageIds =
|
||||
await messageParticipantRepository.find({
|
||||
where: {
|
||||
messageId: In(
|
||||
participants.map((participant) => participant.messageId),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const participantsToCreate: Pick<
|
||||
MessageParticipantWorkspaceEntity,
|
||||
'messageId' | 'handle' | 'displayName' | 'role'
|
||||
>[] = participants
|
||||
.filter(
|
||||
(participant) =>
|
||||
!existingParticipantsBasedOnMessageIds.find(
|
||||
(existingParticipant) =>
|
||||
existingParticipant.messageId === participant.messageId &&
|
||||
existingParticipant.handle === participant.handle &&
|
||||
existingParticipant.displayName === participant.displayName &&
|
||||
existingParticipant.role === participant.role,
|
||||
),
|
||||
)
|
||||
.map((participant) => {
|
||||
return {
|
||||
messageId: participant.messageId,
|
||||
handle: participant.handle,
|
||||
displayName: participant.displayName,
|
||||
role: participant.role,
|
||||
};
|
||||
});
|
||||
|
||||
const createdParticipants = await messageParticipantRepository.insert(
|
||||
participantsToCreate,
|
||||
transactionManager,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageParticipantRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageParticipantWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageParticipant',
|
||||
);
|
||||
|
||||
await this.matchParticipantService.matchParticipants({
|
||||
participants: createdParticipants.raw ?? [],
|
||||
objectMetadataName: 'messageParticipant',
|
||||
transactionManager,
|
||||
matchWith: 'workspaceMemberAndPerson',
|
||||
workspaceId,
|
||||
const existingParticipantsBasedOnMessageIds =
|
||||
await messageParticipantRepository.find({
|
||||
where: {
|
||||
messageId: In(
|
||||
participants.map((participant) => participant.messageId),
|
||||
),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const participantsToCreate: Pick<
|
||||
MessageParticipantWorkspaceEntity,
|
||||
'messageId' | 'handle' | 'displayName' | 'role'
|
||||
>[] = participants
|
||||
.filter(
|
||||
(participant) =>
|
||||
!existingParticipantsBasedOnMessageIds.find(
|
||||
(existingParticipant) =>
|
||||
existingParticipant.messageId === participant.messageId &&
|
||||
existingParticipant.handle === participant.handle &&
|
||||
existingParticipant.displayName === participant.displayName &&
|
||||
existingParticipant.role === participant.role,
|
||||
),
|
||||
)
|
||||
.map((participant) => {
|
||||
return {
|
||||
messageId: participant.messageId,
|
||||
handle: participant.handle,
|
||||
displayName: participant.displayName,
|
||||
role: participant.role,
|
||||
};
|
||||
});
|
||||
|
||||
const createdParticipants = await messageParticipantRepository.insert(
|
||||
participantsToCreate,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.matchParticipantService.matchParticipants({
|
||||
participants: createdParticipants.raw ?? [],
|
||||
objectMetadataName: 'messageParticipant',
|
||||
transactionManager,
|
||||
matchWith: 'workspaceMemberAndPerson',
|
||||
workspaceId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -50,7 +50,6 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
const authContext = buildSystemAuthContext(activeWorkspace.id);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
@@ -76,6 +75,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
});
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
} catch (error) {
|
||||
this.exceptionHandlerService.captureExceptions([error], {
|
||||
|
||||
+20
-26
@@ -29,20 +29,17 @@ export class NotePostQueryHookService {
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const noteTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<NoteTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'noteTarget',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const noteTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<NoteTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'noteTarget',
|
||||
);
|
||||
|
||||
await noteTargetRepository.softDelete({
|
||||
noteId: In(payload.map((note) => note.id)),
|
||||
});
|
||||
},
|
||||
);
|
||||
await noteTargetRepository.softDelete({
|
||||
noteId: In(payload.map((note) => note.id)),
|
||||
});
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
|
||||
async handleNoteTargetsRestore(
|
||||
@@ -57,19 +54,16 @@ export class NotePostQueryHookService {
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const noteTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<NoteTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'noteTarget',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const noteTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<NoteTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'noteTarget',
|
||||
);
|
||||
|
||||
await noteTargetRepository.restore({
|
||||
noteId: In(payload.map((note) => note.id)),
|
||||
});
|
||||
},
|
||||
);
|
||||
await noteTargetRepository.restore({
|
||||
noteId: In(payload.map((note) => note.id)),
|
||||
});
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-26
@@ -29,20 +29,17 @@ export class TaskPostQueryHookService {
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const taskTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<TaskTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'taskTarget',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const taskTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<TaskTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'taskTarget',
|
||||
);
|
||||
|
||||
await taskTargetRepository.softDelete({
|
||||
taskId: In(payload.map((task) => task.id)),
|
||||
});
|
||||
},
|
||||
);
|
||||
await taskTargetRepository.softDelete({
|
||||
taskId: In(payload.map((task) => task.id)),
|
||||
});
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
|
||||
async handleTaskTargetsRestore(
|
||||
@@ -57,19 +54,16 @@ export class TaskPostQueryHookService {
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const taskTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<TaskTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'taskTarget',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const taskTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<TaskTargetWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'taskTarget',
|
||||
);
|
||||
|
||||
await taskTargetRepository.restore({
|
||||
taskId: In(payload.map((task) => task.id)),
|
||||
});
|
||||
},
|
||||
);
|
||||
await taskTargetRepository.restore({
|
||||
taskId: In(payload.map((task) => task.id)),
|
||||
});
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-27
@@ -38,38 +38,35 @@ export class UpsertTimelineActivityFromInternalEvent {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceEventBatch.workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceEventBatch.workspaceId,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceEventBatch.workspaceId,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const userIds = workspaceEventBatch.events
|
||||
.map((event) => event.userId)
|
||||
.filter(isDefined);
|
||||
const userIds = workspaceEventBatch.events
|
||||
.map((event) => event.userId)
|
||||
.filter(isDefined);
|
||||
|
||||
const workspaceMembers = await workspaceMemberRepository.findBy({
|
||||
userId: In(userIds),
|
||||
});
|
||||
const workspaceMembers = await workspaceMemberRepository.findBy({
|
||||
userId: In(userIds),
|
||||
});
|
||||
|
||||
for (const eventData of workspaceEventBatch.events) {
|
||||
const workspaceMember = workspaceMembers.find(
|
||||
(workspaceMember) => workspaceMember.userId === eventData.userId,
|
||||
);
|
||||
for (const eventData of workspaceEventBatch.events) {
|
||||
const workspaceMember = workspaceMembers.find(
|
||||
(workspaceMember) => workspaceMember.userId === eventData.userId,
|
||||
);
|
||||
|
||||
if (eventData.userId && workspaceMember) {
|
||||
eventData.workspaceMemberId = workspaceMember.id;
|
||||
}
|
||||
if (eventData.userId && workspaceMember) {
|
||||
eventData.workspaceMemberId = workspaceMember.id;
|
||||
}
|
||||
}
|
||||
|
||||
await this.timelineActivityService.upsertEvents(workspaceEventBatch);
|
||||
},
|
||||
);
|
||||
await this.timelineActivityService.upsertEvents(workspaceEventBatch);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+62
-67
@@ -33,79 +33,74 @@ export class TimelineActivityRepository {
|
||||
}: TimelineActivityPayloadWorkspaceIdAndObjectSingularName) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const recentTimelineActivities =
|
||||
await this.findRecentTimelineActivities({
|
||||
objectSingularName,
|
||||
workspaceId,
|
||||
payloads,
|
||||
isFeatureFlagTimelineActivityMigrated,
|
||||
});
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const recentTimelineActivities = await this.findRecentTimelineActivities({
|
||||
objectSingularName,
|
||||
workspaceId,
|
||||
payloads,
|
||||
isFeatureFlagTimelineActivityMigrated,
|
||||
});
|
||||
|
||||
const payloadsWithDiff = payloads
|
||||
.filter(({ properties }) => {
|
||||
const isDiffEmpty =
|
||||
properties.diff !== null &&
|
||||
properties.diff &&
|
||||
Object.keys(properties.diff).length === 0;
|
||||
const payloadsWithDiff = payloads
|
||||
.filter(({ properties }) => {
|
||||
const isDiffEmpty =
|
||||
properties.diff !== null &&
|
||||
properties.diff &&
|
||||
Object.keys(properties.diff).length === 0;
|
||||
|
||||
return !isDiffEmpty;
|
||||
})
|
||||
.map(({ properties, ...rest }) => ({
|
||||
...rest,
|
||||
properties: isDefined(properties.diff)
|
||||
? { diff: properties.diff }
|
||||
: {},
|
||||
}));
|
||||
return !isDiffEmpty;
|
||||
})
|
||||
.map(({ properties, ...rest }) => ({
|
||||
...rest,
|
||||
properties: isDefined(properties.diff)
|
||||
? { diff: properties.diff }
|
||||
: {},
|
||||
}));
|
||||
|
||||
const payloadsToInsert: TimelineActivityPayloadWorkspaceIdAndObjectSingularName['payloads'] =
|
||||
[];
|
||||
const payloadsToInsert: TimelineActivityPayloadWorkspaceIdAndObjectSingularName['payloads'] =
|
||||
[];
|
||||
|
||||
const timelineActivityPropertyName =
|
||||
await this.getTimelineActivityPropertyName(
|
||||
objectSingularName,
|
||||
isFeatureFlagTimelineActivityMigrated,
|
||||
);
|
||||
|
||||
for (const payload of payloadsWithDiff) {
|
||||
const recentTimelineActivity = recentTimelineActivities.find(
|
||||
(timelineActivity) =>
|
||||
timelineActivity[timelineActivityPropertyName] ===
|
||||
payload.recordId &&
|
||||
timelineActivity.workspaceMemberId ===
|
||||
payload.workspaceMemberId &&
|
||||
(!isDefined(payload.linkedRecordId) ||
|
||||
timelineActivity.linkedRecordId === payload.linkedRecordId) &&
|
||||
timelineActivity.name === payload.name,
|
||||
);
|
||||
|
||||
if (recentTimelineActivity) {
|
||||
const mergedProperties = objectRecordDiffMerge(
|
||||
recentTimelineActivity.properties,
|
||||
payload.properties,
|
||||
);
|
||||
|
||||
await this.updateTimelineActivity({
|
||||
id: recentTimelineActivity.id,
|
||||
properties: mergedProperties,
|
||||
workspaceMemberId: payload.workspaceMemberId,
|
||||
workspaceId,
|
||||
});
|
||||
} else {
|
||||
payloadsToInsert.push(payload);
|
||||
}
|
||||
}
|
||||
|
||||
await this.insertTimelineActivities({
|
||||
const timelineActivityPropertyName =
|
||||
await this.getTimelineActivityPropertyName(
|
||||
objectSingularName,
|
||||
payloads: payloadsToInsert,
|
||||
workspaceId,
|
||||
isFeatureFlagTimelineActivityMigrated,
|
||||
});
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
for (const payload of payloadsWithDiff) {
|
||||
const recentTimelineActivity = recentTimelineActivities.find(
|
||||
(timelineActivity) =>
|
||||
timelineActivity[timelineActivityPropertyName] ===
|
||||
payload.recordId &&
|
||||
timelineActivity.workspaceMemberId === payload.workspaceMemberId &&
|
||||
(!isDefined(payload.linkedRecordId) ||
|
||||
timelineActivity.linkedRecordId === payload.linkedRecordId) &&
|
||||
timelineActivity.name === payload.name,
|
||||
);
|
||||
|
||||
if (recentTimelineActivity) {
|
||||
const mergedProperties = objectRecordDiffMerge(
|
||||
recentTimelineActivity.properties,
|
||||
payload.properties,
|
||||
);
|
||||
|
||||
await this.updateTimelineActivity({
|
||||
id: recentTimelineActivity.id,
|
||||
properties: mergedProperties,
|
||||
workspaceMemberId: payload.workspaceMemberId,
|
||||
workspaceId,
|
||||
});
|
||||
} else {
|
||||
payloadsToInsert.push(payload);
|
||||
}
|
||||
}
|
||||
|
||||
await this.insertTimelineActivities({
|
||||
objectSingularName,
|
||||
payloads: payloadsToInsert,
|
||||
workspaceId,
|
||||
isFeatureFlagTimelineActivityMigrated,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async findRecentTimelineActivities({
|
||||
|
||||
@@ -192,7 +192,6 @@ export class TimelineActivityService {
|
||||
|
||||
const activityTargets =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const activityTargetRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -209,6 +208,7 @@ export class TimelineActivityService {
|
||||
},
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
if (activityTargets.length === 0) {
|
||||
@@ -277,7 +277,6 @@ export class TimelineActivityService {
|
||||
|
||||
const activities =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const activityRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -303,6 +302,7 @@ export class TimelineActivityService {
|
||||
},
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
if (activities.length === 0) {
|
||||
|
||||
+28
-31
@@ -36,37 +36,34 @@ export class WorkflowCreateManyPostQueryHook
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'workflowVersion',
|
||||
);
|
||||
|
||||
const position = await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const workflowVersionsToCreate = payload.map((workflow) => ({
|
||||
workflowId: workflow.id,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
name: 'v1',
|
||||
position,
|
||||
}));
|
||||
|
||||
await Promise.all(
|
||||
workflowVersionsToCreate.map((workflowVersion) => {
|
||||
return workflowVersionRepository.insert(workflowVersion);
|
||||
}),
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'workflowVersion',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const position = await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const workflowVersionsToCreate = payload.map((workflow) => ({
|
||||
workflowId: workflow.id,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
name: 'v1',
|
||||
position,
|
||||
}));
|
||||
|
||||
await Promise.all(
|
||||
workflowVersionsToCreate.map((workflowVersion) => {
|
||||
return workflowVersionRepository.insert(workflowVersion);
|
||||
}),
|
||||
);
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-24
@@ -38,31 +38,28 @@ export class WorkflowCreateOnePostQueryHook
|
||||
|
||||
const workflow = payload[0];
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext as WorkspaceAuthContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'workflowVersion',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'workflowVersion',
|
||||
);
|
||||
|
||||
const position = await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
const position = await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
await workflowVersionRepository.insert({
|
||||
workflowId: workflow.id,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
name: 'v1',
|
||||
position,
|
||||
});
|
||||
},
|
||||
);
|
||||
await workflowVersionRepository.insert({
|
||||
workflowId: workflow.id,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
name: 'v1',
|
||||
position,
|
||||
});
|
||||
}, authContext as WorkspaceAuthContext);
|
||||
}
|
||||
}
|
||||
|
||||
+60
-63
@@ -62,7 +62,6 @@ export class WorkflowCommonWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -79,6 +78,7 @@ export class WorkflowCommonWorkspaceService {
|
||||
|
||||
return this.getValidWorkflowVersionOrFail(workflowVersion);
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,78 +165,75 @@ export class WorkflowCommonWorkspaceService {
|
||||
}): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
for (const workflowId of workflowIds) {
|
||||
switch (operation) {
|
||||
case 'delete':
|
||||
await workflowAutomatedTriggerRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
for (const workflowId of workflowIds) {
|
||||
switch (operation) {
|
||||
case 'delete':
|
||||
await workflowAutomatedTriggerRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
await workflowRunRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
await workflowRunRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
await workflowVersionRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
await workflowVersionRepository.softDelete({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
break;
|
||||
case 'restore':
|
||||
await workflowAutomatedTriggerRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
break;
|
||||
case 'restore':
|
||||
await workflowAutomatedTriggerRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
await workflowRunRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
await workflowRunRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
await workflowVersionRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
await workflowVersionRepository.restore({
|
||||
workflowId,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
await this.deactivateVersionOnDelete({
|
||||
workflowVersionRepository,
|
||||
workflowId,
|
||||
workspaceId,
|
||||
operation,
|
||||
});
|
||||
|
||||
await this.handleServerlessFunctionSubEntities({
|
||||
workflowVersionRepository,
|
||||
workflowId,
|
||||
workspaceId,
|
||||
operation,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await this.deactivateVersionOnDelete({
|
||||
workflowVersionRepository,
|
||||
workflowId,
|
||||
workspaceId,
|
||||
operation,
|
||||
});
|
||||
|
||||
await this.handleServerlessFunctionSubEntities({
|
||||
workflowVersionRepository,
|
||||
workflowId,
|
||||
workspaceId,
|
||||
operation,
|
||||
});
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async deactivateVersionOnDelete({
|
||||
|
||||
+51
-56
@@ -48,36 +48,33 @@ export class WorkflowVersionValidationWorkspaceService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowAlreadyHasDraftVersion =
|
||||
await workflowVersionRepository.exists({
|
||||
where: {
|
||||
workflowId: payload.data.workflowId,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
const workflowAlreadyHasDraftVersion =
|
||||
await workflowVersionRepository.exists({
|
||||
where: {
|
||||
workflowId: payload.data.workflowId,
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (workflowAlreadyHasDraftVersion) {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Cannot create multiple draft versions for the same workflow',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
{
|
||||
userFriendlyMessage: msg`Cannot create multiple draft versions for the same workflow`,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (workflowAlreadyHasDraftVersion) {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'Cannot create multiple draft versions for the same workflow',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
{
|
||||
userFriendlyMessage: msg`Cannot create multiple draft versions for the same workflow`,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
async validateWorkflowVersionForUpdateOne({
|
||||
@@ -130,35 +127,33 @@ export class WorkflowVersionValidationWorkspaceService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const otherWorkflowVersionsExist =
|
||||
await workflowVersionRepository.exists({
|
||||
where: {
|
||||
workflowId: workflowVersion.workflowId,
|
||||
deletedAt: IsNull(),
|
||||
id: Not(workflowVersion.id),
|
||||
},
|
||||
});
|
||||
const otherWorkflowVersionsExist = await workflowVersionRepository.exists(
|
||||
{
|
||||
where: {
|
||||
workflowId: workflowVersion.workflowId,
|
||||
deletedAt: IsNull(),
|
||||
id: Not(workflowVersion.id),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!otherWorkflowVersionsExist) {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'The initial version of a workflow can not be deleted',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
{
|
||||
userFriendlyMessage: msg`The initial version of a workflow can not be deleted`,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!otherWorkflowVersionsExist) {
|
||||
throw new WorkflowQueryValidationException(
|
||||
'The initial version of a workflow can not be deleted',
|
||||
WorkflowQueryValidationExceptionCode.FORBIDDEN,
|
||||
{
|
||||
userFriendlyMessage: msg`The initial version of a workflow can not be deleted`,
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -91,7 +91,9 @@ describe('WorkflowVersionEdgeWorkspaceService', () => {
|
||||
globalWorkspaceOrmManager = {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation(async (_authContext, callback) => callback()),
|
||||
.mockImplementation(async (callback: () => any, _authContext?: any) =>
|
||||
callback(),
|
||||
),
|
||||
getRepository: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkflowVersionWorkspaceRepository),
|
||||
|
||||
+2
-2
@@ -45,7 +45,6 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -96,6 +95,7 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
});
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -166,6 +165,7 @@ export class WorkflowVersionEdgeWorkspaceService {
|
||||
});
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ describe('WorkflowVersionStepWorkspaceService', () => {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
} as unknown as jest.Mocked<GlobalWorkspaceOrmManager>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
|
||||
+16
-19
@@ -46,28 +46,25 @@ export class WorkflowVersionStepHelpersWorkspaceService {
|
||||
}): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const updateData: Partial<WorkflowVersionWorkspaceEntity> = {};
|
||||
const updateData: Partial<WorkflowVersionWorkspaceEntity> = {};
|
||||
|
||||
if (steps !== undefined) {
|
||||
updateData.steps = steps;
|
||||
}
|
||||
if (steps !== undefined) {
|
||||
updateData.steps = steps;
|
||||
}
|
||||
|
||||
if (trigger !== undefined) {
|
||||
updateData.trigger = trigger;
|
||||
}
|
||||
if (trigger !== undefined) {
|
||||
updateData.trigger = trigger;
|
||||
}
|
||||
|
||||
await workflowVersionRepository.update(workflowVersionId, updateData);
|
||||
},
|
||||
);
|
||||
await workflowVersionRepository.update(workflowVersionId, updateData);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -533,7 +533,6 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const responseKeys = Object.keys(response);
|
||||
|
||||
@@ -598,6 +597,7 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -727,7 +727,6 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -777,6 +776,7 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
|
||||
return emptyNodeStep;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -797,7 +797,6 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -878,6 +877,7 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
branches,
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+45
-51
@@ -50,7 +50,6 @@ export class WorkflowVersionWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
@@ -139,6 +138,7 @@ export class WorkflowVersionWorkspaceService {
|
||||
trigger: newWorkflowVersionTrigger,
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,6 @@ export class WorkflowVersionWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -311,6 +310,7 @@ export class WorkflowVersionWorkspaceService {
|
||||
trigger: remappedTrigger ?? null,
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -325,61 +325,55 @@ export class WorkflowVersionWorkspaceService {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowVersion = await workflowVersionRepository.findOneOrFail({
|
||||
where: {
|
||||
id: workflowVersionId,
|
||||
},
|
||||
});
|
||||
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
const triggerPosition = positions.find(
|
||||
(position) => position.id === TRIGGER_STEP_ID,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowVersion',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const updatedTrigger =
|
||||
isDefined(triggerPosition) && isDefined(workflowVersion.trigger)
|
||||
? {
|
||||
...workflowVersion.trigger,
|
||||
position: triggerPosition.position,
|
||||
}
|
||||
: undefined;
|
||||
const workflowVersion = await workflowVersionRepository.findOneOrFail({
|
||||
where: {
|
||||
id: workflowVersionId,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedSteps = workflowVersion.steps?.map((step) => {
|
||||
const updatedStep = positions.find(
|
||||
(position) => position.id === step.id,
|
||||
);
|
||||
assertWorkflowVersionIsDraft(workflowVersion);
|
||||
|
||||
if (updatedStep) {
|
||||
return {
|
||||
...step,
|
||||
position: updatedStep.position,
|
||||
};
|
||||
}
|
||||
const triggerPosition = positions.find(
|
||||
(position) => position.id === TRIGGER_STEP_ID,
|
||||
);
|
||||
|
||||
return step;
|
||||
});
|
||||
const updatedTrigger =
|
||||
isDefined(triggerPosition) && isDefined(workflowVersion.trigger)
|
||||
? {
|
||||
...workflowVersion.trigger,
|
||||
position: triggerPosition.position,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const updatePayload = {
|
||||
...(!isDefined(updatedTrigger) ? {} : { trigger: updatedTrigger }),
|
||||
...(!isDefined(updatedSteps) ? {} : { steps: updatedSteps }),
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(
|
||||
workflowVersionId,
|
||||
updatePayload,
|
||||
const updatedSteps = workflowVersion.steps?.map((step) => {
|
||||
const updatedStep = positions.find(
|
||||
(position) => position.id === step.id,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (updatedStep) {
|
||||
return {
|
||||
...step,
|
||||
position: updatedStep.position,
|
||||
};
|
||||
}
|
||||
|
||||
return step;
|
||||
});
|
||||
|
||||
const updatePayload = {
|
||||
...(!isDefined(updatedTrigger) ? {} : { trigger: updatedTrigger }),
|
||||
...(!isDefined(updatedSteps) ? {} : { steps: updatedSteps }),
|
||||
};
|
||||
|
||||
await workflowVersionRepository.update(workflowVersionId, updatePayload);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+60
-63
@@ -41,72 +41,69 @@ export class ResumeDelayedWorkflowJob {
|
||||
}: ResumeDelayedWorkflowJobData): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
const workflowRun =
|
||||
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
|
||||
workflowRunId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (workflowRun.status !== WorkflowRunStatus.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = workflowRun.state?.flow?.steps?.find(
|
||||
(step) => step.id === stepId,
|
||||
);
|
||||
|
||||
const stepInfo = workflowRun.state?.stepInfos[stepId];
|
||||
|
||||
if (!step || !isWorkflowDelayAction(step)) {
|
||||
throw new WorkflowRunException(
|
||||
'Step not found or is not a delay action',
|
||||
WorkflowRunExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (stepInfo?.status !== StepStatus.PENDING) {
|
||||
throw new WorkflowRunException(
|
||||
'Step is not pending',
|
||||
WorkflowRunExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workflowRunWorkspaceService.updateWorkflowRunStepInfo({
|
||||
stepId,
|
||||
stepInfo: {
|
||||
status: StepStatus.SUCCESS,
|
||||
result: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<RunWorkflowJobData>(
|
||||
RunWorkflowJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
lastExecutedStepId: stepId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
const workflowRun =
|
||||
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
|
||||
workflowRunId,
|
||||
workspaceId,
|
||||
status: WorkflowRunStatus.FAILED,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error during delay resume',
|
||||
});
|
||||
|
||||
if (workflowRun.status !== WorkflowRunStatus.RUNNING) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const step = workflowRun.state?.flow?.steps?.find(
|
||||
(step) => step.id === stepId,
|
||||
);
|
||||
|
||||
const stepInfo = workflowRun.state?.stepInfos[stepId];
|
||||
|
||||
if (!step || !isWorkflowDelayAction(step)) {
|
||||
throw new WorkflowRunException(
|
||||
'Step not found or is not a delay action',
|
||||
WorkflowRunExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (stepInfo?.status !== StepStatus.PENDING) {
|
||||
throw new WorkflowRunException(
|
||||
'Step is not pending',
|
||||
WorkflowRunExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workflowRunWorkspaceService.updateWorkflowRunStepInfo({
|
||||
stepId,
|
||||
stepInfo: {
|
||||
status: StepStatus.SUCCESS,
|
||||
result: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<RunWorkflowJobData>(
|
||||
RunWorkflowJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
lastExecutedStepId: stepId,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||
workflowRunId,
|
||||
workspaceId,
|
||||
status: WorkflowRunStatus.FAILED,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown error during delay resume',
|
||||
});
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-22
@@ -39,32 +39,29 @@ export class RunWorkflowJob {
|
||||
}: RunWorkflowJobData): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
if (lastExecutedStepId) {
|
||||
await this.resumeWorkflowExecution({
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
lastExecutedStepId,
|
||||
});
|
||||
} else {
|
||||
await this.startWorkflowExecution({
|
||||
workflowRunId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
if (lastExecutedStepId) {
|
||||
await this.resumeWorkflowExecution({
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
status: WorkflowRunStatus.FAILED,
|
||||
error: error.message,
|
||||
lastExecutedStepId,
|
||||
});
|
||||
} else {
|
||||
await this.startWorkflowExecution({
|
||||
workflowRunId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||
workspaceId,
|
||||
workflowRunId,
|
||||
status: WorkflowRunStatus.FAILED,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async startWorkflowExecution({
|
||||
|
||||
+17
-20
@@ -76,11 +76,9 @@ export class WorkflowCleanWorkflowRunsJob {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunsToDelete = await this.coreDataSource.query(
|
||||
`
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowRunsToDelete = await this.coreDataSource.query(
|
||||
`
|
||||
WITH ranked_runs AS (
|
||||
SELECT id,
|
||||
ROW_NUMBER() OVER (
|
||||
@@ -95,23 +93,22 @@ export class WorkflowCleanWorkflowRunsJob {
|
||||
WHERE rn > ${NUMBER_OF_WORKFLOW_RUNS_TO_KEEP}
|
||||
OR "createdAt" < NOW() - INTERVAL '14 days';
|
||||
`,
|
||||
);
|
||||
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkflowRunWorkspaceEntity,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkflowRunWorkspaceEntity,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
for (const workflowRunToDelete of workflowRunsToDelete) {
|
||||
await workflowRunRepository.delete(workflowRunToDelete.id);
|
||||
}
|
||||
|
||||
for (const workflowRunToDelete of workflowRunsToDelete) {
|
||||
await workflowRunRepository.delete(workflowRunToDelete.id);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Deleted ${workflowRunsToDelete.length} workflow runs for workspace ${workspaceId}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
this.logger.log(
|
||||
`Deleted ${workflowRunsToDelete.length} workflow runs for workspace ${workspaceId}`,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-34
@@ -51,41 +51,38 @@ export class WorkflowHandleStaledRunsWorkspaceService {
|
||||
private async handleStaledRunsForWorkspace(workspaceId: string) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkflowRunWorkspaceEntity,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
||||
|
||||
const staledWorkflowRuns = await workflowRunRepository.find({
|
||||
where: {
|
||||
status: WorkflowRunStatus.ENQUEUED,
|
||||
enqueuedAt: Or(LessThan(oneHourAgo), IsNull()),
|
||||
},
|
||||
});
|
||||
|
||||
if (staledWorkflowRuns.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await workflowRunRepository.update(
|
||||
staledWorkflowRuns.map((workflowRun) => workflowRun.id),
|
||||
{
|
||||
enqueuedAt: null,
|
||||
status: WorkflowRunStatus.NOT_STARTED,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workflowThrottlingWorkspaceService.recomputeWorkflowRunNotStartedCount(
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
WorkflowRunWorkspaceEntity,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
|
||||
|
||||
const staledWorkflowRuns = await workflowRunRepository.find({
|
||||
where: {
|
||||
status: WorkflowRunStatus.ENQUEUED,
|
||||
enqueuedAt: Or(LessThan(oneHourAgo), IsNull()),
|
||||
},
|
||||
});
|
||||
|
||||
if (staledWorkflowRuns.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await workflowRunRepository.update(
|
||||
staledWorkflowRuns.map((workflowRun) => workflowRun.id),
|
||||
{
|
||||
enqueuedAt: null,
|
||||
status: WorkflowRunStatus.NOT_STARTED,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workflowThrottlingWorkspaceService.recomputeWorkflowRunNotStartedCount(
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,6 @@ export class WorkflowRunEnqueueWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -152,6 +151,7 @@ export class WorkflowRunEnqueueWorkspaceService {
|
||||
);
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
} catch (error) {
|
||||
this.metricsService.incrementCounter({
|
||||
|
||||
+2
-2
@@ -80,7 +80,6 @@ export class WorkflowThrottlingWorkspaceService {
|
||||
|
||||
const currentlyNotStartedWorkflowRunCount =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -95,6 +94,7 @@ export class WorkflowThrottlingWorkspaceService {
|
||||
},
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
await this.setWorkflowRunNotStartedCount(
|
||||
@@ -113,7 +113,6 @@ export class WorkflowThrottlingWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
@@ -128,6 +127,7 @@ export class WorkflowThrottlingWorkspaceService {
|
||||
},
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+28
-31
@@ -53,38 +53,35 @@ export class DeleteWorkflowRunsCommand extends ActiveOrSuspendedWorkspacesMigrat
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
try {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const createdAtCondition = {
|
||||
createdAt: LessThan(
|
||||
this.createdBeforeDate || new Date().toISOString(),
|
||||
),
|
||||
};
|
||||
|
||||
const workflowRunCount = await workflowRunRepository.count({
|
||||
where: createdAtCondition,
|
||||
});
|
||||
|
||||
if (!options.dryRun && workflowRunCount > 0) {
|
||||
await workflowRunRepository.delete(createdAtCondition);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${options.dryRun ? ' (DRY RUN): ' : ''}Deleted ${workflowRunCount} workflow runs`,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
try {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Error while deleting workflowRun', error);
|
||||
|
||||
const createdAtCondition = {
|
||||
createdAt: LessThan(
|
||||
this.createdBeforeDate || new Date().toISOString(),
|
||||
),
|
||||
};
|
||||
|
||||
const workflowRunCount = await workflowRunRepository.count({
|
||||
where: createdAtCondition,
|
||||
});
|
||||
|
||||
if (!options.dryRun && workflowRunCount > 0) {
|
||||
await workflowRunRepository.delete(createdAtCondition);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`${options.dryRun ? ' (DRY RUN): ' : ''}Deleted ${workflowRunCount} workflow runs`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error('Error while deleting workflowRun', error);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-30
@@ -57,7 +57,6 @@ export class WorkflowRunWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
@@ -136,6 +135,7 @@ export class WorkflowRunWorkspaceService {
|
||||
|
||||
return workflowRun.id;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,7 +346,6 @@ export class WorkflowRunWorkspaceService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
@@ -359,6 +358,7 @@ export class WorkflowRunWorkspaceService {
|
||||
where: { id: workflowRunId },
|
||||
});
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -395,35 +395,32 @@ export class WorkflowRunWorkspaceService {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||
id: workflowRunId,
|
||||
});
|
||||
|
||||
if (!workflowRunToUpdate) {
|
||||
throw new WorkflowRunException(
|
||||
`workflowRun ${workflowRunId} not found`,
|
||||
WorkflowRunExceptionCode.WORKFLOW_RUN_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await workflowRunRepository.update(
|
||||
workflowRunToUpdate.id,
|
||||
partialUpdate,
|
||||
undefined,
|
||||
['id'],
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowRunRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowRunWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowRun',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||
id: workflowRunId,
|
||||
});
|
||||
|
||||
if (!workflowRunToUpdate) {
|
||||
throw new WorkflowRunException(
|
||||
`workflowRun ${workflowRunId} not found`,
|
||||
WorkflowRunExceptionCode.WORKFLOW_RUN_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await workflowRunRepository.update(
|
||||
workflowRunToUpdate.id,
|
||||
partialUpdate,
|
||||
undefined,
|
||||
['id'],
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private getInitState(
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ describe('WorkflowStatusesUpdate', () => {
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
};
|
||||
|
||||
const mockServerlessFunctionService = {
|
||||
|
||||
+27
-30
@@ -78,36 +78,33 @@ export class WorkflowStatusesUpdateJob {
|
||||
async handle(event: WorkflowVersionBatchEvent): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(event.workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
switch (event.type) {
|
||||
case WorkflowVersionEventType.CREATE:
|
||||
case WorkflowVersionEventType.DELETE:
|
||||
await Promise.all(
|
||||
event.workflowIds.map((workflowId) =>
|
||||
this.handleWorkflowVersionCreatedOrDeleted({
|
||||
workflowId,
|
||||
workspaceId: event.workspaceId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case WorkflowVersionEventType.STATUS_UPDATE:
|
||||
await Promise.all(
|
||||
event.statusUpdates.map((statusUpdate) =>
|
||||
this.handleWorkflowVersionStatusUpdated({
|
||||
statusUpdate,
|
||||
workspaceId: event.workspaceId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
switch (event.type) {
|
||||
case WorkflowVersionEventType.CREATE:
|
||||
case WorkflowVersionEventType.DELETE:
|
||||
await Promise.all(
|
||||
event.workflowIds.map((workflowId) =>
|
||||
this.handleWorkflowVersionCreatedOrDeleted({
|
||||
workflowId,
|
||||
workspaceId: event.workspaceId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case WorkflowVersionEventType.STATUS_UPDATE:
|
||||
await Promise.all(
|
||||
event.statusUpdates.map((statusUpdate) =>
|
||||
this.handleWorkflowVersionStatusUpdated({
|
||||
statusUpdate,
|
||||
workspaceId: event.workspaceId,
|
||||
}),
|
||||
),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async handleWorkflowVersionCreatedOrDeleted({
|
||||
|
||||
+65
-74
@@ -206,38 +206,35 @@ const createWorkflow = async ({
|
||||
}): Promise<string> => {
|
||||
const authContext = buildSystemAuthContext(context.workspaceId);
|
||||
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
const workflowPosition =
|
||||
await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
const workflowPosition =
|
||||
await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflow',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const workflow = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
};
|
||||
const workflow = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
statuses: [WorkflowStatus.DRAFT],
|
||||
position: workflowPosition,
|
||||
};
|
||||
|
||||
await workflowRepository.insert(workflow);
|
||||
await workflowRepository.insert(workflow);
|
||||
|
||||
return workflow.id;
|
||||
},
|
||||
);
|
||||
return workflow.id;
|
||||
}, authContext);
|
||||
};
|
||||
|
||||
const createWorkflowVersion = async ({
|
||||
@@ -255,41 +252,38 @@ const createWorkflowVersion = async ({
|
||||
}): Promise<string> => {
|
||||
const authContext = buildSystemAuthContext(context.workspaceId);
|
||||
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowVersionRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflowVersion',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowVersionRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflowVersion',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
const versionPosition =
|
||||
await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
const versionPosition =
|
||||
await deps.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
objectMetadata: {
|
||||
isCustom: false,
|
||||
nameSingular: 'workflowVersion',
|
||||
},
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const workflowVersion = {
|
||||
id: uuidv4(),
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps,
|
||||
position: versionPosition,
|
||||
};
|
||||
const workflowVersion = {
|
||||
id: uuidv4(),
|
||||
workflowId,
|
||||
name: 'v1',
|
||||
status: WorkflowVersionStatus.DRAFT,
|
||||
trigger,
|
||||
steps,
|
||||
position: versionPosition,
|
||||
};
|
||||
|
||||
await workflowVersionRepository.insert(workflowVersion);
|
||||
await workflowVersionRepository.insert(workflowVersion);
|
||||
|
||||
return workflowVersion.id;
|
||||
},
|
||||
);
|
||||
return workflowVersion.id;
|
||||
}, authContext);
|
||||
};
|
||||
|
||||
const updateWorkflowStatus = async ({
|
||||
@@ -305,20 +299,17 @@ const updateWorkflowStatus = async ({
|
||||
}) => {
|
||||
const authContext = buildSystemAuthContext(context.workspaceId);
|
||||
|
||||
await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository(
|
||||
context.workspaceId,
|
||||
'workflow',
|
||||
context.rolePermissionConfig,
|
||||
);
|
||||
|
||||
await workflowRepository.update(workflowId, {
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
lastPublishedVersionId: workflowVersionId,
|
||||
});
|
||||
},
|
||||
);
|
||||
await workflowRepository.update(workflowId, {
|
||||
statuses: [WorkflowStatus.ACTIVE],
|
||||
lastPublishedVersionId: workflowVersionId,
|
||||
});
|
||||
}, authContext);
|
||||
};
|
||||
|
||||
+1
-1
@@ -35,7 +35,6 @@ export const createGetWorkflowCurrentVersionTool = (
|
||||
const authContext = buildSystemAuthContext(context.workspaceId);
|
||||
|
||||
return await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowRepository =
|
||||
await deps.globalWorkspaceOrmManager.getRepository<WorkflowWorkspaceEntity>(
|
||||
@@ -103,6 +102,7 @@ export const createGetWorkflowCurrentVersionTool = (
|
||||
},
|
||||
};
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
} catch (error) {
|
||||
return {
|
||||
|
||||
+20
-26
@@ -27,22 +27,19 @@ export class AutomatedTriggerWorkspaceService {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
);
|
||||
|
||||
await workflowAutomatedTriggerRepository.insert({
|
||||
type,
|
||||
settings,
|
||||
workflowId,
|
||||
});
|
||||
},
|
||||
);
|
||||
await workflowAutomatedTriggerRepository.insert({
|
||||
type,
|
||||
settings,
|
||||
workflowId,
|
||||
});
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
async deleteAutomatedTrigger({
|
||||
@@ -54,17 +51,14 @@ export class AutomatedTriggerWorkspaceService {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workflowAutomatedTrigger',
|
||||
);
|
||||
|
||||
await workflowAutomatedTriggerRepository.delete({ workflowId });
|
||||
},
|
||||
);
|
||||
await workflowAutomatedTriggerRepository.delete({ workflowId });
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ describe('WorkflowDatabaseEventTriggerListener', () => {
|
||||
getRepository: jest.fn().mockResolvedValue(mockRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((_authContext: any, fn: () => any) => fn()),
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
} as any;
|
||||
|
||||
messageQueueService = {
|
||||
|
||||
+86
-93
@@ -247,64 +247,60 @@ export class WorkflowDatabaseEventTriggerListener {
|
||||
}) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const { fieldIdByJoinColumnName } =
|
||||
buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const { fieldIdByJoinColumnName } = buildFieldMapsFromFlatObjectMetadata(
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
for (const [joinColumnName, joinFieldId] of Object.entries(
|
||||
fieldIdByJoinColumnName,
|
||||
)) {
|
||||
const joinField = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: joinFieldId,
|
||||
});
|
||||
|
||||
const joinRecordIds = records
|
||||
.map((record) => record[joinColumnName])
|
||||
.filter(isDefined);
|
||||
|
||||
if (joinRecordIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectMetadataId =
|
||||
joinField.relationTargetObjectMetadataId;
|
||||
|
||||
if (!isDefined(relatedObjectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectMetadataNameSingular =
|
||||
flatObjectMetadataMaps.byId[relatedObjectMetadataId]?.nameSingular;
|
||||
|
||||
if (!isDefined(relatedObjectMetadataNameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
relatedObjectMetadataNameSingular,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
for (const [joinColumnName, joinFieldId] of Object.entries(
|
||||
fieldIdByJoinColumnName,
|
||||
)) {
|
||||
const joinField = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: joinFieldId,
|
||||
});
|
||||
const relatedRecords = await relatedObjectRepository.find({
|
||||
where: { id: In(joinRecordIds) },
|
||||
});
|
||||
|
||||
const joinRecordIds = records
|
||||
.map((record) => record[joinColumnName])
|
||||
.filter(isDefined);
|
||||
|
||||
if (joinRecordIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectMetadataId =
|
||||
joinField.relationTargetObjectMetadataId;
|
||||
|
||||
if (!isDefined(relatedObjectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectMetadataNameSingular =
|
||||
flatObjectMetadataMaps.byId[relatedObjectMetadataId]?.nameSingular;
|
||||
|
||||
if (!isDefined(relatedObjectMetadataNameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
relatedObjectMetadataNameSingular,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const relatedRecords = await relatedObjectRepository.find({
|
||||
where: { id: In(joinRecordIds) },
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
record[joinField.name] = relatedRecords.find(
|
||||
(relatedRecord) => relatedRecord.id === record[joinColumnName],
|
||||
);
|
||||
}
|
||||
for (const record of records) {
|
||||
record[joinField.name] = relatedRecords.find(
|
||||
(relatedRecord) => relatedRecord.id === record[joinColumnName],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private async shouldIgnoreEvent(
|
||||
@@ -339,50 +335,47 @@ export class WorkflowDatabaseEventTriggerListener {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
authContext,
|
||||
async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
automatedTriggerTableName,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workflowAutomatedTriggerRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||
workspaceId,
|
||||
automatedTriggerTableName,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const eventListeners = await workflowAutomatedTriggerRepository.find({
|
||||
where: {
|
||||
type: AutomatedTriggerType.DATABASE_EVENT,
|
||||
settings: Raw(
|
||||
() =>
|
||||
`"${automatedTriggerTableName}"."settings"->>'eventName' = :eventName`,
|
||||
{ eventName: databaseEventName },
|
||||
),
|
||||
},
|
||||
});
|
||||
const eventListeners = await workflowAutomatedTriggerRepository.find({
|
||||
where: {
|
||||
type: AutomatedTriggerType.DATABASE_EVENT,
|
||||
settings: Raw(
|
||||
() =>
|
||||
`"${automatedTriggerTableName}"."settings"->>'eventName' = :eventName`,
|
||||
{ eventName: databaseEventName },
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
for (const eventListener of eventListeners) {
|
||||
for (const eventPayload of payload.events) {
|
||||
const shouldTriggerJob = this.shouldTriggerJob({
|
||||
eventPayload,
|
||||
eventListener,
|
||||
action,
|
||||
});
|
||||
for (const eventListener of eventListeners) {
|
||||
for (const eventPayload of payload.events) {
|
||||
const shouldTriggerJob = this.shouldTriggerJob({
|
||||
eventPayload,
|
||||
eventListener,
|
||||
action,
|
||||
});
|
||||
|
||||
if (shouldTriggerJob) {
|
||||
await this.messageQueueService.add<WorkflowTriggerJobData>(
|
||||
WorkflowTriggerJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
workflowId: eventListener.workflowId,
|
||||
payload: eventPayload,
|
||||
},
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
}
|
||||
if (shouldTriggerJob) {
|
||||
await this.messageQueueService.add<WorkflowTriggerJobData>(
|
||||
WorkflowTriggerJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
workflowId: eventListener.workflowId,
|
||||
payload: eventPayload,
|
||||
},
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
private shouldTriggerJob({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user