Files
twenty/packages/twenty-server/src/database/commands/upgrade-version-command/1-21/1-21-backfill-message-thread-subject.command.ts
T
Félix Malfait 7053e1bbc5 fix: bypass permission checks in 1.21 backfill-message-thread-subject command (#19375)
## Summary
- `upgrade:1-21:backfill-message-thread-subject` was failing on every
workspace with `Method not allowed because permissions are not
implemented at datasource level`.
- The global workspace datasource gates raw `query()` calls behind
`shouldBypassPermissionChecks`. Both the column-existence probe and the
UPDATE in this command now pass that flag, matching the pattern used by
the other 1.20/1.21 upgrade commands.

## Test plan
- [ ] Re-run `yarn command:prod
upgrade:1-21:backfill-message-thread-subject` and confirm all workspaces
complete without the permissions error
- [ ] Spot-check a workspace to confirm `messageThread.subject` is
backfilled from the most recent message

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 09:59:30 +02:00

79 lines
2.5 KiB
TypeScript

import { Command } from 'nest-commander';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
@Command({
name: 'upgrade:1-21:backfill-message-thread-subject',
description:
'Backfill messageThread.subject from the most recently received message in each thread',
})
export class BackfillMessageThreadSubjectCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
dataSource,
options,
}: RunOnWorkspaceArgs): Promise<void> {
if (!dataSource) {
this.logger.log(`No data source for workspace ${workspaceId}, skipping`);
return;
}
const schemaName = getWorkspaceSchemaName(workspaceId);
if (options.dryRun) {
this.logger.log(
`[DRY RUN] Would backfill messageThread.subject for workspace ${workspaceId}`,
);
return;
}
const columnExists = await dataSource.query(
`SELECT 1 FROM information_schema.columns
WHERE table_schema = $1
AND table_name = 'messageThread'
AND column_name = 'subject'`,
[schemaName],
undefined,
{ shouldBypassPermissionChecks: true },
);
if (columnExists.length === 0) {
this.logger.log(
`Column "subject" does not exist yet on messageThread for workspace ${workspaceId}, skipping (will be created by sync-metadata)`,
);
return;
}
const result = await dataSource.query(
`UPDATE "${schemaName}"."messageThread" mt
SET "subject" = sub.subject
FROM (
SELECT DISTINCT ON ("messageThreadId") "messageThreadId", "subject"
FROM "${schemaName}"."message"
ORDER BY "messageThreadId", "receivedAt" DESC NULLS LAST
) sub
WHERE mt.id = sub."messageThreadId"
AND mt."subject" IS NULL`,
undefined,
undefined,
{ shouldBypassPermissionChecks: true },
);
this.logger.log(
`Backfilled subject for ${result?.[1] ?? 0} message threads in workspace ${workspaceId}`,
);
}
}