feat: add query quota of workspace (#5603)
This commit is contained in:
@@ -21,4 +21,4 @@ export class QuotaModule {}
|
|||||||
|
|
||||||
export { QuotaManagementService, QuotaService };
|
export { QuotaManagementService, QuotaService };
|
||||||
export { Quota_FreePlanV1, Quota_ProPlanV1, Quotas } from './schema';
|
export { Quota_FreePlanV1, Quota_ProPlanV1, Quotas } from './schema';
|
||||||
export { QuotaType } from './types';
|
export { QuotaQueryType, QuotaType } from './types';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Injectable, NotFoundException } from '@nestjs/common';
|
|||||||
import { WorkspaceBlobStorage } from '../storage';
|
import { WorkspaceBlobStorage } from '../storage';
|
||||||
import { PermissionService } from '../workspaces/permission';
|
import { PermissionService } from '../workspaces/permission';
|
||||||
import { QuotaService } from './service';
|
import { QuotaService } from './service';
|
||||||
|
import { QuotaQueryType } from './types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QuotaManagementService {
|
export class QuotaManagementService {
|
||||||
@@ -40,21 +41,21 @@ export class QuotaManagementService {
|
|||||||
|
|
||||||
// get workspace's owner quota and total size of used
|
// get workspace's owner quota and total size of used
|
||||||
// quota was apply to owner's account
|
// quota was apply to owner's account
|
||||||
async getWorkspaceUsage(workspaceId: string) {
|
async getWorkspaceUsage(workspaceId: string): Promise<QuotaQueryType> {
|
||||||
const { user: owner } =
|
const { user: owner } =
|
||||||
await this.permissions.getWorkspaceOwner(workspaceId);
|
await this.permissions.getWorkspaceOwner(workspaceId);
|
||||||
if (!owner) throw new NotFoundException('Workspace owner not found');
|
if (!owner) throw new NotFoundException('Workspace owner not found');
|
||||||
const { storageQuota, blobLimit } = await this.getUserQuota(owner.id);
|
const { storageQuota, blobLimit } = await this.getUserQuota(owner.id);
|
||||||
// get all workspaces size of owner used
|
// 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) {
|
async checkBlobQuota(workspaceId: string, size: number) {
|
||||||
const { quota, size: usageSize } =
|
const { storageQuota, usedSize } =
|
||||||
await this.getWorkspaceUsage(workspaceId);
|
await this.getWorkspaceUsage(workspaceId);
|
||||||
|
|
||||||
return quota - (size + usageSize);
|
return storageQuota - (size + usedSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { commonFeatureSchema, FeatureKind } from '../features';
|
import { commonFeatureSchema, FeatureKind } from '../features';
|
||||||
@@ -37,6 +38,20 @@ export const QuotaSchema = commonFeatureSchema
|
|||||||
|
|
||||||
export type Quota = z.infer<typeof QuotaSchema>;
|
export type Quota = z.infer<typeof QuotaSchema>;
|
||||||
|
|
||||||
|
/// ======== query types ========
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class QuotaQueryType {
|
||||||
|
@Field(() => Int)
|
||||||
|
storageQuota!: number;
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
usedSize!: number;
|
||||||
|
|
||||||
|
@Field(() => Int)
|
||||||
|
blobLimit!: number;
|
||||||
|
}
|
||||||
|
|
||||||
/// ======== utils ========
|
/// ======== utils ========
|
||||||
|
|
||||||
export function formatSize(bytes: number, decimals: number = 2): string {
|
export function formatSize(bytes: number, decimals: number = 2): string {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export class WorkspaceBlobResolver {
|
|||||||
Permission.Write
|
Permission.Write
|
||||||
);
|
);
|
||||||
|
|
||||||
const { quota, size, limit } =
|
const { storageQuota, usedSize, blobLimit } =
|
||||||
await this.quota.getWorkspaceUsage(workspaceId);
|
await this.quota.getWorkspaceUsage(workspaceId);
|
||||||
|
|
||||||
const unlimited = await this.feature.hasWorkspaceFeature(
|
const unlimited = await this.feature.hasWorkspaceFeature(
|
||||||
@@ -137,7 +137,7 @@ export class WorkspaceBlobResolver {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const checkExceeded = (recvSize: number) => {
|
const checkExceeded = (recvSize: number) => {
|
||||||
if (!quota) {
|
if (!storageQuota) {
|
||||||
throw new GraphQLError('cannot find user quota', {
|
throw new GraphQLError('cannot find user quota', {
|
||||||
extensions: {
|
extensions: {
|
||||||
status: HttpStatus[HttpStatus.FORBIDDEN],
|
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
|
// only skip total storage check if workspace has unlimited feature
|
||||||
if (total > quota && !unlimited) {
|
if (total > storageQuota && !unlimited) {
|
||||||
this.logger.log(`storage size limit exceeded: ${total} > ${quota}`);
|
this.logger.log(
|
||||||
|
`storage size limit exceeded: ${total} > ${storageQuota}`
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
} else if (recvSize > limit) {
|
} else if (recvSize > blobLimit) {
|
||||||
this.logger.log(`blob size limit exceeded: ${recvSize} > ${limit}`);
|
this.logger.log(`blob size limit exceeded: ${recvSize} > ${blobLimit}`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
import { Auth, CurrentUser, Public } from '../../auth';
|
import { Auth, CurrentUser, Public } from '../../auth';
|
||||||
import { AuthService } from '../../auth/service';
|
import { AuthService } from '../../auth/service';
|
||||||
import { FeatureManagementService, FeatureType } from '../../features';
|
import { FeatureManagementService, FeatureType } from '../../features';
|
||||||
import { QuotaManagementService } from '../../quota';
|
import { QuotaManagementService, QuotaQueryType } from '../../quota';
|
||||||
import { WorkspaceBlobStorage } from '../../storage';
|
import { WorkspaceBlobStorage } from '../../storage';
|
||||||
import { UsersService, UserType } from '../../users';
|
import { UsersService, UserType } from '../../users';
|
||||||
import { PermissionService } from '../permission';
|
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, {
|
@Query(() => Boolean, {
|
||||||
description: 'Get is owner of workspace',
|
description: 'Get is owner of workspace',
|
||||||
complexity: 2,
|
complexity: 2,
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ type WorkspaceType {
|
|||||||
"""Owner of workspace"""
|
"""Owner of workspace"""
|
||||||
owner: UserType!
|
owner: UserType!
|
||||||
|
|
||||||
|
"""quota of workspace"""
|
||||||
|
quota: QuotaQueryType!
|
||||||
|
|
||||||
"""Available features of workspace"""
|
"""Available features of workspace"""
|
||||||
availableFeatures: [FeatureType!]!
|
availableFeatures: [FeatureType!]!
|
||||||
|
|
||||||
@@ -183,6 +186,12 @@ type InvitationType {
|
|||||||
invitee: UserType!
|
invitee: UserType!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuotaQueryType {
|
||||||
|
storageQuota: Int!
|
||||||
|
usedSize: Int!
|
||||||
|
blobLimit: Int!
|
||||||
|
}
|
||||||
|
|
||||||
type TokenType {
|
type TokenType {
|
||||||
token: String!
|
token: String!
|
||||||
refresh: String!
|
refresh: String!
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
query workspaceQuota($id: String!) {
|
||||||
|
workspace(id: $id) {
|
||||||
|
quota {
|
||||||
|
storageQuota
|
||||||
|
usedSize
|
||||||
|
blobLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -827,6 +827,23 @@ export type AcceptInviteByInviteIdMutation = {
|
|||||||
acceptInviteById: boolean;
|
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 =
|
export type Queries =
|
||||||
| {
|
| {
|
||||||
name: 'checkBlobSizesQuery';
|
name: 'checkBlobSizesQuery';
|
||||||
@@ -957,6 +974,11 @@ export type Queries =
|
|||||||
name: 'listWorkspaceFeaturesQuery';
|
name: 'listWorkspaceFeaturesQuery';
|
||||||
variables: ListWorkspaceFeaturesQueryVariables;
|
variables: ListWorkspaceFeaturesQueryVariables;
|
||||||
response: ListWorkspaceFeaturesQuery;
|
response: ListWorkspaceFeaturesQuery;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: 'workspaceQuotaQuery';
|
||||||
|
variables: WorkspaceQuotaQueryVariables;
|
||||||
|
response: WorkspaceQuotaQuery;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Mutations =
|
export type Mutations =
|
||||||
|
|||||||
Reference in New Issue
Block a user