Make billing catalog dynamic for all runtimes and bill optional service resources.

Derive admin tabs and addon rows from enums, and add Redis/RabbitMQ/ES CPU/RAM/disk to deploy cost using the app runtime unit rates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-15 18:22:35 +03:30
parent 35235fe0fc
commit bb27c90ae4
6 changed files with 292 additions and 109 deletions
@@ -1,10 +1,20 @@
import { AppRuntime, PricingResourceType } from '../common/enums';
import { AppRuntime, OptionalService, PricingResourceType } from '../common/enums';
export const BILLING_RUNTIMES: AppRuntime[] = [
AppRuntime.NODEJS,
AppRuntime.LARAVEL,
AppRuntime.WORDPRESS,
];
/** All application runtimes — new enum values appear in billing automatically. */
export function getAllBillingRuntimes(): AppRuntime[] {
return Object.values(AppRuntime);
}
export const RUNTIME_DISPLAY_LABELS: Record<AppRuntime, string> = {
[AppRuntime.NODEJS]: 'Node.js',
[AppRuntime.LARAVEL]: 'Laravel',
[AppRuntime.WORDPRESS]: 'WordPress',
[AppRuntime.GO]: 'Go',
[AppRuntime.PHP]: 'PHP',
[AppRuntime.PYTHON]: 'Python',
[AppRuntime.DJANGO]: 'Django',
[AppRuntime.DOTNET]: '.NET',
};
export const RUNTIME_PRICING_RESOURCES: PricingResourceType[] = [
PricingResourceType.BASE_FEE,
@@ -14,12 +24,38 @@ export const RUNTIME_PRICING_RESOURCES: PricingResourceType[] = [
PricingResourceType.DATABASE_ADDON,
];
export const ADDON_PRICING_RESOURCES: PricingResourceType[] = [
PricingResourceType.REDIS_ADDON,
PricingResourceType.RABBITMQ_ADDON,
PricingResourceType.ELASTICSEARCH_ADDON,
PricingResourceType.CUSTOM_DOMAIN_ADDON,
];
/** Maps optional service → flat addon row in addon_rates. */
export const OPTIONAL_SERVICE_PRICING_TYPE: Record<OptionalService, PricingResourceType> = {
[OptionalService.REDIS]: PricingResourceType.REDIS_ADDON,
[OptionalService.RABBITMQ]: PricingResourceType.RABBITMQ_ADDON,
[OptionalService.ELASTICSEARCH]: PricingResourceType.ELASTICSEARCH_ADDON,
};
export function getBillableAddonResourceTypes(): PricingResourceType[] {
return [
...Object.values(OPTIONAL_SERVICE_PRICING_TYPE),
PricingResourceType.CUSTOM_DOMAIN_ADDON,
];
}
export const OPTIONAL_SERVICE_LABELS: Record<OptionalService, string> = {
[OptionalService.REDIS]: 'Redis',
[OptionalService.RABBITMQ]: 'RabbitMQ',
[OptionalService.ELASTICSEARCH]: 'Elasticsearch (logging)',
};
/** Deploy footprint aligned with helm/cloudhost-app defaults (limits used for billing). */
export const OPTIONAL_SERVICE_DEPLOY_SPECS: Record<
OptionalService,
{ cpuLimit: string; memoryLimit: string; storageGi: number }
> = {
[OptionalService.REDIS]: { cpuLimit: '200m', memoryLimit: '256Mi', storageGi: 1 },
[OptionalService.RABBITMQ]: { cpuLimit: '500m', memoryLimit: '512Mi', storageGi: 2 },
[OptionalService.ELASTICSEARCH]: { cpuLimit: '50m', memoryLimit: '64Mi', storageGi: 0 },
};
/** Fluent Bit sidecar per workload when Elasticsearch logging is enabled. */
export const FLUENT_BIT_SIDECAR = { cpuLimit: '50m', memoryLimit: '64Mi' };
export const RESOURCE_LABELS: Record<PricingResourceType, string> = {
[PricingResourceType.BASE_FEE]: 'Base fee',
@@ -27,8 +63,14 @@ export const RESOURCE_LABELS: Record<PricingResourceType, string> = {
[PricingResourceType.MEMORY_PER_GB]: 'Memory (per GB)',
[PricingResourceType.STORAGE_PER_GB]: 'Storage (per GB)',
[PricingResourceType.DATABASE_ADDON]: 'Database addon',
[PricingResourceType.REDIS_ADDON]: 'Redis addon',
[PricingResourceType.RABBITMQ_ADDON]: 'RabbitMQ addon',
[PricingResourceType.ELASTICSEARCH_ADDON]: 'Elasticsearch addon',
[PricingResourceType.REDIS_ADDON]: 'Redis (flat addon)',
[PricingResourceType.RABBITMQ_ADDON]: 'RabbitMQ (flat addon)',
[PricingResourceType.ELASTICSEARCH_ADDON]: 'Elasticsearch (flat addon)',
[PricingResourceType.CUSTOM_DOMAIN_ADDON]: 'Custom domain + SSL',
};
/** @deprecated Use getAllBillingRuntimes() */
export const BILLING_RUNTIMES = getAllBillingRuntimes();
/** @deprecated Use getBillableAddonResourceTypes() */
export const ADDON_PRICING_RESOURCES = getBillableAddonResourceTypes();