File size: 4,590 Bytes
5a81b95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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);
});