Commit a1f00dc6 authored by tom's avatar tom

stats api

parent 64300957
......@@ -96,6 +96,7 @@ const config = Object.freeze({
},
statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '',
},
homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
......
......@@ -2,23 +2,24 @@ import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config';
import { RESOURCES } from './resources';
import type { ApiResource } from './resources';
export default function buildUrl(
resource: keyof typeof RESOURCES,
resource: ApiResource,
pathParams?: Record<string, string>,
queryParams?: Record<string, string>,
queryParams?: Record<string, string | undefined>,
) {
// FIXME was not able to figure out how to send CORS with credentials from localhost
// so for local development we use nextjs api as proxy server (only!)
const base = appConfig.host === 'localhost' ? appConfig.baseUrl : appConfig.api.endpoint;
const baseUrl = appConfig.host === 'localhost' ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = appConfig.host === 'localhost' ?
'/proxy' + appConfig.api.basePath + RESOURCES[resource].path :
appConfig.api.basePath + RESOURCES[resource].path;
const url = new URL(compile(path)(pathParams), base);
'/proxy' + basePath + resource.path :
basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(key, value);
value && url.searchParams.append(key, value);
});
return url.toString();
......
import appConfig from 'configs/app/config';
export interface ApiResource {
path: string;
endpoint?: string;
basePath?: string;
}
export const RESOURCES = {
// account
user_info: {
......@@ -25,6 +33,18 @@ export const RESOURCES = {
path: '/api/account/v1/user/api_keys/:id?',
},
// STATS
stats_counters: {
path: '/api/v1/counters',
endpoint: appConfig.statsApi.endpoint,
basePath: appConfig.statsApi.basePath,
},
stats_charts: {
path: '/api/v1/charts/line',
endpoint: appConfig.statsApi.endpoint,
basePath: appConfig.statsApi.basePath,
},
// DEPRECATED
old_api: {
path: '/api',
......
import React from 'react';
import appConfig from 'configs/app/config';
import type { Params as FetchParams } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl';
import type { RESOURCES, ResourceError } from './resources';
import { RESOURCES } from './resources';
import type { ResourceError, ApiResource } from './resources';
export interface Params {
pathParams?: Record<string, string>;
queryParams?: Record<string, string>;
queryParams?: Record<string, string | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method'>;
}
......@@ -16,10 +18,17 @@ export default function useApiFetch() {
const fetch = useFetch();
return React.useCallback(<R extends keyof typeof RESOURCES, SuccessType = unknown, ErrorType = ResourceError>(
resource: R,
resourceName: R,
{ pathParams, queryParams, fetchParams }: Params = {},
) => {
const resource: ApiResource = RESOURCES[resourceName];
const url = buildUrl(resource, pathParams, queryParams);
return fetch<SuccessType, ErrorType>(url, { credentials: 'include', ...fetchParams });
return fetch<SuccessType, ErrorType>(url, {
credentials: 'include',
...(resource.endpoint && appConfig.host === 'localhost' ? { headers: {
'x-endpoint': resource.endpoint,
} } : {}),
...fetchParams,
});
}, [ fetch ]);
}
......@@ -2,6 +2,7 @@ import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import type { UserInfo, CustomAbis, PublicTags, AddressTags, TransactionTags, ApiKeys, WatchlistAddress } from 'types/api/account';
import type { Stats, Charts } from 'types/api/stats';
import type { CsrfData } from 'types/client/account';
import type { RESOURCES, ResourceError } from './resources';
......@@ -18,7 +19,9 @@ export default function useApiQuery<R extends keyof typeof RESOURCES>(
) {
const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError, ResourcePayload<R>>([ resource ], async() => {
return useQuery<unknown, ResourceError, ResourcePayload<R>>(
pathParams || queryParams ? [ resource, { ...pathParams, ...queryParams } ] : [ resource ],
async() => {
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams });
}, queryOptions);
}
......@@ -32,4 +35,6 @@ export type ResourcePayload<Q extends keyof typeof RESOURCES> =
Q extends 'private_tags_tx' ? TransactionTags :
Q extends 'api_keys' ? ApiKeys :
Q extends 'watchlist' ? Array<WatchlistAddress> :
Q extends 'stats_counters' ? Stats :
Q extends 'stats_charts' ? Charts :
never;
......@@ -9,6 +9,7 @@ import { resourceKey, RESOURCES } from 'lib/api/resources';
export interface Params {
method?: RequestInit['method'];
headers?: RequestInit['headers'];
body?: Record<string, unknown>;
credentials?: RequestCredentials;
}
......
......@@ -10,7 +10,7 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
return;
}
const response = await fetchFactory(_req)(
const response = await fetchFactory(_req, _req.headers['x-endpoint']?.toString())(
_req.url.replace(/^\/proxy/, ''),
_pickBy(_pick(_req, [ 'body', 'method' ]), Boolean),
);
......
import type { NextApiRequest } from 'next';
import appConfig from 'configs/app/config';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const { name, from, to } = req.query;
return `/v1/charts/line?name=${ name }${ from ? `&from=${ from }&to=${ to }` : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ], appConfig.statsApi.endpoint);
export default requestHandler;
import appConfig from 'configs/app/config';
import handler from 'lib/api/handler';
const getUrl = () => '/v1/counters';
const requestHandler = handler(getUrl, [ 'GET' ], appConfig.statsApi.endpoint);
export default requestHandler;
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { Charts } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries';
import type { StatsIntervalIds } from 'types/client/stats';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
import ChartWidget from '../shared/chart/ChartWidget';
import { STATS_INTERVALS } from './constants';
......@@ -22,19 +19,18 @@ function formatDate(date: Date) {
}
const ChartWidgetContainer = ({ id, title, description, interval }: Props) => {
const fetch = useFetch();
const selectedInterval = STATS_INTERVALS[interval];
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const url = `/node-api/stats/charts?name=${ id }${ startDate ? `&from=${ startDate }&to=${ endDate }` : '' }`;
const { data, isLoading } = useQuery<unknown, unknown, Charts>(
[ QueryKeys.charts, id, startDate ],
async() => await fetch(url),
);
const { data, isLoading } = useApiQuery('stats_charts', {
queryParams: {
name: id,
from: startDate,
to: endDate,
},
});
const items = data?.chart
.map((item) => {
......
import { Grid } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type { Stats } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import useApiQuery from 'lib/api/useApiQuery';
import NumberWidget from './NumberWidget';
import NumberWidgetSkeleton from './NumberWidgetSkeleton';
......@@ -13,12 +9,7 @@ import NumberWidgetSkeleton from './NumberWidgetSkeleton';
const skeletonsCount = 8;
const NumberWidgetsList = () => {
const fetch = useFetch();
const { data, isLoading } = useQuery<unknown, unknown, Stats>(
[ QueryKeys.stats ],
async() => await fetch(`/node-api/stats/counters`),
);
const { data, isLoading } = useApiQuery('stats_counters');
return (
<Grid
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment