Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/.environment
Original file line number Diff line number Diff line change
@@ -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)
53 changes: 28 additions & 25 deletions backend/src/routes/region.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string, any> = 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`,
Expand All @@ -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)
)];
Expand All @@ -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)
)];
Expand All @@ -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)
)];
Expand All @@ -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<HostRegionsList>(res, regionsWithLinks);

Expand Down Expand Up @@ -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`,
Expand Down
18 changes: 15 additions & 3 deletions backend/src/schemas/region.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
32 changes: 16 additions & 16 deletions resources/host/regions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[
{
{
"au": {
"id": "au",
"label": "Australia (au)",
"name": "au.platform.sh",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -584,4 +584,4 @@
"update_date": "2024-10-21T16:09:17.273Z",
"eol_date": null
}
]
}
Loading