fix(worker): better deletion of project

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-04-01 10:39:33 +02:00
parent 58c4a6a741
commit d38ccb4717
4 changed files with 88 additions and 23 deletions

View File

@@ -1,9 +1,10 @@
import { logger } from '@/utils/logger'; import { logger } from '@/utils/logger';
import { generateSalt } from '@openpanel/common/server'; import { TABLE_NAMES, ch, db } from '@openpanel/db';
import { TABLE_NAMES, ch, chQuery, db } from '@openpanel/db'; import type { CronQueuePayload } from '@openpanel/queue';
import type { Job } from 'bullmq';
import { escape } from 'sqlstring'; import { escape } from 'sqlstring';
export async function deleteProjects() { export async function deleteProjects(job: Job<CronQueuePayload>) {
const projects = await db.project.findMany({ const projects = await db.project.findMany({
where: { where: {
deleteAt: { deleteAt: {
@@ -24,16 +25,33 @@ export async function deleteProjects() {
}); });
} }
if (process.env.SELF_HOSTED) { logger.info('Deleting projects', {
await ch.command({ projects,
query: `DELETE FROM ${TABLE_NAMES.events} WHERE project_id IN (${projects.map((project) => escape(project.id)).join(',')});`,
clickhouse_settings: {
lightweight_deletes_sync: 0,
},
}); });
} else {
projects.forEach((project) => {
job.log(`Delete project: "${project.id}"`);
});
const where = `project_id IN (${projects.map((project) => escape(project.id)).join(',')})`;
const tables = [
TABLE_NAMES.events,
TABLE_NAMES.profiles,
TABLE_NAMES.events_bots,
TABLE_NAMES.sessions,
TABLE_NAMES.cohort_events_mv,
TABLE_NAMES.dau_mv,
TABLE_NAMES.event_names_mv,
TABLE_NAMES.event_property_values_mv,
];
for (const table of tables) {
const query = process.env.SELF_HOSTED
? `ALTER TABLE ${table} DELETE WHERE ${where};`
: `ALTER TABLE ${table}_replicated ON CLUSTER '{cluster}' DELETE WHERE ${where};`;
await ch.command({ await ch.command({
query: `DELETE FROM ${TABLE_NAMES.events}_replicated ON CLUSTER '{cluster}' WHERE project_id IN (${projects.map((project) => escape(project.id)).join(',')});`, query,
clickhouse_settings: { clickhouse_settings: {
lightweight_deletes_sync: 0, lightweight_deletes_sync: 0,
}, },

View File

@@ -25,7 +25,7 @@ export async function cronJob(job: Job<CronQueuePayload>) {
return await ping(); return await ping();
} }
case 'deleteProjects': { case 'deleteProjects': {
return await deleteProjects(); return await deleteProjects(job);
} }
} }
} }

View File

@@ -0,0 +1,47 @@
-- DropForeignKey
ALTER TABLE "dashboards" DROP CONSTRAINT "dashboards_organizationId_fkey";
-- DropForeignKey
ALTER TABLE "integrations" DROP CONSTRAINT "integrations_organizationId_fkey";
-- DropForeignKey
ALTER TABLE "invites" DROP CONSTRAINT "invites_createdById_fkey";
-- DropForeignKey
ALTER TABLE "notifications" DROP CONSTRAINT "notifications_integrationId_fkey";
-- DropForeignKey
ALTER TABLE "notifications" DROP CONSTRAINT "notifications_notificationRuleId_fkey";
-- DropForeignKey
ALTER TABLE "reports" DROP CONSTRAINT "reports_dashboardId_fkey";
-- DropForeignKey
ALTER TABLE "reset_password" DROP CONSTRAINT "reset_password_accountId_fkey";
-- DropForeignKey
ALTER TABLE "shares" DROP CONSTRAINT "shares_organizationId_fkey";
-- AddForeignKey
ALTER TABLE "invites" ADD CONSTRAINT "invites_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "dashboards" ADD CONSTRAINT "dashboards_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "reports" ADD CONSTRAINT "reports_dashboardId_fkey" FOREIGN KEY ("dashboardId") REFERENCES "dashboards"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "shares" ADD CONSTRAINT "shares_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_integrationId_fkey" FOREIGN KEY ("integrationId") REFERENCES "integrations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_notificationRuleId_fkey" FOREIGN KEY ("notificationRuleId") REFERENCES "notification_rules"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "integrations" ADD CONSTRAINT "integrations_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "reset_password" ADD CONSTRAINT "reset_password_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -36,7 +36,7 @@ model Organization {
projects Project[] projects Project[]
members Member[] members Member[]
createdByUserId String? createdByUserId String?
createdBy User? @relation(name: "organizationCreatedBy", fields: [createdByUserId], references: [id]) createdBy User? @relation(name: "organizationCreatedBy", fields: [createdByUserId], references: [id], onDelete: SetNull)
ProjectAccess ProjectAccess[] ProjectAccess ProjectAccess[]
Client Client[] Client Client[]
Dashboard Dashboard[] Dashboard Dashboard[]
@@ -129,7 +129,7 @@ model Member {
userId String? userId String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
invitedById String? invitedById String?
invitedBy User? @relation("invitedBy", fields: [invitedById], references: [id]) invitedBy User? @relation("invitedBy", fields: [invitedById], references: [id], onDelete: SetNull)
organizationId String organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
meta Json? meta Json?
@@ -142,7 +142,7 @@ model Member {
model Invite { model Invite {
id String @id id String @id
email String email String
createdBy User @relation(fields: [createdById], references: [id]) createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
createdById String createdById String
organizationId String organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
@@ -263,7 +263,7 @@ enum ChartType {
model Dashboard { model Dashboard {
id String @id @default(dbgenerated("gen_random_uuid()")) id String @id @default(dbgenerated("gen_random_uuid()"))
name String name String
organization Organization @relation(fields: [organizationId], references: [id]) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String organizationId String
projectId String projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@ -302,7 +302,7 @@ model Report {
funnelWindow Float? funnelWindow Float?
dashboardId String dashboardId String
dashboard Dashboard @relation(fields: [dashboardId], references: [id]) dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@ -314,7 +314,7 @@ model ShareOverview {
id String @unique id String @unique
projectId String @unique projectId String @unique
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id]) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String organizationId String
public Boolean @default(false) public Boolean @default(false)
password String? password String?
@@ -389,10 +389,10 @@ model Notification {
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
sendToApp Boolean @default(false) sendToApp Boolean @default(false)
sendToEmail Boolean @default(false) sendToEmail Boolean @default(false)
integration Integration? @relation(fields: [integrationId], references: [id]) integration Integration? @relation(fields: [integrationId], references: [id], onDelete: Cascade)
integrationId String? @db.Uuid integrationId String? @db.Uuid
notificationRuleId String? @db.Uuid notificationRuleId String? @db.Uuid
notificationRule NotificationRule? @relation(fields: [notificationRuleId], references: [id]) notificationRule NotificationRule? @relation(fields: [notificationRuleId], references: [id], onDelete: Cascade)
/// [IPrismaNotificationPayload] /// [IPrismaNotificationPayload]
payload Json? payload Json?
@@ -404,7 +404,7 @@ model Integration {
name String name String
/// [IPrismaIntegrationConfig] /// [IPrismaIntegrationConfig]
config Json config Json
organization Organization @relation(fields: [organizationId], references: [id]) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String organizationId String
notificationRules NotificationRule[] notificationRules NotificationRule[]
notifications Notification[] notifications Notification[]
@@ -417,7 +417,7 @@ model Integration {
model ResetPassword { model ResetPassword {
id String @id id String @id
accountId String accountId String
account Account @relation(fields: [accountId], references: [id]) account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
expiresAt DateTime expiresAt DateTime
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt