<commit_before>/**
 * Things Gateway Database.
 *
 * Stores a list of Things connected to the gateway.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const config = require('config');
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const Passwords = require('./passwords');
const assert = require('assert');

// Imported as a module so we use a relative path.
const ThingsData = require('../static/things.json');

const TABLES = [
  'users',
  'jsonwebtoken_to_user',
  'things',
  'settings',
];

var Database = {
  /**
   * SQLLite3 Database object.
   */
  db: null,

  /**
   * Open the database.
   */
  open: function() {
    var filename = config.get('database.filename');
    var removeBeforeOpen = config.get('database.removeBeforeOpen');
    // Check if database already exists
    var exists = fs.existsSync(filename);
    if (exists && removeBeforeOpen) {
      fs.unlinkSync(filename);
      exists = false;
    }
    console.log(exists ? 'Opening' : 'Creating', 'database:', filename);
    // Open database or create it if it doesn't exist
    this.db = new sqlite3.Database(filename);
    this.db.serialize(() => {
      this.createTables();
      // If database newly created, populate with default data
      if (!exists) {
        this.populate();
      }
    });
  },

  createTables: function() {
    // Create Things table
    this.db.run('CREATE TABLE IF NOT EXISTS things (' +
      'id TEXT PRIMARY KEY,' +
      'description TEXT' +
    ');');

    // Create Users table
    this.db.run('CREATE TABLE IF NOT EXISTS users (' +
      'id INTEGER PRIMARY KEY ASC,' +
      'email TEXT UNIQUE,' +
      'password TEXT,' +
      'name TEXT' +
    ');');

    /**
     * This really should have a foreign key constraint but it does not work
     * with our version of node-sqlite / sqlite.
     *
     * https://github.com/mapbox/node-sqlite3/pull/660
     *
     * Instead, the INTEGER user is either the id of the user or -1 if NULL
     */
    this.db.run('CREATE TABLE IF NOT EXISTS jsonwebtoken_to_user (' +
      'id INTEGER PRIMARY KEY ASC,' +
      'keyId TEXT UNIQUE,' + // public id (kid in JWT terms).
      'user INTEGER,' +
      'issuedAt DATE,' +
      'publicKey TEXT' +
    ');');

    // Create Settings table
    this.db.run('CREATE TABLE IF NOT EXISTS settings (' +
      'key TEXT PRIMARY KEY,' +
      'value TEXT' +
    ');');
  },

  /**
   * Populate the database with default data.
   */
  populate: function() {
    console.log('Populating database with default things...');
    // Populate Things table
    var insertSQL = this.db.prepare(
      'INSERT INTO things (id, description) VALUES (?, ?)');
    for (var thing of ThingsData) {
      var thingId = thing.id;
      delete thing.id;
      insertSQL.run(thingId, JSON.stringify(thing));
    }
    insertSQL.finalize();

    // Add default user if provided
    var defaultUser = config.get('authentication.defaultUser');
    if (defaultUser) {
      var passwordHash = Passwords.hashSync(defaultUser.password);
      this.db.run(
        'INSERT INTO users (email, password, name) VALUES (?, ?, ?)',
        [defaultUser.email, passwordHash, defaultUser.name],
        function(error) {
          if (error) {
            console.error('Failed to save default user.');
          } else {
            console.log('Saved default user ' + defaultUser.email);
          }
        });
    }

    // Add any settings provided.
    var generateSettings = function(obj, baseKey) {
      const settings = [];
      
      for (const key in obj) {
        let newKey;
        if (baseKey !== '') {
          newKey = baseKey + '.' + key;
        } else {
          newKey = key;
        }
        
        if (typeof obj[key] === 'object') {
          Array.prototype.push.apply(settings,
                                     generateSettings(obj[key], newKey));
        } else {
          settings.push([newKey, obj[key]]);
        }
      }
      return settings;
    };

    var settings = generateSettings(config.get('settings.defaults'), '');
    for (const setting of settings) {
      this.db.run(
        'INSERT INTO settings (key, value) VALUES (?, ?)',
        [setting[0], setting[1]],
        function(error) {
          if (error) {
            console.error('Failed to insert setting ' +
                          setting[0]);
          } else {
            console.log('Saved setting ' + setting[0] + ' = ' +
                        setting[1]);
          }
        });
    }<commit_msg>Switch from Array.prototype.push.apply to spread syntax.<commit_after>/**
 * Things Gateway Database.
 *
 * Stores a list of Things connected to the gateway.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

const config = require('config');
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const Passwords = require('./passwords');
const assert = require('assert');

// Imported as a module so we use a relative path.
const ThingsData = require('../static/things.json');

const TABLES = [
  'users',
  'jsonwebtoken_to_user',
  'things',
  'settings',
];

var Database = {
  /**
   * SQLLite3 Database object.
   */
  db: null,

  /**
   * Open the database.
   */
  open: function() {
    var filename = config.get('database.filename');
    var removeBeforeOpen = config.get('database.removeBeforeOpen');
    // Check if database already exists
    var exists = fs.existsSync(filename);
    if (exists && removeBeforeOpen) {
      fs.unlinkSync(filename);
      exists = false;
    }
    console.log(exists ? 'Opening' : 'Creating', 'database:', filename);
    // Open database or create it if it doesn't exist
    this.db = new sqlite3.Database(filename);
    this.db.serialize(() => {
      this.createTables();
      // If database newly created, populate with default data
      if (!exists) {
        this.populate();
      }
    });
  },

  createTables: function() {
    // Create Things table
    this.db.run('CREATE TABLE IF NOT EXISTS things (' +
      'id TEXT PRIMARY KEY,' +
      'description TEXT' +
    ');');

    // Create Users table
    this.db.run('CREATE TABLE IF NOT EXISTS users (' +
      'id INTEGER PRIMARY KEY ASC,' +
      'email TEXT UNIQUE,' +
      'password TEXT,' +
      'name TEXT' +
    ');');

    /**
     * This really should have a foreign key constraint but it does not work
     * with our version of node-sqlite / sqlite.
     *
     * https://github.com/mapbox/node-sqlite3/pull/660
     *
     * Instead, the INTEGER user is either the id of the user or -1 if NULL
     */
    this.db.run('CREATE TABLE IF NOT EXISTS jsonwebtoken_to_user (' +
      'id INTEGER PRIMARY KEY ASC,' +
      'keyId TEXT UNIQUE,' + // public id (kid in JWT terms).
      'user INTEGER,' +
      'issuedAt DATE,' +
      'publicKey TEXT' +
    ');');

    // Create Settings table
    this.db.run('CREATE TABLE IF NOT EXISTS settings (' +
      'key TEXT PRIMARY KEY,' +
      'value TEXT' +
    ');');
  },

  /**
   * Populate the database with default data.
   */
  populate: function() {
    console.log('Populating database with default things...');
    // Populate Things table
    var insertSQL = this.db.prepare(
      'INSERT INTO things (id, description) VALUES (?, ?)');
    for (var thing of ThingsData) {
      var thingId = thing.id;
      delete thing.id;
      insertSQL.run(thingId, JSON.stringify(thing));
    }
    insertSQL.finalize();

    // Add default user if provided
    var defaultUser = config.get('authentication.defaultUser');
    if (defaultUser) {
      var passwordHash = Passwords.hashSync(defaultUser.password);
      this.db.run(
        'INSERT INTO users (email, password, name) VALUES (?, ?, ?)',
        [defaultUser.email, passwordHash, defaultUser.name],
        function(error) {
          if (error) {
            console.error('Failed to save default user.');
          } else {
            console.log('Saved default user ' + defaultUser.email);
          }
        });
    }

    // Add any settings provided.
    var generateSettings = function(obj, baseKey) {
      const settings = [];
      
      for (const key in obj) {
        let newKey;
        if (baseKey !== '') {
          newKey = baseKey + '.' + key;
        } else {
          newKey = key;
        }
        
        if (typeof obj[key] === 'object') {
          settings.push(...generateSettings(obj[key], newKey));
        } else {
          settings.push([newKey, obj[key]]);
        }
      }
      return settings;
    };

    var settings = generateSettings(config.get('settings.defaults'), '');
    for (const setting of settings) {
      this.db.run(
        'INSERT INTO settings (key, value) VALUES (?, ?)',
        [setting[0], setting[1]],
        function(error) {
          if (error) {
            console.error('Failed to insert setting ' +
                          setting[0]);
          } else {
            console.log('Saved setting ' + setting[0] + ' = ' +
                        setting[1]);
          }
        });
    }
{'fraction_non_alphanumeric': 0.1349680170575693, 'fraction_numerical': 0.003837953091684435, 'mean_word_length': 2.1553985872855703, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2694021', 'n_tokens_mistral': 2801, 'n_tokens_neox': 2688, 'n_words': 806}