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:
Weiko
2026-01-27 18:24:51 +01:00
committed by GitHub
parent dd98146c99
commit 2daebc6d0f
151 changed files with 3743 additions and 4002 deletions
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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({
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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:
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -66,7 +66,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
}),
executeInWorkspaceContext: jest
.fn()
.mockImplementation((_authContext: any, fn: () => any) => fn()),
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
};
beforeEach(async () => {
@@ -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,
);
}
}
@@ -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,