Base Project Files

This commit is contained in:
NGPixel 2016-08-16 20:56:55 -04:00
parent fc939dda50
commit b035a68ca4
20 changed files with 727 additions and 0 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["es2015"]
}

17
.gitattributes vendored Normal file
View File

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

9
.gitignore vendored
View File

@ -26,6 +26,9 @@ coverage
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Deployment builds
dist
# Dependency directories
node_modules
jspm_packages
@ -35,3 +38,9 @@ jspm_packages
# Optional REPL history
.node_repl_history
# SublimeText Files
*.sublime-workspace
# Config Files
config.yml

4
.snyk Normal file
View File

@ -0,0 +1,4 @@
failThreshold: high
version: v1.5.2
ignore: {}
patch: {}

30
.travis.yml Normal file
View File

@ -0,0 +1,30 @@
language: node_js
node_js:
- '6'
- '5'
- '4.4'
services:
- redis-server
- mongodb
cache:
directories:
- node_modules
before_script:
- npm install -g snyk
before_deploy:
- npm install -g gulp
- gulp deploy
- snyk auth $SNYK_TOKEN
- snyk monitor
deploy:
provider: releases
file:
- dist/requarks-wiki.zip
- dist/requarks-wiki.tar.gz
skip_cleanup: true
overwrite: true
on:
branch: master
repo: requarks/wiki
tags: true
node: '6'

54
config.sample.yml Normal file
View File

@ -0,0 +1,54 @@
###################################################
# REQUARKS WIKI - CONFIGURATION #
###################################################
# -------------------------------------------------
# Title of this site
# -------------------------------------------------
title: Wiki
# -------------------------------------------------
# Full path to the site, without the trailing slash
# -------------------------------------------------
host: http://localhost
# -------------------------------------------------
# Port the server should listen to (80 by default)
# -------------------------------------------------
# To use process.env.PORT, comment the line below:
port: 80
# -------------------------------------------------
# MongoDB Connection String
# -------------------------------------------------
# Full explanation + examples in the documentation (https://opsstatus.readme.io/)
db: mongodb://localhost/wiki
# -------------------------------------------------
# Redis Connection Info
# -------------------------------------------------
# Full explanation + examples in the documentation (https://opsstatus.readme.io/)
redis:
host: localhost
port: 6379
db: 0
# -------------------------------------------------
# Secret key to use when encrypting sessions
# -------------------------------------------------
# Use a long and unique random string (256-bit keys are perfect!)
sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz
# -------------------------------------------------
# Administrator email
# -------------------------------------------------
# An account will be created using the email specified here.
# The password is set to "admin123" by default. Change it immediately upon login!!!
admin: admin@company.com

94
gulpfile.js Normal file
View File

@ -0,0 +1,94 @@
var gulp = require("gulp");
var merge = require('merge-stream');
var babel = require("gulp-babel");
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var nodemon = require('gulp-nodemon');
var plumber = require('gulp-plumber');
var zip = require('gulp-zip');
var tar = require('gulp-tar');
var gzip = require('gulp-gzip');
var sass = require('gulp-sass');
var cleanCSS = require('gulp-clean-css');
var include = require("gulp-include");
/**
* Paths
*
* @type {Object}
*/
var paths = {
scriptlibs: {
},
scriptapps: [
'./client/js/components/*.js',
'./client/js/app.js'
],
scriptappswatch: [
'./client/js/**/*.js'
],
csslibs: [
],
cssapps: [
'./client/css/app.scss'
],
cssappswatch: [
'./client/css/**/*.scss'
],
fonts: [
'./node_modules/font-awesome/fonts/*-webfont.*',
'!./node_modules/font-awesome/fonts/*-webfont.svg'
],
deploypackage: [
'./**/*',
'!node_modules', '!node_modules/**',
'!coverage', '!coverage/**',
'!client/js', '!client/js/**',
'!dist', '!dist/**',
'!tests', '!tests/**',
'!gulpfile.js', '!inch.json', '!config.json', '!wiki.sublime-project'
]
};
/**
* TASK - Starts server in development mode
*/
gulp.task('server', ['scripts', 'css', 'fonts'], function() {
nodemon({
script: './server',
ignore: ['public/', 'client/', 'tests/'],
ext: 'js json',
env: { 'NODE_ENV': 'development' }
});
});
/**
* TASK - Start dev watchers
*/
gulp.task('watch', function() {
gulp.watch([paths.scriptappswatch], ['scripts-app']);
gulp.watch([paths.cssappswatch], ['css-app']);
});
/**
* TASK - Starts development server with watchers
*/
gulp.task('default', ['watch', 'server']);
/**
* TASK - Creates deployment packages
*/
gulp.task('deploy', ['scripts', 'css', 'fonts'], function() {
var zipStream = gulp.src(paths.deploypackage)
.pipe(zip('requarks-wiki.zip'))
.pipe(gulp.dest('dist'));
var targzStream = gulp.src(paths.deploypackage)
.pipe(tar('requarks-wiki.tar'))
.pipe(gzip())
.pipe(gulp.dest('dist'));
return merge(zipStream, targzStream);
});

