import fs from "node:fs/promises"; import fsSync from "node:fs"; import path from "node:path"; import { PrismaClient } from "../server/generated/prisma/client.js"; import { PrismaPg } from "@prisma/adapter-pg"; function loadEnvFromDotEnv() { const p = path.resolve(process.cwd(), ".env"); if (!fsSync.existsSync(p)) return; const raw = fsSync.readFileSync(p, "utf8"); for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const idx = trimmed.indexOf("="); if (idx === -1) continue; const key = trimmed.slice(0, idx).trim(); let val = trimmed.slice(idx + 1).trim(); if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { val = val.slice(1, -1); } if (!key) continue; if (key === "DATABASE_URL") { process.env[key] = val; continue; } if (!process.env[key]) process.env[key] = val; } } loadEnvFromDotEnv(); const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); const prisma = new PrismaClient({ adapter }); function datasetRoot() { const teamId = process.env.TEAM_ID || "demo-team"; const userId = process.env.USER_ID || "demo-user"; return path.resolve(process.cwd(), "..", ".data", "crmfs", "teams", teamId, "users", userId); } async function ensureDir(p) { await fs.mkdir(p, { recursive: true }); } async function writeJson(p, value) { await fs.writeFile(p, JSON.stringify(value, null, 2) + "\n", "utf8"); } function jsonlLine(value) { return JSON.stringify(value) + "\n"; } async function main() { const root = datasetRoot(); const tmp = root + ".tmp"; await fs.rm(tmp, { recursive: true, force: true }); await ensureDir(tmp); const contactsDir = path.join(tmp, "contacts"); const notesDir = path.join(tmp, "notes"); const messagesDir = path.join(tmp, "messages"); const eventsDir = path.join(tmp, "events"); const indexDir = path.join(tmp, "index"); await Promise.all([ ensureDir(contactsDir), ensureDir(notesDir), ensureDir(messagesDir), ensureDir(eventsDir), ensureDir(indexDir), ]); const teamId = process.env.TEAM_ID || "demo-team"; const contacts = await prisma.contact.findMany({ where: { teamId }, orderBy: { updatedAt: "desc" }, include: { note: { select: { content: true, updatedAt: true } }, messages: { select: { kind: true, direction: true, channel: true, content: true, durationSec: true, transcriptJson: true, occurredAt: true, }, orderBy: { occurredAt: "asc" }, }, events: { select: { title: true, startsAt: true, endsAt: true, status: true, note: true }, orderBy: { startsAt: "asc" }, }, }, take: 5000, }); const contactIndex = []; for (const c of contacts) { await writeJson(path.join(contactsDir, `${c.id}.json`), { id: c.id, teamId: c.teamId, name: c.name, avatarUrl: c.avatarUrl ?? null, email: c.email ?? null, phone: c.phone ?? null, createdAt: c.createdAt, updatedAt: c.updatedAt, }); await fs.writeFile( path.join(notesDir, `${c.id}.md`), (c.note?.content?.trim() ? c.note.content.trim() : "") + "\n", "utf8", ); await fs.writeFile( path.join(messagesDir, `${c.id}.jsonl`), c.messages .map((m) => jsonlLine({ kind: m.kind, direction: m.direction, channel: m.channel, occurredAt: m.occurredAt, content: m.content, durationSec: m.durationSec ?? null, transcript: m.transcriptJson ?? null, }), ) .join(""), "utf8", ); await fs.writeFile( path.join(eventsDir, `${c.id}.jsonl`), c.events .map((e) => jsonlLine({ title: e.title, startsAt: e.startsAt, endsAt: e.endsAt, status: e.status ?? null, note: e.note ?? null, }), ) .join(""), "utf8", ); const lastMessageAt = c.messages.length ? c.messages[c.messages.length - 1].occurredAt : null; const nextEventAt = c.events.find((e) => new Date(e.startsAt).getTime() >= Date.now())?.startsAt ?? null; contactIndex.push({ id: c.id, name: c.name, lastMessageAt, nextEventAt, updatedAt: c.updatedAt, }); } await writeJson(path.join(indexDir, "contacts.json"), contactIndex); await writeJson(path.join(tmp, "meta.json"), { exportedAt: new Date().toISOString(), version: 1 }); await ensureDir(path.dirname(root)); await fs.rm(root, { recursive: true, force: true }); await fs.rename(tmp, root); console.log("exported", root); } await main() .catch((e) => { console.error(e); process.exitCode = 1; }) .finally(async () => { await prisma.$disconnect(); });