mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
fix(server): handle hanging workspace after user account deleted (#8377)
fix CLOUD-74
This commit is contained in:
@@ -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),
|
||||
@@ -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: {
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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() {
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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';
|
||||
@@ -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, {
|
||||
@@ -352,6 +353,7 @@ export class WorkspaceResolver {
|
||||
id,
|
||||
},
|
||||
});
|
||||
await this.workspaceStorage.deleteSpace(id);
|
||||
|
||||
this.event.emit('workspace.deleted', id);
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ export interface DocEvents {
|
||||
|
||||
export interface UserEvents {
|
||||
updated: Payload<Omit<User, 'password'>>;
|
||||
deleted: Payload<User>;
|
||||
deleted: Payload<
|
||||
User & {
|
||||
ownedWorkspaces: Workspace['id'][];
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user