Commit a1f00dc6 authored by tom's avatar tom

stats api

parent 64300957
...@@ -96,6 +96,7 @@ const config = Object.freeze({ ...@@ -96,6 +96,7 @@ const config = Object.freeze({
}, },
statsApi: { statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST), endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '',
}, },
homepage: { homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [], charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
......
...@@ -2,23 +2,24 @@ import { compile } from 'path-to-regexp'; ...@@ -2,23 +2,24 @@ import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import { RESOURCES } from './resources'; import type { ApiResource } from './resources';
export default function buildUrl( export default function buildUrl(
resource: keyof typeof RESOURCES, resource: ApiResource,
pathParams?: Record<string, string>, 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 // 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!) // 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' ? const path = appConfig.host === 'localhost' ?
'/proxy' + appConfig.api.basePath + RESOURCES[resource].path : '/proxy' + basePath + resource.path :
appConfig.api.basePath + RESOURCES[resource].path; basePath + resource.path;
const url = new URL(compile(path)(pathParams), base); const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(key, value); value && url.searchParams.append(key, value);
}); });
return url.toString(); return url.toString();
......
import appConfig from 'configs/app/config';
export interface ApiResource {
path: string;
endpoint?: string;
basePath?: string;
}
export const RESOURCES = { export const RESOURCES = {
// account // account
user_info: { user_info: {
...@@ -25,6 +33,18 @@ export const RESOURCES = { ...@@ -25,6 +33,18 @@ export const RESOURCES = {
path: '/api/account/v1/user/api_keys/:id?', 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 // DEPRECATED
old_api: { old_api: {
path: '/api', path: '/api',
......
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config';
import type { Params as FetchParams } from 'lib/hooks/useFetch'; import type { Params as FetchParams } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl'; import buildUrl from './buildUrl';
import type { RESOURCES, ResourceError } from './resources'; import { RESOURCES } from './resources';
import type { ResourceError, ApiResource } from './resources';
export interface Params { export interface Params {
pathParams?: Record<string, string>; pathParams?: Record<string, string>;
queryParams?: Record<string, string>; queryParams?: Record<string, string | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method'>; fetchParams?: Pick<FetchParams, 'body' | 'method'>;
} }
...@@ -16,10 +18,17 @@ export default function useApiFetch() { ...@@ -16,10 +18,17 @@ export default function useApiFetch() {
const fetch = useFetch(); const fetch = useFetch();
return React.useCallback(<R extends keyof typeof RESOURCES, SuccessType = unknown, ErrorType = ResourceError>( return React.useCallback(<R extends keyof typeof RESOURCES, SuccessType = unknown, ErrorType = ResourceError>(
resource: R, resourceName: R,
{ pathParams, queryParams, fetchParams }: Params = {}, { pathParams, queryParams, fetchParams }: Params = {},
) => { ) => {
const resource: ApiResource = RESOURCES[resourceName];
const url = buildUrl(resource, pathParams, queryParams); 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 ]); }, [ fetch ]);
} }
...@@ -2,6 +2,7 @@ import type { UseQueryOptions } from '@tanstack/react-query'; ...@@ -2,6 +2,7 @@ import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } 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 { 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 { CsrfData } from 'types/client/account';
import type { RESOURCES, ResourceError } from './resources'; import type { RESOURCES, ResourceError } from './resources';
...@@ -18,9 +19,11 @@ export default function useApiQuery<R extends keyof typeof RESOURCES>( ...@@ -18,9 +19,11 @@ export default function useApiQuery<R extends keyof typeof RESOURCES>(
) { ) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
return useQuery<unknown, ResourceError, ResourcePayload<R>>([ resource ], async() => { return useQuery<unknown, ResourceError, ResourcePayload<R>>(
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams }); pathParams || queryParams ? [ resource, { ...pathParams, ...queryParams } ] : [ resource ],
}, queryOptions); async() => {
return apiFetch<R, ResourcePayload<R>, ResourceError>(resource, { pathParams, queryParams, fetchParams });
}, queryOptions);
} }
export type ResourcePayload<Q extends keyof typeof RESOURCES> = export type ResourcePayload<Q extends keyof typeof RESOURCES> =
...@@ -32,4 +35,6 @@ export type ResourcePayload<Q extends keyof typeof RESOURCES> = ...@@ -32,4 +35,6 @@ export type ResourcePayload<Q extends keyof typeof RESOURCES> =
Q extends 'private_tags_tx' ? TransactionTags : Q extends 'private_tags_tx' ? TransactionTags :
Q extends 'api_keys' ? ApiKeys : Q extends 'api_keys' ? ApiKeys :
Q extends 'watchlist' ? Array<WatchlistAddress> : Q extends 'watchlist' ? Array<WatchlistAddress> :
never; Q extends 'stats_counters' ? Stats :
Q extends 'stats_charts' ? Charts :
never;
...@@ -9,6 +9,7 @@ import { resourceKey, RESOURCES } from 'lib/api/resources'; ...@@ -9,6 +9,7 @@ import { resourceKey, RESOURCES } from 'lib/api/resources';
export interface Params { export interface Params {
method?: RequestInit['method']; method?: RequestInit['method'];
headers?: RequestInit['headers'];
body?: Record<string, unknown>; body?: Record<string, unknown>;
credentials?: RequestCredentials; credentials?: RequestCredentials;
} }
......
...@@ -10,7 +10,7 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => { ...@@ -10,7 +10,7 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
return; return;
} }
const response = await fetchFactory(_req)( const response = await fetchFactory(_req, _req.headers['x-endpoint']?.toString())(
_req.url.replace(/^\/proxy/, ''), _req.url.replace(/^\/proxy/, ''),
_pickBy(_pick(_req, [ 'body', 'method' ]), Boolean), _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 React from 'react';
import type { Charts } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries';
import type { StatsIntervalIds } from 'types/client/stats'; 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 ChartWidget from '../shared/chart/ChartWidget';
import { STATS_INTERVALS } from './constants'; import { STATS_INTERVALS } from './constants';
...@@ -22,19 +19,18 @@ function formatDate(date: Date) { ...@@ -22,19 +19,18 @@ function formatDate(date: Date) {
} }
const ChartWidgetContainer = ({ id, title, description, interval }: Props) => { const ChartWidgetContainer = ({ id, title, description, interval }: Props) => {
const fetch = useFetch();
const selectedInterval = STATS_INTERVALS[interval]; const selectedInterval = STATS_INTERVALS[interval];
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined; const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : 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 } = useApiQuery('stats_charts', {
queryParams: {
const { data, isLoading } = useQuery<unknown, unknown, Charts>( name: id,
[ QueryKeys.charts, id, startDate ], from: startDate,
async() => await fetch(url), to: endDate,
); },
});
const items = data?.chart const items = data?.chart
.map((item) => { .map((item) => {
......
import { Grid } from '@chakra-ui/react'; import { Grid } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { Stats } from 'types/api/stats'; import useApiQuery from 'lib/api/useApiQuery';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch';
import NumberWidget from './NumberWidget'; import NumberWidget from './NumberWidget';
import NumberWidgetSkeleton from './NumberWidgetSkeleton'; import NumberWidgetSkeleton from './NumberWidgetSkeleton';
...@@ -13,12 +9,7 @@ import NumberWidgetSkeleton from './NumberWidgetSkeleton'; ...@@ -13,12 +9,7 @@ import NumberWidgetSkeleton from './NumberWidgetSkeleton';
const skeletonsCount = 8; const skeletonsCount = 8;
const NumberWidgetsList = () => { const NumberWidgetsList = () => {
const fetch = useFetch(); const { data, isLoading } = useApiQuery('stats_counters');
const { data, isLoading } = useQuery<unknown, unknown, Stats>(
[ QueryKeys.stats ],
async() => await fetch(`/node-api/stats/counters`),
);
return ( return (
<Grid <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