Add content negotiation for raw JSONL logs #16

Merged
probablycorey merged 1 commits from probablycorey/raw-jsonl-endpoint into main 2026-03-10 18:02:08 +00:00
2 changed files with 31 additions and 9 deletions

View File

@ -59,17 +59,22 @@ export function listLogFiles(): string[] {
}
export function readLogFile(filename: string): StoredLogEvent[] {
// Reject path traversal attempts (e.g. "../../../etc/passwd")
if (filename !== basename(filename)) return []
const raw = readLogFileRaw(filename)
if (!raw) return []
return raw
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as StoredLogEvent)
}
export function readLogFileRaw(filename: string): string | undefined {
if (filename !== basename(filename)) throw new Error("Invalid filename")
const path = `${logsDir}/${filename}`
try {
return readFileSync(path, "utf-8")
.trim()
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as StoredLogEvent)
} catch {
return []
return
}
}

View File

@ -1,7 +1,7 @@
import { serve } from "bun"
import { handleGiteaWebhook } from "../bridge"
import { startDiscord } from "../discord"
import { log } from "../log"
import { listLogFiles, log, readLogFileRaw } from "../log"
import { LogsPage } from "./logs"
await startDiscord()
@ -32,7 +32,24 @@ const server = serve({
},
},
"/logs": {
GET: (req) => LogsPage(req),
GET: (req) => {
const accept = req.headers.get("Accept") || ""
const wantsRaw = !accept.includes("text/html")
if (!wantsRaw) return LogsPage(req)
const file = new URL(req.url).searchParams.get("file")
if (!file) {
const files = listLogFiles()
return Response.json(files)
}
try {
const raw = readLogFileRaw(file)
if (!raw) return new Response("Not found", { status: 404 })
return new Response(raw, { headers: { "Content-Type": "application/jsonl" } })
} catch {
return new Response("Internal server error", { status: 500 })
}
},
},
"/discord/auth": async () => {
const permissions = 536870912 // from https://discord.com/developers/applications