Skip to content

Commit

Permalink
fix(server): handle hanging workspace after user account deleted
Browse files Browse the repository at this point in the history
  • Loading branch information
forehalo committed Sep 25, 2024
1 parent 2df2003 commit a279d22
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 16 deletions.
20 changes: 12 additions & 8 deletions packages/backend/server/src/core/doc/adapters/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,6 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
async deleteSpace(workspaceId: string) {
const ident = { where: { workspaceId } };
await this.db.$transaction([
this.db.workspace.deleteMany({
where: {
id: workspaceId,
},
}),
this.db.snapshot.deleteMany(ident),
this.db.update.deleteMany(ident),
this.db.snapshotHistory.deleteMany(ident),
Expand Down Expand Up @@ -344,6 +339,17 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
return false;
}

const historyMaxAge = await this.options
.historyMaxAge(snapshot.spaceId)
.catch(
() =>
0 /* edgecase: user deleted but owned workspaces not handled correctly */
);

if (historyMaxAge === 0) {
return false;
}

await this.db.snapshotHistory
.create({
select: {
Expand All @@ -355,9 +361,7 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter {
timestamp: new Date(snapshot.timestamp),
blob: Buffer.from(snapshot.bin),
createdBy: snapshot.editor,
expiredAt: new Date(
Date.now() + (await this.options.historyMaxAge(snapshot.spaceId))
),
expiredAt: new Date(Date.now() + historyMaxAge),
},
})
.catch(() => {
Expand Down
15 changes: 14 additions & 1 deletion packages/backend/server/src/core/doc/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { Injectable, Logger, OnModuleInit, Optional } from '@nestjs/common';
import { Cron, CronExpression, SchedulerRegistry } from '@nestjs/schedule';
import { PrismaClient } from '@prisma/client';

import { CallTimer, Config, metrics } from '../../fundamentals';
import {
CallTimer,
Config,
type EventPayload,
metrics,
OnEvent,
} from '../../fundamentals';
import { PgWorkspaceDocStorageAdapter } from './adapters/workspace';

@Injectable()
Expand Down Expand Up @@ -73,4 +79,11 @@ export class DocStorageCronJob implements OnModuleInit {
.gauge('updates_queue_count')
.record(await this.db.update.count());
}

@OnEvent('user.deleted')
async clearUserWorkspaces(payload: EventPayload<'user.deleted'>) {
for (const workspace of payload.ownedWorkspaces) {
await this.workspace.deleteSpace(workspace);
}
}
}
3 changes: 2 additions & 1 deletion packages/backend/server/src/core/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Module } from '@nestjs/common';

import { PermissionModule } from '../permission';
import { StorageModule } from '../storage';
import { UserAvatarController } from './controller';
import { UserManagementResolver, UserResolver } from './resolver';
import { UserService } from './service';

@Module({
imports: [StorageModule],
imports: [StorageModule, PermissionModule],
providers: [UserResolver, UserService, UserManagementResolver],
controllers: [UserAvatarController],
exports: [UserService],
Expand Down
9 changes: 6 additions & 3 deletions packages/backend/server/src/core/user/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
WrongSignInCredentials,
WrongSignInMethod,
} from '../../fundamentals';
import { PermissionService } from '../permission';
import { Quota_FreePlanV1_1 } from '../quota/schema';
import { validators } from '../utils/validators';

Expand All @@ -34,7 +35,8 @@ export class UserService {
private readonly config: Config,
private readonly crypto: CryptoHelper,
private readonly prisma: PrismaClient,
private readonly emitter: EventEmitter
private readonly emitter: EventEmitter,
private readonly permission: PermissionService
) {}

get userCreatingData() {
Expand Down Expand Up @@ -276,12 +278,13 @@ export class UserService {
}

async deleteUser(id: string) {
const ownedWorkspaces = await this.permission.getOwnedWorkspaces(id);
const user = await this.prisma.user.delete({ where: { id } });
this.emitter.emit('user.deleted', user);
this.emitter.emit('user.deleted', { ...user, ownedWorkspaces });
}

@OnEvent('user.updated')
async onUserUpdated(user: EventPayload<'user.deleted'>) {
async onUserUpdated(user: EventPayload<'user.updated'>) {
const { enabled, customerIo } = this.config.metrics;
if (enabled && customerIo?.token) {
const payload = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
UserNotFound,
} from '../../../fundamentals';
import { CurrentUser, Public } from '../../auth';
import type { Editor } from '../../doc';
import { type Editor, PgWorkspaceDocStorageAdapter } from '../../doc';
import { DocContentService } from '../../doc-renderer';
import { Permission, PermissionService } from '../../permission';
import { QuotaManagementService, QuotaQueryType } from '../../quota';
Expand Down Expand Up @@ -86,7 +86,8 @@ export class WorkspaceResolver {
private readonly event: EventEmitter,
private readonly blobStorage: WorkspaceBlobStorage,
private readonly mutex: RequestMutex,
private readonly doc: DocContentService
private readonly doc: DocContentService,
private readonly workspaceStorage: PgWorkspaceDocStorageAdapter
) {}

@ResolveField(() => Permission, {
Expand Down Expand Up @@ -352,6 +353,7 @@ export class WorkspaceResolver {
id,
},
});
await this.workspaceStorage.deleteSpace(id);

this.event.emit('workspace.deleted', id);

Expand Down
6 changes: 5 additions & 1 deletion packages/backend/server/src/fundamentals/event/def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export interface DocEvents {

export interface UserEvents {
updated: Payload<Omit<User, 'password'>>;
deleted: Payload<User>;
deleted: Payload<
User & {
ownedWorkspaces: Workspace['id'][];
}
>;
}

/**
Expand Down

0 comments on commit a279d22

Please sign in to comment.