From bc465f97048e4000717e095b86f5f935de8361d3 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 15 Mar 2024 16:55:48 +0800 Subject: [PATCH] Revert "fix(server): wrap read-modify-write apis with distributed lock (#5979)" This reverts commit 34f892b05b24212752b2832327d16296c693a66b. --- packages/backend/server/src/app.module.ts | 2 - .../server/src/core/features/feature.ts | 5 +- .../backend/server/src/core/quota/quota.ts | 7 +- .../backend/server/src/core/quota/service.ts | 12 +- .../backend/server/src/core/user/service.ts | 2 +- .../core/workspaces/resolvers/workspace.ts | 138 ++++++++---------- .../server/src/fundamentals/error/index.ts | 1 - .../fundamentals/error/too-many-requests.ts | 14 -- .../server/src/fundamentals/graphql/index.ts | 14 +- .../backend/server/src/fundamentals/index.ts | 9 -- .../server/src/fundamentals/mutex/bucket.ts | 15 -- .../server/src/fundamentals/mutex/index.ts | 14 -- .../server/src/fundamentals/mutex/mutex.ts | 96 ------------ .../server/src/fundamentals/prisma/index.ts | 4 - .../backend/server/src/plugins/redis/index.ts | 29 +--- .../server/src/plugins/redis/instances.ts | 7 - .../backend/server/src/plugins/redis/mutex.ts | 96 ------------ packages/backend/server/tests/tsconfig.json | 2 +- .../server/tests/workspace-invite.e2e.ts | 24 +-- packages/backend/server/tsconfig.json | 2 +- 20 files changed, 80 insertions(+), 413 deletions(-) delete mode 100644 packages/backend/server/src/fundamentals/error/too-many-requests.ts delete mode 100644 packages/backend/server/src/fundamentals/mutex/bucket.ts delete mode 100644 packages/backend/server/src/fundamentals/mutex/index.ts delete mode 100644 packages/backend/server/src/fundamentals/mutex/mutex.ts delete mode 100644 packages/backend/server/src/plugins/redis/mutex.ts diff --git a/packages/backend/server/src/app.module.ts b/packages/backend/server/src/app.module.ts index 3e03605c18f81..f5f44aaeb0441 100644 --- a/packages/backend/server/src/app.module.ts +++ b/packages/backend/server/src/app.module.ts @@ -28,7 +28,6 @@ import { GqlModule } from './fundamentals/graphql'; import { HelpersModule } from './fundamentals/helpers'; import { MailModule } from './fundamentals/mailer'; import { MetricsModule } from './fundamentals/metrics'; -import { MutexModule } from './fundamentals/mutex'; import { PrismaModule } from './fundamentals/prisma'; import { StorageProviderModule } from './fundamentals/storage'; import { RateLimiterModule } from './fundamentals/throttler'; @@ -40,7 +39,6 @@ export const FunctionalityModules = [ ScheduleModule.forRoot(), EventModule, CacheModule, - MutexModule, PrismaModule, MetricsModule, RateLimiterModule, diff --git a/packages/backend/server/src/core/features/feature.ts b/packages/backend/server/src/core/features/feature.ts index 61a99aa1afa3f..48d89d53b8029 100644 --- a/packages/backend/server/src/core/features/feature.ts +++ b/packages/backend/server/src/core/features/feature.ts @@ -1,4 +1,5 @@ -import { PrismaTransaction } from '../../fundamentals'; +import { PrismaClient } from '@prisma/client'; + import { Feature, FeatureSchema, FeatureType } from './types'; class FeatureConfig { @@ -66,7 +67,7 @@ export type FeatureConfigType = InstanceType< const FeatureCache = new Map>(); -export async function getFeature(prisma: PrismaTransaction, featureId: number) { +export async function getFeature(prisma: PrismaClient, featureId: number) { const cachedQuota = FeatureCache.get(featureId); if (cachedQuota) { diff --git a/packages/backend/server/src/core/quota/quota.ts b/packages/backend/server/src/core/quota/quota.ts index 3f481de06dcd3..d6d7657c01495 100644 --- a/packages/backend/server/src/core/quota/quota.ts +++ b/packages/backend/server/src/core/quota/quota.ts @@ -1,4 +1,5 @@ -import { PrismaTransaction } from '../../fundamentals'; +import { PrismaClient } from '@prisma/client'; + import { formatDate, formatSize, Quota, QuotaSchema } from './types'; const QuotaCache = new Map(); @@ -6,14 +7,14 @@ const QuotaCache = new Map(); export class QuotaConfig { readonly config: Quota; - static async get(tx: PrismaTransaction, featureId: number) { + static async get(prisma: PrismaClient, featureId: number) { const cachedQuota = QuotaCache.get(featureId); if (cachedQuota) { return cachedQuota; } - const quota = await tx.features.findFirst({ + const quota = await prisma.features.findFirst({ where: { id: featureId, }, diff --git a/packages/backend/server/src/core/quota/service.ts b/packages/backend/server/src/core/quota/service.ts index fcff2d7da4291..d8b2b65fe7d6f 100644 --- a/packages/backend/server/src/core/quota/service.ts +++ b/packages/backend/server/src/core/quota/service.ts @@ -1,15 +1,13 @@ import { Injectable } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; -import { - type EventPayload, - OnEvent, - PrismaTransaction, -} from '../../fundamentals'; +import { type EventPayload, OnEvent } from '../../fundamentals'; import { FeatureKind } from '../features'; import { QuotaConfig } from './quota'; import { QuotaType } from './types'; +type Transaction = Parameters[0]>[0]; + @Injectable() export class QuotaService { constructor(private readonly prisma: PrismaClient) {} @@ -142,8 +140,8 @@ export class QuotaService { }); } - async hasQuota(userId: string, quota: QuotaType, tx?: PrismaTransaction) { - const executor = tx ?? this.prisma; + async hasQuota(userId: string, quota: QuotaType, transaction?: Transaction) { + const executor = transaction ?? this.prisma; return executor.userFeatures .count({ diff --git a/packages/backend/server/src/core/user/service.ts b/packages/backend/server/src/core/user/service.ts index a85aaac37fd55..4c60f00975990 100644 --- a/packages/backend/server/src/core/user/service.ts +++ b/packages/backend/server/src/core/user/service.ts @@ -54,7 +54,7 @@ export class UserService { return this.createUser({ email, - name: email.split('@')[0], + name: 'Unnamed', ...data, }); } diff --git a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts index fd3ca1ecd89f0..d322a69c1b4d6 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts @@ -25,9 +25,7 @@ import { EventEmitter, type FileUpload, MailService, - MutexService, Throttle, - TooManyRequestsException, } from '../../../fundamentals'; import { CurrentUser, Public } from '../../auth'; import { QuotaManagementService, QuotaQueryType } from '../../quota'; @@ -60,8 +58,7 @@ export class WorkspaceResolver { private readonly quota: QuotaManagementService, private readonly users: UserService, private readonly event: EventEmitter, - private readonly blobStorage: WorkspaceBlobStorage, - private readonly mutex: MutexService + private readonly blobStorage: WorkspaceBlobStorage ) {} @ResolveField(() => Permission, { @@ -339,87 +336,74 @@ export class WorkspaceResolver { throw new ForbiddenException('Cannot change owner'); } - try { - // lock to prevent concurrent invite - const lockFlag = `invite:${workspaceId}`; - await using lock = await this.mutex.lock(lockFlag); - if (!lock) { - return new TooManyRequestsException('Server is busy'); - } + // member limit check + const [memberCount, quota] = await Promise.all([ + this.prisma.workspaceUserPermission.count({ + where: { workspaceId }, + }), + this.quota.getWorkspaceUsage(workspaceId), + ]); + if (memberCount >= quota.memberLimit) { + throw new PayloadTooLargeException('Workspace member limit reached.'); + } - // member limit check - const [memberCount, quota] = await Promise.all([ - this.prisma.workspaceUserPermission.count({ - where: { workspaceId }, - }), - this.quota.getWorkspaceUsage(workspaceId), - ]); - if (memberCount >= quota.memberLimit) { - return new PayloadTooLargeException('Workspace member limit reached.'); - } + let target = await this.users.findUserByEmail(email); + if (target) { + const originRecord = await this.prisma.workspaceUserPermission.findFirst({ + where: { + workspaceId, + userId: target.id, + }, + }); + // only invite if the user is not already in the workspace + if (originRecord) return originRecord.id; + } else { + target = await this.users.createAnonymousUser(email, { + registered: false, + }); + } - let target = await this.users.findUserByEmail(email); - if (target) { - const originRecord = - await this.prisma.workspaceUserPermission.findFirst({ - where: { - workspaceId, - userId: target.id, - }, - }); - // only invite if the user is not already in the workspace - if (originRecord) return originRecord.id; - } else { - target = await this.users.createAnonymousUser(email, { - registered: false, + const inviteId = await this.permissions.grant( + workspaceId, + target.id, + permission + ); + if (sendInviteMail) { + const inviteInfo = await this.getInviteInfo(inviteId); + + try { + await this.mailer.sendInviteEmail(email, inviteId, { + workspace: { + id: inviteInfo.workspace.id, + name: inviteInfo.workspace.name, + avatar: inviteInfo.workspace.avatar, + }, + user: { + avatar: inviteInfo.user?.avatarUrl || '', + name: inviteInfo.user?.name || '', + }, }); - } - - const inviteId = await this.permissions.grant( - workspaceId, - target.id, - permission - ); - if (sendInviteMail) { - const inviteInfo = await this.getInviteInfo(inviteId); - - try { - await this.mailer.sendInviteEmail(email, inviteId, { - workspace: { - id: inviteInfo.workspace.id, - name: inviteInfo.workspace.name, - avatar: inviteInfo.workspace.avatar, - }, - user: { - avatar: inviteInfo.user?.avatarUrl || '', - name: inviteInfo.user?.name || '', - }, - }); - } catch (e) { - const ret = await this.permissions.revokeWorkspace( - workspaceId, - target.id + } catch (e) { + const ret = await this.permissions.revokeWorkspace( + workspaceId, + target.id + ); + + if (!ret) { + this.logger.fatal( + `failed to send ${workspaceId} invite email to ${email} and failed to revoke permission: ${inviteId}, ${e}` ); - - if (!ret) { - this.logger.fatal( - `failed to send ${workspaceId} invite email to ${email} and failed to revoke permission: ${inviteId}, ${e}` - ); - } else { - this.logger.warn( - `failed to send ${workspaceId} invite email to ${email}, but successfully revoked permission: ${e}` - ); - } - return new InternalServerErrorException( - 'Failed to send invite email. Please try again.' + } else { + this.logger.warn( + `failed to send ${workspaceId} invite email to ${email}, but successfully revoked permission: ${e}` ); } + return new InternalServerErrorException( + 'Failed to send invite email. Please try again.' + ); } - return inviteId; - } catch (e) { - this.logger.error('failed to invite user', e); - return new TooManyRequestsException('Server is busy'); } + return inviteId; } @Throttle({ diff --git a/packages/backend/server/src/fundamentals/error/index.ts b/packages/backend/server/src/fundamentals/error/index.ts index 71ecd22a46462..0681702e4c3f7 100644 --- a/packages/backend/server/src/fundamentals/error/index.ts +++ b/packages/backend/server/src/fundamentals/error/index.ts @@ -1,2 +1 @@ export * from './payment-required'; -export * from './too-many-requests'; diff --git a/packages/backend/server/src/fundamentals/error/too-many-requests.ts b/packages/backend/server/src/fundamentals/error/too-many-requests.ts deleted file mode 100644 index 3a4a96130d32a..0000000000000 --- a/packages/backend/server/src/fundamentals/error/too-many-requests.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; - -export class TooManyRequestsException extends HttpException { - constructor(desc?: string, code: string = 'Too Many Requests') { - super( - HttpException.createBody( - desc ?? code, - code, - HttpStatus.TOO_MANY_REQUESTS - ), - HttpStatus.TOO_MANY_REQUESTS - ); - } -} diff --git a/packages/backend/server/src/fundamentals/graphql/index.ts b/packages/backend/server/src/fundamentals/graphql/index.ts index 04b5c6a1d2f11..3f53ea05ddbb5 100644 --- a/packages/backend/server/src/fundamentals/graphql/index.ts +++ b/packages/backend/server/src/fundamentals/graphql/index.ts @@ -11,12 +11,6 @@ import { GraphQLError } from 'graphql'; import { Config } from '../config'; import { GQLLoggerPlugin } from './logger-plugin'; -export type GraphqlContext = { - req: Request; - res: Response; - isAdminQuery: boolean; -}; - @Global() @Module({ imports: [ @@ -36,13 +30,7 @@ export type GraphqlContext = { : '../../../schema.gql' ), sortSchema: true, - context: ({ - req, - res, - }: { - req: Request; - res: Response; - }): GraphqlContext => ({ + context: ({ req, res }: { req: Request; res: Response }) => ({ req, res, isAdminQuery: false, diff --git a/packages/backend/server/src/fundamentals/index.ts b/packages/backend/server/src/fundamentals/index.ts index 9c125c7ccab80..02c5515e16dbe 100644 --- a/packages/backend/server/src/fundamentals/index.ts +++ b/packages/backend/server/src/fundamentals/index.ts @@ -14,23 +14,14 @@ export { } from './config'; export * from './error'; export { EventEmitter, type EventPayload, OnEvent } from './event'; -export type { GraphqlContext } from './graphql'; export { CryptoHelper, URLHelper } from './helpers'; export { MailService } from './mailer'; export { CallCounter, CallTimer, metrics } from './metrics'; -export { - BucketService, - LockGuard, - MUTEX_RETRY, - MUTEX_WAIT, - MutexService, -} from './mutex'; export { getOptionalModuleMetadata, GlobalExceptionFilter, OptionalModule, } from './nestjs'; -export type { PrismaTransaction } from './prisma'; export * from './storage'; export { type StorageProvider, StorageProviderFactory } from './storage'; export { AuthThrottlerGuard, CloudThrottlerGuard, Throttle } from './throttler'; diff --git a/packages/backend/server/src/fundamentals/mutex/bucket.ts b/packages/backend/server/src/fundamentals/mutex/bucket.ts deleted file mode 100644 index 446676cb39e1b..0000000000000 --- a/packages/backend/server/src/fundamentals/mutex/bucket.ts +++ /dev/null @@ -1,15 +0,0 @@ -export class BucketService { - private readonly bucket = new Map(); - - get(key: string) { - return this.bucket.get(key); - } - - set(key: string, value: string) { - this.bucket.set(key, value); - } - - delete(key: string) { - this.bucket.delete(key); - } -} diff --git a/packages/backend/server/src/fundamentals/mutex/index.ts b/packages/backend/server/src/fundamentals/mutex/index.ts deleted file mode 100644 index 5c50fba547eeb..0000000000000 --- a/packages/backend/server/src/fundamentals/mutex/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { BucketService } from './bucket'; -import { MutexService } from './mutex'; - -@Global() -@Module({ - providers: [BucketService, MutexService], - exports: [BucketService, MutexService], -}) -export class MutexModule {} - -export { BucketService, MutexService }; -export { LockGuard, MUTEX_RETRY, MUTEX_WAIT } from './mutex'; diff --git a/packages/backend/server/src/fundamentals/mutex/mutex.ts b/packages/backend/server/src/fundamentals/mutex/mutex.ts deleted file mode 100644 index ffdd8eb8897b4..0000000000000 --- a/packages/backend/server/src/fundamentals/mutex/mutex.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { randomUUID } from 'node:crypto'; -import { setTimeout } from 'node:timers/promises'; - -import { Inject, Injectable, Logger, Scope } from '@nestjs/common'; -import { CONTEXT } from '@nestjs/graphql'; - -import type { GraphqlContext } from '../graphql'; -import { BucketService } from './bucket'; - -export class LockGuard - implements AsyncDisposable -{ - constructor( - private readonly mutex: M, - private readonly key: string - ) {} - - async [Symbol.asyncDispose]() { - return this.mutex.unlock(this.key); - } -} - -export const MUTEX_RETRY = 5; -export const MUTEX_WAIT = 100; - -@Injectable({ scope: Scope.REQUEST }) -export class MutexService { - protected logger = new Logger(MutexService.name); - - constructor( - @Inject(CONTEXT) private readonly context: GraphqlContext, - private readonly bucket: BucketService - ) {} - - protected getId() { - let id = this.context.req.headers['x-transaction-id'] as string; - - if (!id) { - id = randomUUID(); - this.context.req.headers['x-transaction-id'] = id; - } - - return id; - } - - /** - * lock an resource and return a lock guard, which will release the lock when disposed - * - * if the lock is not available, it will retry for [MUTEX_RETRY] times - * - * usage: - * ```typescript - * { - * // lock is acquired here - * await using lock = await mutex.lock('resource-key'); - * if (lock) { - * // do something - * } else { - * // failed to lock - * } - * } - * // lock is released here - * ``` - * @param key resource key - * @returns LockGuard - */ - async lock(key: string): Promise { - const id = this.getId(); - const fetchLock = async (retry: number): Promise => { - if (retry === 0) { - this.logger.error( - `Failed to fetch lock ${key} after ${MUTEX_RETRY} retry` - ); - return undefined; - } - const current = this.bucket.get(key); - if (current && current !== id) { - this.logger.warn( - `Failed to fetch lock ${key}, retrying in ${MUTEX_WAIT} ms` - ); - await setTimeout(MUTEX_WAIT * (MUTEX_RETRY - retry + 1)); - return fetchLock(retry - 1); - } - this.bucket.set(key, id); - return new LockGuard(this, key); - }; - - return fetchLock(MUTEX_RETRY); - } - - async unlock(key: string): Promise { - if (this.bucket.get(key) === this.getId()) { - this.bucket.delete(key); - } - } -} diff --git a/packages/backend/server/src/fundamentals/prisma/index.ts b/packages/backend/server/src/fundamentals/prisma/index.ts index 517997fec5255..535e238122e64 100644 --- a/packages/backend/server/src/fundamentals/prisma/index.ts +++ b/packages/backend/server/src/fundamentals/prisma/index.ts @@ -16,7 +16,3 @@ const clientProvider: Provider = { }) export class PrismaModule {} export { PrismaService } from './service'; - -export type PrismaTransaction = Parameters< - Parameters[0] ->[0]; diff --git a/packages/backend/server/src/plugins/redis/index.ts b/packages/backend/server/src/plugins/redis/index.ts index 1ca721147aa1b..58d82c464232d 100644 --- a/packages/backend/server/src/plugins/redis/index.ts +++ b/packages/backend/server/src/plugins/redis/index.ts @@ -1,27 +1,18 @@ import { Global, Provider, Type } from '@nestjs/common'; -import { CONTEXT } from '@nestjs/graphql'; import { Redis, type RedisOptions } from 'ioredis'; import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'; -import { - BucketService, - Cache, - type GraphqlContext, - MutexService, - SessionCache, -} from '../../fundamentals'; +import { Cache, SessionCache } from '../../fundamentals'; import { ThrottlerStorage } from '../../fundamentals/throttler'; import { SocketIoAdapterImpl } from '../../fundamentals/websocket'; import { Plugin } from '../registry'; import { RedisCache } from './cache'; import { CacheRedis, - MutexRedis, SessionRedis, SocketIoRedis, ThrottlerRedis, } from './instances'; -import { MutexRedisService } from './mutex'; import { createSockerIoAdapterImpl } from './ws-adapter'; function makeProvider(token: Type, impl: Type): Provider { @@ -56,31 +47,15 @@ const socketIoRedisAdapterProvider: Provider = { inject: [SocketIoRedis], }; -// mutex -const mutexRedisAdapterProvider: Provider = { - provide: MutexService, - useFactory: (redis: Redis, ctx: GraphqlContext, bucket: BucketService) => { - return new MutexRedisService(redis, ctx, bucket); - }, - inject: [MutexRedis, CONTEXT, BucketService], -}; - @Global() @Plugin({ name: 'redis', - providers: [ - CacheRedis, - SessionRedis, - ThrottlerRedis, - SocketIoRedis, - MutexRedis, - ], + providers: [CacheRedis, SessionRedis, ThrottlerRedis, SocketIoRedis], overrides: [ cacheProvider, sessionCacheProvider, socketIoRedisAdapterProvider, throttlerStorageProvider, - mutexRedisAdapterProvider, ], requires: ['plugins.redis.host'], }) diff --git a/packages/backend/server/src/plugins/redis/instances.ts b/packages/backend/server/src/plugins/redis/instances.ts index 8fbd13b0c685a..1e85dec622f76 100644 --- a/packages/backend/server/src/plugins/redis/instances.ts +++ b/packages/backend/server/src/plugins/redis/instances.ts @@ -54,10 +54,3 @@ export class SocketIoRedis extends Redis { super({ ...config.plugins.redis, db: (config.plugins.redis?.db ?? 0) + 3 }); } } - -@Injectable() -export class MutexRedis extends Redis { - constructor(config: Config) { - super({ ...config.plugins.redis, db: (config.plugins.redis?.db ?? 0) + 4 }); - } -} diff --git a/packages/backend/server/src/plugins/redis/mutex.ts b/packages/backend/server/src/plugins/redis/mutex.ts deleted file mode 100644 index 9006507f08752..0000000000000 --- a/packages/backend/server/src/plugins/redis/mutex.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { setTimeout } from 'node:timers/promises'; - -import { Injectable, Logger } from '@nestjs/common'; -import Redis, { Command } from 'ioredis'; - -import { - BucketService, - type GraphqlContext, - LockGuard, - MUTEX_RETRY, - MUTEX_WAIT, - MutexService, -} from '../../fundamentals'; - -const lockScript = `local key = KEYS[1] -local clientId = ARGV[1] -local releaseTime = ARGV[2] - -if redis.call("get", key) == clientId or redis.call("set", key, clientId, "NX", "PX", releaseTime) then - return 1 -else - return 0 -end`; -const unlockScript = `local key = KEYS[1] -local clientId = ARGV[1] - -if redis.call("get", key) == clientId then - return redis.call("del", key) -else - return 0 -end`; - -@Injectable() -export class MutexRedisService extends MutexService { - constructor( - private readonly redis: Redis, - context: GraphqlContext, - bucket: BucketService - ) { - super(context, bucket); - this.logger = new Logger(MutexRedisService.name); - } - - override async lock( - key: string, - releaseTimeInMS: number = 200 - ): Promise { - const clientId = this.getId(); - this.logger.debug(`Client ${clientId} lock try to lock ${key}`); - const releaseTime = releaseTimeInMS.toString(); - - const fetchLock = async (retry: number): Promise => { - if (retry === 0) { - this.logger.error( - `Failed to fetch lock ${key} after ${MUTEX_RETRY} retry` - ); - return undefined; - } - try { - const success = await this.redis.sendCommand( - new Command('EVAL', [lockScript, '1', key, clientId, releaseTime]) - ); - if (success === 1) { - return new LockGuard(this, key); - } else { - this.logger.warn( - `Failed to fetch lock ${key}, retrying in ${MUTEX_WAIT} ms` - ); - await setTimeout(MUTEX_WAIT * (MUTEX_RETRY - retry + 1)); - return fetchLock(retry - 1); - } - } catch (error: any) { - this.logger.error( - `Unexpected error when fetch lock ${key}: ${error.message}` - ); - return undefined; - } - }; - - return fetchLock(MUTEX_RETRY); - } - - override async unlock(key: string, ignoreUnlockFail = false): Promise { - const clientId = this.getId(); - const result = await this.redis.sendCommand( - new Command('EVAL', [unlockScript, '1', key, clientId]) - ); - if (result === 0) { - if (!ignoreUnlockFail) { - throw new Error(`Failed to release lock ${key}`); - } else { - this.logger.warn(`Failed to release lock ${key}`); - } - } - } -} diff --git a/packages/backend/server/tests/tsconfig.json b/packages/backend/server/tests/tsconfig.json index 445549efc4909..b67a10b39d379 100644 --- a/packages/backend/server/tests/tsconfig.json +++ b/packages/backend/server/tests/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../../../tsconfig.json", "compilerOptions": { "composite": true, - "target": "ES2022", + "target": "ESNext", "emitDecoratorMetadata": true, "experimentalDecorators": true, "rootDir": ".", diff --git a/packages/backend/server/tests/workspace-invite.e2e.ts b/packages/backend/server/tests/workspace-invite.e2e.ts index 6a0c12018b3ef..767e518e2fd8f 100644 --- a/packages/backend/server/tests/workspace-invite.e2e.ts +++ b/packages/backend/server/tests/workspace-invite.e2e.ts @@ -104,7 +104,7 @@ test('should create user if not exist', async t => { const user = await auth.getUserByEmail('u2@affine.pro'); t.not(user, undefined, 'failed to create user'); - t.is(user?.name, 'u2', 'failed to create user'); + t.is(user?.name, 'Unnamed', 'failed to create user'); }); test('should invite a user by link', async t => { @@ -255,25 +255,3 @@ test('should support pagination for member', async t => { ); t.is(secondPageWorkspace.members.length, 1, 'failed to check invite id'); }); - -test('should limit member count correctly', async t => { - const { app } = t.context; - const u1 = await signUp(app, 'u1', 'u1@affine.pro', '1'); - for (let i = 0; i < 10; i++) { - const workspace = await createWorkspace(app, u1.token.token); - await Promise.allSettled( - Array.from({ length: 10 }).map(async (_, i) => - inviteUser( - app, - u1.token.token, - workspace.id, - `u${i}@affine.pro`, - 'Admin' - ) - ) - ); - - const ws = await getWorkspace(app, u1.token.token, workspace.id); - t.assert(ws.members.length <= 3, 'failed to check member list'); - } -}); diff --git a/packages/backend/server/tsconfig.json b/packages/backend/server/tsconfig.json index ec754fecdb2a3..4fc8005390f6f 100644 --- a/packages/backend/server/tsconfig.json +++ b/packages/backend/server/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../../tsconfig.json", "compilerOptions": { "composite": true, - "target": "ES2022", + "target": "ESNext", "module": "ESNext", "emitDecoratorMetadata": true, "experimentalDecorators": true,