├── public └── example.json ├── .gitignore ├── readme.md ├── views ├── post.html ├── addpost.html ├── login.html └── index.html ├── config ├── config.json └── index.js ├── models ├── index.js ├── post.js └── user.js ├── controllers ├── index.js ├── home.js ├── post.js └── auth.js ├── bin ├── index.js ├── rt.js ├── errorHandler.js ├── master.js ├── dbinit.js └── worker.js ├── package.json └── logger └── index.js /public/example.json: -------------------------------------------------------------------------------- 1 | {"example":true} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /database/ 2 | /doc/ 3 | /node_modules/ 4 | /.vscode/ -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Что это? 2 | Смотрите [здесь](https://habrahabr.ru/post/314394/) -------------------------------------------------------------------------------- /views/post.html: -------------------------------------------------------------------------------- 1 | {{#post}} 2 |

{{title}}

3 |
4 | {{text}} 5 | {{/post}} 6 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port":3000, 3 | "mongoUri":"mongodb://127.0.0.1/armleodb" 4 | } 5 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | 3 | config.port = config.port || 8080; 4 | 5 | module.exports = config; -------------------------------------------------------------------------------- /views/addpost.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /views/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | {{#user}} 2 | Hello {{username}} 3 | {{/user}} 4 | {{^user}} 5 | Login here! 6 | {{/user}} 7 | {{#posts}} 8 |
{{title}} 9 | {{/posts}} 10 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Загрузить модель юзера (пользователя) 3 | // На *nix-ах все файлы чуствительны к регистру 4 | User:require('./user'), 5 | Post:require('./post') 6 | }; 7 | // Не забудем точку с запЕтой! -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | const Logger = require('../logger'); 2 | const logger = new Logger(); 3 | 4 | let app = new (require('express').Router)(); 5 | 6 | app.use(require('./auth')); 7 | app.use(require('./home')); 8 | app.use(require('./post')); 9 | 10 | module.exports = app; -------------------------------------------------------------------------------- /controllers/home.js: -------------------------------------------------------------------------------- 1 | let app = new (require('express').Router)(); 2 | const models = require('../models'); 3 | 4 | 5 | app.get('/',(req,res,next)=>{ 6 | //Создадим новый handler который сидит по пути `/` 7 | models.Post.find({}).exec().then((posts)=>{ 8 | 9 | res.render('index',{ 10 | user:req.user, 11 | posts 12 | }); 13 | // Отправим рендер образа под именем index 14 | }).catch(next); 15 | }); 16 | 17 | module.exports = app; 18 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | // Project/index.js 2 | process.stdout.isTTY = true; 3 | // Заставим думать node.js что мой любимый git bash это консоль! 4 | // Смотрите https://github.com/nodejs/node/issues/3006 5 | 6 | const cluster = require('cluster'); 7 | // загрузим кластер 8 | if(cluster.isMaster) 9 | { 10 | // если мы <> то запустим код из ветки мастер 11 | require('./master'); 12 | } 13 | else 14 | { 15 | // Если мы worker запустим код из ветки для worker-a 16 | require('./worker'); 17 | } -------------------------------------------------------------------------------- /bin/rt.js: -------------------------------------------------------------------------------- 1 | const Logger = require('../logger'); 2 | const logger = new Logger(); 3 | 4 | module.exports = function(req,res,next) 5 | { 6 | // Засечь начало 7 | let beginTime = Date.now(); 8 | // В конце ответа 9 | res.on('finish',()=>{ 10 | let d = Date.now();// получить дату в мс 11 | logger.log('Reponse time: ' + (d - beginTime),{ 12 | url:req.url, // записать в лог куда пришел запрос (Включает urlencode string :) 13 | time:(d - beginTime) // сколько прошло времени 14 | }); 15 | }); 16 | // Передать действие другому обработчику 17 | next(); 18 | } -------------------------------------------------------------------------------- /bin/errorHandler.js: -------------------------------------------------------------------------------- 1 | const Logger = require('../logger'); 2 | const logger = new Logger(); // Загрузить логгер! 3 | // Все обработчики ошибок должны иметь 4 параметра, иначе они будут обычными контроллерами 4 | module.exports = function(err,req,res,next) 5 | { 6 | // err всегда установлен ибо Express.js проверяет была ли передана ошибка или нет, и вызывает обработчики только если ошибка есть; 7 | logger.error(err); 8 | // В дальнейшем мы будем отправлять ошибки по почте, записывать в файл и так далее. 9 | res.status(503).send(err.stack || err.message); 10 | // Здесь можно вызвать next() или самим сообщить об ошибке клиенту. 11 | // В будущем можно сделать страниц 503 с ошибкой 12 | }; -------------------------------------------------------------------------------- /controllers/post.js: -------------------------------------------------------------------------------- 1 | let app = new (require('express').Router)(); 2 | const models = require("../models"); 3 | 4 | app.get('/post', function(req,res,next) 5 | { 6 | if(!req.user) return res.redirect('/login'); 7 | res.render('addpost',{ 8 | user:req.user 9 | }); 10 | }); 11 | 12 | app.post('/post', function(req, res, next) 13 | { 14 | if(!req.user) return res.redirect('/login'); 15 | let post = new models.Post(req.body); 16 | post.save() 17 | .then(()=>{ 18 | res.redirect('/post/' + post.slug); 19 | }).catch(next); 20 | }); 21 | 22 | app.get('/post/:slug',(req, res, next)=>{ 23 | models.Post.findOne({ 24 | slug:req.params.slug 25 | }).exec().then((post)=>{ 26 | if(!post) res.redirect('/#notfound'); 27 | res.render('post',{ 28 | user:req.user, 29 | post 30 | }); 31 | }).catch(next); 32 | }); 33 | 34 | module.exports = app; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "habrahabr", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon bin/index" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "apidoc": "^0.17.6", 13 | "bluebird": "^3.5.1", 14 | "body-parser": "^1.18.2", 15 | "busboy": "^0.2.14", 16 | "connect-mongo": "^2.0.0", 17 | "consolidate": "^0.14.5", 18 | "cookie-parser": "^1.4.3", 19 | "express": "^4.16.2", 20 | "express-session": "^1.15.6", 21 | "image-type": "^3.0.0", 22 | "mongoose": "^4.12.1", 23 | "mongoose-unique-validator": "^1.0.6", 24 | "mongoose-url-slugs": "^1.0.2", 25 | "mustache": "^2.3.0", 26 | "nodemon": "^1.12.1", 27 | "passport": "^0.4.0", 28 | "passport-local": "^1.0.0", 29 | "request": "^2.83.0", 30 | "request-promise-native": "^1.0.5", 31 | "stack-trace": "0.0.10" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bin/master.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | // Загрузим нативный модуль cluster 3 | const CPUCount = 1; 4 | // Получим количество ядер процессора 5 | // Создание дочернего процесса требует много ресурсов. Поэтому в связке с 8 ядерным сервером и Nodemon-ом дает адские лаги при сохранении. 6 | // Рекомендую при активной разработке ставить CPUCount в 1 иначе вы будете страдать как я.... 7 | const Logger = require('../logger'); 8 | const logger = new Logger(); // Загрузить логгер! 9 | cluster.on('disconnect', (worker, code, signal) => { 10 | // В случае отключения IPC запустить нового рабочего (мы узнаем про это подробнее далее) 11 | logger.log(`Worker ${worker.id} died`); 12 | // запишем в лог отключение сервера, что бы разработчики обратили внимание. 13 | cluster.fork(); 14 | // Создадим рабочего 15 | }); 16 | 17 | cluster.on('online', (worker) => { 18 | //Если рабочий соединился с нами запишем это в лог! 19 | logger.log(`Worker ${worker.id} running`); 20 | }); 21 | // Создадим рабочих в количестве CPUCount 22 | for(let i = 0; i < CPUCount; ++i) 23 | { 24 | cluster.fork(); // Родить рабочего! :) 25 | } -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | // Загрузим mongoose т.к. нам требуется несколько классов или типов для нашей модели 2 | const mongoose = require('mongoose'); 3 | const URLSlugs = require('mongoose-url-slugs'); 4 | // Создаем новую схему! 5 | let postSchema = new mongoose.Schema({ 6 | title:{ 7 | type:String, // тип: String 8 | required:[true,"titleRequired"], 9 | // Данное поле обязательно. Если его нет вывести ошибку с текстом titleRequired 10 | // Максимальная длинна 32 Юникод символа (Unicode symbol != byte) 11 | minlength:[6,"tooShort"], 12 | unique:true // Оно должно быть уникальным 13 | }, 14 | text:{ 15 | type:String, // тип String 16 | 17 | required:[true,"textRequired"] 18 | // Думаю здесь все тоже очевидно 19 | }, 20 | // Здесь будут и другие поля, но сейчас еще рано их сюда ставить! 21 | // Например коментарии 22 | // Оценки 23 | // и тд 24 | 25 | // slug:String 26 | }); 27 | 28 | // Теперь подключим плагины (внешнии модули) 29 | 30 | // Подключим генератор на основе названия 31 | postSchema.plugin(URLSlugs('title')); 32 | 33 | // Компилируем и Экспортируем модель 34 | module.exports = mongoose.model('Post',postSchema); 35 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | // Загрузим mongoose т.к. нам требуется несколько классов или типов для нашей модели 2 | const mongoose = require('mongoose'); 3 | // Создаем новую схему! 4 | let userSchema = new mongoose.Schema({ 5 | // Логин 6 | username:{ 7 | type:String, // тип: String 8 | required:[true,"usernameRequired"], 9 | // Данное поле обязательно. Если его нет вывести ошибку с текстом usernameRequired 10 | maxlength:[32,"tooLong"], 11 | // Максимальная длинна 32 Юникод символа (Unicode symbol != byte) 12 | minlength:[6,"tooShort"], 13 | // Слишком короткий Логин! 14 | match:[/^[a-z0-9]+$/,"usernameIncorrect"], 15 | // Мой любимй формат! ЗАПРЕТИТЬ НИЖНЕЕ ТИРЕ! 16 | unique:true // Оно должно быть уникальным 17 | }, 18 | // Пароль 19 | password:{ 20 | type:String, // тип String 21 | // В дальнейшем мы добавим сюда хеширование 22 | maxlength:[32,"tooLong"], 23 | minlength:[8, "tooShort"], 24 | match:[/^[A-Za-z0-9]+$/,"passwordIncorrect"], 25 | required:[true,"passwordRequired"] 26 | // Думаю здесь все уже очевидно 27 | }, 28 | // Здесь будут и другие поля, но сейчас еще рано их сюда ставить! 29 | }); 30 | 31 | // Теперь подключим плагины (внешнии модули) 32 | 33 | 34 | 35 | // Компилируем и Экспортируем модель 36 | module.exports = mongoose.model('User',userSchema); 37 | -------------------------------------------------------------------------------- /bin/dbinit.js: -------------------------------------------------------------------------------- 1 | // Инициализация датабазы! 2 | // Загрузим mongoose 3 | const mongoose = require('mongoose'); 4 | // Заменим библиотеку Обещаний (Promise) которая идет в поставку с mongoose (mpromise) 5 | mongoose.Promise = require('bluebird'); 6 | // На Bluebird 7 | const Logger = require('../logger'); 8 | const logger = new Logger(); // Загрузить логгер! 9 | 10 | // Подключимся к серверу MongoDB 11 | const config = require('../config'); 12 | mongoose.connect(config.mongoUri, { 13 | useMongoClient:true, 14 | poolSize: 10 15 | // Поставим количество подключений в пуле 16 | // 10 рекомендуемое количество для моего проекта. 17 | // Вам возможно понадобится и то меньше... 18 | }); 19 | 20 | // В случае ошибки будет вызвано данная функция 21 | mongoose.connection.on('error',(err)=> 22 | { 23 | logger.error("Database Connection Error: " + err); 24 | // Скажите админу пусть включит MongoDB сервер :) 25 | logger.error('Админ сервер MongoDB Запусти!'); 26 | process.exit(2); 27 | }); 28 | 29 | // Данная функция будет вызвано когда подключение будет установлено 30 | mongoose.connection.on('connected',()=> 31 | { 32 | // Подключение установлено 33 | logger.info("Succesfully connected to MongoDB Database"); 34 | // В дальнейшем здесь мы будем запускать сервер. 35 | }); 36 | require('./../models'); // Попытка проиницилизировать модели (скомпилировать) 37 | -------------------------------------------------------------------------------- /controllers/auth.js: -------------------------------------------------------------------------------- 1 | let app = new (require('express').Router)(); 2 | // Создадим роутер 3 | const models = require('./../models'); 4 | // Загрузим модели 5 | const passport = require('passport'); 6 | const LocalStrategy = require('passport-local').Strategy; 7 | 8 | app.use(passport.initialize()); 9 | app.use(passport.session()); 10 | 11 | // Инициализация паспорта 12 | 13 | passport.use(new LocalStrategy( 14 | function(username, password, done) { 15 | models.User.findOne({ username: username }, function (err, user) { 16 | if (err) { return done(err); } 17 | if (!user) { 18 | return done(null, false, { message: 'Incorrect username.' }); 19 | } 20 | if (user.password != password) { 21 | return done(null, false, { message: 'Incorrect password.' }); 22 | } 23 | return done(null, user); 24 | }); 25 | } 26 | )); 27 | // Сериализация паспорта 28 | passport.serializeUser(function(user, done) { 29 | done(null, user._id); 30 | }); 31 | // ДеСериализация паспорта 32 | passport.deserializeUser(function(id, done) { 33 | models.User.findById(id, function(err, user) { 34 | done(err, user); 35 | }); 36 | }); 37 | // Конттроллер 38 | app.post('/login', 39 | passport.authenticate('local', { 40 | successRedirect: '/', 41 | failureRedirect: '/login' 42 | }) 43 | ); 44 | // Образ 45 | app.get('/login',function(req,res,next) 46 | { 47 | if(req.user) return res.redirect('/'); 48 | // Пропускать только неафторизованных 49 | res.render('login',{ 50 | user:req.user 51 | }); 52 | }); 53 | 54 | module.exports = app; 55 | -------------------------------------------------------------------------------- /logger/index.js: -------------------------------------------------------------------------------- 1 | const stackTrace = require('stack-trace'); // Для получения имени родительского модуля 2 | const util = require('util'); //util.inspect() 3 | const path = require('path'); //path.relative() path.sep 4 | const projectname = require('../package').name; //package.json -> project name 5 | 6 | module.exports = class Logger // Класс логера :) 7 | { 8 | constructor() 9 | { 10 | function generateLogFunction(level) // Функция генератор функий логгера :) 11 | { 12 | return function(message,meta) 13 | { 14 | //let d = Date.now(); // Будем потом записовать время вызова 15 | let mes = this.module + " -- "; 16 | mes += level + " -- "; 17 | mes += message; // прицепить сообщение 18 | if(meta) mes += " " + util.inspect(meta) + " "; // Записать доп инфу (Object||Error) 19 | mes += '\n'; // Конец строки :) 20 | 21 | this.write(mes); 22 | // Записать во все потоки наше сообщение 23 | } 24 | }; 25 | 26 | this.trace = stackTrace.get()[1]; // Получить стек вызова 27 | this.filename = this.trace.getFileName(); // Получить имя файла которое вызвало конструктор 28 | this.module = projectname + path.sep + path.relative(__dirname + "/..",this.filename); // Записать име модуля 29 | this.streams = [process.stdout]; // Потоки в которые мы будем записовать логи 30 | // В дальнейшем здесь будет стрим к файлу 31 | this.log = generateLogFunction('Log'); // Лог поведения 32 | this.info = generateLogFunction('Info'); // Лог информативный 33 | this.error = generateLogFunction('Error'); // Лог ошибок 34 | this.warn = generateLogFunction('Warning'); // Лог предупреждений 35 | } 36 | write(d) 37 | { 38 | this.streams.forEach((stream)=>{ 39 | stream.write(d); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bin/worker.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const bodyParser = require('body-parser'); 4 | const session = require('express-session'); // Сессии 5 | const MongoStore = require('connect-mongo')(session); // Хранилище сессий в монгодб 6 | const cons = require('consolidate'); 7 | const config = require('../config'); 8 | const Logger = require('../logger'); 9 | const logger = new Logger(); // Загрузить логгер! 10 | require('./dbinit'); // Инициализация датабазы 11 | // Загрузим express 12 | let app = express(); 13 | // Создадим новый сервер 14 | // Время ответа 15 | app.use(require('./rt')); 16 | // Промонтировать файлы из project/public в наш сайт по адресу /public 17 | app.use('/public',express.static(path.join(__dirname,'../public'))); 18 | 19 | 20 | // Парсер Куки! 21 | app.use(require('cookie-parser')()); 22 | // Теперь сессия 23 | // поставить хендлер для сессий 24 | app.use(session({ 25 | secret: 'Химера Хирера', 26 | // Замените на что нибудь 27 | resave: false, 28 | // Пересохранять даже если нету изменений 29 | saveUninitialized: true, 30 | // Сохранять пустые сессии 31 | store: new MongoStore({ mongooseConnection: require('mongoose').connection }) 32 | // Использовать монго хранилище 33 | })); 34 | // JSON Парсер :) 35 | app.use(bodyParser.json({ 36 | limit:"10kb" 37 | })); 38 | app.use(bodyParser.urlencoded({ 39 | extended:true 40 | })); 41 | // Используем движок усов 42 | app.engine('html', cons.mustache); 43 | // установить движок рендеринга 44 | app.set('view engine', 'html'); 45 | // папка с образами 46 | app.set('views', __dirname + '/../views'); 47 | 48 | app.use(require('./../controllers')); // Монтируем контроллеры! 49 | 50 | 51 | // Обработчик ошибок 52 | app.use(require('./errorHandler')); 53 | 54 | 55 | // Запустим сервер на порту 3000 и сообщим об этом в консоли. 56 | // Все Worker-ы должны иметь один и тот же порт 57 | app.listen(config.port, function(err){ 58 | if(err) throw err; 59 | // Если есть ошибка сообщить об этом 60 | logger.log(`Running server at port ${config.port}!`); 61 | // Иначе сообщить что мы успешно соединились с мастером 62 | // И ждем сообщений от клиентов 63 | }); 64 | --------------------------------------------------------------------------------