10
inch.json Normal file
View File

@ -0,0 +1,10 @@
{
"files": {
"included": [
"controllers/**/*.js",
"middlewares/**/*.js",
"models/**/*.js",
],
"excluded": []
}
}

11
locales/en/common.js Normal file
View File

@ -0,0 +1,11 @@
{
"wiki": "Wiki",
"headers": {
"overview": "Overview"
},
"footer": {
"poweredby": "Powered by",
"home": "Home",
"admin": "Administration"
}
}

11
locales/fr/common.js Normal file
View File

@ -0,0 +1,11 @@
{
"wiki": "Wiki",
"headers": {
"overview": "Vue d'ensemble"
},
"footer": {
"poweredby": "Propulsé par",
"home": "Accueil",
"admin": "Administration"
}
}

34
middlewares/auth.js Normal file
View File

@ -0,0 +1,34 @@
"use strict";
var Promise = require('bluebird'),
moment = require('moment-timezone');
/**
* Authentication middleware
*
* @param {Express Request} req Express Request object
* @param {Express Response} res Express Response object
* @param {Function} next Next callback function
* @return {any} void
*/
module.exports = (req, res, next) => {
// Is user authenticated ?
if (!req.isAuthenticated()) {
return res.redirect('/login');
}
// Set i18n locale
req.i18n.changeLanguage(req.user.lang);
res.locals.userMoment = moment;
res.locals.userMoment.locale(req.user.lang);
// Expose user data
res.locals.user = req.user;
return next();
};

28
middlewares/security.js Normal file
View File

@ -0,0 +1,28 @@
/**
* Security Middleware
*
* @param {Express Request} req Express request object
* @param {Express Response} res Express response object
* @param {Function} next next callback function
* @return {any} void
*/
module.exports = function(req, res, next) {
//-> Disable X-Powered-By
app.disable('x-powered-by');
//-> Disable Frame Embedding
res.set('X-Frame-Options', 'deny');
//-> Re-enable XSS Fitler if disabled
res.set('X-XSS-Protection', '1; mode=block');
//-> Disable MIME-sniffing
res.set('X-Content-Type-Options', 'nosniff');
//-> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge');
return next();
};

34
models/config.js Normal file
View File

@ -0,0 +1,34 @@
"use strict";
var fs = require('fs'),
yaml = require('js-yaml'),
_ = require('lodash');
/**
* Load Application Configuration
*
* @param {String} confPath Path to the configuration file
* @return {Object} Application Configuration
*/
module.exports = (confPath) => {
var appconfig = {};
try {
appconfig = yaml.safeLoad(fs.readFileSync(confPath, 'utf8'));
} catch (ex) {
winston.error(ex);
process.exit(1);
}
return _.defaultsDeep(appconfig, {
title: "Requarks Wiki",
host: "http://localhost",
port: process.env.PORT,
db: "mongodb://localhost/wiki",
redis: null,
sessionSecret: null,
admin: null
});
};

158
models/db/user.js Normal file
View File

