180 lines
6.4 KiB
TypeScript
180 lines
6.4 KiB
TypeScript
import { type NextRequest, NextResponse } from 'next/server';
|
|
|
|
import { withAuth, buildItemFilter } from '@/lib/apiHelpers';
|
|
import pool from '@/lib/db';
|
|
|
|
export const GET = withAuth(async (req: NextRequest) => {
|
|
const p = req.nextUrl.searchParams;
|
|
const combinators = p.getAll('combinator');
|
|
const itemsWhitelist = p.getAll('item');
|
|
const itemsBlacklist = p.getAll('exclude');
|
|
const signalType = p.get('signal') ?? 'both';
|
|
const from = p.get('from');
|
|
const to = p.get('to');
|
|
const useRegex = p.get('regex') === 'true';
|
|
const orderBy = p.get('order_by') ?? 'value_asc';
|
|
const limitStr = p.get('limit');
|
|
const limit = limitStr ? parseInt(limitStr, 10) : null;
|
|
|
|
const conditions: string[] = [];
|
|
const values: unknown[] = [];
|
|
const param = { current: 1 };
|
|
|
|
if (combinators.length > 0) {
|
|
conditions.push(`combinator = ANY($${param.current++})`);
|
|
values.push(combinators);
|
|
}
|
|
|
|
const wlClause = buildItemFilter(itemsWhitelist, useRegex, true, values, param);
|
|
if (wlClause) conditions.push(wlClause);
|
|
|
|
const blClause = buildItemFilter(itemsBlacklist, useRegex, false, values, param);
|
|
if (blClause) conditions.push(blClause);
|
|
|
|
if (from) {
|
|
conditions.push(`real_time >= $${param.current++}`);
|
|
values.push(new Date(from));
|
|
}
|
|
if (to) {
|
|
conditions.push(`real_time <= $${param.current++}`);
|
|
values.push(new Date(to));
|
|
}
|
|
|
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
|
|
const valueCol = signalType === 'red' ? 'red' : 'green';
|
|
const selectCols =
|
|
signalType === 'green'
|
|
? 'real_time, game_tick, combinator, item_key, green'
|
|
: signalType === 'red'
|
|
? 'real_time, game_tick, combinator, item_key, red'
|
|
: 'real_time, game_tick, combinator, item_key, green, red';
|
|
|
|
if ((orderBy === 'delta_asc' || orderBy === 'delta_desc') && limit !== null) {
|
|
const baseConditions: string[] = [];
|
|
const baseValues: unknown[] = [];
|
|
const baseParam = { current: 1 };
|
|
|
|
if (combinators.length > 0) {
|
|
baseConditions.push(`combinator = ANY($${baseParam.current++})`);
|
|
baseValues.push(combinators);
|
|
}
|
|
|
|
const wlBase = buildItemFilter(itemsWhitelist, useRegex, true, baseValues, baseParam);
|
|
if (wlBase) baseConditions.push(wlBase);
|
|
|
|
const blBase = buildItemFilter(itemsBlacklist, useRegex, false, baseValues, baseParam);
|
|
if (blBase) baseConditions.push(blBase);
|
|
|
|
const baseWhere = baseConditions.length > 0 ? `WHERE ${baseConditions.join(' AND ')}` : '';
|
|
const baseWhereAnd = baseConditions.length > 0 ? `AND ${baseConditions.join(' AND ')}` : '';
|
|
|
|
const deltaQuery = `
|
|
WITH snap_now AS (
|
|
SELECT DISTINCT ON (combinator, item_key)
|
|
combinator, item_key, ${valueCol} AS val, real_time AS ref_time
|
|
FROM signals
|
|
${baseWhere}
|
|
ORDER BY combinator, item_key, real_time DESC
|
|
),
|
|
snap_then AS (
|
|
SELECT DISTINCT ON (s.combinator, s.item_key)
|
|
s.combinator, s.item_key, s.${valueCol} AS val
|
|
FROM signals s
|
|
JOIN snap_now n USING (combinator, item_key)
|
|
WHERE s.real_time <= n.ref_time - INTERVAL '10 minutes'
|
|
${baseWhereAnd}
|
|
ORDER BY s.combinator, s.item_key, s.real_time DESC
|
|
),
|
|
deltas AS (
|
|
SELECT
|
|
snap_now.combinator,
|
|
snap_now.item_key,
|
|
(snap_now.val - COALESCE(snap_then.val, snap_now.val)) AS delta
|
|
FROM snap_now
|
|
LEFT JOIN snap_then USING (combinator, item_key)
|
|
)
|
|
SELECT combinator, item_key, delta
|
|
FROM deltas
|
|
ORDER BY delta ${orderBy === 'delta_asc' ? 'ASC' : 'DESC'}
|
|
LIMIT $${baseParam.current}
|
|
`;
|
|
|
|
const deltaResult = await pool.query<{
|
|
combinator: string;
|
|
item_key: string;
|
|
delta: number;
|
|
}>(deltaQuery, [...baseValues, limit]);
|
|
|
|
const top = deltaResult.rows;
|
|
if (top.length === 0) return NextResponse.json([]);
|
|
|
|
const seriesConditions = top.map(
|
|
(_, idx) =>
|
|
`(combinator = $${param.current + idx * 2} AND item_key = $${param.current + idx * 2 + 1})`,
|
|
);
|
|
const fullWhere = `${where ? where + ' AND' : 'WHERE'} (${seriesConditions.join(' OR ')})`;
|
|
const orderCase = top
|
|
.map(
|
|
(_, idx) =>
|
|
`WHEN combinator = $${param.current + idx * 2} AND item_key = $${param.current + idx * 2 + 1} THEN ${idx}`,
|
|
)
|
|
.join(' ');
|
|
const result = await pool.query(
|
|
`SELECT ${selectCols} FROM signals ${fullWhere} ORDER BY CASE ${orderCase} ELSE ${top.length} END, real_time ASC`,
|
|
[...values, ...top.flatMap((r) => [r.combinator, r.item_key])],
|
|
);
|
|
return NextResponse.json(result.rows);
|
|
}
|
|
|
|
if (
|
|
(orderBy === 'value_asc' || orderBy === 'value_desc' || orderBy === 'abs_desc') &&
|
|
limit !== null
|
|
) {
|
|
const latestVals = await pool.query<{
|
|
combinator: string;
|
|
item_key: string;
|
|
val: number;
|
|
}>(
|
|
`SELECT DISTINCT ON (combinator, item_key)
|
|
combinator, item_key, ${valueCol} AS val
|
|
FROM signals ${where}
|
|
ORDER BY combinator, item_key, real_time DESC`,
|
|
values,
|
|
);
|
|
|
|
let sorted = latestVals.rows;
|
|
if (orderBy === 'value_asc') sorted = [...sorted].sort((a, b) => a.val - b.val);
|
|
if (orderBy === 'value_desc') sorted = [...sorted].sort((a, b) => b.val - a.val);
|
|
if (orderBy === 'abs_desc')
|
|
sorted = [...sorted].sort((a, b) => Math.abs(b.val) - Math.abs(a.val));
|
|
|
|
const top = sorted.slice(0, limit);
|
|
if (top.length === 0) return NextResponse.json([]);
|
|
|
|
const seriesConditions = top.map(
|
|
(_, idx) =>
|
|
`(combinator = $${param.current + idx * 2} AND item_key = $${param.current + idx * 2 + 1})`,
|
|
);
|
|
const fullWhere = `${where ? where + ' AND' : 'WHERE'} (${seriesConditions.join(' OR ')})`;
|
|
const orderCase = top
|
|
.map(
|
|
(_, idx) =>
|
|
`WHEN combinator = $${param.current + idx * 2} AND item_key = $${param.current + idx * 2 + 1} THEN ${idx}`,
|
|
)
|
|
.join(' ');
|
|
const result = await pool.query(
|
|
`SELECT ${selectCols} FROM signals ${fullWhere} ORDER BY CASE ${orderCase} ELSE ${top.length} END, real_time ASC`,
|
|
[...values, ...top.flatMap((r) => [r.combinator, r.item_key])],
|
|
);
|
|
return NextResponse.json(result.rows);
|
|
}
|
|
|
|
const rowLimit = orderBy === 'time' && limit ? `LIMIT ${limit}` : '';
|
|
const result = await pool.query(
|
|
`SELECT ${selectCols} FROM signals ${where} ORDER BY real_time ASC ${rowLimit}`,
|
|
values,
|
|
);
|
|
return NextResponse.json(result.rows);
|
|
});
|