Source: schemas/user.js documentation

/**
 * @file Mongoose {@link User} Schema definitions
 * @author based on whatever its take to suceed boilerplate by Trevis Gulby
 */

/** @module models */

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
let mongoaddr = 'mongodb://localhost:27017/test3';
mongoaddr = process.env.MONGO ? process.env.MONGO : mongoaddr;
mongoose.connect(mongoaddr.toString());

/** Return lowercase for email fields
 * @param {String} a the string to be converted
 * @return {String} the a param in lowercase
 */
function toLower (a) {
    return a.toLowerCase();
}

/** Return float value for assets.qtt field
 * @param {String} a the string to be converted
 * @return {Float} the a float value
 */
function toFloat (a) {
    return parseFloat(a);
}

/** The saved user balances per asset object
 * @constructor
 * @property {String} name the asset full name
 * @property {String} ticker the asset ticker/symbol
 * @property {Float} qtt the asset qtt
 */
const AssetsSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true,
    },
    ticker: {
        type: String,
        required: true,
        trim: true,
    },
    qtt: {
        type: Number,
        required: true,
        set: toFloat,
    },
});

/** The user api's model
 * @constructor
 * @property {String} name The new Api name like n26, coinbase ...
 * @property {String} key The user api key
 * @property {String} secret The user api secret (hashed)
 */
const ApiSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
    },
    key: {
        type: String,
        required: true,
    },
    secret: {
        type: String,
        required: true,
        trim: true,
    },
});

/** A tipical coin_board user
 * @constructor
 * @property {String} email the user email
 * @property {String} username the user username
 * @property {String} usercurrency the user default fiat or crypto currency
 * @property {String} ethaddr WIP around decentralisation and smart contracts
 * @property {String} telegramid user telegram id for bot access when registered
 * @property {String} password user password
 * @property {Array} Apis see {@link module:models~ApiSchema}
 * @property {Array} Assets see {@link module:models~AssetsSchema}
 * @property {Object} Date the user creation timestamp
 */
const UserSchema = new mongoose.Schema({
    username: {
        type: String,
        unique: true,
        required: true,
        trim: true,
    },
    email: {
        type: String,
        unique: true,
        required: true,
        trim: true,
        set: toLower,
    },
    usercurrency: {
        type: String,
        required: true,
        trim: true,
    },
    ethaddr: {
        type: String,
        unique: false,
        required: false,
        trim: false,
    },
    telegramid: {
        type: String,
        unique: true,
        required: false,
    },
    password: {
        type: String,
        required: true,
    },
    scrapperid: {
        type: String,
        required: false,
        default: 'notset',
    },
    Apis: {
        Bank: [ApiSchema],
        Crypto: [ApiSchema],
        Markets: [ApiSchema],
    },
    Assets: {
        Bank: [AssetsSchema],
        Crypto: [AssetsSchema],
        Markets: [AssetsSchema],
    },
    Date: {
        type: Date,
        default: Date.now,
    },
});

/** Getters for schemas => tojson */
UserSchema.set('toJSON', {getters: true, virtuals: false});

/** hashing a password before saving it to the database
 *  @memberof module:models~UserSchema
 */
UserSchema.pre('save', function (next) {
    /* eslint no-invalid-this: 0 */
    let user = this;
    bcrypt.hash(user.password, 10, function (err, hash) {
        if (err) {
            return next(err);
        } else {
            user.password = hash;
            return next();
        }
    });
});

/** Hash api secret too */
ApiSchema.pre('save', (next) => {
    let api = this;
    bcrypt.hash(api.secret, 10, function (err, hash) {
        api.secret = hash;
        next();
    });
});

/** Main authentication method for a User
 * @param {String} username the user unique username
 * @param {String} password the user password
 * @param {function} callback to get the user data or error
 * @memberof module:models~UserSchema
 */
UserSchema.statics.authenticate = function (username, password, callback) {
    User.findOne({username: username})
        .exec(function (err, user) {
            if (err) {
                return callback(err);
            } else if (!user) {
                let err = new Error('User not found.');
                err.status = 401;
                return callback(err);
            }
            bcrypt.compare(password, user.password, function (err, result) {
                if (result === true) {
                    return callback(null, user);
                } else {
                    return callback();
                }
            });
        });
};

/** Add a new Api object a User
 * @param {String} id the User id from session
 * @param {String} apitype from enum ['Bank', 'Crypto', 'Markets']
 * @param {String} apiid the name of the new api service
 * @param {String} apikey the new api service key
 * @param {String} apisecret the new api service secret
 * @param {function} callback to get the result data or error
 * @memberof module:models~UserSchema
 */
UserSchema.statics.addapi = function (id,
    apitype, apiid, apikey, apisecret, callback) {
    let newapi = {
        name: apiid,
        key: apikey,
        secret: apisecret,
    };
    Apis.create(newapi, (error, api) => {
        let elemtype = {};
        elemtype['Apis.' + apitype] = api;
        User.findOneAndUpdate({_id: id}, {$push: elemtype},
            (error, success) => {
                if (error) {
                    callback && callback(error);
                } else {
                    callback && callback(null, success);
                }
            });
    });
};

/** Add a new Asset object to a User
 * @param {String} id the User id from session
 * @param {String} assettype from enum ['Bank', 'Crypto', 'Markets']
 * @param {String} assetid the name of the new Asset
 * @param {String} assetticker the ticker / symbol
 * @param {String} assetqtt the qtt to parsed in float
 * @param {function} callback to get the result data or error
 * @memberof module:models~UserSchema
 */
UserSchema.statics.addasset = (id, assettype, assetid,
    assetticker, assetqtt, callback) => {
    let newasset = {
        name: assetid,
        ticker: assetticker,
        qtt: assetqtt,
    };
    Assets.create(newasset, (error, asset) => {
        let elemtype = {};
        elemtype['Assets.' + assettype] = asset;
        User.findOneAndUpdate({_id: id}, {$push: elemtype},
            (error, success) => {
                if (error) {
                    callback && callback(error);
                } else {
                    callback && callback(null, success);
                }
            });
    });
};

let Apis = mongoose.model('Apis', ApiSchema);
let Assets = mongoose.model('Assets', AssetsSchema);
let User = mongoose.model('User', UserSchema);

module.exports = User;
module.exports.Apis = Apis;
module.exports.Assets = Assets;