furhter bug fixes and updates
This commit is contained in:
@@ -1,9 +1,21 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getMapPopulationSeries } from '@/lib/queries';
|
||||
import { formatDbDateTime } from '@/lib/timezone';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const limitParam = req.nextUrl.searchParams.get('limit');
|
||||
const beforeParam = req.nextUrl.searchParams.get('before'); // ISO string — page further back for pan-to-load-more
|
||||
const limit = Math.min(Math.max(Number(limitParam) || 40, 1), 200);
|
||||
const points = await getMapPopulationSeries(limit);
|
||||
|
||||
let before: string | undefined;
|
||||
if (beforeParam) {
|
||||
const d = new Date(beforeParam);
|
||||
if (Number.isNaN(d.getTime())) {
|
||||
return NextResponse.json({ error: 'invalid before param' }, { status: 400 });
|
||||
}
|
||||
before = formatDbDateTime(d);
|
||||
}
|
||||
|
||||
const points = await getMapPopulationSeries(limit, before);
|
||||
return NextResponse.json({ points });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { query } from '@/lib/db';
|
||||
import { getSteamSummariesBatch, DEFAULT_AVATAR_URL } from '@/lib/steamApi';
|
||||
import { steam2ToSteam64 } from '@/lib/steam';
|
||||
import { parseDbDateTime } from '@/lib/timezone';
|
||||
|
||||
interface SessionRow {
|
||||
steamid: string;
|
||||
current_name: string;
|
||||
session_start_dt: string;
|
||||
session_end_dt: string | null;
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const sp = req.nextUrl.searchParams;
|
||||
const date = sp.get('date');
|
||||
const startTime = sp.get('startTime');
|
||||
const endTime = sp.get('endTime');
|
||||
|
||||
if (!date || !startTime || !endTime) {
|
||||
return NextResponse.json({ error: 'date, startTime, endTime are required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const windowStartStr = `${date} ${startTime}:00`;
|
||||
const windowEndStr = `${date} ${endTime}:00`;
|
||||
const windowStartMs = parseDbDateTime(windowStartStr).getTime();
|
||||
const windowEndMs = parseDbDateTime(windowEndStr).getTime();
|
||||
const windowMinutes = Math.round((windowEndMs - windowStartMs) / 60000);
|
||||
|
||||
const rows = await query<SessionRow>(
|
||||
`SELECT s.steamid, COALESCE(p.current_name, s.player_name) AS current_name,
|
||||
s.session_start_dt, s.session_end_dt
|
||||
FROM playtime_display_sessions s
|
||||
LEFT JOIN playtime_display_players p ON p.steamid = s.steamid
|
||||
WHERE s.session_start_dt < ?
|
||||
AND (s.session_end_dt IS NULL OR s.session_end_dt > ?)`,
|
||||
[windowEndStr, windowStartStr],
|
||||
);
|
||||
|
||||
// Clip each session row to the window and accumulate per-player segments —
|
||||
// a player can have more than one segment if they disconnected and
|
||||
// reconnected within the window.
|
||||
interface Segment {
|
||||
startMin: number;
|
||||
endMin: number;
|
||||
}
|
||||
const byPlayer = new Map<string, { name: string; segments: Segment[]; totalMinutes: number }>();
|
||||
|
||||
for (const r of rows) {
|
||||
const sStart = parseDbDateTime(r.session_start_dt).getTime();
|
||||
const sEnd = r.session_end_dt ? parseDbDateTime(r.session_end_dt).getTime() : Date.now();
|
||||
const clippedStart = Math.max(sStart, windowStartMs);
|
||||
const clippedEnd = Math.min(sEnd, windowEndMs);
|
||||
if (clippedEnd <= clippedStart) continue;
|
||||
|
||||
const startMin = Math.round((clippedStart - windowStartMs) / 60000);
|
||||
const endMin = Math.round((clippedEnd - windowStartMs) / 60000);
|
||||
|
||||
if (!byPlayer.has(r.steamid)) {
|
||||
byPlayer.set(r.steamid, { name: r.current_name, segments: [], totalMinutes: 0 });
|
||||
}
|
||||
const entry = byPlayer.get(r.steamid)!;
|
||||
entry.segments.push({ startMin, endMin });
|
||||
entry.totalMinutes += endMin - startMin;
|
||||
}
|
||||
|
||||
const steam64ByPlayer = new Map<string, string>();
|
||||
for (const steamid of byPlayer.keys()) {
|
||||
const id64 = steam2ToSteam64(steamid);
|
||||
if (id64) steam64ByPlayer.set(steamid, id64);
|
||||
}
|
||||
const avatars = await getSteamSummariesBatch([...steam64ByPlayer.values()]);
|
||||
|
||||
const players = [...byPlayer.entries()]
|
||||
.map(([steamid, data]) => {
|
||||
const id64 = steam64ByPlayer.get(steamid);
|
||||
const avatarUrl = (id64 && avatars.get(id64)?.avatarUrl) || DEFAULT_AVATAR_URL;
|
||||
return {
|
||||
steamid,
|
||||
name: data.name,
|
||||
avatarUrl,
|
||||
totalMinutes: data.totalMinutes,
|
||||
segments: data.segments,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.totalMinutes - a.totalMinutes);
|
||||
|
||||
return NextResponse.json({ windowMinutes, players });
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { query } from '@/lib/db';
|
||||
|
||||
function pad(n: number) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
const MAX_DATES = 120; // safety cap — one query per matching date
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const sp = req.nextUrl.searchParams;
|
||||
const daysParam = sp.get('days'); // comma list, JS convention: 0=Sun..6=Sat
|
||||
const startTime = sp.get('startTime'); // "06:00"
|
||||
const endTime = sp.get('endTime'); // "15:00"
|
||||
const startDate = sp.get('start'); // "2026-07-01"
|
||||
const endDate = sp.get('end'); // "2026-08-22"
|
||||
|
||||
if (!daysParam || !startTime || !endTime || !startDate || !endDate) {
|
||||
return NextResponse.json({ error: 'days, startTime, endTime, start, end are all required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const days = daysParam.split(',').map(Number).filter((d) => d >= 0 && d <= 6);
|
||||
if (days.length === 0) {
|
||||
return NextResponse.json({ error: 'no valid days selected' }, { status: 400 });
|
||||
}
|
||||
if (!/^\d{2}:\d{2}$/.test(startTime) || !/^\d{2}:\d{2}$/.test(endTime) || startTime >= endTime) {
|
||||
return NextResponse.json({ error: 'startTime must be before endTime, both as HH:MM' }, { status: 400 });
|
||||
}
|
||||
|
||||
const [startY, startM, startD] = startDate.split('-').map(Number);
|
||||
const [endY, endM, endD] = endDate.split('-').map(Number);
|
||||
if (!startY || !startM || !startD || !endY || !endM || !endD) {
|
||||
return NextResponse.json({ error: 'invalid start/end date' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Calendar-date arithmetic anchored to UTC purely as a way to enumerate
|
||||
// plain Y-M-D dates and their day-of-week unambiguously — these Date
|
||||
// objects never represent a real instant, just a calendar day.
|
||||
const cursor = new Date(Date.UTC(startY, startM - 1, startD));
|
||||
const endCursor = new Date(Date.UTC(endY, endM - 1, endD));
|
||||
if (cursor > endCursor) {
|
||||
return NextResponse.json({ error: 'start date must be before end date' }, { status: 400 });
|
||||
}
|
||||
|
||||
const matchingDates: string[] = [];
|
||||
while (cursor <= endCursor && matchingDates.length < MAX_DATES) {
|
||||
if (days.includes(cursor.getUTCDay())) {
|
||||
matchingDates.push(`${cursor.getUTCFullYear()}-${pad(cursor.getUTCMonth() + 1)}-${pad(cursor.getUTCDate())}`);
|
||||
}
|
||||
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
||||
}
|
||||
const truncated = matchingDates.length >= MAX_DATES && cursor <= endCursor;
|
||||
|
||||
// Each window boundary is built directly as a "YYYY-MM-DD HH:MM:SS"
|
||||
// string — already in the exact format the DB stores, so no Date
|
||||
// round-trip (and no timezone ambiguity) is needed for these queries.
|
||||
const points = await Promise.all(
|
||||
matchingDates.map(async (dateStr) => {
|
||||
const windowStart = `${dateStr} ${startTime}:00`;
|
||||
const windowEnd = `${dateStr} ${endTime}:00`;
|
||||
const rows = await query<{ steamid: string }>(
|
||||
`SELECT DISTINCT s.steamid
|
||||
FROM playtime_display_sessions s
|
||||
WHERE s.session_start_dt < ?
|
||||
AND (s.session_end_dt IS NULL OR s.session_end_dt > ?)`,
|
||||
[windowEnd, windowStart],
|
||||
);
|
||||
return { date: dateStr, count: rows.length };
|
||||
}),
|
||||
);
|
||||
|
||||
return NextResponse.json({ points, truncated });
|
||||
}
|
||||
Reference in New Issue
Block a user