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
5 changes: 1 addition & 4 deletions app/api/github/releases/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/constants/supported-games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
export const SUPPORTED_GAMES = [
"Minecraft",
"Rust",
"Hytale",
"Palworld",
"Rust",
] as const
19 changes: 13 additions & 6 deletions packages/core/lib/bytepay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,19 @@ async function fetchCategoryTree(): Promise<CategoryHub[]> {
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,
],
}
})
}
Expand Down
35 changes: 33 additions & 2 deletions packages/core/lib/spec-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
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 {
Expand All @@ -45,13 +51,18 @@
const withBreaks = html
.replace(/<\/(li|p|div|h[1-6])>/gi, "\n")
.replace(/<br\s*\/?>/gi, "\n")
const text = withBreaks
.replace(/<[^>]*>/g, " ")
.replace(/&amp;/g, "&")

Check failure

Code scanning / CodeQL

Double escaping or unescaping High

This replacement may produce '&' characters that are double-unescaped
here
.
.replace(/&gt;/g, ">")
.replace(/&lt;/g, "<")
.replace(/&quot;/g, '"')
.replace(/&#0?39;/g, "'")
.replace(/&nbsp;/g, " ")
.replace(/[ \t]+/g, " ")
return text
.split(/[•\n]/)
.map((s) => s.trim())
.map((s) => s.trim().replace(/^>\s*/, ""))
.filter(Boolean)
}

Expand Down Expand Up @@ -200,7 +211,27 @@
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. */
Expand Down
10 changes: 9 additions & 1 deletion packages/core/products/billing-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export async function getGamePlans(categorySlug: string): Promise<GamePlanSpec[]
id: product.slug,
name: product.name,
category: categorySlug,
location: parsed.location,
description: parsed.description,
databases: parsed.databases,
backups: parsed.backups,
ramGB: parsed.ramGB,
ramType: parsed.ramType,
storageGB: parsed.storageGB,
Expand Down Expand Up @@ -99,7 +102,10 @@ export async function getDedicatedPlans(categorySlug: string): Promise<Dedicated
id: product.slug,
hardware: parsed.hardware as DedicatedPlanSpec["hardware"],
cpuModel: parsed.cpuModel,
location: parsed.location,
description: parsed.description,
databases: parsed.databases,
backups: parsed.backups,
cores: parsed.cpu,
ramGB: parsed.ramGB,
storageGB: parsed.storageGB,
Expand Down Expand Up @@ -148,8 +154,10 @@ export async function getVpsPlans(categorySlug: string): Promise<VpsPlanSpec[]>
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,
Expand Down
4 changes: 4 additions & 0 deletions packages/core/types/servers/dedicated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions packages/core/types/servers/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
4 changes: 4 additions & 0 deletions packages/core/types/servers/vps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/components/Layouts/Dedicated/dedicated-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function PlanCard({ plan }: { plan: DedicatedPlanSpec }) {
return (
<div
className={cn(
"relative h-full flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"relative flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"hover:shadow-xl hover:shadow-primary/5",
plan.popular
? "border-primary/40 hover:border-primary/60"
Expand Down Expand Up @@ -170,6 +170,8 @@ function PlanCard({ plan }: { plan: DedicatedPlanSpec }) {
<PlanInfoRow label="Uplink" value={`${plan.uplink.amount} ${plan.uplink.unit}`} />
)}
{plan.location && <PlanInfoRow label="Location" value={plan.location} />}
{plan.databases != null && <PlanInfoRow label="Databases" value={`${plan.databases}x MySQL`} />}
{plan.backups && <PlanInfoRow label="Backups" value="Automatic" />}
</CollapsibleContent>
</Collapsible>

Expand Down Expand Up @@ -346,7 +348,7 @@ export function DedicatedHub({ plans }: DedicatedHubProps) {
<p className="text-xs text-muted-foreground mb-4">
Showing {filtered.length} of {plans.length} server{plans.length !== 1 ? "s" : ""}
</p>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="grid items-start sm:grid-cols-2 lg:grid-cols-3 gap-6">
{filtered.map((plan) => (
<PlanCard key={plan.id} plan={plan} />
))}
Expand Down
7 changes: 5 additions & 2 deletions packages/ui/components/Layouts/Games/game-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function PlanCard({ plan }: { plan: GamePlanSpec }) {
return (
<div
className={cn(
"relative h-full flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"relative flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"hover:shadow-xl hover:shadow-primary/5",
plan.popular
? "border-primary/40 hover:border-primary/60"
Expand Down Expand Up @@ -143,6 +143,9 @@ function PlanCard({ plan }: { plan: GamePlanSpec }) {
{plan.uplink && (
<PlanInfoRow label="Uplink" value={`${plan.uplink.amount} ${plan.uplink.unit}`} />
)}
{plan.location && <PlanInfoRow label="Locations" value={plan.location} />}
{plan.databases != null && <PlanInfoRow label="Databases" value={`${plan.databases}x MySQL`} />}
{plan.backups && <PlanInfoRow label="Backups" value="Automatic" />}
</CollapsibleContent>
</Collapsible>

Expand Down Expand Up @@ -288,7 +291,7 @@ export function GameHub({ plans }: GameHubProps) {
<p className="text-xs text-muted-foreground mb-4">
Showing {filtered.length} of {plans.length} plan{plans.length !== 1 ? "s" : ""}
</p>
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="grid items-start sm:grid-cols-2 lg:grid-cols-3 gap-6">
{filtered.map((plan) => (
<PlanCard key={plan.id} plan={plan} />
))}
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/components/Layouts/VPS/vps-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function PlanCard({ plan }: { plan: VpsPlanSpec }) {
return (
<div
className={cn(
"relative h-full flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"relative flex flex-col rounded-2xl border bg-card/30 backdrop-blur-sm transition-all duration-300",
"hover:shadow-xl hover:shadow-primary/5",
plan.popular
? "border-primary/40 hover:border-primary/60"
Expand Down Expand Up @@ -213,6 +213,8 @@ function PlanCard({ plan }: { plan: VpsPlanSpec }) {
/>
)}
{plan.location && <PlanInfoRow label="Location" value={plan.location} />}
{plan.databases != null && <PlanInfoRow label="Databases" value={`${plan.databases}x MySQL`} />}
{plan.backups && <PlanInfoRow label="Backups" value="Automatic" />}
</CollapsibleContent>
</Collapsible>

Expand Down Expand Up @@ -431,7 +433,7 @@ export function VpsHub({ plans }: VpsHubProps) {
<p className="text-xs text-muted-foreground mb-4">
Showing {filtered.length} of {plans.length} plans
</p>
<div className="grid sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6">
<div className="grid items-start sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{filtered.map((plan) => (
<PlanCard key={plan.id} plan={plan} />
))}
Expand Down