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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
|
const mariadb = require("mariadb");
const bcrypt = require("bcrypt");
const nanoid = require("nanoid");
const fs = require("fs").promises;
class DB {
constructor() {
this.pool = mariadb.createPool({
host: process.env.DBHost,
user: process.env.DBUser,
password: process.env.DBPassword,
database: process.env.DBName,
});
}
connect() {
return this.pool.getConnection();
}
async init() {
const tables = await this.getTables();
for (const table of tables) if (table && table.length > 1) await this.query(table);
console.info("Database initialized!");
const res = await this.query("SELECT id FROM users LIMIT 1");
if (res.length === 0) await this.initValues();
}
async initValues() {
await this.query("INSERT INTO types (name) VALUES ('pupil'), ('teacher')");
await this.query(
"INSERT INTO class (name) VALUES ('TGM13.1'), ('TGM13.2'), ('TGTM13'), ('TGI13.1'), ('TGI13.2'), ('teacher')",
);
await this.initPolls();
await this.initMottovote();
await this.initProfiles();
await this.initQuestions();
await this.initUsers();
}
async resetAll() {
await this.regenerateTables();
await this.initValues();
}
async regenerateTables() {
const drops = await fs.readFile(__dirname + "/drop.sql", "utf8");
for (const stmt of drops.split(";")) if (stmt && stmt.length > 1) await this.query(stmt);
const tables = await this.getTables();
for (const table of tables) if (table && table.length > 1) await this.query(table);
}
async resetQuotes() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS quotes");
await this.query(tables[3]);
}
async resetSecrets() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS secrets");
await this.query(tables[17]);
}
async resetProfiles() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS profile_comments");
await this.query("DROP TABLE IF EXISTS profile_answers");
await this.query("DROP TABLE IF EXISTS profile_image_ratios");
await this.query("DROP TABLE IF EXISTS profile_questions");
await this.query("DROP TABLE IF EXISTS profile_input_types");
await this.query(tables[8]);
await this.query(tables[9]);
await this.query(tables[10]);
await this.query(tables[11]);
await this.query(tables[18]);
await this.initProfiles();
}
async initProfiles() {
const types = ["number", "file", "date", "text", "color"];
for (const type of types) {
try {
await this.query("INSERT INTO profile_input_types (type) VALUES (?)", type);
} catch (e) {
console.log(e);
}
}
const data = await fs.readFile(__dirname + "/profile.txt", "utf8");
const questions = data.split("\n");
for (const question of questions) {
if (question) {
const [q, type, ...parameters] = question.split(" - ");
let insertId;
try {
insertId = (
await this.query("INSERT INTO profile_questions (question, question_type) VALUE (?, ?)", [
q,
types.indexOf(type) + 1,
])
).insertId;
} catch (e) {
console.log("Profile question already exists!");
}
if (type === "image") {
const [x, y] = parameters[0].split("/").map((v) => parseInt(v));
console.log(insertId, x, y);
try {
await this.query("INSERT INTO profile_image_ratios (question_id, x, y) VALUE (?,?,?)", [
insertId,
x,
y,
]);
} catch (e) {
console.log(e);
}
}
}
}
}
async resetMottovote() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS motto_votes");
await this.query("DROP TABLE IF EXISTS mottos");
await this.query(tables[6]);
await this.query(tables[7]);
await this.initMottovote();
}
async initMottovote() {
const data = await fs.readFile(__dirname + "/mottos.txt", "utf8");
const mottos = data.split("\n");
for (const motto of mottos) {
const [name, desc] = motto.split(" - ");
if (motto) {
await this.query("INSERT INTO mottos (name, description) VALUES (?, ?)", [name, desc]).catch(() =>
console.log("Vote option already exists!"),
);
}
}
}
async resetPolls() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS ranking_questions");
await this.query("DROP TABLE IF EXISTS ranking_answers");
await this.query(tables[4]);
await this.query(tables[5]);
await this.initPolls();
}
async initPolls() {
const data = await fs.readFile(__dirname + "/poll.txt", "utf8");
const parts = data.split("--");
for (const [i, part] of parts.entries()) {
const questions = part.split("\n");
for (const question of questions) {
if (question) {
await this.query("INSERT INTO ranking_questions (question, type_id) VALUE (?,?)", [
question,
i + 1,
]).catch(() => console.log("Poll question already exists!"));
}
}
}
}
async initUsers() {
const data = await fs.readFile(__dirname + "/names.csv", "utf8");
const classes = data.split("--");
const userPasswords = {};
console.log("Generating users");
for (const [classIndex, clazz] of classes.entries()) {
const students = clazz.split("\n");
userPasswords[classIndex] = [];
for (const student of students) {
// Fix undefined
if (student && student.length > 3) {
const [_, surname, name] = student.split(",");
const names = name.split(" ");
const middlename = names.length > 1 && names[1] ? names.slice(1).join(" ") : null;
let username = surname.toLowerCase().slice(0, 6);
if (middlename) username += middlename[0].toLowerCase();
username += names[0].toLowerCase().slice(0, 2);
const pwd = nanoid.nanoid(8);
const password = await bcrypt.hash(pwd, 10);
userPasswords[classIndex].push({ username, pwd });
await this.query(
"INSERT INTO users (username, name, middlename, surname, password, class_id, type_id) VALUE (?,?,?,?,?,?,?)",
[
username,
names[0].replace("\r", ""),
middlename,
surname,
password,
classIndex + 1,
classIndex + 1 === 6 ? 2 : 1,
],
);
}
}
}
await fs.writeFile(__dirname + "/users.json", JSON.stringify(userPasswords));
console.log("Initialized users!");
}
async initQuestions() {
const data = (await fs.readFile(__dirname + "/questions.txt", "utf8")).split("\n");
for (const question of data) {
try {
const [q, a] = question.split(" - ");
const { insertId } = await this.query("INSERT INTO question_questions (question) VALUE (?)", [q]);
for (const answer of a.split(",")) {
await this.query("INSERT INTO question_options (answer_option, question_id) VALUE (?,?)", [
answer,
insertId,
]);
}
} catch (e) {
console.error(e);
console.info("Question already exists!");
}
}
}
async resetQuestions() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS question_answers");
await this.query("DROP TABLE IF EXISTS question_options");
await this.query("DROP TABLE IF EXISTS question_questions");
await this.query(tables[12]);
await this.query(tables[13]);
await this.query(tables[14]);
await this.initQuestions();
}
async resetCharacteristics() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS profile_char");
await this.query(tables[15]);
}
async resetTeacherPredict() {
const tables = await this.getTables();
await this.query("DROP TABLE IF EXISTS teacher_prediction");
await this.query(tables[16]);
}
async regenerateUser(uid) {
const pwd = nanoid.nanoid(8);
const password = await bcrypt.hash(pwd, 10);
await this.query("UPDATE users SET password = ? WHERE id = ?", [password, uid]);
return pwd;
}
async dump() {
// Users
const users = await this.query(
"SELECT u.id, u.username, u.name, u.middlename, u.surname, c.name class, t.name type FROM users u INNER JOIN class c ON u.class_id = c.id INNER JOIN types t ON u.type_id = t.id WHERE t.name = 'pupil'",
);
// Profile
const profile = await this.query(
"SELECT q.question, a.answer, a.user_id FROM profile_questions q INNER JOIN profile_answers a ON a.question_id = q.id",
);
// Questions (percentage)
const rawQuestions = await this.query(
"SELECT q.id, q.question question, o.answer_option option, COUNT(a.user_id) count FROM question_questions q INNER JOIN question_options o ON q.id = o.question_id INNER JOIN question_answers a ON o.id = a.option_id GROUP BY o.id",
);
const questions = [];
rawQuestions.forEach((e) => {
if (!questions[e.id - 1]) questions[e.id - 1] = [];
questions[e.id - 1].push(e);
});
// Ranking
const ranking = await this.query(
"SELECT q.id, question, t.name type FROM ranking_questions q INNER JOIN types t ON type_id = t.id ORDER BY q.id",
);
const answers = await this.query(
"SELECT question_id, u.name, u.middlename, u.surname, c.name class, count(*) count FROM ranking_questions q INNER JOIN ranking_answers a ON q.id = a.question_id INNER JOIN users u ON answer_id = u.id INNER JOIN class c ON u.class_id = c.id GROUP BY question_id, answer_id ORDER BY count DESC",
);
ranking.forEach((question) => (question.answers = []));
answers.forEach((answer) => ranking.filter((q) => q.id === answer.question_id)[0].answers.push(answer));
// Comments and characteristics
for (const user of users) {
user.comments = await this.query(
`SELECT comment from profile_comments where profile_id=${user.id} order by CHAR_LENGTH(comment)`,
);
user.chars = await this.query("SELECT txt from profile_char where profile_id=" + user.id);
}
const quotes = await this.query(
"SELECT q.id, a.name, a.middlename, a.surname, q.quote, c.name class FROM quotes q INNER JOIN users a ON q.author_id = a.id INNER JOIN class c ON a.class_id = c.id ORDER BY c.id, a.surname, a.name, a.middlename",
);
const secrets = await this.query("SELECT id, secret FROM secrets ORDER BY CHAR_LENGTH(secret)");
return { users, profile, questions, ranking, quotes, secrets };
}
async query(query, params) {
const conn = await this.connect();
try {
return await conn.query(query, params);
} finally {
conn.release();
}
}
async getTables() {
return (await fs.readFile(__dirname + "/tables.sql", "utf8")).split(";");
}
}
module.exports = new DB();
|