mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat: integrate user usage into apis (#5075)
This commit is contained in:
8
.github/workflows/build-test.yml
vendored
8
.github/workflows/build-test.yml
vendored
@@ -374,7 +374,9 @@ jobs:
|
|||||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||||
|
|
||||||
- name: Run init-db script
|
- 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:
|
env:
|
||||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||||
|
|
||||||
@@ -464,7 +466,9 @@ jobs:
|
|||||||
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
DATABASE_URL: postgresql://affine:affine@localhost:5432/affine
|
||||||
|
|
||||||
- name: Run init-db script
|
- 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
|
- name: Download storage.node
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -135,7 +135,8 @@
|
|||||||
"ENABLE_LOCAL_EMAIL": "true",
|
"ENABLE_LOCAL_EMAIL": "true",
|
||||||
"OAUTH_EMAIL_LOGIN": "noreply@toeverything.info",
|
"OAUTH_EMAIL_LOGIN": "noreply@toeverything.info",
|
||||||
"OAUTH_EMAIL_PASSWORD": "affine",
|
"OAUTH_EMAIL_PASSWORD": "affine",
|
||||||
"OAUTH_EMAIL_SENDER": "noreply@toeverything.info"
|
"OAUTH_EMAIL_SENDER": "noreply@toeverything.info",
|
||||||
|
"FEATURES_EARLY_ACCESS_PREVIEW": "false"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
"nodemonConfig": {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ model User {
|
|||||||
invoices UserInvoice[]
|
invoices UserInvoice[]
|
||||||
workspacePermissions WorkspaceUserPermission[]
|
workspacePermissions WorkspaceUserPermission[]
|
||||||
pagePermissions WorkspacePageUserPermission[]
|
pagePermissions WorkspacePageUserPermission[]
|
||||||
|
UserQuotaGates UserQuotaGates[]
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
@@ -157,6 +158,33 @@ model Features {
|
|||||||
@@map("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 {
|
model Account {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String @map("user_id")
|
userId String @map("user_id")
|
||||||
|
|||||||
@@ -188,11 +188,6 @@ export interface AFFiNEConfig {
|
|||||||
fs: {
|
fs: {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* default storage quota
|
|
||||||
* @default 10 * 1024 * 1024 * 1024 (10GB)
|
|
||||||
*/
|
|
||||||
quota: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
|||||||
AFFINE_SERVER_HOST: 'host',
|
AFFINE_SERVER_HOST: 'host',
|
||||||
AFFINE_SERVER_SUB_PATH: 'path',
|
AFFINE_SERVER_SUB_PATH: 'path',
|
||||||
AFFINE_ENV: 'affineEnv',
|
AFFINE_ENV: 'affineEnv',
|
||||||
AFFINE_FREE_USER_QUOTA: 'objectStorage.quota',
|
|
||||||
DATABASE_URL: 'db.url',
|
DATABASE_URL: 'db.url',
|
||||||
ENABLE_R2_OBJECT_STORAGE: ['objectStorage.r2.enabled', 'boolean'],
|
ENABLE_R2_OBJECT_STORAGE: ['objectStorage.r2.enabled', 'boolean'],
|
||||||
R2_OBJECT_STORAGE_ACCOUNT_ID: 'objectStorage.r2.accountId',
|
R2_OBJECT_STORAGE_ACCOUNT_ID: 'objectStorage.r2.accountId',
|
||||||
@@ -192,8 +191,6 @@ export const getDefaultAFFiNEConfig: () => AFFiNEConfig = () => {
|
|||||||
fs: {
|
fs: {
|
||||||
path: join(homedir(), '.affine-storage'),
|
path: join(homedir(), '.affine-storage'),
|
||||||
},
|
},
|
||||||
// 10GB
|
|
||||||
quota: 10 * 1024 * 1024 * 1024,
|
|
||||||
},
|
},
|
||||||
rateLimiter: {
|
rateLimiter: {
|
||||||
ttl: 60,
|
ttl: 60,
|
||||||
|
|||||||
@@ -1,12 +1,39 @@
|
|||||||
import {
|
import {
|
||||||
|
CommonFeature,
|
||||||
FeatureKind,
|
FeatureKind,
|
||||||
Features,
|
Features,
|
||||||
FeatureType,
|
FeatureType,
|
||||||
upsertFeature,
|
} from '../../modules/features/types';
|
||||||
} from '../../modules/features';
|
import { Quotas } from '../../modules/quota/types';
|
||||||
import { Quotas } from '../../modules/quota';
|
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
|
|
||||||
|
// upgrade features from lower version to higher version
|
||||||
|
async function upsertFeature(
|
||||||
|
db: PrismaService,
|
||||||
|
feature: CommonFeature
|
||||||
|
): Promise<void> {
|
||||||
|
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 {
|
export class UserFeaturesInit1698652531198 {
|
||||||
// do the migration
|
// do the migration
|
||||||
static async up(db: PrismaService) {
|
static async up(db: PrismaService) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import Google from 'next-auth/providers/google';
|
|||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { SessionService } from '../../session';
|
import { SessionService } from '../../session';
|
||||||
import { NewFeaturesKind } from '../users/types';
|
import { FeatureType } from '../features';
|
||||||
import { isStaff } from '../users/utils';
|
import { Quota_FreePlanV1 } from '../quota';
|
||||||
import { MailService } from './mailer';
|
import { MailService } from './mailer';
|
||||||
import {
|
import {
|
||||||
decode,
|
decode,
|
||||||
@@ -44,6 +44,17 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
|||||||
email: data.email,
|
email: data.email,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
emailVerified: data.emailVerified,
|
emailVerified: data.emailVerified,
|
||||||
|
features: {
|
||||||
|
create: {
|
||||||
|
reason: 'created by email sign up',
|
||||||
|
activated: true,
|
||||||
|
feature: {
|
||||||
|
connect: {
|
||||||
|
feature_version: Quota_FreePlanV1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (data.email && !data.name) {
|
if (data.email && !data.name) {
|
||||||
userData.name = data.email.split('@')[0];
|
userData.name = data.email.split('@')[0];
|
||||||
@@ -223,18 +234,23 @@ export const NextAuthOptionsProvider: FactoryProvider<NextAuthOptions> = {
|
|||||||
}
|
}
|
||||||
const email = profile?.email ?? user.email;
|
const email = profile?.email ?? user.email;
|
||||||
if (email) {
|
if (email) {
|
||||||
if (isStaff(email)) {
|
// FIXME: cannot inject FeatureManagementService here
|
||||||
return true;
|
// it will cause prisma.account to be undefined
|
||||||
}
|
// then prismaAdapter.getUserByAccount will throw error
|
||||||
return prisma.newFeaturesWaitingList
|
if (email.endsWith('@toeverything.info')) return true;
|
||||||
.findUnique({
|
return prisma.userFeatures
|
||||||
|
.count({
|
||||||
where: {
|
where: {
|
||||||
|
user: {
|
||||||
email,
|
email,
|
||||||
type: NewFeaturesKind.EarlyAccess,
|
},
|
||||||
|
feature: {
|
||||||
|
feature: FeatureType.EarlyAccess,
|
||||||
|
},
|
||||||
|
activated: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(user => !!user)
|
.then(count => count > 0);
|
||||||
.catch(() => false);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { nanoid } from 'nanoid';
|
|||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { verifyChallengeResponse } from '../../storage';
|
import { verifyChallengeResponse } from '../../storage';
|
||||||
|
import { Quota_FreePlanV1 } from '../quota';
|
||||||
import { MailService } from './mailer';
|
import { MailService } from './mailer';
|
||||||
|
|
||||||
export type UserClaim = Pick<
|
export type UserClaim = Pick<
|
||||||
@@ -190,6 +191,17 @@ export class AuthService {
|
|||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
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: {
|
data: {
|
||||||
name: 'Unnamed',
|
name: 'Unnamed',
|
||||||
email,
|
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<User> {
|
async changeEmail(id: string, newEmail: string): Promise<User> {
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -3,34 +3,6 @@ import { Module } from '@nestjs/common';
|
|||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { FeatureService } from './configure';
|
import { FeatureService } from './configure';
|
||||||
import { FeatureManagementService } from './feature';
|
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<void> {
|
|
||||||
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.
|
* Feature module provider pre-user feature flag management.
|
||||||
@@ -46,9 +18,4 @@ export class FeatureModule {}
|
|||||||
|
|
||||||
export type { CommonFeature, Feature } from './types';
|
export type { CommonFeature, Feature } from './types';
|
||||||
export { FeatureKind, Features, FeatureType } from './types';
|
export { FeatureKind, Features, FeatureType } from './types';
|
||||||
export {
|
export { FeatureManagementService, FeatureService, PrismaService };
|
||||||
FeatureManagementService,
|
|
||||||
FeatureService,
|
|
||||||
PrismaService,
|
|
||||||
upsertFeature,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { GqlModule } from '../graphql.module';
|
|||||||
import { ServerConfigModule } from './config';
|
import { ServerConfigModule } from './config';
|
||||||
import { DocModule } from './doc';
|
import { DocModule } from './doc';
|
||||||
import { PaymentModule } from './payment';
|
import { PaymentModule } from './payment';
|
||||||
|
import { QuotaModule } from './quota';
|
||||||
import { SelfHostedModule } from './self-hosted';
|
import { SelfHostedModule } from './self-hosted';
|
||||||
import { SyncModule } from './sync';
|
import { SyncModule } from './sync';
|
||||||
import { UsersModule } from './users';
|
import { UsersModule } from './users';
|
||||||
@@ -37,7 +38,8 @@ switch (SERVER_FLAVOR) {
|
|||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
DocModule,
|
DocModule,
|
||||||
PaymentModule
|
PaymentModule,
|
||||||
|
QuotaModule
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'allinone':
|
case 'allinone':
|
||||||
@@ -48,6 +50,7 @@ switch (SERVER_FLAVOR) {
|
|||||||
GqlModule,
|
GqlModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
|
QuotaModule,
|
||||||
SyncModule,
|
SyncModule,
|
||||||
DocModule,
|
DocModule,
|
||||||
PaymentModule
|
PaymentModule
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { UsersModule } from '../users';
|
import { FeatureModule } from '../features';
|
||||||
import { SubscriptionResolver, UserSubscriptionResolver } from './resolver';
|
import { SubscriptionResolver, UserSubscriptionResolver } from './resolver';
|
||||||
import { ScheduleManager } from './schedule';
|
import { ScheduleManager } from './schedule';
|
||||||
import { SubscriptionService } from './service';
|
import { SubscriptionService } from './service';
|
||||||
@@ -8,7 +8,7 @@ import { StripeProvider } from './stripe';
|
|||||||
import { StripeWebhook } from './webhook';
|
import { StripeWebhook } from './webhook';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersModule],
|
imports: [FeatureModule],
|
||||||
providers: [
|
providers: [
|
||||||
ScheduleManager,
|
ScheduleManager,
|
||||||
StripeProvider,
|
StripeProvider,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Stripe from 'stripe';
|
|||||||
|
|
||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { UsersService } from '../users';
|
import { FeatureManagementService } from '../features';
|
||||||
import { ScheduleManager } from './schedule';
|
import { ScheduleManager } from './schedule';
|
||||||
|
|
||||||
const OnEvent = (
|
const OnEvent = (
|
||||||
@@ -82,8 +82,8 @@ export class SubscriptionService {
|
|||||||
config: Config,
|
config: Config,
|
||||||
private readonly stripe: Stripe,
|
private readonly stripe: Stripe,
|
||||||
private readonly db: PrismaService,
|
private readonly db: PrismaService,
|
||||||
private readonly user: UsersService,
|
private readonly scheduleManager: ScheduleManager,
|
||||||
private readonly scheduleManager: ScheduleManager
|
private readonly features: FeatureManagementService
|
||||||
) {
|
) {
|
||||||
this.paymentConfig = config.payment;
|
this.paymentConfig = config.payment;
|
||||||
|
|
||||||
@@ -658,7 +658,7 @@ export class SubscriptionService {
|
|||||||
user: User,
|
user: User,
|
||||||
couponType: CouponType
|
couponType: CouponType
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const earlyAccess = await this.user.isEarlyAccessUser(user.email);
|
const earlyAccess = await this.features.canEarlyAccess(user.email);
|
||||||
if (earlyAccess) {
|
if (earlyAccess) {
|
||||||
try {
|
try {
|
||||||
const coupon = await this.stripe.coupons.retrieve(couponType);
|
const coupon = await this.stripe.coupons.retrieve(couponType);
|
||||||
|
|||||||
@@ -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<UserFeatureGate, 'freeUser' | 'proUser'>;
|
|
||||||
|
|
||||||
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<UserFeatureGate, 'earlyAccessPreview'>;
|
|
||||||
|
|
||||||
export const FeatureGates = {
|
|
||||||
...UserType,
|
|
||||||
...UserLevel,
|
|
||||||
} satisfies UserFeatureGate;
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureModule } from '../features';
|
||||||
import { StorageModule } from '../storage';
|
import { StorageModule } from '../storage';
|
||||||
import { UserResolver } from './resolver';
|
import { UserResolver } from './resolver';
|
||||||
import { UsersService } from './users';
|
import { UsersService } from './users';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [StorageModule],
|
imports: [StorageModule, FeatureModule],
|
||||||
providers: [UserResolver, UsersService],
|
providers: [UserResolver, UsersService],
|
||||||
exports: [UsersService],
|
exports: [UsersService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Mutation,
|
Mutation,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Query,
|
Query,
|
||||||
registerEnumType,
|
|
||||||
ResolveField,
|
ResolveField,
|
||||||
Resolver,
|
Resolver,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
@@ -24,14 +23,10 @@ import { PrismaService } from '../../prisma/service';
|
|||||||
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
import { CloudThrottlerGuard, Throttle } from '../../throttler';
|
||||||
import type { FileUpload } from '../../types';
|
import type { FileUpload } from '../../types';
|
||||||
import { Auth, CurrentUser, Public, Publicable } from '../auth/guard';
|
import { Auth, CurrentUser, Public, Publicable } from '../auth/guard';
|
||||||
|
import { AuthService } from '../auth/service';
|
||||||
|
import { FeatureManagementService } from '../features';
|
||||||
import { StorageService } from '../storage/storage.service';
|
import { StorageService } from '../storage/storage.service';
|
||||||
import { NewFeaturesKind } from './types';
|
|
||||||
import { UsersService } from './users';
|
import { UsersService } from './users';
|
||||||
import { isStaff } from './utils';
|
|
||||||
|
|
||||||
registerEnumType(NewFeaturesKind, {
|
|
||||||
name: 'NewFeaturesKind',
|
|
||||||
});
|
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
export class UserType implements Partial<User> {
|
export class UserType implements Partial<User> {
|
||||||
@@ -71,14 +66,6 @@ export class RemoveAvatar {
|
|||||||
success!: boolean;
|
success!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class AddToNewFeaturesWaitingList {
|
|
||||||
@Field()
|
|
||||||
email!: string;
|
|
||||||
@Field(() => NewFeaturesKind, { description: 'New features kind' })
|
|
||||||
type!: NewFeaturesKind;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User resolver
|
* User resolver
|
||||||
* All op rate limit: 10 req/m
|
* All op rate limit: 10 req/m
|
||||||
@@ -88,9 +75,11 @@ export class AddToNewFeaturesWaitingList {
|
|||||||
@Resolver(() => UserType)
|
@Resolver(() => UserType)
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly auth: AuthService,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly storage: StorageService,
|
private readonly storage: StorageService,
|
||||||
private readonly users: UsersService
|
private readonly users: UsersService,
|
||||||
|
private readonly feature: FeatureManagementService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Throttle({
|
@Throttle({
|
||||||
@@ -138,7 +127,7 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
@Public()
|
@Public()
|
||||||
async user(@Args('email') email: string) {
|
async user(@Args('email') email: string) {
|
||||||
if (!(await this.users.canEarlyAccess(email))) {
|
if (!(await this.feature.canEarlyAccess(email))) {
|
||||||
return new GraphQLError(
|
return new GraphQLError(
|
||||||
`You don't have early access permission\nVisit https://community.affine.pro/c/insider-general/ for more information`,
|
`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,
|
ttl: 60,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@Mutation(() => AddToNewFeaturesWaitingList)
|
@Mutation(() => Int)
|
||||||
async addToNewFeaturesWaitingList(
|
async addToEarlyAccess(
|
||||||
@CurrentUser() user: UserType,
|
@CurrentUser() currentUser: UserType,
|
||||||
@Args('type', {
|
|
||||||
type: () => NewFeaturesKind,
|
|
||||||
})
|
|
||||||
type: NewFeaturesKind,
|
|
||||||
@Args('email') email: string
|
@Args('email') email: string
|
||||||
): Promise<AddToNewFeaturesWaitingList> {
|
): Promise<number> {
|
||||||
if (!isStaff(user.email)) {
|
if (!this.feature.isStaff(currentUser.email)) {
|
||||||
throw new ForbiddenException('You are not allowed to do this');
|
throw new ForbiddenException('You are not allowed to do this');
|
||||||
}
|
}
|
||||||
await this.prisma.newFeaturesWaitingList.create({
|
const user = await this.users.findUserByEmail(email);
|
||||||
data: {
|
if (user) {
|
||||||
email,
|
return this.feature.addEarlyAccess(user.id);
|
||||||
type,
|
} else {
|
||||||
|
const user = await this.auth.createAnonymousUser(email);
|
||||||
|
return this.feature.addEarlyAccess(user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throttle({
|
||||||
|
default: {
|
||||||
|
limit: 10,
|
||||||
|
ttl: 60,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
return {
|
@Mutation(() => Int)
|
||||||
email,
|
async removeEarlyAccess(
|
||||||
type,
|
@CurrentUser() currentUser: UserType,
|
||||||
};
|
@Args('email') email: string
|
||||||
|
): Promise<number> {
|
||||||
|
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<UserType[]> {
|
||||||
|
if (!this.feature.isStaff(user.email)) {
|
||||||
|
throw new ForbiddenException('You are not allowed to do this');
|
||||||
|
}
|
||||||
|
return this.feature.listEarlyAccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export enum NewFeaturesKind {
|
|
||||||
EarlyAccess,
|
|
||||||
}
|
|
||||||
@@ -1,54 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { Config } from '../../config';
|
|
||||||
import { PrismaService } from '../../prisma';
|
import { PrismaService } from '../../prisma';
|
||||||
import { getStorageQuota } from './gates';
|
|
||||||
import { NewFeaturesKind } from './types';
|
|
||||||
import { isStaff } from './utils';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
constructor(
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findUserByEmail(email: string) {
|
async findUserByEmail(email: string) {
|
||||||
return this.prisma.user
|
return this.prisma.user
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export function isStaff(email: string) {
|
|
||||||
return email.endsWith('@toeverything.info');
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { DocModule } from '../doc';
|
import { DocModule } from '../doc';
|
||||||
|
import { QuotaModule } from '../quota';
|
||||||
import { UsersService } from '../users';
|
import { UsersService } from '../users';
|
||||||
import { WorkspacesController } from './controller';
|
import { WorkspacesController } from './controller';
|
||||||
import { DocHistoryResolver } from './history.resolver';
|
import { DocHistoryResolver } from './history.resolver';
|
||||||
@@ -8,7 +9,7 @@ import { PermissionService } from './permission';
|
|||||||
import { PagePermissionResolver, WorkspaceResolver } from './resolver';
|
import { PagePermissionResolver, WorkspaceResolver } from './resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DocModule],
|
imports: [DocModule, QuotaModule],
|
||||||
controllers: [WorkspacesController],
|
controllers: [WorkspacesController],
|
||||||
providers: [
|
providers: [
|
||||||
WorkspaceResolver,
|
WorkspaceResolver,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { DocID } from '../../utils/doc';
|
|||||||
import { Auth, CurrentUser, Public } from '../auth';
|
import { Auth, CurrentUser, Public } from '../auth';
|
||||||
import { MailService } from '../auth/mailer';
|
import { MailService } from '../auth/mailer';
|
||||||
import { AuthService } from '../auth/service';
|
import { AuthService } from '../auth/service';
|
||||||
|
import { QuotaManagementService } from '../quota';
|
||||||
import { UsersService } from '../users';
|
import { UsersService } from '../users';
|
||||||
import { UserType } from '../users/resolver';
|
import { UserType } from '../users/resolver';
|
||||||
import { PermissionService, PublicPageMode } from './permission';
|
import { PermissionService, PublicPageMode } from './permission';
|
||||||
@@ -148,6 +149,7 @@ export class WorkspaceResolver {
|
|||||||
private readonly permissions: PermissionService,
|
private readonly permissions: PermissionService,
|
||||||
private readonly users: UsersService,
|
private readonly users: UsersService,
|
||||||
private readonly event: EventEmitter,
|
private readonly event: EventEmitter,
|
||||||
|
private readonly quota: QuotaManagementService,
|
||||||
@Inject(StorageProvide) private readonly storage: Storage
|
@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, {
|
@Query(() => Boolean, {
|
||||||
description: 'Get is owner of workspace',
|
description: 'Get is owner of workspace',
|
||||||
complexity: 2,
|
complexity: 2,
|
||||||
@@ -656,36 +666,9 @@ export class WorkspaceResolver {
|
|||||||
return this.storage.listBlobs(workspaceId);
|
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)
|
@Query(() => WorkspaceBlobSizes)
|
||||||
async collectAllBlobSizes(@CurrentUser() user: UserType) {
|
async collectAllBlobSizes(@CurrentUser() user: UserType) {
|
||||||
const workspaces = await this.prisma.workspaceUserPermission
|
const size = await this.quota.getUserUsage(user.id);
|
||||||
.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);
|
|
||||||
return { size };
|
return { size };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,7 +676,7 @@ export class WorkspaceResolver {
|
|||||||
async checkBlobSize(
|
async checkBlobSize(
|
||||||
@CurrentUser() user: UserType,
|
@CurrentUser() user: UserType,
|
||||||
@Args('workspaceId') workspaceId: string,
|
@Args('workspaceId') workspaceId: string,
|
||||||
@Args('size', { type: () => Float }) size: number
|
@Args('size', { type: () => Float }) blobSize: number
|
||||||
) {
|
) {
|
||||||
const canWrite = await this.permissions.tryCheckWorkspace(
|
const canWrite = await this.permissions.tryCheckWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -701,13 +684,8 @@ export class WorkspaceResolver {
|
|||||||
Permission.Write
|
Permission.Write
|
||||||
);
|
);
|
||||||
if (canWrite) {
|
if (canWrite) {
|
||||||
const { user } = await this.permissions.getWorkspaceOwner(workspaceId);
|
const size = await this.quota.checkBlobQuota(workspaceId, blobSize);
|
||||||
if (user) {
|
return { size };
|
||||||
const quota = await this.users.getStorageQuotaById(user.id);
|
|
||||||
const { size: currentSize } = await this.collectAllBlobSizes(user);
|
|
||||||
|
|
||||||
return { size: quota - (size + currentSize) };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -725,14 +703,12 @@ export class WorkspaceResolver {
|
|||||||
Permission.Write
|
Permission.Write
|
||||||
);
|
);
|
||||||
|
|
||||||
// quota was apply to owner's account
|
const { quota, size } = await this.quota.getWorkspaceUsage(workspaceId);
|
||||||
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 checkExceeded = (recvSize: number) => {
|
const checkExceeded = (recvSize: number) => {
|
||||||
|
if (!quota) {
|
||||||
|
throw new ForbiddenException('cannot find user quota');
|
||||||
|
}
|
||||||
if (size + recvSize > quota) {
|
if (size + recvSize > quota) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`storage size limit exceeded: ${size + recvSize} > ${quota}`
|
`storage size limit exceeded: ${size + recvSize} > ${quota}`
|
||||||
|
|||||||
@@ -51,17 +51,6 @@ type RemoveAvatar {
|
|||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddToNewFeaturesWaitingList {
|
|
||||||
email: String!
|
|
||||||
|
|
||||||
"""New features kind"""
|
|
||||||
type: NewFeaturesKind!
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NewFeaturesKind {
|
|
||||||
EarlyAccess
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenType {
|
type TokenType {
|
||||||
token: String!
|
token: String!
|
||||||
refresh: String!
|
refresh: String!
|
||||||
@@ -196,6 +185,9 @@ type WorkspaceType {
|
|||||||
"""Owner of workspace"""
|
"""Owner of workspace"""
|
||||||
owner: UserType!
|
owner: UserType!
|
||||||
|
|
||||||
|
"""Blobs size of workspace"""
|
||||||
|
blobsSize: Int!
|
||||||
|
|
||||||
"""Shared pages of workspace"""
|
"""Shared pages of workspace"""
|
||||||
sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages")
|
sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages")
|
||||||
|
|
||||||
@@ -269,7 +261,6 @@ type Query {
|
|||||||
|
|
||||||
"""List blobs of workspace"""
|
"""List blobs of workspace"""
|
||||||
listBlobs(workspaceId: String!): [String!]!
|
listBlobs(workspaceId: String!): [String!]!
|
||||||
collectBlobSizes(workspaceId: String!): WorkspaceBlobSizes!
|
|
||||||
collectAllBlobSizes: WorkspaceBlobSizes!
|
collectAllBlobSizes: WorkspaceBlobSizes!
|
||||||
checkBlobSize(workspaceId: String!, size: Float!): WorkspaceBlobSizes!
|
checkBlobSize(workspaceId: String!, size: Float!): WorkspaceBlobSizes!
|
||||||
|
|
||||||
@@ -278,6 +269,7 @@ type Query {
|
|||||||
|
|
||||||
"""Get user by email"""
|
"""Get user by email"""
|
||||||
user(email: String!): UserType
|
user(email: String!): UserType
|
||||||
|
listEarlyAccess: [UserType!]!
|
||||||
prices: [SubscriptionPrice!]!
|
prices: [SubscriptionPrice!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +307,8 @@ type Mutation {
|
|||||||
"""Remove user avatar"""
|
"""Remove user avatar"""
|
||||||
removeAvatar: RemoveAvatar!
|
removeAvatar: RemoveAvatar!
|
||||||
deleteAccount: DeleteAccount!
|
deleteAccount: DeleteAccount!
|
||||||
addToNewFeaturesWaitingList(type: NewFeaturesKind!, email: String!): AddToNewFeaturesWaitingList!
|
addToEarlyAccess(email: String!): Int!
|
||||||
|
removeEarlyAccess(email: String!): Int!
|
||||||
|
|
||||||
"""Create a subscription checkout link of stripe"""
|
"""Create a subscription checkout link of stripe"""
|
||||||
checkout(recurring: SubscriptionRecurring!, idempotencyKey: String!): String!
|
checkout(recurring: SubscriptionRecurring!, idempotencyKey: String!): String!
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ class FakePrisma {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
get newFeaturesWaitingList() {
|
||||||
|
return {
|
||||||
|
async findUnique() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test.beforeEach(async t => {
|
test.beforeEach(async t => {
|
||||||
@@ -119,6 +126,7 @@ test('should find default user', async t => {
|
|||||||
})
|
})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect(res => {
|
.expect(res => {
|
||||||
|
console.log(res.body);
|
||||||
t.is(res.body.data.user.email, 'alex.yang@example.org');
|
t.is(res.body.data.user.email, 'alex.yang@example.org');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import ava, { type TestFn } from 'ava';
|
|||||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||||
|
|
||||||
import { AppModule } from '../src/app';
|
import { AppModule } from '../src/app';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
import { MailService } from '../src/modules/auth/mailer';
|
import { MailService } from '../src/modules/auth/mailer';
|
||||||
import { AuthService } from '../src/modules/auth/service';
|
import { AuthService } from '../src/modules/auth/service';
|
||||||
import {
|
import {
|
||||||
@@ -37,6 +42,7 @@ test.beforeEach(async t => {
|
|||||||
await client.$disconnect();
|
await client.$disconnect();
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
|
providers: [RevertCommand, RunCommand],
|
||||||
}).compile();
|
}).compile();
|
||||||
const app = module.createNestApplication();
|
const app = module.createNestApplication();
|
||||||
app.use(
|
app.use(
|
||||||
@@ -52,6 +58,13 @@ test.beforeEach(async t => {
|
|||||||
t.context.app = app;
|
t.context.app = app;
|
||||||
t.context.auth = auth;
|
t.context.auth = auth;
|
||||||
t.context.mail = mail;
|
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 => {
|
test.afterEach(async t => {
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import { PrismaClient } from '@prisma/client';
|
|||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
|
|
||||||
import { ConfigModule } from '../src/config';
|
import { ConfigModule } from '../src/config';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
import { GqlModule } from '../src/graphql.module';
|
import { GqlModule } from '../src/graphql.module';
|
||||||
import { AuthModule } from '../src/modules/auth';
|
import { AuthModule } from '../src/modules/auth';
|
||||||
import { AuthResolver } from '../src/modules/auth/resolver';
|
import { AuthResolver } from '../src/modules/auth/resolver';
|
||||||
@@ -40,10 +45,19 @@ test.beforeEach(async () => {
|
|||||||
GqlModule,
|
GqlModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
RateLimiterModule,
|
RateLimiterModule,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
authService = module.get(AuthService);
|
authService = module.get(AuthService);
|
||||||
authResolver = module.get(AuthResolver);
|
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 () => {
|
test.afterEach.always(async () => {
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import { PrismaClient } from '@prisma/client';
|
|||||||
import ava, { type TestFn } from 'ava';
|
import ava, { type TestFn } from 'ava';
|
||||||
|
|
||||||
import { ConfigModule } from '../src/config';
|
import { ConfigModule } from '../src/config';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
import { GqlModule } from '../src/graphql.module';
|
import { GqlModule } from '../src/graphql.module';
|
||||||
import { AuthModule } from '../src/modules/auth';
|
import { AuthModule } from '../src/modules/auth';
|
||||||
import { AuthService } from '../src/modules/auth/service';
|
import { AuthService } from '../src/modules/auth/service';
|
||||||
@@ -45,8 +50,16 @@ test.beforeEach(async t => {
|
|||||||
AuthModule,
|
AuthModule,
|
||||||
RateLimiterModule,
|
RateLimiterModule,
|
||||||
],
|
],
|
||||||
|
providers: [RevertCommand, RunCommand],
|
||||||
}).compile();
|
}).compile();
|
||||||
t.context.auth = t.context.module.get(AuthService);
|
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 => {
|
test.afterEach.always(async t => {
|
||||||
|
|||||||
153
packages/backend/server/tests/quota.spec.ts
Normal file
153
packages/backend/server/tests/quota.spec.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/// <reference types="../src/global.d.ts" />
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
@@ -324,7 +324,7 @@ async function listBlobs(
|
|||||||
return res.body.data.listBlobs;
|
return res.body.data.listBlobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectBlobSizes(
|
async function getWorkspaceBlobsSize(
|
||||||
app: INestApplication,
|
app: INestApplication,
|
||||||
token: string,
|
token: string,
|
||||||
workspaceId: string
|
workspaceId: string
|
||||||
@@ -335,14 +335,14 @@ async function collectBlobSizes(
|
|||||||
.send({
|
.send({
|
||||||
query: `
|
query: `
|
||||||
query {
|
query {
|
||||||
collectBlobSizes(workspaceId: "${workspaceId}") {
|
workspace(id: "${workspaceId}") {
|
||||||
size
|
blobsSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
.expect(200);
|
.expect(200);
|
||||||
return res.body.data.collectBlobSizes.size;
|
return res.body.data.workspace.blobsSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function collectAllBlobSizes(
|
async function collectAllBlobSizes(
|
||||||
@@ -566,13 +566,13 @@ export {
|
|||||||
changeEmail,
|
changeEmail,
|
||||||
checkBlobSize,
|
checkBlobSize,
|
||||||
collectAllBlobSizes,
|
collectAllBlobSizes,
|
||||||
collectBlobSizes,
|
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
currentUser,
|
currentUser,
|
||||||
flushDB,
|
flushDB,
|
||||||
getInviteInfo,
|
getInviteInfo,
|
||||||
getPublicWorkspace,
|
getPublicWorkspace,
|
||||||
getWorkspace,
|
getWorkspace,
|
||||||
|
getWorkspaceBlobsSize,
|
||||||
inviteUser,
|
inviteUser,
|
||||||
leaveWorkspace,
|
leaveWorkspace,
|
||||||
listBlobs,
|
listBlobs,
|
||||||
|
|||||||
@@ -6,17 +6,24 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
import { AppModule } from '../src/app';
|
import { AppModule } from '../src/app';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
|
import { QuotaService, QuotaType } from '../src/modules/quota';
|
||||||
import {
|
import {
|
||||||
checkBlobSize,
|
checkBlobSize,
|
||||||
collectAllBlobSizes,
|
collectAllBlobSizes,
|
||||||
collectBlobSizes,
|
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
|
getWorkspaceBlobsSize,
|
||||||
listBlobs,
|
listBlobs,
|
||||||
setBlob,
|
setBlob,
|
||||||
signUp,
|
signUp,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
|
let quota: QuotaService;
|
||||||
|
|
||||||
const client = new PrismaClient();
|
const client = new PrismaClient();
|
||||||
|
|
||||||
@@ -33,6 +40,7 @@ test.beforeEach(async () => {
|
|||||||
test.beforeEach(async () => {
|
test.beforeEach(async () => {
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
|
providers: [RevertCommand, RunCommand],
|
||||||
}).compile();
|
}).compile();
|
||||||
app = module.createNestApplication();
|
app = module.createNestApplication();
|
||||||
app.use(
|
app.use(
|
||||||
@@ -41,6 +49,15 @@ test.beforeEach(async () => {
|
|||||||
maxFiles: 5,
|
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();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,7 +120,7 @@ test('should calc blobs size', async t => {
|
|||||||
const buffer2 = Buffer.from([0, 1]);
|
const buffer2 = Buffer.from([0, 1]);
|
||||||
await setBlob(app, u1.token.token, workspace.id, buffer2);
|
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');
|
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');
|
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');
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import ava, { type TestFn } from 'ava';
|
|||||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||||
|
|
||||||
import { AppModule } from '../src/app';
|
import { AppModule } from '../src/app';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
import { MailService } from '../src/modules/auth/mailer';
|
import { MailService } from '../src/modules/auth/mailer';
|
||||||
import { AuthService } from '../src/modules/auth/service';
|
import { AuthService } from '../src/modules/auth/service';
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +44,7 @@ test.beforeEach(async t => {
|
|||||||
await client.$disconnect();
|
await client.$disconnect();
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
|
providers: [RevertCommand, RunCommand],
|
||||||
}).compile();
|
}).compile();
|
||||||
const app = module.createNestApplication();
|
const app = module.createNestApplication();
|
||||||
app.use(
|
app.use(
|
||||||
@@ -51,9 +57,17 @@ test.beforeEach(async t => {
|
|||||||
|
|
||||||
const auth = module.get(AuthService);
|
const auth = module.get(AuthService);
|
||||||
const mail = module.get(MailService);
|
const mail = module.get(MailService);
|
||||||
|
|
||||||
t.context.app = app;
|
t.context.app = app;
|
||||||
t.context.auth = auth;
|
t.context.auth = auth;
|
||||||
t.context.mail = mail;
|
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 => {
|
test.afterEach.always(async t => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ava, { type TestFn } from 'ava';
|
|||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
|
||||||
import { AppModule } from '../src/app';
|
import { AppModule } from '../src/app';
|
||||||
|
import { Quotas } from '../src/modules/quota';
|
||||||
import { UsersService } from '../src/modules/users';
|
import { UsersService } from '../src/modules/users';
|
||||||
import { PermissionService } from '../src/modules/workspaces/permission';
|
import { PermissionService } from '../src/modules/workspaces/permission';
|
||||||
import { WorkspaceResolver } from '../src/modules/workspaces/resolver';
|
import { WorkspaceResolver } from '../src/modules/workspaces/resolver';
|
||||||
@@ -20,6 +21,9 @@ class FakePermission {
|
|||||||
user: new FakePrisma().fakeUser,
|
user: new FakePrisma().fakeUser,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
async getOwnedWorkspaces() {
|
||||||
|
return [''];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fakeUserService = {
|
const fakeUserService = {
|
||||||
@@ -42,6 +46,19 @@ test.beforeEach(async t => {
|
|||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
userFeatures: {
|
||||||
|
async count() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
async findFirst() {
|
||||||
|
return {
|
||||||
|
createdAt: new Date(),
|
||||||
|
expiredAt: new Date(),
|
||||||
|
reason: '',
|
||||||
|
feature: Quotas[0],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.overrideProvider(PermissionService)
|
.overrideProvider(PermissionService)
|
||||||
.useClass(FakePermission)
|
.useClass(FakePermission)
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
import { AppModule } from '../src/app';
|
import { AppModule } from '../src/app';
|
||||||
|
import {
|
||||||
|
collectMigrations,
|
||||||
|
RevertCommand,
|
||||||
|
RunCommand,
|
||||||
|
} from '../src/data/commands/run';
|
||||||
import {
|
import {
|
||||||
acceptInviteById,
|
acceptInviteById,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
@@ -34,6 +39,7 @@ test.beforeEach(async t => {
|
|||||||
await client.$disconnect();
|
await client.$disconnect();
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
|
providers: [RevertCommand, RunCommand],
|
||||||
}).compile();
|
}).compile();
|
||||||
const app = module.createNestApplication();
|
const app = module.createNestApplication();
|
||||||
app.use(
|
app.use(
|
||||||
@@ -45,6 +51,13 @@ test.beforeEach(async t => {
|
|||||||
await app.init();
|
await app.init();
|
||||||
t.context.client = client;
|
t.context.client = client;
|
||||||
t.context.app = app;
|
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 => {
|
test.afterEach.always(async t => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
query blobSizes($workspaceId: String!) {
|
query blobSizes($workspaceId: String!) {
|
||||||
collectBlobSizes(workspaceId: $workspaceId) {
|
workspace(id: $workspaceId) {
|
||||||
size
|
blobsSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ mutation setBlob($workspaceId: String!, $blob: Upload!) {
|
|||||||
export const blobSizesQuery = {
|
export const blobSizesQuery = {
|
||||||
id: 'blobSizesQuery' as const,
|
id: 'blobSizesQuery' as const,
|
||||||
operationName: 'blobSizes',
|
operationName: 'blobSizes',
|
||||||
definitionName: 'collectBlobSizes',
|
definitionName: 'workspace',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
query blobSizes($workspaceId: String!) {
|
query blobSizes($workspaceId: String!) {
|
||||||
collectBlobSizes(workspaceId: $workspaceId) {
|
workspace(id: $workspaceId) {
|
||||||
size
|
blobsSize
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ export enum InvoiceStatus {
|
|||||||
Void = 'Void',
|
Void = 'Void',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NewFeaturesKind {
|
|
||||||
EarlyAccess = 'EarlyAccess',
|
|
||||||
}
|
|
||||||
|
|
||||||
/** User permission in workspace */
|
/** User permission in workspace */
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
Admin = 'Admin',
|
Admin = 'Admin',
|
||||||
@@ -127,7 +123,7 @@ export type BlobSizesQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type BlobSizesQuery = {
|
export type BlobSizesQuery = {
|
||||||
__typename?: 'Query';
|
__typename?: 'Query';
|
||||||
collectBlobSizes: { __typename?: 'WorkspaceBlobSizes'; size: number };
|
workspace: { __typename?: 'WorkspaceType'; blobsSize: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AllBlobSizesQueryVariables = Exact<{ [key: string]: never }>;
|
export type AllBlobSizesQueryVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|||||||
@@ -107,6 +107,20 @@ export async function createRandomUser(): Promise<{
|
|||||||
...user,
|
...user,
|
||||||
emailVerified: new Date(),
|
emailVerified: new Date(),
|
||||||
password: await hash(user.password),
|
password: await hash(user.password),
|
||||||
|
features: {
|
||||||
|
create: {
|
||||||
|
reason: 'created by test case',
|
||||||
|
activated: true,
|
||||||
|
feature: {
|
||||||
|
connect: {
|
||||||
|
feature_version: {
|
||||||
|
feature: 'free_plan_v1',
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
462
yarn.lock
462
yarn.lock
@@ -2487,7 +2487,30 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 7.23.3
|
||||||
resolution: "@babel/core@npm:7.23.3"
|
resolution: "@babel/core@npm:7.23.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2522,6 +2545,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@babel/generator@npm:^7.23.6":
|
||||||
version: 7.23.6
|
version: 7.23.6
|
||||||
resolution: "@babel/generator@npm:7.23.6"
|
resolution: "@babel/generator@npm:7.23.6"
|
||||||
@@ -2656,6 +2691,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@babel/helper-module-transforms@npm:^7.23.3":
|
||||||
version: 7.23.3
|
version: 7.23.3
|
||||||
resolution: "@babel/helper-module-transforms@npm:7.23.3"
|
resolution: "@babel/helper-module-transforms@npm:7.23.3"
|
||||||
@@ -2740,6 +2790,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@babel/helper-string-parser@npm:^7.23.4":
|
||||||
version: 7.23.4
|
version: 7.23.4
|
||||||
resolution: "@babel/helper-string-parser@npm:7.23.4"
|
resolution: "@babel/helper-string-parser@npm:7.23.4"
|
||||||
@@ -4089,6 +4146,24 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@babel/traverse@npm:^7.23.6":
|
||||||
version: 7.23.6
|
version: 7.23.6
|
||||||
resolution: "@babel/traverse@npm:7.23.6"
|
resolution: "@babel/traverse@npm:7.23.6"
|
||||||
@@ -4107,7 +4182,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 7.23.4
|
||||||
resolution: "@babel/types@npm:7.23.4"
|
resolution: "@babel/types@npm:7.23.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5318,6 +5404,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/android-arm64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/android-arm64@npm:0.19.8"
|
resolution: "@esbuild/android-arm64@npm:0.19.8"
|
||||||
@@ -5346,6 +5439,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/android-arm@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/android-arm@npm:0.19.8"
|
resolution: "@esbuild/android-arm@npm:0.19.8"
|
||||||
@@ -5374,6 +5474,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/android-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/android-x64@npm:0.19.8"
|
resolution: "@esbuild/android-x64@npm:0.19.8"
|
||||||
@@ -5402,6 +5509,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/darwin-arm64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/darwin-arm64@npm:0.19.8"
|
resolution: "@esbuild/darwin-arm64@npm:0.19.8"
|
||||||
@@ -5430,6 +5544,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/darwin-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/darwin-x64@npm:0.19.8"
|
resolution: "@esbuild/darwin-x64@npm:0.19.8"
|
||||||
@@ -5458,6 +5579,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/freebsd-arm64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/freebsd-arm64@npm:0.19.8"
|
resolution: "@esbuild/freebsd-arm64@npm:0.19.8"
|
||||||
@@ -5486,6 +5614,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/freebsd-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/freebsd-x64@npm:0.19.8"
|
resolution: "@esbuild/freebsd-x64@npm:0.19.8"
|
||||||
@@ -5514,6 +5649,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-arm64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-arm64@npm:0.19.8"
|
resolution: "@esbuild/linux-arm64@npm:0.19.8"
|
||||||
@@ -5542,6 +5684,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-arm@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-arm@npm:0.19.8"
|
resolution: "@esbuild/linux-arm@npm:0.19.8"
|
||||||
@@ -5570,6 +5719,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-ia32@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-ia32@npm:0.19.8"
|
resolution: "@esbuild/linux-ia32@npm:0.19.8"
|
||||||
@@ -5598,6 +5754,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-loong64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-loong64@npm:0.19.8"
|
resolution: "@esbuild/linux-loong64@npm:0.19.8"
|
||||||
@@ -5626,6 +5789,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-mips64el@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-mips64el@npm:0.19.8"
|
resolution: "@esbuild/linux-mips64el@npm:0.19.8"
|
||||||
@@ -5654,6 +5824,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-ppc64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-ppc64@npm:0.19.8"
|
resolution: "@esbuild/linux-ppc64@npm:0.19.8"
|
||||||
@@ -5682,6 +5859,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-riscv64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-riscv64@npm:0.19.8"
|
resolution: "@esbuild/linux-riscv64@npm:0.19.8"
|
||||||
@@ -5710,6 +5894,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-s390x@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-s390x@npm:0.19.8"
|
resolution: "@esbuild/linux-s390x@npm:0.19.8"
|
||||||
@@ -5738,6 +5929,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/linux-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/linux-x64@npm:0.19.8"
|
resolution: "@esbuild/linux-x64@npm:0.19.8"
|
||||||
@@ -5766,6 +5964,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/netbsd-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/netbsd-x64@npm:0.19.8"
|
resolution: "@esbuild/netbsd-x64@npm:0.19.8"
|
||||||
@@ -5794,6 +5999,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/openbsd-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/openbsd-x64@npm:0.19.8"
|
resolution: "@esbuild/openbsd-x64@npm:0.19.8"
|
||||||
@@ -5822,6 +6034,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/sunos-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/sunos-x64@npm:0.19.8"
|
resolution: "@esbuild/sunos-x64@npm:0.19.8"
|
||||||
@@ -5850,6 +6069,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/win32-arm64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/win32-arm64@npm:0.19.8"
|
resolution: "@esbuild/win32-arm64@npm:0.19.8"
|
||||||
@@ -5878,6 +6104,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/win32-ia32@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/win32-ia32@npm:0.19.8"
|
resolution: "@esbuild/win32-ia32@npm:0.19.8"
|
||||||
@@ -5906,6 +6139,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@esbuild/win32-x64@npm:0.19.8":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "@esbuild/win32-x64@npm:0.19.8"
|
resolution: "@esbuild/win32-x64@npm:0.19.8"
|
||||||
@@ -6692,7 +6932,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 10.0.11
|
||||||
resolution: "@graphql-tools/utils@npm:10.0.11"
|
resolution: "@graphql-tools/utils@npm:10.0.11"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11799,6 +12052,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@smithy/abort-controller@npm:^2.0.14":
|
||||||
version: 2.0.14
|
version: 2.0.14
|
||||||
resolution: "@smithy/abort-controller@npm:2.0.14"
|
resolution: "@smithy/abort-controller@npm:2.0.14"
|
||||||
@@ -12061,7 +12324,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 2.1.10
|
||||||
resolution: "@smithy/node-http-handler@npm:2.1.10"
|
resolution: "@smithy/node-http-handler@npm:2.1.10"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12074,6 +12337,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@smithy/property-provider@npm:^2.0.0, @smithy/property-provider@npm:^2.0.15":
|
||||||
version: 2.0.15
|
version: 2.0.15
|
||||||
resolution: "@smithy/property-provider@npm:2.0.15"
|
resolution: "@smithy/property-provider@npm:2.0.15"
|
||||||
@@ -12084,7 +12360,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 3.0.10
|
||||||
resolution: "@smithy/protocol-http@npm:3.0.10"
|
resolution: "@smithy/protocol-http@npm:3.0.10"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -12094,6 +12370,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@smithy/querystring-builder@npm:^2.0.14":
|
||||||
version: 2.0.14
|
version: 2.0.14
|
||||||
resolution: "@smithy/querystring-builder@npm:2.0.14"
|
resolution: "@smithy/querystring-builder@npm:2.0.14"
|
||||||
@@ -14604,7 +14901,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 4.17.12
|
||||||
resolution: "@types/lodash-es@npm:4.17.12"
|
resolution: "@types/lodash-es@npm:4.17.12"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14613,6 +14910,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@types/lodash.debounce@npm:^4.0.7":
|
||||||
version: 4.0.9
|
version: 4.0.9
|
||||||
resolution: "@types/lodash.debounce@npm:4.0.9"
|
resolution: "@types/lodash.debounce@npm:4.0.9"
|
||||||
@@ -14747,7 +15053,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 18.18.13
|
||||||
resolution: "@types/node@npm:18.18.13"
|
resolution: "@types/node@npm:18.18.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14756,6 +15062,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@types/nodemailer@npm:^6.4.14":
|
||||||
version: 6.4.14
|
version: 6.4.14
|
||||||
resolution: "@types/nodemailer@npm:6.4.14"
|
resolution: "@types/nodemailer@npm:6.4.14"
|
||||||
@@ -15077,7 +15390,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 8.5.10
|
||||||
resolution: "@types/ws@npm:8.5.10"
|
resolution: "@types/ws@npm:8.5.10"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -16128,7 +16450,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 8.11.2
|
||||||
resolution: "acorn@npm:8.11.2"
|
resolution: "acorn@npm:8.11.2"
|
||||||
bin:
|
bin:
|
||||||
@@ -20758,7 +21089,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"esbuild@npm:^0.19.3, esbuild@npm:^0.19.7":
|
"esbuild@npm:^0.19.3":
|
||||||
version: 0.19.8
|
version: 0.19.8
|
||||||
resolution: "esbuild@npm:0.19.8"
|
resolution: "esbuild@npm:0.19.8"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -20835,6 +21166,83 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"escalade@npm:^3.1.1":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "escalade@npm:3.1.1"
|
resolution: "escalade@npm:3.1.1"
|
||||||
@@ -23081,7 +23489,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"graphql-ws@npm:5.14.2, graphql-ws@npm:^5.14.0":
|
"graphql-ws@npm:5.14.2":
|
||||||
version: 5.14.2
|
version: 5.14.2
|
||||||
resolution: "graphql-ws@npm:5.14.2"
|
resolution: "graphql-ws@npm:5.14.2"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -23090,6 +23498,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"graphql@npm:0.13.1 - 16, graphql@npm:^16.0.0, graphql@npm:^16.8.1":
|
||||||
version: 16.8.1
|
version: 16.8.1
|
||||||
resolution: "graphql@npm:16.8.1"
|
resolution: "graphql@npm:16.8.1"
|
||||||
@@ -26223,7 +26640,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 0.2.88
|
||||||
resolution: "lib0@npm:0.2.88"
|
resolution: "lib0@npm:0.2.88"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -37089,7 +37518,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 2.3.4
|
||||||
resolution: "yaml@npm:2.3.4"
|
resolution: "yaml@npm:2.3.4"
|
||||||
checksum: f8207ce43065a22268a2806ea6a0fa3974c6fde92b4b2fa0082357e487bc333e85dc518910007e7ac001b532c7c84bd3eccb6c7757e94182b564028b0008f44b
|
checksum: f8207ce43065a22268a2806ea6a0fa3974c6fde92b4b2fa0082357e487bc333e85dc518910007e7ac001b532c7c84bd3eccb6c7757e94182b564028b0008f44b
|
||||||
@@ -37103,6 +37532,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"yargs-parser@npm:21.1.1, yargs-parser@npm:>=21.1.1, yargs-parser@npm:^21.1.1":
|
||||||
version: 21.1.1
|
version: 21.1.1
|
||||||
resolution: "yargs-parser@npm:21.1.1"
|
resolution: "yargs-parser@npm:21.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user