├── 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 |
8 |
--------------------------------------------------------------------------------
/views/login.html:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------