File size: 21,004 Bytes
de995c4
514e876
de995c4
 
514e876
 
 
de995c4
 
514e876
de995c4
 
 
514e876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de995c4
514e876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de995c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514e876
 
 
 
 
 
 
 
de995c4
 
 
514e876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de995c4
 
514e876
 
 
 
 
 
 
 
de995c4
514e876
 
 
 
 
 
 
 
 
 
 
 
 
de995c4
514e876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de995c4
 
514e876
 
de995c4
 
514e876
 
 
de995c4
514e876
 
 
 
 
 
 
 
 
 
 
de995c4
 
514e876
 
de995c4
 
514e876
 
 
de995c4
514e876
 
 
 
 
 
 
 
 
 
 
de995c4
 
514e876
 
de995c4
 
 
 
 
 
 
 
 
 
 
 
 
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
const sqlite3 = require('sqlite3').verbose();
const { createClient } = require('@libsql/client');
const path = require('path');

// 检查是否使用 Turso(云端数据库)
const USE_TURSO = !!(process.env.TURSO_DATABASE_URL && process.env.TURSO_AUTH_TOKEN);
const DB_PATH = process.env.DB_PATH || './database/fitjourney.db';

let db = null;
let tursoClient = null;

const initDatabase = () => {
  return new Promise((resolve, reject) => {
    if (USE_TURSO) {
      // 使用 Turso 云端数据库
      try {
        tursoClient = createClient({
          url: process.env.TURSO_DATABASE_URL,
          authToken: process.env.TURSO_AUTH_TOKEN,
        });
        console.log('✅ Connected to Turso database');
        
        // 创建表
        createTables()
          .then(() => resolve())
          .catch(reject);
      } catch (error) {
        console.error('❌ Error connecting to Turso:', error.message);
        reject(error);
      }
    } else {
      // 使用本地 SQLite 数据库
      const dbDir = path.dirname(DB_PATH);
      const fs = require('fs');
      if (!fs.existsSync(dbDir)) {
        fs.mkdirSync(dbDir, { recursive: true });
      }

      db = new sqlite3.Database(DB_PATH, (err) => {
        if (err) {
          console.error('❌ Error opening database:', err.message);
          reject(err);
          return;
        }
        console.log('✅ Connected to local SQLite database');
        
        // 创建用户表
        createTables()
          .then(() => resolve())
          .catch(reject);
      });
    }
  });
};

