Spaces:
Paused
Paused
| 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); | |
| }); | |