Repository navigation
Expand file tree
/
Copy pathdb.ts
More file actions
137 lines (115 loc) · 3.97 KB
/
Copy pathdb.ts
File metadata and controls
137 lines (115 loc) · 3.97 KB
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
import neo4j from "neo4j-driver";
import { hash_password } from "./utils/passwords";
import { userQueryIdentifierFields } from "./config";
export const {
NEO4J_URL = "bolt://localhost",
NEO4J_USERNAME = "neo4j",
NEO4J_PASSWORD = "neo4j",
DEFAULT_ADMIN_USERNAME: admin_username = "administrator",
DEFAULT_ADMIN_PASSWORD: admin_password = "administrator",
} = process.env;
const auth = neo4j.auth.basic(NEO4J_USERNAME, NEO4J_PASSWORD);
export const drivers = {
v1: neo4j.driver(NEO4J_URL, auth, {}),
v2: neo4j.driver(NEO4J_URL, auth, { disableLosslessIntegers: true }),
};
export const driver = drivers.v2; // alias
// Set once the DB setup (admin account, IDs, constraints) has completed
let initialized = false;
// Live check: whether Neo4J can be reached right now
export const get_connection_status = async () => {
try {
await driver.verifyConnectivity();
return true;
} catch {
return false;
}
};
const set_ids_to_nodes_without_ids = async () => {
const id_setting_query = `
MATCH (u:User)
WHERE u._id IS NULL
SET u._id = toString(id(u))
RETURN COUNT(u) as count
`;
const session = driver.session();
try {
const { records } = await session.run(id_setting_query);
const count = records[0].get("count");
console.log(`[Neo4J] ID of ${count} nodes have been set`);
} catch (error) {
console.error(`Setting IDs failed`);
throw error;
} finally {
session.close();
}
};
const create_admin_if_not_exists = async () => {
console.log(`[Neo4J] Creating admin account`);
const session = driver.session();
try {
const password_hashed = await hash_password(admin_password);
const query = `
// Find the administrator account or create it if it does not exist
MERGE (administrator:User {username:$admin_username})
// Check if the administrator account is missing its password
// If the administrator account does not have a password (newly created), set it
// TODO: consider using ON CREATE
WITH administrator
WHERE administrator.password_hashed IS NULL
SET administrator.password_hashed = $password_hashed
SET administrator._id = randomUUID() // THIS IS IMPORTANT
SET administrator.isAdmin = true
// Set some additional properties
SET administrator.display_name = 'Administrator'
// Return the account
RETURN administrator
`;
const { records } = await session.run(query, {
admin_username,
password_hashed,
});
if (records.length)
console.log(`[Neo4J] Admin creation: admin account created`);
else console.log(`[Neo4J] Admin creation: admin already existed`);
} catch (error) {
console.error(`Admin creation failed`);
throw error;
} finally {
session.close();
}
};
const create_constraints = async () => {
// const fields = ["_id", "username"];
const fields = userQueryIdentifierFields; // Is this really OK?
const session = driver.session();
try {
for (const field of fields) {
console.log(`[Neo4J] Creating ${field} constraint`);
await session.run(
`CREATE CONSTRAINT IF NOT EXISTS FOR (u:User) REQUIRE u.${field} IS UNIQUE`
);
}
} finally {
session.close();
}
};
// Retries until the setup succeeds, so a DB that is not up yet never crashes the app.
// Not strictly needed: init() could let the error propagate and the process exit,
// and Kubernetes would restart the pod (with backoff) until the DB is up. Retrying
// here recovers faster once the DB is back and avoids CrashLoopBackOff.
export const init = async () => {
try {
console.log("[Neo4J] Initializing DB");
await create_admin_if_not_exists();
await set_ids_to_nodes_without_ids();
await create_constraints();
initialized = true;
console.log(`[Neo4J] DB initialized`);
} catch (error) {
console.error("[Neo4J] DB initialization failed, retrying in 10s", error);
setTimeout(init, 10000);
}
};
export const url = NEO4J_URL;
export const get_initialized = () => initialized;