feat(billing): cap each owner's stored file volume
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,8 +4,10 @@ import {
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
@@ -146,6 +148,15 @@ export class ApplyBillingPlanDto {
|
||||
status?: 'active' | 'expired' | 'suspended';
|
||||
}
|
||||
|
||||
export class SetStorageQuotaDto {
|
||||
/** گیگابایت؛ null = بدون سقف */
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(100_000)
|
||||
@ValidateIf((_, value) => value !== null)
|
||||
quotaGb!: number | null;
|
||||
}
|
||||
|
||||
export class UpdateOrgModulesDto {
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import {
|
||||
ApplyBillingPlanDto,
|
||||
SetStorageQuotaDto,
|
||||
CreatePlatformAdminDto,
|
||||
CreatePlatformOrganizationDto,
|
||||
ResetPlatformAdminPasswordDto,
|
||||
@@ -122,6 +123,16 @@ export class PlatformController {
|
||||
return this.platform.applyBillingPlan(id, dto);
|
||||
}
|
||||
|
||||
@Get('users/:id/storage')
|
||||
storageUsage(@Param('id') id: string, @Query('refresh') refresh?: string) {
|
||||
return this.platform.storageUsage(id, refresh === '1' || refresh === 'true');
|
||||
}
|
||||
|
||||
@Patch('users/:id/billing/storage')
|
||||
setStorageQuota(@Param('id') id: string, @Body() dto: SetStorageQuotaDto) {
|
||||
return this.platform.setStorageQuota(id, dto.quotaGb);
|
||||
}
|
||||
|
||||
@Patch('organizations/:id/modules')
|
||||
updateOrganizationModules(
|
||||
@Param('id') id: string,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MembershipsService } from '../memberships/memberships.service';
|
||||
import { OrgRolesService } from '../org-roles/org-roles.service';
|
||||
import { PipelinesService } from '../pipelines/pipelines.service';
|
||||
import { BillingService, toSummary } from '../billing/billing.service';
|
||||
import { StorageQuotaService } from '../billing/storage-quota.service';
|
||||
import { PlatformAdminAlertService } from '../billing/platform-admin-alert.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { buildSearchWhere, ci } from '../common/search';
|
||||
@@ -51,6 +52,7 @@ export class PlatformService {
|
||||
private readonly orgRoles: OrgRolesService,
|
||||
private readonly pipelines: PipelinesService,
|
||||
private readonly billing: BillingService,
|
||||
private readonly storage: StorageQuotaService,
|
||||
private readonly adminAlerts: PlatformAdminAlertService,
|
||||
) {}
|
||||
|
||||
@@ -408,6 +410,26 @@ export class PlatformService {
|
||||
return this.userToDto(row);
|
||||
}
|
||||
|
||||
async storageUsage(userId: string, refresh = false) {
|
||||
const owner = await this.prisma.membership.findFirst({
|
||||
where: { userId, role: 'owner', isActive: true },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!owner) {
|
||||
throw new BadRequestException('حجم فایل فقط برای مالک محیط معنی دارد');
|
||||
}
|
||||
const account = await this.billing.getByOwnerUserId(userId);
|
||||
if (!account) {
|
||||
throw new BadRequestException('اول برای این مالک یک پلن اعمال کنید');
|
||||
}
|
||||
return this.storage.usageForOwner(userId, refresh);
|
||||
}
|
||||
|
||||
async setStorageQuota(userId: string, quotaGb: number | null) {
|
||||
await this.storageUsage(userId, false);
|
||||
return this.storage.setQuotaGb(userId, quotaGb);
|
||||
}
|
||||
|
||||
async applyBillingPlan(userId: string, dto: ApplyBillingPlanDto) {
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
|
||||
Reference in New Issue
Block a user