fix: strip deletedAt from connectedAccount where clause in findMany core path
https://sonarly.com/issue/17951?type=bug The `MessageChannelDataAccessService.findMany` method passes a `deletedAt: IsNull()` filter on the `connectedAccount` relation to the core TypeORM repository, but the new `ConnectedAccountEntity` lacks a `deletedAt` column, causing an `EntityPropertyNotFoundError`. Fix: Three changes in `MessageChannelDataAccessService`: **1. `toCoreWhere` — always strip `connectedAccount` from the where clause** Previously, after extracting `accountOwnerId`, any remaining `connectedAccount` properties (like `deletedAt: IsNull()`) were kept as a nested relation filter. But `ConnectedAccountEntity` in the core schema doesn't have `deletedAt` (or `accountOwnerId`), so TypeORM throws `EntityPropertyNotFoundError`. The fix unconditionally deletes `coreWhere.connectedAccount` after resolving `accountOwnerId` to `connectedAccountId`. **2. `find` — route through `toCoreWhere`** The `find` method was spreading `where` directly into the core query without processing `connectedAccount` filters. This would fail for any caller passing `connectedAccount` relation filters (e.g., the blocklist reimport job). Now routes through `toCoreWhere` like the other methods. **3. `findMany` — apply relation-stripping pattern from `findOne`** The `findOne` method already correctly handles the migrated path: it strips `connectedAccount` and `messageFolders` from `relations`, strips `connectedAccount` from `select`, queries the core repository, then resolves connected accounts and message folders separately via their data access services. The `findMany` method was missing this logic — it passed all options (including `relations: ['connectedAccount']` and nested `connectedAccount` select/where) directly to the core repository. The fix applies the identical pattern, with the optimization of batch-loading connected accounts via a Map to avoid N+1 queries.
This commit is contained in:
+103
-36
@@ -50,8 +50,7 @@ export class MessageChannelDataAccessService {
|
||||
};
|
||||
|
||||
if ('accountOwnerId' in connectedAccountWhere) {
|
||||
const { accountOwnerId, ...restConnectedAccount } =
|
||||
connectedAccountWhere;
|
||||
const { accountOwnerId } = connectedAccountWhere;
|
||||
|
||||
const resolvedConnectedAccounts =
|
||||
await this.connectedAccountDataAccessService.find(workspaceId, {
|
||||
@@ -63,13 +62,9 @@ export class MessageChannelDataAccessService {
|
||||
} else {
|
||||
coreWhere.connectedAccountId = '00000000-0000-0000-0000-000000000000';
|
||||
}
|
||||
|
||||
if (Object.keys(restConnectedAccount).length > 0) {
|
||||
coreWhere.connectedAccount = restConnectedAccount;
|
||||
} else {
|
||||
delete coreWhere.connectedAccount;
|
||||
}
|
||||
}
|
||||
|
||||
delete coreWhere.connectedAccount;
|
||||
}
|
||||
|
||||
return coreWhere;
|
||||
@@ -157,11 +152,13 @@ export class MessageChannelDataAccessService {
|
||||
where?: FindOptionsWhere<MessageChannelWorkspaceEntity>,
|
||||
): Promise<MessageChannelWorkspaceEntity[]> {
|
||||
if (await this.isMigrated(workspaceId)) {
|
||||
const coreWhere = await this.toCoreWhere(
|
||||
workspaceId,
|
||||
(where ?? {}) as Record<string, unknown>,
|
||||
);
|
||||
|
||||
return this.coreRepository.find({
|
||||
where: {
|
||||
...(where as Record<string, unknown>),
|
||||
workspaceId,
|
||||
} as FindOptionsWhere<MessageChannelEntity>,
|
||||
where: coreWhere as FindOptionsWhere<MessageChannelEntity>,
|
||||
}) as unknown as Promise<MessageChannelWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
@@ -177,41 +174,111 @@ export class MessageChannelDataAccessService {
|
||||
if (await this.isMigrated(workspaceId)) {
|
||||
const baseWhere = options.where;
|
||||
|
||||
if (!baseWhere) {
|
||||
return this.coreRepository.find({
|
||||
...options,
|
||||
where: { workspaceId },
|
||||
} as FindManyOptions<MessageChannelEntity>) as unknown as Promise<
|
||||
MessageChannelWorkspaceEntity[]
|
||||
>;
|
||||
}
|
||||
const requestedRelations =
|
||||
(options.relations as string[] | undefined)?.slice() ?? [];
|
||||
|
||||
if (Array.isArray(baseWhere)) {
|
||||
const needsConnectedAccount =
|
||||
requestedRelations.includes('connectedAccount');
|
||||
|
||||
const needsMessageFolders =
|
||||
requestedRelations.includes('messageFolders');
|
||||
|
||||
const coreRelations = requestedRelations.filter(
|
||||
(r) => r !== 'connectedAccount' && r !== 'messageFolders',
|
||||
);
|
||||
|
||||
const coreSelect = options.select
|
||||
? (({ connectedAccount, ...rest }) => rest)(
|
||||
options.select as Record<string, unknown>,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const coreOptions = {
|
||||
...options,
|
||||
relations: coreRelations,
|
||||
...(coreSelect ? { select: coreSelect } : {}),
|
||||
};
|
||||
|
||||
let results: MessageChannelEntity[];
|
||||
|
||||
if (!baseWhere) {
|
||||
results = await this.coreRepository.find({
|
||||
...coreOptions,
|
||||
where: { workspaceId },
|
||||
} as FindManyOptions<MessageChannelEntity>);
|
||||
} else if (Array.isArray(baseWhere)) {
|
||||
const coreWhereArray = await Promise.all(
|
||||
baseWhere.map((whereItem) =>
|
||||
this.toCoreWhere(workspaceId, whereItem as Record<string, unknown>),
|
||||
),
|
||||
);
|
||||
|
||||
return this.coreRepository.find({
|
||||
...options,
|
||||
results = await this.coreRepository.find({
|
||||
...coreOptions,
|
||||
where: coreWhereArray,
|
||||
} as FindManyOptions<MessageChannelEntity>) as unknown as Promise<
|
||||
MessageChannelWorkspaceEntity[]
|
||||
>;
|
||||
} as FindManyOptions<MessageChannelEntity>);
|
||||
} else {
|
||||
const coreWhere = await this.toCoreWhere(
|
||||
workspaceId,
|
||||
baseWhere as Record<string, unknown>,
|
||||
);
|
||||
|
||||
results = await this.coreRepository.find({
|
||||
...coreOptions,
|
||||
where: coreWhere,
|
||||
} as FindManyOptions<MessageChannelEntity>);
|
||||
}
|
||||
|
||||
const coreWhere = await this.toCoreWhere(
|
||||
workspaceId,
|
||||
baseWhere as Record<string, unknown>,
|
||||
);
|
||||
const workspaceResults =
|
||||
results as unknown as MessageChannelWorkspaceEntity[];
|
||||
|
||||
return this.coreRepository.find({
|
||||
...options,
|
||||
where: coreWhere,
|
||||
} as FindManyOptions<MessageChannelEntity>) as unknown as Promise<
|
||||
MessageChannelWorkspaceEntity[]
|
||||
>;
|
||||
if (needsConnectedAccount && workspaceResults.length > 0) {
|
||||
const connectedAccountIds = [
|
||||
...new Set(
|
||||
workspaceResults.map((result) => result.connectedAccountId),
|
||||
),
|
||||
];
|
||||
|
||||
const connectedAccounts = await Promise.all(
|
||||
connectedAccountIds.map((id) =>
|
||||
this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id },
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const connectedAccountById = new Map(
|
||||
connectedAccounts
|
||||
.filter((account) => account !== null)
|
||||
.map((account) => [account.id, account]),
|
||||
);
|
||||
|
||||
for (const result of workspaceResults) {
|
||||
const connectedAccount = connectedAccountById.get(
|
||||
result.connectedAccountId,
|
||||
);
|
||||
|
||||
if (connectedAccount) {
|
||||
result.connectedAccount = connectedAccount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsMessageFolders && workspaceResults.length > 0) {
|
||||
const workspaceRepository =
|
||||
await this.getWorkspaceRepository(workspaceId);
|
||||
|
||||
for (const result of workspaceResults) {
|
||||
const workspaceChannel = await workspaceRepository.findOne({
|
||||
where: { id: result.id },
|
||||
relations: ['messageFolders'],
|
||||
});
|
||||
|
||||
result.messageFolders = workspaceChannel?.messageFolders ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceResults;
|
||||
}
|
||||
|
||||
const workspaceRepository = await this.getWorkspaceRepository(workspaceId);
|
||||
|
||||
Reference in New Issue
Block a user