@@ -16,12 +16,24 @@ import type {
1616 BillingPlanProductConfigs ,
1717 BillingPlanQuota ,
1818} from 'core/modules/platform/billing/billing-plan.types'
19+ import type {
20+ StoragePlanCatalog ,
21+ StoragePlanPricingConfigs ,
22+ StoragePlanProductConfigs ,
23+ } from 'core/modules/platform/billing/storage-plan.types'
1924import { sql } from 'drizzle-orm'
2025import { injectable } from 'tsyringe'
2126import type { ZodType } from 'zod'
2227import { z } from 'zod'
2328
2429import { getUiSchemaTranslator } from '../../ui/ui-schema/ui-schema.i18n'
30+ import type { BuilderStorageProvider } from '../setting/storage-provider.utils'
31+ import {
32+ maskStorageProviderSecrets ,
33+ mergeStorageProviderSecrets ,
34+ parseStorageProviders ,
35+ serializeStorageProviders ,
36+ } from '../setting/storage-provider.utils'
2537import type { SystemSettingDbField } from './system-setting.constants'
2638import {
2739 BILLING_PLAN_FIELD_DESCRIPTORS ,
@@ -135,6 +147,29 @@ export class SystemSettingService {
135147 BILLING_PLAN_PRICING_SCHEMA ,
136148 { } ,
137149 ) as BillingPlanPricingConfigs
150+ const storagePlanCatalog = this . parseSetting (
151+ rawValues [ SYSTEM_SETTING_DEFINITIONS . storagePlanCatalog . key ] ,
152+ STORAGE_PLAN_CATALOG_SCHEMA ,
153+ SYSTEM_SETTING_DEFINITIONS . storagePlanCatalog . defaultValue as StoragePlanCatalog ,
154+ ) as StoragePlanCatalog
155+ const storagePlanProducts = this . parseSetting (
156+ rawValues [ SYSTEM_SETTING_DEFINITIONS . storagePlanProducts . key ] ,
157+ STORAGE_PLAN_PRODUCTS_SCHEMA ,
158+ { } ,
159+ ) as StoragePlanProductConfigs
160+ const storagePlanPricing = this . parseSetting (
161+ rawValues [ SYSTEM_SETTING_DEFINITIONS . storagePlanPricing . key ] ,
162+ STORAGE_PLAN_PRICING_SCHEMA ,
163+ { } ,
164+ ) as StoragePlanPricingConfigs
165+ const managedStorageProvider = this . parseSetting (
166+ rawValues [ SYSTEM_SETTING_DEFINITIONS . managedStorageProvider . key ] ,
167+ SYSTEM_SETTING_DEFINITIONS . managedStorageProvider . schema ,
168+ SYSTEM_SETTING_DEFINITIONS . managedStorageProvider . defaultValue ,
169+ )
170+ const managedStorageProviders = this . parseManagedStorageProviders (
171+ rawValues [ SYSTEM_SETTING_DEFINITIONS . managedStorageProviders . key ] ,
172+ )
138173 return {
139174 allowRegistration,
140175 maxRegistrableUsers,
@@ -151,6 +186,11 @@ export class SystemSettingService {
151186 billingPlanOverrides,
152187 billingPlanProducts,
153188 billingPlanPricing,
189+ storagePlanCatalog,
190+ storagePlanProducts,
191+ storagePlanPricing,
192+ managedStorageProvider,
193+ managedStorageProviders,
154194 }
155195 }
156196
@@ -169,6 +209,26 @@ export class SystemSettingService {
169209 return settings . billingPlanPricing ?? { }
170210 }
171211
212+ async getStoragePlanCatalog ( ) : Promise < StoragePlanCatalog > {
213+ const settings = await this . getSettings ( )
214+ return settings . storagePlanCatalog ?? { }
215+ }
216+
217+ async getStoragePlanProducts ( ) : Promise < StoragePlanProductConfigs > {
218+ const settings = await this . getSettings ( )
219+ return settings . storagePlanProducts ?? { }
220+ }
221+
222+ async getStoragePlanPricing ( ) : Promise < StoragePlanPricingConfigs > {
223+ const settings = await this . getSettings ( )
224+ return settings . storagePlanPricing ?? { }
225+ }
226+
227+ async getManagedStorageProviderKey ( ) : Promise < string | null > {
228+ const settings = await this . getSettings ( )
229+ return settings . managedStorageProvider ?? null
230+ }
231+
172232 async getOverview ( ) : Promise < SystemSettingOverview > {
173233 const settings = await this . getSettings ( )
174234 const totalUsers = await this . getTotalUserCount ( )
@@ -194,9 +254,17 @@ export class SystemSettingService {
194254
195255 const updates : Array < { field : SystemSettingDbField ; value : unknown } > = [ ]
196256
197- const enqueueUpdate = < K extends SystemSettingDbField > ( field : K , value : unknown ) => {
257+ const enqueueUpdate = < K extends SystemSettingDbField > (
258+ field : K ,
259+ value : unknown ,
260+ currentValue ?: SystemSettings [ K ] ,
261+ ) => {
198262 updates . push ( { field, value } )
199- ; ( current as unknown as Record < string , unknown > ) [ field ] = value
263+ if ( currentValue !== undefined ) {
264+ ; ( current as unknown as Record < string , unknown > ) [ field ] = currentValue
265+ } else {
266+ ; ( current as unknown as Record < string , unknown > ) [ field ] = value
267+ }
200268 }
201269
202270 if ( patch . allowRegistration !== undefined && patch . allowRegistration !== current . allowRegistration ) {
@@ -287,6 +355,37 @@ export class SystemSettingService {
287355 }
288356 }
289357
358+ if ( patch . storagePlanCatalog !== undefined ) {
359+ const parsed = STORAGE_PLAN_CATALOG_SCHEMA . parse ( patch . storagePlanCatalog )
360+ enqueueUpdate ( 'storagePlanCatalog' , parsed )
361+ }
362+
363+ if ( patch . storagePlanProducts !== undefined ) {
364+ const parsed = STORAGE_PLAN_PRODUCTS_SCHEMA . parse ( patch . storagePlanProducts )
365+ enqueueUpdate ( 'storagePlanProducts' , parsed )
366+ }
367+
368+ if ( patch . storagePlanPricing !== undefined ) {
369+ const parsed = STORAGE_PLAN_PRICING_SCHEMA . parse ( patch . storagePlanPricing )
370+ enqueueUpdate ( 'storagePlanPricing' , parsed )
371+ }
372+
373+ if ( patch . managedStorageProvider !== undefined && patch . managedStorageProvider !== current . managedStorageProvider ) {
374+ enqueueUpdate ( 'managedStorageProvider' , this . normalizeNullableString ( patch . managedStorageProvider ) )
375+ }
376+
377+ if ( patch . managedStorageProviders !== undefined ) {
378+ const normalizedProviders = this . normalizeManagedStorageProvidersPatch (
379+ patch . managedStorageProviders ,
380+ current . managedStorageProviders ?? [ ] ,
381+ )
382+ const nextSerialized = serializeStorageProviders ( normalizedProviders )
383+ const currentSerialized = serializeStorageProviders ( current . managedStorageProviders ?? [ ] )
384+ if ( nextSerialized !== currentSerialized ) {
385+ enqueueUpdate ( 'managedStorageProviders' , nextSerialized , normalizedProviders )
386+ }
387+ }
388+
290389 if ( updates . length === 0 ) {
291390 return current
292391 }
@@ -309,6 +408,10 @@ export class SystemSettingService {
309408 const map = { } as SystemSettingValueMap
310409
311410 ; ( Object . keys ( SYSTEM_SETTING_DEFINITIONS ) as SystemSettingDbField [ ] ) . forEach ( ( field ) => {
411+ if ( field === 'managedStorageProviders' ) {
412+ ; ( map as Record < string , unknown > ) [ field ] = maskStorageProviderSecrets ( settings . managedStorageProviders ?? [ ] )
413+ return
414+ }
312415 ; ( map as Record < string , unknown > ) [ field ] = settings [ field ]
313416 } )
314417
@@ -342,6 +445,51 @@ export class SystemSettingService {
342445 return map
343446 }
344447
448+ private parseManagedStorageProviders ( raw : unknown ) : BuilderStorageProvider [ ] {
449+ if ( ! raw ) {
450+ return [ ]
451+ }
452+
453+ if ( Array . isArray ( raw ) || typeof raw === 'object' ) {
454+ try {
455+ return parseStorageProviders ( JSON . stringify ( raw ) )
456+ } catch {
457+ return [ ]
458+ }
459+ }
460+
461+ if ( typeof raw === 'string' ) {
462+ const normalized = raw . trim ( )
463+ if ( ! normalized ) {
464+ return [ ]
465+ }
466+ return parseStorageProviders ( normalized )
467+ }
468+
469+ return [ ]
470+ }
471+
472+ private normalizeManagedStorageProvidersPatch (
473+ patch : unknown ,
474+ current : BuilderStorageProvider [ ] ,
475+ ) : BuilderStorageProvider [ ] {
476+ let normalized : string
477+ if ( typeof patch === 'string' ) {
478+ normalized = patch
479+ } else if ( patch == null ) {
480+ normalized = '[]'
481+ } else {
482+ try {
483+ normalized = JSON . stringify ( patch )
484+ } catch {
485+ normalized = '[]'
486+ }
487+ }
488+
489+ const incoming = parseStorageProviders ( normalized )
490+ return mergeStorageProviderSecrets ( incoming , current ?? [ ] )
491+ }
492+
345493 private extractPlanFieldUpdates ( patch : UpdateSystemSettingsInput ) : PlanFieldUpdateSummary {
346494 const summary : PlanFieldUpdateSummary = {
347495 hasUpdates : false ,
@@ -608,6 +756,17 @@ const PLAN_PRICING_ENTRY_SCHEMA = z.object({
608756
609757const BILLING_PLAN_PRICING_SCHEMA = z . record ( z . string ( ) , PLAN_PRICING_ENTRY_SCHEMA ) . default ( { } )
610758
759+ const STORAGE_PLAN_CATALOG_ENTRY_SCHEMA = z . object ( {
760+ name : z . string ( ) . trim ( ) . min ( 1 ) ,
761+ description : z . string ( ) . trim ( ) . nullable ( ) . optional ( ) ,
762+ capacityBytes : z . number ( ) . int ( ) . min ( 0 ) . nullable ( ) . optional ( ) ,
763+ isActive : z . boolean ( ) . optional ( ) ,
764+ } )
765+
766+ const STORAGE_PLAN_CATALOG_SCHEMA = z . record ( z . string ( ) , STORAGE_PLAN_CATALOG_ENTRY_SCHEMA ) . default ( { } )
767+ const STORAGE_PLAN_PRODUCTS_SCHEMA = z . record ( z . string ( ) , PLAN_PRODUCT_ENTRY_SCHEMA ) . default ( { } )
768+ const STORAGE_PLAN_PRICING_SCHEMA = z . record ( z . string ( ) , PLAN_PRICING_ENTRY_SCHEMA ) . default ( { } )
769+
611770type PlanQuotaUpdateMap = Partial < Record < BillingPlanId , Partial < BillingPlanQuota > > >
612771type PlanPricingUpdateMap = Partial < Record < BillingPlanId , Partial < BillingPlanPricing > > >
613772type PlanProductUpdateMap = Partial < Record < BillingPlanId , BillingPlanPaymentInfo > >
0 commit comments