diff --git a/backend/.environment b/backend/.environment index ee8b761..eff3b86 100644 --- a/backend/.environment +++ b/backend/.environment @@ -1 +1,2 @@ export BASE_URL=$(echo $PLATFORM_ROUTES | base64 -d | jq -r 'to_entries[] | select(.value.id == "backend") | .key' | sed 's:/*$::') +export GITHUB_BRANCH=$(echo $PLATFORM_BRANCH) \ No newline at end of file diff --git a/backend/src/routes/region.routes.ts b/backend/src/routes/region.routes.ts index 03507db..c6cb421 100644 --- a/backend/src/routes/region.routes.ts +++ b/backend/src/routes/region.routes.ts @@ -11,7 +11,7 @@ import { HostRegionsList } from '../schemas/region.schema.js'; import { HeaderAcceptSchema, ErrorDetailsSchema } from '../schemas/api.schema.js'; -import { withSelfLinkArray } from '../utils/api.schema.js'; +import { withSelfLink } from '../utils/api.schema.js'; const TAG = 'Regions'; const PATH = '/regions'; @@ -76,14 +76,14 @@ regionRouter.route({ const safeZone = zone ? escapeHtml(zone) : undefined; const safeCountryCode = country_code ? escapeHtml(country_code) : undefined; - // Get regions list - let regions = await resourceManager.getResource('host/regions.json'); + // Get regions record + let regionsRecord: Record = await resourceManager.getResource('host/regions.json'); // Apply filters if (name) { - const region = regions.find((r: any) => r.name === name); - if (!region) { - const availableRegions = regions.map((r: any) => r.name); + const entry = Object.entries(regionsRecord).find(([, r]) => r.name === name); + if (!entry) { + const availableRegions = Object.values(regionsRecord).map((r: any) => r.name); apiLogger.warn({ region: safeName }, 'Region not found'); return sendErrorFormatted(res, { title: `Region '${safeName}' not found`, @@ -95,12 +95,12 @@ regionRouter.route({ } if (provider) { - regions = regions.filter( - (r: any) => r.provider?.name?.toLowerCase() === provider.toLowerCase() + const filtered = Object.fromEntries( + Object.entries(regionsRecord).filter(([, r]) => r.provider?.name?.toLowerCase() === provider.toLowerCase()) ); - if (regions.length === 0) { + if (Object.keys(filtered).length === 0) { const availableProviders = [...new Set( - (await resourceManager.getResource('host/regions.json')) + Object.values(regionsRecord) .map((r: any) => r.provider?.name) .filter(Boolean) )]; @@ -112,15 +112,16 @@ regionRouter.route({ extra: { availableProviders } }); } + regionsRecord = filtered; } if (zone) { - regions = regions.filter( - (r: any) => r.zone && r.zone.toLowerCase() === zone.toLowerCase() + const filtered = Object.fromEntries( + Object.entries(regionsRecord).filter(([, r]) => r.zone && r.zone.toLowerCase() === zone.toLowerCase()) ); - if (regions.length === 0) { + if (Object.keys(filtered).length === 0) { const availableZones = [...new Set( - (await resourceManager.getResource('host/regions.json')) + Object.values(regionsRecord) .map((r: any) => r.zone) .filter((z: string) => z) )]; @@ -132,15 +133,16 @@ regionRouter.route({ extra: { availableZones } }); } + regionsRecord = filtered; } if (country_code) { - regions = regions.filter( - (r: any) => r.country_code?.toLowerCase() === country_code.toLowerCase() + const filtered = Object.fromEntries( + Object.entries(regionsRecord).filter(([, r]) => r.country_code?.toLowerCase() === country_code.toLowerCase()) ); - if (regions.length === 0) { + if (Object.keys(filtered).length === 0) { const availableCountryCodes = [...new Set( - (await resourceManager.getResource('host/regions.json')) + Object.values(regionsRecord) .map((r: any) => r.country_code) .filter(Boolean) )]; @@ -152,12 +154,13 @@ regionRouter.route({ extra: { availableCountryCodes } }); } + regionsRecord = filtered; } - // Return list - const regionSafe = HostRegionsListSchema.parse(regions); + // Return record with self links + const parsed = HostRegionsListSchema.parse(regionsRecord); const baseUrl = `${config.server.BASE_URL}`; - const regionsWithLinks = withSelfLinkArray(regionSafe, (id) => `${baseUrl}${PATH}/${encodeURIComponent(id)}`); + const regionsWithLinks = withSelfLink(parsed, (id) => `${baseUrl}${PATH}/${encodeURIComponent(id)}`); sendFormatted(res, regionsWithLinks); @@ -204,14 +207,14 @@ regionRouter.route({ const { id } = req.params as { id: string }; const safeId = escapeHtml(id); - // Get regions list - let regions = await resourceManager.getResource('host/regions.json'); + // Get regions record and look up by key + const regionsRecord = await resourceManager.getResource('host/regions.json'); // Apply filters if (id) { - const region = regions.find((r: any) => r.id === id); + const region = regionsRecord[id] ?? Object.values(regionsRecord).find((r: any) => r.id === id); if (!region) { - const availableRegions = regions.map((r: any) => r.id); + const availableRegions = Object.keys(regionsRecord); apiLogger.warn({ region: safeId }, 'Region not found'); return sendErrorFormatted(res, { title: `Region '${safeId}' not found`, diff --git a/backend/src/schemas/region.schema.ts b/backend/src/schemas/region.schema.ts index 93090a2..71d1bd2 100644 --- a/backend/src/schemas/region.schema.ts +++ b/backend/src/schemas/region.schema.ts @@ -128,11 +128,23 @@ export const HostRegionSchema = z.object({ description: 'Detailed host region metadata mirrored from resources/host/regions.json', }); -export const HostRegionsListSchema = z.array(HostRegionSchema.omit({ private: true, note: true, datacenter: true })) - .openapi('HostRegionList', { - description: 'Complete list of host region metadata entries' +export const HostRegionListItemSchema = HostRegionSchema.omit({ private: true, note: true, datacenter: true }) + .openapi('HostRegionListItem', { + description: 'Region metadata entry without internal fields' }); +// export const HostRegionsListSchema = z.array(HostRegionListItemSchema) +// .openapi('HostRegionList', { +// description: 'Complete list of host region metadata entries' +// }); + +export const HostRegionsListSchema = z.record( + z.string(), + HostRegionSchema +).openapi('HostRegionsFile', { + description: 'Raw regions.json file structure: a record of region ID to full region object' +}); + const providerFilterValues = ['AWS', 'Azure', 'Google', 'OVH', 'none'] as const; const zoneFilterValues = ['Australia', 'Europe', 'North America'] as const; const countryCodeFilterValues = ['AU', 'CA', 'CH', 'DE', 'FR', 'GB', 'IE', 'SE', 'US', 'none'] as const; diff --git a/resources/host/regions.json b/resources/host/regions.json index e40dec5..b00fc63 100644 --- a/resources/host/regions.json +++ b/resources/host/regions.json @@ -1,5 +1,5 @@ -[ - { +{ + "au": { "id": "au", "label": "Australia (au)", "name": "au.platform.sh", @@ -41,7 +41,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "au-2": { "id": "au-2", "label": "Australia (au-2)", "name": "au-2.platform.sh", @@ -83,7 +83,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "ca-1": { "id": "ca-1", "label": "Canada (ca-1)", "name": "ca-1.platform.sh", @@ -125,7 +125,7 @@ "update_date": "2025-10-09T16:18:04.260Z", "eol_date": null }, - { + "ch-1": { "id": "ch-1", "label": "Switzerland (ch-1)", "name": "ch-1.platform.sh", @@ -167,7 +167,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "de-2": { "id": "de-2", "label": "Germany (de-2)", "name": "de-2.platform.sh", @@ -209,7 +209,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "eu": { "id": "eu", "label": "Europe - Ireland (eu)", "name": "eu.platform.sh", @@ -251,7 +251,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "eu-5": { "id": "eu-5", "label": "Europe - Sweden (eu-5)", "name": "eu-5.platform.sh", @@ -291,7 +291,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "fr-3": { "id": "fr-3", "label": "France (fr-3)", "name": "fr-3.platform.sh", @@ -331,7 +331,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "fr-4": { "id": "fr-4", "label": "France (fr-4)", "name": "fr-4.platform.sh", @@ -373,7 +373,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "uk-1": { "id": "uk-1", "label": "United Kingdom (uk-1)", "name": "uk-1.platform.sh", @@ -416,7 +416,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "us": { "id": "us", "label": "United States - East (us)", "name": "us.platform.sh", @@ -458,7 +458,7 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null }, - { + "us-2": { "id": "us-2", "label": "United States - East (us-2)", "name": "us-2.platform.sh", @@ -500,7 +500,7 @@ "update_date": "2025-08-09T07:05:33.080Z", "eol_date": null }, - { + "us-3": { "id": "us-3", "label": "United States - West (us-3)", "name": "us-3.platform.sh", @@ -542,7 +542,7 @@ "update_date": "2025-10-09T16:15:09.555Z", "eol_date": null }, - { + "us-4": { "id": "us-4", "label": "United States - East (us-4)", "name": "us-4.platform.sh", @@ -584,4 +584,4 @@ "update_date": "2024-10-21T16:09:17.273Z", "eol_date": null } -] +}