Add managed databases and services with billing-aligned upgrades.
Introduce product types for managed PostgreSQL, Redis, and RabbitMQ with a dedicated dashboard, Helm-only deploy pipeline, external access, snapshots with progress, and prorated resource or storage upgrades matching application billing rules. PVCs use an expandable StorageClass with automatic migration when legacy disks cannot resize in place. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
DatabaseType,
|
||||
OptionalService,
|
||||
PricingResourceType,
|
||||
ProductType,
|
||||
} from '../common/enums';
|
||||
import { CalculateCostDto } from './dto/billing.dto';
|
||||
import {
|
||||
@@ -329,6 +330,19 @@ export class PricingCatalogService implements OnModuleInit {
|
||||
rates: PricingRate[],
|
||||
optional: OptionalBillingContext,
|
||||
): CostBreakdownLine[] {
|
||||
const productType = dto.productType ?? ProductType.APPLICATION;
|
||||
|
||||
if (
|
||||
productType === ProductType.MANAGED_REDIS ||
|
||||
productType === ProductType.MANAGED_RABBITMQ
|
||||
) {
|
||||
return this.buildOptionalServiceLines(dto, optional);
|
||||
}
|
||||
|
||||
if (productType === ProductType.MANAGED_DATABASE) {
|
||||
return this.buildManagedDatabaseLines(dto, rates);
|
||||
}
|
||||
|
||||
const lines: CostBreakdownLine[] = [];
|
||||
const quantities = this.getQuantities(dto);
|
||||
|
||||
@@ -351,6 +365,52 @@ export class PricingCatalogService implements OnModuleInit {
|
||||
return lines;
|
||||
}
|
||||
|
||||
private buildManagedDatabaseLines(
|
||||
dto: CalculateCostDto,
|
||||
rates: PricingRate[],
|
||||
): CostBreakdownLine[] {
|
||||
const lines: CostBreakdownLine[] = [];
|
||||
const quantities = this.getManagedDatabaseQuantities(dto);
|
||||
const allowed = new Set([
|
||||
PricingResourceType.DATABASE_ADDON,
|
||||
PricingResourceType.CPU_PER_CORE,
|
||||
PricingResourceType.MEMORY_PER_GB,
|
||||
PricingResourceType.STORAGE_PER_GB,
|
||||
]);
|
||||
|
||||
for (const rate of rates) {
|
||||
if (!allowed.has(rate.resourceType)) continue;
|
||||
const qty = quantities.get(rate.resourceType) ?? 0;
|
||||
if (qty <= 0) continue;
|
||||
const line = this.lineFromPrices(
|
||||
RESOURCE_LABELS[rate.resourceType],
|
||||
qty,
|
||||
Number(rate.hourlyPrice),
|
||||
Number(rate.monthlyPrice),
|
||||
Number(rate.yearlyPrice),
|
||||
rate.resourceType,
|
||||
dto,
|
||||
);
|
||||
if (line) lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private getManagedDatabaseQuantities(dto: CalculateCostDto): Map<PricingResourceType, number> {
|
||||
const cpuQty = this.parseCpuToCores(dto.cpuLimit || '500m');
|
||||
const memoryQty = this.parseMemoryToGb(dto.memoryLimit || '512Mi');
|
||||
const storageQty = dto.dbStorageSize
|
||||
? parseFloat(String(dto.dbStorageSize).replace(/Gi$/i, '')) || 0
|
||||
: 1;
|
||||
|
||||
const map = new Map<PricingResourceType, number>();
|
||||
map.set(PricingResourceType.DATABASE_ADDON, 1);
|
||||
map.set(PricingResourceType.CPU_PER_CORE, cpuQty);
|
||||
map.set(PricingResourceType.MEMORY_PER_GB, memoryQty);
|
||||
map.set(PricingResourceType.STORAGE_PER_GB, storageQty);
|
||||
return map;
|
||||
}
|
||||
|
||||
private buildOptionalServiceLines(
|
||||
dto: CalculateCostDto,
|
||||
optional: OptionalBillingContext,
|
||||
@@ -589,7 +649,10 @@ export class PricingCatalogService implements OnModuleInit {
|
||||
}
|
||||
|
||||
getQuantities(dto: CalculateCostDto): Map<PricingResourceType, number> {
|
||||
const replicas = dto.replicas || 1;
|
||||
if ((dto.productType ?? ProductType.APPLICATION) === ProductType.MANAGED_DATABASE) {
|
||||
return this.getManagedDatabaseQuantities(dto);
|
||||
}
|
||||
const replicas = dto.replicas ?? 1;
|
||||
const hasDatabase = dto.databaseType !== DatabaseType.NONE && dto.databaseType !== 'none';
|
||||
const cpuQty = this.parseCpuToCores(dto.cpuLimit) * replicas;
|
||||
const memoryQty = this.parseMemoryToGb(dto.memoryLimit) * replicas;
|
||||
|
||||
Reference in New Issue
Block a user