diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index cb26bc18c..753749fbc 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -374,7 +374,9 @@ jobs: DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Run init-db script - run: yarn workspace @affine/server exec node --loader ts-node/esm/transpile-only ./scripts/init-db.ts + run: | + yarn workspace @affine/server data-migration run + yarn workspace @affine/server exec node --loader ts-node/esm/transpile-only ./scripts/init-db.ts env: DATABASE_URL: postgresql://affine:affine@localhost:5432/affine @@ -464,7 +466,9 @@ jobs: DATABASE_URL: postgresql://affine:affine@localhost:5432/affine - name: Run init-db script - run: yarn workspace @affine/server exec node --loader ts-node/esm/transpile-only ./scripts/init-db.ts + run: | + yarn workspace @affine/server data-migration run + yarn workspace @affine/server exec node --loader ts-node/esm/transpile-only ./scripts/init-db.ts - name: Download storage.node uses: actions/download-artifact@v3 with: diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 4c39a3c98..08b446e59 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -135,7 +135,8 @@ "ENABLE_LOCAL_EMAIL": "true", "OAUTH_EMAIL_LOGIN": "noreply@toeverything.info", "OAUTH_EMAIL_PASSWORD": "affine", - "OAUTH_EMAIL_SENDER": "noreply@toeverything.info" + "OAUTH_EMAIL_SENDER": "noreply@toeverything.info", + "FEATURES_EARLY_ACCESS_PREVIEW": "false" } }, "nodemonConfig": { diff --git a/packages/backend/server/schema.prisma b/packages/backend/server/schema.prisma index fae3ff0b1..b9dc73f2d 100644 --- a/packages/backend/server/schema.prisma +++ b/packages/backend/server/schema.prisma @@ -28,6 +28,7 @@ model User { invoices UserInvoice[] workspacePermissions WorkspaceUserPermission[] pagePermissions WorkspacePageUserPermission[] + UserQuotaGates UserQuotaGates[] @@map("users") } @@ -157,6 +158,33 @@ model Features { @@map("features") } +// quota gates is a way to enable/disable quotas for a user +// for example, pro plan is a quota that allow some users access to more resources after they pay +model UserQuotaGates { + id String @id @default(uuid()) @db.VarChar + userId String @map("user_id") @db.VarChar + quotaId String? @db.VarChar + + reason String @db.VarChar + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + quota UserQuotas? @relation(fields: [quotaId], references: [id]) + + @@map("user_quota_gates") +} + +model UserQuotas { + id String @id @default(uuid()) @db.VarChar + quota String @db.VarChar + configs Json @db.Json + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) + + UserQuotaGates UserQuotaGates[] + + @@map("user_quotas") +} + model Account { id String @id @default(cuid()) userId String @map("user_id") diff --git a/packages/backend/server/src/config/def.ts b/packages/backend/server/src/config/def.ts index 725e616e5..145e1b3b5 100644 --- a/packages/backend/server/src/config/def.ts +++ b/packages/backend/server/src/config/def.ts @@ -188,11 +188,6 @@ export interface AFFiNEConfig { fs: { path: string; }; - /** - * default storage quota - * @default 10 * 1024 * 1024 * 1024 (10GB) - */ - quota: number; }; /** diff --git a/packages/backend/server/src/config/default.ts b/packages/backend/server/src/config/default.ts index b89f4f68b..aa2f0ba76 100644 --- a/packages/backend/server/src/config/default.ts +++ b/packages/backend/server/src/config/default.ts @@ -58,7 +58,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { AFFINE_SERVER_HOST: 'host', AFFINE_SERVER_SUB_PATH: 'path', AFFINE_ENV: 'affineEnv', - AFFINE_FREE_USER_QUOTA: 'objectStorage.quota', DATABASE_URL: 'db.url', ENABLE_R2_OBJECT_STORAGE: ['objectStorage.r2.enabled', 'boolean'], R2_OBJECT_STORAGE_ACCOUNT_ID: 'objectStorage.r2.accountId', @@ -192,8 +191,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => { fs: { path: join(homedir(), '.affine-storage'), }, - // 10GB - quota: 10 * 1024 * 1024 * 1024, }, rateLimiter: { ttl: 60, diff --git a/packages/backend/server/src/data/migrations/1698652531198-user-features-init.ts b/packages/backend/server/src/data/migrations/1698652531198-user-features-init.ts index b4990c011..d6336dcfd 100644 --- a/packages/backend/server/src/data/migrations/1698652531198-user-features-init.ts +++ b/packages/backend/server/src/data/migrations/1698652531198-user-features-init.ts @@ -1,12 +1,39 @@ import { + CommonFeature, FeatureKind, Features, FeatureType, - upsertFeature, -} from '../../modules/features'; -import { Quotas } from '../../modules/quota'; +} from '../../modules/features/types'; +import { Quotas } from '../../modules/quota/types'; import { PrismaService } from '../../prisma'; +// upgrade features from lower version to higher version +async function upsertFeature( + db: PrismaService, + feature: CommonFeature +): Promise { + const hasEqualOrGreaterVersion = + (await db.features.count({ + where: { + feature: feature.feature, + version: { + gte: feature.version, + }, + }, + })) > 0; + // will not update exists version + if (!hasEqualOrGreaterVersion) { + await db.features.create({ + data: { + feature: feature.feature, + type: feature.type, + version: feature.version, + configs: feature.configs, + }, + }); + } +} + export class UserFeaturesInit1698652531198 { // do the migration static async up(db: PrismaService) { diff --git a/packages/backend/server/src/modules/auth/next-auth-options.ts b/packages/backend/server/src/modules/auth/next-auth-options.ts index 5e0317b50..81bf7308a 100644 --- a/packages/backend/server/src/modules/auth/next-auth-options.ts +++ b/packages/backend/server/src/modules/auth/next-auth-options.ts @@ -11,8 +11,8 @@ import Google from 'next-auth/providers/google'; import { Config } from '../../config'; import { PrismaService } from '../../prisma'; import { SessionService } from '../../session'; -import { NewFeaturesKind } from '../users/types'; -import { isStaff } from '../users/utils'; +import { FeatureType } from '../features'; +import { Quota_FreePlanV1 } from '../quota'; import { MailService } from './mailer'; import { decode, @@ -44,6 +44,17 @@ export const NextAuthOptionsProvider: FactoryProvider = { email: data.email, avatarUrl: '', emailVerified: data.emailVerified, + features: { + create: { + reason: 'created by email sign up', + activated: true, + feature: { + connect: { + feature_version: Quota_FreePlanV1, + }, + }, + }, + }, }; if (data.email && !data.name) { userData.name = data.email.split('@')[0]; @@ -223,18 +234,23 @@ export const NextAuthOptionsProvider: FactoryProvider = { } const email = profile?.email ?? user.email; if (email) { - if (isStaff(email)) { - return true; - } - return prisma.newFeaturesWaitingList - .findUnique({ + // FIXME: cannot inject FeatureManagementService here + // it will cause prisma.account to be undefined + // then prismaAdapter.getUserByAccount will throw error + if (email.endsWith('@toeverything.info')) return true; + return prisma.userFeatures + .count({ where: { - email, - type: NewFeaturesKind.EarlyAccess, + user: { + email, + }, + feature: { + feature: FeatureType.EarlyAccess, + }, + activated: true, }, }) - .then(user => !!user) - .catch(() => false); + .then(count => count > 0); } return false; }, diff --git a/packages/backend/server/src/modules/auth/service.ts b/packages/backend/server/src/modules/auth/service.ts index b6150a65d..d544c4165 100644 --- a/packages/backend/server/src/modules/auth/service.ts +++ b/packages/backend/server/src/modules/auth/service.ts @@ -14,6 +14,7 @@ import { nanoid } from 'nanoid'; import { Config } from '../../config'; import { PrismaService } from '../../prisma'; import { verifyChallengeResponse } from '../../storage'; +import { Quota_FreePlanV1 } from '../quota'; import { MailService } from './mailer'; export type UserClaim = Pick< @@ -190,6 +191,17 @@ export class AuthService { name, email, password: hashedPassword, + features: { + create: { + reason: 'created by api sign up', + activated: true, + feature: { + connect: { + feature_version: Quota_FreePlanV1, + }, + }, + }, + }, }, }); } @@ -209,6 +221,17 @@ export class AuthService { data: { name: 'Unnamed', email, + features: { + create: { + reason: 'created by invite sign up', + activated: true, + feature: { + connect: { + feature_version: Quota_FreePlanV1, + }, + }, + }, + }, }, }); } @@ -258,6 +281,7 @@ export class AuthService { }, }); } + async changeEmail(id: string, newEmail: string): Promise { const user = await this.prisma.user.findUnique({ where: { diff --git a/packages/backend/server/src/modules/features/index.ts b/packages/backend/server/src/modules/features/index.ts index ebb93dd4a..d1e689fdf 100644 --- a/packages/backend/server/src/modules/features/index.ts +++ b/packages/backend/server/src/modules/features/index.ts @@ -3,34 +3,6 @@ import { Module } from '@nestjs/common'; import { PrismaService } from '../../prisma'; import { FeatureService } from './configure'; import { FeatureManagementService } from './feature'; -import type { CommonFeature } from './types'; - -// upgrade features from lower version to higher version -async function upsertFeature( - db: PrismaService, - feature: CommonFeature -): Promise { - const hasEqualOrGreaterVersion = - (await db.features.count({ - where: { - feature: feature.feature, - version: { - gte: feature.version, - }, - }, - })) > 0; - // will not update exists version - if (!hasEqualOrGreaterVersion) { - await db.features.create({ - data: { - feature: feature.feature, - type: feature.type, - version: feature.version, - configs: feature.configs, - }, - }); - } -} /** * Feature module provider pre-user feature flag management. @@ -46,9 +18,4 @@ export class FeatureModule {} export type { CommonFeature, Feature } from './types'; export { FeatureKind, Features, FeatureType } from './types'; -export { - FeatureManagementService, - FeatureService, - PrismaService, - upsertFeature, -}; +export { FeatureManagementService, FeatureService, PrismaService }; diff --git a/packages/backend/server/src/modules/index.ts b/packages/backend/server/src/modules/index.ts index 92bc986c9..0b22eeb06 100644 --- a/packages/backend/server/src/modules/index.ts +++ b/packages/backend/server/src/modules/index.ts @@ -6,6 +6,7 @@ import { GqlModule } from '../graphql.module'; import { ServerConfigModule } from './config'; import { DocModule } from './doc'; import { PaymentModule } from './payment'; +import { QuotaModule } from './quota'; import { SelfHostedModule } from './self-hosted'; import { SyncModule } from './sync'; import { UsersModule } from './users'; @@ -37,7 +38,8 @@ switch (SERVER_FLAVOR) { WorkspaceModule, UsersModule, DocModule, - PaymentModule + PaymentModule, + QuotaModule ); break; case 'allinone': @@ -48,6 +50,7 @@ switch (SERVER_FLAVOR) { GqlModule, WorkspaceModule, UsersModule, + QuotaModule, SyncModule, DocModule, PaymentModule diff --git a/packages/backend/server/src/modules/payment/index.ts b/packages/backend/server/src/modules/payment/index.ts index 30393504b..f7aec52c4 100644 --- a/packages/backend/server/src/modules/payment/index.ts +++ b/packages/backend/server/src/modules/payment/index.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; -import { UsersModule } from '../users'; +import { FeatureModule } from '../features'; import { SubscriptionResolver, UserSubscriptionResolver } from './resolver'; import { ScheduleManager } from './schedule'; import { SubscriptionService } from './service'; @@ -8,7 +8,7 @@ import { StripeProvider } from './stripe'; import { StripeWebhook } from './webhook'; @Module({ - imports: [UsersModule], + imports: [FeatureModule], providers: [ ScheduleManager, StripeProvider, diff --git a/packages/backend/server/src/modules/payment/service.ts b/packages/backend/server/src/modules/payment/service.ts index c30ac0327..aabb3c3a8 100644 --- a/packages/backend/server/src/modules/payment/service.ts +++ b/packages/backend/server/src/modules/payment/service.ts @@ -11,7 +11,7 @@ import Stripe from 'stripe'; import { Config } from '../../config'; import { PrismaService } from '../../prisma'; -import { UsersService } from '../users'; +import { FeatureManagementService } from '../features'; import { ScheduleManager } from './schedule'; const OnEvent = ( @@ -82,8 +82,8 @@ export class SubscriptionService { config: Config, private readonly stripe: Stripe, private readonly db: PrismaService, - private readonly user: UsersService, - private readonly scheduleManager: ScheduleManager + private readonly scheduleManager: ScheduleManager, + private readonly features: FeatureManagementService ) { this.paymentConfig = config.payment; @@ -658,7 +658,7 @@ export class SubscriptionService { user: User, couponType: CouponType ): Promise { - const earlyAccess = await this.user.isEarlyAccessUser(user.email); + const earlyAccess = await this.features.canEarlyAccess(user.email); if (earlyAccess) { try { const coupon = await this.stripe.coupons.retrieve(couponType); diff --git a/packages/backend/server/src/modules/users/gates.ts b/packages/backend/server/src/modules/users/gates.ts deleted file mode 100644 index 191e00752..000000000 --- a/packages/backend/server/src/modules/users/gates.ts +++ /dev/null @@ -1,42 +0,0 @@ -type FeatureEarlyAccessPreview = { - whitelist: RegExp[]; -}; - -type FeatureStorageLimit = { - storageQuota: number; -}; - -type UserFeatureGate = { - earlyAccessPreview: FeatureEarlyAccessPreview; - freeUser: FeatureStorageLimit; - proUser: FeatureStorageLimit; -}; - -const UserLevel = { - freeUser: { - storageQuota: 10 * 1024 * 1024 * 1024, - }, - proUser: { - storageQuota: 100 * 1024 * 1024 * 1024, - }, -} satisfies Pick; - -export function getStorageQuota(features: string[]) { - for (const feature of features) { - if (feature in UserLevel) { - return UserLevel[feature as keyof typeof UserLevel].storageQuota; - } - } - return null; -} - -const UserType = { - earlyAccessPreview: { - whitelist: [/@toeverything\.info$/], - }, -} satisfies Pick; - -export const FeatureGates = { - ...UserType, - ...UserLevel, -} satisfies UserFeatureGate; diff --git a/packages/backend/server/src/modules/users/index.ts b/packages/backend/server/src/modules/users/index.ts index 308a1a9a1..8b506241d 100644 --- a/packages/backend/server/src/modules/users/index.ts +++ b/packages/backend/server/src/modules/users/index.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; +import { FeatureModule } from '../features'; import { StorageModule } from '../storage'; import { UserResolver } from './resolver'; import { UsersService } from './users'; @Module({ - imports: [StorageModule], + imports: [StorageModule, FeatureModule], providers: [UserResolver, UsersService], exports: [UsersService], }) diff --git a/packages/backend/server/src/modules/users/resolver.ts b/packages/backend/server/src/modules/users/resolver.ts index 806fa41ca..70d0333f3 100644 --- a/packages/backend/server/src/modules/users/resolver.ts +++ b/packages/backend/server/src/modules/users/resolver.ts @@ -12,7 +12,6 @@ import { Mutation, ObjectType, Query, - registerEnumType, ResolveField, Resolver, } from '@nestjs/graphql'; @@ -24,14 +23,10 @@ import { PrismaService } from '../../prisma/service'; import { CloudThrottlerGuard, Throttle } from '../../throttler'; import type { FileUpload } from '../../types'; import { Auth, CurrentUser, Public, Publicable } from '../auth/guard'; +import { AuthService } from '../auth/service'; +import { FeatureManagementService } from '../features'; import { StorageService } from '../storage/storage.service'; -import { NewFeaturesKind } from './types'; import { UsersService } from './users'; -import { isStaff } from './utils'; - -registerEnumType(NewFeaturesKind, { - name: 'NewFeaturesKind', -}); @ObjectType() export class UserType implements Partial { @@ -71,14 +66,6 @@ export class RemoveAvatar { success!: boolean; } -@ObjectType() -export class AddToNewFeaturesWaitingList { - @Field() - email!: string; - @Field(() => NewFeaturesKind, { description: 'New features kind' }) - type!: NewFeaturesKind; -} - /** * User resolver * All op rate limit: 10 req/m @@ -88,9 +75,11 @@ export class AddToNewFeaturesWaitingList { @Resolver(() => UserType) export class UserResolver { constructor( + private readonly auth: AuthService, private readonly prisma: PrismaService, private readonly storage: StorageService, - private readonly users: UsersService + private readonly users: UsersService, + private readonly feature: FeatureManagementService ) {} @Throttle({ @@ -138,7 +127,7 @@ export class UserResolver { }) @Public() async user(@Args('email') email: string) { - if (!(await this.users.canEarlyAccess(email))) { + if (!(await this.feature.canEarlyAccess(email))) { return new GraphQLError( `You don't have early access permission\nVisit https://community.affine.pro/c/insider-general/ for more information`, { @@ -233,27 +222,55 @@ export class UserResolver { ttl: 60, }, }) - @Mutation(() => AddToNewFeaturesWaitingList) - async addToNewFeaturesWaitingList( - @CurrentUser() user: UserType, - @Args('type', { - type: () => NewFeaturesKind, - }) - type: NewFeaturesKind, + @Mutation(() => Int) + async addToEarlyAccess( + @CurrentUser() currentUser: UserType, @Args('email') email: string - ): Promise { - if (!isStaff(user.email)) { + ): Promise { + if (!this.feature.isStaff(currentUser.email)) { throw new ForbiddenException('You are not allowed to do this'); } - await this.prisma.newFeaturesWaitingList.create({ - data: { - email, - type, - }, - }); - return { - email, - type, - }; + const user = await this.users.findUserByEmail(email); + if (user) { + return this.feature.addEarlyAccess(user.id); + } else { + const user = await this.auth.createAnonymousUser(email); + return this.feature.addEarlyAccess(user.id); + } + } + + @Throttle({ + default: { + limit: 10, + ttl: 60, + }, + }) + @Mutation(() => Int) + async removeEarlyAccess( + @CurrentUser() currentUser: UserType, + @Args('email') email: string + ): Promise { + if (!this.feature.isStaff(currentUser.email)) { + throw new ForbiddenException('You are not allowed to do this'); + } + const user = await this.users.findUserByEmail(email); + if (!user) { + throw new BadRequestException(`User ${email} not found`); + } + return this.feature.removeEarlyAccess(user.id); + } + + @Throttle({ + default: { + limit: 10, + ttl: 60, + }, + }) + @Query(() => [UserType]) + async listEarlyAccess(@CurrentUser() user: UserType): Promise { + if (!this.feature.isStaff(user.email)) { + throw new ForbiddenException('You are not allowed to do this'); + } + return this.feature.listEarlyAccess(); } } diff --git a/packages/backend/server/src/modules/users/types.ts b/packages/backend/server/src/modules/users/types.ts deleted file mode 100644 index 565ff3827..000000000 --- a/packages/backend/server/src/modules/users/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum NewFeaturesKind { - EarlyAccess, -} diff --git a/packages/backend/server/src/modules/users/users.ts b/packages/backend/server/src/modules/users/users.ts index 124264cba..6215ea443 100644 --- a/packages/backend/server/src/modules/users/users.ts +++ b/packages/backend/server/src/modules/users/users.ts @@ -1,54 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { Config } from '../../config'; import { PrismaService } from '../../prisma'; -import { getStorageQuota } from './gates'; -import { NewFeaturesKind } from './types'; -import { isStaff } from './utils'; @Injectable() export class UsersService { - constructor( - private readonly prisma: PrismaService, - private readonly config: Config - ) {} - - async canEarlyAccess(email: string) { - if (this.config.featureFlags.earlyAccessPreview && !isStaff(email)) { - return this.isEarlyAccessUser(email); - } else { - return true; - } - } - - async isEarlyAccessUser(email: string) { - return this.prisma.newFeaturesWaitingList - .count({ - where: { email, type: NewFeaturesKind.EarlyAccess }, - }) - .then(count => count > 0) - .catch(() => false); - } - - async getStorageQuotaById(id: string) { - const features = await this.prisma.user - .findUnique({ - where: { id }, - select: { - features: { - select: { - feature: true, - }, - }, - }, - }) - .then(user => user?.features.map(f => f.feature) ?? []); - - return ( - getStorageQuota(features.map(f => f.feature)) || - this.config.objectStorage.quota - ); - } + constructor(private readonly prisma: PrismaService) {} async findUserByEmail(email: string) { return this.prisma.user diff --git a/packages/backend/server/src/modules/users/utils.ts b/packages/backend/server/src/modules/users/utils.ts deleted file mode 100644 index a10cedbca..000000000 --- a/packages/backend/server/src/modules/users/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isStaff(email: string) { - return email.endsWith('@toeverything.info'); -} diff --git a/packages/backend/server/src/modules/workspaces/index.ts b/packages/backend/server/src/modules/workspaces/index.ts index 22ee57988..50259cd5d 100644 --- a/packages/backend/server/src/modules/workspaces/index.ts +++ b/packages/backend/server/src/modules/workspaces/index.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { DocModule } from '../doc'; +import { QuotaModule } from '../quota'; import { UsersService } from '../users'; import { WorkspacesController } from './controller'; import { DocHistoryResolver } from './history.resolver'; @@ -8,7 +9,7 @@ import { PermissionService } from './permission'; import { PagePermissionResolver, WorkspaceResolver } from './resolver'; @Module({ - imports: [DocModule], + imports: [DocModule, QuotaModule], controllers: [WorkspacesController], providers: [ WorkspaceResolver, diff --git a/packages/backend/server/src/modules/workspaces/resolver.ts b/packages/backend/server/src/modules/workspaces/resolver.ts index bbb772d76..7a81860ff 100644 --- a/packages/backend/server/src/modules/workspaces/resolver.ts +++ b/packages/backend/server/src/modules/workspaces/resolver.ts @@ -42,6 +42,7 @@ import { DocID } from '../../utils/doc'; import { Auth, CurrentUser, Public } from '../auth'; import { MailService } from '../auth/mailer'; import { AuthService } from '../auth/service'; +import { QuotaManagementService } from '../quota'; import { UsersService } from '../users'; import { UserType } from '../users/resolver'; import { PermissionService, PublicPageMode } from './permission'; @@ -148,6 +149,7 @@ export class WorkspaceResolver { private readonly permissions: PermissionService, private readonly users: UsersService, private readonly event: EventEmitter, + private readonly quota: QuotaManagementService, @Inject(StorageProvide) private readonly storage: Storage ) {} @@ -233,6 +235,14 @@ export class WorkspaceResolver { })); } + @ResolveField(() => Int, { + description: 'Blobs size of workspace', + complexity: 2, + }) + async blobsSize(@Parent() workspace: WorkspaceType) { + return this.storage.blobsSize([workspace.id]); + } + @Query(() => Boolean, { description: 'Get is owner of workspace', complexity: 2, @@ -656,36 +666,9 @@ export class WorkspaceResolver { return this.storage.listBlobs(workspaceId); } - @Query(() => WorkspaceBlobSizes) - async collectBlobSizes( - @CurrentUser() user: UserType, - @Args('workspaceId') workspaceId: string - ) { - await this.permissions.checkWorkspace(workspaceId, user.id); - - return this.storage.blobsSize([workspaceId]).then(size => ({ size })); - } - @Query(() => WorkspaceBlobSizes) async collectAllBlobSizes(@CurrentUser() user: UserType) { - const workspaces = await this.prisma.workspaceUserPermission - .findMany({ - where: { - userId: user.id, - accepted: true, - type: Permission.Owner, - }, - select: { - workspace: { - select: { - id: true, - }, - }, - }, - }) - .then(data => data.map(({ workspace }) => workspace.id)); - - const size = await this.storage.blobsSize(workspaces); + const size = await this.quota.getUserUsage(user.id); return { size }; } @@ -693,7 +676,7 @@ export class WorkspaceResolver { async checkBlobSize( @CurrentUser() user: UserType, @Args('workspaceId') workspaceId: string, - @Args('size', { type: () => Float }) size: number + @Args('size', { type: () => Float }) blobSize: number ) { const canWrite = await this.permissions.tryCheckWorkspace( workspaceId, @@ -701,13 +684,8 @@ export class WorkspaceResolver { Permission.Write ); if (canWrite) { - const { user } = await this.permissions.getWorkspaceOwner(workspaceId); - if (user) { - const quota = await this.users.getStorageQuotaById(user.id); - const { size: currentSize } = await this.collectAllBlobSizes(user); - - return { size: quota - (size + currentSize) }; - } + const size = await this.quota.checkBlobQuota(workspaceId, blobSize); + return { size }; } return false; } @@ -725,14 +703,12 @@ export class WorkspaceResolver { Permission.Write ); - // quota was apply to owner's account - const { user: owner } = - await this.permissions.getWorkspaceOwner(workspaceId); - if (!owner) return new NotFoundException('Workspace owner not found'); - const quota = await this.users.getStorageQuotaById(owner.id); - const { size } = await this.collectAllBlobSizes(owner); + const { quota, size } = await this.quota.getWorkspaceUsage(workspaceId); const checkExceeded = (recvSize: number) => { + if (!quota) { + throw new ForbiddenException('cannot find user quota'); + } if (size + recvSize > quota) { this.logger.log( `storage size limit exceeded: ${size + recvSize} > ${quota}` diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index c6ee591b2..3fc7457ff 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -51,17 +51,6 @@ type RemoveAvatar { success: Boolean! } -type AddToNewFeaturesWaitingList { - email: String! - - """New features kind""" - type: NewFeaturesKind! -} - -enum NewFeaturesKind { - EarlyAccess -} - type TokenType { token: String! refresh: String! @@ -196,6 +185,9 @@ type WorkspaceType { """Owner of workspace""" owner: UserType! + """Blobs size of workspace""" + blobsSize: Int! + """Shared pages of workspace""" sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages") @@ -269,7 +261,6 @@ type Query { """List blobs of workspace""" listBlobs(workspaceId: String!): [String!]! - collectBlobSizes(workspaceId: String!): WorkspaceBlobSizes! collectAllBlobSizes: WorkspaceBlobSizes! checkBlobSize(workspaceId: String!, size: Float!): WorkspaceBlobSizes! @@ -278,6 +269,7 @@ type Query { """Get user by email""" user(email: String!): UserType + listEarlyAccess: [UserType!]! prices: [SubscriptionPrice!]! } @@ -315,7 +307,8 @@ type Mutation { """Remove user avatar""" removeAvatar: RemoveAvatar! deleteAccount: DeleteAccount! - addToNewFeaturesWaitingList(type: NewFeaturesKind!, email: String!): AddToNewFeaturesWaitingList! + addToEarlyAccess(email: String!): Int! + removeEarlyAccess(email: String!): Int! """Create a subscription checkout link of stripe""" checkout(recurring: SubscriptionRecurring!, idempotencyKey: String!): String! diff --git a/packages/backend/server/tests/app.e2e.ts b/packages/backend/server/tests/app.e2e.ts index fb216ef17..08785d5e3 100644 --- a/packages/backend/server/tests/app.e2e.ts +++ b/packages/backend/server/tests/app.e2e.ts @@ -45,6 +45,13 @@ class FakePrisma { }, }; } + get newFeaturesWaitingList() { + return { + async findUnique() { + return null; + }, + }; + } } test.beforeEach(async t => { @@ -119,6 +126,7 @@ test('should find default user', async t => { }) .expect(200) .expect(res => { + console.log(res.body); t.is(res.body.data.user.email, 'alex.yang@example.org'); }); }); diff --git a/packages/backend/server/tests/auth.e2e.ts b/packages/backend/server/tests/auth.e2e.ts index 66bc18fc0..7beb208ae 100644 --- a/packages/backend/server/tests/auth.e2e.ts +++ b/packages/backend/server/tests/auth.e2e.ts @@ -9,6 +9,11 @@ import ava, { type TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from '../src/app'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; import { MailService } from '../src/modules/auth/mailer'; import { AuthService } from '../src/modules/auth/service'; import { @@ -37,6 +42,7 @@ test.beforeEach(async t => { await client.$disconnect(); const module = await Test.createTestingModule({ imports: [AppModule], + providers: [RevertCommand, RunCommand], }).compile(); const app = module.createNestApplication(); app.use( @@ -52,6 +58,13 @@ test.beforeEach(async t => { t.context.app = app; t.context.auth = auth; t.context.mail = mail; + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); }); test.afterEach(async t => { diff --git a/packages/backend/server/tests/auth.spec.ts b/packages/backend/server/tests/auth.spec.ts index c60f46d8a..b294702b0 100644 --- a/packages/backend/server/tests/auth.spec.ts +++ b/packages/backend/server/tests/auth.spec.ts @@ -4,6 +4,11 @@ import { PrismaClient } from '@prisma/client'; import test from 'ava'; import { ConfigModule } from '../src/config'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; import { GqlModule } from '../src/graphql.module'; import { AuthModule } from '../src/modules/auth'; import { AuthResolver } from '../src/modules/auth/resolver'; @@ -40,10 +45,19 @@ test.beforeEach(async () => { GqlModule, AuthModule, RateLimiterModule, + RevertCommand, + RunCommand, ], }).compile(); authService = module.get(AuthService); authResolver = module.get(AuthResolver); + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); }); test.afterEach.always(async () => { diff --git a/packages/backend/server/tests/mailer.e2e.ts b/packages/backend/server/tests/mailer.e2e.ts index 10b219325..bcecaeb4b 100644 --- a/packages/backend/server/tests/mailer.e2e.ts +++ b/packages/backend/server/tests/mailer.e2e.ts @@ -11,6 +11,11 @@ import { PrismaClient } from '@prisma/client'; import ava, { type TestFn } from 'ava'; import { ConfigModule } from '../src/config'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; import { GqlModule } from '../src/graphql.module'; import { AuthModule } from '../src/modules/auth'; import { AuthService } from '../src/modules/auth/service'; @@ -45,8 +50,16 @@ test.beforeEach(async t => { AuthModule, RateLimiterModule, ], + providers: [RevertCommand, RunCommand], }).compile(); t.context.auth = t.context.module.get(AuthService); + + // init features + const run = t.context.module.get(RunCommand); + const revert = t.context.module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); }); test.afterEach.always(async t => { diff --git a/packages/backend/server/tests/quota.spec.ts b/packages/backend/server/tests/quota.spec.ts new file mode 100644 index 000000000..2ea0d2b21 --- /dev/null +++ b/packages/backend/server/tests/quota.spec.ts @@ -0,0 +1,153 @@ +/// + +import { Test, TestingModule } from '@nestjs/testing'; +import { PrismaClient } from '@prisma/client'; +import ava, { type TestFn } from 'ava'; + +import { ConfigModule } from '../src/config'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; +import { AuthModule } from '../src/modules/auth'; +import { AuthService } from '../src/modules/auth/service'; +import { + QuotaManagementService, + QuotaModule, + Quotas, + QuotaService, + QuotaType, +} from '../src/modules/quota'; +import { PrismaModule } from '../src/prisma'; +import { StorageModule } from '../src/storage'; +import { RateLimiterModule } from '../src/throttler'; + +const test = ava as TestFn<{ + auth: AuthService; + quota: QuotaService; + storageQuota: QuotaManagementService; + app: TestingModule; +}>; + +// cleanup database before each test +test.beforeEach(async () => { + const client = new PrismaClient(); + await client.$connect(); + await client.user.deleteMany({}); + await client.$disconnect(); +}); + +test.beforeEach(async t => { + const module = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + auth: { + accessTokenExpiresIn: 1, + refreshTokenExpiresIn: 1, + leeway: 1, + }, + host: 'example.org', + https: true, + }), + StorageModule.forRoot(), + PrismaModule, + AuthModule, + QuotaModule, + RateLimiterModule, + RevertCommand, + RunCommand, + ], + }).compile(); + + const quota = module.get(QuotaService); + const storageQuota = module.get(QuotaManagementService); + const auth = module.get(AuthService); + + t.context.app = module; + t.context.quota = quota; + t.context.storageQuota = storageQuota; + t.context.auth = auth; + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); +}); + +test.afterEach.always(async t => { + await t.context.app.close(); +}); + +test('should be able to set quota', async t => { + const { auth, quota } = t.context; + + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const q1 = await quota.getUserQuota(u1.id); + t.truthy(q1, 'should have quota'); + t.is(q1?.feature.feature, QuotaType.Quota_FreePlanV1, 'should be free plan'); + + await quota.switchUserQuota(u1.id, QuotaType.Quota_ProPlanV1); + + const q2 = await quota.getUserQuota(u1.id); + t.is(q2?.feature.feature, QuotaType.Quota_ProPlanV1, 'should be pro plan'); + + const fail = quota.switchUserQuota(u1.id, 'not_exists_plan_v1' as QuotaType); + await t.throwsAsync(fail, { instanceOf: Error }, 'should throw error'); +}); + +test('should be able to check storage quota', async t => { + const { auth, quota, storageQuota } = t.context; + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const q1 = await storageQuota.getUserQuota(u1.id); + t.is(q1?.blobLimit, Quotas[0].configs.blobLimit, 'should be free plan'); + t.is(q1?.storageQuota, Quotas[0].configs.storageQuota, 'should be free plan'); + + await quota.switchUserQuota(u1.id, QuotaType.Quota_ProPlanV1); + const q2 = await storageQuota.getUserQuota(u1.id); + t.is(q2?.blobLimit, Quotas[1].configs.blobLimit, 'should be pro plan'); + t.is(q2?.storageQuota, Quotas[1].configs.storageQuota, 'should be pro plan'); +}); + +test('should be able revert quota', async t => { + const { auth, quota, storageQuota } = t.context; + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const q1 = await storageQuota.getUserQuota(u1.id); + t.is(q1?.blobLimit, Quotas[0].configs.blobLimit, 'should be free plan'); + t.is(q1?.storageQuota, Quotas[0].configs.storageQuota, 'should be free plan'); + + await quota.switchUserQuota(u1.id, QuotaType.Quota_ProPlanV1); + const q2 = await storageQuota.getUserQuota(u1.id); + t.is(q2?.blobLimit, Quotas[1].configs.blobLimit, 'should be pro plan'); + t.is(q2?.storageQuota, Quotas[1].configs.storageQuota, 'should be pro plan'); + + await quota.switchUserQuota(u1.id, QuotaType.Quota_FreePlanV1); + const q3 = await storageQuota.getUserQuota(u1.id); + t.is(q3?.blobLimit, Quotas[0].configs.blobLimit, 'should be free plan'); + + const quotas = await quota.getUserQuotas(u1.id); + t.is(quotas.length, 3, 'should have 3 quotas'); + t.is( + quotas[0].feature.feature, + QuotaType.Quota_FreePlanV1, + 'should be free plan' + ); + t.is( + quotas[1].feature.feature, + QuotaType.Quota_ProPlanV1, + 'should be pro plan' + ); + t.is( + quotas[2].feature.feature, + QuotaType.Quota_FreePlanV1, + 'should be free plan' + ); + t.is(quotas[0].activated, false, 'should be activated'); + t.is(quotas[1].activated, false, 'should be activated'); + t.is(quotas[2].activated, true, 'should be activated'); +}); diff --git a/packages/backend/server/tests/utils.ts b/packages/backend/server/tests/utils.ts index 916195b24..34e9f945f 100644 --- a/packages/backend/server/tests/utils.ts +++ b/packages/backend/server/tests/utils.ts @@ -324,7 +324,7 @@ async function listBlobs( return res.body.data.listBlobs; } -async function collectBlobSizes( +async function getWorkspaceBlobsSize( app: INestApplication, token: string, workspaceId: string @@ -335,14 +335,14 @@ async function collectBlobSizes( .send({ query: ` query { - collectBlobSizes(workspaceId: "${workspaceId}") { - size + workspace(id: "${workspaceId}") { + blobsSize } } `, }) .expect(200); - return res.body.data.collectBlobSizes.size; + return res.body.data.workspace.blobsSize; } async function collectAllBlobSizes( @@ -566,13 +566,13 @@ export { changeEmail, checkBlobSize, collectAllBlobSizes, - collectBlobSizes, createWorkspace, currentUser, flushDB, getInviteInfo, getPublicWorkspace, getWorkspace, + getWorkspaceBlobsSize, inviteUser, leaveWorkspace, listBlobs, diff --git a/packages/backend/server/tests/workspace-blobs.spec.ts b/packages/backend/server/tests/workspace-blobs.spec.ts index acd53ebe3..a7c0e8834 100644 --- a/packages/backend/server/tests/workspace-blobs.spec.ts +++ b/packages/backend/server/tests/workspace-blobs.spec.ts @@ -6,17 +6,24 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; import { AppModule } from '../src/app'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; +import { QuotaService, QuotaType } from '../src/modules/quota'; import { checkBlobSize, collectAllBlobSizes, - collectBlobSizes, createWorkspace, + getWorkspaceBlobsSize, listBlobs, setBlob, signUp, } from './utils'; let app: INestApplication; +let quota: QuotaService; const client = new PrismaClient(); @@ -33,6 +40,7 @@ test.beforeEach(async () => { test.beforeEach(async () => { const module = await Test.createTestingModule({ imports: [AppModule], + providers: [RevertCommand, RunCommand], }).compile(); app = module.createNestApplication(); app.use( @@ -41,6 +49,15 @@ test.beforeEach(async () => { maxFiles: 5, }) ); + quota = module.get(QuotaService); + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); + await app.init(); }); @@ -103,7 +120,7 @@ test('should calc blobs size', async t => { const buffer2 = Buffer.from([0, 1]); await setBlob(app, u1.token.token, workspace.id, buffer2); - const size = await collectBlobSizes(app, u1.token.token, workspace.id); + const size = await getWorkspaceBlobsSize(app, u1.token.token, workspace.id); t.is(size, 4, 'failed to collect blob sizes'); }); @@ -143,3 +160,39 @@ test('should calc all blobs size', async t => { ); t.is(size2, -1, 'failed to check blob size'); }); + +test('should be able calc quota after switch plan', async t => { + const u1 = await signUp(app, 'darksky', 'darksky@affine.pro', '1'); + + const workspace1 = await createWorkspace(app, u1.token.token); + + const buffer1 = Buffer.from([0, 0]); + await setBlob(app, u1.token.token, workspace1.id, buffer1); + const buffer2 = Buffer.from([0, 1]); + await setBlob(app, u1.token.token, workspace1.id, buffer2); + + const workspace2 = await createWorkspace(app, u1.token.token); + + const buffer3 = Buffer.from([0, 0]); + await setBlob(app, u1.token.token, workspace2.id, buffer3); + const buffer4 = Buffer.from([0, 1]); + await setBlob(app, u1.token.token, workspace2.id, buffer4); + + const size1 = await checkBlobSize( + app, + u1.token.token, + workspace1.id, + 10 * 1024 * 1024 * 1024 - 8 + ); + t.is(size1, 0, 'failed to check free plan blob size'); + + quota.switchUserQuota(u1.id, QuotaType.Quota_ProPlanV1); + + const size2 = await checkBlobSize( + app, + u1.token.token, + workspace1.id, + 100 * 1024 * 1024 * 1024 - 8 + ); + t.is(size2, 0, 'failed to check pro plan blob size'); +}); diff --git a/packages/backend/server/tests/workspace-invite.e2e.ts b/packages/backend/server/tests/workspace-invite.e2e.ts index dcaf623e1..87cf5289b 100644 --- a/packages/backend/server/tests/workspace-invite.e2e.ts +++ b/packages/backend/server/tests/workspace-invite.e2e.ts @@ -9,6 +9,11 @@ import ava, { type TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from '../src/app'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; import { MailService } from '../src/modules/auth/mailer'; import { AuthService } from '../src/modules/auth/service'; import { @@ -39,6 +44,7 @@ test.beforeEach(async t => { await client.$disconnect(); const module = await Test.createTestingModule({ imports: [AppModule], + providers: [RevertCommand, RunCommand], }).compile(); const app = module.createNestApplication(); app.use( @@ -51,9 +57,17 @@ test.beforeEach(async t => { const auth = module.get(AuthService); const mail = module.get(MailService); + t.context.app = app; t.context.auth = auth; t.context.mail = mail; + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); }); test.afterEach.always(async t => { diff --git a/packages/backend/server/tests/workspace-usage.spec.ts b/packages/backend/server/tests/workspace-usage.spec.ts index a7051b1eb..aecf55cbf 100644 --- a/packages/backend/server/tests/workspace-usage.spec.ts +++ b/packages/backend/server/tests/workspace-usage.spec.ts @@ -4,6 +4,7 @@ import ava, { type TestFn } from 'ava'; import { stub } from 'sinon'; import { AppModule } from '../src/app'; +import { Quotas } from '../src/modules/quota'; import { UsersService } from '../src/modules/users'; import { PermissionService } from '../src/modules/workspaces/permission'; import { WorkspaceResolver } from '../src/modules/workspaces/resolver'; @@ -20,6 +21,9 @@ class FakePermission { user: new FakePrisma().fakeUser, }; } + async getOwnedWorkspaces() { + return ['']; + } } const fakeUserService = { @@ -42,6 +46,19 @@ test.beforeEach(async t => { return []; }, }, + userFeatures: { + async count() { + return 1; + }, + async findFirst() { + return { + createdAt: new Date(), + expiredAt: new Date(), + reason: '', + feature: Quotas[0], + }; + }, + }, }) .overrideProvider(PermissionService) .useClass(FakePermission) diff --git a/packages/backend/server/tests/workspace.e2e.ts b/packages/backend/server/tests/workspace.e2e.ts index 41bf97bef..0e5aff80f 100644 --- a/packages/backend/server/tests/workspace.e2e.ts +++ b/packages/backend/server/tests/workspace.e2e.ts @@ -6,6 +6,11 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; import { AppModule } from '../src/app'; +import { + collectMigrations, + RevertCommand, + RunCommand, +} from '../src/data/commands/run'; import { acceptInviteById, createWorkspace, @@ -34,6 +39,7 @@ test.beforeEach(async t => { await client.$disconnect(); const module = await Test.createTestingModule({ imports: [AppModule], + providers: [RevertCommand, RunCommand], }).compile(); const app = module.createNestApplication(); app.use( @@ -45,6 +51,13 @@ test.beforeEach(async t => { await app.init(); t.context.client = client; t.context.app = app; + + // init features + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + const migrations = await collectMigrations(); + await Promise.allSettled(migrations.map(m => revert.run([m.name]))); + await run.run(); }); test.afterEach.always(async t => { diff --git a/packages/frontend/graphql/src/graphql/blob-size.gql b/packages/frontend/graphql/src/graphql/blob-size.gql index 4ef6d8e8a..bc8256ee2 100644 --- a/packages/frontend/graphql/src/graphql/blob-size.gql +++ b/packages/frontend/graphql/src/graphql/blob-size.gql @@ -1,5 +1,5 @@ query blobSizes($workspaceId: String!) { - collectBlobSizes(workspaceId: $workspaceId) { - size + workspace(id: $workspaceId) { + blobsSize } } diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index bc2d22c79..5d4b1696d 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -56,12 +56,12 @@ mutation setBlob($workspaceId: String!, $blob: Upload!) { export const blobSizesQuery = { id: 'blobSizesQuery' as const, operationName: 'blobSizes', - definitionName: 'collectBlobSizes', + definitionName: 'workspace', containsFile: false, query: ` query blobSizes($workspaceId: String!) { - collectBlobSizes(workspaceId: $workspaceId) { - size + workspace(id: $workspaceId) { + blobsSize } }`, }; diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index 7010647b6..caeccf371 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -40,10 +40,6 @@ export enum InvoiceStatus { Void = 'Void', } -export enum NewFeaturesKind { - EarlyAccess = 'EarlyAccess', -} - /** User permission in workspace */ export enum Permission { Admin = 'Admin', @@ -127,7 +123,7 @@ export type BlobSizesQueryVariables = Exact<{ export type BlobSizesQuery = { __typename?: 'Query'; - collectBlobSizes: { __typename?: 'WorkspaceBlobSizes'; size: number }; + workspace: { __typename?: 'WorkspaceType'; blobsSize: number }; }; export type AllBlobSizesQueryVariables = Exact<{ [key: string]: never }>; diff --git a/tests/kit/utils/cloud.ts b/tests/kit/utils/cloud.ts index 3059a2712..c1a429540 100644 --- a/tests/kit/utils/cloud.ts +++ b/tests/kit/utils/cloud.ts @@ -107,6 +107,20 @@ export async function createRandomUser(): Promise<{ ...user, emailVerified: new Date(), password: await hash(user.password), + features: { + create: { + reason: 'created by test case', + activated: true, + feature: { + connect: { + feature_version: { + feature: 'free_plan_v1', + version: 1, + }, + }, + }, + }, + }, }, }); diff --git a/yarn.lock b/yarn.lock index 2929dcd54..a52bd1f84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2487,7 +2487,30 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.0, @babel/core@npm:^7.18.9, @babel/core@npm:^7.20.12, @babel/core@npm:^7.20.7, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.3, @babel/core@npm:^7.7.5": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.16, @babel/core@npm:^7.14.0, @babel/core@npm:^7.20.12, @babel/core@npm:^7.20.7, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.5, @babel/core@npm:^7.22.9, @babel/core@npm:^7.7.5": + version: 7.23.2 + resolution: "@babel/core@npm:7.23.2" + dependencies: + "@ampproject/remapping": "npm:^2.2.0" + "@babel/code-frame": "npm:^7.22.13" + "@babel/generator": "npm:^7.23.0" + "@babel/helper-compilation-targets": "npm:^7.22.15" + "@babel/helper-module-transforms": "npm:^7.23.0" + "@babel/helpers": "npm:^7.23.2" + "@babel/parser": "npm:^7.23.0" + "@babel/template": "npm:^7.22.15" + "@babel/traverse": "npm:^7.23.2" + "@babel/types": "npm:^7.23.0" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: b69d7008695b2ac7a3a2db83c5c712fbb79f7031c4480f6351cde327930e38873003d1d021059b729a1d0cb48093f1d384c64269b78f6189f50051fe4f64dc2d + languageName: node + linkType: hard + +"@babel/core@npm:^7.18.9, @babel/core@npm:^7.23.3": version: 7.23.3 resolution: "@babel/core@npm:7.23.3" dependencies: @@ -2522,6 +2545,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/generator@npm:7.23.0" + dependencies: + "@babel/types": "npm:^7.23.0" + "@jridgewell/gen-mapping": "npm:^0.3.2" + "@jridgewell/trace-mapping": "npm:^0.3.17" + jsesc: "npm:^2.5.1" + checksum: bd1598bd356756065d90ce26968dd464ac2b915c67623f6f071fb487da5f9eb454031a380e20e7c9a7ce5c4a49d23be6cb9efde404952b0b3f3c0c3a9b73d68a + languageName: node + linkType: hard + "@babel/generator@npm:^7.23.6": version: 7.23.6 resolution: "@babel/generator@npm:7.23.6" @@ -2656,6 +2691,21 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/helper-module-transforms@npm:7.23.0" + dependencies: + "@babel/helper-environment-visitor": "npm:^7.22.20" + "@babel/helper-module-imports": "npm:^7.22.15" + "@babel/helper-simple-access": "npm:^7.22.5" + "@babel/helper-split-export-declaration": "npm:^7.22.6" + "@babel/helper-validator-identifier": "npm:^7.22.20" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: d72fe444f7b6c5aadaac8f393298d603eedd48e5dead67273a48e5c83a677cbccbd8a12a06c5bf5d97924666083279158a4bd0e799d28b86cbbfacba9e41f598 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.23.3": version: 7.23.3 resolution: "@babel/helper-module-transforms@npm:7.23.3" @@ -2740,6 +2790,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 7f275a7f1a9504da06afc33441e219796352a4a3d0288a961bc14d1e30e06833a71621b33c3e60ee3ac1ff3c502d55e392bcbc0665f6f9d2629809696fab7cdd + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.23.4": version: 7.23.4 resolution: "@babel/helper-string-parser@npm:7.23.4" @@ -4089,6 +4146,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.23.2": + version: 7.23.2 + resolution: "@babel/traverse@npm:7.23.2" + dependencies: + "@babel/code-frame": "npm:^7.22.13" + "@babel/generator": "npm:^7.23.0" + "@babel/helper-environment-visitor": "npm:^7.22.20" + "@babel/helper-function-name": "npm:^7.23.0" + "@babel/helper-hoist-variables": "npm:^7.22.5" + "@babel/helper-split-export-declaration": "npm:^7.22.6" + "@babel/parser": "npm:^7.23.0" + "@babel/types": "npm:^7.23.0" + debug: "npm:^4.1.0" + globals: "npm:^11.1.0" + checksum: e4fcb8f8395804956df4ae1301230a14b6eb35b74a7058a0e0b40f6f4be7281e619e6dafe400e833d4512da5d61cf17ea177d04b00a8f7cf3d8d69aff83ca3d8 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.23.6": version: 7.23.6 resolution: "@babel/traverse@npm:7.23.6" @@ -4107,7 +4182,18 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.13, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.17.0, @babel/types@npm:^7.18.13, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.22.5" + "@babel/helper-validator-identifier": "npm:^7.22.20" + to-fast-properties: "npm:^2.0.0" + checksum: ca5b896a26c91c5672254725c4c892a35567d2122afc47bd5331d1611a7f9230c19fc9ef591a5a6f80bf0d80737e104a9ac205c96447c74bee01d4319db58001 + languageName: node + linkType: hard + +"@babel/types@npm:^7.18.9, @babel/types@npm:^7.23.3, @babel/types@npm:^7.23.4": version: 7.23.4 resolution: "@babel/types@npm:7.23.4" dependencies: @@ -5318,6 +5404,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-arm64@npm:0.19.7" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/android-arm64@npm:0.19.8" @@ -5346,6 +5439,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-arm@npm:0.19.7" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/android-arm@npm:0.19.8" @@ -5374,6 +5474,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/android-x64@npm:0.19.7" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/android-x64@npm:0.19.8" @@ -5402,6 +5509,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/darwin-arm64@npm:0.19.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/darwin-arm64@npm:0.19.8" @@ -5430,6 +5544,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/darwin-x64@npm:0.19.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/darwin-x64@npm:0.19.8" @@ -5458,6 +5579,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/freebsd-arm64@npm:0.19.7" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/freebsd-arm64@npm:0.19.8" @@ -5486,6 +5614,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/freebsd-x64@npm:0.19.7" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/freebsd-x64@npm:0.19.8" @@ -5514,6 +5649,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-arm64@npm:0.19.7" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-arm64@npm:0.19.8" @@ -5542,6 +5684,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-arm@npm:0.19.7" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-arm@npm:0.19.8" @@ -5570,6 +5719,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-ia32@npm:0.19.7" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-ia32@npm:0.19.8" @@ -5598,6 +5754,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-loong64@npm:0.19.7" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-loong64@npm:0.19.8" @@ -5626,6 +5789,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-mips64el@npm:0.19.7" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-mips64el@npm:0.19.8" @@ -5654,6 +5824,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-ppc64@npm:0.19.7" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-ppc64@npm:0.19.8" @@ -5682,6 +5859,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-riscv64@npm:0.19.7" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-riscv64@npm:0.19.8" @@ -5710,6 +5894,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-s390x@npm:0.19.7" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-s390x@npm:0.19.8" @@ -5738,6 +5929,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/linux-x64@npm:0.19.7" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/linux-x64@npm:0.19.8" @@ -5766,6 +5964,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/netbsd-x64@npm:0.19.7" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/netbsd-x64@npm:0.19.8" @@ -5794,6 +5999,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/openbsd-x64@npm:0.19.7" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/openbsd-x64@npm:0.19.8" @@ -5822,6 +6034,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/sunos-x64@npm:0.19.7" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/sunos-x64@npm:0.19.8" @@ -5850,6 +6069,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-arm64@npm:0.19.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/win32-arm64@npm:0.19.8" @@ -5878,6 +6104,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-ia32@npm:0.19.7" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/win32-ia32@npm:0.19.8" @@ -5906,6 +6139,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.19.7": + version: 0.19.7 + resolution: "@esbuild/win32-x64@npm:0.19.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.19.8": version: 0.19.8 resolution: "@esbuild/win32-x64@npm:0.19.8" @@ -6692,7 +6932,20 @@ __metadata: languageName: node linkType: hard -"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.10, @graphql-tools/utils@npm:^10.0.11, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5, @graphql-tools/utils@npm:^10.0.8": +"@graphql-tools/utils@npm:^10.0.0, @graphql-tools/utils@npm:^10.0.2, @graphql-tools/utils@npm:^10.0.5": + version: 10.0.7 + resolution: "@graphql-tools/utils@npm:10.0.7" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.1.1" + dset: "npm:^3.1.2" + tslib: "npm:^2.4.0" + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 50383782f7e2667f44891f060a0b91f1c551becccf919f041e0ce70bafd42021bf8b446273ce2b3efcd2de53d0b59b99f954c2f4094041fba86d478f616a30ea + languageName: node + linkType: hard + +"@graphql-tools/utils@npm:^10.0.10, @graphql-tools/utils@npm:^10.0.11, @graphql-tools/utils@npm:^10.0.8": version: 10.0.11 resolution: "@graphql-tools/utils@npm:10.0.11" dependencies: @@ -11799,6 +12052,16 @@ __metadata: languageName: node linkType: hard +"@smithy/abort-controller@npm:^2.0.12": + version: 2.0.12 + resolution: "@smithy/abort-controller@npm:2.0.12" + dependencies: + "@smithy/types": "npm:^2.4.0" + tslib: "npm:^2.5.0" + checksum: ade23e7e6d3b30615cb376e2578b7f9545a2e0c1ab67f570a76ce5dde3547c6dde0964976e3e914f4844df0dd0ddf9ddc38820ba69f61eed2fffe6e563d0c4e4 + languageName: node + linkType: hard + "@smithy/abort-controller@npm:^2.0.14": version: 2.0.14 resolution: "@smithy/abort-controller@npm:2.0.14" @@ -12061,7 +12324,7 @@ __metadata: languageName: node linkType: hard -"@smithy/node-http-handler@npm:^2.1.10, @smithy/node-http-handler@npm:^2.1.8, @smithy/node-http-handler@npm:^2.1.9": +"@smithy/node-http-handler@npm:^2.1.10, @smithy/node-http-handler@npm:^2.1.9": version: 2.1.10 resolution: "@smithy/node-http-handler@npm:2.1.10" dependencies: @@ -12074,6 +12337,19 @@ __metadata: languageName: node linkType: hard +"@smithy/node-http-handler@npm:^2.1.8": + version: 2.1.8 + resolution: "@smithy/node-http-handler@npm:2.1.8" + dependencies: + "@smithy/abort-controller": "npm:^2.0.12" + "@smithy/protocol-http": "npm:^3.0.8" + "@smithy/querystring-builder": "npm:^2.0.12" + "@smithy/types": "npm:^2.4.0" + tslib: "npm:^2.5.0" + checksum: aca079234edc6d8946df0408949af3eee0f862225e6ebafcd72123b96f087213e2a4f7bb71d6d6a21eebc78dae636f5c999c91700f7577c6ba61998f05b070ae + languageName: node + linkType: hard + "@smithy/property-provider@npm:^2.0.0, @smithy/property-provider@npm:^2.0.15": version: 2.0.15 resolution: "@smithy/property-provider@npm:2.0.15" @@ -12084,7 +12360,7 @@ __metadata: languageName: node linkType: hard -"@smithy/protocol-http@npm:^3.0.10, @smithy/protocol-http@npm:^3.0.8, @smithy/protocol-http@npm:^3.0.9": +"@smithy/protocol-http@npm:^3.0.10, @smithy/protocol-http@npm:^3.0.9": version: 3.0.10 resolution: "@smithy/protocol-http@npm:3.0.10" dependencies: @@ -12094,6 +12370,27 @@ __metadata: languageName: node linkType: hard +"@smithy/protocol-http@npm:^3.0.8": + version: 3.0.8 + resolution: "@smithy/protocol-http@npm:3.0.8" + dependencies: + "@smithy/types": "npm:^2.4.0" + tslib: "npm:^2.5.0" + checksum: 014df5fe50231434b5227b8359f31d925de77c581d576170b4d62fdd64cb3c24b35aeec636f229aba3cd303f32a12e0c1be3355af883dbe73f995e4b975ac0f7 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^2.0.12": + version: 2.0.12 + resolution: "@smithy/querystring-builder@npm:2.0.12" + dependencies: + "@smithy/types": "npm:^2.4.0" + "@smithy/util-uri-escape": "npm:^2.0.0" + tslib: "npm:^2.5.0" + checksum: e3ba93e7195b6240b052ff88833685f926ee14191880214bf7c073aae5315e4956b57762a96745e2bd2f1d2bc7f2fa66f797400a739fdde7c13bed83d2c56cdf + languageName: node + linkType: hard + "@smithy/querystring-builder@npm:^2.0.14": version: 2.0.14 resolution: "@smithy/querystring-builder@npm:2.0.14" @@ -14604,7 +14901,7 @@ __metadata: languageName: node linkType: hard -"@types/lodash-es@npm:^4.17.11, @types/lodash-es@npm:^4.17.6, @types/lodash-es@npm:^4.17.9": +"@types/lodash-es@npm:^4.17.11": version: 4.17.12 resolution: "@types/lodash-es@npm:4.17.12" dependencies: @@ -14613,6 +14910,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash-es@npm:^4.17.6, @types/lodash-es@npm:^4.17.9": + version: 4.17.9 + resolution: "@types/lodash-es@npm:4.17.9" + dependencies: + "@types/lodash": "npm:*" + checksum: 5e3a8a74134e67c37f1b8eb4a2897c88038f1b1bd7f508feec9e5561b52787d7efcc30c18981e9c6edec2b894f127b60312a431d98b84e12e785bea9cb5d1d40 + languageName: node + linkType: hard + "@types/lodash.debounce@npm:^4.0.7": version: 4.0.9 resolution: "@types/lodash.debounce@npm:4.0.9" @@ -14747,7 +15053,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.0.0, @types/node@npm:^18.11.18, @types/node@npm:^18.11.9": +"@types/node@npm:^18.0.0, @types/node@npm:^18.11.9": version: 18.18.13 resolution: "@types/node@npm:18.18.13" dependencies: @@ -14756,6 +15062,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.11.18": + version: 18.18.5 + resolution: "@types/node@npm:18.18.5" + checksum: a7363aab9f402290799d3e2696fbc70c76a8a65e2354f72b8f399c38edc346f600066f8ac59dde985cfc64160cfeb63ed7fc917aecdfe7ec469345d3ce029bda + languageName: node + linkType: hard + "@types/nodemailer@npm:^6.4.14": version: 6.4.14 resolution: "@types/nodemailer@npm:6.4.14" @@ -15077,7 +15390,16 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.10, @types/ws@npm:^8.5.5, @types/ws@npm:^8.5.7": +"@types/ws@npm:^8.0.0, @types/ws@npm:^8.5.5, @types/ws@npm:^8.5.7": + version: 8.5.7 + resolution: "@types/ws@npm:8.5.7" + dependencies: + "@types/node": "npm:*" + checksum: 48e426be74d6bdc176c06f98cc96f7fc91dba10aaf88c87108b57e1dba588f4607dcd062d7a83686a3857dc7af09fdd420d8a816c0306cb0362ece2f0e37983c + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.10": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" dependencies: @@ -16128,7 +16450,16 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.10.0, acorn@npm:^8.11.2, acorn@npm:^8.4.1, acorn@npm:^8.6.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": +"acorn@npm:^8.10.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" + bin: + acorn: bin/acorn + checksum: 522310c20fdc3c271caed3caf0f06c51d61cb42267279566edd1d58e83dbc12eebdafaab666a0f0be1b7ad04af9c6bc2a6f478690a9e6391c3c8b165ada917dd + languageName: node + linkType: hard + +"acorn@npm:^8.11.2, acorn@npm:^8.6.0": version: 8.11.2 resolution: "acorn@npm:8.11.2" bin: @@ -20758,7 +21089,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.19.3, esbuild@npm:^0.19.7": +"esbuild@npm:^0.19.3": version: 0.19.8 resolution: "esbuild@npm:0.19.8" dependencies: @@ -20835,6 +21166,83 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.19.7": + version: 0.19.7 + resolution: "esbuild@npm:0.19.7" + dependencies: + "@esbuild/android-arm": "npm:0.19.7" + "@esbuild/android-arm64": "npm:0.19.7" + "@esbuild/android-x64": "npm:0.19.7" + "@esbuild/darwin-arm64": "npm:0.19.7" + "@esbuild/darwin-x64": "npm:0.19.7" + "@esbuild/freebsd-arm64": "npm:0.19.7" + "@esbuild/freebsd-x64": "npm:0.19.7" + "@esbuild/linux-arm": "npm:0.19.7" + "@esbuild/linux-arm64": "npm:0.19.7" + "@esbuild/linux-ia32": "npm:0.19.7" + "@esbuild/linux-loong64": "npm:0.19.7" + "@esbuild/linux-mips64el": "npm:0.19.7" + "@esbuild/linux-ppc64": "npm:0.19.7" + "@esbuild/linux-riscv64": "npm:0.19.7" + "@esbuild/linux-s390x": "npm:0.19.7" + "@esbuild/linux-x64": "npm:0.19.7" + "@esbuild/netbsd-x64": "npm:0.19.7" + "@esbuild/openbsd-x64": "npm:0.19.7" + "@esbuild/sunos-x64": "npm:0.19.7" + "@esbuild/win32-arm64": "npm:0.19.7" + "@esbuild/win32-ia32": "npm:0.19.7" + "@esbuild/win32-x64": "npm:0.19.7" + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 326b9d98a77c5f2fb9a535b292bdc67c88bdfb4a19d29a221d65fd69f4800faea1f34947e8e6bc25ca3bd5db01f61c6968fec91f8c335e21e29b50330d90bd89 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -23081,7 +23489,7 @@ __metadata: languageName: node linkType: hard -"graphql-ws@npm:5.14.2, graphql-ws@npm:^5.14.0": +"graphql-ws@npm:5.14.2": version: 5.14.2 resolution: "graphql-ws@npm:5.14.2" peerDependencies: @@ -23090,6 +23498,15 @@ __metadata: languageName: node linkType: hard +"graphql-ws@npm:^5.14.0": + version: 5.14.1 + resolution: "graphql-ws@npm:5.14.1" + peerDependencies: + graphql: ">=0.11 <=16" + checksum: d3b0917df3ae20aa65b5193527f7005cdce35d7c59856adc3aad2ff128952b9f6f207c0cc4f92bb5d7b5210d458243a93fb3e58339253ed830a5b17619d21ea8 + languageName: node + linkType: hard + "graphql@npm:0.13.1 - 16, graphql@npm:^16.0.0, graphql@npm:^16.8.1": version: 16.8.1 resolution: "graphql@npm:16.8.1" @@ -26223,7 +26640,19 @@ __metadata: languageName: node linkType: hard -"lib0@npm:^0.2.74, lib0@npm:^0.2.85, lib0@npm:^0.2.86, lib0@npm:^0.2.87, lib0@npm:^0.2.88": +"lib0@npm:^0.2.74, lib0@npm:^0.2.85, lib0@npm:^0.2.87": + version: 0.2.87 + resolution: "lib0@npm:0.2.87" + dependencies: + isomorphic.js: "npm:^0.2.4" + bin: + 0gentesthtml: bin/gentesthtml.js + 0serve: bin/0serve.js + checksum: 078a55d1a6eb85a6fe836cf8c1268fa4761e475679db7f31c4993acd8a3a15e16c20e4481da9239402ff8a02a3718be9572767b3d6759e23a3518bcc0cc6b520 + languageName: node + linkType: hard + +"lib0@npm:^0.2.86, lib0@npm:^0.2.88": version: 0.2.88 resolution: "lib0@npm:0.2.88" dependencies: @@ -37089,7 +37518,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.3.4, yaml@npm:^2.2.1, yaml@npm:^2.3.1, yaml@npm:^2.3.4": +"yaml@npm:2.3.4, yaml@npm:^2.3.4": version: 2.3.4 resolution: "yaml@npm:2.3.4" checksum: f8207ce43065a22268a2806ea6a0fa3974c6fde92b4b2fa0082357e487bc333e85dc518910007e7ac001b532c7c84bd3eccb6c7757e94182b564028b0008f44b @@ -37103,6 +37532,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.1, yaml@npm:^2.3.1": + version: 2.3.3 + resolution: "yaml@npm:2.3.3" + checksum: 3b1a974b9d3672c671d47099a41c0de77b7ff978d0849aa55a095587486e82cd072321d19f2b4c791a367f766310b5a82dff098839b0f4ddcbbbe477f82dfb07 + languageName: node + linkType: hard + "yargs-parser@npm:21.1.1, yargs-parser@npm:>=21.1.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1"