├── .gitignore ├── 06-rest-in-practice ├── end │ ├── .babelrc │ ├── package.json │ └── src │ │ ├── config │ │ ├── config.js │ │ └── database.js │ │ ├── controllers │ │ └── songsController.js │ │ ├── index.js │ │ ├── middlewares │ │ └── errors.js │ │ ├── models │ │ └── song.js │ │ ├── routes │ │ └── songs.js │ │ └── views │ │ └── error.pug └── start │ ├── .babelrc │ ├── package.json │ └── src │ ├── config │ ├── config.js │ └── database.js │ ├── index.js │ ├── middlewares │ └── errors.js │ └── views │ └── error.pug ├── 07-pagination ├── end │ ├── cursor-based-pagination │ │ ├── .babelrc │ │ ├── package.json │ │ └── src │ │ │ ├── config │ │ │ ├── config.js │ │ │ └── database.js │ │ │ ├── controllers │ │ │ └── songsController.js │ │ │ ├── index.js │ │ │ ├── middlewares │ │ │ └── errors.js │ │ │ ├── models │ │ │ └── song.js │ │ │ ├── routes │ │ │ └── songs.js │ │ │ └── views │ │ │ └── error.pug │ └── offset-based-pagination │ │ ├── .babelrc │ │ ├── package.json │ │ └── src │ │ ├── config │ │ ├── config.js │ │ └── database.js │ │ ├── controllers │ │ └── songsController.js │ │ ├── index.js │ │ ├── middlewares │ │ └── errors.js │ │ ├── models │ │ └── song.js │ │ ├── routes │ │ └── songs.js │ │ └── views │ │ └── error.pug └── start │ ├── .babelrc │ ├── package.json │ └── src │ ├── config │ ├── config.js │ └── database.js │ ├── controllers │ └── songsController.js │ ├── index.js │ ├── middlewares │ └── errors.js │ ├── models │ └── song.js │ ├── routes │ └── songs.js │ └── views │ └── error.pug ├── 08-sorting-filtering ├── end │ ├── .babelrc │ ├── package.json │ └── src │ │ ├── config │ │ ├── config.js │ │ └── database.js │ │ ├── controllers │ │ └── songsController.js │ │ ├── index.js │ │ ├── middlewares │ │ ├── errors.js │ │ └── filters │ │ │ └── songs.js │ │ ├── models │ │ └── song.js │ │ ├── routes │ │ └── songs.js │ │ └── views │ │ └── error.pug └── start │ ├── .babelrc │ ├── package.json │ └── src │ ├── config │ ├── config.js │ └── database.js │ ├── controllers │ └── songsController.js │ ├── index.js │ ├── middlewares │ └── errors.js │ ├── models │ └── song.js │ ├── routes │ └── songs.js │ └── views │ └── error.pug ├── 09-searching ├── end │ ├── .babelrc │ ├── package.json │ └── src │ │ ├── config │ │ ├── config.js │ │ └── database.js │ │ ├── controllers │ │ └── songsController.js │ │ ├── index.js │ │ ├── middlewares │ │ ├── errors.js │ │ └── filters │ │ │ └── songs.js │ │ ├── models │ │ └── song.js │ │ ├── routes │ │ └── songs.js │ │ └── views │ │ └── error.pug └── start │ ├── .babelrc │ ├── package.json │ └── src │ ├── config │ ├── config.js │ └── database.js │ ├── controllers │ └── songsController.js │ ├── index.js │ ├── middlewares │ ├── errors.js │ └── filters │ │ └── songs.js │ ├── models │ └── song.js │ ├── routes │ └── songs.js │ └── views │ └── error.pug └── 10-security ├── end ├── .babelrc ├── .env ├── package.json └── src │ ├── config │ ├── config.js │ ├── database.js │ └── passport.js │ ├── controllers │ ├── authController.js │ └── songsController.js │ ├── index.js │ ├── middlewares │ ├── auth.js │ ├── errors.js │ └── filters │ │ └── songs.js │ ├── models │ ├── song.js │ └── user.js │ ├── routes │ ├── auth.js │ └── songs.js │ └── views │ └── error.pug └── start ├── .babelrc ├── package.json └── src ├── config ├── config.js └── database.js ├── controllers └── songsController.js ├── index.js ├── middlewares ├── errors.js └── filters │ └── songs.js ├── models └── song.js ├── routes └── songs.js └── views └── error.pug /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_STORE -------------------------------------------------------------------------------- /06-rest-in-practice/end/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /06-rest-in-practice/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongoose": "^5.1.2", 19 | "mongoose-url-slugs": "^1.0.2", 20 | "pug": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.26.3", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-env": "^1.6.1", 28 | "rimraf": "^2.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const songs = await Song.find().sort({ createdAt: 'desc' }); 12 | return res.status(200).send({ data: songs }); 13 | }, 14 | 15 | async create(req, res) { 16 | const song = await new Song({ 17 | title: req.body.title 18 | }).save(); 19 | 20 | return res.status(201).send({ data: song, message: `Song was created` }); 21 | }, 22 | 23 | async update(req, res, next) { 24 | const song = await Song.find({ 'slug': req.params.slug }); 25 | if (!song) return next(); 26 | 27 | song.title = req.body.title; 28 | await song.save(); 29 | 30 | return res.status(200).send({ data: song, message: `Song was updated` }); 31 | }, 32 | 33 | async remove(req, res, next) { 34 | const song = await Song.findOne({ 'slug': req.params.slug }); 35 | if (!song) return next(); 36 | await song.remove(); 37 | 38 | return res.status(200).send({ message: `Song was removed` }); 39 | } 40 | } -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String 6 | }, { 7 | timestamps: true 8 | }); 9 | 10 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 11 | 12 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | 5 | export default () => { 6 | const api = Router(); 7 | 8 | // GET /songs/:slug 9 | api.get('/:slug', catchAsync(songsController.findOne)); 10 | 11 | // GET /songs 12 | api.get('/', catchAsync(songsController.findAll)); 13 | 14 | // POST /songs 15 | api.post('/', catchAsync(songsController.create)); 16 | 17 | // PUT /songs/:slug 18 | api.put('/:slug', catchAsync(songsController.update)); 19 | 20 | // DELETE /songs/:slug 21 | api.delete('/:slug', catchAsync(songsController.remove)); 22 | 23 | return api; 24 | } -------------------------------------------------------------------------------- /06-rest-in-practice/end/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /06-rest-in-practice/start/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /06-rest-in-practice/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongoose": "^5.1.2", 19 | "mongoose-url-slugs": "^1.0.2", 20 | "pug": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.26.3", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-env": "^1.6.1", 28 | "rimraf": "^2.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /06-rest-in-practice/start/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /06-rest-in-practice/start/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /06-rest-in-practice/start/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import { notFound, catchErrors } from './middlewares/errors'; 5 | import bodyParser from 'body-parser'; 6 | import register from 'babel-core/register'; 7 | import babelPolyfill from 'babel-polyfill'; 8 | 9 | // Connect to database 10 | import dbConfig from './config/database'; 11 | import mongoose from 'mongoose'; 12 | 13 | mongoose.connect(dbConfig.mongoUrl); 14 | mongoose.Promise = global.Promise; 15 | mongoose.connection.on('error', (err) => { 16 | console.log('Could not connect to the database. Exiting now...'); 17 | process.exit(); 18 | }); 19 | 20 | const app = express(); 21 | 22 | app.set('view engine', 'pug'); 23 | app.set('views', join(__dirname, 'views')); 24 | app.use(express.static('public')); 25 | app.use(bodyParser.urlencoded({ extended: false })); 26 | app.use(bodyParser.json()); 27 | 28 | // routes config 29 | // ... 30 | 31 | // errors handling 32 | app.use(notFound); 33 | app.use(catchErrors); 34 | 35 | // let's play! 36 | app.listen(config.server.port, () => { 37 | console.log(`Server is up!`); 38 | }); -------------------------------------------------------------------------------- /06-rest-in-practice/start/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /06-rest-in-practice/start/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongo-cursor-pagination": "^6.3.0", 19 | "mongoose": "^5.1.2", 20 | "mongoose-url-slugs": "^1.0.2", 21 | "pug": "^2.0.3" 22 | }, 23 | "devDependencies": { 24 | "babel-cli": "^6.26.0", 25 | "babel-core": "^6.26.3", 26 | "babel-plugin-transform-async-to-generator": "^6.24.1", 27 | "babel-polyfill": "^6.26.0", 28 | "babel-preset-env": "^1.6.1", 29 | "rimraf": "^2.6.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const songsPromise = Song.paginate({ 12 | limit: req.query.per_page || 2, 13 | previous: req.query.previous || null, 14 | next: req.query.next || null 15 | }); 16 | const countPromise = Song.count(); 17 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 18 | 19 | const links = {}; 20 | if (songs.hasNext) { 21 | links.next = `${req.protocol}://${req.get('host')}${req.path}?next=${songs.next}`; 22 | } 23 | if (songs.hasPrevious) { 24 | links.previous = `${req.protocol}://${req.get('host')}${req.path}?previous=${songs.previous}`; 25 | } 26 | res.links(links); 27 | res.set('total-count', count); 28 | 29 | return res.status(200).send( songs.results ); 30 | }, 31 | 32 | async create(req, res) { 33 | const song = await new Song({ 34 | title: req.body.title 35 | }).save(); 36 | 37 | return res.status(201).send({ data: song, message: `Song was created` }); 38 | }, 39 | 40 | async update(req, res, next) { 41 | const song = await Song.find({ 'slug': req.params.slug }); 42 | if (!song) return next(); 43 | 44 | song.title = req.body.title; 45 | await song.save(); 46 | 47 | return res.status(200).send({ data: song, message: `Song was updated` }); 48 | }, 49 | 50 | async remove(req, res, next) { 51 | const song = await Song.findOne({ 'slug': req.params.slug }); 52 | if (!song) return next(); 53 | await song.remove(); 54 | 55 | return res.status(200).send({ message: `Song was removed` }); 56 | } 57 | } -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | import mongoPagination from 'mongo-cursor-pagination'; 4 | 5 | const Song = mongoose.Schema({ 6 | title: String 7 | }, { 8 | timestamps: true 9 | }); 10 | 11 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 12 | Song.plugin(mongoPagination.mongoosePlugin); 13 | 14 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | 5 | export default () => { 6 | const api = Router(); 7 | 8 | // GET /songs/:slug 9 | api.get('/:slug', catchAsync(songsController.findOne)); 10 | 11 | // GET /songs 12 | api.get('/', catchAsync(songsController.findAll)); 13 | 14 | // POST /songs 15 | api.post('/', catchAsync(songsController.create)); 16 | 17 | // PUT /songs/:slug 18 | api.put('/:slug', catchAsync(songsController.update)); 19 | 20 | // DELETE /songs/:slug 21 | api.delete('/:slug', catchAsync(songsController.remove)); 22 | 23 | return api; 24 | } -------------------------------------------------------------------------------- /07-pagination/end/cursor-based-pagination/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongoose": "^5.1.2", 19 | "mongoose-url-slugs": "^1.0.2", 20 | "pug": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.26.3", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-env": "^1.6.1", 28 | "rimraf": "^2.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const offset = parseInt(req.query.offset) || 0; 12 | const per_page = parseInt(req.query.per_page) || 2; 13 | const songsPromise = Song.find().skip(offset).limit(per_page).sort({ createdAt: 'desc' }); 14 | const countPromise = Song.count(); 15 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 16 | return res.status(200).send({ data: songs, count }); 17 | }, 18 | 19 | async create(req, res) { 20 | const song = await new Song({ 21 | title: req.body.title 22 | }).save(); 23 | 24 | return res.status(201).send({ data: song, message: `Song was created` }); 25 | }, 26 | 27 | async update(req, res, next) { 28 | const song = await Song.find({ 'slug': req.params.slug }); 29 | if (!song) return next(); 30 | 31 | song.title = req.body.title; 32 | await song.save(); 33 | 34 | return res.status(200).send({ data: song, message: `Song was updated` }); 35 | }, 36 | 37 | async remove(req, res, next) { 38 | const song = await Song.findOne({ 'slug': req.params.slug }); 39 | if (!song) return next(); 40 | await song.remove(); 41 | 42 | return res.status(200).send({ message: `Song was removed` }); 43 | } 44 | } -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String 6 | }, { 7 | timestamps: true 8 | }); 9 | 10 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 11 | 12 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | 5 | export default () => { 6 | const api = Router(); 7 | 8 | // GET /songs/:slug 9 | api.get('/:slug', catchAsync(songsController.findOne)); 10 | 11 | // GET /songs 12 | api.get('/', catchAsync(songsController.findAll)); 13 | 14 | // POST /songs 15 | api.post('/', catchAsync(songsController.create)); 16 | 17 | // PUT /songs/:slug 18 | api.put('/:slug', catchAsync(songsController.update)); 19 | 20 | // DELETE /songs/:slug 21 | api.delete('/:slug', catchAsync(songsController.remove)); 22 | 23 | return api; 24 | } -------------------------------------------------------------------------------- /07-pagination/end/offset-based-pagination/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /07-pagination/start/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /07-pagination/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongoose": "^5.1.2", 19 | "mongoose-url-slugs": "^1.0.2", 20 | "pug": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.26.3", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-env": "^1.6.1", 28 | "rimraf": "^2.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /07-pagination/start/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /07-pagination/start/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /07-pagination/start/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | console.log(song); 7 | if (!song) return next(); 8 | return res.status(200).send({ data: song }); 9 | }, 10 | 11 | async findAll(req, res) { 12 | const songs = await Song.find().sort({ createdAt: 'desc' }); 13 | return res.status(200).send({ data: songs }); 14 | }, 15 | 16 | async create(req, res) { 17 | const song = await new Song({ 18 | title: req.body.title 19 | }).save(); 20 | 21 | return res.status(201).send({ data: song, message: `Song was created` }); 22 | }, 23 | 24 | async update(req, res, next) { 25 | const song = await Song.find({ 'slug': req.params.slug }); 26 | if (!song) return next(); 27 | 28 | song.title = req.body.title; 29 | await song.save(); 30 | 31 | return res.status(200).send({ data: song, message: `Song was updated` }); 32 | }, 33 | 34 | async remove(req, res, next) { 35 | const song = await Song.findOne({ 'slug': req.params.slug }); 36 | if (!song) return next(); 37 | await song.remove(); 38 | 39 | console.log(song); 40 | 41 | return res.status(200).send({ message: `Song was removed` }); 42 | } 43 | } -------------------------------------------------------------------------------- /07-pagination/start/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /07-pagination/start/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /07-pagination/start/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String 6 | }, { 7 | timestamps: true 8 | }); 9 | 10 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 11 | 12 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /07-pagination/start/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | 5 | export default () => { 6 | const api = Router(); 7 | 8 | // GET /songs/:slug 9 | api.get('/:slug', catchAsync(songsController.findOne)); 10 | 11 | // GET /songs 12 | api.get('/', catchAsync(songsController.findAll)); 13 | 14 | // POST /songs 15 | api.post('/', catchAsync(songsController.create)); 16 | 17 | // PUT /songs/:slug 18 | api.put('/:slug', catchAsync(songsController.update)); 19 | 20 | // DELETE /songs/:slug 21 | api.delete('/:slug', catchAsync(songsController.remove)); 22 | 23 | return api; 24 | } -------------------------------------------------------------------------------- /07-pagination/start/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /08-sorting-filtering/end/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "lodash": "^4.17.10", 19 | "mongoose": "^5.1.2", 20 | "mongoose-url-slugs": "^1.0.2", 21 | "pug": "^2.0.3", 22 | "qs": "^6.5.2" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^6.26.3", 27 | "babel-plugin-transform-async-to-generator": "^6.24.1", 28 | "babel-polyfill": "^6.26.0", 29 | "babel-preset-env": "^1.6.1", 30 | "rimraf": "^2.6.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const sort_by = {}; 12 | sort_by[req.query.sort_by || 'createdAt'] = req.query.order_by || 'desc'; 13 | const offset = parseInt(req.query.offset) || 0; 14 | const per_page = parseInt(req.query.per_page) || 2; 15 | const songsPromise = 16 | Song.find(req.filters) 17 | .skip(offset) 18 | .limit(per_page) 19 | .sort(sort_by); 20 | 21 | const countPromise = Song.count(req.filters); 22 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 23 | return res.status(200).send({ data: songs, count }); 24 | }, 25 | 26 | async create(req, res) { 27 | const song = await new Song({ 28 | title: req.body.title, 29 | genre: req.body.genre, 30 | duration: req.body.duration 31 | }).save(); 32 | 33 | return res.status(201).send({ data: song, message: `Song was created` }); 34 | }, 35 | 36 | async update(req, res, next) { 37 | const song = await Song.find({ 'slug': req.params.slug }); 38 | if (!song) return next(); 39 | 40 | song.title = req.body.title; 41 | await song.save(); 42 | 43 | return res.status(200).send({ data: song, message: `Song was updated` }); 44 | }, 45 | 46 | async remove(req, res, next) { 47 | const song = await Song.findOne({ 'slug': req.params.slug }); 48 | if (!song) return next(); 49 | await song.remove(); 50 | 51 | return res.status(200).send({ message: `Song was removed` }); 52 | } 53 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/middlewares/filters/songs.js: -------------------------------------------------------------------------------- 1 | import Song from '../../models/song'; 2 | import qs from 'qs'; 3 | import _ from 'lodash'; 4 | 5 | export default function getFilters(req, res, next) { 6 | const availableFilters = Object.keys(Song.schema.paths); 7 | const filters = qs.parse(req.query); 8 | 9 | req.filters = _.pickBy(filters, (value, key) => availableFilters.indexOf(key) > -1); 10 | next(); 11 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String, 6 | genre: { type: String, enum: ['rock', 'pop', 'electronic'] }, 7 | duration: Number 8 | }, { 9 | timestamps: true 10 | }); 11 | 12 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 13 | 14 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | import getFilters from '../middlewares/filters/songs'; 5 | 6 | export default () => { 7 | const api = Router(); 8 | 9 | // GET /songs/:slug 10 | api.get('/:slug', catchAsync(songsController.findOne)); 11 | 12 | // GET /songs 13 | api.get('/', getFilters, catchAsync(songsController.findAll)); 14 | 15 | // POST /songs 16 | api.post('/', catchAsync(songsController.create)); 17 | 18 | // PUT /songs/:slug 19 | api.put('/:slug', catchAsync(songsController.update)); 20 | 21 | // DELETE /songs/:slug 22 | api.delete('/:slug', catchAsync(songsController.remove)); 23 | 24 | return api; 25 | } -------------------------------------------------------------------------------- /08-sorting-filtering/end/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /08-sorting-filtering/start/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /08-sorting-filtering/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "mongoose": "^5.1.2", 19 | "mongoose-url-slugs": "^1.0.2", 20 | "pug": "^2.0.3" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.26.0", 24 | "babel-core": "^6.26.3", 25 | "babel-plugin-transform-async-to-generator": "^6.24.1", 26 | "babel-polyfill": "^6.26.0", 27 | "babel-preset-env": "^1.6.1", 28 | "rimraf": "^2.6.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const offset = parseInt(req.query.offset) || 0; 12 | const per_page = parseInt(req.query.per_page) || 2; 13 | const songsPromise = Song.find().skip(offset).limit(per_page).sort({ createdAt: 'desc' }); 14 | const countPromise = Song.count(); 15 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 16 | return res.status(200).send({ data: songs, count }); 17 | }, 18 | 19 | async create(req, res) { 20 | const song = await new Song({ 21 | title: req.body.title 22 | }).save(); 23 | 24 | return res.status(201).send({ data: song, message: `Song was created` }); 25 | }, 26 | 27 | async update(req, res, next) { 28 | const song = await Song.find({ 'slug': req.params.slug }); 29 | if (!song) return next(); 30 | 31 | song.title = req.body.title; 32 | await song.save(); 33 | 34 | return res.status(200).send({ data: song, message: `Song was updated` }); 35 | }, 36 | 37 | async remove(req, res, next) { 38 | const song = await Song.findOne({ 'slug': req.params.slug }); 39 | if (!song) return next(); 40 | await song.remove(); 41 | 42 | return res.status(200).send({ message: `Song was removed` }); 43 | } 44 | } -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String 6 | }, { 7 | timestamps: true 8 | }); 9 | 10 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 11 | 12 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | 5 | export default () => { 6 | const api = Router(); 7 | 8 | // GET /songs/:slug 9 | api.get('/:slug', catchAsync(songsController.findOne)); 10 | 11 | // GET /songs 12 | api.get('/', catchAsync(songsController.findAll)); 13 | 14 | // POST /songs 15 | api.post('/', catchAsync(songsController.create)); 16 | 17 | // PUT /songs/:slug 18 | api.put('/:slug', catchAsync(songsController.update)); 19 | 20 | // DELETE /songs/:slug 21 | api.delete('/:slug', catchAsync(songsController.remove)); 22 | 23 | return api; 24 | } -------------------------------------------------------------------------------- /08-sorting-filtering/start/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /09-searching/end/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator", "transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /09-searching/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "lodash": "^4.17.10", 19 | "mongoose": "^5.2.8", 20 | "mongoose-url-slugs": "^1.0.2", 21 | "pug": "^2.0.3", 22 | "qs": "^6.5.2" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^6.26.3", 27 | "babel-plugin-transform-async-to-generator": "^6.24.1", 28 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 29 | "babel-polyfill": "^6.26.0", 30 | "babel-preset-env": "^1.6.1", 31 | "rimraf": "^2.6.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /09-searching/end/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /09-searching/end/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment', 3 | settings: { 4 | useNewUrlParser: true 5 | } 6 | } -------------------------------------------------------------------------------- /09-searching/end/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | let sort_by = {}; 12 | sort_by[req.query.sort_by || 'createdAt'] = req.query.order_by || 'desc'; 13 | if (req.query.q) sort_by = { score: { $meta: 'textScore' } }; 14 | 15 | const offset = parseInt(req.query.offset) || 0; 16 | const per_page = parseInt(req.query.per_page) || 2; 17 | const songsPromise = 18 | Song.find(req.filters, { score: { $meta: 'textScore' } }) 19 | .skip(offset) 20 | .limit(per_page) 21 | .sort(sort_by); 22 | 23 | const countPromise = Song.countDocuments(req.filters); 24 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 25 | return res.status(200).send({ data: songs, count }); 26 | }, 27 | 28 | async create(req, res) { 29 | const song = await new Song({ 30 | title: req.body.title, 31 | genre: req.body.genre, 32 | duration: req.body.duration 33 | }).save(); 34 | 35 | return res.status(201).send({ data: song, message: `Song was created` }); 36 | }, 37 | 38 | async update(req, res, next) { 39 | const song = await Song.find({ 'slug': req.params.slug }); 40 | if (!song) return next(); 41 | 42 | song.title = req.body.title; 43 | await song.save(); 44 | 45 | return res.status(200).send({ data: song, message: `Song was updated` }); 46 | }, 47 | 48 | async remove(req, res, next) { 49 | const song = await Song.findOne({ 'slug': req.params.slug }); 50 | if (!song) return next(); 51 | await song.remove(); 52 | 53 | return res.status(200).send({ message: `Song was removed` }); 54 | } 55 | } -------------------------------------------------------------------------------- /09-searching/end/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl, dbConfig.settings); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /09-searching/end/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /09-searching/end/src/middlewares/filters/songs.js: -------------------------------------------------------------------------------- 1 | import Song from '../../models/song'; 2 | import qs from 'qs'; 3 | import _ from 'lodash'; 4 | 5 | export default function getFilters(req, res, next) { 6 | const availableFilters = Object.keys(Song.schema.paths); 7 | const filters = qs.parse(req.query); 8 | 9 | const schemaFilters = _.pickBy(filters, (value, key) => availableFilters.indexOf(key) > -1); 10 | let searchFilter = {}; 11 | if (filters.q) { 12 | searchFilter = { 13 | $text: { 14 | $search: filters.q 15 | } 16 | } 17 | } 18 | 19 | req.filters = { ...searchFilter, ...schemaFilters }; 20 | next(); 21 | } -------------------------------------------------------------------------------- /09-searching/end/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String, 6 | genre: { type: String, enum: ['rock', 'pop', 'electronic'] }, 7 | duration: Number 8 | }, { 9 | timestamps: true 10 | }); 11 | 12 | Song.index({ title: 'text' }); 13 | 14 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 15 | 16 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /09-searching/end/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | import getFilters from '../middlewares/filters/songs'; 5 | 6 | export default () => { 7 | const api = Router(); 8 | 9 | // GET /songs/:slug 10 | api.get('/:slug', catchAsync(songsController.findOne)); 11 | 12 | // GET /songs 13 | api.get('/', getFilters, catchAsync(songsController.findAll)); 14 | 15 | // POST /songs 16 | api.post('/', catchAsync(songsController.create)); 17 | 18 | // PUT /songs/:slug 19 | api.put('/:slug', catchAsync(songsController.update)); 20 | 21 | // DELETE /songs/:slug 22 | api.delete('/:slug', catchAsync(songsController.remove)); 23 | 24 | return api; 25 | } -------------------------------------------------------------------------------- /09-searching/end/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /09-searching/start/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } -------------------------------------------------------------------------------- /09-searching/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "lodash": "^4.17.10", 19 | "mongoose": "^5.1.2", 20 | "mongoose-url-slugs": "^1.0.2", 21 | "pug": "^2.0.3", 22 | "qs": "^6.5.2" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^6.26.3", 27 | "babel-plugin-transform-async-to-generator": "^6.24.1", 28 | "babel-polyfill": "^6.26.0", 29 | "babel-preset-env": "^1.6.1", 30 | "rimraf": "^2.6.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /09-searching/start/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /09-searching/start/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment' 3 | } -------------------------------------------------------------------------------- /09-searching/start/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | const sort_by = {}; 12 | sort_by[req.query.sort_by || 'createdAt'] = req.query.order_by || 'desc'; 13 | 14 | // search query param 15 | 16 | const offset = parseInt(req.query.offset) || 0; 17 | const per_page = parseInt(req.query.per_page) || 2; 18 | const songsPromise = 19 | Song.find(req.filters) 20 | .skip(offset) 21 | .limit(per_page) 22 | .sort(sort_by); 23 | 24 | // search 25 | 26 | const countPromise = Song.count(req.filters); 27 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 28 | return res.status(200).send({ data: songs, count }); 29 | }, 30 | 31 | async create(req, res) { 32 | const song = await new Song({ 33 | title: req.body.title, 34 | genre: req.body.genre, 35 | duration: req.body.duration 36 | }).save(); 37 | 38 | return res.status(201).send({ data: song, message: `Song was created` }); 39 | }, 40 | 41 | async update(req, res, next) { 42 | const song = await Song.find({ 'slug': req.params.slug }); 43 | if (!song) return next(); 44 | 45 | song.title = req.body.title; 46 | await song.save(); 47 | 48 | return res.status(200).send({ data: song, message: `Song was updated` }); 49 | }, 50 | 51 | async remove(req, res, next) { 52 | const song = await Song.findOne({ 'slug': req.params.slug }); 53 | if (!song) return next(); 54 | await song.remove(); 55 | 56 | return res.status(200).send({ message: `Song was removed` }); 57 | } 58 | } -------------------------------------------------------------------------------- /09-searching/start/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /09-searching/start/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /09-searching/start/src/middlewares/filters/songs.js: -------------------------------------------------------------------------------- 1 | import Song from '../../models/song'; 2 | import qs from 'qs'; 3 | import _ from 'lodash'; 4 | 5 | export default function getFilters(req, res, next) { 6 | const availableFilters = Object.keys(Song.schema.paths); 7 | const filters = qs.parse(req.query); 8 | 9 | req.filters = _.pickBy(filters, (value, key) => availableFilters.indexOf(key) > -1); 10 | next(); 11 | } -------------------------------------------------------------------------------- /09-searching/start/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String, 6 | genre: { type: String, enum: ['rock', 'pop', 'electronic'] }, 7 | duration: Number 8 | }, { 9 | timestamps: true 10 | }); 11 | 12 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 13 | 14 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /09-searching/start/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | import getFilters from '../middlewares/filters/songs'; 5 | 6 | export default () => { 7 | const api = Router(); 8 | 9 | // GET /songs/:slug 10 | api.get('/:slug', catchAsync(songsController.findOne)); 11 | 12 | // GET /songs 13 | api.get('/', getFilters, catchAsync(songsController.findAll)); 14 | 15 | // POST /songs 16 | api.post('/', catchAsync(songsController.create)); 17 | 18 | // PUT /songs/:slug 19 | api.put('/:slug', catchAsync(songsController.update)); 20 | 21 | // DELETE /songs/:slug 22 | api.delete('/:slug', catchAsync(songsController.remove)); 23 | 24 | return api; 25 | } -------------------------------------------------------------------------------- /09-searching/start/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /10-security/end/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator", "transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /10-security/end/.env: -------------------------------------------------------------------------------- 1 | JWT_SECRET=_secret_string_ -------------------------------------------------------------------------------- /10-security/end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist --copy-files", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "dotenv": "^6.0.0", 18 | "express": "^4.16.3", 19 | "jsonwebtoken": "^8.3.0", 20 | "lodash": "^4.17.10", 21 | "mongoose": "^5.2.8", 22 | "mongoose-url-slugs": "^1.0.2", 23 | "passport": "^0.4.0", 24 | "passport-jwt": "^4.0.0", 25 | "passport-local": "^1.0.0", 26 | "passport-local-mongoose": "^5.0.1", 27 | "pug": "^2.0.3", 28 | "qs": "^6.5.2" 29 | }, 30 | "devDependencies": { 31 | "babel-cli": "^6.26.0", 32 | "babel-core": "^6.26.3", 33 | "babel-plugin-transform-async-to-generator": "^6.24.1", 34 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 35 | "babel-polyfill": "^6.26.0", 36 | "babel-preset-env": "^1.6.1", 37 | "rimraf": "^2.6.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /10-security/end/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /10-security/end/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment', 3 | settings: { 4 | useNewUrlParser: true 5 | } 6 | } -------------------------------------------------------------------------------- /10-security/end/src/config/passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import passportJWT from 'passport-jwt'; 3 | import User from '../models/user'; 4 | 5 | const JWTStrategy = passportJWT.Strategy; 6 | const ExtractJWT = passportJWT.ExtractJwt; 7 | 8 | 9 | function verifyCallback(payload, done) { 10 | return User.findOne({_id: payload.id}) 11 | .then(user => { 12 | return done(null, user); 13 | }) 14 | .catch(err => { 15 | return done(err); 16 | }); 17 | } 18 | 19 | export default () => { 20 | const config = { 21 | jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), 22 | secretOrKey: process.env.JWT_SECRET 23 | }; 24 | 25 | passport.use(User.createStrategy()); 26 | passport.use(new JWTStrategy(config, verifyCallback)); 27 | } -------------------------------------------------------------------------------- /10-security/end/src/controllers/authController.js: -------------------------------------------------------------------------------- 1 | import User from '../models/user'; 2 | import jwt from 'jsonwebtoken'; 3 | 4 | export default { 5 | async login (req, res, next) { 6 | // generate token 7 | const token = jwt.sign({ id: req.user._id }, process.env.JWT_SECRET, { expiresIn: 1200 }); 8 | // return token 9 | return res.send({ token }); 10 | }, 11 | 12 | async register(req, res, next) { 13 | const { first_name, last_name, email, password } = req.body; 14 | const user = new User({ first_name, last_name, email }); 15 | await User.register(user, password); 16 | 17 | res.send('User created successfully. Now you can log in.'); 18 | } 19 | } -------------------------------------------------------------------------------- /10-security/end/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | let sort_by = {}; 12 | sort_by[req.query.sort_by || 'createdAt'] = req.query.order_by || 'desc'; 13 | if (req.query.q) sort_by = { score: { $meta: 'textScore' } }; 14 | 15 | const offset = parseInt(req.query.offset) || 0; 16 | const per_page = parseInt(req.query.per_page) || 2; 17 | const songsPromise = 18 | Song.find(req.filters, { score: { $meta: 'textScore' } }) 19 | .skip(offset) 20 | .limit(per_page) 21 | .sort(sort_by); 22 | 23 | const countPromise = Song.countDocuments(req.filters); 24 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 25 | return res.status(200).send({ data: songs, count }); 26 | }, 27 | 28 | async create(req, res) { 29 | const song = await new Song({ 30 | title: req.body.title, 31 | genre: req.body.genre, 32 | duration: req.body.duration 33 | }).save(); 34 | 35 | return res.status(201).send({ data: song, message: `Song was created` }); 36 | }, 37 | 38 | async update(req, res, next) { 39 | const song = await Song.find({ 'slug': req.params.slug }); 40 | if (!song) return next(); 41 | 42 | song.title = req.body.title; 43 | await song.save(); 44 | 45 | return res.status(200).send({ data: song, message: `Song was updated` }); 46 | }, 47 | 48 | async remove(req, res, next) { 49 | const song = await Song.findOne({ 'slug': req.params.slug }); 50 | if (!song) return next(); 51 | await song.remove(); 52 | 53 | return res.status(200).send({ message: `Song was removed` }); 54 | } 55 | } -------------------------------------------------------------------------------- /10-security/end/src/index.js: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | dotenv.config({ path: '.env' }); 3 | console.log(process.env.JWT_SECRET); 4 | import express from 'express'; 5 | import { join } from 'path'; 6 | import config from './config/config'; 7 | import songs from './routes/songs'; 8 | import auth from './routes/auth'; 9 | import passport from './config/passport'; 10 | import { notFound, catchErrors } from './middlewares/errors'; 11 | import bodyParser from 'body-parser'; 12 | import register from 'babel-core/register'; 13 | import babelPolyfill from 'babel-polyfill'; 14 | 15 | // Connect to database 16 | import dbConfig from './config/database'; 17 | import mongoose from 'mongoose'; 18 | 19 | // Configure passport 20 | passport(); 21 | 22 | mongoose.connect(dbConfig.mongoUrl, dbConfig.settings); 23 | mongoose.Promise = global.Promise; 24 | mongoose.connection.on('error', (err) => { 25 | console.log('Could not connect to the database. Exiting now...'); 26 | process.exit(); 27 | }); 28 | 29 | const app = express(); 30 | 31 | app.set('view engine', 'pug'); 32 | app.set('views', join(__dirname, 'views')); 33 | app.use(express.static('public')); 34 | app.use(bodyParser.urlencoded({ extended: false })); 35 | app.use(bodyParser.json()); 36 | 37 | // routes config 38 | app.use('/api/songs', songs()); 39 | app.use('/api/auth', auth()); 40 | 41 | // errors handling 42 | app.use(notFound); 43 | app.use(catchErrors); 44 | 45 | // let's play! 46 | app.listen(config.server.port, () => { 47 | console.log(`Server is up!`); 48 | }); -------------------------------------------------------------------------------- /10-security/end/src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | 3 | export default (req, res, next) => { 4 | return passport.authenticate('jwt', { session: false })(req, res, next); 5 | } -------------------------------------------------------------------------------- /10-security/end/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /10-security/end/src/middlewares/filters/songs.js: -------------------------------------------------------------------------------- 1 | import Song from '../../models/song'; 2 | import qs from 'qs'; 3 | import _ from 'lodash'; 4 | 5 | export default function getFilters(req, res, next) { 6 | const availableFilters = Object.keys(Song.schema.paths); 7 | const filters = qs.parse(req.query); 8 | 9 | const schemaFilters = _.pickBy(filters, (value, key) => availableFilters.indexOf(key) > -1); 10 | let searchFilter = {}; 11 | if (filters.q) { 12 | searchFilter = { 13 | $text: { 14 | $search: filters.q 15 | } 16 | } 17 | } 18 | 19 | req.filters = { ...searchFilter, ...schemaFilters }; 20 | next(); 21 | } -------------------------------------------------------------------------------- /10-security/end/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String, 6 | genre: { type: String, enum: ['rock', 'pop', 'electronic'] }, 7 | duration: Number 8 | }, { 9 | timestamps: true 10 | }); 11 | 12 | Song.index({ title: 'text' }); 13 | 14 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 15 | 16 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /10-security/end/src/models/user.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import passportLocalMongoose from 'passport-local-mongoose'; 3 | 4 | const UserSchema = mongoose.Schema({ 5 | first_name: String, 6 | last_name: String, 7 | email: { 8 | type: String, 9 | unique: true, 10 | lowercase: true, 11 | trim: true 12 | } 13 | }, { 14 | timestamps: true // created_at / updated_at 15 | }); 16 | 17 | UserSchema.plugin(passportLocalMongoose, { usernameField: 'email' }); 18 | 19 | export default mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /10-security/end/src/routes/auth.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import AuthController from '../controllers/authController'; 4 | import passport from 'passport'; 5 | 6 | export default () => { 7 | const api = Router(); 8 | 9 | api.post('/login', passport.authenticate('local', { session: false }), AuthController.login); 10 | 11 | api.post('/register', AuthController.register); 12 | 13 | return api; 14 | } -------------------------------------------------------------------------------- /10-security/end/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import jwtAuth from '../middlewares/auth'; 4 | import songsController from '../controllers/songsController'; 5 | import getFilters from '../middlewares/filters/songs'; 6 | 7 | export default () => { 8 | const api = Router(); 9 | 10 | // GET /songs/:slug 11 | api.get('/:slug', catchAsync(songsController.findOne)); 12 | 13 | // GET /songs 14 | api.get('/', getFilters, catchAsync(songsController.findAll)); 15 | 16 | // POST /songs 17 | api.post('/', jwtAuth, catchAsync(songsController.create)); 18 | 19 | // PUT /songs/:slug 20 | api.put('/:slug', catchAsync(songsController.update)); 21 | 22 | // DELETE /songs/:slug 23 | api.delete('/:slug', catchAsync(songsController.remove)); 24 | 25 | return api; 26 | } -------------------------------------------------------------------------------- /10-security/end/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` -------------------------------------------------------------------------------- /10-security/start/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator", "transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /10-security/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf ./dist && babel ./src -d ./dist", 8 | "prestart": "npm run -s build", 9 | "start": "NODE_ENV=production PORT=8080 node dist/index.js", 10 | "dev": "NODE_ENV=development PORT=8080 nodemon -w ./src --exec \"babel-node ./src\"" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "body-parser": "^1.18.3", 17 | "express": "^4.16.3", 18 | "lodash": "^4.17.10", 19 | "mongoose": "^5.2.8", 20 | "mongoose-url-slugs": "^1.0.2", 21 | "pug": "^2.0.3", 22 | "qs": "^6.5.2" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^6.26.3", 27 | "babel-plugin-transform-async-to-generator": "^6.24.1", 28 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 29 | "babel-polyfill": "^6.26.0", 30 | "babel-preset-env": "^1.6.1", 31 | "rimraf": "^2.6.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /10-security/start/src/config/config.js: -------------------------------------------------------------------------------- 1 | // required environment variables 2 | ['NODE_ENV', 'PORT'].forEach((name) => { 3 | if (!process.env[name]) { 4 | throw new Error(`Environment variable ${name} is missing`) 5 | } 6 | }); 7 | 8 | export default { 9 | env: process.env.NODE_ENV, 10 | server: { 11 | port: Number(process.env.PORT) 12 | } 13 | }; -------------------------------------------------------------------------------- /10-security/start/src/config/database.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mongoUrl: 'mongodb://localhost:27017/overment', 3 | settings: { 4 | useNewUrlParser: true 5 | } 6 | } -------------------------------------------------------------------------------- /10-security/start/src/controllers/songsController.js: -------------------------------------------------------------------------------- 1 | import Song from '../models/song'; 2 | 3 | export default { 4 | async findOne(req, res, next) { 5 | const song = await Song.findOne({ slug: req.params.slug }); 6 | if (!song) return next(); 7 | return res.status(200).send({ data: song }); 8 | }, 9 | 10 | async findAll(req, res) { 11 | let sort_by = {}; 12 | sort_by[req.query.sort_by || 'createdAt'] = req.query.order_by || 'desc'; 13 | if (req.query.q) sort_by = { score: { $meta: 'textScore' } }; 14 | 15 | const offset = parseInt(req.query.offset) || 0; 16 | const per_page = parseInt(req.query.per_page) || 2; 17 | const songsPromise = 18 | Song.find(req.filters, { score: { $meta: 'textScore' } }) 19 | .skip(offset) 20 | .limit(per_page) 21 | .sort(sort_by); 22 | 23 | const countPromise = Song.countDocuments(req.filters); 24 | const [songs, count] = await Promise.all([songsPromise, countPromise]); 25 | return res.status(200).send({ data: songs, count }); 26 | }, 27 | 28 | async create(req, res) { 29 | const song = await new Song({ 30 | title: req.body.title, 31 | genre: req.body.genre, 32 | duration: req.body.duration 33 | }).save(); 34 | 35 | return res.status(201).send({ data: song, message: `Song was created` }); 36 | }, 37 | 38 | async update(req, res, next) { 39 | const song = await Song.find({ 'slug': req.params.slug }); 40 | if (!song) return next(); 41 | 42 | song.title = req.body.title; 43 | await song.save(); 44 | 45 | return res.status(200).send({ data: song, message: `Song was updated` }); 46 | }, 47 | 48 | async remove(req, res, next) { 49 | const song = await Song.findOne({ 'slug': req.params.slug }); 50 | if (!song) return next(); 51 | await song.remove(); 52 | 53 | return res.status(200).send({ message: `Song was removed` }); 54 | } 55 | } -------------------------------------------------------------------------------- /10-security/start/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { join } from 'path'; 3 | import config from './config/config'; 4 | import songs from './routes/songs'; 5 | import { notFound, catchErrors } from './middlewares/errors'; 6 | import bodyParser from 'body-parser'; 7 | import register from 'babel-core/register'; 8 | import babelPolyfill from 'babel-polyfill'; 9 | 10 | // Connect to database 11 | import dbConfig from './config/database'; 12 | import mongoose from 'mongoose'; 13 | 14 | mongoose.connect(dbConfig.mongoUrl, dbConfig.settings); 15 | mongoose.Promise = global.Promise; 16 | mongoose.connection.on('error', (err) => { 17 | console.log('Could not connect to the database. Exiting now...'); 18 | process.exit(); 19 | }); 20 | 21 | const app = express(); 22 | 23 | app.set('view engine', 'pug'); 24 | app.set('views', join(__dirname, 'views')); 25 | app.use(express.static('public')); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(bodyParser.json()); 28 | 29 | // routes config 30 | app.use('/api/songs', songs()); 31 | 32 | // errors handling 33 | app.use(notFound); 34 | app.use(catchErrors); 35 | 36 | // let's play! 37 | app.listen(config.server.port, () => { 38 | console.log(`Server is up!`); 39 | }); -------------------------------------------------------------------------------- /10-security/start/src/middlewares/errors.js: -------------------------------------------------------------------------------- 1 | export function notFound(req, res, next) { 2 | const err = new Error('404 page not found'); 3 | err.status = 404; 4 | next(err); 5 | } 6 | 7 | export function catchAsync(fn) { 8 | return (req, res, next) => { 9 | fn(req, res, next).catch(err => next(err)); 10 | } 11 | } 12 | 13 | export function catchErrors(err, req, res, next) { 14 | res.status(err.status || 500); 15 | res.render('error', { 16 | message: err.message 17 | }); 18 | } -------------------------------------------------------------------------------- /10-security/start/src/middlewares/filters/songs.js: -------------------------------------------------------------------------------- 1 | import Song from '../../models/song'; 2 | import qs from 'qs'; 3 | import _ from 'lodash'; 4 | 5 | export default function getFilters(req, res, next) { 6 | const availableFilters = Object.keys(Song.schema.paths); 7 | const filters = qs.parse(req.query); 8 | 9 | const schemaFilters = _.pickBy(filters, (value, key) => availableFilters.indexOf(key) > -1); 10 | let searchFilter = {}; 11 | if (filters.q) { 12 | searchFilter = { 13 | $text: { 14 | $search: filters.q 15 | } 16 | } 17 | } 18 | 19 | req.filters = { ...searchFilter, ...schemaFilters }; 20 | next(); 21 | } -------------------------------------------------------------------------------- /10-security/start/src/models/song.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import URLSlugs from 'mongoose-url-slugs'; 3 | 4 | const Song = mongoose.Schema({ 5 | title: String, 6 | genre: { type: String, enum: ['rock', 'pop', 'electronic'] }, 7 | duration: Number 8 | }, { 9 | timestamps: true 10 | }); 11 | 12 | Song.index({ title: 'text' }); 13 | 14 | Song.plugin(URLSlugs('title', { field: 'slug', update: true })); 15 | 16 | export default mongoose.model('Song', Song); -------------------------------------------------------------------------------- /10-security/start/src/routes/songs.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { catchAsync } from "../middlewares/errors"; 3 | import songsController from '../controllers/songsController'; 4 | import getFilters from '../middlewares/filters/songs'; 5 | 6 | export default () => { 7 | const api = Router(); 8 | 9 | // GET /songs/:slug 10 | api.get('/:slug', catchAsync(songsController.findOne)); 11 | 12 | // GET /songs 13 | api.get('/', getFilters, catchAsync(songsController.findAll)); 14 | 15 | // POST /songs 16 | api.post('/', catchAsync(songsController.create)); 17 | 18 | // PUT /songs/:slug 19 | api.put('/:slug', catchAsync(songsController.update)); 20 | 21 | // DELETE /songs/:slug 22 | api.delete('/:slug', catchAsync(songsController.remove)); 23 | 24 | return api; 25 | } -------------------------------------------------------------------------------- /10-security/start/src/views/error.pug: -------------------------------------------------------------------------------- 1 | h1 Oops! Looks like something went wrong! 2 | P= `Error message: ${message}` --------------------------------------------------------------------------------