Commit 0534f55d authored by Felix Kopp's avatar Felix Kopp 😴

Increase backend indent size to 4 spaces, install jsdoc

parent 0361300b
/**
* src/index.js
*
* Starting point for the application
* @file Starting point for the application
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -32,20 +31,19 @@ const routes = require('./routes.js');
*/
middleware.forEach(m => {
app.use(m);
app.use(m);
});
for (var route in routes) {
if (routes.hasOwnProperty(route)) {
app.use('/' + route.toString(), routes[route]);
}
if (routes.hasOwnProperty(route))
app.use('/' + route.toString(), routes[route]);
}
// Fallback to 404 for all routes that are not defined
app.use((req, res, next) => {
const err = new Error('Not found');
err.status = 404;
next(err);
const err = new Error('Not found');
err.status = 404;
next(err);
});
// Default error message
......
/**
* src/bootstrap/db.js
*
* Establish a connection to the database
* @file Establish a connection to the database.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -25,24 +24,24 @@ const params = require('../config/db.js');
const appConfig = require('../config/app.js');
const mongooseOpts = {
auth: {
user: params.user,
password: params.password
},
authSource: params.authSource,
dbName: params.database,
useNewUrlParser: true,
ssl: params.ssl
auth: {
user: params.user,
password: params.password
},
authSource: params.authSource,
dbName: params.database,
useNewUrlParser: true,
ssl: params.ssl
};
// Make 100% sure we are using an encrypted database
// connection in production (I'm paranoid)
if (appConfig.env === 'production') {
mongooseOpts.ssl = true;
mongooseOpts.authSource = params.authSource;
mongooseOpts.ssl = true;
mongooseOpts.authSource = params.authSource;
}
mongoose.connect(
'mongodb://' + params.host + ':' + params.port,
mongooseOpts
'mongodb://' + params.host + ':' + params.port,
mongooseOpts
);
/**
* src/bootstrap/middleware.js
*
* Middleware loader
* @file Middleware loader.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -21,6 +20,6 @@
*/
module.exports = [
require('cors')(),
require('body-parser').json([])
require('cors')(),
require('body-parser').json([])
];
/**
* src/bootstrap/scheduler.js
*
* Task scheduler for constant repeating tasks
* @file Task scheduler for constant repeating tasks.
* @author Felix Kopp <santler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -28,13 +27,13 @@ const User = require('../lib/db/model/user.js');
// click the activation link within 24 hours
setInterval(() => {
console.log('Executing task "Delete unverified users"');
console.log('Executing task "Delete unverified users"');
const deadline = Date.now() - 1000 * 3600 * 24;
const deadline = Date.now() - 1000 * 3600 * 24;
User.deleteMany({
verified: false,
joinedDate: {$lte: deadline}
});
User.deleteMany({
verified: false,
joinedDate: {$lte: deadline}
});
}, 1000 * 60 * 30);
/**
* src/bootstrap/smtp.js
*
* Email transport creator
* @file Email transport creator.
* @author Felix Kopp <sandtler@sandtler.club>
*
* TODO: Include this export in a global variable so that we don't need to
* reconnect every time we want to use it
......@@ -29,28 +28,30 @@ const appConfig = require('../config/app.js');
const fs = require('fs');
const transportOptions = {
host: config.host,
port: config.port,
secure: config.secure,
auth: {
user: config.user,
pass: config.pass
}
// TODO: add config option in src/config/smtp.js for this
// debug: true,
// logger: true
host: config.host,
port: config.port,
secure: config.secure,
auth: {
user: config.user,
pass: config.pass
}
// TODO: add config option in src/config/smtp.js for this
// debug: true,
// logger: true
};
// DKIM config is optional for development and will be disabled if we omit the
// parameter for nodemailer
if (config.dkim) {
transportOptions.dkim = {
domainName: config.dkim.domainName,
keySelector: config.dkim.keySelector,
privateKey: fs.readFileSync(config.dkim.keyFile, 'utf8')
};
transportOptions.dkim = {
domainName: config.dkim.domainName,
keySelector: config.dkim.keySelector,
privateKey: fs.readFileSync(config.dkim.keyFile, 'utf8')
};
} else if (appConfig.env === 'production') {
console.warn('Runtime environment set to production, but DKIM is disabled');
console.warn(
'Runtime environment set to production, but DKIM is disabled'
);
}
const transport = nodemailer.createTransport(transportOptions);
......@@ -58,18 +59,17 @@ const transport = nodemailer.createTransport(transportOptions);
// Only test the connection if we are in production mode to not flood the
// development console
if (appConfig.env === 'production') {
transport.verify()
.then(success => {
if (!success) {
console.error('Cannot instantiate the SMTP connection!');
} else {
console.log('SMTP connected');
}
})
.catch(error => {
console.log('Cannot connect to the SMTP server');
console.error(error);
});
transport.verify()
.then(success => {
if (!success)
console.error('Cannot instantiate the SMTP connection!');
else
console.log('SMTP connected');
})
.catch(error => {
console.log('Cannot connect to the SMTP server');
console.error(error);
});
}
global.DeVid.smtpTransport = transport;
......
/**
* src/bootstrap/urls.js
*
* Generate URLs for frontend and backend, and store them in a global variable.
* @file Generate URL strings, and store them in a global variable.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -23,19 +22,19 @@
const appConfig = require('../config/app.js');
['frontend', 'backend'].forEach(key => {
global[key] = appConfig[key];
global[key] = appConfig[key];
let port;
const isDefaultPort = (
appConfig[key].proto === 'http' && appConfig[key].port === 80
|| appConfig[key].proto === 'https' && appConfig[key].port === 443
);
let port;
if (
appConfig[key].proto === 'http' && appConfig[key].port === 80 ||
appConfig[key].proto === 'https' && appConfig[key].port === 443
) {
port = '';
} else {
port = ':' + appConfig[key].port.toString();
}
if (isDefaultPort)
port = '';
else
port = ':' + appConfig[key].port.toString();
global[key].fullPath =
appConfig[key].proto + '://' +
appConfig[key].host + port + '/';
global[key].fullPath =
`${appConfig[key].proto}://${appConfig[key].host}${port}/`;
});
......@@ -29,22 +29,39 @@
* is not a priority at the moment, however.
*/
module.exports = {
env: process.env.NODE_ENV || 'production', // environment (see note above)
locale: 'de', // ISO 3166-1 Alpha-2 locale name
appName: 'DEVid', // Application name
supportEmail: 'devid@sandtler.club', // Support email address
// TODO: Use URIParser instead of this string concatenation mess
backend: {
proto: 'https', // Backend protocol
host: 'backend.devid.sandtler.club', // Backend host name
port: 443 // Backend port
},
frontend: {
proto: 'https', // Frontend protocol
host: 'devid.sandtler.club', // Frontend host name
port: 443 // Frontend port
},
port: 4201, // Listening port
signingKey: 's3cr3t' // Signature key for web tokens
/**
* The main app configuration object.
*
* @type {Object}
* @prop {string} [env='production'] The environment (either ).
* @prop {string} [locale='de'] The ISO 3166-1 Alpha-2 locale name to use.
* @prop {string} appName The app name.
* @prop {string} supportEmail The support email address.
* @prop {Object} backend The backend URL.
* @prop {Object} frontend The frontend URL.
* @prop {number} port The port to listen on. This will usually get
* proxied in the production environment.
* @prop {string} signingKey The signature key for JSON web tokens.
* This should be considered critically sensitive data.
*/
const APP_CONFIG = {
env: process.env.NODE_ENV || 'production', // environment (see note above)
locale: 'de', // ISO 3166-1 Alpha-2 locale name
appName: 'DEVid', // Application name
supportEmail: 'devid@sandtler.club', // Support email address
// TODO: Use URIParser instead of this string concatenation mess
backend: {
proto: 'https', // Backend protocol
host: 'backend.devid.sandtler.club', // Backend host name
port: 443 // Backend port
},
frontend: {
proto: 'https', // Frontend protocol
host: 'devid.sandtler.club', // Frontend host name
port: 443 // Frontend port
},
port: 4201, // Listening port
signingKey: 's3cr3t' // Signature key for web tokens
};
module.exports = APP_CONFIG;
/**
* src/config/db.default.js
*
* Sample configuration data for database connection.
* @file Sample configuration data for database connection.
* To be renamed or copied to `db.js` and alterred accordingly.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -22,16 +21,16 @@
*/
module.exports = {
host: 'localhost', // hostname or IP
port: 27017, // port
database: 'devid', // database
host: 'localhost', // hostname or IP
port: 27017, // port
database: 'devid', // database
// Only required if the database server requires
// authentication (which is the case in production)
user: 'devid', // username
password: 'changeme', // password
authSource: 'devid',
// Only required if the database server requires
// authentication (which is the case in production)
user: 'devid', // username
password: 'changeme', // password
authSource: 'devid',
// Set to false if your dev environment does not use SSL
ssl: true
// Set to false if your dev environment does not use SSL
ssl: true
};
/**
* src/config/smtp.default.js
*
* Sample configuration data for email transport.
* @file Sample configuration data for email transport.
* To be renamed or copied to `smtp.js` and alterred accordingly.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -22,9 +21,9 @@
*/
module.exports = {
host: 'smtp.sandtler.club', // SMTP host name
port: 465, // SMTP port
user: 'username', // SMTP username
pass: 'changeme', // SMTP password
secure: true // Whether to use TLS
host: 'smtp.sandtler.club', // SMTP host name
port: 465, // SMTP port
user: 'username', // SMTP username
pass: 'changeme', // SMTP password
secure: true // Whether to use TLS
};
#!/usr/bin/env nodejs
/**
* src/index.js
*
* App entry point
* @file App entry point
* @author Felix Kopp <sandtler@santler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -22,9 +21,9 @@
*/
console.log(
'DEvid Backend v' +
require('../package.json').version +
'\n'
'DEvid Backend v' +
require('../package.json').version +
'\n'
);
console.log('Loading libraries, please wait...');
......@@ -55,8 +54,4 @@ console.log('Listening on port ' + port.toString());
require('./bootstrap/scheduler.js');
console.log(
'Ready! Bootstrap done in ' +
(Date.now() - timeOnStart).toString() +
'ms.'
);
console.log(`Ready! Bootstrap done in ${Date.now() - timeOnStart} ms.`);
/**
* src/lib/conversions.js
*
* A collection of common conversion functions
* @file A collection of common conversion functions.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -38,23 +37,18 @@ const underscoreRegex = /_/g;
* (or `null` if `objectId` was invalid)
*/
function objectIdToVideo(objectId) {
if (typeof objectId === 'string') {
if (objectId.length !== 12) {
return null;
if (typeof objectId === 'string') {
if (objectId.length !== 12)
return null;
} else {
if (!(objectId instanceof ObjectId))
return null;
}
} else {
if (!(objectId instanceof ObjectId)) {
return null;
}
objectId = objectId.toString();
}
const base64String = Buffer.from(objectId, 'hex').toString('base64');
return base64String.replace(plusRegex, '-').replace(slashRegex, '_');
const base64String = Buffer.from(objectId, 'hex').toString('base64');
return base64String.replace(plusRegex, '-').replace(slashRegex, '_');
}
......@@ -67,18 +61,17 @@ function objectIdToVideo(objectId) {
* (or `null` if `videoId` was invalid)
*/
function videoToObjectId(videoId) {
if (typeof videoId !== 'string' || videoId.length !== 16) {
return null;
}
if (typeof videoId !== 'string' || videoId.length !== 16)
return null;
videoId = videoId.replace(dashRegex, '+').replace(underscoreRegex, '/');
const hexString = Buffer.from(videoId, 'base64').toString('hex');
videoId = videoId.replace(dashRegex, '+').replace(underscoreRegex, '/');
const hexString = Buffer.from(videoId, 'base64').toString('hex');
return new ObjectId(hexString);
return new ObjectId(hexString);
}
module.exports = {
objectIdToVideo: objectIdToVideo,
videoToObjectId: videoToObjectId
objectIdToVideo: objectIdToVideo,
videoToObjectId: videoToObjectId
};
/**
* src/lib/db/model/channel.js
*
* Channel database model for Mongoose
* @file Channel database model for Mongoose
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......
/**
* src/lib/db/model/comment.js
*
* Comment database model for Mongoose
* @file Comment database model for Mongoose
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......
/**
* src/lib/db/model/user.js
*
* User database model for Mongoose
* @file User database model for Mongoose.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......
/**
* src/lib/db/model/video.js
*
* Video database model for Mongoose
* @file Video database model for Mongoose
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......
/**
* src/lib/db/password-hash.js
*
* Password hashing utility using Node's native APIs
* @file Password hashing utility using Node's native APIs.
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -31,60 +30,47 @@ const crypto = require('crypto');
* @returns {Promise} A promise for the hashed password
*/
function passwordHash(password, salt) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
hash.on('readable', () => {
const data = hash.read();
hash.on('readable', () => {
const data = hash.read();
if (data)
resolve(data);
else
reject();
});
if (data) {
resolve(data);
} else {
reject();
}
// Start the hashing process
hash.write(password + salt);
hash.end();
});
// Start the hashing process
hash.write(password + salt);
hash.end();
});
}
/**
* Check a password for validity.
*
* @param {string} password The password
* @param {string} salt The salt
* @param {Buffer} hash The hash to check against
* @returns {Promise} A Promise for a boolean being `true` if the
* hashes match and `false` otherwise.
* @param {string} password The password.
* @param {string} salt The salt.
* @param {Buffer} hash The hash to check against.
* @returns {Promise<boolean>} A Promise for a boolean being `true` if the
* hashes match and `false` otherwise.
*/
function passwordCheck(password, salt, hash) {
return new Promise(async (resolve, reject) => {
try {
const checkHash = await passwordHash(password, salt);
resolve(Buffer.compare(hash, checkHash) === 0);
} catch (err) {
reject();
}
});
return new Promise(async (resolve, reject) => {
try {
const checkHash = await passwordHash(password, salt);
resolve(Buffer.compare(hash, checkHash) === 0);
} catch (err) {
reject();
}
});
}
module.exports = {
check: passwordCheck,
hash: passwordHash
check: passwordCheck,
hash: passwordHash
};
/**
* src/lib/db/schema/channel.js
*
* Channel database schema for Mongoose
* @file Channel database schema for Mongoose.
* @author Felix Kopp <sandtler@sandtler.club>
*
* NOTE: I don't know if it's reasonable to separate channel data from the
* actual user account data. This file may likely be removed.
......@@ -28,9 +27,9 @@ const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;
const channelSchema = new Schema({
_id: ObjectId,
description: String,
subscriberCount: Number
_id: ObjectId,
description: String,
subscriberCount: Number
});
module.exports = channelSchema;
/**
* src/lib/db/schema/comment.js
*
* Comment database schema for Mongoose
* @file Comment database schema for Mongoose
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -25,14 +24,14 @@ const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;
const commentSchema = new Schema({
_id: ObjectId,
user_id: ObjectId,
video_id: ObjectId,
content: String,
time: {
type: Date,
default: Date.now
}
_id: ObjectId,
user_id: ObjectId,
video_id: ObjectId,
content: String,
time: {
type: Date,
default: Date.now
}
});
module.exports = commentSchema;
/**
* src/lib/db/schema/settings.js
*
* User settings database schema (child of `user`)
* @file User settings database schema (child of `user`).
* @author Felix Kopp <sandtler@sandtler.club>
*
* @license
* Copyright (c) 2019 Felix Kopp <sandtler@420blaze.it>
......@@ -25,24 +24,24 @@ const Schema = mongoose.Schema;
const ObjectId = mongoose.Schema.Types.ObjectId;
const settingsSchema = new Schema({
// If true, display the profile picture publicly
showPP: {
type: Boolean,
default: true
},
// If true, make it rain newsletters right into the spambox
newsletter: {
type: Boolean,
default: false // I just don't need this level of tryhard my friends
}
// If true, display the profile picture publicly
showPP: {
type: Boolean,
default: true
},
// If true, make it rain newsletters right into the spambox
newsletter: {
type: Boolean,
default: false // I just don't need this level of tryhard my friends
}
}, { _id: false });
// TODO: Merge schema and model for all tables in one file
settingsSchema.methods.toClientJSON = function() {
return {
newsletter: this.newsletter,
showPP: this.showPP
};
return {
newsletter: this.newsletter,
showPP: this.showPP
};
};