Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169// AC Machine Logs — REST API for machine log retrieval// GET /api/machine-logs?machineId=X&limit=50 — recent logs for a machine// GET /api/machine-logs?machineId=X&type=crash — filter by type// GET /api/machine-logs?machineId=X&since=ISO_DATE — logs since timestamp
import { authorize } from "../../backend/authorization.mjs";import { connect } from "../../backend/database.mjs";import { respond } from "../../backend/http.mjs";
async function ensureIndexes(db) { const logs = db.collection("ac-machine-logs");
await Promise.all([ logs.createIndex({ machineId: 1, when: -1 }), logs.createIndex({ user: 1, type: 1, when: -1 }), // TTL index: auto-expire log documents 7 days after receivedAt logs.createIndex({ receivedAt: 1 }, { expireAfterSeconds: 604800 }), ]);}
export async function handler(event) { // ── Preflight ────────────────────────────────────────────────────── if (event.httpMethod === "OPTIONS") { return respond(204, ""); }
// ── Auth (optional for POST — devices send sub in body) ────────── const authHeader = event.headers.authorization || event.headers.Authorization; const user = authHeader ? await authorize({ authorization: authHeader }) : null; // GET requires JWT auth; POST allows device auth via body sub if (event.httpMethod === "GET" && (!user || !user.sub)) { return respond(401, { success: false, error: "Unauthorized" }); }
const database = await connect();
try { const db = database.db;
// Ensure indexes (idempotent, fast on subsequent calls) await ensureIndexes(db);
const logs = db.collection("ac-machine-logs");
// ── POST — upload session log from device ───────────────────────── // Accepts both JWT auth (from dashboard) and device auth (sub in body) if (event.httpMethod === "POST") { let body; try { body = JSON.parse(event.body || "{}"); } catch { return respond(400, { success: false, error: "Invalid JSON" }); }
const { machineId, lines, sessionType, sub: deviceSub } = body || {}; if (!machineId || !Array.isArray(lines)) { return respond(400, { success: false, error: "Need machineId (string) and lines (array of strings)", }); }
// Use JWT user if available, fall back to device sub from body // (device sends its config.json sub for identification) const ownerSub = user?.sub || deviceSub; if (!ownerSub) { return respond(401, { success: false, error: "No user identity" }); }
// Verify machine belongs to user (or register if new) const machinesCol = db.collection("ac-machines"); const existing = await machinesCol.findOne({ machineId }); if (existing && existing.user !== ownerSub) { return respond(403, { success: false, error: "Machine belongs to another user" }); } if (!existing) { // Auto-register machine on first log upload await machinesCol.insertOne({ machineId, user: ownerSub, label: machineId, registeredAt: new Date(), lastSeen: new Date(), }); } else { await machinesCol.updateOne( { machineId }, { $set: { lastSeen: new Date() } }, ); }
const now = new Date(); const docs = lines.slice(0, 500).map((line, i) => ({ machineId, user: ownerSub, type: sessionType || "log", level: "info", message: typeof line === "string" ? line : String(line), when: new Date(now.getTime() - (lines.length - i) * 10), receivedAt: now, }));
if (docs.length > 0) { await logs.insertMany(docs, { ordered: false }); }
return respond(200, { success: true, inserted: docs.length }); }
// ── GET — retrieve logs ─────────────────────────────────────────── if (event.httpMethod !== "GET") { return respond(405, { success: false, error: "Method not allowed" }); }
const params = event.queryStringParameters || {};
const { machineId, type, since, limit: rawLimit } = params;
if (!machineId || typeof machineId !== "string") { return respond(400, { success: false, error: "Missing machineId query parameter", }); }
const limit = Math.min(200, Math.max(1, Number(rawLimit) || 50));
// Build query — always scoped to user + machineId const query = { machineId, user: user.sub };
// Optional type filter if (type) { const validTypes = ["log", "crash", "perf", "boot", "update"]; if (!validTypes.includes(type)) { return respond(400, { success: false, error: `Invalid type. Must be one of: ${validTypes.join(", ")}`, }); } query.type = type; }
// Optional since filter if (since) { const sinceDate = new Date(since); if (isNaN(sinceDate.getTime())) { return respond(400, { success: false, error: "Invalid since parameter. Must be a valid ISO date string.", }); } query.when = { $gte: sinceDate }; }
const results = await logs .find(query) .sort({ when: -1 }) .limit(limit) .toArray();
return respond(200, { success: true, logs: results }); } catch (err) { return respond(500, { success: false, error: err.message }); } finally { await database.disconnect(); }}