diff --git a/app/api/github/releases/route.ts b/app/api/github/releases/route.ts index f6fa8c8..46de7b2 100644 --- a/app/api/github/releases/route.ts +++ b/app/api/github/releases/route.ts @@ -2,11 +2,8 @@ import { NextResponse } from 'next/server' const DEFAULT_REPOSITORIES = [ 'NodeByteHosting/website', - 'NodeByteHosting/backend', 'NodeByteHosting/Game-Panel', - 'NodeByteLTD/ByteSend', - 'NodeByteLTD/ByteProxy', - 'NodeByteLTD/bytesend-go' + 'NodeByteHosting/BytePay' ]; export interface GitHubRelease { diff --git a/packages/core/constants/supported-games.ts b/packages/core/constants/supported-games.ts index 1f27c38..ad17d0f 100644 --- a/packages/core/constants/supported-games.ts +++ b/packages/core/constants/supported-games.ts @@ -6,6 +6,7 @@ */ export const SUPPORTED_GAMES = [ "Minecraft", - "Rust", "Hytale", + "Palworld", + "Rust", ] as const diff --git a/packages/core/lib/bytepay.ts b/packages/core/lib/bytepay.ts index 046377d..d94fa62 100644 --- a/packages/core/lib/bytepay.ts +++ b/packages/core/lib/bytepay.ts @@ -211,12 +211,19 @@ async function fetchCategoryTree(): Promise { name: hub.name, slug: hub.slug, description: hub.description, - // A hub with no sub-categories in Paymenter IS the leaf its products - // belong to directly (e.g. bare-metal servers filed straight under - // "Dedicated Servers" with no tiers underneath yet) — treat it as - // its own single child so pages that iterate `hub.children` still - // find those products instead of seeing an empty list. - children: children.length > 0 ? children : [{ id: hub.id, name: hub.name, slug: hub.slug, description: hub.description, parentId: null }], + // Products can be filed directly on the hub category itself — either + // because it has no sub-categories at all yet (e.g. bare-metal + // servers filed straight under "Dedicated Servers"), or because it + // has real sub-categories but *also* some generic products of its + // own (e.g. unified "GAME-BASE"/"GAME-PLUS" tiers filed directly on + // "Game Servers" alongside its existing Minecraft/Rust/Hytale + // children). Always include the hub itself as a leaf, in addition to + // any real children, so pages that iterate `hub.children` never miss + // hub-level products. + children: [ + { id: hub.id, name: hub.name, slug: hub.slug, description: hub.description, parentId: null }, + ...children, + ], } }) } diff --git a/packages/core/lib/spec-parser.ts b/packages/core/lib/spec-parser.ts index 21999d4..d19f54b 100644 --- a/packages/core/lib/spec-parser.ts +++ b/packages/core/lib/spec-parser.ts @@ -27,6 +27,12 @@ export interface ParsedSpecs { hardware?: "amd" | "intel" | "arm" /** Short description extracted from the first bullet's subtitle. */ description?: string + /** Location(s) named in a "location"/"region" bullet, e.g. "Newcastle, UK / New York, US" — omitted if none named. */ + location?: string + /** Number of databases included, e.g. "3x MySQL Databases" → 3. */ + databases?: number + /** Whether the description mentions automatic/included backups. */ + backups?: boolean } function stripHtml(html: string): string { @@ -48,10 +54,15 @@ function bulletLines(html: string): string[] { const text = withBreaks .replace(/<[^>]*>/g, " ") .replace(/&/g, "&") + .replace(/>/g, ">") + .replace(/</g, "<") + .replace(/"/g, '"') + .replace(/�?39;/g, "'") + .replace(/ /g, " ") .replace(/[ \t]+/g, " ") return text .split(/[•\n]/) - .map((s) => s.trim()) + .map((s) => s.trim().replace(/^>\s*/, "")) .filter(Boolean) } @@ -200,7 +211,27 @@ export function parseDescriptionSpecs(html: string | null): ParsedSpecs { if (m) { description = m[1].trim(); break } } - return { cpu, ramGB, ramType, storageGB, storageDescription, storageType, bandwidth, uplink, cpuModel, hardware, description } + // ── Location ─────────────────────────────────────────────────────────────── + // "Flexible Regional Hosting: ... choose between Newcastle, UK or New York, + // US ..." — pull "City, XX" style place names out of any bullet mentioning + // location/region, joining multiple choices with " / ". + let location: string | undefined + const locationLine = lines.find((line) => /\blocation|\bregion/i.test(line)) + if (locationLine) { + const places = locationLine.match(/\b[A-Z][a-zA-Z]+(?:\s[A-Z][a-zA-Z]+)*,\s*[A-Z]{2,3}\b/g) + if (places && places.length > 0) location = places.join(" / ") + } + + // ── Databases ────────────────────────────────────────────────────────────── + // "3x MySQL Databases", "5x MySQL Databases Included", "10 isolated MySQL databases" + let databases: number | undefined + const dbMatch = text.match(/\b(\d+)x?\s+(?:isolated\s+)?(?:MySQL\s+|PostgreSQL\s+|SQL\s+)?[Dd]atabases?\b/) + if (dbMatch) databases = parseInt(dbMatch[1]) + + // ── Backups ──────────────────────────────────────────────────────────────── + const backups = /\bbackups?\b/i.test(text) || undefined + + return { cpu, ramGB, ramType, storageGB, storageDescription, storageType, bandwidth, uplink, cpuModel, hardware, description, location, databases, backups } } /** Human-friendly storage type label — falls back to "Storage Array" when the description didn't name a drive type. */ diff --git a/packages/core/products/billing-service.ts b/packages/core/products/billing-service.ts index de6535e..6ca3075 100644 --- a/packages/core/products/billing-service.ts +++ b/packages/core/products/billing-service.ts @@ -61,7 +61,10 @@ export async function getGamePlans(categorySlug: string): Promise series: series as VpsPlanSpec["series"], hardware: parsed.hardware, cpuModel: parsed.cpuModel, - location: undefined, + location: parsed.location, description: parsed.description, + databases: parsed.databases, + backups: parsed.backups, cpu: parsed.cpu, ramGB: parsed.ramGB, storageGB: parsed.storageGB, diff --git a/packages/core/types/servers/dedicated.ts b/packages/core/types/servers/dedicated.ts index 7cf76d6..37e63ce 100644 --- a/packages/core/types/servers/dedicated.ts +++ b/packages/core/types/servers/dedicated.ts @@ -16,6 +16,10 @@ export interface DedicatedPlanSpec { id: string description?: string + /** Number of databases included, e.g. "3x MySQL Databases" → 3. */ + databases?: number + /** Whether the description mentions automatic/included backups. */ + backups?: boolean cpuModel?: string hardware?: "amd" | "intel" priceGBP: number diff --git a/packages/core/types/servers/game.ts b/packages/core/types/servers/game.ts index 3759fee..46f9a66 100644 --- a/packages/core/types/servers/game.ts +++ b/packages/core/types/servers/game.ts @@ -21,6 +21,10 @@ export interface GamePlanSpec { /** Paymenter category slug this plan was fetched from, e.g. "minecraft" — plans are shown in one unified grid regardless, this is just for search/de-duplication. */ category: string description?: string + /** Number of databases included, e.g. "3x MySQL Databases" → 3. */ + databases?: number + /** Whether the description mentions automatic/included backups. */ + backups?: boolean cpuModel?: string priceGBP: number /** One-time setup fee in GBP (0 if none). */ diff --git a/packages/core/types/servers/vps.ts b/packages/core/types/servers/vps.ts index 11d7d56..a2c06e8 100644 --- a/packages/core/types/servers/vps.ts +++ b/packages/core/types/servers/vps.ts @@ -17,6 +17,10 @@ export interface VpsPlanSpec { id: string description?: string + /** Number of databases included, e.g. "3x MySQL Databases" → 3. */ + databases?: number + /** Whether the description mentions automatic/included backups. */ + backups?: boolean cpuModel?: string priceGBP: number /** One-time setup fee in GBP (0 if none). */ diff --git a/packages/ui/components/Layouts/Dedicated/dedicated-hub.tsx b/packages/ui/components/Layouts/Dedicated/dedicated-hub.tsx index d92e971..4adc058 100644 --- a/packages/ui/components/Layouts/Dedicated/dedicated-hub.tsx +++ b/packages/ui/components/Layouts/Dedicated/dedicated-hub.tsx @@ -56,7 +56,7 @@ function PlanCard({ plan }: { plan: DedicatedPlanSpec }) { return (
)} {plan.location && } + {plan.databases != null && } + {plan.backups && } @@ -346,7 +348,7 @@ export function DedicatedHub({ plans }: DedicatedHubProps) {

Showing {filtered.length} of {plans.length} server{plans.length !== 1 ? "s" : ""}

-
+
{filtered.map((plan) => ( ))} diff --git a/packages/ui/components/Layouts/Games/game-hub.tsx b/packages/ui/components/Layouts/Games/game-hub.tsx index 00a9b83..49c03c9 100644 --- a/packages/ui/components/Layouts/Games/game-hub.tsx +++ b/packages/ui/components/Layouts/Games/game-hub.tsx @@ -49,7 +49,7 @@ function PlanCard({ plan }: { plan: GamePlanSpec }) { return (
)} + {plan.location && } + {plan.databases != null && } + {plan.backups && } @@ -288,7 +291,7 @@ export function GameHub({ plans }: GameHubProps) {

Showing {filtered.length} of {plans.length} plan{plans.length !== 1 ? "s" : ""}

-
+
{filtered.map((plan) => ( ))} diff --git a/packages/ui/components/Layouts/VPS/vps-hub.tsx b/packages/ui/components/Layouts/VPS/vps-hub.tsx index 33491db..5a5c06c 100644 --- a/packages/ui/components/Layouts/VPS/vps-hub.tsx +++ b/packages/ui/components/Layouts/VPS/vps-hub.tsx @@ -96,7 +96,7 @@ function PlanCard({ plan }: { plan: VpsPlanSpec }) { return (
)} {plan.location && } + {plan.databases != null && } + {plan.backups && } @@ -431,7 +433,7 @@ export function VpsHub({ plans }: VpsHubProps) {

Showing {filtered.length} of {plans.length} plans

-
+
{filtered.map((plan) => ( ))}