@ -0,0 +1,158 @@
"use strict";
var modb = require('mongoose');
var bcrypt = require('bcryptjs-then');
var Promise = require('bluebird');
var _ = require('lodash');
/**
* User Schema
*
* @type {Object}
*/
var userSchema = modb.Schema({
email: {
type: String,
required: true,
index: true,
minlength: 6
},
password: {
type: String,
required: true
},
firstName: {
type: String,
required: true,
minlength: 1
},
lastName: {
type: String,
required: true,
minlength: 1
},
timezone: {
type: String,
required: true,
default: 'UTC'
},
lang: {
type: String,
required: true,
default: 'en'
},
rights: [{
type: String,
required: true
}]
},
{
timestamps: {}
});
/**
* VIRTUAL - Full Name
*/
userSchema.virtual('fullName').get(function() {
return this.firstName + ' ' + this.lastName;
});
/**
* INSTANCE - Validate password against hash
*
* @param {string} uPassword The user password
* @return {Promise<Boolean>} Promise with valid / invalid boolean
*/
userSchema.methods.validatePassword = function(uPassword) {
let self = this;
return bcrypt.compare(uPassword, self.password);
};
/**
* MODEL - Generate hash from password
*
* @param {string} uPassword The user password
* @return {Promise<String>} Promise with generated hash
*/
userSchema.statics.generateHash = function(uPassword) {
return bcrypt.hash(uPassword, 10);
};
/**
* MODEL - Create a new user
*
* @param {Object} nUserData User data
* @return {Promise} Promise of the create operation
*/
userSchema.statics.new = function(nUserData) {
let self = this;
return self.generateHash(nUserData.password).then((passhash) => {
return this.create({
_id: db.ObjectId(),
email: nUserData.email,
firstName: nUserData.firstName,
lastName: nUserData.lastName,
password: passhash,
rights: ['admin']
});
});
};
/**
* MODEL - Edit a user
*
* @param {String} userId The user identifier
* @param {Object} data The user data
* @return {Promise} Promise of the update operation
*/
userSchema.statics.edit = function(userId, data) {
let self = this;
// Change basic info
let fdata = {
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
timezone: data.timezone,
lang: data.lang,
rights: data.rights
};
let waitTask = null;
// Change password?
if(!_.isEmpty(data.password) && _.trim(data.password) !== '********') {
waitTask = self.generateHash(data.password).then((passhash) => {
fdata.password = passhash;
return fdata;
});
} else {
waitTask = Promise.resolve(fdata);
}
// Update user
return waitTask.then((udata) => {
return this.findByIdAndUpdate(userId, udata, { runValidators: true });
});
};
/**
* MODEL - Delete a user
*
* @param {String} userId The user ID
* @return {Promise} Promise of the delete operation
*/
userSchema.statics.erase = function(userId) {
return this.findByIdAndRemove(userId);
};
module.exports = modb.model('User', userSchema);

53
models/mongodb.js Normal file
View File

@ -0,0 +1,53 @@
"use strict";
var modb = require('mongoose'),
fs = require("fs"),
path = require("path"),
_ = require('lodash');
/**
* MongoDB module
*
* @param {Object} appconfig Application config
* @return {Object} Mongoose instance
*/
module.exports = function(appconfig) {
modb.Promise = require('bluebird');
let dbModels = {};
let dbModelsPath = path.join(ROOTPATH, 'models/db');
// Event handlers
modb.connection.on('error', (err) => {
winston.error('Failed to connect to MongoDB instance.');
});
modb.connection.once('open', function() {
winston.log('Connected to MongoDB instance.');
});
// Store connection handle
dbModels.connection = modb.connection;
dbModels.ObjectId = modb.Types.ObjectId;
// Load Models
fs
.readdirSync(dbModelsPath)
.filter(function(file) {
return (file.indexOf(".") !== 0);
})
.forEach(function(file) {
let modelName = _.upperFirst(_.split(file,'.')[0]);
dbModels[modelName] = require(path.join(dbModelsPath, file));
});
// Connect
dbModels.connectPromise = modb.connect(appconfig.db);
return dbModels;
};

41
models/redis.js Normal file
View File

@ -0,0 +1,41 @@
"use strict";
var Redis = require('ioredis'),
_ = require('lodash');
/**
* Redis module
*
* @param {Object} appconfig Application config
* @return {Redis} Redis instance
*/
module.exports = (appconfig) => {
let rd = null;
if(_.isArray(appconfig.redis)) {
rd = new Redis.Cluster(appconfig.redis, {
scaleReads: 'master',
redisOptions: {
lazyConnect: false
}
});
} else {
rd = new Redis(_.defaultsDeep(appconfig.redis), {
lazyConnect: false
});
}
// Handle connection errors
rd.on('error', (err) => {
winston.error('Failed to connect to Redis instance(s). [err-1]');
});
rd.on('node error', (err) => {
winston.error('Failed to connect to Redis instance(s). [err-2]');
});
return rd;
};

