Initial commit: AMS Backend - Deno + Oak + MongoDB
This commit is contained in:
138
src/routes/export.ts
Normal file
138
src/routes/export.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
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<string, unknown[]> = {};
|
||||
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<string, unknown[]>;
|
||||
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<string, { inserted: number; skipped: number }> = {};
|
||||
|
||||
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<string, unknown>;
|
||||
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<string, number> = {};
|
||||
|
||||
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 };
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user