├── 05-http-server-streams ├── 01-file-server-get │ ├── files │ │ └── .gitkeep │ ├── test │ │ ├── fixtures │ │ │ ├── index.js │ │ │ ├── big.png │ │ │ └── small.png │ │ └── server.test.js │ ├── index.js │ ├── README.md │ └── server.js ├── 03-file-server-delete │ ├── files │ │ └── .gitkeep │ ├── test │ │ ├── fixtures │ │ │ ├── index.js │ │ │ ├── big.png │ │ │ └── small.png │ │ └── server.test.js │ ├── index.js │ ├── README.md │ └── server.js └── 02-file-server-post │ ├── test │ ├── fixtures │ │ ├── index.js │ │ ├── big.png │ │ └── small.png │ └── server.test.js │ ├── index.js │ ├── LimitExceededError.js │ ├── LimitSizeStream.js │ ├── README.md │ └── server.js ├── config.yml ├── .gitignore ├── 02-event-loop └── 01-events-order │ ├── solution.txt │ ├── index.js │ ├── test │ └── index.test.js │ └── README.md ├── 06-koajs └── 01-chat-app │ ├── index.js │ ├── README.md │ ├── app.js │ ├── public │ └── index.html │ └── test │ └── chat.test.js ├── 07-mongodb-mongoose ├── 02-rest-api │ ├── index.js │ ├── config.js │ ├── controllers │ │ ├── categories.js │ │ └── products.js │ ├── libs │ │ └── connection.js │ ├── models │ │ ├── Category.js │ │ └── Product.js │ ├── app.js │ ├── README.md │ └── test │ │ └── app.test.js └── 01-schema-model │ ├── config.js │ ├── libs │ └── connection.js │ ├── models │ ├── Category.js │ └── Product.js │ ├── README.md │ └── test │ └── Models.test.js ├── README.md ├── .eslintrc.yml ├── .travis.yml ├── 01-intro └── 01-sum │ ├── sum.js │ ├── README.md │ └── test │ └── sum.test.js ├── 03-streams ├── 01-limit-size-stream │ ├── LimitExceededError.js │ ├── LimitSizeStream.js │ ├── README.md │ └── test │ │ └── LimitSizeStream.test.js └── 02-line-split-stream │ ├── LineSplitStream.js │ ├── README.md │ └── test │ └── LineSplitStream.test.js ├── data └── users.json ├── reporter.js ├── local.js └── package.json /05-http-server-streams/01-file-server-get/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | git: 3 | copy: 4 | - data/** 5 | - '*.*' 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | node_modules 4 | package-lock.json 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); 2 | -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); 2 | -------------------------------------------------------------------------------- /02-event-loop/01-events-order/solution.txt: -------------------------------------------------------------------------------- 1 | James 2 | Richard 3 | John 4 | Robert 5 | James 6 | Michael -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | console.log('hello world'); 2 | -------------------------------------------------------------------------------- /06-koajs/01-chat-app/index.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | app.listen(3000, () => { 4 | console.log('App is running on http://localhost:3000'); 5 | }); 6 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/index.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | app.listen(3000, () => { 4 | console.log('App is running on http://localhost:3000'); 5 | }); 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | 4 | Задачник для курса по Node.JS на сайте https://learn.javascript.ru/courses/nodejs. 5 | 6 | Содержит теоретические материалы и практические задания. 7 | -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/test/fixtures/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/01-file-server-get/test/fixtures/big.png -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/index.js: -------------------------------------------------------------------------------- 1 | const server = require('./server'); 2 | 3 | server.listen(3000, () => { 4 | console.log('Server is listening on http://localhost:3000'); 5 | }); 6 | -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/test/fixtures/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/01-file-server-get/test/fixtures/small.png -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/index.js: -------------------------------------------------------------------------------- 1 | const server = require('./server'); 2 | 3 | server.listen(3000, () => { 4 | console.log('Server is listening on http://localhost:3000'); 5 | }); 6 | -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/test/fixtures/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/02-file-server-post/test/fixtures/big.png -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/test/fixtures/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/02-file-server-post/test/fixtures/small.png -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/test/fixtures/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/03-file-server-delete/test/fixtures/big.png -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/index.js: -------------------------------------------------------------------------------- 1 | const server = require('./server'); 2 | 3 | server.listen(3000, () => { 4 | console.log('Server is listening on http://localhost:3000'); 5 | }); 6 | -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/test/fixtures/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushev-s/nodejs-20200914-2_ushev-s90/HEAD/05-http-server-streams/03-file-server-delete/test/fixtures/small.png -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mongodb: { 3 | uri: (process.env.NODE_ENV === 'test') ? 4 | 'mongodb://localhost/6-module-2-task' : 5 | 'mongodb://localhost/any-shop', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/01-schema-model/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mongodb: { 3 | uri: (process.env.NODE_ENV === 'test' ? 4 | 'mongodb://localhost/6-module-1-task' : 5 | 'mongodb://localhost/any-shop'), 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | commonjs: true 3 | es6: true 4 | node: true 5 | extends: google 6 | parserOptions: 7 | ecmaVersion: 2018 8 | rules: 9 | max-len: 10 | - error 11 | - code: 100 12 | require-jsdoc: 13 | - off 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 14.4.0 4 | 5 | services: 6 | - mongodb 7 | 8 | script: npm test --silent 9 | 10 | notifications: 11 | webhooks: 12 | - https://learn.javascript.ru/taskbook/travis/notifications 13 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/controllers/categories.js: -------------------------------------------------------------------------------- 1 | const Category = require('../models/Category'); 2 | 3 | module.exports.categoryList = async function categoryList(ctx, next) { 4 | const categories = await Category.find({}); 5 | ctx.body = { categories }; 6 | }; 7 | -------------------------------------------------------------------------------- /01-intro/01-sum/sum.js: -------------------------------------------------------------------------------- 1 | function sum(a, b) { 2 | if (typeof a !== 'number' || typeof b !== 'number') { 3 | throw new TypeError( 4 | 'Incorrect Type. You have to pass numbers into arguments' 5 | ); 6 | } else { 7 | return a + b; 8 | } 9 | } 10 | 11 | module.exports = sum; 12 | -------------------------------------------------------------------------------- /03-streams/01-limit-size-stream/LimitExceededError.js: -------------------------------------------------------------------------------- 1 | class LimitExceededError extends Error { 2 | constructor() { 3 | super('Limit has been exceeded.'); 4 | 5 | this.name = this.constructor.name; 6 | Error.captureStackTrace(this, this.constructor); 7 | 8 | this.code = 'LIMIT_EXCEEDED'; 9 | } 10 | } 11 | 12 | module.exports = LimitExceededError; 13 | -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/LimitExceededError.js: -------------------------------------------------------------------------------- 1 | class LimitExceededError extends Error { 2 | constructor() { 3 | super('Limit has been exceeded.'); 4 | 5 | this.name = this.constructor.name; 6 | Error.captureStackTrace(this, this.constructor); 7 | 8 | this.code = 'LIMIT_EXCEEDED'; 9 | } 10 | } 11 | 12 | module.exports = LimitExceededError; 13 | -------------------------------------------------------------------------------- /data/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "email": "user1@mail.com", 5 | "displayName": "user1", 6 | "password": "123123" 7 | }, 8 | { 9 | "email": "user2@mail.com", 10 | "displayName": "user2", 11 | "password": "123123" 12 | }, 13 | { 14 | "email": "user3@mail.com", 15 | "displayName": "user3", 16 | "password": "123123" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /01-intro/01-sum/README.md: -------------------------------------------------------------------------------- 1 | # Сумма двух чисел 2 | 3 | Это первая задача курса, которая поможет разобраться с тем, как устроен задачник, а также с тем, как 4 | оформлять решение и отправлять его на проверку. 5 | 6 | В модуле `sum.js` необходимо реализовать функцию, которая принимает два аргумента и возвращает их 7 | сумму. Если аргументы не являются числами — функция должна бросать ошибку TypeError. 8 | 9 | ```js 10 | 11 | sum(1, 2); // 3 12 | sum('1', []); // TypeError 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/libs/connection.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const beautifyUnique = require('mongoose-beautiful-unique-validation'); 3 | const config = require('../config'); 4 | 5 | mongoose.set('useNewUrlParser', true); 6 | mongoose.set('useFindAndModify', false); 7 | mongoose.set('useCreateIndex', true); 8 | 9 | mongoose.set('debug', false); 10 | 11 | mongoose.plugin(beautifyUnique); 12 | 13 | module.exports = mongoose.createConnection(config.mongodb.uri); 14 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/01-schema-model/libs/connection.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const beautifyUnique = require('mongoose-beautiful-unique-validation'); 3 | const config = require('../config'); 4 | 5 | mongoose.set('useNewUrlParser', true); 6 | mongoose.set('useFindAndModify', false); 7 | mongoose.set('useCreateIndex', true); 8 | 9 | mongoose.set('debug', false); 10 | 11 | mongoose.plugin(beautifyUnique); 12 | 13 | module.exports = mongoose.createConnection(config.mongodb.uri); 14 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/01-schema-model/models/Category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const connection = require('../libs/connection'); 3 | 4 | const subCategorySchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true 8 | } 9 | }); 10 | 11 | const categorySchema = new mongoose.Schema({ 12 | title: { 13 | type: String, 14 | required: true, 15 | }, 16 | subcategories: [subCategorySchema], 17 | }); 18 | 19 | module.exports = connection.model('Category', categorySchema); 20 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/models/Category.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const connection = require('../libs/connection'); 3 | 4 | const subCategorySchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | }, 9 | }); 10 | 11 | const categorySchema = new mongoose.Schema({ 12 | title: { 13 | type: String, 14 | required: true, 15 | }, 16 | 17 | subcategories: [subCategorySchema], 18 | }); 19 | 20 | module.exports = connection.model('Category', categorySchema); 21 | -------------------------------------------------------------------------------- /01-intro/01-sum/test/sum.test.js: -------------------------------------------------------------------------------- 1 | const sum = require('../sum'); 2 | const expect = require('chai').expect; 3 | 4 | describe('intro/sum', () => { 5 | describe('функция sum', () => { 6 | it('складывает два числа', () => { 7 | expect(sum(1, 2)).to.equal(3); 8 | }); 9 | 10 | [ 11 | ['1', []], 12 | ['1', '1'] 13 | ].forEach(([a, b]) => { 14 | it('бросает TypeError, если аргументы - не числа', () => { 15 | expect(() => sum(a, b)).throw(TypeError); 16 | }); 17 | }) 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /02-event-loop/01-events-order/index.js: -------------------------------------------------------------------------------- 1 | const intervalId = setInterval(() => { 2 | console.log('James'); 3 | }, 10); 4 | 5 | setTimeout(() => { 6 | const promise = new Promise((resolve) => { 7 | console.log('Richard'); 8 | resolve('Robert'); 9 | }); 10 | 11 | promise 12 | .then((value) => { 13 | console.log(value); 14 | 15 | setTimeout(() => { 16 | console.log('Michael'); 17 | 18 | clearInterval(intervalId); 19 | }, 10); 20 | }); 21 | 22 | console.log('John'); 23 | }, 10); 24 | -------------------------------------------------------------------------------- /03-streams/01-limit-size-stream/LimitSizeStream.js: -------------------------------------------------------------------------------- 1 | const stream = require('stream'); 2 | const LimitExceededError = require('./LimitExceededError'); 3 | 4 | class LimitSizeStream extends stream.Transform { 5 | constructor(options) { 6 | super(options); 7 | this.limit = options.limit; 8 | this.totalSize = 0; 9 | } 10 | 11 | _transform(chunk, encoding, callback) { 12 | this.totalSize += chunk.length; 13 | 14 | if (this.limit) { 15 | if (+this.limit < this.totalSize) { 16 | callback(new LimitExceededError()); 17 | return; 18 | } 19 | } 20 | 21 | this.push(chunk); 22 | callback(); 23 | } 24 | } 25 | 26 | module.exports = LimitSizeStream; 27 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/01-schema-model/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const connection = require('../libs/connection'); 4 | 5 | const productSchema = new Schema({ 6 | title: { 7 | type: String, 8 | required: true, 9 | }, 10 | description: { 11 | type: String, 12 | required: true, 13 | }, 14 | price: { 15 | type: Number, 16 | required: true, 17 | }, 18 | category: { 19 | type: Schema.Types.ObjectId, 20 | ref: 'Category', 21 | required: true, 22 | }, 23 | subcategory: { 24 | type: Schema.Types.ObjectId, 25 | required: true, 26 | }, 27 | images: [String] 28 | }); 29 | 30 | module.exports = connection.model('Product', productSchema); 31 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/models/Product.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const connection = require('../libs/connection'); 3 | 4 | const productSchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | }, 9 | 10 | description: { 11 | type: String, 12 | required: true, 13 | }, 14 | 15 | price: { 16 | type: Number, 17 | required: true, 18 | }, 19 | 20 | category: { 21 | type: mongoose.Schema.Types.ObjectId, 22 | ref: 'Category', 23 | required: true, 24 | }, 25 | 26 | subcategory: { 27 | type: mongoose.Schema.Types.ObjectId, 28 | required: true, 29 | }, 30 | 31 | images: [String], 32 | 33 | }); 34 | 35 | module.exports = connection.model('Product', productSchema); 36 | -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/LimitSizeStream.js: -------------------------------------------------------------------------------- 1 | const stream = require('stream'); 2 | const LimitExceededError = require('./LimitExceededError'); 3 | 4 | class LimitSizeStream extends stream.Transform { 5 | constructor(options) { 6 | super(options); 7 | 8 | this.limit = options.limit; 9 | this.size = 0; 10 | this.isObjectMode = !!options.readableObjectMode; 11 | } 12 | 13 | _transform(chunk, encoding, callback) { 14 | if (this.isObjectMode) { 15 | this.size += 1; 16 | } else { 17 | this.size += chunk.length; 18 | } 19 | 20 | if (this.size > this.limit) { 21 | callback(new LimitExceededError()); 22 | } else { 23 | callback(null, chunk); 24 | } 25 | } 26 | } 27 | 28 | module.exports = LimitSizeStream; 29 | -------------------------------------------------------------------------------- /03-streams/02-line-split-stream/LineSplitStream.js: -------------------------------------------------------------------------------- 1 | const stream = require('stream'); 2 | const os = require('os'); 3 | 4 | class LineSplitStream extends stream.Transform { 5 | constructor(options) { 6 | super(options); 7 | this.line = ''; 8 | } 9 | 10 | _transform(chunk, encoding, callback) { 11 | const rows = chunk.toString().split(`${os.EOL}`); 12 | for (let i = 0; i < rows.length; i++) { 13 | if (i === 0) { 14 | this.line += rows[i]; 15 | } else { 16 | this.push(this.line); 17 | this.line = rows[i]; 18 | } 19 | } 20 | callback(); 21 | } 22 | 23 | _flush(callback) { 24 | if (this.line) { 25 | this.push(this.line); 26 | } 27 | callback(); 28 | } 29 | } 30 | 31 | module.exports = LineSplitStream; 32 | -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/README.md: -------------------------------------------------------------------------------- 1 | # Файловый сервер - удаление файла 2 | 3 | В данной задаче вам необходимо будет реализовать http-сервер, который по запросу пользователя будет 4 | удалять файл с диска. 5 | 6 | - `DELETE /[filename]` - удаление файла из папки `files`. 7 | - При успешном удалении сервер должен вернуть ответ со статусом `200` 8 | - Если файла на диске нет - сервер должен вернуть ошибку `404`. 9 | - Вложенные папки не поддерживаются, при запросе вида `/dir1/dir2/filename` - ошибка `400`. 10 | 11 | При любых других ошибках сервер должен, по возможности, возвращать ошибку `500`. 12 | 13 | Для выполнения запросов можно воспользоваться программой `postman`. Проверить правильность 14 | реализации можно также с помощью тестов. 15 | 16 | Запуск приложения осуществляется с помощью команды `node index.js`. 17 | -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/README.md: -------------------------------------------------------------------------------- 1 | # Файловый сервер - отдача файла 2 | 3 | В данной задаче вам необходимо будет реализовать http-сервер, который по запросу пользователя 4 | отдавать файл с диска. 5 | 6 | - `GET /[filename]` - получание файла из папки `files` (поддерживаются любые типы файлов). 7 | - Если файла на диске нет - сервер должен вернуть ошибку `404`. 8 | - Вложенные папки не поддерживаются, при запросе вида `/dir1/dir2/filename` - ошибка `400`. 9 | - При обрыве соединения необходимо завершить работу стрима. 10 | 11 | При любых ошибках сервер должен, по возможности, возвращать ошибку `500`. 12 | 13 | Для выполнения запросов можно воспользоваться программой `postman`. Проверить правильность 14 | реализации можно также с помощью тестов. 15 | 16 | Запуск приложения осуществляется с помощью команды `node index.js`. 17 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/app.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const Router = require('koa-router'); 3 | const {productsBySubcategory, productList, productById} = require('./controllers/products'); 4 | const {categoryList} = require('./controllers/categories'); 5 | 6 | const app = new Koa(); 7 | 8 | app.use(async (ctx, next) => { 9 | try { 10 | await next(); 11 | } catch (err) { 12 | if (err.status) { 13 | ctx.status = err.status; 14 | ctx.body = {error: err.message}; 15 | } else { 16 | console.error(err); 17 | ctx.status = 500; 18 | ctx.body = {error: 'Internal server error'}; 19 | } 20 | } 21 | }); 22 | 23 | const router = new Router({prefix: '/api'}); 24 | 25 | router.get('/categories', categoryList); 26 | router.get('/products', productsBySubcategory, productList); 27 | router.get('/products/:id', productById); 28 | 29 | app.use(router.routes()); 30 | 31 | module.exports = app; 32 | -------------------------------------------------------------------------------- /06-koajs/01-chat-app/README.md: -------------------------------------------------------------------------------- 1 | # Чат на Koa.js 2 | 3 | В этом задании вам необходимо будет реализовать простой чат на koa.js, используя технологию 4 | [long polling](http://learn.javascript.ru/xhr-longpoll). 5 | 6 | 7 | Суть технологии достаточно проста: клиент делает запрос за получением новых сообщений, а сервер этот 8 | запрос "подвешивает" до тех пор, пока новое сообщение не будет отправлено. После получения сообщения 9 | клиент вновь делает запрос и точно также ждет, пока сервер не ответит. 10 | 11 | 12 | Клиентская часть для браузера уже реализована и находится в статических файлах в папке public, 13 | необходимо реализовать лишь обработчики для двух типов запросов: 14 | 15 | - `GET /subscribe` - получение новых сообщений 16 | - `POST /publish` - отправка сообщения 17 | 18 | 19 | Подсказка: "задержать" запрос в Koa.js очень просто - надо лишь создать новый объект Promise и 20 | через `await` в обработчике запроса ждать его. 21 | 22 | Запуск приложения осуществляется с помощью команды `node index.js`. 23 | -------------------------------------------------------------------------------- /02-event-loop/01-events-order/test/index.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const {EOL} = require('os'); 3 | const {execSync} = require('child_process'); 4 | const path = require('path'); 5 | const expect = require('chai').expect; 6 | 7 | describe('event-loop/events-order', () => { 8 | describe('Порядок вывода сообщений', () => { 9 | it('файл с решением должен быть в папке с задачей', () => { 10 | const isExists = fs.existsSync(path.join(__dirname, '../solution.txt')); 11 | expect(isExists).to.be.true; 12 | }); 13 | 14 | it('порядок вывода совпадает', () => { 15 | const solution = fs.readFileSync( 16 | path.join(__dirname, '../solution.txt'), 17 | { 18 | encoding: 'utf-8', 19 | } 20 | ).replace(/\r\n|\r|\n/g, EOL); 21 | 22 | const output = execSync(`node ${path.join(__dirname, '../index.js')}`, { 23 | encoding: 'utf-8', 24 | }).replace(/\r\n|\r|\n/g, EOL); 25 | 26 | expect(solution.trim()).to.equal(output.trim()); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /02-event-loop/01-events-order/README.md: -------------------------------------------------------------------------------- 1 | # Порядок вывода сообщений в консоль 2 | 3 | В данной задаче вам необходимо (не запуская код) определить, в каком порядке будут выводиться 4 | сообщения в консоль. Не расстраивайтесь если вывод не соответствует вашим ожиданиям, попробуйте 5 | понять, в чем вы допустили просчет. 6 | 7 | В качестве решения создайте текстовый файл `solution.txt` в папке с заданием, в котором каждый вывод 8 | начинается с новой строки. 9 | 10 | Например, 11 | ```text 12 | James 13 | Michael 14 | ``` 15 | 16 | Код для запуска находится в файле `index.js`: 17 | ```js 18 | const intervalId = setInterval(() => { 19 | console.log('James'); 20 | }, 10); 21 | 22 | setTimeout(() => { 23 | const promise = new Promise((resolve) => { 24 | console.log('Richard'); 25 | resolve('Robert'); 26 | }); 27 | 28 | promise 29 | .then((value) => { 30 | console.log(value); 31 | 32 | setTimeout(() => { 33 | console.log('Michael'); 34 | 35 | clearInterval(intervalId); 36 | }, 10); 37 | }); 38 | 39 | console.log('John'); 40 | }, 10); 41 | ``` 42 | -------------------------------------------------------------------------------- /reporter.js: -------------------------------------------------------------------------------- 1 | const mocha = require('mocha'); 2 | 3 | function Reporter(runner) { 4 | mocha.reporters.Base.call(this, runner); 5 | let passes = 0; 6 | let failures = 0; 7 | const tests = []; 8 | 9 | runner.on('pass', function(test) { 10 | passes++; 11 | tests.push({ 12 | description: test.title, 13 | success: true, 14 | suite: test.parent.titlePath(), 15 | time: test.duration, 16 | }); 17 | }); 18 | 19 | runner.on('fail', function(test, err) { 20 | failures++; 21 | tests.push({ 22 | description: test.title, 23 | success: false, 24 | suite: test.parent.titlePath(), 25 | time: test.duration, 26 | }); 27 | }); 28 | 29 | runner.on('end', function() { 30 | console.log(JSON.stringify({ 31 | result: { 32 | mocha: tests, 33 | }, 34 | summary: { 35 | success: passes, 36 | failed: failures, 37 | }, 38 | })); 39 | }); 40 | } 41 | 42 | module.exports = Reporter; 43 | 44 | // To have this reporter "extend" a built-in reporter uncomment the following line: 45 | // mocha.utils.inherits(MyReporter, mocha.reporters.Spec); 46 | -------------------------------------------------------------------------------- /05-http-server-streams/02-file-server-post/README.md: -------------------------------------------------------------------------------- 1 | # Файловый сервер - запись файла 2 | 3 | В данной задаче вам необходимо будет реализовать http-сервер, который по запросу пользователя 4 | создавать файл на диске и записывать туда тело запроса. 5 | 6 | - `POST /[filename]` - создание нового файла в папке `files` и запись в него тела запроса. 7 | - Если файл уже есть на диске - сервер должен вернуть ошибку `409`. 8 | - Максимальный размер загружаемого файла не должен превышать 1МБ, при превышении лимита - ошибка 9 | `413`. 10 | - Если в процессе загрузки файла на сервер произошел обрыв соединения — созданный файл с диска 11 | надо удалять. 12 | - Вложенные папки не поддерживаются, при запросе вида `/dir1/dir2/filename` - ошибка `400`. 13 | 14 | При любых ошибках сервер должен, по возможности, возвращать ошибку `500`. 15 | 16 | Для ограничения размера загружаемого файла можно воспользоваться классом `LimitSizeStream` из 2-го 17 | модуля. 18 | 19 | Для выполнения запросов можно воспользоваться программой `postman`. Проверить правильность 20 | реализации можно также с помощью тестов. 21 | 22 | Запуск приложения осуществляется с помощью команды `node index.js`. 23 | -------------------------------------------------------------------------------- /07-mongodb-mongoose/02-rest-api/controllers/products.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | const Product = require('../models/Product'); 3 | 4 | module.exports.productsBySubcategory = async function productsBySubcategory( 5 | ctx, 6 | next 7 | ) { 8 | const { subcategory } = ctx.request.query; 9 | if (!subcategory) { 10 | ctx.response.status = 400; 11 | ctx.body = 'Please, specify subcategory param'; 12 | } else { 13 | const products = await Product.find({ subcategory }); 14 | ctx.response.status = 200; 15 | ctx.body = { products }; 16 | } 17 | }; 18 | 19 | module.exports.productList = async function productList(ctx, next) { 20 | const products = await Product.find({}); 21 | ctx.response.status = 200; 22 | ctx.body = { products }; 23 | }; 24 | 25 | module.exports.productById = async function productById(ctx, next) { 26 | const { id } = ctx.params; 27 | if (!mongoose.Types.ObjectId.isValid(id)) { 28 | ctx.response.status = 400; 29 | ctx.body = 'Invalid product ID'; 30 | } else { 31 | const product = await Product.findById(id); 32 | if (!product) { 33 | ctx.response.status = 404; 34 | ctx.body = 'Product not found'; 35 | } else { 36 | ctx.response.status = 200; 37 | ctx.body = { product }; 38 | } 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /05-http-server-streams/01-file-server-get/server.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const http = require('http'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const server = new http.Server(); 7 | 8 | server.on('request', (req, res) => { 9 | try { 10 | let pathname = url.parse(req.url).pathname.slice(1); 11 | if (pathname.indexOf('/') >= 0) { 12 | res.statusCode = 400; 13 | res.end('Your have to send only a filename!'); 14 | return; 15 | } 16 | const filepath = path.join(__dirname, 'files', pathname); 17 | 18 | switch (req.method) { 19 | case 'GET': 20 | const readStream = fs.createReadStream(filepath).on('error', (err) => { 21 | if (err.code === 'ENOENT') { 22 | res.statusCode = 404; 23 | res.end('No such file or directory'); 24 | } else { 25 | res.statusCode = 500; 26 | res.end('Internal Server Error'); 27 | } 28 | }); 29 | readStream.pipe(res); 30 | break; 31 | 32 | default: 33 | res.statusCode = 501; 34 | res.end('Not implemented'); 35 | } 36 | } catch (err) { 37 | console.log(err); 38 | res.statusCode = 500; 39 | res.end('Internal Server Error'); 40 | } 41 | }); 42 | 43 | module.exports = server; 44 | -------------------------------------------------------------------------------- /06-koajs/01-chat-app/app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Koa = require('koa'); 3 | const app = new Koa(); 4 | const subscribers = {} 5 | 6 | app.use(require('koa-static')(path.join(__dirname, 'public'))); 7 | app.use(require('koa-bodyparser')()); 8 | 9 | const Router = require('koa-router'); 10 | const router = new Router(); 11 | 12 | router.get('/subscribe', async (ctx, next) => { 13 | const id = Math.random(); 14 | subscribers[id] = ctx.res; 15 | await new Promise((resolve, reject) => { 16 | ctx.res.on('close', () => { 17 | delete subscribers[id]; 18 | resolve(`Client id=${id} disconnected`); 19 | }).on('finish', () =>{ 20 | resolve('Message received'); 21 | }) 22 | }) 23 | }); 24 | 25 | router.post('/publish', async (ctx, next) => { 26 | const message = ctx.request.body.message; 27 | if (message) { 28 | for (const id in subscribers) { 29 | //console.log(`your message: ${message} delivered to id: ${id}`); 30 | const res = subscribers[id]; 31 | res.statusCode = 200; 32 | res.end(message); 33 | } 34 | 35 | ctx.response.status = 200; 36 | ctx.response.body = 'You have successfully send message'; 37 | } else { 38 | ctx.response.status = 400; 39 | ctx.response.body = 'Your message is empty!'; 40 | } 41 | }); 42 | 43 | app.use(router.routes()); 44 | 45 | module.exports = app; 46 | -------------------------------------------------------------------------------- /03-streams/02-line-split-stream/README.md: -------------------------------------------------------------------------------- 1 | # Стрим, разбивающий текст на строки 2 | 3 | При работе с большими объемами текстовых данных при помощи стримов очень часто возникает задача 4 | разбить данные по какому-то ключу или признаку. Например, при обработке большого CSV файла 5 | необходимо разбить данные по символу переноса строки, для того, чтобы потоково обработать каждый 6 | элемент файла. 7 | 8 | В текущей задаче вам потребуется написать `LineSplitStream` - стрим, который принимает текстовые 9 | данные, а отдает их же, но уже построчно, например: 10 | 11 | ```js 12 | 13 | const LineSplitStream = require('./LineSplitStream'); 14 | const os = require('os'); 15 | 16 | const lines = new LineSplitStream({ 17 | encoding: 'utf-8', 18 | }); 19 | 20 | function onData(line) { 21 | console.log(line); 22 | } 23 | 24 | lines.on('data', onData); 25 | 26 | lines.write(`первая строка${os.EOL}вторая строка${os.EOL}третья строка`); 27 | 28 | lines.end(); 29 | 30 | ``` 31 | 32 | В результате выполнения кода выше функция `onData` будет вызвана три раза. 33 | 34 | Символ переноса строки отличаются для разных операционных систем, в Windows - это `\r\n`, в Mac и 35 | Linux - `\n`. Для того, чтобы наш код работал корректно во всех операционных системах можно 36 | воспользоваться свойством `os.EOL` модуля `os`, которое будет содержать корректный символ для той 37 | ОС, на которой запущена программа. 38 | -------------------------------------------------------------------------------- /05-http-server-streams/03-file-server-delete/server.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const http = require('http'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const server = new http.Server(); 7 | 8 | server.on('request', async (req, res) => { 9 | try { 10 | let pathname = url.parse(req.url).pathname.slice(1); 11 | if (pathname.indexOf('/') >= 0) { 12 | res.statusCode = 400; 13 | res.end('Your have to send only a filename!'); 14 | return; 15 | } 16 | 17 | const filepath = path.join(__dirname, 'files', pathname); 18 | 19 | if (req.method === 'DELETE') { 20 | fs.unlink(filepath, (error) => { 21 | if (error) { 22 | if (error.code === 'ENOENT') { 23 | res.statusCode = 404; 24 | res.end('File does not exist'); 25 | } else { 26 | console.log(error); 27 | res.statusCode = 500; 28 | res.end(error.message); 29 | } 30 | } else { 31 | res.statusCode = 200; 32 | res.end('File successfully deleted'); 33 | } 34 | }); 35 | } else { 36 | res.statusCode = 501; 37 | res.end(`Requested ${req.method} method is not implemented`); 38 | } 39 | } catch (err) { 40 | console.log(err); 41 | res.statusCode = 500; 42 | res.end('Internal Server Error'); 43 | } 44 | }); 45 | 46 | module.exports = server; 47 | -------------------------------------------------------------------------------- /local.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const Mocha = require('mocha'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const glob = require('glob'); 7 | 8 | require('colors'); 9 | 10 | const _module = process.argv[2]; 11 | const task = process.argv[3]; 12 | 13 | if (!_module) { 14 | return console.error( 15 | `${'Не указан модуль с задачей. Например:'.red.bold} 16 | ${'npm run test:local 01-intro 01-sum'.yellow}` 17 | ); 18 | } 19 | 20 | if (!task) { 21 | return console.error( 22 | `${'Не указана задача. Например:'.red.bold} 23 | ${'npm run test:local 01-intro 01-sum'.yellow}` 24 | ); 25 | } 26 | 27 | const mocha = new Mocha({ 28 | reporter: 'spec', 29 | useColors: true, 30 | }); 31 | 32 | 33 | const testDir = path.join(__dirname, _module, task, 'test'); 34 | 35 | if (!fs.existsSync(testDir)) { 36 | return console.error( 37 | `${'Задача'.red.bold} ${`${_module}/${task}`.yellow} ${'отсутствует. Проверьте правильность команды.'.red.bold}` 38 | ); 39 | } 40 | 41 | const files = glob.sync(`${testDir}/**/*.test.js`); 42 | 43 | if (!files.length) { 44 | return console.error( 45 | `${'К задаче'.red.bold} ${`${_module}/${task}`.yellow} ${'отсутствуют тесты'.red.bold}` 46 | ); 47 | } 48 | 49 | files.forEach(file => { 50 | mocha.addFile(file); 51 | }); 52 | 53 | // Run the tests. 54 | mocha.run(function(failures) { 55 | process.exitCode = failures ? 1 : 0; 56 | }); 57 | -------------------------------------------------------------------------------- /06-koajs/01-chat-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |Добро пожаловать в чат!
9 | 10 | 14 | 15 |