From a93c12e1220de1cfe3bb29c6e3dcf6f11a661149 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 14 Dec 2023 09:50:41 +0000 Subject: [PATCH] feat: user usage gql & test case improve (#5076) --- packages/backend/server/schema.prisma | 28 ---- .../backend/server/src/data/commands/run.ts | 79 ++++++---- packages/backend/server/src/graphql.module.ts | 1 + .../server/src/modules/payment/resolver.ts | 10 +- .../backend/server/src/modules/quota/index.ts | 1 - .../server/src/modules/users/resolver.ts | 8 +- packages/backend/server/src/schema.gql | 2 +- packages/backend/server/tests/app.e2e.ts | 1 - packages/backend/server/tests/auth.e2e.ts | 13 +- packages/backend/server/tests/auth.spec.ts | 13 +- packages/backend/server/tests/feature.spec.ts | 140 ++++++++++++++++++ packages/backend/server/tests/mailer.e2e.ts | 13 +- packages/backend/server/tests/quota.spec.ts | 13 +- packages/backend/server/tests/utils.ts | 9 ++ .../server/tests/workspace-blobs.spec.ts | 13 +- .../server/tests/workspace-invite.e2e.ts | 13 +- .../backend/server/tests/workspace.e2e.ts | 13 +- .../graphql/src/graphql/early-access-add.gql | 3 + .../graphql/src/graphql/early-access-list.gql | 17 +++ .../src/graphql/early-access-remove.gql | 3 + .../frontend/graphql/src/graphql/index.ts | 47 ++++++ packages/frontend/graphql/src/schema.ts | 56 +++++++ 22 files changed, 364 insertions(+), 132 deletions(-) create mode 100644 packages/backend/server/tests/feature.spec.ts create mode 100644 packages/frontend/graphql/src/graphql/early-access-add.gql create mode 100644 packages/frontend/graphql/src/graphql/early-access-list.gql create mode 100644 packages/frontend/graphql/src/graphql/early-access-remove.gql diff --git a/packages/backend/server/schema.prisma b/packages/backend/server/schema.prisma index b9dc73f2dc..fae3ff0b10 100644 --- a/packages/backend/server/schema.prisma +++ b/packages/backend/server/schema.prisma @@ -28,7 +28,6 @@ model User { invoices UserInvoice[] workspacePermissions WorkspaceUserPermission[] pagePermissions WorkspacePageUserPermission[] - UserQuotaGates UserQuotaGates[] @@map("users") } @@ -158,33 +157,6 @@ model Features { @@map("features") } -// quota gates is a way to enable/disable quotas for a user -// for example, pro plan is a quota that allow some users access to more resources after they pay -model UserQuotaGates { - id String @id @default(uuid()) @db.VarChar - userId String @map("user_id") @db.VarChar - quotaId String? @db.VarChar - - reason String @db.VarChar - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - quota UserQuotas? @relation(fields: [quotaId], references: [id]) - - @@map("user_quota_gates") -} - -model UserQuotas { - id String @id @default(uuid()) @db.VarChar - quota String @db.VarChar - configs Json @db.Json - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) - - UserQuotaGates UserQuotaGates[] - - @@map("user_quotas") -} - model Account { id String @id @default(cuid()) userId String @map("user_id") diff --git a/packages/backend/server/src/data/commands/run.ts b/packages/backend/server/src/data/commands/run.ts index cdfe8450e4..97b3c3098d 100644 --- a/packages/backend/server/src/data/commands/run.ts +++ b/packages/backend/server/src/data/commands/run.ts @@ -64,35 +64,8 @@ export class RunCommand extends CommandRunner { continue; } - this.logger.log(`Running ${migration.name}...`); - const record = await this.db.dataMigration.create({ - data: { - name: migration.name, - startedAt: new Date(), - }, - }); + await this.runMigration(migration); - try { - await migration.up(this.db); - } catch (e) { - await this.db.dataMigration.delete({ - where: { - id: record.id, - }, - }); - await migration.down(this.db); - this.logger.error('Failed to run data migration', e); - process.exit(1); - } - - await this.db.dataMigration.update({ - where: { - id: record.id, - }, - data: { - finishedAt: new Date(), - }, - }); done.push(migration); } @@ -101,6 +74,56 @@ export class RunCommand extends CommandRunner { this.logger.log(` ✔ ${migration.name}`); }); } + + async runOne(name: string) { + const migrations = await collectMigrations(); + const migration = migrations.find(m => m.name === name); + + if (!migration) { + throw new Error(`Unknown migration name: ${name}.`); + } + const exists = await this.db.dataMigration.count({ + where: { + name: migration.name, + }, + }); + + if (exists) return; + + await this.runMigration(migration); + } + + private async runMigration(migration: Migration) { + this.logger.log(`Running ${migration.name}...`); + const record = await this.db.dataMigration.create({ + data: { + name: migration.name, + startedAt: new Date(), + }, + }); + + try { + await migration.up(this.db); + } catch (e) { + await this.db.dataMigration.delete({ + where: { + id: record.id, + }, + }); + await migration.down(this.db); + this.logger.error('Failed to run data migration', e); + process.exit(1); + } + + await this.db.dataMigration.update({ + where: { + id: record.id, + }, + data: { + finishedAt: new Date(), + }, + }); + } } @Command({ diff --git a/packages/backend/server/src/graphql.module.ts b/packages/backend/server/src/graphql.module.ts index e9c86ffa35..38ded5c1df 100644 --- a/packages/backend/server/src/graphql.module.ts +++ b/packages/backend/server/src/graphql.module.ts @@ -29,6 +29,7 @@ import { GQLLoggerPlugin } from './graphql/logger-plugin'; context: ({ req, res }: { req: Request; res: Response }) => ({ req, res, + isAdminQuery: false, }), plugins: [new GQLLoggerPlugin()], }; diff --git a/packages/backend/server/src/modules/payment/resolver.ts b/packages/backend/server/src/modules/payment/resolver.ts index 7374f8e1d5..282d63acaa 100644 --- a/packages/backend/server/src/modules/payment/resolver.ts +++ b/packages/backend/server/src/modules/payment/resolver.ts @@ -1,6 +1,7 @@ import { HttpStatus } from '@nestjs/common'; import { Args, + Context, Field, Int, Mutation, @@ -254,8 +255,13 @@ export class UserSubscriptionResolver { constructor(private readonly db: PrismaService) {} @ResolveField(() => UserSubscriptionType, { nullable: true }) - async subscription(@CurrentUser() me: User, @Parent() user: User) { - if (me.id !== user.id) { + async subscription( + @Context() ctx: { isAdminQuery: boolean }, + @CurrentUser() me: User, + @Parent() user: User + ) { + // allow admin to query other user's subscription + if (!ctx.isAdminQuery && me.id !== user.id) { throw new GraphQLError( 'You are not allowed to access this subscription', { diff --git a/packages/backend/server/src/modules/quota/index.ts b/packages/backend/server/src/modules/quota/index.ts index 4055fa1684..4e7e8ccd7f 100644 --- a/packages/backend/server/src/modules/quota/index.ts +++ b/packages/backend/server/src/modules/quota/index.ts @@ -17,5 +17,4 @@ import { QuotaManagementService } from './storage'; export class QuotaModule {} export { QuotaManagementService, QuotaService }; -export { PrismaService } from '../../prisma'; export { Quota_FreePlanV1, Quota_ProPlanV1, Quotas, QuotaType } from './types'; diff --git a/packages/backend/server/src/modules/users/resolver.ts b/packages/backend/server/src/modules/users/resolver.ts index 70d0333f38..75d1c19940 100644 --- a/packages/backend/server/src/modules/users/resolver.ts +++ b/packages/backend/server/src/modules/users/resolver.ts @@ -6,6 +6,7 @@ import { } from '@nestjs/common'; import { Args, + Context, Field, ID, Int, @@ -267,10 +268,15 @@ export class UserResolver { }, }) @Query(() => [UserType]) - async listEarlyAccess(@CurrentUser() user: UserType): Promise { + async earlyAccessUsers( + @Context() ctx: { isAdminQuery: boolean }, + @CurrentUser() user: UserType + ): Promise { if (!this.feature.isStaff(user.email)) { throw new ForbiddenException('You are not allowed to do this'); } + // allow query other user's subscription + ctx.isAdminQuery = true; return this.feature.listEarlyAccess(); } } diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 3fc7457ffd..001dc645ed 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -269,7 +269,7 @@ type Query { """Get user by email""" user(email: String!): UserType - listEarlyAccess: [UserType!]! + earlyAccessUsers: [UserType!]! prices: [SubscriptionPrice!]! } diff --git a/packages/backend/server/tests/app.e2e.ts b/packages/backend/server/tests/app.e2e.ts index 08785d5e3b..95e8b6c5eb 100644 --- a/packages/backend/server/tests/app.e2e.ts +++ b/packages/backend/server/tests/app.e2e.ts @@ -126,7 +126,6 @@ test('should find default user', async t => { }) .expect(200) .expect(res => { - console.log(res.body); t.is(res.body.data.user.email, 'alex.yang@example.org'); }); }); diff --git a/packages/backend/server/tests/auth.e2e.ts b/packages/backend/server/tests/auth.e2e.ts index 7beb208aea..a159a8ae6b 100644 --- a/packages/backend/server/tests/auth.e2e.ts +++ b/packages/backend/server/tests/auth.e2e.ts @@ -9,16 +9,13 @@ import ava, { type TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from '../src/app'; -import { - collectMigrations, - RevertCommand, - RunCommand, -} from '../src/data/commands/run'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import { MailService } from '../src/modules/auth/mailer'; import { AuthService } from '../src/modules/auth/service'; import { changeEmail, createWorkspace, + initFeatureConfigs, sendChangeEmail, sendVerifyChangeEmail, signUp, @@ -60,11 +57,7 @@ test.beforeEach(async t => { 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(); + await initFeatureConfigs(module); }); test.afterEach(async t => { diff --git a/packages/backend/server/tests/auth.spec.ts b/packages/backend/server/tests/auth.spec.ts index b294702b00..b3edd260f2 100644 --- a/packages/backend/server/tests/auth.spec.ts +++ b/packages/backend/server/tests/auth.spec.ts @@ -4,11 +4,7 @@ import { PrismaClient } from '@prisma/client'; import test from 'ava'; import { ConfigModule } from '../src/config'; -import { - collectMigrations, - RevertCommand, - RunCommand, -} from '../src/data/commands/run'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import { GqlModule } from '../src/graphql.module'; import { AuthModule } from '../src/modules/auth'; import { AuthResolver } from '../src/modules/auth/resolver'; @@ -16,6 +12,7 @@ import { AuthService } from '../src/modules/auth/service'; import { PrismaModule } from '../src/prisma'; import { mintChallengeResponse, verifyChallengeResponse } from '../src/storage'; import { RateLimiterModule } from '../src/throttler'; +import { initFeatureConfigs } from './utils'; let authService: AuthService; let authResolver: AuthResolver; @@ -53,11 +50,7 @@ test.beforeEach(async () => { 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(); + await initFeatureConfigs(module); }); test.afterEach.always(async () => { diff --git a/packages/backend/server/tests/feature.spec.ts b/packages/backend/server/tests/feature.spec.ts new file mode 100644 index 0000000000..ac8b776a97 --- /dev/null +++ b/packages/backend/server/tests/feature.spec.ts @@ -0,0 +1,140 @@ +/// + +import { Test, TestingModule } from '@nestjs/testing'; +import { PrismaClient } from '@prisma/client'; +import ava, { type TestFn } from 'ava'; + +import { ConfigModule } from '../src/config'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; +import { AuthModule } from '../src/modules/auth'; +import { AuthService } from '../src/modules/auth/service'; +import { + FeatureManagementService, + FeatureModule, + FeatureService, + FeatureType, +} from '../src/modules/features'; +import { PrismaModule } from '../src/prisma'; +import { RateLimiterModule } from '../src/throttler'; +import { initFeatureConfigs } from './utils'; + +const test = ava as TestFn<{ + auth: AuthService; + feature: FeatureService; + early_access: FeatureManagementService; + 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, + featureFlags: { + earlyAccessPreview: true, + }, + }), + PrismaModule, + AuthModule, + FeatureModule, + RateLimiterModule, + RevertCommand, + RunCommand, + ], + }).compile(); + + const quota = module.get(FeatureService); + const storageQuota = module.get(FeatureManagementService); + const auth = module.get(AuthService); + + t.context.app = module; + t.context.feature = quota; + t.context.early_access = storageQuota; + t.context.auth = auth; + + // init features + await initFeatureConfigs(module); +}); + +test.afterEach.always(async t => { + await t.context.app.close(); +}); + +test('should be able to set feature', async t => { + const { auth, feature } = t.context; + + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const f1 = await feature.getUserFeatures(u1.id); + t.is(f1.length, 0, 'should be empty'); + + await feature.addUserFeature(u1.id, FeatureType.EarlyAccess, 1, 'test'); + + const f2 = await feature.getUserFeatures(u1.id); + t.is(f2.length, 1, 'should have one feature'); + t.is( + f2[0].feature.feature, + FeatureType.EarlyAccess, + 'should be early access' + ); +}); + +test('should be able to check early access', async t => { + const { auth, feature, early_access } = t.context; + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const f1 = await early_access.canEarlyAccess(u1.email); + t.false(f1, 'should not have early access'); + + await early_access.addEarlyAccess(u1.id); + const f2 = await early_access.canEarlyAccess(u1.email); + t.true(f2, 'should have early access'); + + const f3 = await feature.listFeatureUsers(FeatureType.EarlyAccess); + t.is(f3.length, 1, 'should have one user'); + t.is(f3[0].id, u1.id, 'should be the same user'); +}); + +test('should be able revert quota', async t => { + const { auth, feature, early_access } = t.context; + const u1 = await auth.signUp('DarkSky', 'darksky@example.org', '123456'); + + const f1 = await early_access.canEarlyAccess(u1.email); + t.false(f1, 'should not have early access'); + + await early_access.addEarlyAccess(u1.id); + const f2 = await early_access.canEarlyAccess(u1.email); + t.true(f2, 'should have early access'); + const q1 = await early_access.listEarlyAccess(); + t.is(q1.length, 1, 'should have one user'); + t.is(q1[0].id, u1.id, 'should be the same user'); + + await early_access.removeEarlyAccess(u1.id); + const f3 = await early_access.canEarlyAccess(u1.email); + t.false(f3, 'should not have early access'); + const q2 = await early_access.listEarlyAccess(); + t.is(q2.length, 0, 'should have no user'); + + const q3 = await feature.getUserFeatures(u1.id); + t.is(q3.length, 1, 'should have 1 feature'); + t.is( + q3[0].feature.feature, + FeatureType.EarlyAccess, + 'should be early access' + ); + t.is(q3[0].activated, false, 'should be deactivated'); +}); diff --git a/packages/backend/server/tests/mailer.e2e.ts b/packages/backend/server/tests/mailer.e2e.ts index bcecaeb4b7..ea8beea45f 100644 --- a/packages/backend/server/tests/mailer.e2e.ts +++ b/packages/backend/server/tests/mailer.e2e.ts @@ -11,16 +11,13 @@ 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 { RevertCommand, RunCommand } from '../src/data/commands/run'; import { GqlModule } from '../src/graphql.module'; import { AuthModule } from '../src/modules/auth'; import { AuthService } from '../src/modules/auth/service'; import { PrismaModule } from '../src/prisma'; import { RateLimiterModule } from '../src/throttler'; +import { initFeatureConfigs } from './utils'; const test = ava as TestFn<{ auth: AuthService; @@ -55,11 +52,7 @@ test.beforeEach(async t => { 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(); + await initFeatureConfigs(t.context.module); }); test.afterEach.always(async t => { diff --git a/packages/backend/server/tests/quota.spec.ts b/packages/backend/server/tests/quota.spec.ts index 2ea0d2b218..fe53b2ed5e 100644 --- a/packages/backend/server/tests/quota.spec.ts +++ b/packages/backend/server/tests/quota.spec.ts @@ -5,11 +5,7 @@ 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 { RevertCommand, RunCommand } from '../src/data/commands/run'; import { AuthModule } from '../src/modules/auth'; import { AuthService } from '../src/modules/auth/service'; import { @@ -22,6 +18,7 @@ import { import { PrismaModule } from '../src/prisma'; import { StorageModule } from '../src/storage'; import { RateLimiterModule } from '../src/throttler'; +import { initFeatureConfigs } from './utils'; const test = ava as TestFn<{ auth: AuthService; @@ -70,11 +67,7 @@ test.beforeEach(async t => { 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(); + await initFeatureConfigs(module); }); test.afterEach.always(async t => { diff --git a/packages/backend/server/tests/utils.ts b/packages/backend/server/tests/utils.ts index 34e9f945fc..98741c99c1 100644 --- a/packages/backend/server/tests/utils.ts +++ b/packages/backend/server/tests/utils.ts @@ -1,10 +1,12 @@ import { randomUUID } from 'node:crypto'; import type { INestApplication } from '@nestjs/common'; +import { TestingModule } from '@nestjs/testing'; import { hashSync } from '@node-rs/argon2'; import { PrismaClient, type User } from '@prisma/client'; import request from 'supertest'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import type { TokenType } from '../src/modules/auth'; import type { UserType } from '../src/modules/users'; import type { InvitationType, WorkspaceType } from '../src/modules/workspaces'; @@ -561,6 +563,13 @@ export class FakePrisma { } } +export async function initFeatureConfigs(module: TestingModule) { + const run = module.get(RunCommand); + const revert = module.get(RevertCommand); + await Promise.allSettled([revert.run(['UserFeaturesInit1698652531198'])]); + await run.runOne('UserFeaturesInit1698652531198'); +} + export { acceptInviteById, changeEmail, diff --git a/packages/backend/server/tests/workspace-blobs.spec.ts b/packages/backend/server/tests/workspace-blobs.spec.ts index a7c0e88347..7db0034ced 100644 --- a/packages/backend/server/tests/workspace-blobs.spec.ts +++ b/packages/backend/server/tests/workspace-blobs.spec.ts @@ -6,17 +6,14 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; import { AppModule } from '../src/app'; -import { - collectMigrations, - RevertCommand, - RunCommand, -} from '../src/data/commands/run'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import { QuotaService, QuotaType } from '../src/modules/quota'; import { checkBlobSize, collectAllBlobSizes, createWorkspace, getWorkspaceBlobsSize, + initFeatureConfigs, listBlobs, setBlob, signUp, @@ -52,11 +49,7 @@ test.beforeEach(async () => { 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 initFeatureConfigs(module); await app.init(); }); diff --git a/packages/backend/server/tests/workspace-invite.e2e.ts b/packages/backend/server/tests/workspace-invite.e2e.ts index 87cf5289b4..52a6eac694 100644 --- a/packages/backend/server/tests/workspace-invite.e2e.ts +++ b/packages/backend/server/tests/workspace-invite.e2e.ts @@ -9,17 +9,14 @@ import ava, { type TestFn } from 'ava'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import { AppModule } from '../src/app'; -import { - collectMigrations, - RevertCommand, - RunCommand, -} from '../src/data/commands/run'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import { MailService } from '../src/modules/auth/mailer'; import { AuthService } from '../src/modules/auth/service'; import { acceptInviteById, createWorkspace, getWorkspace, + initFeatureConfigs, inviteUser, leaveWorkspace, revokeUser, @@ -63,11 +60,7 @@ test.beforeEach(async t => { 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(); + await initFeatureConfigs(module); }); test.afterEach.always(async t => { diff --git a/packages/backend/server/tests/workspace.e2e.ts b/packages/backend/server/tests/workspace.e2e.ts index 0e5aff80f6..86904acc6a 100644 --- a/packages/backend/server/tests/workspace.e2e.ts +++ b/packages/backend/server/tests/workspace.e2e.ts @@ -6,17 +6,14 @@ import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; import request from 'supertest'; import { AppModule } from '../src/app'; -import { - collectMigrations, - RevertCommand, - RunCommand, -} from '../src/data/commands/run'; +import { RevertCommand, RunCommand } from '../src/data/commands/run'; import { acceptInviteById, createWorkspace, currentUser, getPublicWorkspace, getWorkspacePublicPages, + initFeatureConfigs, inviteUser, publishPage, revokePublicPage, @@ -53,11 +50,7 @@ test.beforeEach(async t => { 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(); + await initFeatureConfigs(module); }); test.afterEach.always(async t => { diff --git a/packages/frontend/graphql/src/graphql/early-access-add.gql b/packages/frontend/graphql/src/graphql/early-access-add.gql new file mode 100644 index 0000000000..eb28bfd1c7 --- /dev/null +++ b/packages/frontend/graphql/src/graphql/early-access-add.gql @@ -0,0 +1,3 @@ +mutation addToEarlyAccess($email: String!) { + addToEarlyAccess(email: $email) +} diff --git a/packages/frontend/graphql/src/graphql/early-access-list.gql b/packages/frontend/graphql/src/graphql/early-access-list.gql new file mode 100644 index 0000000000..13c92f22a6 --- /dev/null +++ b/packages/frontend/graphql/src/graphql/early-access-list.gql @@ -0,0 +1,17 @@ +query earlyAccessUsers { + earlyAccessUsers { + id + name + email + avatarUrl + emailVerified + createdAt + subscription { + plan + recurring + status + start + end + } + } +} diff --git a/packages/frontend/graphql/src/graphql/early-access-remove.gql b/packages/frontend/graphql/src/graphql/early-access-remove.gql new file mode 100644 index 0000000000..dedea87709 --- /dev/null +++ b/packages/frontend/graphql/src/graphql/early-access-remove.gql @@ -0,0 +1,3 @@ +mutation removeEarlyAccess($email: String!) { + removeEarlyAccess(email: $email) +} diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index 5d4b1696dc..972fc945ca 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -188,6 +188,53 @@ mutation deleteWorkspace($id: String!) { }`, }; +export const addToEarlyAccessMutation = { + id: 'addToEarlyAccessMutation' as const, + operationName: 'addToEarlyAccess', + definitionName: 'addToEarlyAccess', + containsFile: false, + query: ` +mutation addToEarlyAccess($email: String!) { + addToEarlyAccess(email: $email) +}`, +}; + +export const earlyAccessUsersQuery = { + id: 'earlyAccessUsersQuery' as const, + operationName: 'earlyAccessUsers', + definitionName: 'earlyAccessUsers', + containsFile: false, + query: ` +query earlyAccessUsers { + earlyAccessUsers { + id + name + email + avatarUrl + emailVerified + createdAt + subscription { + plan + recurring + status + start + end + } + } +}`, +}; + +export const removeEarlyAccessMutation = { + id: 'removeEarlyAccessMutation' as const, + operationName: 'removeEarlyAccess', + definitionName: 'removeEarlyAccess', + containsFile: false, + query: ` +mutation removeEarlyAccess($email: String!) { + removeEarlyAccess(email: $email) +}`, +}; + export const getCurrentUserQuery = { id: 'getCurrentUserQuery' as const, operationName: 'getCurrentUser', diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index caeccf3719..483711bfd2 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -223,6 +223,47 @@ export type DeleteWorkspaceMutation = { deleteWorkspace: boolean; }; +export type AddToEarlyAccessMutationVariables = Exact<{ + email: Scalars['String']['input']; +}>; + +export type AddToEarlyAccessMutation = { + __typename?: 'Mutation'; + addToEarlyAccess: number; +}; + +export type EarlyAccessUsersQueryVariables = Exact<{ [key: string]: never }>; + +export type EarlyAccessUsersQuery = { + __typename?: 'Query'; + earlyAccessUsers: Array<{ + __typename?: 'UserType'; + id: string; + name: string; + email: string; + avatarUrl: string | null; + emailVerified: string | null; + createdAt: string | null; + subscription: { + __typename?: 'UserSubscription'; + plan: SubscriptionPlan; + recurring: SubscriptionRecurring; + status: SubscriptionStatus; + start: string; + end: string; + } | null; + }>; +}; + +export type RemoveEarlyAccessMutationVariables = Exact<{ + email: Scalars['String']['input']; +}>; + +export type RemoveEarlyAccessMutation = { + __typename?: 'Mutation'; + removeEarlyAccess: number; +}; + export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never }>; export type GetCurrentUserQuery = { @@ -698,6 +739,11 @@ export type Queries = variables: AllBlobSizesQueryVariables; response: AllBlobSizesQuery; } + | { + name: 'earlyAccessUsersQuery'; + variables: EarlyAccessUsersQueryVariables; + response: EarlyAccessUsersQuery; + } | { name: 'getCurrentUserQuery'; variables: GetCurrentUserQueryVariables; @@ -835,6 +881,16 @@ export type Mutations = variables: DeleteWorkspaceMutationVariables; response: DeleteWorkspaceMutation; } + | { + name: 'addToEarlyAccessMutation'; + variables: AddToEarlyAccessMutationVariables; + response: AddToEarlyAccessMutation; + } + | { + name: 'removeEarlyAccessMutation'; + variables: RemoveEarlyAccessMutationVariables; + response: RemoveEarlyAccessMutation; + } | { name: 'leaveWorkspaceMutation'; variables: LeaveWorkspaceMutationVariables;