import { Router } from "@oak/oak"; import { getDB } from "../db/mongo.ts"; import { authMiddleware, type AuthState } from "../middleware/auth.ts"; import type { Context, Next } from "@oak/oak"; export const exportRouter = new Router({ prefix: "/api/data" }); async function adminOnly(ctx: Context, next: Next) { const user = (ctx.state as AuthState).user; if (user.role !== "admin") { ctx.response.status = 403; ctx.response.body = { error: "Admin access required" }; return; } await next(); } // Export all data as JSON exportRouter.get("/export", authMiddleware, adminOnly, async (ctx) => { try { const db = await getDB(); const url = ctx.request.url; // Which collections to export (default: all) const collectionsParam = url.searchParams.get("collections"); const available = ["tasks", "projects", "agents", "labels", "logs", "users"]; const requested = collectionsParam ? collectionsParam.split(",").filter((c) => available.includes(c)) : available; const data: Record = {}; for (const col of requested) { data[col] = await db.collection(col).find({}).toArray(); } const exportPayload = { version: "1.0", exportedAt: new Date().toISOString(), collections: requested, data, }; ctx.response.headers.set( "Content-Disposition", `attachment; filename="ams-export-${new Date().toISOString().slice(0, 10)}.json"` ); ctx.response.type = "application/json"; ctx.response.body = exportPayload; } catch (error: unknown) { const message = error instanceof Error ? error.message : "Unknown error"; ctx.response.status = 500; ctx.response.body = { error: "Export failed", message }; } }); // Import data from JSON exportRouter.post("/import", authMiddleware, adminOnly, async (ctx) => { try { const body = await ctx.request.body.json(); const { data, mode } = body as { data: Record; mode: "merge" | "replace"; }; if (!data || typeof data !== "object") { ctx.response.status = 400; ctx.response.body = { error: "Invalid import data" }; return; } const db = await getDB(); const available = ["tasks", "projects", "agents", "labels", "logs"]; const results: Record = {}; for (const [collection, documents] of Object.entries(data)) { // Skip users for security if (!available.includes(collection) || !Array.isArray(documents)) continue; if (mode === "replace") { await db.collection(collection).deleteMany({}); } let inserted = 0; let skipped = 0; for (const doc of documents) { try { const { _id, ...rest } = doc as Record; if (mode === "merge" && _id) { // Try to find existing by _id, skip if exists const existing = await db .collection(collection) .findOne({ _id }); if (existing) { skipped++; continue; } } await db.collection(collection).insertOne(rest); inserted++; } catch { skipped++; } } results[collection] = { inserted, skipped }; } ctx.response.body = { success: true, message: "Import completed", results, }; } catch (error: unknown) { const message = error instanceof Error ? error.message : "Unknown error"; ctx.response.status = 500; ctx.response.body = { error: "Import failed", message }; } }); // Get export stats (what's available to export) exportRouter.get("/stats", authMiddleware, adminOnly, async (ctx) => { try { const db = await getDB(); const collections = ["tasks", "projects", "agents", "labels", "logs", "users"]; const stats: Record = {}; for (const col of collections) { stats[col] = await db.collection(col).countDocuments({}); } ctx.response.body = { stats }; } catch (error: unknown) { const message = error instanceof Error ? error.message : "Unknown error"; ctx.response.status = 500; ctx.response.body = { error: "Failed to get stats", message }; } });