Skip to content
Draft
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
64 changes: 64 additions & 0 deletions apps/insights/src/app/api/pyth/get-history/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { NextRequest } from "next/server";
import { z } from "zod";

import { getHistory } from "../../../../services/clickhouse";

const queryParamsSchema = z.object({
symbol: z.string(),
range: z.enum(["1H", "1D", "1W", "1M"]),
cluster: z.enum(["pythnet", "pythtest-conformance"]),
from: z.string().transform(Number),
until: z.string().transform(Number),
});

export async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams;

// Parse and validate query parameters
const symbol = searchParams.get("symbol");
const range = searchParams.get("range");
const cluster = searchParams.get("cluster");
const from = searchParams.get("from");
const until = searchParams.get("until");

if (!symbol || !range || !cluster) {
return new Response(
"Missing required parameters. Must provide `symbol`, `range`, and `cluster`",
{ status: 400 }
);
}

try {
// Validate parameters using the schema
const validatedParams = queryParamsSchema.parse({
symbol,
range,
cluster,
from,
until,
});

const data = await getHistory({
symbol: validatedParams.symbol,
range: validatedParams.range,
cluster: validatedParams.cluster,
from: validatedParams.from,
until: validatedParams.until,
});

return Response.json(data);
} catch (error) {
if (error instanceof z.ZodError) {
return new Response(
`Invalid parameters: ${error.errors.map(e => e.message).join(", ")}`,
{ status: 400 }
);
}

console.error("Error fetching history data:", error);
return new Response(
"Internal server error",
{ status: 500 }
);
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ChartPageLoading as default } from "../../../../components/PriceFeed/chart-page";
export { ChartPageLoading as default } from "../../../../components/PriceFeed/Chart/chart-page";
2 changes: 1 addition & 1 deletion apps/insights/src/app/price-feeds/[slug]/(main)/page.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { ChartPage as default } from "../../../../components/PriceFeed/chart-page";
export { ChartPage as default } from "../../../../components/PriceFeed/Chart/chart-page";

export const revalidate = 3600;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Spinner } from "@pythnetwork/component-library/Spinner";

import { Chart } from "./chart";
import styles from "./chart-page.module.scss";
import { getFeed } from "./get-feed";
import { getFeed } from "../get-feed";
import { ChartToolbar } from "./chart-toolbar";

type Props = {
params: Promise<{
Expand All @@ -26,7 +27,7 @@ type ChartPageImplProps =
});

const ChartPageImpl = (props: ChartPageImplProps) => (
<Card title="Chart" className={styles.chartCard}>
<Card title="Chart" className={styles.chartCard} toolbar={<ChartToolbar />}>
<div className={styles.chart}>
{props.isLoading ? (
<div className={styles.spinnerContainer}>
Expand Down
70 changes: 70 additions & 0 deletions apps/insights/src/components/PriceFeed/Chart/chart-toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client'
import { SingleToggleGroup } from "@pythnetwork/component-library/SingleToggleGroup";
import { useLogger } from "@pythnetwork/component-library/useLogger";
import { parseAsStringEnum, useQueryState } from 'nuqs';
import { useCallback } from 'react';

export enum Interval {
Live,
OneHour,
OneDay,
OneWeek,
OneMonth,
}

export const INTERVAL_NAMES = {
[Interval.Live]: "Live",
[Interval.OneHour]: "1H",
[Interval.OneDay]: "1D",
[Interval.OneWeek]: "1W",
[Interval.OneMonth]: "1M",
} as const;

export const toInterval = (name: (typeof INTERVAL_NAMES)[keyof typeof INTERVAL_NAMES]): Interval => {
switch (name) {
case "Live": {
return Interval.Live;
}
case "1H": {
return Interval.OneHour;
}
case "1D": {
return Interval.OneDay;
}
case "1W": {
return Interval.OneWeek;
}
case "1M": {
return Interval.OneMonth;
}
}
};
export const ChartToolbar = () => {
const logger = useLogger();
const [interval, setInterval] = useQueryState(
"interval",
parseAsStringEnum(Object.values(INTERVAL_NAMES)).withDefault("Live"),
);

const handleSelectionChange = useCallback((newValue: Interval) => {
setInterval(INTERVAL_NAMES[newValue]).catch((error: unknown) => {
logger.error("Failed to update interval", error);
});
}, [logger, setInterval]);

return (
<SingleToggleGroup
selectedKey={toInterval(interval)}
// @ts-expect-error - wrong param type
onSelectionChange={handleSelectionChange}
rounded
items={[
{ id: Interval.Live, children: INTERVAL_NAMES[Interval.Live] },
{ id: Interval.OneHour, children: INTERVAL_NAMES[Interval.OneHour] },
{ id: Interval.OneDay, children: INTERVAL_NAMES[Interval.OneDay] },
{ id: Interval.OneWeek, children: INTERVAL_NAMES[Interval.OneWeek] },
{ id: Interval.OneMonth, children: INTERVAL_NAMES[Interval.OneMonth] },
]}
/>
);
};
Loading
Loading