feat(api): integrate openapi-fetch for Hetzner and Hostinger services, enhancing API interaction and simplifying data fetching

This commit is contained in:
Mauricio Siu
2025-07-23 01:21:11 -06:00
parent 6758ef1542
commit f3dc480b70
10 changed files with 36799 additions and 258 deletions

View File

@@ -71,11 +71,11 @@ export const ShowHostingerServers = () => {
?.sort((a, b) => {
// Sort by monthly promotional price (first_period_price)
const monthlyPriceA =
a.prices.find(
a.prices?.find(
(p) => p.period === 1 && p.period_unit === "month",
)?.first_period_price || 0;
const monthlyPriceB =
b.prices.find(
b.prices?.find(
(p) => p.period === 1 && p.period_unit === "month",
)?.first_period_price || 0;
return monthlyPriceA - monthlyPriceB;
@@ -108,19 +108,19 @@ export const ShowHostingerServers = () => {
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex items-center gap-1">
<Cpu className="h-4 w-4 text-blue-500" />
<span>{plan.metadata.cpus} vCPU</span>
<span>{plan.metadata?.cpus} vCPU</span>
</div>
<div className="flex items-center gap-1">
<MemoryStick className="h-4 w-4 text-green-500" />
<span>{plan.metadata.memory}GB RAM</span>
<span>{plan.metadata?.memory}GB RAM</span>
</div>
<div className="flex items-center gap-1">
<HardDrive className="h-4 w-4 text-orange-500" />
<span>{plan.metadata.disk_space}GB NVMe</span>
<span>{plan.metadata?.disk_space}GB NVMe</span>
</div>
<div className="flex items-center gap-1">
<Globe className="h-4 w-4 text-indigo-500" />
<span>{plan.metadata.bandwidth}TB</span>
<span>{plan.metadata?.bandwidth}TB</span>
</div>
</div>
@@ -148,7 +148,7 @@ export const ShowHostingerServers = () => {
{/* Monthly prices */}
<TabsContent value="monthly" className="mt-2">
{plan.prices
{plan.prices?.
.filter(
(p) =>
p.period === 1 && p.period_unit === "month",
@@ -187,20 +187,20 @@ export const ShowHostingerServers = () => {
{/* Yearly prices */}
<TabsContent value="yearly" className="mt-2">
{plan.prices
{plan.prices?.
.filter(
(p) =>
p.period === 1 && p.period_unit === "year",
)
.map((price) => {
const monthlyEquivalent = plan.prices.find(
const monthlyEquivalent = plan.prices?.find(
(p) =>
p.period === 1 &&
p.period_unit === "month",
);
const savings = monthlyEquivalent
? calculateSavings(
monthlyEquivalent.price / 100,
monthlyEquivalent?.price / 100,
price.first_period_price / 100,
)
: 0;
@@ -247,20 +247,20 @@ export const ShowHostingerServers = () => {
{/* Biennial prices */}
<TabsContent value="biennial" className="mt-2">
{plan.prices
{plan.prices?.
.filter(
(p) =>
p.period === 2 && p.period_unit === "year",
)
.map((price) => {
const monthlyEquivalent = plan.prices.find(
const monthlyEquivalent = plan.prices?.find(
(p) =>
p.period === 1 &&
p.period_unit === "month",
);
const savings = monthlyEquivalent
? calculateSavings(
(monthlyEquivalent.price / 100) * 24,
(monthlyEquivalent?.price / 100) * 24,
price.first_period_price / 100,
)
: 0;

View File

@@ -182,7 +182,8 @@
"tsx": "^4.16.2",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "4.3.2",
"vitest": "^1.6.1"
"vitest": "^1.6.1",
"openapi-typescript": "7.8.0"
},
"ct3aMetadata": {
"initVersion": "7.25.2"

View File

@@ -7,26 +7,16 @@ import { createTRPCRouter, protectedProcedure } from "../trpc";
export const hetznerRouter = createTRPCRouter({
locations: protectedProcedure.query(async () => {
const apiKey = process.env.HETZNER_API_KEY;
if (!apiKey) {
throw new Error("Hetzner API key not configured");
}
return await fetchHetznerLocations(apiKey);
const locations = await fetchHetznerLocations();
console.log(locations);
return locations;
}),
serverTypes: protectedProcedure.query(async () => {
const apiKey = process.env.HETZNER_API_KEY;
if (!apiKey) {
throw new Error("Hetzner API key not configured");
}
return await fetchHetznerServerTypes(apiKey);
return await fetchHetznerServerTypes();
}),
servers: protectedProcedure.query(async () => {
const apiKey = process.env.HETZNER_API_KEY;
if (!apiKey) {
throw new Error("Hetzner API key not configured");
}
return await fetchHetznerServers(apiKey);
return await fetchHetznerServers();
}),
});

View File

@@ -1,34 +1,16 @@
import {
fetchHostingerCatalog,
fetchHostingerDataCenters,
fetchHostingerServers,
} from "@dokploy/server/index";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const hostingerRouter = createTRPCRouter({
vpsPlans: protectedProcedure.query(async () => {
const apiKey = process.env.HOSTINGER_API_KEY;
if (!apiKey) {
throw new Error("Hostinger API key not configured");
}
const catalogItems = await fetchHostingerCatalog(apiKey);
return catalogItems.filter((item) => item.name.startsWith("KVM"));
}),
servers: protectedProcedure.query(async () => {
const apiKey = process.env.HOSTINGER_API_KEY;
if (!apiKey) {
return [];
}
return await fetchHostingerServers(apiKey);
const catalogItems = await fetchHostingerCatalog();
return catalogItems.filter((item) => item?.name?.startsWith("KVM"));
}),
dataCenters: protectedProcedure.query(async () => {
const apiKey = process.env.HOSTINGER_API_KEY;
if (!apiKey) {
throw new Error("Hostinger API key not configured");
}
return await fetchHostingerDataCenters(apiKey);
return await fetchHostingerDataCenters();
}),
});