const createTables = () => {
  return new Promise((resolve, reject) => {
    const createUsersTable = `
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT UNIQUE NOT NULL,
        password_hash TEXT NOT NULL,
        is_verified INTEGER DEFAULT 0,
        verification_code TEXT,
        verification_expires INTEGER,
        reset_token TEXT,
        reset_expires INTEGER,
        is_activated INTEGER DEFAULT 0,
        activation_expires INTEGER,
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        updated_at INTEGER DEFAULT (strftime('%s', 'now'))
      )
    `;

    const createTokensTable = `
      CREATE TABLE IF NOT EXISTS user_tokens (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        token_hash TEXT NOT NULL,
        expires_at INTEGER NOT NULL,
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
      )
    `;

    const createActivationCodesTable = `
      CREATE TABLE IF NOT EXISTS activation_codes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        code TEXT UNIQUE NOT NULL,
        days INTEGER NOT NULL DEFAULT 30,
        is_used INTEGER DEFAULT 0,
        used_by INTEGER,
        used_at INTEGER,
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        FOREIGN KEY (used_by) REFERENCES users (id) ON DELETE SET NULL
      )
    `;

    // 教练表
    const createCoachesTable = `
      CREATE TABLE IF NOT EXISTS coaches (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE NOT NULL,
        password_hash TEXT NOT NULL,
        password_plain TEXT,
        name TEXT,
        phone TEXT,
        is_active INTEGER DEFAULT 1,
        created_at DATETIME DEFAULT (datetime('now', 'localtime')),
        updated_at DATETIME DEFAULT (datetime('now', 'localtime'))
      )
    `;

    // 教练邀请码表
    const createInviteCodesTable = `
      CREATE TABLE IF NOT EXISTS invite_codes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        coach_id INTEGER NOT NULL,
        code TEXT UNIQUE NOT NULL,
        is_active INTEGER DEFAULT 1,
        created_at INTEGER DEFAULT (strftime('%s', 'now')),
        FOREIGN KEY (coach_id) REFERENCES coaches (id) ON DELETE CASCADE
      )
    `;

    // 教练-学员关系表
    const createTrainerStudentsTable = `
      CREATE TABLE IF NOT EXISTS trainer_students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        coach_id INTEGER NOT NULL,
        user_id INTEGER NOT NULL,
        student_name TEXT,
        joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (coach_id) REFERENCES coaches (id) ON DELETE CASCADE,
        FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
        UNIQUE(coach_id, user_id)
      )
    `;

    // 用户数据表(用于教练查看学员数据)
    const createUserDataTable = `
      CREATE TABLE IF NOT EXISTS user_data (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER UNIQUE NOT NULL,
        diet_data TEXT,
        sleep_data TEXT,
        exercise_data TEXT,
        status_data TEXT,
        personal_data TEXT,
        updated_at DATETIME DEFAULT (datetime('now', 'localtime')),
        FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
      )
    `;

    // 迁移脚本:为现有的users表添加新列
    const migrateUsersTable = () => {
      return new Promise((migrateResolve, migrateReject) => {
        // 检查是否存在is_activated列
        db.all("PRAGMA table_info(users)", (err, columns) => {
          if (err) {
            console.error('❌ Error checking table info:', err.message);
            migrateReject(err);
            return;
          }

          const hasIsActivated = columns.some(col => col.name === 'is_activated');
          const hasActivationExpires = columns.some(col => col.name === 'activation_expires');

          let migrationCount = 0;
          let totalMigrations = 0;

          const checkMigrationComplete = () => {
            migrationCount++;
            if (migrationCount === totalMigrations) {
              migrateResolve();
            }
          };

          // 添加is_activated列
          if (!hasIsActivated) {
            totalMigrations++;
            db.run("ALTER TABLE users ADD COLUMN is_activated INTEGER DEFAULT 0", (err) => {
              if (err) {
                console.error('❌ Error adding is_activated column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added is_activated column to users table');
              checkMigrationComplete();
            });
          }

          // 添加activation_expires列
          if (!hasActivationExpires) {
            totalMigrations++;
            db.run("ALTER TABLE users ADD COLUMN activation_expires INTEGER", (err) => {
              if (err) {
                console.error('❌ Error adding activation_expires column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added activation_expires column to users table');
              checkMigrationComplete();
            });
          }

          // 如果没有需要迁移的列,直接完成
          if (totalMigrations === 0) {
            console.log('✅ Users table migration not needed');
            migrateResolve();
          }
        });
      });
    };

    // 迁移脚本:为教练表添加password_plain列
    const migrateCoachesTable = () => {
      return new Promise((migrateResolve, migrateReject) => {
        db.all("PRAGMA table_info(coaches)", (err, columns) => {
          if (err) {
            console.error('❌ Error checking coaches table info:', err.message);
            migrateReject(err);
            return;
          }

          const hasPasswordPlain = columns.some(col => col.name === 'password_plain');

          if (!hasPasswordPlain) {
            db.run("ALTER TABLE coaches ADD COLUMN password_plain TEXT", (err) => {
              if (err) {
                console.error('❌ Error adding password_plain column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added password_plain column to coaches table');
              migrateResolve();
            });
          } else {
            console.log('✅ Coaches table migration not needed');
            migrateResolve();
          }
        });
      });
    };

    // 迁移脚本:为trainer_students表添加student_name列
    const migrateTrainerStudentsTable = () => {
      return new Promise((migrateResolve, migrateReject) => {
        db.all("PRAGMA table_info(trainer_students)", (err, columns) => {
          if (err) {
            console.error('❌ Error checking trainer_students table info:', err.message);
            migrateReject(err);
            return;
          }

          const hasStudentName = columns.some(col => col.name === 'student_name');

          if (!hasStudentName) {
            db.run("ALTER TABLE trainer_students ADD COLUMN student_name TEXT", (err) => {
              if (err) {
                console.error('❌ Error adding student_name column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added student_name column to trainer_students table');
              migrateResolve();
            });
          } else {
            console.log('✅ Trainer_students table migration not needed');
            migrateResolve();
          }
        });
      });
    };

    const migrateUserDataTable = () => {
      return new Promise((migrateResolve, migrateReject) => {
        db.all("PRAGMA table_info(user_data)", (err, columns) => {
          if (err) {
            console.error('❌ Error checking user_data table info:', err.message);
            migrateReject(err);
            return;
          }

          const hasStatusData = columns.some(col => col.name === 'status_data');
          const hasPersonalData = columns.some(col => col.name === 'personal_data');
          
          let migrationCount = 0;
          let totalMigrations = 0;
          
          const checkMigrationComplete = () => {
            migrationCount++;
            if (migrationCount === totalMigrations) {
              migrateResolve();
            }
          };
          
          if (!hasStatusData) {
            totalMigrations++;
          }
          if (!hasPersonalData) {
            totalMigrations++;
          }
          
          if (totalMigrations === 0) {
            console.log('✅ User_data table migration not needed');
            migrateResolve();
            return;
          }
          
          if (!hasStatusData) {
            db.run("ALTER TABLE user_data ADD COLUMN status_data TEXT", (err) => {
              if (err) {
                console.error('❌ Error adding status_data column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added status_data column to user_data table');
              checkMigrationComplete();
            });
          }
          
          if (!hasPersonalData) {
            db.run("ALTER TABLE user_data ADD COLUMN personal_data TEXT", (err) => {
              if (err) {
                console.error('❌ Error adding personal_data column:', err.message);
                migrateReject(err);
                return;
              }
              console.log('✅ Added personal_data column to user_data table');
              checkMigrationComplete();
            });
          }
        });
      });
    };

    // 迁移 user_data 表的时间戳从UTC转换为本地时间(东八区)
    const migrateUserDataTimestamp = () => {
      return new Promise((migrateResolve, migrateReject) => {
        // 更新所有现有记录的 updated_at,将UTC时间转换为东八区时间 (+8小时)
        db.run(
          `UPDATE user_data 
           SET updated_at = datetime(updated_at, '+8 hours') 
           WHERE updated_at IS NOT NULL 
           AND updated_at NOT LIKE '%+%'`,
          (err) => {
            if (err) {
              console.error('❌ Error migrating user_data timestamps:', err.message);
              migrateReject(err);
              return;
            }
            console.log('✅ Migrated user_data timestamps to local time (UTC+8)');
            migrateResolve();
          }
        );
      });
    };

    // 修正 coaches 表的时间戳(如果之前加了 12 小时,现在改为只加 8 小时)
    const fixCoachesTimezone = () => {
      return new Promise((migrateResolve, migrateReject) => {
        // 检查是否有 DATETIME 格式的数据(已经被迁移过的)
        db.get(
          `SELECT created_at FROM coaches WHERE created_at > '2025-01-01' LIMIT 1`,
          (err, row) => {
            if (err) {
              console.error('❌ Error checking coaches timestamps:', err.message);
              migrateReject(err);
              return;
            }

            if (row && row.created_at) {
              // 如果时间看起来比正常时间快(即多加了4小时),则修正
              // 这里我们减去4小时,将 UTC+12 改为 UTC+8
              db.run(
                `UPDATE coaches 
                 SET created_at = datetime(created_at, '-4 hours'),
                     updated_at = datetime(updated_at, '-4 hours')
                 WHERE created_at > datetime('now', 'localtime', '+1 hour')`,
                (err) => {
                  if (err) {
                    console.error('❌ Error fixing coaches timezone:', err.message);
                    migrateReject(err);
                    return;
                  }
                  console.log('✅ Fixed coaches timezone (UTC+12 -> UTC+8)');
                  migrateResolve();
                }
              );
            } else {
              console.log('✅ Coaches timezone fix not needed');
              migrateResolve();
            }
          }
        );
      });
    };

    const migrateCoachesTimestamp = () => {
      return new Promise((migrateResolve, migrateReject) => {
        // 检查coaches表的created_at字段类型
        db.all("PRAGMA table_info(coaches)", (err, columns) => {
          if (err) {
            console.error('❌ Error checking coaches table info:', err.message);
            migrateReject(err);
            return;
          }

          const createdAtCol = columns.find(col => col.name === 'created_at');
          
          // 如果created_at是INTEGER类型,需要转换
          if (createdAtCol && createdAtCol.type === 'INTEGER') {
            console.log('🔄 Migrating coaches table timestamp fields from INTEGER to DATETIME...');
            
            // SQLite不支持直接修改列类型,需要重建表
            db.serialize(() => {
              // 1. 创建新表
              db.run(`
                CREATE TABLE coaches_new (
                  id INTEGER PRIMARY KEY AUTOINCREMENT,
                  username TEXT UNIQUE NOT NULL,
                  password_hash TEXT NOT NULL,
                  password_plain TEXT,
                  name TEXT,
                  phone TEXT,
                  is_active INTEGER DEFAULT 1,
                  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
                )
              `, (err) => {
                if (err) {
                  console.error('❌ Error creating new coaches table:', err.message);
                  migrateReject(err);
                  return;
                }

                // 2. 复制数据,将INTEGER时间戳转换为DATETIME(服务器已在东八区,使用localtime即可)
                db.run(`
                  INSERT INTO coaches_new (id, username, password_hash, password_plain, name, phone, is_active, created_at, updated_at)
                  SELECT id, username, password_hash, password_plain, name, phone, is_active, 
                         datetime(created_at, 'unixepoch', 'localtime'),
                         datetime(updated_at, 'unixepoch', 'localtime')
                  FROM coaches
                `, (err) => {
                  if (err) {
                    console.error('❌ Error copying data to new coaches table:', err.message);
                    migrateReject(err);
                    return;
                  }

                  // 3. 删除旧表
                  db.run('DROP TABLE coaches', (err) => {
                    if (err) {
                      console.error('❌ Error dropping old coaches table:', err.message);
                      migrateReject(err);
                      return;
                    }

                    // 4. 重命名新表
                    db.run('ALTER TABLE coaches_new RENAME TO coaches', (err) => {
                      if (err) {
                        console.error('❌ Error renaming new coaches table:', err.message);
                        migrateReject(err);
                        return;
                      }
                      console.log('✅ Coaches table timestamp migration completed');
                      migrateResolve();
                    });
                  });
                });
              });
            });
          } else {
            console.log('✅ Coaches table timestamp migration not needed');
            migrateResolve();
          }
        });
      });
    };

    // 使用统一的方式执行SQL(支持 Turso 和 SQLite)
    const executeSQL = async (sql, errorMsg, successMsg) => {
      try {
        await runQuery(sql);
        console.log(successMsg);
      } catch (err) {
        console.error(errorMsg, err.message);
        throw err;
      }
    };

    // 按顺序创建所有表
    (async () => {
      try {
        await executeSQL(createUsersTable, '❌ Error creating users table:', '✅ Users table ready');
        await executeSQL(createTokensTable, '❌ Error creating tokens table:', '✅ Tokens table ready');
        await executeSQL(createActivationCodesTable, '❌ Error creating activation codes table:', '✅ Activation codes table ready');
        await executeSQL(createCoachesTable, '❌ Error creating coaches table:', '✅ Coaches table ready');
        await executeSQL(createInviteCodesTable, '❌ Error creating invite codes table:', '✅ Invite codes table ready');
        await executeSQL(createTrainerStudentsTable, '❌ Error creating trainer_students table:', '✅ Trainer students table ready');
        await executeSQL(createUserDataTable, '❌ Error creating user_data table:', '✅ User data table ready');

        // 只在本地 SQLite 时执行迁移(Turso 不需要)
        if (!USE_TURSO) {
          await migrateUsersTable();
          await migrateCoachesTable();
          await migrateTrainerStudentsTable();
          await migrateUserDataTable();
          await migrateUserDataTimestamp();
          await migrateCoachesTimestamp();
          await fixCoachesTimezone();
        }

        console.log('✅ Database migration completed');
        resolve();
      } catch (error) {
        reject(error);
      }
    })();
  });
};

const getDatabase = () => {
  if (USE_TURSO) {
    if (!tursoClient) {
      throw new Error('Turso database not initialized. Call initDatabase() first.');
    }
    return tursoClient;
  } else {
    if (!db) {
      throw new Error('Database not initialized. Call initDatabase() first.');
    }
    return db;
  }
};

// 辅助函数:执行SQL查询(支持 Turso 和 SQLite)
const runQuery = async (sql, params = []) => {
  if (USE_TURSO) {
    try {
      const result = await tursoClient.execute({ sql, args: params });
      return { 
        id: result.lastInsertRowid ? Number(result.lastInsertRowid) : null,
        changes: result.rowsAffected 
      };
    } catch (error) {
      throw error;
    }
  } else {
    return new Promise((resolve, reject) => {
      db.run(sql, params, function(err) {
        if (err) {
          reject(err);
        } else {
          resolve({ id: this.lastID, changes: this.changes });
        }
      });
    });
  }
};

const getQuery = async (sql, params = []) => {
  if (USE_TURSO) {
    try {
      const result = await tursoClient.execute({ sql, args: params });
      return result.rows[0] || undefined;
    } catch (error) {
      throw error;
    }
  } else {
    return new Promise((resolve, reject) => {
      db.get(sql, params, (err, row) => {
        if (err) {
          reject(err);
        } else {
          resolve(row);
        }
      });
    });
  }
};

const allQuery = async (sql, params = []) => {
  if (USE_TURSO) {
    try {
      const result = await tursoClient.execute({ sql, args: params });
      return result.rows || [];
    } catch (error) {
      throw error;
    }
  } else {
    return new Promise((resolve, reject) => {
      db.all(sql, params, (err, rows) => {
        if (err) {
          reject(err);
        } else {
          resolve(rows);
        }
      });
    });
  }
};

module.exports = {
  initDatabase,
  getDatabase,
  runQuery,
  getQuery,
  allQuery
};