From ee2520ec1847ced7f91e896f97a95168d00684b0 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Tue, 16 Jan 2024 09:45:55 +0000 Subject: [PATCH] feat: add query quota of workspace (#5603) --- .../backend/server/src/modules/quota/index.ts | 2 +- .../server/src/modules/quota/storage.ts | 11 +++++----- .../backend/server/src/modules/quota/types.ts | 15 +++++++++++++ .../src/modules/workspaces/resolvers/blob.ts | 16 ++++++++------ .../modules/workspaces/resolvers/workspace.ts | 11 +++++++++- packages/backend/server/src/schema.gql | 9 ++++++++ .../frontend/graphql/src/graphql/index.ts | 17 ++++++++++++++ .../graphql/src/graphql/workspace-quota.gql | 9 ++++++++ packages/frontend/graphql/src/schema.ts | 22 +++++++++++++++++++ 9 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 packages/frontend/graphql/src/graphql/workspace-quota.gql diff --git a/packages/backend/server/src/modules/quota/index.ts b/packages/backend/server/src/modules/quota/index.ts index 224422fa17..ce9b3addbc 100644 --- a/packages/backend/server/src/modules/quota/index.ts +++ b/packages/backend/server/src/modules/quota/index.ts @@ -21,4 +21,4 @@ export class QuotaModule {} export { QuotaManagementService, QuotaService }; export { Quota_FreePlanV1, Quota_ProPlanV1, Quotas } from './schema'; -export { QuotaType } from './types'; +export { QuotaQueryType, QuotaType } from './types'; diff --git a/packages/backend/server/src/modules/quota/storage.ts b/packages/backend/server/src/modules/quota/storage.ts index fa4fdcd28b..ff55b110fc 100644 --- a/packages/backend/server/src/modules/quota/storage.ts +++ b/packages/backend/server/src/modules/quota/storage.ts @@ -3,6 +3,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { WorkspaceBlobStorage } from '../storage'; import { PermissionService } from '../workspaces/permission'; import { QuotaService } from './service'; +import { QuotaQueryType } from './types'; @Injectable() export class QuotaManagementService { @@ -40,21 +41,21 @@ export class QuotaManagementService { // get workspace's owner quota and total size of used // quota was apply to owner's account - async getWorkspaceUsage(workspaceId: string) { + async getWorkspaceUsage(workspaceId: string): Promise { const { user: owner } = await this.permissions.getWorkspaceOwner(workspaceId); if (!owner) throw new NotFoundException('Workspace owner not found'); const { storageQuota, blobLimit } = await this.getUserQuota(owner.id); // get all workspaces size of owner used - const usageSize = await this.getUserUsage(owner.id); + const usedSize = await this.getUserUsage(owner.id); - return { quota: storageQuota, size: usageSize, limit: blobLimit }; + return { storageQuota, usedSize, blobLimit }; } async checkBlobQuota(workspaceId: string, size: number) { - const { quota, size: usageSize } = + const { storageQuota, usedSize } = await this.getWorkspaceUsage(workspaceId); - return quota - (size + usageSize); + return storageQuota - (size + usedSize); } } diff --git a/packages/backend/server/src/modules/quota/types.ts b/packages/backend/server/src/modules/quota/types.ts index 5f94952713..af6f916ef4 100644 --- a/packages/backend/server/src/modules/quota/types.ts +++ b/packages/backend/server/src/modules/quota/types.ts @@ -1,3 +1,4 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql'; import { z } from 'zod'; import { commonFeatureSchema, FeatureKind } from '../features'; @@ -37,6 +38,20 @@ export const QuotaSchema = commonFeatureSchema export type Quota = z.infer; +/// ======== query types ======== + +@ObjectType() +export class QuotaQueryType { + @Field(() => Int) + storageQuota!: number; + + @Field(() => Int) + usedSize!: number; + + @Field(() => Int) + blobLimit!: number; +} + /// ======== utils ======== export function formatSize(bytes: number, decimals: number = 2): string { diff --git a/packages/backend/server/src/modules/workspaces/resolvers/blob.ts b/packages/backend/server/src/modules/workspaces/resolvers/blob.ts index 28d3951752..f1d960d74a 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/blob.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/blob.ts @@ -128,7 +128,7 @@ export class WorkspaceBlobResolver { Permission.Write ); - const { quota, size, limit } = + const { storageQuota, usedSize, blobLimit } = await this.quota.getWorkspaceUsage(workspaceId); const unlimited = await this.feature.hasWorkspaceFeature( @@ -137,7 +137,7 @@ export class WorkspaceBlobResolver { ); const checkExceeded = (recvSize: number) => { - if (!quota) { + if (!storageQuota) { throw new GraphQLError('cannot find user quota', { extensions: { status: HttpStatus[HttpStatus.FORBIDDEN], @@ -145,13 +145,15 @@ export class WorkspaceBlobResolver { }, }); } - const total = size + recvSize; + const total = usedSize + recvSize; // only skip total storage check if workspace has unlimited feature - if (total > quota && !unlimited) { - this.logger.log(`storage size limit exceeded: ${total} > ${quota}`); + if (total > storageQuota && !unlimited) { + this.logger.log( + `storage size limit exceeded: ${total} > ${storageQuota}` + ); return true; - } else if (recvSize > limit) { - this.logger.log(`blob size limit exceeded: ${recvSize} > ${limit}`); + } else if (recvSize > blobLimit) { + this.logger.log(`blob size limit exceeded: ${recvSize} > ${blobLimit}`); return true; } else { return false; diff --git a/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts b/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts index ddc7b5dd85..290f9241f3 100644 --- a/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/modules/workspaces/resolvers/workspace.ts @@ -31,7 +31,7 @@ import { import { Auth, CurrentUser, Public } from '../../auth'; import { AuthService } from '../../auth/service'; import { FeatureManagementService, FeatureType } from '../../features'; -import { QuotaManagementService } from '../../quota'; +import { QuotaManagementService, QuotaQueryType } from '../../quota'; import { WorkspaceBlobStorage } from '../../storage'; import { UsersService, UserType } from '../../users'; import { PermissionService } from '../permission'; @@ -149,6 +149,15 @@ export class WorkspaceResolver { })); } + @ResolveField(() => QuotaQueryType, { + name: 'quota', + description: 'quota of workspace', + complexity: 2, + }) + workspaceQuota(@Parent() workspace: WorkspaceType) { + return this.quota.getWorkspaceUsage(workspace.id); + } + @Query(() => Boolean, { description: 'Get is owner of workspace', complexity: 2, diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 57b78e22d9..c630dd064b 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -131,6 +131,9 @@ type WorkspaceType { """Owner of workspace""" owner: UserType! + """quota of workspace""" + quota: QuotaQueryType! + """Available features of workspace""" availableFeatures: [FeatureType!]! @@ -183,6 +186,12 @@ type InvitationType { invitee: UserType! } +type QuotaQueryType { + storageQuota: Int! + usedSize: Int! + blobLimit: Int! +} + type TokenType { token: String! refresh: String! diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index 18cc67d522..a4b85368b9 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -880,3 +880,20 @@ mutation acceptInviteByInviteId($workspaceId: String!, $inviteId: String!, $send ) }`, }; + +export const workspaceQuotaQuery = { + id: 'workspaceQuotaQuery' as const, + operationName: 'workspaceQuota', + definitionName: 'workspace', + containsFile: false, + query: ` +query workspaceQuota($id: String!) { + workspace(id: $id) { + quota { + storageQuota + usedSize + blobLimit + } + } +}`, +}; diff --git a/packages/frontend/graphql/src/graphql/workspace-quota.gql b/packages/frontend/graphql/src/graphql/workspace-quota.gql new file mode 100644 index 0000000000..b82247b70f --- /dev/null +++ b/packages/frontend/graphql/src/graphql/workspace-quota.gql @@ -0,0 +1,9 @@ +query workspaceQuota($id: String!) { + workspace(id: $id) { + quota { + storageQuota + usedSize + blobLimit + } + } +} diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index cba95af545..557a10dced 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -827,6 +827,23 @@ export type AcceptInviteByInviteIdMutation = { acceptInviteById: boolean; }; +export type WorkspaceQuotaQueryVariables = Exact<{ + id: Scalars['String']['input']; +}>; + +export type WorkspaceQuotaQuery = { + __typename?: 'Query'; + workspace: { + __typename?: 'WorkspaceType'; + quota: { + __typename?: 'QuotaQueryType'; + storageQuota: number; + usedSize: number; + blobLimit: number; + }; + }; +}; + export type Queries = | { name: 'checkBlobSizesQuery'; @@ -957,6 +974,11 @@ export type Queries = name: 'listWorkspaceFeaturesQuery'; variables: ListWorkspaceFeaturesQueryVariables; response: ListWorkspaceFeaturesQuery; + } + | { + name: 'workspaceQuotaQuery'; + variables: WorkspaceQuotaQueryVariables; + response: WorkspaceQuotaQuery; }; export type Mutations =