const { randomUUID } = require('crypto'); const neo4j = require('neo4j-driver'); const fs = require('fs'); const path = require('path'); const OWNER_UID = 'executive_claus'; const mockEmails = [ { id: randomUUID(), from: 'cto@example.com', to: ['claus@example.com'], subject: 'Weekly status', body: 'Reminder: prepare the Twin status deck.', receivedAt: new Date().toISOString(), }, { id: randomUUID(), from: 'coach@example.com', to: ['claus@example.com'], subject: 'Fodboldtræning flyttet', body: 'Træningen er flyttet til 17:00 pga. vejr.', receivedAt: new Date(Date.now() - 3600_000).toISOString(), }, ]; const now = Date.now(); const mockEvents = [ { id: randomUUID(), title: 'Wingman weekly sync', start: new Date(now + 2 * 3_600_000).toISOString(), end: new Date(now + 3 * 3_600_000).toISOString(), location: 'Zoom', source: 'google', }, { id: randomUUID(), title: 'Børnenes fodboldkamp', start: new Date(now + 24 * 3_600_000).toISOString(), end: new Date(now + 26 * 3_600_000).toISOString(), location: 'KampKlar', source: 'ics', }, ]; const emailToNode = (item) => ({ labels: ['Email', 'Communication', 'Private'], properties: { externalId: item.id, from: item.from, to: item.to, subject: item.subject, bodyPreview: item.body.slice(0, 140), receivedAt: item.receivedAt, belongsTo: OWNER_UID, }, ownerUid: OWNER_UID, }); const eventToNode = (item) => ({ labels: ['CalendarEvent', 'Schedule', 'Private'], properties: { externalId: item.id, title: item.title, start: item.start, end: item.end, location: item.location ?? 'unspecified', source: item.source ?? 'mock', belongsTo: OWNER_UID, }, ownerUid: OWNER_UID, }); const persistNodes = async (session, nodes) => { const persisted = []; for (const node of nodes) { const labels = node.labels?.length ? node.labels : ['Private']; const labelString = labels.map((l) => `:${l.replace(/[^A-Za-z0-9_]/g, '')}`).join(''); const props = { ...(node.properties || {}) }; if (!props.externalId) props.externalId = randomUUID(); const cypher = ` MERGE (n${labelString} {externalId: $props.externalId}) SET n += $props WITH n, $ownerUid AS ownerUid OPTIONAL MATCH (u:User {uid: ownerUid}) WITH n, ownerUid, u FOREACH (_ IN CASE WHEN ownerUid IS NULL THEN [] ELSE [1] END | MERGE (u:User {uid: ownerUid}) ON CREATE SET u.name = 'Claus', u.role = 'Executive', u.access_level = 'god_mode' MERGE (n)-[:BELONGS_TO]->(u) ) RETURN n.externalId AS externalId `; const res = await session.run(cypher, { props, ownerUid: node.ownerUid ?? null }); const rec = res.records[0]; if (rec) persisted.push(rec.get('externalId')); } return persisted; }; const loadEnvIfNeeded = () => { if (process.env.NEO4J_URI && process.env.NEO4J_USERNAME && process.env.NEO4J_PASSWORD) return; const envPath = path.join(__dirname, '..', 'apps', 'backend', '.env'); if (fs.existsSync(envPath)) { const lines = fs.readFileSync(envPath, 'utf-8').split(/\r?\n/); for (const line of lines) { const m = line.match(/^([A-Z0-9_]+)=(.*)$/); if (m) { const [, k, v] = m; if (!process.env[k]) process.env[k] = v; } } } }; const main = async () => { loadEnvIfNeeded(); const uri = process.env.NEO4J_URI; const user = process.env.NEO4J_USERNAME; const password = process.env.NEO4J_PASSWORD; if (!uri || !user || !password) { console.error('Missing NEO4J_URI/NEO4J_USERNAME/NEO4J_PASSWORD'); process.exit(1); } const driver = neo4j.driver(uri, neo4j.auth.basic(user, password)); const session = driver.session(); try { const emailNodes = mockEmails.map(emailToNode); const calendarNodes = mockEvents.map(eventToNode); await persistNodes(session, emailNodes); await persistNodes(session, calendarNodes); const res = await session.run( 'MATCH (u:User)-[r:BELONGS_TO]-(n:Private) RETURN u.uid AS uid, type(r) AS rel, labels(n) AS labels, coalesce(n.title, n.subject, n.bodyPreview) AS title LIMIT 5' ); const output = res.records.map((rec) => ({ uid: rec.get('uid'), rel: rec.get('rel'), labels: rec.get('labels'), title: rec.get('title'), })); console.log(JSON.stringify(output, null, 2)); } finally { await session.close(); await driver.close(); } }; main().catch((err) => { console.error(err); process.exit(1); });