mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 08:02:14 +03:00
Wires the previously-dead appendSessionRequest() DB function to a new POST /api/tools/traffic-inspector/sessions/[id]/requests route. appendSessionRequest now returns the inserted seq so callers can confirm order. InspectorSessionRequestAppendSchema (1 MB cap) guards the endpoint. Integration test covers seq increments 1-2-3, requestCount sync, 400/404 paths, and stack-trace-free error responses.
162 lines
4.9 KiB
TypeScript
162 lines
4.9 KiB
TypeScript
/**
|
|
* Database module: InspectorSessions
|
|
* CRUD + snapshot for inspector_sessions and inspector_session_requests tables.
|
|
*/
|
|
|
|
import { randomUUID } from "crypto";
|
|
import { getDbInstance } from "./core";
|
|
import type { InspectorSessionRow } from "./_rowTypes";
|
|
import { InterceptedRequestSchema } from "../../mitm/inspector/types";
|
|
import type { InterceptedRequest } from "../../mitm/inspector/types";
|
|
|
|
interface InspectorSessionDbRow {
|
|
id: string;
|
|
name: string | null;
|
|
started_at: string;
|
|
ended_at: string | null;
|
|
request_count: number;
|
|
profile: string | null;
|
|
}
|
|
|
|
interface InspectorSessionRequestDbRow {
|
|
session_id: string;
|
|
seq: number;
|
|
payload: string;
|
|
}
|
|
|
|
function mapSessionRow(row: InspectorSessionDbRow): InspectorSessionRow {
|
|
return {
|
|
id: row.id,
|
|
name: row.name,
|
|
started_at: row.started_at,
|
|
ended_at: row.ended_at,
|
|
request_count: row.request_count,
|
|
profile: row.profile as "llm" | "custom" | "all" | null,
|
|
};
|
|
}
|
|
|
|
export function createSession(opts?: {
|
|
name?: string;
|
|
profile?: "llm" | "custom" | "all";
|
|
}): { id: string; started_at: string } {
|
|
const db = getDbInstance();
|
|
const id = randomUUID();
|
|
const started_at = new Date().toISOString();
|
|
|
|
db.prepare(
|
|
`INSERT INTO inspector_sessions (id, name, started_at, profile) VALUES (?, ?, ?, ?)`
|
|
).run(id, opts?.name ?? null, started_at, opts?.profile ?? null);
|
|
|
|
return { id, started_at };
|
|
}
|
|
|
|
export function stopSession(id: string): void {
|
|
const db = getDbInstance();
|
|
const ended_at = new Date().toISOString();
|
|
db.prepare("UPDATE inspector_sessions SET ended_at = ? WHERE id = ?").run(ended_at, id);
|
|
}
|
|
|
|
export function renameSession(id: string, name: string): void {
|
|
const db = getDbInstance();
|
|
db.prepare("UPDATE inspector_sessions SET name = ? WHERE id = ?").run(name, id);
|
|
}
|
|
|
|
export function listSessions(): InspectorSessionRow[] {
|
|
const db = getDbInstance();
|
|
const rows = db
|
|
.prepare("SELECT * FROM inspector_sessions ORDER BY started_at DESC")
|
|
.all() as InspectorSessionDbRow[];
|
|
return rows.map(mapSessionRow);
|
|
}
|
|
|
|
export function getSession(id: string): InspectorSessionRow | null {
|
|
const db = getDbInstance();
|
|
const row = db
|
|
.prepare("SELECT * FROM inspector_sessions WHERE id = ?")
|
|
.get(id) as InspectorSessionDbRow | undefined;
|
|
return row ? mapSessionRow(row) : null;
|
|
}
|
|
|
|
export function appendSessionRequest(sessionId: string, payload: string): number {
|
|
const db = getDbInstance();
|
|
let insertedSeq = 0;
|
|
|
|
const runTransaction = db.transaction(() => {
|
|
// Get next seq atomically within transaction
|
|
const seqRow = db
|
|
.prepare(
|
|
"SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq FROM inspector_session_requests WHERE session_id = ?"
|
|
)
|
|
.get(sessionId) as { next_seq: number };
|
|
|
|
const nextSeq = seqRow.next_seq;
|
|
|
|
db.prepare(
|
|
`INSERT INTO inspector_session_requests (session_id, seq, payload) VALUES (?, ?, ?)`
|
|
).run(sessionId, nextSeq, payload);
|
|
|
|
db.prepare(
|
|
"UPDATE inspector_sessions SET request_count = request_count + 1 WHERE id = ?"
|
|
).run(sessionId);
|
|
|
|
insertedSeq = nextSeq;
|
|
});
|
|
|
|
runTransaction();
|
|
return insertedSeq;
|
|
}
|
|
|
|
export function getSessionRequests(sessionId: string): Array<{ seq: number; payload: string }> {
|
|
const db = getDbInstance();
|
|
const rows = db
|
|
.prepare(
|
|
"SELECT seq, payload FROM inspector_session_requests WHERE session_id = ? ORDER BY seq ASC"
|
|
)
|
|
.all(sessionId) as InspectorSessionRequestDbRow[];
|
|
return rows.map((r) => ({ seq: r.seq, payload: r.payload }));
|
|
}
|
|
|
|
export function deleteSession(id: string): void {
|
|
const db = getDbInstance();
|
|
// Cascade via FK ON DELETE CASCADE for inspector_session_requests
|
|
db.prepare("DELETE FROM inspector_sessions WHERE id = ?").run(id);
|
|
}
|
|
|
|
/**
|
|
* Return a parsed + validated snapshot of all requests for the given session,
|
|
* sorted by ascending seq.
|
|
*
|
|
* Returns null when the session does not exist.
|
|
* Rows whose payload fails InterceptedRequestSchema validation are silently
|
|
* skipped (defensive — protects callers from corrupt/partial rows).
|
|
*
|
|
* Satisfies master-plan §3.8 (F2 spec) named-export contract.
|
|
*/
|
|
export function snapshotSession(sessionId: string): InterceptedRequest[] | null {
|
|
// 1. Verify session exists.
|
|
const session = getSession(sessionId);
|
|
if (session === null) return null;
|
|
|
|
// 2. Retrieve raw rows (already ordered by seq ASC).
|
|
const rawRows = getSessionRequests(sessionId);
|
|
|
|
// 3. Parse each payload JSON, validate via Zod schema, skip bad rows.
|
|
const results: InterceptedRequest[] = [];
|
|
for (const row of rawRows) {
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(row.payload);
|
|
} catch {
|
|
// Corrupt JSON — skip.
|
|
continue;
|
|
}
|
|
const result = InterceptedRequestSchema.safeParse(parsed);
|
|
if (result.success) {
|
|
results.push(result.data as InterceptedRequest);
|
|
}
|
|
// Invalid rows are silently skipped per defensive contract.
|
|
}
|
|
|
|
return results;
|
|
}
|