improvement
This commit is contained in:
83
apps/sdk-api/src/controllers/misc.controller.ts
Normal file
83
apps/sdk-api/src/controllers/misc.controller.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
|
import { redis } from '@mixan/redis';
|
||||||
|
|
||||||
|
interface GetFaviconParams {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBuffer(arrayBuffer: ArrayBuffer) {
|
||||||
|
const buffer = Buffer.alloc(arrayBuffer.byteLength);
|
||||||
|
const view = new Uint8Array(arrayBuffer);
|
||||||
|
for (let i = 0; i < buffer.length; ++i) {
|
||||||
|
buffer[i] = view[i]!;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUrlBuffer(url: string) {
|
||||||
|
const arrayBuffer = await fetch(url).then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
return res.arrayBuffer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (arrayBuffer) {
|
||||||
|
return toBuffer(arrayBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFavicon(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Querystring: GetFaviconParams;
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
if (!request.query.url) {
|
||||||
|
return reply.status(404).send('Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBuffer(buffer: Buffer, hostname?: string) {
|
||||||
|
if (hostname) {
|
||||||
|
redis.set(`favicon:${hostname}`, buffer.toString('base64'));
|
||||||
|
}
|
||||||
|
reply.type('image/png');
|
||||||
|
return reply.send(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = decodeURIComponent(request.query.url);
|
||||||
|
const { hostname, origin } = new URL(url);
|
||||||
|
|
||||||
|
const cache = await redis.get(`favicon:${hostname}`);
|
||||||
|
if (cache) {
|
||||||
|
return sendBuffer(Buffer.from(cache, 'base64'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try just get the favicon.ico
|
||||||
|
const buffer = await getUrlBuffer(`${origin}/favicon.ico`);
|
||||||
|
if (buffer) {
|
||||||
|
return sendBuffer(buffer, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that didnt work try parse html
|
||||||
|
const res = await fetch(url).then((res) => res.text());
|
||||||
|
const favicon =
|
||||||
|
res.match(/<link.*?rel="icon".*?href="(.+?)".*?>/) ||
|
||||||
|
res.match(/<link.*?rel="shortcut icon".*?href="(.+?)".*?>/);
|
||||||
|
|
||||||
|
if (favicon?.[1]) {
|
||||||
|
const faviconUrl = favicon[1].startsWith('http')
|
||||||
|
? favicon[1]
|
||||||
|
: `${origin}${favicon[1]}`;
|
||||||
|
|
||||||
|
const buffer = await getUrlBuffer(faviconUrl);
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
return sendBuffer(buffer, hostname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.status(404).send('Not found');
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { redisPub } from '@mixan/redis';
|
|||||||
|
|
||||||
import eventRouter from './routes/event.router';
|
import eventRouter from './routes/event.router';
|
||||||
import liveRouter from './routes/live.router';
|
import liveRouter from './routes/live.router';
|
||||||
|
import miscRouter from './routes/misc.router';
|
||||||
import profileRouter from './routes/profile.router';
|
import profileRouter from './routes/profile.router';
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
@@ -32,6 +33,7 @@ const startServer = async () => {
|
|||||||
fastify.register(eventRouter, { prefix: '/event' });
|
fastify.register(eventRouter, { prefix: '/event' });
|
||||||
fastify.register(profileRouter, { prefix: '/profile' });
|
fastify.register(profileRouter, { prefix: '/profile' });
|
||||||
fastify.register(liveRouter, { prefix: '/live' });
|
fastify.register(liveRouter, { prefix: '/live' });
|
||||||
|
fastify.register(miscRouter, { prefix: '/misc' });
|
||||||
fastify.setErrorHandler((error, request, reply) => {
|
fastify.setErrorHandler((error, request, reply) => {
|
||||||
fastify.log.error(error);
|
fastify.log.error(error);
|
||||||
});
|
});
|
||||||
|
|||||||
14
apps/sdk-api/src/routes/misc.router.ts
Normal file
14
apps/sdk-api/src/routes/misc.router.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import * as controller from '@/controllers/misc.controller';
|
||||||
|
import type { FastifyPluginCallback } from 'fastify';
|
||||||
|
|
||||||
|
const miscRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||||
|
fastify.route({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/favicon',
|
||||||
|
handler: controller.getFavicon,
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default miscRouter;
|
||||||
@@ -22,7 +22,7 @@ export function ChartEmpty() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'aspect-video w-full max-h-[400px] min-h-[200px] flex justify-center items-center'
|
'aspect-video w-full max-h-[300px] min-h-[200px] flex justify-center items-center'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
No data
|
No data
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function ChartLoading({ className }: ChartLoadingProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'aspect-video w-full bg-slate-200 animate-pulse rounded max-h-[400px] min-h-[200px]',
|
'aspect-video w-full bg-slate-200 animate-pulse rounded max-h-[300px] min-h-[200px]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function ReportAreaChart({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
|
'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
|
||||||
editMode && 'border border-border bg-white rounded-md p-4'
|
editMode && 'border border-border bg-white rounded-md p-4'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -49,7 +49,7 @@ export function ReportAreaChart({
|
|||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
<AreaChart
|
<AreaChart
|
||||||
width={width}
|
width={width}
|
||||||
height={Math.min(Math.max(width * 0.5625, 250), 400)}
|
height={Math.min(Math.max(width * 0.5625, 250), 300)}
|
||||||
data={rechartData}
|
data={rechartData}
|
||||||
>
|
>
|
||||||
<Tooltip content={<ReportChartTooltip />} />
|
<Tooltip content={<ReportChartTooltip />} />
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
const { editMode, metric, onClick } = useChartContext();
|
const { editMode, metric, onClick } = useChartContext();
|
||||||
const number = useNumber();
|
const number = useNumber();
|
||||||
const series = useMemo(
|
const series = useMemo(
|
||||||
() => (editMode ? data.series : data.series.slice(0, 20)),
|
() => (editMode ? data.series : data.series.slice(0, 10)),
|
||||||
[data]
|
[data, editMode]
|
||||||
);
|
);
|
||||||
const maxCount = Math.max(...series.map((serie) => serie.metrics[metric]));
|
const maxCount = Math.max(...series.map((serie) => serie.metrics[metric]));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col w-full divide-y text-xs',
|
'flex flex-col w-full text-xs -mx-2',
|
||||||
editMode &&
|
editMode &&
|
||||||
'text-base bg-white border border-border rounded-md p-4 pt-2'
|
'text-base bg-white border border-border rounded-md p-4 pt-2'
|
||||||
)}
|
)}
|
||||||
@@ -45,8 +45,8 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
|||||||
<div
|
<div
|
||||||
key={serie.name}
|
key={serie.name}
|
||||||
className={cn(
|
className={cn(
|
||||||
'py-2 flex flex-1 w-full gap-4 items-center',
|
'py-2 px-2 flex flex-1 w-full gap-4 items-center even:bg-slate-50 [&_[role=progressbar]]:even:bg-white [&_[role=progressbar]]:shadow-sm rounded',
|
||||||
isClickable && 'cursor-pointer hover:bg-gray-100'
|
isClickable && 'cursor-pointer hover:!bg-slate-100'
|
||||||
)}
|
)}
|
||||||
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
|
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function ReportHistogramChart({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
|
'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
|
||||||
editMode && 'border border-border bg-white rounded-md p-4'
|
editMode && 'border border-border bg-white rounded-md p-4'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -53,7 +53,7 @@ export function ReportHistogramChart({
|
|||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
<BarChart
|
<BarChart
|
||||||
width={width}
|
width={width}
|
||||||
height={Math.min(Math.max(width * 0.5625, 250), 400)}
|
height={Math.min(Math.max(width * 0.5625, 250), 300)}
|
||||||
data={rechartData}
|
data={rechartData}
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
<CartesianGrid strokeDasharray="3 3" vertical={false} />
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function ReportLineChart({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
|
'max-sm:-mx-3 aspect-video w-full max-h-[300px] min-h-[200px]',
|
||||||
editMode && 'border border-border bg-white rounded-md p-4'
|
editMode && 'border border-border bg-white rounded-md p-4'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -51,7 +51,7 @@ export function ReportLineChart({
|
|||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
<LineChart
|
<LineChart
|
||||||
width={width}
|
width={width}
|
||||||
height={Math.min(Math.max(width * 0.5625, 250), 400)}
|
height={Math.min(Math.max(width * 0.5625, 250), 300)}
|
||||||
data={rechartData}
|
data={rechartData}
|
||||||
>
|
>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ export function SerieIcon({ name, ...props }: SerieIconProps) {
|
|||||||
|
|
||||||
if (name.includes('http')) {
|
if (name.includes('http')) {
|
||||||
Icon = ((_props) => (
|
Icon = ((_props) => (
|
||||||
<SocialIcon network={networkFor(name)} />
|
<img
|
||||||
|
className="w-4 h-4 object-cover"
|
||||||
|
src={`${String(process.env.NEXT_PUBLIC_API_URL)}/misc/favicon?url=${encodeURIComponent(name)}`}
|
||||||
|
/>
|
||||||
)) as LucideIcon;
|
)) as LucideIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user