183 lines
4.9 KiB
JavaScript
183 lines
4.9 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import fsSync from "node:fs";
|
|
import path from "node:path";
|
|
import { PrismaClient } from "@prisma/client";
|
|
|
|
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 prisma = new PrismaClient();
|
|
|
|
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,
|
|
company: c.company ?? null,
|
|
country: c.country ?? null,
|
|
location: c.location ?? null,
|
|
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,
|
|
company: c.company ?? null,
|
|
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();
|
|
});
|