95
package.json Normal file
View File

@ -0,0 +1,95 @@
{
"name": "wiki",
"version": "1.0.0",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "server.js",
"scripts": {
"start": "node server",
"dev": "gulp",
"test": "snyk test && istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec ./tests/index.js && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage && rm -rf ./coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Requarks/wiki.git"
},
"keywords": [
"wiki",
"wikis",
"docs",
"documentation",
"markdown",
"guides"
],
"author": "Nicolas Giard",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/Requarks/wiki/issues"
},
"homepage": "https://github.com/Requarks/wiki#readme",
"engines": {
"node": ">=4.4.5"
},
"dependencies": {
"auto-load": "^2.1.0",
"bluebird": "^3.4.1",
"body-parser": "^1.15.2",
"compression": "^1.6.2",
"connect-flash": "^0.1.1",
"connect-redis": "^3.1.0",
"cookie-parser": "^1.4.3",
"express": "^4.14.0",
"express-brute": "^0.7.0-beta.0",
"express-brute-redis": "0.0.1",
"express-session": "^1.14.0",
"express-validator": "^2.20.8",
"gridlex": "^2.1.1",
"i18next": "^3.4.1",
"i18next-express-middleware": "^1.0.1",
"i18next-node-fs-backend": "^0.1.2",
"ioredis": "^2.3.0",
"js-yaml": "^3.6.1",
"lodash": "^4.15.0",
"markdown-it": "^7.0.1",
"moment": "^2.14.1",
"moment-timezone": "^0.5.5",
"mongoose": "^4.5.9",
"mongoose-delete": "^0.3.4",
"node-bcrypt": "0.0.1",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"pug": "^2.0.0-beta5",
"serve-favicon": "^2.3.0",
"simplemde": "^1.11.2",
"validator": "^5.5.0",
"validator-as-promised": "^1.0.2",
"winston": "^2.2.0"
},
"devDependencies": {
"babel-preset-es2015": "^6.13.2",
"chai": "^3.5.0",
"chai-as-promised": "^5.3.0",
"codacy-coverage": "^2.0.0",
"font-awesome": "^4.6.3",
"gridlex": "^2.1.1",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-clean-css": "^2.0.12",
"gulp-concat": "^2.6.0",
"gulp-gzip": "^1.4.0",
"gulp-include": "^2.3.1",
"gulp-nodemon": "^2.1.0",
"gulp-plumber": "^1.1.0",
"gulp-sass": "^2.3.2",
"gulp-tar": "^1.9.0",
"gulp-uglify": "^2.0.0",
"gulp-zip": "^3.2.0",
"istanbul": "^0.4.4",
"jquery": "^3.1.0",
"merge-stream": "^1.0.0",
"mocha": "^3.0.2",
"mocha-lcov-reporter": "^1.2.0",
"nodemon": "^1.10.0",
"snyk": "^1.18.0",
"vue": "^1.0.26"
}
}

18
server.js Normal file
View File

@ -0,0 +1,18 @@
// ===========================================
// REQUARKS WIKI
// 1.0.0
// Licensed under AGPLv3
// ===========================================
// ----------------------------------------
// Load modules
// ----------------------------------------
global.winston = require('winston');
winston.info('Requarks Wiki is initializing...');
global.ROOTPATH = __dirname;
var appconfig = require('./models/config')('./config.yml');
global.db = require('./models/db')(appconfig);
global.red = require('./models/redis')(appconfig);

11
tests/index.js Normal file
View File

@ -0,0 +1,11 @@
"use strict";
let path = require('path'),
fs = require('fs');
// ========================================
// Load global modules
// ========================================
global._ = require('lodash');
global.winston = require('winston');

12
wiki.sublime-project Normal file
View File

@ -0,0 +1,12 @@
{
"folders":
[
{
"file_exclude_patterns":
[
"wiki.sublime-project"
],
"path": "."
}
]
}