├── chat-app ├── .gitignore ├── package.json ├── index.html └── server.js ├── mflix-js ├── .gitignore ├── build │ ├── favicon.ico │ ├── static │ │ ├── media │ │ │ ├── mongoleaf.0ebc1843.png │ │ │ └── pixelatedLeaf.6c93bd20.svg │ │ ├── js │ │ │ ├── runtime~main.229c360f.js │ │ │ └── runtime~main.229c360f.js.map │ │ └── css │ │ │ └── main.d2c98b4b.chunk.css │ ├── manifest.json │ ├── precache-manifest.a719d5b08722739b116177c801bfc531.js │ ├── asset-manifest.json │ ├── service-worker.js │ └── index.html ├── index.js ├── test │ ├── config │ │ ├── setup.js │ │ ├── teardown.js │ │ └── mongoEnvironment.js │ ├── timeouts.test.js │ ├── connection-pooling.test.js │ ├── error-handling.test.js │ ├── migration.test.js │ ├── user-report.test.js │ ├── projection.test.js │ ├── db-connection.test.js │ ├── get-comments.test.js │ ├── facets.test.js │ ├── delete-comments.test.js │ ├── user-management.test.js │ ├── user-preferences.test.js │ ├── create-update-comments.test.js │ ├── text-subfield.test.js │ ├── paging.test.js │ └── lessons │ │ ├── mongoclient.spec.js │ │ ├── change-updates.js │ │ ├── writes-with-error-handling.spec.js │ │ ├── callbacks-promises-async.spec.js │ │ ├── change-insert.js │ │ ├── basic-writes.spec.js │ │ ├── basic-deletes.spec.js │ │ ├── basic-reads.spec.js │ │ ├── cursor-methods-agg-equivalents.spec.js │ │ └── basic-updates.spec.js ├── mflix │ └── src │ │ └── main │ │ └── resources │ │ └── build │ │ ├── favicon.ico │ │ ├── static │ │ ├── media │ │ │ ├── mongoleaf.0ebc1843.png │ │ │ └── pixelatedLeaf.6c93bd20.svg │ │ ├── js │ │ │ ├── runtime~main.229c360f.js │ │ │ └── runtime~main.229c360f.js.map │ │ └── css │ │ │ └── main.c55712aa.chunk.css │ │ ├── manifest.json │ │ ├── precache-manifest.bc43c92732837d0c02c5ebc5f172828d.js │ │ ├── asset-manifest.json │ │ ├── service-worker.js │ │ └── index.html ├── jest.config.js ├── .eslintrc ├── .prettierrc ├── dotenv_win ├── .env ├── src │ ├── api │ │ ├── users.route.js │ │ ├── movies.route.js │ │ ├── comments.controller.js │ │ ├── movies.controller.js │ │ └── users.controller.js │ ├── server.js │ ├── index.js │ ├── migrations │ │ └── movie-last-updated-migration.js │ └── dao │ │ ├── commentsDAO.js │ │ ├── usersDAO.js │ │ └── moviesDAO.js ├── .babelrc ├── package.json └── README.rst ├── curd-filedb-app ├── .gitignore ├── server.js ├── file.json ├── utill │ └── file.utill.js ├── package.json ├── model │ └── file.model.js └── controller │ └── file.route.js ├── js ├── Basic │ ├── HelloWorld.js │ ├── nullandBool.js │ ├── Celsius.js │ ├── Variable.js │ └── assignFun.js ├── intermidiate │ ├── Auhentication.js │ ├── thisKeyword.js │ ├── myObjects.js │ └── ToDo.js ├── Projectjs │ └── Trello.js ├── jsIfElse │ ├── Scope.js │ ├── forLoop.js │ ├── IfElse.js │ ├── KingsTeritoryIssue.js │ └── logicalOps.js ├── Array │ ├── ForEachExample.js │ ├── Marvels.js │ └── ThreeMethods.js └── funtions │ ├── DefultParameter.js │ └── sayHello.js ├── clouser.js ├── demo.js ├── scope.js ├── patteren.js ├── isEvenRecur.js ├── countChar.js ├── chess.js ├── fizzBuzz.js ├── recursive.js ├── sumRange.js ├── reverseArr.js └── list.js /chat-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /mflix-js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /curd-filedb-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /js/Basic/HelloWorld.js: -------------------------------------------------------------------------------- 1 | console.log('Hello World'); 2 | -------------------------------------------------------------------------------- /js/Basic/nullandBool.js: -------------------------------------------------------------------------------- 1 | let actualmarks =10; 2 | let value = (actualmarks < 10) 3 | console.log(value); 4 | -------------------------------------------------------------------------------- /js/intermidiate/Auhentication.js: -------------------------------------------------------------------------------- 1 | // User authentication 2 | let userEmail = 'loco'; 3 | let password = '1234'; 4 | -------------------------------------------------------------------------------- /js/Basic/Celsius.js: -------------------------------------------------------------------------------- 1 | let tempFernehite = 200; 2 | let celcius = (tempFernehite - 32)*5/9; 3 | console.log(celcius); 4 | -------------------------------------------------------------------------------- /mflix-js/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekharasumansahu/JavaScript-HiteshChoudhary-/HEAD/mflix-js/build/favicon.ico -------------------------------------------------------------------------------- /mflix-js/index.js: -------------------------------------------------------------------------------- 1 | require("@babel/register") 2 | require("dotenv").config() 3 | 4 | exports = module.exports = require("./src") 5 | -------------------------------------------------------------------------------- /clouser.js: -------------------------------------------------------------------------------- 1 | let outer = (x)=>{ 2 | return (y)=>{ 3 | return x+y; 4 | } 5 | } 6 | let call = outer(10); 7 | console.log(call(20)); 8 | -------------------------------------------------------------------------------- /js/Basic/Variable.js: -------------------------------------------------------------------------------- 1 | let name1='Sekhar' 2 | //console.log(name) 3 | const name = 'sekhu' 4 | //name = 'suman' 5 | console.log(name); 6 | -------------------------------------------------------------------------------- /mflix-js/test/config/setup.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | module.exports = async function() { 3 | console.log("Setup Mongo Connection") 4 | } 5 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | let power = (num, power)=>{ 2 | let count = 1; 3 | let sum = 1; 4 | while(count <= power){ 5 | sum *= num; 6 | count ++; 7 | } 8 | return sum; 9 | } 10 | -------------------------------------------------------------------------------- /mflix-js/build/static/media/mongoleaf.0ebc1843.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekharasumansahu/JavaScript-HiteshChoudhary-/HEAD/mflix-js/build/static/media/mongoleaf.0ebc1843.png -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekharasumansahu/JavaScript-HiteshChoudhary-/HEAD/mflix-js/mflix/src/main/resources/build/favicon.ico -------------------------------------------------------------------------------- /mflix-js/test/config/teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async function() { 2 | console.log("Teardown Mongo Connection") 3 | delete global.mflixClient 4 | delete global.mflixDB 5 | } 6 | -------------------------------------------------------------------------------- /scope.js: -------------------------------------------------------------------------------- 1 | let outer = ()=>{ 2 | let x = 10; 3 | let inner = ()=>{ 4 | let y = 20; 5 | return console.log(x, y); 6 | } 7 | //console.log(x,y); 8 | } 9 | console.log(outer()); 10 | -------------------------------------------------------------------------------- /mflix-js/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globalSetup: "./test/config/setup.js", 3 | globalTeardown: "./test/config/teardown.js", 4 | testEnvironment: "./test/config/mongoEnvironment", 5 | } 6 | -------------------------------------------------------------------------------- /mflix-js/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "prettier"], 3 | "rules": { 4 | "jsx-a11y/href-no-hash": "off", 5 | "jsx-a11y/anchor-is-valid": ["warn", { "aspects": ["invalidHref"] }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mflix-js/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "singleQuote": false, 6 | "trailingComma": "all", 7 | "parser": "babylon", 8 | "semi": false 9 | } 10 | -------------------------------------------------------------------------------- /js/Projectjs/Trello.js: -------------------------------------------------------------------------------- 1 | const Todo =[]; 2 | Todo.push('One'); 3 | Todo.push('Second'); 4 | Todo.push('Three'); 5 | 6 | Todo.forEach(function(todo,index){ 7 | console.log(`Number ${index+1} is ${todo}`); 8 | }) 9 | -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/static/media/mongoleaf.0ebc1843.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sekharasumansahu/JavaScript-HiteshChoudhary-/HEAD/mflix-js/mflix/src/main/resources/build/static/media/mongoleaf.0ebc1843.png -------------------------------------------------------------------------------- /js/jsIfElse/Scope.js: -------------------------------------------------------------------------------- 1 | let globalvalue = 'sekhar' 2 | if (true) { 3 | //let localvalue = 'suman' 4 | var localvalue = 'suman' 5 | console.log(localvalue); 6 | console.log(globalvalue); 7 | } 8 | console.log(localvalue); 9 | -------------------------------------------------------------------------------- /js/Array/ForEachExample.js: -------------------------------------------------------------------------------- 1 | var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jly', 'Aug', 'Sep', 'Oct', 'Nov', "Dec"] 2 | 3 | months.forEach(function(mnth, index) { 4 | console.log(`The ${index+1} month is ${mnth}`); 5 | }) 6 | -------------------------------------------------------------------------------- /patteren.js: -------------------------------------------------------------------------------- 1 | function printPatteren(symbol, count){ 2 | let string = symbol; 3 | let times = count; 4 | while(string.length <= times){ 5 | console.log(string); 6 | string += symbol; 7 | } 8 | } 9 | printPatteren('#', 7); 10 | printPatteren('*', 10); 11 | -------------------------------------------------------------------------------- /isEvenRecur.js: -------------------------------------------------------------------------------- 1 | function isEven(num){ 2 | if(num < 0){ 3 | return 'Invalid Negative Number'; 4 | } 5 | if(num == 0){ 6 | return true; 7 | } else if(num == 1){ 8 | return false; 9 | } else { 10 | return isEven(num -2); 11 | } 12 | } 13 | console.log(isEven(-75)); 14 | -------------------------------------------------------------------------------- /countChar.js: -------------------------------------------------------------------------------- 1 | 2 | function countChar(value, char) { 3 | let i = 0; 4 | let count = 0; 5 | while (i < value.length) { 6 | if (value[i] == char) { 7 | count++; 8 | } 9 | i++; 10 | } 11 | console.log(count); 12 | } 13 | 14 | countChar('Long Live the king In the North..', ' '); -------------------------------------------------------------------------------- /js/jsIfElse/forLoop.js: -------------------------------------------------------------------------------- 1 | const days = ['First','Second','Thired','Fourth','Fifth','Sixth','Seventh']; 2 | 3 | // for (var i = 0; i < days.length; i++) { 4 | // console.log(days[i]); 5 | // } 6 | 7 | for (var i = days.length-1; i >=0; i--) { 8 | console.log(days[i]); 9 | } 10 | -------------------------------------------------------------------------------- /curd-filedb-app/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | app.use(express.json()); 5 | app.use(require('./controller/file.route')); 6 | 7 | app.listen(5000, (err)=>{ 8 | if(err) console.log(err); 9 | console.log('Server running on port '+ 5000); 10 | }) -------------------------------------------------------------------------------- /js/intermidiate/thisKeyword.js: -------------------------------------------------------------------------------- 1 | let MyClass = { 2 | name : 'Sekhar', 3 | id : 18261, 4 | email : 'sekharsahu143@gmail.com', 5 | dept : 'ServiceNow', 6 | 7 | getDept : function () { 8 | return `Dept is ${this.dept}` 9 | } 10 | 11 | } 12 | 13 | console.log(MyClass.getDept()); 14 | -------------------------------------------------------------------------------- /mflix-js/dotenv_win: -------------------------------------------------------------------------------- 1 | # Ticket: Connection 2 | # Rename this file to .env after filling in your MFLIX_DB_URI and your SECRET_KEY 3 | # Do not surround the URI with quotes 4 | SECRET_KEY=super_secret_key_you_should_change 5 | MFLIX_DB_URI=mongodb+srv://m220student:m220password@ 6 | MFLIX_NS=sample_mflix 7 | PORT=5000 8 | -------------------------------------------------------------------------------- /js/jsIfElse/IfElse.js: -------------------------------------------------------------------------------- 1 | var studentMarks = 0; 2 | if(studentMarks == 10) { 3 | console.log('very good keep it up.'); 4 | } else if (studentMarks == 5) { 5 | console.log('Good, But you need to work hard.'); 6 | } else if (studentMarks == 3){ 7 | console.log('very poor'); 8 | } else { 9 | console.log('Sorry you failed.'); 10 | } 11 | -------------------------------------------------------------------------------- /mflix-js/.env: -------------------------------------------------------------------------------- 1 | # Ticket: Connection 2 | # Rename this file to .env after filling in your MFLIX_DB_URI and your SECRET_KEY 3 | # Do not surround the URI with quotes 4 | SECRET_KEY=mysecretkey 5 | MFLIX_DB_URI=mongodb+srv://m001-student:Sekharsahu@123@cluster0.ysylc.mongodb.net/sample_mflix?retryWrites=true&w=majority 6 | MFLIX_NS=sample_mflix 7 | PORT=5000 8 | -------------------------------------------------------------------------------- /js/jsIfElse/KingsTeritoryIssue.js: -------------------------------------------------------------------------------- 1 | //let king = 'sekhar'; 2 | 3 | if (true) { 4 | //let king = 'sekhu 1' 5 | if (true) { 6 | king = 'sekhu 2'; //if a variable is not differentiated as let or var bydefult those are global variable. 7 | console.log(king); 8 | } 9 | } 10 | 11 | if (true) { 12 | console.log(king); 13 | } 14 | -------------------------------------------------------------------------------- /chess.js: -------------------------------------------------------------------------------- 1 | function chess(length){ 2 | let size = length; 3 | let string = ''; 4 | for(let i = 1; i<= size; i++){ 5 | for(let j = 1; j <= size; j++){ 6 | if((i+j) % 2 == 0){ 7 | string += ' '; 8 | }else { 9 | string += '#'; 10 | } 11 | 12 | if(j == size){ 13 | string += '\n'; 14 | } 15 | } 16 | } 17 | console.log(string); 18 | } 19 | chess(16); 20 | -------------------------------------------------------------------------------- /mflix-js/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mflix", 3 | "name": "Mflix", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /mflix-js/test/timeouts.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Timeouts", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Timeout is set to 2500 milliseconds", async () => { 9 | const { wtimeout } = await MoviesDAO.getConfiguration() 10 | expect(wtimeout).toBe(2500) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /js/jsIfElse/logicalOps.js: -------------------------------------------------------------------------------- 1 | let isUser = false; 2 | let isPaid = false; 3 | let Isverified = false; 4 | let isRegistered =false; 5 | 6 | if (isUser && isRegistered && isPaid && Isverified) { 7 | console.log('Welcome folk.'); 8 | } else if ( isUser || Isverified || isRegistered){ 9 | console.log('Welcome, please subscribe soon!'); 10 | } else { 11 | console.log('please register !'); 12 | } 13 | -------------------------------------------------------------------------------- /mflix-js/test/connection-pooling.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Connection Pooling", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Connection pool size is 50", async () => { 9 | const response = await MoviesDAO.getConfiguration() 10 | expect(response.poolSize).toBe(50) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Mflix", 3 | "name": "Mflix", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /fizzBuzz.js: -------------------------------------------------------------------------------- 1 | function fizzBuzz(count){ 2 | let times = count; 3 | let counter = 1; 4 | while(counter <= times){ 5 | if(counter % 3 == 0){ 6 | if(counter % 5 == 0) { 7 | console.log('FizzBuzz'); 8 | } 9 | console.log('Fizz'); 10 | 11 | } else if (counter % 5 == 0) { 12 | console.log('Buzz'); 13 | } else { 14 | console.log(counter); 15 | } 16 | counter ++; 17 | } 18 | } 19 | fizzBuzz(100); 20 | -------------------------------------------------------------------------------- /curd-filedb-app/file.json: -------------------------------------------------------------------------------- 1 | [{"id":887,"title":"Second data","description":"This data just got updated 4"},{"id":19,"title":"Fifth save","description":"This is the fifth insertion into file."},{"id":867,"title":"Sixth save","description":"This is the Sixth insertion into file."},{"id":876,"title":"Sevn save","description":"This is the seventh insertion into file."},{"id":760,"title":"Eight save","description":"This is the eight insertion into file."}] -------------------------------------------------------------------------------- /js/funtions/DefultParameter.js: -------------------------------------------------------------------------------- 1 | // let myMulti = function (num1 , num2) { 2 | // return num1*num2; 3 | // } 4 | // 5 | // console.log(myMulti(2,3)); 6 | 7 | 8 | let guestUser = function (name='YourName', courseCount='predefined') { // Known as defult parameters that they come when ever no parameters are passed. 9 | return 'Hello '+name+ ' yours course count is '+courseCount; 10 | } 11 | console.log(guestUser('Sekhu',5)); 12 | -------------------------------------------------------------------------------- /mflix-js/test/error-handling.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | const badObjectId = "helloworld" 4 | 5 | describe("Get Comments", () => { 6 | beforeAll(async () => { 7 | await MoviesDAO.injectDB(global.mflixClient) 8 | }) 9 | 10 | test("Handles invalid ID error correctly", async () => { 11 | const response = await MoviesDAO.getMovieByID(badObjectId) 12 | expect(response).toBeNull() 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /js/Array/Marvels.js: -------------------------------------------------------------------------------- 1 | const superheros = ['Iron Man', 'SpiderMan', 'Captain America']; 2 | console.log(superheros); 3 | console.log(superheros[0]); 4 | console.log(superheros[2]); 5 | console.log(superheros.length); 6 | console.log(superheros[superheros.length-1]); 7 | 8 | console.log('We have '+superheros.length+' named as'); 9 | console.log(`We have ${superheros.length} superheros firstone is ${superheros[0]} and last one is ${superheros[superheros.length-1]}`); 10 | -------------------------------------------------------------------------------- /recursive.js: -------------------------------------------------------------------------------- 1 | function findSolution(target) { 2 | function find(current, history) { 3 | if (current == target) { 4 | return history; 5 | } else if (current > target) { 6 | return null; 7 | } else { 8 | return find(current + 5, `(${history} + 5)`) || 9 | find(current * 3, `(${history} * 3)`); 10 | } 11 | } 12 | return find(1, "1"); 13 | } 14 | 15 | console.log(findSolution(24)); 16 | // → (((1 * 3) + 5) * 3) -------------------------------------------------------------------------------- /curd-filedb-app/utill/file.utill.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | 4 | module.exports = { 5 | async getUid () { 6 | return Math.floor(Math.random() * 1000); 7 | }, 8 | 9 | async getFileArr () { 10 | let data = fs.readFileSync('./file.json'); 11 | return JSON.parse(data); 12 | }, 13 | 14 | async writeFile(dataArr) { 15 | let content = JSON.stringify(dataArr); 16 | fs.writeFileSync('./file.json', content, 'utf8', (err) => { 17 | if (err) console.log(err); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /js/intermidiate/myObjects.js: -------------------------------------------------------------------------------- 1 | let courseData = { 2 | Title : 'Java Script', 3 | Author : 'Hitesh Choudhary', 4 | Duration : '10hrs', 5 | Description : 'This course teaches the basics of java Script language', 6 | Price : '10$' 7 | } 8 | 9 | console.log(courseData); 10 | console.log(`There is a new course available "${courseData.Title}" by "${courseData.Author}" of duration "${courseData.Duration}" with description "${courseData.Description}" at price "${courseData.Price}".`); 11 | -------------------------------------------------------------------------------- /mflix-js/test/migration.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | import { ObjectId } from "mongodb" 3 | 4 | describe("Migration", () => { 5 | beforeAll(async () => { 6 | await MoviesDAO.injectDB(global.mflixClient) 7 | }) 8 | 9 | test("migration", async () => { 10 | const movie = await MoviesDAO.movies.findOne({ 11 | _id: ObjectId("573a1391f29313caabcd82da"), 12 | lastupdated: { $type: "date" }, 13 | }) 14 | expect(movie).not.toBeNull() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /sumRange.js: -------------------------------------------------------------------------------- 1 | function range(start, end, step = 1) { 2 | let range = []; 3 | 4 | if (step > 0) { 5 | while(start <= end) { 6 | range.push(start); 7 | start += step; 8 | } 9 | } else { 10 | while(end >= start) { 11 | range.push(end); 12 | end += step; 13 | } 14 | } 15 | console.log(range); 16 | return range; 17 | } 18 | 19 | function sum(range){ 20 | let sum = 0; 21 | range.forEach((r)=>{ 22 | sum += r; 23 | }) 24 | return sum; 25 | } 26 | 27 | console.log(sum(range(1,1000, 5))); -------------------------------------------------------------------------------- /js/funtions/sayHello.js: -------------------------------------------------------------------------------- 1 | // let sayHello = function (name,city) { 2 | // console.log(`Hello ${name}`); 3 | // console.log(`Wellcome to ${city}`); 4 | // } 5 | // sayHello('Sekhar','Ganjam'); 6 | 7 | 8 | // let fullNameMaker = function (firstname, lastname) { 9 | // console.log(`Welcome to LCO ${firstname} ${lastname}`); 10 | // } 11 | // 12 | // fullNameMaker('Sekhar','sahu'); 13 | 14 | let myAdd = function (num1, num2) { 15 | console.log(num1+num2); 16 | return num1+num2; 17 | 18 | } 19 | 20 | let addition = myAdd(2,5); 21 | console.log(addition); 22 | -------------------------------------------------------------------------------- /js/Array/ThreeMethods.js: -------------------------------------------------------------------------------- 1 | const numbers = ['One','Two','Three','Four','Five','Six']; 2 | // numbers[1] = 'Something'; 3 | // console.log(numbers); 4 | // 5 | // console.log(numbers.shift()); //Shift the first number 6 | // console.log(numbers); 7 | // 8 | // numbers.unshift('Something'); 9 | // console.log(numbers); 10 | // 11 | // console.log(numbers.pop()); // Removes the last element 12 | // console.log(numbers); 13 | // 14 | // numbers.push('Seven'); // Adds value at last 15 | // console.log(numbers); 16 | 17 | numbers.splice(2,1,'Sekhar'); 18 | console.log(numbers); 19 | -------------------------------------------------------------------------------- /curd-filedb-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curd-filedb-app", 3 | "version": "1.0.0", 4 | "description": "CURD REST Api using json file as DB", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "test", 8 | "start" : "node server.js", 9 | "dev" : "nodemon server.js" 10 | }, 11 | "keywords": [ 12 | "REST", 13 | "CURD", 14 | "FILEDB" 15 | ], 16 | "author": "sekhara sahu ", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "nodemon": "^2.0.5" 20 | }, 21 | "dependencies": { 22 | "express": "^4.17.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chat-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-app", 3 | "version": "1.0.0", 4 | "description": "Real time chatt app", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "test", 8 | "start" : "nodemon server.js" 9 | }, 10 | "keywords": [ 11 | "REAL", 12 | "TIME", 13 | "CHATT", 14 | "APP" 15 | ], 16 | "author": "sekhara sahu ", 17 | "license": "ISC", 18 | "dependencies": { 19 | "express": "^4.17.1", 20 | "mongodb": "^3.6.2", 21 | "socket.io": "^2.3.0" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reverseArr.js: -------------------------------------------------------------------------------- 1 | function reverse(arr) { 2 | let newArr = []; 3 | for (let i = arr.length - 1; i >= 0; i--) { 4 | newArr.push(arr[i]); 5 | } 6 | return newArr; 7 | } 8 | 9 | function reversePlace(arr){ 10 | let head = 0; 11 | let tail = arr.length - 1; 12 | 13 | while (tail > head) { 14 | 15 | arr[head] = arr[head] + arr[tail]; 16 | arr[tail] = arr[head] - arr[tail]; 17 | arr[head] = arr[head] - arr[tail]; 18 | 19 | head ++; 20 | tail --; 21 | } 22 | return arr; 23 | } 24 | 25 | 26 | let arr = [ 27 | 1,2,3,4,5,6,7,8,9,10,11,12 28 | ]; 29 | console.log(reverse(arr)); 30 | console.log(reversePlace(arr)); -------------------------------------------------------------------------------- /js/Basic/assignFun.js: -------------------------------------------------------------------------------- 1 | var totalMarks = 100; 2 | var currentMarks = 69; 3 | 4 | var calculateGrade = function (cMarks, tMarks) { 5 | let percentage = (cMarks/tMarks)*100; 6 | let MyGrade = '' 7 | if (percentage >= 90) { 8 | MyGrade='A'; 9 | } else if (percentage >= 70 && percentage <= 90) { 10 | MyGrade ='B'; 11 | } else if (percentage >= 50 && percentage <= 70) { 12 | MyGrade = 'C'; 13 | } else { 14 | MyGrade = 'F'; 15 | } 16 | return `Your grade is '${MyGrade}' and marks is ${currentMarks} from totalMarks ${totalMarks}` 17 | } 18 | var result = calculateGrade(currentMarks,totalMarks); 19 | console.log(result); 20 | -------------------------------------------------------------------------------- /mflix-js/src/api/users.route.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express" 2 | import usersCtrl from "./users.controller" 3 | import commentsCtrl from "./comments.controller" 4 | 5 | const router = new Router() 6 | 7 | // associate put, delete, and get(id) 8 | router.route("/register").post(usersCtrl.register) 9 | router.route("/login").post(usersCtrl.login) 10 | router.route("/logout").post(usersCtrl.logout) 11 | router.route("/delete").delete(usersCtrl.delete) 12 | router.route("/update-preferences").put(usersCtrl.save) 13 | router.route("/comment-report").get(commentsCtrl.apiCommentReport) 14 | router.route("/make-admin").post(usersCtrl.createAdminUser) 15 | 16 | export default router 17 | -------------------------------------------------------------------------------- /mflix-js/test/user-report.test.js: -------------------------------------------------------------------------------- 1 | import CommentsDAO from "../src/dao/commentsDAO" 2 | 3 | describe("User Report", () => { 4 | beforeAll(async () => { 5 | await CommentsDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Should return exactly twenty results", async () => { 9 | const userReport = await CommentsDAO.mostActiveCommenters() 10 | expect(userReport.length).toBe(20) 11 | }) 12 | 13 | test("Should return correct comment totals", async () => { 14 | const userReport = await CommentsDAO.mostActiveCommenters() 15 | const topCommenter = userReport[0] 16 | expect(topCommenter).toEqual({ 17 | _id: "roger_ashton-griffiths@gameofthron.es", 18 | count: 331, 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /chat-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 |

Hello World...

28 | 29 | -------------------------------------------------------------------------------- /mflix-js/src/server.js: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import bodyParser from "body-parser" 3 | import cors from "cors" 4 | import morgan from "morgan" 5 | import movies from "../src/api/movies.route" 6 | import users from "../src/api/users.route" 7 | 8 | const app = express() 9 | 10 | app.use(cors()) 11 | process.env.NODE_ENV !== "prod" && app.use(morgan("dev")) 12 | app.use(bodyParser.json()) 13 | app.use(bodyParser.urlencoded({ extended: true })) 14 | 15 | // Register api routes 16 | app.use("/api/v1/movies", movies) 17 | app.use("/api/v1/user", users) 18 | app.use("/status", express.static("build")) 19 | app.use("/", express.static("build")) 20 | app.use("*", (req, res) => res.status(404).json({ error: "not found" })) 21 | 22 | export default app 23 | -------------------------------------------------------------------------------- /mflix-js/src/api/movies.route.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express" 2 | import MoviesCtrl from "./movies.controller" 3 | import CommentsCtrl from "./comments.controller" 4 | 5 | const router = new Router() 6 | 7 | // associate put, delete, and get(id) 8 | router.route("/").get(MoviesCtrl.apiGetMovies) 9 | router.route("/search").get(MoviesCtrl.apiSearchMovies) 10 | router.route("/countries").get(MoviesCtrl.apiGetMoviesByCountry) 11 | router.route("/facet-search").get(MoviesCtrl.apiFacetedSearch) 12 | router.route("/id/:id").get(MoviesCtrl.apiGetMovieById) 13 | router.route("/config-options").get(MoviesCtrl.getConfig) 14 | 15 | router 16 | .route("/comment") 17 | .post(CommentsCtrl.apiPostComment) 18 | .put(CommentsCtrl.apiUpdateComment) 19 | .delete(CommentsCtrl.apiDeleteComment) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /js/intermidiate/ToDo.js: -------------------------------------------------------------------------------- 1 | let MySchedule = { 2 | day : 'Mon', 3 | meeting : 0, 4 | meetdone : 0, 5 | } 6 | 7 | let addMeet = function (schedule, meet) { 8 | schedule.meeting += meet; 9 | } 10 | 11 | let subMeet = function (schedule, meet) { 12 | schedule.meetdone += meet; 13 | } 14 | 15 | let summary = function (schedule) { 16 | return `Day ${schedule.day}, Total meet : ${schedule.meeting}, meetdone : ${schedule.meetdone}, Remaining : ${schedule.meeting - schedule.meetdone}` 17 | } 18 | 19 | let reset = function (schedule) { 20 | schedule.day = ''; 21 | schedule.meeting = 0; 22 | schedule.meetdone = 0; 23 | } 24 | 25 | console.log(MySchedule); 26 | addMeet(MySchedule, 5); 27 | subMeet(MySchedule, 3); 28 | console.log(MySchedule); 29 | let summry = summary(MySchedule); 30 | console.log(summry); 31 | reset(MySchedule); 32 | console.log(MySchedule); 33 | -------------------------------------------------------------------------------- /mflix-js/build/precache-manifest.a719d5b08722739b116177c801bfc531.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "02d67aeb5a489b91dfe0", 4 | "url": "/static/css/main.d2c98b4b.chunk.css" 5 | }, 6 | { 7 | "revision": "02d67aeb5a489b91dfe0", 8 | "url": "/static/js/main.02d67aeb.chunk.js" 9 | }, 10 | { 11 | "revision": "908cc23a38d7e569f683", 12 | "url": "/static/js/1.908cc23a.chunk.js" 13 | }, 14 | { 15 | "revision": "229c360febb4351a89df", 16 | "url": "/static/js/runtime~main.229c360f.js" 17 | }, 18 | { 19 | "revision": "0ebc18432c0a71cfd876a27933f91a4a", 20 | "url": "/static/media/mongoleaf.0ebc1843.png" 21 | }, 22 | { 23 | "revision": "6c93bd2010905059e08c8b522adfc219", 24 | "url": "/static/media/pixelatedLeaf.6c93bd20.svg" 25 | }, 26 | { 27 | "revision": "d9771c3493c1b6e3743ded7cf5102068", 28 | "url": "/index.html" 29 | } 30 | ]; -------------------------------------------------------------------------------- /mflix-js/test/config/mongoEnvironment.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require("mongodb").MongoClient 2 | const NodeEnvironment = require("jest-environment-node") 3 | module.exports = class MongoEnvironment extends NodeEnvironment { 4 | async setup() { 5 | if (!this.global.mflixClient) { 6 | this.global.mflixClient = await MongoClient.connect( 7 | process.env.MFLIX_DB_URI, 8 | // TODO: Connection Pooling 9 | // Set the connection pool size to 50 for the testing environment. 10 | // TODO: Timeouts 11 | // Set the write timeout limit to 2500 milliseconds for the testing environment. 12 | { useNewUrlParser: true }, 13 | ) 14 | await super.setup() 15 | } 16 | } 17 | 18 | async teardown() { 19 | await this.global.mflixClient.close() 20 | await super.teardown() 21 | } 22 | 23 | runScript(script) { 24 | return super.runScript(script) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/precache-manifest.bc43c92732837d0c02c5ebc5f172828d.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "a005e40fea81192b94cb", 4 | "url": "/static/css/main.c55712aa.chunk.css" 5 | }, 6 | { 7 | "revision": "a005e40fea81192b94cb", 8 | "url": "/static/js/main.a005e40f.chunk.js" 9 | }, 10 | { 11 | "revision": "18f02ebe3183a00be104", 12 | "url": "/static/js/1.18f02ebe.chunk.js" 13 | }, 14 | { 15 | "revision": "229c360febb4351a89df", 16 | "url": "/static/js/runtime~main.229c360f.js" 17 | }, 18 | { 19 | "revision": "0ebc18432c0a71cfd876a27933f91a4a", 20 | "url": "/static/media/mongoleaf.0ebc1843.png" 21 | }, 22 | { 23 | "revision": "6c93bd2010905059e08c8b522adfc219", 24 | "url": "/static/media/pixelatedLeaf.6c93bd20.svg" 25 | }, 26 | { 27 | "revision": "0dfd306019869e2cc8f1afa2d3c27110", 28 | "url": "/index.html" 29 | } 30 | ]; -------------------------------------------------------------------------------- /mflix-js/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/static/css/main.d2c98b4b.chunk.css", 3 | "main.js": "/static/js/main.02d67aeb.chunk.js", 4 | "main.js.map": "/static/js/main.02d67aeb.chunk.js.map", 5 | "static/js/1.908cc23a.chunk.js": "/static/js/1.908cc23a.chunk.js", 6 | "static/js/1.908cc23a.chunk.js.map": "/static/js/1.908cc23a.chunk.js.map", 7 | "runtime~main.js": "/static/js/runtime~main.229c360f.js", 8 | "runtime~main.js.map": "/static/js/runtime~main.229c360f.js.map", 9 | "static/media/mongoleaf.png": "/static/media/mongoleaf.0ebc1843.png", 10 | "static/media/pixelatedLeaf.svg": "/static/media/pixelatedLeaf.6c93bd20.svg", 11 | "static/css/main.d2c98b4b.chunk.css.map": "/static/css/main.d2c98b4b.chunk.css.map", 12 | "index.html": "/index.html", 13 | "precache-manifest.a719d5b08722739b116177c801bfc531.js": "/precache-manifest.a719d5b08722739b116177c801bfc531.js", 14 | "service-worker.js": "/service-worker.js" 15 | } -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/static/css/main.c55712aa.chunk.css", 3 | "main.js": "/static/js/main.a005e40f.chunk.js", 4 | "main.js.map": "/static/js/main.a005e40f.chunk.js.map", 5 | "static/js/1.18f02ebe.chunk.js": "/static/js/1.18f02ebe.chunk.js", 6 | "static/js/1.18f02ebe.chunk.js.map": "/static/js/1.18f02ebe.chunk.js.map", 7 | "runtime~main.js": "/static/js/runtime~main.229c360f.js", 8 | "runtime~main.js.map": "/static/js/runtime~main.229c360f.js.map", 9 | "static/media/mongoleaf.png": "/static/media/mongoleaf.0ebc1843.png", 10 | "static/media/pixelatedLeaf.svg": "/static/media/pixelatedLeaf.6c93bd20.svg", 11 | "static/css/main.c55712aa.chunk.css.map": "/static/css/main.c55712aa.chunk.css.map", 12 | "index.html": "/index.html", 13 | "precache-manifest.bc43c92732837d0c02c5ebc5f172828d.js": "/precache-manifest.bc43c92732837d0c02c5ebc5f172828d.js", 14 | "service-worker.js": "/service-worker.js" 15 | } -------------------------------------------------------------------------------- /mflix-js/test/projection.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Projection", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Can perform a country search for one country", async () => { 9 | const kosovoList = ["Kosovo"] 10 | const movies = await MoviesDAO.getMoviesByCountry(kosovoList) 11 | console.log(movies[0]) 12 | expect(movies.length).toEqual(2) 13 | }) 14 | 15 | test("Can perform a country search for three countries", async () => { 16 | const countriesList = ["Russia", "Japan", "Mexico"] 17 | const movies = await MoviesDAO.getMoviesByCountry(countriesList) 18 | expect(movies.length).toEqual(1468) 19 | //console.log(movies[0]); 20 | movies.map(movie => { 21 | const movieKeys = Object.keys(movie).sort() 22 | const expectedKeys = ["_id", "title"] 23 | //console.log(movieKeys); 24 | //console.log(expectedKeys); 25 | expect(movieKeys).toEqual(expectedKeys) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /mflix-js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/transform-runtime", 7 | "@babel/plugin-syntax-dynamic-import", 8 | "@babel/plugin-syntax-import-meta", 9 | "@babel/plugin-proposal-class-properties", 10 | "@babel/plugin-proposal-json-strings", 11 | [ 12 | "@babel/plugin-proposal-decorators", 13 | { 14 | "legacy": true 15 | } 16 | ], 17 | "@babel/plugin-proposal-function-sent", 18 | "@babel/plugin-proposal-export-namespace-from", 19 | "@babel/plugin-proposal-numeric-separator", 20 | "@babel/plugin-proposal-throw-expressions", 21 | "@babel/plugin-proposal-export-default-from", 22 | "@babel/plugin-proposal-logical-assignment-operators", 23 | "@babel/plugin-proposal-optional-chaining", 24 | [ 25 | "@babel/plugin-proposal-pipeline-operator", 26 | { 27 | "proposal": "minimal" 28 | } 29 | ], 30 | "@babel/plugin-proposal-nullish-coalescing-operator", 31 | "@babel/plugin-proposal-do-expressions" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /mflix-js/test/db-connection.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Connection", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Can access MFlix data", async () => { 9 | const mflix = global.mflixClient.db(process.env.MFLIX_NS) 10 | const collections = await mflix.listCollections().toArray() 11 | const collectionNames = collections.map(elem => elem.name) 12 | expect(collectionNames).toContain("movies") 13 | expect(collectionNames).toContain("comments") 14 | expect(collectionNames).toContain("users") 15 | }) 16 | 17 | test("Can retrieve a movie by id", async () => { 18 | const id = "573a13a6f29313caabd17bd5" 19 | const movie = await MoviesDAO.getMovieByID(id) 20 | expect(movie.title).toEqual("Once Upon a Time in Mexico") 21 | }) 22 | 23 | test("Can retrieve first page of movies", async () => { 24 | const { 25 | moviesList: firstPage, 26 | totalNumMovies: numMovies, 27 | } = await MoviesDAO.getMovies() 28 | expect(firstPage.length).toEqual(20) 29 | expect(numMovies).toEqual(23530) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /mflix-js/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.a719d5b08722739b116177c801bfc531.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^/]+\.[^/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /curd-filedb-app/model/file.model.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Utill = require('../utill/file.utill'); 3 | 4 | module.exports = { 5 | async saveData(obj) { 6 | try { 7 | let dataArr = await Utill.getFileArr(); 8 | console.log(dataArr); 9 | 10 | dataArr.push(obj); 11 | await Utill.writeFile(dataArr); 12 | } catch (err) { 13 | console.log(err); 14 | } 15 | }, 16 | 17 | 18 | async getAllData() { 19 | try { 20 | let dataArr = await Utill.getFileArr(); 21 | return dataArr; 22 | 23 | } catch (err) { 24 | console.log(err); 25 | } 26 | }, 27 | 28 | async updateById(id, obj) { 29 | try { 30 | let dataArr = await Utill.getFileArr(); 31 | let index = dataArr.findIndex((d) => { 32 | return d.id == id; 33 | }); 34 | 35 | dataArr[index] = {id, ...obj}; 36 | await Utill.writeFile(dataArr); 37 | 38 | } catch (err) { 39 | console.log(err); 40 | } 41 | }, 42 | 43 | async deleteById(id) { 44 | try { 45 | let dataArr = await Utill.getFileArr(); 46 | let filterData = dataArr.filter((d)=>{ 47 | return d.id != id; 48 | }); 49 | await Utill.writeFile(filterData); 50 | 51 | } catch (err) { 52 | console.log(err); 53 | } 54 | } 55 | }; -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.bc43c92732837d0c02c5ebc5f172828d.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^/]+\.[^/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /mflix-js/test/get-comments.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Get Comments", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Can fetch comments for a movie", async () => { 9 | const id = "573a13b5f29313caabd42c2f" 10 | const movie = await MoviesDAO.getMovieByID(id) 11 | expect(movie.title).toEqual("The Express") 12 | expect(movie.comments.length).toBe(147) 13 | }) 14 | 15 | test("comments should be sorted by date", async () => { 16 | // most recent to least 17 | expect.assertions(10) 18 | const movieIds = ["573a13b5f29313caabd42c2f"] 19 | const promises = movieIds.map(async id => { 20 | const movie = await MoviesDAO.getMovieByID(id) 21 | const comments = movie.comments 22 | const sortedComments = comments.slice() 23 | sortedComments.sort((a, b) => b.date.getTime() - a.date.getTime()) 24 | 25 | for (let i = 0; i < Math.min(10, comments.length); i++) { 26 | const randomInt = Math.floor(Math.random() * comments.length - 1) 27 | expect(comments[randomInt]).toEqual(sortedComments[randomInt]) 28 | } 29 | }) 30 | await Promise.all(promises) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /mflix-js/src/index.js: -------------------------------------------------------------------------------- 1 | import app from "./server" 2 | import { MongoClient } from "mongodb" 3 | import MoviesDAO from "../src/dao/moviesDAO" 4 | import UsersDAO from "./dao/usersDAO" 5 | import CommentsDAO from "./dao/commentsDAO" 6 | 7 | const port = process.env.PORT || 8000 8 | 9 | /** 10 | Ticket: Connection Pooling 11 | 12 | Please change the configuration of the MongoClient object by setting the 13 | maximum connection pool size to 50 active connections. 14 | */ 15 | 16 | /** 17 | Ticket: Timeouts 18 | 19 | Please prevent the program from waiting indefinitely by setting the write 20 | concern timeout limit to 2500 milliseconds. 21 | */ 22 | 23 | MongoClient.connect( 24 | process.env.MFLIX_DB_URI, 25 | // TODO: Connection Pooling 26 | // Set the poolSize to 50 connections. 27 | // TODO: Timeouts 28 | // Set the write timeout limit to 2500 milliseconds. 29 | { 30 | useNewUrlParser: true, 31 | useUnifiedTopology: true, 32 | }, 33 | ) 34 | .catch(err => { 35 | console.error(err.stack) 36 | process.exit(1) 37 | }) 38 | .then(async client => { 39 | await MoviesDAO.injectDB(client) 40 | await UsersDAO.injectDB(client) 41 | await CommentsDAO.injectDB(client) 42 | app.listen(port, () => { 43 | console.log(`listening on port ${port}`) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /list.js: -------------------------------------------------------------------------------- 1 | //Method to create linked list from array 2 | function arrToList(arr){ 3 | let i = 0; 4 | function list(value){ 5 | if(value == undefined) { 6 | return null; 7 | } 8 | i++; 9 | return { 10 | value: value, 11 | next: list(arr[i]) 12 | } 13 | } 14 | return list(arr[i]); 15 | } 16 | 17 | let arr = [ 18 | 1,2,3 19 | ]; 20 | 21 | //console.log(arrToList(arr)); 22 | //console.log(JSON.stringify(arrToList(arr))); 23 | 24 | 25 | //Method to convert linked list to array 26 | function listToArr(list){ 27 | let arr = []; 28 | while(list.next) { 29 | arr.push(list.value); 30 | list = list.next; 31 | } 32 | arr.push(list.value); 33 | return arr; 34 | } 35 | 36 | let list = arrToList(arr); 37 | //console.log(JSON.stringify(list)); 38 | //console.log(listToArr(list)); 39 | 40 | 41 | //method to add list at the front 42 | function prepand(element, position , list){ 43 | let i = 0; 44 | let newList = {}; 45 | function addEle(list) { 46 | if(list == null) { 47 | return 'Invalid Position given.' 48 | } 49 | 50 | if (i == position) { 51 | return newList = { 52 | value: element, 53 | next: list 54 | }; 55 | } 56 | i++; 57 | list = list.next; 58 | return addEle(list) 59 | } 60 | return addEle(list); 61 | } 62 | 63 | console.log(JSON.stringify(prepand(4,2, arrToList(arr)))); 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /chat-app/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const http = require('http').Server(app); 4 | const io = require('socket.io')(http); 5 | 6 | app.get('/', (req, res)=>{ 7 | res.sendFile('/home/wavelabs/Desktop/sekharwork/JavaScript-HiteshChoudhary-/chat-app/index.html'); 8 | }) 9 | 10 | let client = 0; 11 | io.on('connection', (socket)=>{ 12 | console.log('User Connected..'); 13 | 14 | client++; 15 | //io.sockets.emit('broadcast', { description: `${client} connected now !!!` }); 16 | 17 | socket.emit('newClient', { description: `Welcome...` }); 18 | socket.broadcast.emit('newClient', { description: `${client} connected now...` }) 19 | 20 | 21 | //setTimeout(() => { 22 | // //socket.send('Message send after 2 sec..'); 23 | // socket.emit('testEvent', { description: 'An custom event named testEvent got fired ' }); 24 | //}, 2000); 25 | 26 | //socket.on('clinetEvent', (data)=>{ 27 | // console.log(data); 28 | //}) 29 | 30 | socket.on('disconnect', () => { 31 | client--; 32 | //io.sockets.emit('broadcast', { description: `${client} connected now !!!` }); 33 | socket.broadcast.emit('newClient', { description: `${client} connected now...` }) 34 | console.log('User disconnected..'); 35 | }) 36 | }) 37 | 38 | http.listen(3000, (err)=>{ 39 | if(err) console.log(err); 40 | console.log('Server running....'); 41 | }) -------------------------------------------------------------------------------- /mflix-js/test/facets.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Facets", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("should require cast members to perform search with", async () => { 9 | const filters = {} 10 | expect(async () => await MoviesDAO.facetedSearch(filters).toThrow()) 11 | }) 12 | 13 | test("should return movies and runtime/rating facets for returned movies", async () => { 14 | const filters = { cast: { $in: ["Tom Hanks"] } } 15 | const actual = await MoviesDAO.facetedSearch({ filters }) 16 | expect(actual.movies.length).toBe(20) 17 | expect(actual.rating.length).toBe(5) 18 | expect(actual.runtime.length).toBe(4) 19 | expect(actual.count).toBe(37) 20 | }) 21 | 22 | test("should also support paging", async () => { 23 | const filters = { cast: { $in: ["Susan Sarandon"] } } 24 | const actual = await MoviesDAO.facetedSearch({ filters, page: 1 }) 25 | expect(actual.movies.length).toBe(18) 26 | expect(actual.rating.length).toBe(3) 27 | expect(actual.runtime.length).toBe(4) 28 | expect(actual.count).toBe(38) 29 | }) 30 | 31 | test("should throw an error if castMembers is empty", async () => { 32 | const filters = { cast: { $in: [] } } 33 | expect(async () => await MoviesDAO.facetedSearch({ filters }).toThrow()) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /mflix-js/build/static/js/runtime~main.229c360f.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c { 25 | beforeAll(async () => { 26 | await CommentsDAO.injectDB(global.mflixClient) 27 | await MoviesDAO.injectDB(global.mflixClient) 28 | }) 29 | 30 | test("Can post a comment", async () => { 31 | const postCommentResult = await CommentsDAO.addComment( 32 | movieId, 33 | testUser, 34 | comment.text, 35 | date, 36 | ) 37 | 38 | expect(postCommentResult.insertedCount).toBe(1) 39 | expect(postCommentResult.insertedId).not.toBe(null) 40 | 41 | const kingKongComments = (await MoviesDAO.getMovieByID(movieId)).comments 42 | 43 | expect(kingKongComments[0]._id).toEqual(postCommentResult.insertedId) 44 | expect(kingKongComments[0].text).toEqual(comment.text) 45 | 46 | comment.id = postCommentResult.insertedId 47 | }) 48 | 49 | test("Cannot delete a comment if email does not match", async () => { 50 | const deleteCommentResult = await CommentsDAO.deleteComment( 51 | comment.id, 52 | newUser.email, 53 | ) 54 | 55 | expect(deleteCommentResult.deletedCount).toBe(0) 56 | }) 57 | 58 | test("Can delete a comment if email matches", async () => { 59 | const deleteCommentResult = await CommentsDAO.deleteComment( 60 | comment.id, 61 | testUser.email, 62 | ) 63 | 64 | expect(deleteCommentResult.deletedCount).toBe(1) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /curd-filedb-app/controller/file.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const Utill = require('../utill/file.utill'); 5 | const file = require('../model/file.model'); 6 | 7 | 8 | //router for generic response 9 | router.get('/', (req, res)=>{ 10 | res.status(200).send({message : 'Server is listening....!!!!'}); 11 | }); 12 | 13 | //router for create data 14 | router.post('/save', async (req, res) => { 15 | try { 16 | let obj = { 17 | id: await Utill.getUid(), 18 | title: req.body.title, 19 | description: req.body.description 20 | }; 21 | 22 | await file.saveData(obj); 23 | 24 | res.status(201).send({ status: 'Success', message: 'Data saved successfuly' }); 25 | 26 | } catch (err) { 27 | console.log(err); 28 | } 29 | 30 | }); 31 | 32 | //router for get all data 33 | router.get('/all', async (req, res)=>{ 34 | 35 | try { 36 | let dataArr = await file.getAllData(); 37 | res.status(200).send({ message: 'Data fetched successfuly', data: dataArr }); 38 | } catch (err) { 39 | console.log(err); 40 | } 41 | 42 | }); 43 | 44 | 45 | //router for update specific data 46 | router.put('/:id', async (req, res)=>{ 47 | try { 48 | let id = parseInt(req.params.id); 49 | let obj = { 50 | title: req.body.title, 51 | description: req.body.description 52 | }; 53 | 54 | await file.updateById(id, obj); 55 | 56 | res.status(200).send({ status: 'Success', message: 'Data updated successfuly' }); 57 | 58 | } catch (err) { 59 | console.log(err); 60 | } 61 | }); 62 | 63 | //route for deleting all data 64 | router.delete('/:id', async (req, res)=>{ 65 | try { 66 | let id = req.params.id; 67 | await file.deleteById(id); 68 | 69 | res.status(200).send({ status: 'Success', message: 'Data deleted successfuly' }); 70 | 71 | } catch (err) { 72 | console.log(err); 73 | } 74 | }); 75 | 76 | 77 | 78 | 79 | module.exports = router; -------------------------------------------------------------------------------- /mflix-js/build/index.html: -------------------------------------------------------------------------------- 1 | mflix
-------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/index.html: -------------------------------------------------------------------------------- 1 | mflix
-------------------------------------------------------------------------------- /mflix-js/test/user-management.test.js: -------------------------------------------------------------------------------- 1 | import UsersDAO from "../src/dao/usersDAO" 2 | const testUser = { 3 | name: "Magical Mr. Mistoffelees", 4 | email: "magicz@cats.com", 5 | password: "somehashedpw", 6 | } 7 | 8 | const sessionUser = { 9 | user_id: testUser.email, 10 | jwt: "hello", 11 | } 12 | 13 | describe("User Management", () => { 14 | beforeAll(async () => { 15 | await UsersDAO.injectDB(global.mflixClient) 16 | }) 17 | 18 | afterAll(async () => { 19 | await UsersDAO.deleteUser(testUser.email) 20 | }) 21 | 22 | test("it can add a new user to the database", async () => { 23 | /** 24 | * The password WILL be hashed at the API layer prior to sending it to the 25 | * UsersDAO object. 26 | * NEVER 27 | * NEVER 28 | * NEVER store plaintext passwords, PLEASE 29 | */ 30 | const actual = await UsersDAO.addUser(testUser); 31 | //console.log(actual); 32 | expect(actual.success).toBeTruthy() 33 | expect(actual.error).toBeUndefined() 34 | 35 | // we should be able to get the user 36 | const user = await UsersDAO.getUser(testUser.email) 37 | // for comparison, we delete the _id key returned from Mongo 38 | //delete user._id 39 | expect(user).toEqual(testUser) 40 | }) 41 | 42 | test("it returns an error when trying to register duplicate user", async () => { 43 | const expected = "A user with the given email already exists." 44 | const actual = await UsersDAO.addUser(testUser) 45 | expect(actual.error).toBe(expected) 46 | expect(actual.success).toBeFalsy() 47 | }) 48 | 49 | test("it allows a user to login", async () => { 50 | const actual = await UsersDAO.loginUser(testUser.email, sessionUser.jwt) 51 | expect(actual.success).toBeTruthy() 52 | const sessionResult = await UsersDAO.getUserSession(testUser.email) 53 | delete sessionResult._id 54 | expect(sessionResult).toEqual(sessionUser) 55 | }) 56 | 57 | test("it allows a user to logout", async () => { 58 | const actual = await UsersDAO.logoutUser(testUser.email) 59 | expect(actual.success).toBeTruthy() 60 | const sessionResult = await UsersDAO.getUserSession(testUser.email) 61 | expect(sessionResult).toBeNull() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /mflix-js/test/user-preferences.test.js: -------------------------------------------------------------------------------- 1 | import UsersDAO from "../src/dao/usersDAO" 2 | 3 | const testUser = { 4 | name: "foo", 5 | email: "foobaz@bar.com", 6 | password: "foobar", 7 | } 8 | 9 | describe("User Preferences", () => { 10 | beforeAll(async () => { 11 | await UsersDAO.injectDB(global.mflixClient) 12 | }) 13 | 14 | afterAll(async () => { 15 | await UsersDAO.deleteUser(testUser.email) 16 | }) 17 | 18 | test("Invalid user should not have preferences", async () => { 19 | await UsersDAO.deleteUser(testUser.email) 20 | const preferences = { 21 | color: "green", 22 | favorite_letter: "q", 23 | favorite_number: 42, 24 | } 25 | 26 | const result = await UsersDAO.updatePreferences(testUser.email, preferences) 27 | const expected = { error: "No user found with that email" } 28 | 29 | expect(result).toEqual(expected) 30 | }) 31 | 32 | test("Null preferences should be valid", async () => { 33 | await UsersDAO.addUser(testUser) 34 | const preferences = null 35 | await UsersDAO.updatePreferences(testUser.email, preferences) 36 | const userData = await UsersDAO.getUser(testUser.email) 37 | 38 | expect(userData.preferences).toEqual({}) 39 | }) 40 | 41 | test("Valid preferences are reflected in DB", async () => { 42 | await UsersDAO.addUser(testUser) 43 | 44 | // first set of preferences 45 | const preferences = { 46 | favorite_cast_member: "Goldie Hawn", 47 | favorite_genre: "Comedy", 48 | preferred_ratings: ["G", "PG", "PG-13"], 49 | } 50 | 51 | let updateResult = await UsersDAO.updatePreferences( 52 | testUser.email, 53 | preferences, 54 | ) 55 | expect(updateResult.matchedCount).toBe(1) 56 | expect(updateResult.modifiedCount).toBe(1) 57 | 58 | const userData = UsersDAO.getUser(testUser.email) 59 | expect(userData.preferences).not.toBeNull() 60 | 61 | // second set of preferences 62 | const newPreferences = { 63 | favorite_cast_member: "Daniel Day-Lewis", 64 | favorite_genre: "Drama", 65 | preferred_ratings: ["R"], 66 | } 67 | 68 | updateResult = await UsersDAO.updatePreferences( 69 | testUser.email, 70 | newPreferences, 71 | ) 72 | expect(updateResult.matchedCount).toBe(1) 73 | expect(updateResult.modifiedCount).toBe(1) 74 | 75 | const newUserData = await UsersDAO.getUser(testUser.email) 76 | expect(newUserData.preferences).toEqual(newPreferences) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /mflix-js/test/create-update-comments.test.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson" 2 | import CommentsDAO from "../src/dao/commentsDAO" 3 | import MoviesDAO from "../src/dao/moviesDAO" 4 | 5 | const testUser = { 6 | name: "foobar", 7 | email: "foobar@baz.com", 8 | } 9 | 10 | const newUser = { 11 | name: "barfoo", 12 | email: "baz@foobar.com", 13 | } 14 | 15 | // Interstellar 16 | const movieId = "573a13b9f29313caabd4ddff" 17 | 18 | const date = new Date() 19 | 20 | let comment = { 21 | text: "fa-fe-fi-fo-fum", 22 | id: "", 23 | } 24 | 25 | const newCommentText = "foo foo foo" 26 | 27 | const newerCommentText = "bar bar bar" 28 | 29 | describe("Create/Update Comments", () => { 30 | beforeAll(async () => { 31 | await CommentsDAO.injectDB(global.mflixClient) 32 | await MoviesDAO.injectDB(global.mflixClient) 33 | }) 34 | 35 | afterAll(async () => { 36 | const commentsCollection = await global.mflixClient 37 | .db(process.env.MFLIX_NS) 38 | .collection("comments") 39 | const deleteResult = await commentsCollection.deleteMany({ 40 | text: "fa-fe-fi-fo-fum", 41 | }) 42 | }) 43 | 44 | test("Can post a comment", async () => { 45 | const postCommentResult = await CommentsDAO.addComment( 46 | movieId, 47 | testUser, 48 | comment.text, 49 | date, 50 | ) 51 | 52 | expect(postCommentResult.insertedCount).toBe(1) 53 | expect(postCommentResult.insertedId).not.toBe(null) 54 | 55 | const movieComments = (await MoviesDAO.getMovieByID(movieId)).comments 56 | 57 | expect(movieComments[0]._id).toEqual(postCommentResult.insertedId) 58 | expect(movieComments[0].text).toEqual(comment.text) 59 | 60 | comment.id = postCommentResult.insertedId 61 | }) 62 | 63 | test("Can update a comment", async () => { 64 | const updateCommentResult = await CommentsDAO.updateComment( 65 | comment.id, 66 | testUser.email, 67 | newCommentText, 68 | date, 69 | ) 70 | expect(updateCommentResult.modifiedCount).toBe(1) 71 | 72 | const movieComments = (await MoviesDAO.getMovieByID(movieId)).comments 73 | 74 | expect(movieComments[0].text).toBe(newCommentText) 75 | }) 76 | 77 | test("Can only update comment if user posted comment", async () => { 78 | const updateCommentResult = await CommentsDAO.updateComment( 79 | comment.id, 80 | newUser.email, 81 | newerCommentText, 82 | date, 83 | ) 84 | 85 | expect(updateCommentResult.modifiedCount).toBe(0) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /mflix-js/src/migrations/movie-last-updated-migration.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require("mongodb").MongoClient 2 | const ObjectId = require("mongodb").ObjectId 3 | const MongoError = require("mongodb").MongoError 4 | require("dotenv").config() 5 | 6 | /** 7 | * Ticket: Migration 8 | * 9 | * Update all the documents in the `movies` collection, such that the 10 | * "lastupdated" field is stored as an ISODate() rather than a string. 11 | * 12 | * The Date.parse() method build into Javascript will prove very useful here! 13 | * Refer to http://mongodb.github.io/node-mongodb-native/3.1/tutorials/crud/#bulkwrite 14 | */ 15 | 16 | // This leading semicolon (;) is to signify to the parser that this is a new expression. This expression is an 17 | // Immediately Invoked Function Expression (IIFE). It's being used to wrap this logic in an asynchronous function 18 | // so we can use await within. 19 | // To read more about this type of expression, refer to https://developer.mozilla.org/en-US/docs/Glossary/IIFE 20 | ;(async () => { 21 | try { 22 | const host = process.env.MFLIX_DB_URI 23 | const client = await MongoClient.connect(host, { useNewUrlParser: true }) 24 | const mflix = client.db(process.env.MFLIX_NS) 25 | 26 | // TODO: Create the proper predicate and projection 27 | // add a predicate that checks that the `lastupdated` field exists, and then 28 | // check that its type is a string 29 | // a projection is not required, but may help reduce the amount of data sent 30 | // over the wire! 31 | const predicate = { somefield: { $someOperator: true } } 32 | const projection = {} 33 | const cursor = await mflix 34 | .collection("movies") 35 | .find(predicate, projection) 36 | .toArray() 37 | const moviesToMigrate = cursor.map(({ _id, lastupdated }) => ({ 38 | updateOne: { 39 | filter: { _id: ObjectId(_id) }, 40 | update: { 41 | $set: { lastupdated: new Date(Date.parse(lastupdated)) }, 42 | }, 43 | }, 44 | })) 45 | console.log( 46 | "\x1b[32m", 47 | `Found ${moviesToMigrate.length} documents to update`, 48 | ) 49 | // TODO: Complete the BulkWrite statement below 50 | const { modifiedCount } = await "some bulk operation" 51 | 52 | console.log("\x1b[32m", `${modifiedCount} documents updated`) 53 | client.close() 54 | process.exit(0) 55 | } catch (e) { 56 | if ( 57 | e instanceof MongoError && 58 | e.message.slice(0, "Invalid Operation".length) === "Invalid Operation" 59 | ) { 60 | console.log("\x1b[32m", "No documents to update") 61 | } else { 62 | console.error("\x1b[31m", `Error during migration, ${e}`) 63 | } 64 | process.exit(1) 65 | } 66 | })() 67 | -------------------------------------------------------------------------------- /mflix-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon -L ./index.js", 8 | "test": "jest --passWithNoTests", 9 | "test:watch": "jest --passWithNoTests --watch", 10 | "changestream:update": "node test/lessons/change-updates.js", 11 | "changestream:insert": "node test/lessons/change-insert.js" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "babel-eslint": "^9.0.0", 18 | "bcryptjs": "^2.4.3", 19 | "bluebird": "^3.5.1", 20 | "body-parser": "^1.18.3", 21 | "chai": "^4.1.2", 22 | "cors": "^2.8.4", 23 | "dotenv": "^6.0.0", 24 | "eslint": "^4.19.1", 25 | "eslint-config-prettier": "^3.1.0", 26 | "eslint-config-react-app": "^2.1.0", 27 | "eslint-plugin-flowtype": "^2.47.1", 28 | "eslint-plugin-import": "^2.12.0", 29 | "eslint-plugin-jsx-a11y": "^6.2.3", 30 | "eslint-plugin-prettier": "^2.6.0", 31 | "eslint-plugin-react": "^7.8.2", 32 | "express": "^4.16.3", 33 | "faker": "^4.1.0", 34 | "jest-runner": "^24.9.0", 35 | "jsonwebtoken": "^8.3.0", 36 | "mocha": "^5.2.0", 37 | "mongodb": "^3.1.6", 38 | "morgan": "^1.9.1", 39 | "npm-check-updates": "^3.1.21", 40 | "prettier": "^1.14.3", 41 | "sinon": "^5.0.10" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.5.5", 45 | "@babel/plugin-proposal-class-properties": "^7.0.0", 46 | "@babel/plugin-proposal-decorators": "^7.0.0", 47 | "@babel/plugin-proposal-do-expressions": "^7.0.0", 48 | "@babel/plugin-proposal-export-default-from": "^7.0.0", 49 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0", 50 | "@babel/plugin-proposal-function-sent": "^7.0.0", 51 | "@babel/plugin-proposal-json-strings": "^7.0.0", 52 | "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", 53 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", 54 | "@babel/plugin-proposal-numeric-separator": "^7.0.0", 55 | "@babel/plugin-proposal-optional-chaining": "^7.0.0", 56 | "@babel/plugin-proposal-pipeline-operator": "^7.0.0", 57 | "@babel/plugin-proposal-throw-expressions": "^7.0.0", 58 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 59 | "@babel/plugin-syntax-import-meta": "^7.0.0", 60 | "@babel/plugin-transform-runtime": "^7.5.5", 61 | "@babel/preset-env": "^7.5.5", 62 | "@babel/register": "^7.5.5", 63 | "@babel/runtime": "^7.5.5", 64 | "babel-core": "^7.0.0-bridge.0", 65 | "babel-jest": "^23.4.2", 66 | "concurrently": "^3.5.1", 67 | "husky": "^1.0.0", 68 | "jest": "^25.0.0", 69 | "jest-cli": "^25.0.0", 70 | "jest-express": "^1.6.0", 71 | "lint-staged": "^7.3.0", 72 | "nodemon": "^1.17.5" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mflix-js/test/text-subfield.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Text and Subfield Search", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Can perform a text search", async () => { 9 | const filters = { text: "mongo" } 10 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 11 | filters, 12 | }) 13 | expect(moviesList.length).toEqual(6) 14 | expect(totalNumMovies).toEqual(6) 15 | const firstMovie = moviesList[0] 16 | expect(firstMovie["title"]).toEqual("Flash Gordon") 17 | }) 18 | 19 | test("Can perform a genre search with one genre", async () => { 20 | const filters = { genre: ["Action"] } 21 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 22 | filters, 23 | }) 24 | expect(moviesList.length).toEqual(20) 25 | expect(totalNumMovies).toEqual(2539) 26 | const firstMovie = moviesList[0] 27 | expect(firstMovie["title"]).toEqual("Gladiator") 28 | }) 29 | 30 | test("Can perform a genre search with multiple genres", async () => { 31 | const filters = { genre: ["Mystery", "Thriller"] } 32 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 33 | filters, 34 | }) 35 | expect(moviesList.length).toEqual(20) 36 | expect(totalNumMovies).toEqual(3485) 37 | const firstMovie = moviesList[0] 38 | expect(firstMovie["title"]).toEqual("2 Fast 2 Furious") 39 | }) 40 | 41 | test("Can perform a cast search with one cast member", async () => { 42 | const filters = { cast: ["Elon Musk"] } 43 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 44 | filters, 45 | }) 46 | expect(moviesList.length).toEqual(1) 47 | expect(totalNumMovies).toEqual(1) 48 | const firstMovie = moviesList[0] 49 | expect(firstMovie["title"]).toEqual("Racing Extinction") 50 | }) 51 | 52 | test("Can perform a cast search with multiple cast members", async () => { 53 | const filters = { cast: ["Robert Redford", "Julia Roberts"] } 54 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 55 | filters, 56 | }) 57 | expect(moviesList.length).toEqual(20) 58 | expect(totalNumMovies).toEqual(61) 59 | const lastMovie = moviesList.slice(-1).pop() 60 | expect(lastMovie["title"]).toEqual("Eat Pray Love") 61 | }) 62 | 63 | test("Can perform a search and return a non-default number of movies per page", async () => { 64 | const filters = { cast: ["Robert Redford", "Julia Roberts"] } 65 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 66 | filters, 67 | moviesPerPage: 33, 68 | }) 69 | expect(moviesList.length).toEqual(33) 70 | expect(totalNumMovies).toEqual(61) 71 | const lastMovie = moviesList.slice(-1).pop() 72 | expect(lastMovie["title"]).toEqual("Sneakers") 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /mflix-js/build/static/css/main.d2c98b4b.chunk.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes spinningLeaf{0%{-webkit-transform:rotateY(0deg);transform:rotateY(0deg)}to{-webkit-transform:rotateY(-1turn);transform:rotateY(-1turn)}}@keyframes spinningLeaf{0%{-webkit-transform:rotateY(0deg);transform:rotateY(0deg)}to{-webkit-transform:rotateY(-1turn);transform:rotateY(-1turn)}}@-webkit-keyframes blink{0%{opacity:0}50%{opacity:1}to{opacity:0}}@keyframes blink{0%{opacity:0}50%{opacity:1}to{opacity:0}} 2 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body,html{height:100%;margin:0;padding:0}#full{background:"#black";height:100%}body,button,div,h1,h2,h3,h4,input,p,select,span,textarea{font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:initial;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}.material-icons.red{color:#cd0000;margin-right:5px;vertical-align:sub}.material-icons:hover{cursor:pointer;font-size:28px;color:#b60000}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:initial}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none} 3 | /*# sourceMappingURL=main.d2c98b4b.chunk.css.map */ -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/static/css/main.c55712aa.chunk.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes spinningLeaf{0%{-webkit-transform:rotateY(0deg);transform:rotateY(0deg)}to{-webkit-transform:rotateY(-1turn);transform:rotateY(-1turn)}}@keyframes spinningLeaf{0%{-webkit-transform:rotateY(0deg);transform:rotateY(0deg)}to{-webkit-transform:rotateY(-1turn);transform:rotateY(-1turn)}}@-webkit-keyframes blink{0%{opacity:0}50%{opacity:1}to{opacity:0}}@keyframes blink{0%{opacity:0}50%{opacity:1}to{opacity:0}} 2 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body,html{height:100%;margin:0;padding:0}#full{background:"#black";height:100%}body,button,div,h1,h2,h3,h4,input,p,select,span,textarea{font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:initial;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:initial;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}.material-icons.red{color:#cd0000;margin-right:5px;vertical-align:sub}.material-icons:hover{cursor:pointer;font-size:28px;color:#b60000}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:initial}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none} 3 | /*# sourceMappingURL=main.c55712aa.chunk.css.map */ -------------------------------------------------------------------------------- /mflix-js/src/api/comments.controller.js: -------------------------------------------------------------------------------- 1 | import UsersDAO from "../dao/usersDAO" 2 | import CommentsDAO from "../dao/commentsDAO" 3 | import MoviesDAO from "../dao/moviesDAO" 4 | import { User } from "./users.controller" 5 | import { ObjectId } from "bson" 6 | 7 | export default class CommentsController { 8 | static async apiPostComment(req, res, next) { 9 | try { 10 | const userJwt = req.get("Authorization").slice("Bearer ".length) 11 | const user = await User.decoded(userJwt) 12 | var { error } = user 13 | if (error) { 14 | res.status(401).json({ error }) 15 | return 16 | } 17 | 18 | const movieId = req.body.movie_id 19 | const comment = req.body.comment 20 | const date = new Date() 21 | 22 | const commentResponse = await CommentsDAO.addComment( 23 | ObjectId(movieId), 24 | user, 25 | comment, 26 | date, 27 | ) 28 | 29 | const updatedComments = await MoviesDAO.getMovieByID(movieId) 30 | 31 | res.json({ status: "success", comments: updatedComments.comments }) 32 | } catch (e) { 33 | res.status(500).json({ e }) 34 | } 35 | } 36 | 37 | static async apiUpdateComment(req, res, next) { 38 | try { 39 | const userJwt = req.get("Authorization").slice("Bearer ".length) 40 | const user = await User.decoded(userJwt) 41 | var { error } = user 42 | if (error) { 43 | res.status(401).json({ error }) 44 | return 45 | } 46 | 47 | const commentId = req.body.comment_id 48 | const text = req.body.updated_comment 49 | const date = new Date() 50 | 51 | const commentResponse = await CommentsDAO.updateComment( 52 | ObjectId(commentId), 53 | user.email, 54 | text, 55 | date, 56 | ) 57 | 58 | var { error } = commentResponse 59 | if (error) { 60 | res.status(400).json({ error }) 61 | } 62 | 63 | if (commentResponse.modifiedCount === 0) { 64 | throw new Error( 65 | "unable to update comment - user may not be original poster", 66 | ) 67 | } 68 | 69 | const movieId = req.body.movie_id 70 | const updatedComments = await MoviesDAO.getMovieByID(movieId) 71 | 72 | res.json({ comments: updatedComments.comments }) 73 | } catch (e) { 74 | res.status(500).json({ e }) 75 | } 76 | } 77 | 78 | static async apiDeleteComment(req, res, next) { 79 | try { 80 | const userJwt = req.get("Authorization").slice("Bearer ".length) 81 | const user = await User.decoded(userJwt) 82 | var { error } = user 83 | if (error) { 84 | res.status(401).json({ error }) 85 | return 86 | } 87 | 88 | const commentId = req.body.comment_id 89 | const userEmail = user.email 90 | const commentResponse = await CommentsDAO.deleteComment( 91 | ObjectId(commentId), 92 | userEmail, 93 | ) 94 | 95 | const movieId = req.body.movie_id 96 | 97 | const { comments } = await MoviesDAO.getMovieByID(movieId) 98 | res.json({ comments }) 99 | } catch (e) { 100 | res.status(500).json({ e }) 101 | } 102 | } 103 | 104 | static async apiCommentReport(req, res, next) { 105 | try { 106 | const userJwt = req.get("Authorization").slice("Bearer ".length) 107 | const user = await User.decoded(userJwt) 108 | var { error } = user 109 | if (error) { 110 | res.status(401).json({ error }) 111 | return 112 | } 113 | 114 | if (UsersDAO.checkAdmin(user.email)) { 115 | const report = await CommentsDAO.mostActiveCommenters() 116 | res.json({ report }) 117 | return 118 | } 119 | 120 | res.status(401).json({ status: "fail" }) 121 | } catch (e) { 122 | res.status(500).json({ e }) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /mflix-js/test/paging.test.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../src/dao/moviesDAO" 2 | 3 | describe("Paging", () => { 4 | beforeAll(async () => { 5 | await MoviesDAO.injectDB(global.mflixClient) 6 | }) 7 | 8 | test("Supports paging by cast", async () => { 9 | const filters = { cast: ["Natalie Portman"] } 10 | /** 11 | * Testing first page 12 | */ 13 | const { moviesList: firstPage, totalNumMovies } = await MoviesDAO.getMovies( 14 | { 15 | filters, 16 | }, 17 | ) 18 | 19 | // check the total number of movies, including both pages 20 | expect(totalNumMovies).toEqual(23) 21 | 22 | // check the number of movies on the first page 23 | expect(firstPage.length).toEqual(20) 24 | 25 | // check some of the movies on the second page 26 | const firstMovie = firstPage[0] 27 | const twentiethMovie = firstPage.slice(-1).pop() 28 | expect(firstMovie.title).toEqual( 29 | "Star Wars: Episode III - Revenge of the Sith", 30 | ) 31 | expect(twentiethMovie.title).toEqual("Knight of Cups") 32 | 33 | /** 34 | * Testing second page 35 | */ 36 | const { moviesList: secondPage } = await MoviesDAO.getMovies({ 37 | filters, 38 | page: 1, 39 | }) 40 | 41 | // check the number of movies on the second page 42 | expect(secondPage.length).toEqual(3) 43 | // check some of the movies on the second page 44 | const twentyFirstMovie = secondPage[0] 45 | const lastMovie = secondPage.slice(-1).pop() 46 | expect(twentyFirstMovie.title).toEqual("A Tale of Love and Darkness") 47 | expect(lastMovie.title).toEqual("True") 48 | }) 49 | 50 | test("Supports paging by genre", async () => { 51 | const filters = { genre: ["Comedy", "Drama"] } 52 | 53 | /** 54 | * Testing first page 55 | */ 56 | const { moviesList: firstPage, totalNumMovies } = await MoviesDAO.getMovies( 57 | { 58 | filters, 59 | }, 60 | ) 61 | 62 | // check the total number of movies, including both pages 63 | expect(totalNumMovies).toEqual(17903) 64 | 65 | // check the number of movies on the first page 66 | expect(firstPage.length).toEqual(20) 67 | 68 | // check some of the movies on the second page 69 | const firstMovie = firstPage[0] 70 | const twentiethMovie = firstPage.slice(-1).pop() 71 | expect(firstMovie.title).toEqual("Titanic") 72 | expect(twentiethMovie.title).toEqual("Dègkeselyè") 73 | 74 | /** 75 | * Testing second page 76 | */ 77 | const { moviesList: secondPage } = await MoviesDAO.getMovies({ 78 | filters, 79 | page: 1, 80 | }) 81 | 82 | // check the number of movies on the second page 83 | expect(secondPage.length).toEqual(20) 84 | // check some of the movies on the second page 85 | const twentyFirstMovie = secondPage[0] 86 | const fortiethMovie = secondPage.slice(-1).pop() 87 | expect(twentyFirstMovie.title).toEqual("8 Mile") 88 | expect(fortiethMovie.title).toEqual("Forrest Gump") 89 | }) 90 | 91 | test("Supports paging by text", async () => { 92 | const filters = { text: "countdown" } 93 | 94 | /** 95 | * Testing first page 96 | */ 97 | const { moviesList: firstPage, totalNumMovies } = await MoviesDAO.getMovies( 98 | { 99 | filters, 100 | }, 101 | ) 102 | 103 | // check the total number of movies, including both pages 104 | expect(totalNumMovies).toEqual(12) 105 | 106 | // check the number of movies on the first page 107 | expect(firstPage.length).toEqual(12) 108 | 109 | // check some of the movies on the second page 110 | const firstMovie = firstPage[0] 111 | const twentiethMovie = firstPage.slice(-1).pop() 112 | expect(firstMovie.title).toEqual("Countdown") 113 | expect(twentiethMovie.title).toEqual("The Front Line") 114 | 115 | /** 116 | * Testing second page 117 | */ 118 | const { moviesList: secondPage } = await MoviesDAO.getMovies({ 119 | filters, 120 | page: 1, 121 | }) 122 | 123 | // check the number of movies on the second page 124 | expect(secondPage.length).toEqual(0) 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /mflix-js/README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Mflix 3 | ===== 4 | 5 | This is a short guide on setting up the system and environment dependencies 6 | required for the MFlix application to run. 7 | 8 | **Disclaimer:** The dependencies and versions in this project are not 9 | maintained. This project is intended for educational purposes and is **not** 10 | intended to be exposed in a network, so use at your own discretion. 11 | 12 | Project Structure 13 | ----------------- 14 | 15 | Downloading the **mflix-js.zip** handout may take a few minutes. Unzipping the 16 | file should create a new directory called **mflix-js**. 17 | 18 | Most of your work will be implementing methods in the **dao** directory, which 19 | contains all database interfacing methods. The API will make calls to Data 20 | Access Objects (DAOs) that interact directly with MongoDB. 21 | 22 | The unit tests in **test** will test these database access methods directly, 23 | without going through the API. The UI will run these methods in integration 24 | tests, and therefore requires the full application to be running. 25 | 26 | The lesson handouts can be found in the **test/lessons** directory. These files 27 | will look like **.spec.js**, and can be run with ``npm test -t 28 | ``. 29 | 30 | The API layer is fully implemented, as is the UI. The application is programmed 31 | to run on port **5000** by default - if you need to run on a port other than 32 | 5000, you can edit the **dotenv_win** (if on Windows) or the **dotenv_unix** file 33 | (if on Linux or Mac) in the root directory to modify the value of **PORT**. 34 | 35 | Please do not modify the API layer in any way, under the **mflix-js/src/api** 36 | directory. This may result in the front-end application failing to validate some 37 | of the labs. 38 | 39 | 40 | Node Library Dependencies 41 | ------------------------- 42 | 43 | The dependencies for the MFlix application should be downloaded using the 44 | ``npm`` command-line tool. You can get this tool by `downloading Node.js 45 | `_. Make sure to choose the correct option for 46 | your operating system. 47 | 48 | Once the installation is complete, you may need to restart your computer before 49 | using the command line tools. You can test that it's installed by running the 50 | following command: 51 | 52 | .. code-block:: sh 53 | 54 | node -v 55 | 56 | This should print out the version of ``node`` you currently have - we recommend 57 | using the latest Long Term Support version, currently 10.16.3, so this command should print something like 58 | ``v10.16.3``. 59 | 60 | Once ``npm`` is installed, you can install the MFlix dependencies by running the 61 | following command from the **mflix-js** directory: 62 | 63 | .. code-block:: sh 64 | 65 | npm install 66 | 67 | You must run this from the top level of the project, so ``npm`` has access to 68 | the **package.json** file where the dependencies are. 69 | 70 | You may see warnings depending on your operating system from **fsevents** or 71 | Husky warning about **git** missing. These are informational only and do not 72 | impact the functionality of the application. You can safely ignore them. 73 | 74 | You may also get a **node-gyp** error. Run ``npm rebuild`` and it should resolve 75 | this and install the dependencies required. 76 | 77 | While running ``npm install``, you might encounter the below error regarding ``node-gyp rebuild``. 78 | Although, it is completely harmless and you can start the application by running ``npm start``. 79 | 80 | .. image:: https://s3.amazonaws.com/university-courses/m220/m220js-npm-install-warning.png 81 | 82 | 83 | Running the Unit Tests 84 | ---------------------- 85 | 86 | To run the unit tests for this course, you will use `Jest 87 | `_. Jest has been included in this 88 | project's dependencies, so ``npm install`` should install everything you need. 89 | 90 | Each course lab contains a module of unit tests that you can call individually 91 | with ``npm test``. For example, to run the test **connection-pooling.test.js**, 92 | run the command: 93 | 94 | .. code-block:: sh 95 | 96 | npm test -t connection-pooling 97 | 98 | Each ticket will contain the exact command to run that ticket's specific unit 99 | tests. You can run these commands from anywhere in the **mflix-js** project. 100 | -------------------------------------------------------------------------------- /mflix-js/src/api/movies.controller.js: -------------------------------------------------------------------------------- 1 | import MoviesDAO from "../dao/moviesDAO" 2 | 3 | export default class MoviesController { 4 | static async apiGetMovies(req, res, next) { 5 | const MOVIES_PER_PAGE = 20 6 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies() 7 | let response = { 8 | movies: moviesList, 9 | page: 0, 10 | filters: {}, 11 | entries_per_page: MOVIES_PER_PAGE, 12 | total_results: totalNumMovies, 13 | } 14 | res.json(response) 15 | } 16 | 17 | static async apiGetMoviesByCountry(req, res, next) { 18 | let countries = req.query.countries == "" ? "USA" : req.query.countries 19 | let countryList = Array.isArray(countries) ? countries : Array(countries) 20 | let moviesList = await MoviesDAO.getMoviesByCountry(countryList) 21 | let response = { 22 | titles: moviesList, 23 | } 24 | res.json(response) 25 | } 26 | 27 | static async apiGetMovieById(req, res, next) { 28 | try { 29 | let id = req.params.id || {} 30 | let movie = await MoviesDAO.getMovieByID(id) 31 | if (!movie) { 32 | res.status(404).json({ error: "Not found" }) 33 | return 34 | } 35 | let updated_type = movie.lastupdated instanceof Date ? "Date" : "other" 36 | res.json({ movie, updated_type }) 37 | } catch (e) { 38 | console.log(`api, ${e}`) 39 | res.status(500).json({ error: e }) 40 | } 41 | } 42 | 43 | static async apiSearchMovies(req, res, next) { 44 | const MOVIES_PER_PAGE = 20 45 | let page 46 | try { 47 | page = req.query.page ? parseInt(req.query.page, 10) : 0 48 | } catch (e) { 49 | console.error(`Got bad value for page:, ${e}`) 50 | page = 0 51 | } 52 | let searchType 53 | try { 54 | searchType = Object.keys(req.query)[0] 55 | } catch (e) { 56 | console.error(`No search keys specified: ${e}`) 57 | } 58 | 59 | let filters = {} 60 | 61 | switch (searchType) { 62 | case "genre": 63 | if (req.query.genre !== "") { 64 | filters.genre = req.query.genre 65 | } 66 | break 67 | case "cast": 68 | if (req.query.cast !== "") { 69 | filters.cast = req.query.cast 70 | } 71 | break 72 | case "text": 73 | if (req.query.text !== "") { 74 | filters.text = req.query.text 75 | } 76 | break 77 | default: 78 | // nothing to do 79 | } 80 | 81 | const { moviesList, totalNumMovies } = await MoviesDAO.getMovies({ 82 | filters, 83 | page, 84 | MOVIES_PER_PAGE, 85 | }) 86 | 87 | let response = { 88 | movies: moviesList, 89 | page: page, 90 | filters, 91 | entries_per_page: MOVIES_PER_PAGE, 92 | total_results: totalNumMovies, 93 | } 94 | 95 | res.json(response) 96 | } 97 | 98 | static async apiFacetedSearch(req, res, next) { 99 | const MOVIES_PER_PAGE = 20 100 | 101 | let page 102 | try { 103 | page = req.query.page ? parseInt(req.query.page, 10) : 0 104 | } catch (e) { 105 | console.error(`Got bad value for page, defaulting to 0: ${e}`) 106 | page = 0 107 | } 108 | 109 | let filters = {} 110 | 111 | filters = 112 | req.query.cast !== "" 113 | ? { cast: new RegExp(req.query.cast, "i") } 114 | : { cast: "Tom Hanks" } 115 | 116 | const facetedSearchResult = await MoviesDAO.facetedSearch({ 117 | filters, 118 | page, 119 | MOVIES_PER_PAGE, 120 | }) 121 | 122 | let response = { 123 | movies: facetedSearchResult.movies, 124 | facets: { 125 | runtime: facetedSearchResult.runtime, 126 | rating: facetedSearchResult.rating, 127 | }, 128 | page: page, 129 | filters, 130 | entries_per_page: MOVIES_PER_PAGE, 131 | total_results: facetedSearchResult.count, 132 | } 133 | 134 | res.json(response) 135 | } 136 | 137 | static async getConfig(req, res, next) { 138 | const { poolSize, wtimeout, authInfo } = await MoviesDAO.getConfiguration() 139 | try { 140 | let response = { 141 | pool_size: poolSize, 142 | wtimeout, 143 | ...authInfo, 144 | } 145 | res.json(response) 146 | } catch (e) { 147 | res.status(500).json({ error: e }) 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /mflix-js/src/dao/commentsDAO.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson" 2 | 3 | let comments 4 | 5 | export default class CommentsDAO { 6 | static async injectDB(conn) { 7 | if (comments) { 8 | return 9 | } 10 | try { 11 | comments = await conn.db(process.env.MFLIX_NS).collection("comments") 12 | } catch (e) { 13 | console.error(`Unable to establish collection handles in userDAO: ${e}`) 14 | } 15 | } 16 | 17 | /** 18 | Ticket: Create/Update Comments 19 | 20 | For this ticket, you will need to implement the following two methods: 21 | 22 | - addComment 23 | - updateComment 24 | 25 | You can find these methods below this docstring. Make sure to read the comments 26 | to better understand the task. 27 | */ 28 | 29 | /** 30 | * Inserts a comment into the `comments` collection, with the following fields: 31 | 32 | - "name", the name of the user posting the comment 33 | - "email", the email of the user posting the comment 34 | - "movie_id", the _id of the movie pertaining to the comment 35 | - "text", the text of the comment 36 | - "date", the date when the comment was posted 37 | 38 | * @param {string} movieId - The _id of the movie in the `movies` collection. 39 | * @param {Object} user - An object containing the user's name and email. 40 | * @param {string} comment - The text of the comment. 41 | * @param {string} date - The date on which the comment was posted. 42 | * @returns {DAOResponse} Returns an object with either DB response or "error" 43 | */ 44 | static async addComment(movieId, user, comment, date) { 45 | try { 46 | // TODO Ticket: Create/Update Comments 47 | // Construct the comment document to be inserted into MongoDB. 48 | const commentDoc = { someField: "someValue" } 49 | 50 | return await comments.insertOne(commentDoc) 51 | } catch (e) { 52 | console.error(`Unable to post comment: ${e}`) 53 | return { error: e } 54 | } 55 | } 56 | 57 | /** 58 | * Updates the comment in the comment collection. Queries for the comment 59 | * based by both comment _id field as well as the email field to doubly ensure 60 | * the user has permission to edit this comment. 61 | * @param {string} commentId - The _id of the comment to update. 62 | * @param {string} userEmail - The email of the user who owns the comment. 63 | * @param {string} text - The updated text of the comment. 64 | * @param {string} date - The date on which the comment was updated. 65 | * @returns {DAOResponse} Returns an object with either DB response or "error" 66 | */ 67 | static async updateComment(commentId, userEmail, text, date) { 68 | try { 69 | // TODO Ticket: Create/Update Comments 70 | // Use the commentId and userEmail to select the proper comment, then 71 | // update the "text" and "date" fields of the selected comment. 72 | const updateResponse = await comments.updateOne( 73 | { someField: "someValue" }, 74 | { $set: { someOtherField: "someOtherValue" } }, 75 | ) 76 | 77 | return updateResponse 78 | } catch (e) { 79 | console.error(`Unable to update comment: ${e}`) 80 | return { error: e } 81 | } 82 | } 83 | 84 | static async deleteComment(commentId, userEmail) { 85 | /** 86 | Ticket: Delete Comments 87 | 88 | Implement the deleteOne() call in this method. 89 | 90 | Ensure the delete operation is limited so only the user can delete their own 91 | comments, but not anyone else's comments. 92 | */ 93 | 94 | try { 95 | // TODO Ticket: Delete Comments 96 | // Use the userEmail and commentId to delete the proper comment. 97 | const deleteResponse = await comments.deleteOne({ 98 | _id: ObjectId(commentId), 99 | }) 100 | 101 | return deleteResponse 102 | } catch (e) { 103 | console.error(`Unable to delete comment: ${e}`) 104 | return { error: e } 105 | } 106 | } 107 | 108 | static async mostActiveCommenters() { 109 | /** 110 | Ticket: User Report 111 | 112 | Build a pipeline that returns the 20 most frequent commenters on the MFlix 113 | site. You can do this by counting the number of occurrences of a user's 114 | email in the `comments` collection. 115 | */ 116 | try { 117 | // TODO Ticket: User Report 118 | // Return the 20 users who have commented the most on MFlix. 119 | const pipeline = [] 120 | 121 | // TODO Ticket: User Report 122 | // Use a more durable Read Concern here to make sure this data is not stale. 123 | const readConcern = comments.readConcern 124 | 125 | const aggregateResult = await comments.aggregate(pipeline, { 126 | readConcern, 127 | }) 128 | 129 | return await aggregateResult.toArray() 130 | } catch (e) { 131 | console.error(`Unable to retrieve most active commenters: ${e}`) 132 | return { error: e } 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Success/Error return object 139 | * @typedef DAOResponse 140 | * @property {boolean} [success] - Success 141 | * @property {string} [error] - Error 142 | */ 143 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/mongoclient.spec.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from "mongodb" 2 | 3 | describe("MongoClient", () => { 4 | /** 5 | * In this lesson, we'll use the MongoClient object to initiate a connection 6 | * with the database. 7 | */ 8 | 9 | test("Client initialized with URI", async () => { 10 | /** 11 | * Here's a MongoClient object initialized with a URI string. The only 12 | * option we've passed is to use the new url parser in order to be 13 | * future compatible, so this client will have the default connection 14 | * parameters. 15 | */ 16 | 17 | let testClient 18 | try { 19 | testClient = await MongoClient.connect(process.env.MFLIX_DB_URI, { 20 | useNewUrlParser: true, 21 | }) 22 | expect(testClient).not.toBeNull() 23 | 24 | // retrieve client options 25 | const clientOptions = testClient.s.options 26 | // console.error("OPTS", clientOptions) 27 | expect(clientOptions).not.toBeUndefined() 28 | 29 | // expect this connection to have SSL enabled 30 | if (typeof clientOptions.ssl !== "undefined") { 31 | expect(clientOptions).toHaveProperty("ssl") 32 | expect(clientOptions.ssl).toBe(true) 33 | 34 | // expect this user to authenticate against the "admin" database 35 | expect(clientOptions).toHaveProperty("authSource") 36 | expect(clientOptions.authSource).toBe("admin") 37 | } 38 | } catch (e) { 39 | expect(e).toBeNull() 40 | } finally { 41 | testClient.close() 42 | } 43 | }) 44 | 45 | test("Client initialized with URI and options", async () => { 46 | /** 47 | * Here we've initialized a MongoClient with a URI string, as well as a few 48 | * optional parameters. In this case, the parameters we set are 49 | * connectTimeoutMS, retryWrites and again useNewUrlParser. 50 | */ 51 | 52 | let testClient 53 | try { 54 | testClient = await MongoClient.connect(process.env.MFLIX_DB_URI, { 55 | connectTimeoutMS: 200, 56 | retryWrites: true, 57 | useNewUrlParser: true, 58 | }) 59 | 60 | const clientOptions = testClient.s.options 61 | 62 | // expect clientOptions to have the correct settings 63 | expect(clientOptions.connectTimeoutMS).toBe(200) 64 | expect(clientOptions.retryWrites).toBe(true) 65 | expect(clientOptions.useNewUrlParser).toBe(true) 66 | } catch (e) { 67 | expect(e).toBeNull() 68 | } finally { 69 | testClient.close() 70 | } 71 | }) 72 | 73 | test("Database handle created from MongoClient", async () => { 74 | /** 75 | * Now that we have a MongoClient object, we can use it to connect to a 76 | * specific database. This connection is called a "database handle", and we 77 | * can use it to explore the database. 78 | 79 | * Here we're looking at the collections on this database. We want to make 80 | * sure that "mflix" has the necessary collections to run the application. 81 | */ 82 | 83 | let testClient 84 | try { 85 | testClient = await MongoClient.connect(process.env.MFLIX_DB_URI, { 86 | useNewUrlParser: true, 87 | }) 88 | 89 | // create a database object for the "mflix" database 90 | const mflixDB = testClient.db(process.env.MFLIX_NS) 91 | 92 | // make sure the "mflix" database has the correct collections 93 | const mflixCollections = await mflixDB.listCollections().toArray() 94 | const actualCollectionNames = mflixCollections.map(obj => obj.name) 95 | const expectedCollectionNames = [ 96 | "movies", 97 | "users", 98 | "comments", 99 | "sessions", 100 | ] 101 | expectedCollectionNames.map(collection => { 102 | expect(actualCollectionNames).toContain(collection) 103 | }) 104 | } catch (e) { 105 | expect(e).toBeNull() 106 | } finally { 107 | testClient.close() 108 | } 109 | }) 110 | 111 | test("Collection handle created from database handle", async () => { 112 | /** 113 | * From the database handle, naturally comes the collection handle. We 114 | * verified in the previous test that the "mflix" database has a collection 115 | * called "movies". Now let's see if the "movies" collection has all the 116 | * data we expect. 117 | */ 118 | 119 | let testClient 120 | try { 121 | testClient = await MongoClient.connect(process.env.MFLIX_DB_URI, { 122 | connectTimeoutMS: 200, 123 | retryWrites: true, 124 | useNewUrlParser: true, 125 | }) 126 | 127 | // create a database object for the "mflix" database 128 | const mflixDB = testClient.db(process.env.MFLIX_NS) 129 | 130 | // create a collection object for the "movies" collection 131 | const movies = mflixDB.collection("movies") 132 | 133 | // expect the "movies" collection to have the correct number of movies 134 | const numMoves = await movies.countDocuments({}) 135 | expect(numMoves).toBe(23530) 136 | } catch (e) { 137 | expect(e).toBeNull() 138 | } finally { 139 | testClient.close() 140 | } 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/change-updates.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | const MongoClient = require("mongodb").MongoClient 3 | const faker = require("faker") 4 | const MIN_LOAN_AMOUNT = 500 5 | const MAX_LOAN_AMOUNT = 100000 6 | ;(async function() { 7 | const client = await MongoClient.connect(process.env.MFLIX_DB_URI, { 8 | wtimeout: 2500, 9 | poolSize: 50, 10 | useNewUrlParser: true, 11 | }) 12 | 13 | /** 14 | In this portion of the lesson, we'll cover `update` change events, as well 15 | as using a pipeline with the change stream. 16 | */ 17 | 18 | try { 19 | const bankDB = await client.db("bankDB") 20 | const loans_collection = await bankDB.collection("loans") 21 | 22 | /** 23 | Using a pipeline with a $match stage allows us to reduce the noise in our 24 | change stream. 25 | 26 | In this example, our bank issues loans between 500 and 100000 USD. This is 27 | a wide range of values. We want to capture the highest-value loans, and 28 | send a message congratulating the broker. 29 | */ 30 | 31 | // empty pipeline 32 | const emptyPipeline = [] 33 | 34 | // pipeline with a filter on the loan size 35 | const highAmountPipeline = [ 36 | { $match: { "fullDocument.amount": { $gt: 85000 } } }, 37 | ] 38 | 39 | /** 40 | By default, "update" operations will NOT return a full document to the 41 | change stream. However, we can specify that we want the entire document 42 | with the flag `{ fullDocument: "updateLookup" }`. 43 | 44 | By using this field, we can use all the fields in the updated document. 45 | In this example, we used the "broker" field to print out a detailed 46 | message, even though we only updated the "amount" and "dueDate" fields. 47 | */ 48 | 49 | // we can use { fullDocument: "updateLookup" } to return full documents to 50 | // the change stream after update operations 51 | const changeStream = loans_collection.watch(highAmountPipeline, { 52 | fullDocument: "updateLookup", 53 | }) 54 | 55 | /** 56 | After filtering on loan amount, we can further specify the operation, by 57 | `operationType`. This field appears in the change event, and it can alert 58 | us to the nature of this operation. 59 | 60 | In this example, we consider events with "insert" to be new loans, and 61 | events with "update" to be existing loans that were refinanced. We use 62 | this distinction to provide a more detailed message. 63 | */ 64 | 65 | changeStream.on("change", change => { 66 | // console.log(change) 67 | let { amount, broker, borrower } = change.fullDocument 68 | switch (change.operationType) { 69 | case "insert": 70 | console.log( 71 | `${broker} just negotiated a new loan with ${borrower} worth ${amount} USD!`, 72 | ) 73 | break 74 | case "update": 75 | console.log( 76 | `${broker} just refinanced a loan with ${borrower} worth ${amount} USD!`, 77 | ) 78 | break 79 | } 80 | }) 81 | 82 | // update the loans in this collection, to simulate loans being created and 83 | // restructured/refinanced 84 | await simulateBankActivity(loans_collection) 85 | 86 | // close the change stream and drop loans_collection when we're done 87 | await changeStream.close() 88 | await loans_collection.drop() 89 | 90 | process.exit(1) 91 | } catch (e) { 92 | console.log(e) 93 | } 94 | 95 | async function simulateBankActivity(collection) { 96 | // produce a random integer - this will be each loan's amount 97 | function getRandomLoanAmount() { 98 | min = Math.ceil(MIN_LOAN_AMOUNT) 99 | max = Math.floor(MAX_LOAN_AMOUNT) 100 | return ( 101 | Math.floor(Math.random() * (MAX_LOAN_AMOUNT - MIN_LOAN_AMOUNT)) + 102 | MIN_LOAN_AMOUNT 103 | ) 104 | } 105 | 106 | // get a random date - this will be each loan's due date 107 | function getRandomDueDate() { 108 | let startDate = new Date() 109 | let endDate = new Date(2030, 0, 1) 110 | return new Date( 111 | startDate.getTime() + 112 | Math.random() * (endDate.getTime() - startDate.getTime()), 113 | ) 114 | } 115 | 116 | // sleep for a given number of ms 117 | async function sleep(ms) { 118 | return new Promise(resolve => setTimeout(resolve, ms)) 119 | } 120 | 121 | // randomly insert/update loan documents with new amounts and due dates 122 | // alternates between inserting and updating data 123 | for (let i = 0; i < 1000; i++) { 124 | let newLoanData = { 125 | borrower: faker.name.findName(), 126 | broker: faker.name.findName(), 127 | amount: getRandomLoanAmount(), 128 | dueDate: getRandomDueDate(), 129 | } 130 | if (i < 50 || i % 2 == 0) { 131 | let insertResult = await collection.insertOne(newLoanData) 132 | } else { 133 | let { amount, broker, dueDate } = newLoanData 134 | 135 | // randomly select a loan in loans_collection 136 | // to update its amount and due date 137 | let updateResult = await collection.updateOne( 138 | { _id: { $exists: true } }, 139 | { 140 | $set: { amount, dueDate }, 141 | }, 142 | ) 143 | } 144 | await sleep(1000) 145 | } 146 | } 147 | })() 148 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/writes-with-error-handling.spec.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "mongodb" 2 | describe("Error Handling", () => { 3 | /* Hello! 4 | * In this lesson we are going to talk about Error Handling. We will cover a 5 | * few different kinds of errors and discuss ways to handle them gracefully. 6 | * 7 | * This way can ensure that our application is resilient to issues that can 8 | * occur in concurrent and distributed systems. 9 | * 10 | * Distributed systems are prone to network issues, while concurrent systems 11 | * will more likely encounter duplicate key errors. 12 | * 13 | * While the errors covered in this lesson aren't very likely to occur, it is 14 | * helpful to know how to deal with them if and when they manifest themselves. 15 | */ 16 | 17 | let errors 18 | // for this lesson we're creating a new collection called errors to work with. 19 | beforeAll(async () => { 20 | errors = await global.mflixClient 21 | .db(process.env.MFLIX_NS) 22 | .collection("errors") 23 | }) 24 | 25 | // and after all the tests run, we'll drop this collection 26 | afterAll(async () => { 27 | await errors.drop() 28 | }) 29 | 30 | it("duplicateKey", async () => { 31 | /** 32 | * add one document to the "errors" database with the _id value set to 0 33 | */ 34 | let insertResult = await errors.insertOne({ 35 | _id: 0, 36 | }) 37 | 38 | /* The first common error can occur when you are trying to insert a document 39 | * in place of an already existing document. In our example there is already 40 | * a document with _id that equals 0, so inserting another document with the 41 | * same _id value should cause a duplicate key error. 42 | * 43 | * Let's test to see if this is true. 44 | * 45 | * In this test case we are specifying that we are expecting to get a 46 | * Duplicate Key error. 47 | */ 48 | 49 | let { n, ok } = insertResult.result 50 | expect({ n, ok }).toEqual({ n: 1, ok: 1 }) 51 | // Let's check that the document was successfully inserted. 52 | expect(insertResult.insertedCount).toBe(1) 53 | 54 | // and what if we tried to insert a document with the same _id? 55 | try { 56 | let dupId = await errors.insertOne({ 57 | _id: 0, 58 | }) 59 | } catch (e) { 60 | expect(e).not.toBeUndefined() 61 | // we get an error message stating we've tried to insert a duplicate key 62 | expect(e.errmsg).toContain("E11000 duplicate key error collection") 63 | console.log(e) 64 | } 65 | }) 66 | 67 | /* Great! It looks like the test passed, but it would be great to know 68 | * exactly what kind of error we are getting. In this test case you can see 69 | * that the error returned is the Duplicate Key error, which means that in 70 | * order to correct it we should not be trying to insert a Document with an 71 | * existing key. 72 | * 73 | * Simply changing the key value should do the trick. 74 | */ 75 | it("avoids duplicateKey", async () => { 76 | try { 77 | let notdupId = await errors.insertOne({ 78 | _id: 3, 79 | }) 80 | } catch (e) { 81 | expect(e).toBeUndefined() 82 | } 83 | }) 84 | 85 | /* Another error to be on the lookout for is the timeout error. In this test 86 | * case we are trying to avoid breaking the application by using the try/catch 87 | * block. 88 | * 89 | * This particular test case won't cause a timeout error. In fact, it is very 90 | * hard to induce a timeout error or any of the errors covered in this lesson 91 | * on an application that is running on Atlas. 92 | * 93 | * But if that does happen, then a try/catch block will help you identify the 94 | * situation. 95 | * 96 | * To fix a timeout issue you need to consider the needs of your application, 97 | * and depending on that you can either reduce durability guarantees by 98 | * lowering the write concern value or increase the timeout and keep the app 99 | * durable. 100 | */ 101 | 102 | it("timeout", async () => { 103 | try { 104 | let dupId = await errors.insertOne( 105 | { 106 | _id: 6, 107 | }, 108 | { wtimeoutMS: 1 }, 109 | ) 110 | } catch (e) { 111 | expect(e).toBeUndefined() 112 | } 113 | }) 114 | 115 | /* 116 | * 117 | * Another possible error can occur when the write concern that is 118 | * requested cannot be fulfilled. 119 | * 120 | * For example, our replica set has 3 nodes that was automatically created by 121 | * Atlas. We can dictate the type of write concern that we want for our write 122 | * operations. In the example below we are asking for a 5 node 123 | * acknowledgement, which is impossible in our situation. As a result we get a 124 | * Write Concern Exception. 125 | * 126 | * This error is easy to solve by either assigning a majority write concern or 127 | * a number that is less than or equal to 3. 128 | * 129 | */ 130 | 131 | it("WriteConcernError", async () => { 132 | try { 133 | let dupId = await errors.insertOne( 134 | { 135 | _id: 6, 136 | }, 137 | { w: 5 }, 138 | ) 139 | } catch (e) { 140 | expect(e).not.toBeNull() 141 | // Now let's check the error that was returned from the driver. 142 | console.log(e) 143 | } 144 | }) 145 | }) 146 | 147 | // That's it for our lesson on error handling. Enjoy the rest of the course! 148 | -------------------------------------------------------------------------------- /mflix-js/build/static/media/pixelatedLeaf.6c93bd20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/static/media/pixelatedLeaf.6c93bd20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/callbacks-promises-async.spec.js: -------------------------------------------------------------------------------- 1 | describe("Callbacks, Promises, and Aysnc/Await", () => { 2 | /** 3 | * In this lesson, we'll discuss the difference between callbacks, promises, 4 | * and async/await in modern Javascript. We'll also discuss how the Node 5 | * driver responds depending on how you call certain methods. 6 | */ 7 | let movies 8 | beforeAll(async () => { 9 | movies = await global.mflixClient 10 | .db(process.env.MFLIX_NS) 11 | .collection("movies") 12 | }) 13 | 14 | test("Callbacks", done => { 15 | /** 16 | * Unless you are completely new to Javascript - and welcome if you are! - 17 | * you are most likely familiar with callbacks. For those that might need 18 | * a refresher, callbacks are a way of passing a bundle of information along 19 | * to a different part of our program. They are functions that some other 20 | * portion of code can run. I like to think of them like handing a remote 21 | * control to someone else. Let's look at an example. 22 | * 23 | * For each example, we'll print out the collection names we have. 24 | */ 25 | movies.findOne({ title: "Once Upon a Time in Mexico" }, function(err, doc) { 26 | expect(err).toBeNull() 27 | expect(doc.title).toBe("Once Upon a Time in Mexico") 28 | expect(doc.cast).toContain("Salma Hayek") 29 | done() 30 | }) 31 | /** 32 | * Here we passed a callback function. If there would have been an error, 33 | * the `err` paramater would have been some other value than Null. The doc 34 | * parameter was the found document. 35 | */ 36 | }) 37 | 38 | test("Promises", done => { 39 | /** 40 | * Now we'll jump into Promises. Promises are a way of saying "Do this 41 | * thing and when it's done do one of two things: Resolve or Reject". 42 | * Promises allow us to be more declarative. Let's look at the same query 43 | * as before but instead use a Promise. 44 | * 45 | * One interesting note. While browsing the Node driver documentation you 46 | * may encounter documentation like the following: 47 | * 48 | * findOne(query, options, callback) -> {Promise} 49 | * 50 | * The Driver team inspects whether a callback is passed. If none is, the 51 | * driver will return a Promise automatically. Great stuff! 52 | */ 53 | 54 | movies 55 | .findOne({ title: "Once Upon a Time in Mexico" }) 56 | .then(doc => { 57 | expect(doc.title).toBe("Once Upon a Time in Mexico") 58 | expect(doc.cast).toContain("Salma Hayek") 59 | done() 60 | }) 61 | .catch(err => { 62 | expect(err).toBeNull() 63 | done() 64 | }) 65 | 66 | /** 67 | * This is pretty nice. We said "Hey Mongo, find one document where the 68 | * title is 'Once Upon a Time in Mexico', THEN I'll do something with that. 69 | * CATCH any error and let me do something with that" 70 | * 71 | * Now on to async/await! 72 | */ 73 | }) 74 | 75 | test("Async/Await", async () => { 76 | /** 77 | * There's another way to handle Promises that results in very clean code, 78 | * and that's using async/await. It's the style we use throughout the 79 | * codebase for this project! 80 | * 81 | * Don't let the terminology intimidate you. We Javascript programmers like 82 | * to add fancy names to things to feel better about things like 83 | * ![] evaluating to false. Forgive us. 84 | * 85 | * To use async/await, we need to mark our function as async, as can be seen 86 | * in the test function for this portion of the lesson. This then let's us 87 | * use the `await` keyword for Promises. Behind the scenes, you can think 88 | * of it as attaching the `.then()` part to a promise and returning that 89 | * value. 90 | * 91 | * What about the `.catch()` part? That's handled by a catch block. An 92 | * important thing to remember is to always surround `await` statements with 93 | * a try/catch block. 94 | * 95 | * Let's see an example. 96 | */ 97 | 98 | try { 99 | let { title } = await movies.findOne({ 100 | title: "Once Upon a Time in Mexico", 101 | }) 102 | let { cast } = await movies.findOne({ 103 | title: "Once Upon a Time in Mexico", 104 | }) 105 | expect(title).toBe("Once Upon a Time in Mexico") 106 | expect(cast).toContain("Salma Hayek") 107 | } catch (e) { 108 | expect(e).toBeNull() 109 | } 110 | 111 | /** 112 | * I've used destructuring here to lift the title and cast keys from the 113 | * returned document. It would have been more efficient to do a single 114 | * findOne operation and grab the title and cast at the same time, but I 115 | * wanted to demonstrate that multiple awaits can be within the same 116 | * try/catch block. 117 | */ 118 | }) 119 | 120 | /** 121 | * And that covers Callbacks, Promises, and Async/Await at a high level and 122 | * how the Node driver will behave when passed a callback or not. If any of 123 | * this is unclear, don't worry. Head to our forums to ask for help. If you 124 | * are very new to Javascript and aren't familiar with callbacks or promises, 125 | * I recommend reading 126 | * https://medium.com/front-end-hacking/callbacks-promises-and-async-await-ad4756e01d90 127 | * 128 | * Some things to remember. 129 | * You should always wrap await statements with a try/catch block. 130 | * If you aren't comfortable with Promises, you can supply a callback to the 131 | * Node driver methods that are asynchronous. 132 | * If you don't pass a callback, a Promise will be returned. 133 | * 134 | */ 135 | }) 136 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/change-insert.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require("mongodb").MongoClient 2 | const faker = require("faker") 3 | require("dotenv").config() 4 | ;(async function() { 5 | const client = await MongoClient.connect(process.env.MFLIX_DB_URI, { 6 | wtimeout: 2500, 7 | poolSize: 50, 8 | useNewUrlParser: true, 9 | }) 10 | 11 | /** 12 | In this lesson, we're going to use change streams to track real-time 13 | changes to the data that our application's using. 14 | 15 | We will open a change stream at the collection level. By default, the change 16 | stream will capture any change made to the collection, but we are going to 17 | start by focusing on inserts. 18 | */ 19 | 20 | try { 21 | /** 22 | A change stream contains change events for different database operations 23 | that result in some change to the data. These could be inserts, updates or 24 | deletes. 25 | 26 | The fields available to us in each change event will vary depending on the 27 | operation, but operation type will always appear, along with clusterTime. 28 | Operation type tells us whether it was an insert, update or delete, and 29 | clusterTime tells us the time when the operation completed. 30 | 31 | The fullDocument field appears in insert change events, and it gives us 32 | the entire document that was just inserted. We can also return this to the 33 | change stream cursor for update operations, we just have to ask for it 34 | specifically - we'll cover that in a bit. 35 | 36 | The updateDescription field only appears in update operations, and it 37 | tells us which fields were updated and which fields were removed from the 38 | existing document. 39 | */ 40 | 41 | const bankDB = await client.db("bankDB") 42 | const loans_collection = await bankDB.collection("loans") 43 | 44 | /** 45 | To open a change stream against a specific collection, we call the 46 | collection method .watch(). This method takes an optional pipeline 47 | parameter, which we can use to transform or filter out change events. 48 | 49 | Opening a change stream opens the flow of change events to the change 50 | stream cursor. From the application side, we pull change events from this 51 | change stream cursor. 52 | 53 | When we pass an empty pipeline, every data change will result in a change 54 | event document in our cursor. 55 | */ 56 | 57 | const emptyPipeline = [] 58 | const changeStream = loans_collection.watch(emptyPipeline) 59 | 60 | /** 61 | Using .on(), we can tell the change stream to do something for each change 62 | event in the cursor. 63 | 64 | In this example, we've asked the change stream to log each change to the 65 | console. We could also write those changes to a file, or maybe send an 66 | email alert. 67 | */ 68 | 69 | changeStream.on("change", change => { 70 | console.log("change", change) 71 | }) 72 | 73 | /** 74 | In addition to "change", .on() also takes the following event types: 75 | "close", "end", and "error". We can use these event types to alert us 76 | for different reasons. 77 | */ 78 | 79 | // populate loans_collection with data 80 | await insertDocs(loans_collection) 81 | 82 | // close the change stream when we're done 83 | changeStream.close() 84 | 85 | // drop loans_collection 86 | loans_collection.drop() 87 | 88 | // exit the program 89 | process.exit(1) 90 | } catch (e) { 91 | console.log(e) 92 | } 93 | 94 | /** 95 | In this example, the namespace bankDB.loans stores a document for each loan 96 | issued by the bank. Our change stream is going to track every change made to 97 | this collection. 98 | */ 99 | 100 | // insert documents into `bankDB.loans` 101 | async function insertDocs(collection) { 102 | let amounts = [2000, 400, 5000, 1200, 350, 10000, 3500, 800] 103 | let borrowerNames = [ 104 | "Manjunath", 105 | "Hamsa", 106 | "Gayatri", 107 | "Tanvi", 108 | "Aditya", 109 | "Akash", 110 | "Arjun", 111 | "Divya", 112 | ] 113 | 114 | // get a random date - this will be each loan's due date 115 | function getRandomDueDate() { 116 | let startDate = new Date() 117 | let endDate = new Date(2030, 0, 1) 118 | return new Date( 119 | startDate.getTime() + 120 | Math.random() * (endDate.getTime() - startDate.getTime()), 121 | ) 122 | } 123 | 124 | // sleep for a given number of ms 125 | async function sleep(ms) { 126 | return new Promise(resolve => setTimeout(resolve, ms)) 127 | } 128 | 129 | // use .map() to create the loan documents 130 | let docs = amounts.map((amount, idx) => ({ 131 | amount, 132 | borrower: borrowerNames[idx], 133 | dueDate: getRandomDueDate(), 134 | })) 135 | 136 | // insert the loan documents 137 | for (var idx = 0; idx < docs.length; idx++) { 138 | let doc = docs[idx] 139 | let insertResult = collection.insertOne(doc) 140 | await sleep(1000) 141 | } 142 | } 143 | 144 | /** 145 | Now that we've opened a change stream on the "loans" collection, we should 146 | see any new loans that come in, as well as any changes made to existing 147 | loans. 148 | 149 | On the output, we can see that each change event has an "operationType". In 150 | this example, they all have type "insert". 151 | 152 | We also have a "fullDocument" field, which has the entire document after the 153 | change event occurred. In this example, the change events are inserts, so 154 | the _id in the full document was just created by MongoDB. 155 | 156 | However, we still haven't pased a pipeline to the change stream. Because of 157 | this, the change stream cursor will capture any change, regardless of 158 | context. We might want to specify which changes we actually care about. 159 | */ 160 | })() 161 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/basic-writes.spec.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "mongodb" 2 | describe("Basic Writes", () => { 3 | /** 4 | * In this lesson, we'll discuss how to perform write operations in MongoDB, 5 | * the "C" and "U" in Create, Read, Update, Delete 6 | * 7 | * The first method we'll talk about is insertOne. insertOne inserts one 8 | * document into the database. 9 | */ 10 | 11 | let videoGames 12 | // for this lesson we're creating a new collection called videoGames 13 | beforeAll(async () => { 14 | videoGames = await global.mflixClient 15 | .db(process.env.MFLIX_NS) 16 | .collection("videoGames") 17 | }) 18 | 19 | // and after all the tests run, we'll drop this collection 20 | afterAll(async () => { 21 | await videoGames.drop() 22 | }) 23 | 24 | it("insertOne", async () => { 25 | /** 26 | * Let's insert a document with the title Fortnite and a year of 2018 27 | */ 28 | let insertResult = await videoGames.insertOne({ 29 | title: "Fortnite", 30 | year: 2018, 31 | }) 32 | // when we insert a document, we get an insertOneWriteOpResult 33 | // one of its properties is result. n is the total documents inserted 34 | // and ok means the database responded that the command executed correctly 35 | let { n, ok } = insertResult.result 36 | expect({ n, ok }).toEqual({ n: 1, ok: 1 }) 37 | // it also contains an insertedCount key, which should be the same as n 38 | // above 39 | expect(insertResult.insertedCount).toBe(1) 40 | // the last property we'll talk about on it is insertedId 41 | // if we don't specify an _id when we write to the database, MongoDB will 42 | // insert one for us and return this to us here 43 | expect(insertResult.insertedId).not.toBeUndefined() 44 | console.log("inserted _id", insertResult.insertedId) 45 | 46 | // let's ensure that we can find document we just inserted with the 47 | // insertedId we just received 48 | let { title, year } = await videoGames.findOne({ 49 | _id: ObjectId(insertResult.insertedId), 50 | }) 51 | expect({ title, year }).toEqual({ title: "Fortnite", year: 2018 }) 52 | 53 | // and what if we tried to insert a document with the same _id? 54 | try { 55 | let dupId = await videoGames.insertOne({ 56 | _id: insertResult.insertedId, 57 | title: "Foonite", 58 | year: 2099, 59 | }) 60 | } catch (e) { 61 | expect(e).not.toBeUndefined() 62 | // we get an error message stating we've tried to insert a duplicate key 63 | expect(e.errmsg).toContain("E11000 duplicate key error collection") 64 | } 65 | }) 66 | it("insertMany", async () => { 67 | /** 68 | * The insertOne method is useful, but what if we want to insert more than 69 | * one document at a time? The preferred method for that is insertMany 70 | */ 71 | 72 | let megaManYears = [ 73 | 1987, 74 | 1988, 75 | 1990, 76 | 1991, 77 | 1992, 78 | 1993, 79 | 1995, 80 | 1996, 81 | 2008, 82 | 2010, 83 | ] 84 | 85 | // Creating documents to insert based on the megaManYears array above 86 | let docs = megaManYears.map((year, idx) => ({ 87 | title: `Mega Man ${idx + 1}`, 88 | year, 89 | })) 90 | 91 | // now let's insert these into the database 92 | let insertResult = await videoGames.insertMany(docs) 93 | 94 | // just like insertOne, we'll get a result object back that has information 95 | // like the number of documents inserted and the inserted _ids 96 | expect(insertResult.insertedCount).toBe(10) 97 | expect(Object.values(insertResult.insertedIds).length).toBe(10) 98 | // and we can see what the insertIds were 99 | console.log(Object.values(insertResult.insertedIds)) 100 | }) 101 | /** 102 | * Inserting is a useful write operation, but it's very simple. It inserts 103 | * new data into the database without regard for what the new data is, or 104 | * what's already in the database as as long as we don't specify a duplicate 105 | * _id. 106 | * 107 | * So what happens if we want to insert a document, but we're not sure if it's 108 | * already in the database? We could do a find to check, but that's 109 | * inefficient when we have the ability to upsert. 110 | */ 111 | it("upsert", async () => { 112 | // this is an upsert. We use the update method instead of insert. 113 | let upsertResult = await videoGames.updateOne( 114 | // this is the "query" portion of the update 115 | { title: "Call of Duty" }, 116 | // this is the update 117 | { 118 | $set: { 119 | title: "Call of Duty", 120 | year: 2003, 121 | }, 122 | }, 123 | // this is the options document. We've specified upsert: true, so if the 124 | // query doesn't find a document to update, it will be written instead as 125 | // a new document 126 | { upsert: true }, 127 | ) 128 | 129 | // we don't expect any documents to have been modified 130 | expect(upsertResult.result.nModified).toBe(0) 131 | // and here's the information that the result.upserted key contains 132 | // an _id and an index 133 | console.log(upsertResult.result.upserted) 134 | 135 | // what if the document existed? 136 | upsertResult = await videoGames.updateOne( 137 | { title: "Call of Duty" }, 138 | // we'll update the year to 2018 139 | { 140 | $set: { 141 | title: "Call of Duty", 142 | year: 2018, 143 | }, 144 | }, 145 | { upsert: true }, 146 | ) 147 | // we can see the second upsert result does not have an upserted key 148 | console.log("second upsert result", upsertResult.result) 149 | expect(upsertResult.result.nModified).toBe(1) 150 | 151 | // upserts are useful, especially when we can make a write operation 152 | // generic enough that updating or inserting should give the same result 153 | // to our application 154 | }) 155 | }) 156 | 157 | /** 158 | * Let's Summarize: 159 | * 160 | * The two idiomatic methods for inserting documents are insertOne and 161 | * insertMany 162 | * 163 | * Trying to insert a duplicate _id will fail 164 | * 165 | * As an alternative to inserting, we can specify an upsert in an update 166 | * operation 167 | */ 168 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/basic-deletes.spec.js: -------------------------------------------------------------------------------- 1 | describe("Basic Deletes", () => { 2 | /** 3 | * In this lesson, we'll discuss how to perform delete operations in MongoDB. 4 | * 5 | * The first method we'll talk about is deleteOne. Before we do this, we need 6 | * to create a collection and insert some documents into it. 7 | */ 8 | 9 | let videoGames 10 | // As with our updates lesson, we're creating a new collection called videoGames 11 | beforeAll(async () => { 12 | videoGames = await global.mflixClient 13 | .db(process.env.MFLIX_NS) 14 | .collection("videoGames") 15 | }) 16 | 17 | // and then after all the tests run, we'll drop this collection 18 | afterAll(async () => { 19 | await videoGames.drop() 20 | }) 21 | 22 | it("insertMany", async () => { 23 | /** 24 | * First, let's insert documents into this collection so that we can delete 25 | * them during this lesson. 26 | */ 27 | 28 | let megaManYears = [ 29 | 1987, 30 | 1988, 31 | 1990, 32 | 1991, 33 | 1992, 34 | 1993, 35 | 1995, 36 | 1996, 37 | 2008, 38 | 2010, 39 | ] 40 | 41 | // Here we are creating documents to insert based on the megaManYears array above 42 | let docs = megaManYears.map((year, idx) => ({ 43 | title: "Mega Man", 44 | year, 45 | })) 46 | 47 | // now let's insert these into the database 48 | let insertResult = await videoGames.insertMany(docs) 49 | 50 | // Let's check that our documents are present. 51 | expect(insertResult.insertedCount).toBe(10) 52 | expect(Object.values(insertResult.insertedIds).length).toBe(10) 53 | // and we can see what the insertIds were 54 | console.log(Object.values(insertResult.insertedIds)) 55 | }) 56 | 57 | it("deleteOne", async () => { 58 | /** 59 | Now let's try to delete a document using deleteOne() 60 | The first thing to understand is that a delete operation is in fact 61 | a write in database world. Confused? What I mean by this is that 62 | when we delete a document from our database we are actually 63 | executing a state change in the data, that implies a database write 64 | Deletes imply that several different things will happen: 65 | - collection data will be changed 66 | - indexes will be updated 67 | - entries in the oplog (replication mechanism) will be added. 68 | 69 | All the same operations that an insert or an update would originate. 70 | 71 | But let's go ahead and see this in action with a single document 72 | delete. 73 | 74 | Before I start deleting data from my collection I'm going to count 75 | the number of documents in the sports collection. 76 | */ 77 | 78 | let countDocumentsBefore = await videoGames.count({}) 79 | 80 | // We will then delete a document without specifying any query predicates using deleteOne() 81 | let deleteDocument = await videoGames.deleteOne({}) 82 | 83 | // To check that our delete was successful, we can check that result.n is 84 | // equal to 1. This is an object returned by the MongoDB Javascript driver 85 | // which will tell the client if a delete has been successful or not. 86 | // Let's test to see if the delete was successful. 87 | 88 | expect(deleteDocument.result.n).toBe(1) 89 | 90 | // Great. That's what we expected! 91 | 92 | // If we now count the number of documents remaining in our collection, we 93 | // should see that there is one less than we inserted 94 | 95 | let countDocuments = await videoGames.count({}) 96 | 97 | console.log( 98 | `collection had ${countDocumentsBefore} documents, now has ${countDocuments}`, 99 | ) 100 | 101 | // We just did our first delete. 102 | }) 103 | 104 | // All good. As expected. 105 | 106 | // Wait, but which document did I just remove from the collection? 107 | // Well, in this case MongoDB will select the first $natural document 108 | // it encounters in the collection and delete that one. 109 | // Given that the insertMany inserts documents in order, the document 110 | // that I have just deleted will be the one with year = 1987. 111 | 112 | it("deletewithPredicate", async () => { 113 | /** 114 | * A delete that takes the first element of the collection $natural 115 | * order tends to be a less than usual thing that you would do in your 116 | * application, so let's use something a bit more elaborate. 117 | * Let's say I would like to delete a document that matches a 118 | * particular year. 119 | */ 120 | 121 | let deleteDocument = await videoGames.deleteOne({ year: 2008 }) 122 | 123 | // To check that our delete was successful, we'll check the result.n object. 124 | 125 | expect(deleteDocument.result.n).toBe(1) 126 | 127 | // If we now count the number of documents remaining in our we should see 128 | // that there is one less than we inserted 129 | let countDocuments = await videoGames.count({}) 130 | 131 | console.log(countDocuments) 132 | // All good. As expected. 133 | }) 134 | 135 | // Next, we will use the deleteMany operator. As the name suggests, this 136 | // operator allows us to delete many documents, which match a given filter in 137 | // our delete statement. Let's check it out: 138 | it("deleteMany", async () => { 139 | // let's see how many documents are left in the collection before we use 140 | // the deleteMany operator. 141 | let countDocuments = await videoGames.count({}) 142 | expect(countDocuments).toEqual(8) 143 | 144 | // Now let's try to delete multiple documents using deleteMany(). 145 | // To do this, we will need to specify a filter for the delete statement 146 | // that will match multiple documents. 147 | let deleteManyDocs = await videoGames.deleteMany({ year: { $lt: 1993 } }) 148 | 149 | // This will delete all documents that have a year before 1993. 150 | // To check that our delete was successful, we can check what the value of 151 | // deleteMayDocs.result.n is equal to. In this case, we expect it to be 4. 152 | expect(deleteManyDocs.result.n).toBe(4) 153 | 154 | let findResult = await videoGames.find({}) 155 | let findResultArray = await findResult.toArray() 156 | 157 | // After the delete operation, we expect all the documents to have a year 158 | // greater than or equal to 1993. 159 | for (var i = 0; i < findResultArray.length - 1; i++) { 160 | let videoGame = findResultArray[i] 161 | expect(videoGame.year).toBeGreaterThanOrEqual(1993) 162 | } 163 | 164 | countDocuments = await videoGames.count({}) 165 | expect(countDocuments).toEqual(4) 166 | }) 167 | }) 168 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/basic-reads.spec.js: -------------------------------------------------------------------------------- 1 | describe("Basic Reads", () => { 2 | /** 3 | * In this lesson, we'll discuss how to perform query operations in MongoDB, 4 | * the "R" in Create, Read, Update, Delete - or CRUD. 5 | * 6 | * The first method we'll talk about is findOne. As the name suggests, findOne 7 | * finds one document for us and returns it. 8 | */ 9 | 10 | let movies 11 | // all this code does is set up a handle for us to use the movies collection 12 | // for our CRUD operations. 13 | beforeAll(async () => { 14 | movies = await global.mflixClient 15 | .db(process.env.MFLIX_NS) 16 | .collection("movies") 17 | }) 18 | 19 | it("findOne", async () => { 20 | /** 21 | * Let's find a document where Salma Hayek is a cast member. Because we're 22 | * using findOne, we'll get a single object back, which will be the first 23 | * match returned by the cursor. This is different than the other query 24 | * methods we'll use, which we'll talk about shortly. 25 | */ 26 | 27 | // Our filter 28 | let filter = "Salma Hayek" 29 | 30 | // Because cast is an array, MongoDB will look at all elements of the array 31 | // to match this.This is because MongoDB treats arrays as first-class 32 | // objects. 33 | let result = await movies.findOne({ cast: filter }) 34 | 35 | // we know we have a few movies where Salma Hayek is a cast member, so we 36 | // do not expect a null value 37 | expect(result).not.toBeNull() 38 | 39 | // we've already explored this dataset, and know that the returned movie 40 | // be Roadracers. Let's check the title, year, and cast. 41 | 42 | let { title, year, cast } = result 43 | console.log(result) 44 | 45 | // we expect a title of Roadracers, the year would be 1994, and the cast 46 | // includes Salma Hayek and David Arquette 47 | 48 | expect(title).toBe("Roadracers") 49 | expect(year).toBe(1994) 50 | expect(cast).toContain("David Arquette") 51 | 52 | // what if we did issue a query that resulted in no document returned? It 53 | // would be null 54 | 55 | let nullResult = await movies.findOne({ cast: "flibberty pingpang" }) 56 | expect(nullResult).toBeNull() 57 | }) 58 | 59 | // Looking at the document, we can see there is a lot of information. 60 | // What if we only wanted the title and year? You may be familiar with 61 | // projection mechanics in the mongo shell, where we might do something like 62 | // 63 | // db.movies.findOne({cast: "Salma Hayek"}, { title: 1, year: 1 }) 64 | // 65 | // The Collection class also has projection functionality, but the usage is 66 | // different to that of the mongo shell. 67 | 68 | it("project", async () => { 69 | // We will use the same query predicate as before, 70 | // but only specify the fields we wish to return in the projection part of the query. 71 | let filter = "Salma Hayek" 72 | 73 | let result = await movies.findOne( 74 | { cast: filter }, 75 | { projection: { title: 1, year: 1 } }, 76 | ) 77 | 78 | expect(result).not.toBeNull() 79 | 80 | // and based on the projection we except the object to have 3 keys, 81 | // title, year, and _id 82 | expect(Object.keys(result).length).toBe(3) 83 | 84 | console.log(result) 85 | 86 | // Note that only the fields we specify in the projection section of the 87 | // query will be returned in our result set with the exception of the _id 88 | // field.We need to explicitly specify when we don't want the _id field to 89 | // be returned. Lets try that. 90 | 91 | let result2 = await movies.findOne( 92 | { cast: filter }, 93 | { projection: { title: 1, year: 1, _id: 0 } }, 94 | ) 95 | 96 | expect(result2).not.toBeNull() 97 | expect(Object.keys(result2).length).toBe(2) 98 | 99 | console.log(result2) 100 | }) 101 | 102 | /** 103 | * Sometimes we don't want to find only one document, but all documents that 104 | * match our query predicate, or filter. To do that, we can use the 105 | * db.collection.find method instead of db.collection.findOne 106 | * 107 | * Lets build a query so that only movies with both Salma Hayek and 108 | * Johnny Depp are returned. For that, we'll use the $all operator 109 | * The $all operator will only return documents where all values specified in 110 | * the query are present in the array for that field. Lets look at an example. 111 | */ 112 | 113 | it("all", async () => { 114 | // in the shell, this would be 115 | // db.movie.find({cast: { $all: ["Salma Hayek", "Johnny Depp"] }}) 116 | // With the Node driver, it will look as follows. 117 | 118 | let result = await movies.find({ 119 | cast: { $all: ["Salma Hayek", "Johnny Depp"] }, 120 | }) 121 | // very similar! 122 | 123 | // and we don't except a null result based on previous knowledge of this 124 | // dataset 125 | expect(result).not.toBeNull() 126 | 127 | let { title, year, cast } = await result.next() 128 | 129 | // you can see that we are using the result.next cursor method. 130 | // The db.collection.find() method in MongoDB returns a cursor. 131 | // To access the documents, you need to iterate the cursor with the .next() 132 | // method, or use the toArray() method. 133 | 134 | // The result.next method will return the next result in the cursor. 135 | // If there are no more results, next() resolves to null. 136 | 137 | // As we are movie experts and have examined this dataset, this time, 138 | // we expect the title of Once Upon a Time in Mexico, the year to be 2003, 139 | // and the cast to include Salma Hayek and Johnny Depp. 140 | 141 | expect(title).toBe("Once Upon a Time in Mexico") 142 | expect(year).toBe(2003) 143 | expect(cast).toContain("Johnny Depp") 144 | expect(cast).toContain("Salma Hayek") 145 | 146 | console.log({ title, year, cast }) 147 | }) 148 | }) 149 | 150 | /** 151 | 152 | Let's Summarize: 153 | 154 | Querying MongoDB through the Driver may feel odd at first, but eventually 155 | it will feel like second nature. 156 | 157 | We saw how to retrieve one document using findOne or get all documents that 158 | match a query filter. We also saw how to include only the the fields we 159 | wanted in the result set, and how to remove the _id field. 160 | 161 | A few things to keep in mind: 162 | 163 | Finding one document typically involves querying on a unique index, 164 | such as the _id field. 165 | 166 | When projecting, by specifying inclusion (for example, title: 1) all 167 | fields we don't include will be excluded, except for the _id field. If 168 | we don't want the _id field returned to us we must explicitly exclude 169 | it in the projection stage. 170 | 171 | */ 172 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/cursor-methods-agg-equivalents.spec.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from "mongodb" 2 | 3 | let movies 4 | describe("Cursor Methods and Aggregation Equivalents", () => { 5 | /** 6 | * In this lesson, we'll discuss the methods we can call against MongoDB 7 | * cursors, and the aggregation stages that would perform the same tasks in a 8 | * pipeline. 9 | */ 10 | 11 | beforeAll(async () => { 12 | /** 13 | * Here we're just establishing a connection to the database, and creating a 14 | * handle for the movies collection. 15 | * 16 | * We wrapped this in a try/catch block, in case there's a network error, or 17 | * some other issue. 18 | */ 19 | try { 20 | movies = await global.mflixClient 21 | .db(process.env.MFLIX_NS) 22 | .collection("movies") 23 | } catch (e) { 24 | console.error( 25 | `Unable to establish a collection handle for "mflix.movies": ${e}`, 26 | ) 27 | } 28 | }) 29 | 30 | test("Can limit the number of results returned by a cursor", async () => { 31 | /** 32 | * We're looking for movies that Sam Raimi directed, but we don't need ALL 33 | * the movies he directed - we only want a couple of them. 34 | * 35 | * Because Sam Raimi directed more than 2 movies in this collection, the 36 | * .limit(2) will return only 2 results. Similarly, .limit(10) will only 37 | * return 10 results, and so on. 38 | */ 39 | const limitedCursor = movies 40 | .find({ directors: "Sam Raimi" }, { _id: 0, title: 1, cast: 1 }) 41 | .limit(2) 42 | 43 | // expect this cursor to contain exactly 2 results 44 | expect((await limitedCursor.toArray()).length).toEqual(2) 45 | }) 46 | 47 | test("Can limit the number of results returned by a pipeline", async () => { 48 | /** 49 | * We can also limit the number of results returned by a pipeline, by adding 50 | * a $limit stage in our aggregation. 51 | * 52 | * This pipeline should return only 2 results. 53 | */ 54 | const limitPipeline = [ 55 | { $match: { directors: "Sam Raimi" } }, 56 | { $project: { _id: 0, title: 1, cast: 1 } }, 57 | { $limit: 2 }, 58 | ] 59 | 60 | const limitedAggregation = await movies.aggregate(limitPipeline) 61 | 62 | expect((await limitedAggregation.toArray()).length).toEqual(2) 63 | }) 64 | 65 | test("Can sort the results returned by a cursor", async () => { 66 | /** 67 | * By default, the results in a cursor are sorted in "natural" order - this 68 | * order is an internal implementation, and often has no particular 69 | * structure. 70 | * 71 | * To sort our results in a more structured way, we can choose a key and a 72 | * sort order, then use .sort() to return the results accordingly. 73 | * 74 | * The "year" field denotes the release date of each movie, so the 75 | * .sort([["year", 1]]) will sort our results on the "year" key, in 76 | * ascending order. Conversely, .sort([["year", -1]]) would return them in 77 | * descending order. 78 | */ 79 | const sortedCursor = movies 80 | .find({ directors: "Sam Raimi" }, { _id: 0, year: 1, title: 1, cast: 1 }) 81 | .sort([["year", 1]]) 82 | 83 | const movieArray = await sortedCursor.toArray() 84 | 85 | // expect each movie in our cursor to be newer than the next movie in the 86 | // cursor 87 | for (var i = 0; i < movieArray.length - 1; i++) { 88 | let movie = movieArray[i] 89 | let nextMovie = movieArray[i + 1] 90 | expect(movie.year).toBeLessThanOrEqual(nextMovie.year) 91 | } 92 | }) 93 | 94 | test("Can sort the results returned by a pipeline", async () => { 95 | /** 96 | * We can also sort the results returned by a pipeline, by adding a $sort 97 | * stage in our aggregation. In the Aggregation Framework, we say 98 | * { $sort: { year: 1 } } instead of .sort([["year", 1]]). 99 | * 100 | * This pipeline should sort our results by year, in ascending order. 101 | */ 102 | const sortPipeline = [ 103 | { $match: { directors: "Sam Raimi" } }, 104 | { $project: { _id: 0, year: 1, title: 1, cast: 1 } }, 105 | { $sort: { year: 1 } }, 106 | ] 107 | 108 | const sortAggregation = await movies.aggregate(sortPipeline) 109 | const movieArray = await sortAggregation.toArray() 110 | 111 | for (var i = 0; i < movieArray.length - 1; i++) { 112 | let movie = movieArray[i] 113 | let nextMovie = movieArray[i + 1] 114 | expect(movie.year).toBeLessThanOrEqual(nextMovie.year) 115 | } 116 | }) 117 | 118 | test("Can skip through results in a cursor", async () => { 119 | /** 120 | * Sometimes we don't need all the results in a cursor. Especially when the 121 | * results in the cursor are sorted, we can skip a few results to retrieve 122 | * just the results that we need. 123 | * 124 | * To skip through results in a cursor, we can use the .skip() method with 125 | * an integer denoting how many documents to skip. For example, skip(5) will 126 | * skip the first 5 documents in a cursor, and only return the documents 127 | * that appear after those first 5. 128 | 129 | * Given that we are sorting on year in ascending order, the .skip(5) will 130 | * skip the 5 oldest movies that Sam Raimi directed, and only return the 131 | * more recent movies. 132 | */ 133 | const skippedCursor = movies 134 | .find({ directors: "Sam Raimi" }, { _id: 0, year: 1, title: 1, cast: 1 }) 135 | .sort([["year", 1]]) 136 | .skip(5) 137 | 138 | const regularCursor = movies 139 | .find({ directors: "Sam Raimi" }, { _id: 0, year: 1, title: 1, cast: 1 }) 140 | .sort([["year", 1]]) 141 | 142 | // expect the skipped cursor to contain the same results as the regular 143 | // cursor, minus the first five results 144 | expect(await skippedCursor.toArray()).toEqual( 145 | (await regularCursor.toArray()).slice(5), 146 | ) 147 | }) 148 | 149 | test("Can skip through results in a pipeline", async () => { 150 | /** 151 | * We can also skip through the results returned by a pipeline, by adding a 152 | * $skip stage in our aggregation. 153 | * 154 | * This pipeline should sort our results by year, in ascending order, and 155 | * then skip the 5 oldest movies. 156 | */ 157 | const skippedPipeline = [ 158 | { $match: { directors: "Sam Raimi" } }, 159 | { $project: { _id: 0, year: 1, title: 1, cast: 1 } }, 160 | { $sort: { year: 1 } }, 161 | { $skip: 5 }, 162 | ] 163 | 164 | const regularPipeline = [ 165 | { $match: { directors: "Sam Raimi" } }, 166 | { $project: { _id: 0, year: 1, title: 1, cast: 1 } }, 167 | { $sort: { year: 1 } }, 168 | ] 169 | 170 | const skippedAggregation = await movies.aggregate(skippedPipeline) 171 | const regularAggregation = await movies.aggregate(regularPipeline) 172 | 173 | expect(await skippedAggregation.toArray()).toEqual( 174 | (await regularAggregation.toArray()).slice(5), 175 | ) 176 | }) 177 | }) 178 | -------------------------------------------------------------------------------- /mflix-js/build/static/js/runtime~main.229c360f.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../webpack/bootstrap"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","p","jsonpArray","window","oldJsonpFunction","slice"],"mappings":"aACA,SAAAA,EAAAC,GAQA,IAPA,IAMAC,EAAAC,EANAC,EAAAH,EAAA,GACAI,EAAAJ,EAAA,GACAK,EAAAL,EAAA,GAIAM,EAAA,EAAAC,EAAA,GACQD,EAAAH,EAAAK,OAAoBF,IAC5BJ,EAAAC,EAAAG,GACAG,EAAAP,IACAK,EAAAG,KAAAD,EAAAP,GAAA,IAEAO,EAAAP,GAAA,EAEA,IAAAD,KAAAG,EACAO,OAAAC,UAAAC,eAAAC,KAAAV,EAAAH,KACAc,EAAAd,GAAAG,EAAAH,IAKA,IAFAe,KAAAhB,GAEAO,EAAAC,QACAD,EAAAU,OAAAV,GAOA,OAHAW,EAAAR,KAAAS,MAAAD,EAAAb,GAAA,IAGAe,IAEA,SAAAA,IAEA,IADA,IAAAC,EACAf,EAAA,EAAiBA,EAAAY,EAAAV,OAA4BF,IAAA,CAG7C,IAFA,IAAAgB,EAAAJ,EAAAZ,GACAiB,GAAA,EACAC,EAAA,EAAkBA,EAAAF,EAAAd,OAA2BgB,IAAA,CAC7C,IAAAC,EAAAH,EAAAE,GACA,IAAAf,EAAAgB,KAAAF,GAAA,GAEAA,IACAL,EAAAQ,OAAApB,IAAA,GACAe,EAAAM,IAAAC,EAAAN,EAAA,KAGA,OAAAD,EAIA,IAAAQ,EAAA,GAKApB,EAAA,CACAqB,EAAA,GAGAZ,EAAA,GAGA,SAAAS,EAAA1B,GAGA,GAAA4B,EAAA5B,GACA,OAAA4B,EAAA5B,GAAA8B,QAGA,IAAAC,EAAAH,EAAA5B,GAAA,CACAK,EAAAL,EACAgC,GAAA,EACAF,QAAA,IAUA,OANAhB,EAAAd,GAAAa,KAAAkB,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAnB,EAGAY,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACA1B,OAAA6B,eAAAT,EAAAM,EAAA,CAA0CI,YAAA,EAAAC,IAAAJ,KAK1CX,EAAAgB,EAAA,SAAAZ,GACA,qBAAAa,eAAAC,aACAlC,OAAA6B,eAAAT,EAAAa,OAAAC,YAAA,CAAwDC,MAAA,WAExDnC,OAAA6B,eAAAT,EAAA,cAAiDe,OAAA,KAQjDnB,EAAAoB,EAAA,SAAAD,EAAAE,GAEA,GADA,EAAAA,IAAAF,EAAAnB,EAAAmB,IACA,EAAAE,EAAA,OAAAF,EACA,KAAAE,GAAA,kBAAAF,QAAAG,WAAA,OAAAH,EACA,IAAAI,EAAAvC,OAAAwC,OAAA,MAGA,GAFAxB,EAAAgB,EAAAO,GACAvC,OAAA6B,eAAAU,EAAA,WAAyCT,YAAA,EAAAK,UACzC,EAAAE,GAAA,iBAAAF,EAAA,QAAAM,KAAAN,EAAAnB,EAAAS,EAAAc,EAAAE,EAAA,SAAAA,GAAgH,OAAAN,EAAAM,IAAqBC,KAAA,KAAAD,IACrI,OAAAF,GAIAvB,EAAA2B,EAAA,SAAAtB,GACA,IAAAM,EAAAN,KAAAiB,WACA,WAA2B,OAAAjB,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAgB,EAAAC,GAAsD,OAAA7C,OAAAC,UAAAC,eAAAC,KAAAyC,EAAAC,IAGtD7B,EAAA8B,EAAA,IAEA,IAAAC,EAAAC,OAAA,aAAAA,OAAA,iBACAC,EAAAF,EAAAhD,KAAA2C,KAAAK,GACAA,EAAAhD,KAAAX,EACA2D,IAAAG,QACA,QAAAvD,EAAA,EAAgBA,EAAAoD,EAAAlD,OAAuBF,IAAAP,EAAA2D,EAAApD,IACvC,IAAAU,EAAA4C,EAIAxC","file":"static/js/runtime~main.229c360f.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /mflix-js/mflix/src/main/resources/build/static/js/runtime~main.229c360f.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../webpack/bootstrap"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","p","jsonpArray","window","oldJsonpFunction","slice"],"mappings":"aACA,SAAAA,EAAAC,GAQA,IAPA,IAMAC,EAAAC,EANAC,EAAAH,EAAA,GACAI,EAAAJ,EAAA,GACAK,EAAAL,EAAA,GAIAM,EAAA,EAAAC,EAAA,GACQD,EAAAH,EAAAK,OAAoBF,IAC5BJ,EAAAC,EAAAG,GACAG,EAAAP,IACAK,EAAAG,KAAAD,EAAAP,GAAA,IAEAO,EAAAP,GAAA,EAEA,IAAAD,KAAAG,EACAO,OAAAC,UAAAC,eAAAC,KAAAV,EAAAH,KACAc,EAAAd,GAAAG,EAAAH,IAKA,IAFAe,KAAAhB,GAEAO,EAAAC,QACAD,EAAAU,OAAAV,GAOA,OAHAW,EAAAR,KAAAS,MAAAD,EAAAb,GAAA,IAGAe,IAEA,SAAAA,IAEA,IADA,IAAAC,EACAf,EAAA,EAAiBA,EAAAY,EAAAV,OAA4BF,IAAA,CAG7C,IAFA,IAAAgB,EAAAJ,EAAAZ,GACAiB,GAAA,EACAC,EAAA,EAAkBA,EAAAF,EAAAd,OAA2BgB,IAAA,CAC7C,IAAAC,EAAAH,EAAAE,GACA,IAAAf,EAAAgB,KAAAF,GAAA,GAEAA,IACAL,EAAAQ,OAAApB,IAAA,GACAe,EAAAM,IAAAC,EAAAN,EAAA,KAGA,OAAAD,EAIA,IAAAQ,EAAA,GAKApB,EAAA,CACAqB,EAAA,GAGAZ,EAAA,GAGA,SAAAS,EAAA1B,GAGA,GAAA4B,EAAA5B,GACA,OAAA4B,EAAA5B,GAAA8B,QAGA,IAAAC,EAAAH,EAAA5B,GAAA,CACAK,EAAAL,EACAgC,GAAA,EACAF,QAAA,IAUA,OANAhB,EAAAd,GAAAa,KAAAkB,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAnB,EAGAY,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACA1B,OAAA6B,eAAAT,EAAAM,EAAA,CAA0CI,YAAA,EAAAC,IAAAJ,KAK1CX,EAAAgB,EAAA,SAAAZ,GACA,qBAAAa,eAAAC,aACAlC,OAAA6B,eAAAT,EAAAa,OAAAC,YAAA,CAAwDC,MAAA,WAExDnC,OAAA6B,eAAAT,EAAA,cAAiDe,OAAA,KAQjDnB,EAAAoB,EAAA,SAAAD,EAAAE,GAEA,GADA,EAAAA,IAAAF,EAAAnB,EAAAmB,IACA,EAAAE,EAAA,OAAAF,EACA,KAAAE,GAAA,kBAAAF,QAAAG,WAAA,OAAAH,EACA,IAAAI,EAAAvC,OAAAwC,OAAA,MAGA,GAFAxB,EAAAgB,EAAAO,GACAvC,OAAA6B,eAAAU,EAAA,WAAyCT,YAAA,EAAAK,UACzC,EAAAE,GAAA,iBAAAF,EAAA,QAAAM,KAAAN,EAAAnB,EAAAS,EAAAc,EAAAE,EAAA,SAAAA,GAAgH,OAAAN,EAAAM,IAAqBC,KAAA,KAAAD,IACrI,OAAAF,GAIAvB,EAAA2B,EAAA,SAAAtB,GACA,IAAAM,EAAAN,KAAAiB,WACA,WAA2B,OAAAjB,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAgB,EAAAC,GAAsD,OAAA7C,OAAAC,UAAAC,eAAAC,KAAAyC,EAAAC,IAGtD7B,EAAA8B,EAAA,IAEA,IAAAC,EAAAC,OAAA,aAAAA,OAAA,iBACAC,EAAAF,EAAAhD,KAAA2C,KAAAK,GACAA,EAAAhD,KAAAX,EACA2D,IAAAG,QACA,QAAAvD,EAAA,EAAgBA,EAAAoD,EAAAlD,OAAuBF,IAAAP,EAAA2D,EAAApD,IACvC,IAAAU,EAAA4C,EAIAxC","file":"static/js/runtime~main.229c360f.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /mflix-js/src/dao/usersDAO.js: -------------------------------------------------------------------------------- 1 | let users 2 | let sessions 3 | 4 | export default class UsersDAO { 5 | static async injectDB(conn) { 6 | if (users && sessions) { 7 | return 8 | } 9 | try { 10 | users = await conn.db(process.env.MFLIX_NS).collection("users") 11 | sessions = await conn.db(process.env.MFLIX_NS).collection("sessions") 12 | } catch (e) { 13 | console.error(`Unable to establish collection handles in userDAO: ${e}`) 14 | } 15 | } 16 | 17 | /** 18 | Ticket: User Management 19 | 20 | For this ticket, you will need to implement the following five methods: 21 | 22 | - getUser 23 | - addUser 24 | - loginUser 25 | - logoutUser 26 | - getUserSession 27 | 28 | You can find these methods below this comment. Make sure to read the comments 29 | in each method to better understand the implementation. 30 | 31 | The method deleteUser is already given to you. 32 | */ 33 | 34 | /** 35 | * Finds a user in the `users` collection 36 | * @param {string} email - The email of the desired user 37 | * @returns {Object | null} Returns either a single user or nothing 38 | */ 39 | static async getUser(email) { 40 | // TODO Ticket: User Management 41 | // Retrieve the user document corresponding with the user's email. 42 | return await users.findOne({ email: email }) 43 | } 44 | 45 | /** 46 | * Adds a user to the `users` collection 47 | * @param {UserInfo} userInfo - The information of the user to add 48 | * @returns {DAOResponse} Returns either a "success" or an "error" Object 49 | */ 50 | static async addUser(userInfo) { 51 | /** 52 | Ticket: Durable Writes 53 | 54 | Please increase the durability of this method by using a non-default write 55 | concern with ``insertOne``. 56 | */ 57 | 58 | try { 59 | // TODO Ticket: User Management 60 | // Insert a user with the "name", "email", and "password" fields. 61 | // TODO Ticket: Durable Writes 62 | // Use a more durable Write Concern for this operation. 63 | //console.log(userInfo); 64 | let addUser = await users.insertOne( 65 | userInfo, 66 | { 67 | w : 'majority' 68 | }); 69 | return { success: true } 70 | } catch (e) { 71 | if (String(e).startsWith("MongoError: E11000 duplicate key error")) { 72 | return { error: "A user with the given email already exists." } 73 | } 74 | console.error(`Error occurred while adding new user, ${e}.`) 75 | return { error: e } 76 | } 77 | } 78 | 79 | /** 80 | * Adds a user to the `sessions` collection 81 | * @param {string} email - The email of the user to login 82 | * @param {string} jwt - A JSON web token representing the user's claims 83 | * @returns {DAOResponse} Returns either a "success" or an "error" Object 84 | */ 85 | static async loginUser(email, jwt) { 86 | try { 87 | // TODO Ticket: User Management 88 | // Use an UPSERT statement to update the "jwt" field in the document, 89 | // matching the "user_id" field with the email passed to this function. 90 | let result = await sessions.updateOne( 91 | { user_id: email }, 92 | { $set: { 'jwt': jwt } }, 93 | {upsert : true} 94 | ) 95 | return { success: true } 96 | } catch (e) { 97 | console.error(`Error occurred while logging in user, ${e}`) 98 | return { error: e } 99 | } 100 | } 101 | 102 | /** 103 | * Removes a user from the `sessons` collection 104 | * @param {string} email - The email of the user to logout 105 | * @returns {DAOResponse} Returns either a "success" or an "error" Object 106 | */ 107 | static async logoutUser(email) { 108 | try { 109 | // TODO Ticket: User Management 110 | // Delete the document in the `sessions` collection matching the email. 111 | await sessions.deleteOne({ user_id: email }) 112 | return { success: true } 113 | } catch (e) { 114 | console.error(`Error occurred while logging out user, ${e}`) 115 | return { error: e } 116 | } 117 | } 118 | 119 | /** 120 | * Gets a user from the `sessions` collection 121 | * @param {string} email - The email of the user to search for in `sessions` 122 | * @returns {Object | null} Returns a user session Object, an "error" Object 123 | * if something went wrong, or null if user was not found. 124 | */ 125 | static async getUserSession(email) { 126 | try { 127 | // TODO Ticket: User Management 128 | // Retrieve the session document corresponding with the user's email. 129 | return sessions.findOne({ user_id: email }) 130 | } catch (e) { 131 | console.error(`Error occurred while retrieving user session, ${e}`) 132 | return null 133 | } 134 | } 135 | 136 | /** 137 | * Removes a user from the `sessions` and `users` collections 138 | * @param {string} email - The email of the user to delete 139 | * @returns {DAOResponse} Returns either a "success" or an "error" Object 140 | */ 141 | static async deleteUser(email) { 142 | try { 143 | await users.deleteOne({ email }) 144 | await sessions.deleteOne({ user_id: email }) 145 | if (!(await this.getUser(email)) && !(await this.getUserSession(email))) { 146 | return { success: true } 147 | } else { 148 | console.error(`Deletion unsuccessful`) 149 | return { error: `Deletion unsuccessful` } 150 | } 151 | } catch (e) { 152 | console.error(`Error occurred while deleting user, ${e}`) 153 | return { error: e } 154 | } 155 | } 156 | 157 | /** 158 | * Given a user's email and an object of new preferences, update that user's 159 | * data to include those preferences. 160 | * @param {string} email - The email of the user to update. 161 | * @param {Object} preferences - The preferences to include in the user's data. 162 | * @returns {DAOResponse} 163 | */ 164 | static async updatePreferences(email, preferences) { 165 | try { 166 | /** 167 | Ticket: User Preferences 168 | 169 | Update the "preferences" field in the corresponding user's document to 170 | reflect the new information in preferences. 171 | */ 172 | 173 | preferences = preferences || {} 174 | 175 | // TODO Ticket: User Preferences 176 | // Use the data in "preferences" to update the user's preferences. 177 | const updateResponse = await users.updateOne( 178 | { 179 | email: email 180 | }, { 181 | $set: { 182 | "preferences": preferences 183 | } 184 | }, { 185 | upsert: true 186 | } 187 | ) 188 | 189 | if (updateResponse.matchedCount === 0) { 190 | return { error: "No user found with that email" } 191 | } 192 | return updateResponse 193 | } catch (e) { 194 | console.error( 195 | `An error occurred while updating this user's preferences, ${e}`, 196 | ) 197 | return { error: e } 198 | } 199 | } 200 | 201 | static async checkAdmin(email) { 202 | try { 203 | const { isAdmin } = await this.getUser(email) 204 | return isAdmin || false 205 | } catch (e) { 206 | return { error: e } 207 | } 208 | } 209 | 210 | static async makeAdmin(email) { 211 | try { 212 | const updateResponse = users.updateOne( 213 | { email }, 214 | { $set: { isAdmin: true } }, 215 | ) 216 | return updateResponse 217 | } catch (e) { 218 | return { error: e } 219 | } 220 | } 221 | } 222 | 223 | /** 224 | * Parameter passed to addUser method 225 | * @typedef UserInfo 226 | * @property {string} name 227 | * @property {string} email 228 | * @property {string} password 229 | */ 230 | 231 | /** 232 | * Success/Error return object 233 | * @typedef DAOResponse 234 | * @property {boolean} [success] - Success 235 | * @property {string} [error] - Error 236 | */ 237 | -------------------------------------------------------------------------------- /mflix-js/src/api/users.controller.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs" 2 | import jwt from "jsonwebtoken" 3 | import UsersDAO from "../dao/usersDAO" 4 | 5 | const hashPassword = async password => await bcrypt.hash(password, 10) 6 | 7 | export class User { 8 | constructor({ name, email, password, preferences = {} } = {}) { 9 | this.name = name 10 | this.email = email 11 | this.password = password 12 | this.preferences = preferences 13 | } 14 | toJson() { 15 | return { name: this.name, email: this.email, preferences: this.preferences } 16 | } 17 | async comparePassword(plainText) { 18 | return await bcrypt.compare(plainText, this.password) 19 | } 20 | encoded() { 21 | return jwt.sign( 22 | { 23 | exp: Math.floor(Date.now() / 1000) + 60 * 60 * 4, 24 | ...this.toJson(), 25 | }, 26 | process.env.SECRET_KEY, 27 | ) 28 | } 29 | static async decoded(userJwt) { 30 | return jwt.verify(userJwt, process.env.SECRET_KEY, (error, res) => { 31 | if (error) { 32 | return { error } 33 | } 34 | return new User(res) 35 | }) 36 | } 37 | } 38 | 39 | export default class UserController { 40 | static async register(req, res) { 41 | try { 42 | const userFromBody = req.body 43 | let errors = {} 44 | if (userFromBody && userFromBody.password.length < 8) { 45 | errors.password = "Your password must be at least 8 characters." 46 | } 47 | if (userFromBody && userFromBody.name.length < 3) { 48 | errors.name = "You must specify a name of at least 3 characters." 49 | } 50 | 51 | if (Object.keys(errors).length > 0) { 52 | res.status(400).json(errors) 53 | return 54 | } 55 | 56 | const userInfo = { 57 | ...userFromBody, 58 | password: await hashPassword(userFromBody.password), 59 | } 60 | 61 | const insertResult = await UsersDAO.addUser(userInfo) 62 | if (!insertResult.success) { 63 | errors.email = insertResult.error 64 | } 65 | const userFromDB = await UsersDAO.getUser(userFromBody.email) 66 | if (!userFromDB) { 67 | errors.general = "Internal error, please try again later" 68 | } 69 | 70 | if (Object.keys(errors).length > 0) { 71 | res.status(400).json(errors) 72 | return 73 | } 74 | 75 | const user = new User(userFromDB) 76 | 77 | res.json({ 78 | auth_token: user.encoded(), 79 | info: user.toJson(), 80 | }) 81 | } catch (e) { 82 | res.status(500).json({ error: e }) 83 | } 84 | } 85 | 86 | static async login(req, res, next) { 87 | try { 88 | const { email, password } = req.body 89 | if (!email || typeof email !== "string") { 90 | res.status(400).json({ error: "Bad email format, expected string." }) 91 | return 92 | } 93 | if (!password || typeof password !== "string") { 94 | res.status(400).json({ error: "Bad password format, expected string." }) 95 | return 96 | } 97 | let userData = await UsersDAO.getUser(email) 98 | if (!userData) { 99 | res.status(401).json({ error: "Make sure your email is correct." }) 100 | return 101 | } 102 | const user = new User(userData) 103 | 104 | if (!(await user.comparePassword(password))) { 105 | res.status(401).json({ error: "Make sure your password is correct." }) 106 | return 107 | } 108 | 109 | const loginResponse = await UsersDAO.loginUser(user.email, user.encoded()) 110 | if (!loginResponse.success) { 111 | res.status(500).json({ error: loginResponse.error }) 112 | return 113 | } 114 | res.json({ auth_token: user.encoded(), info: user.toJson() }) 115 | } catch (e) { 116 | res.status(400).json({ error: e }) 117 | return 118 | } 119 | } 120 | 121 | static async logout(req, res) { 122 | try { 123 | const userJwt = req.get("Authorization").slice("Bearer ".length) 124 | const userObj = await User.decoded(userJwt) 125 | var { error } = userObj 126 | if (error) { 127 | res.status(401).json({ error }) 128 | return 129 | } 130 | const logoutResult = await UsersDAO.logoutUser(userObj.email) 131 | var { error } = logoutResult 132 | if (error) { 133 | res.status(500).json({ error }) 134 | return 135 | } 136 | res.json(logoutResult) 137 | } catch (e) { 138 | res.status(500).json(e) 139 | } 140 | } 141 | 142 | static async delete(req, res) { 143 | try { 144 | let { password } = req.body 145 | if (!password || typeof password !== "string") { 146 | res.status(400).json({ error: "Bad password format, expected string." }) 147 | return 148 | } 149 | const userJwt = req.get("Authorization").slice("Bearer ".length) 150 | const userClaim = await User.decoded(userJwt) 151 | var { error } = userClaim 152 | if (error) { 153 | res.status(401).json({ error }) 154 | return 155 | } 156 | const user = new User(await UsersDAO.getUser(userClaim.email)) 157 | if (!(await user.comparePassword(password))) { 158 | res.status(401).json({ error: "Make sure your password is correct." }) 159 | return 160 | } 161 | const deleteResult = await UsersDAO.deleteUser(userClaim.email) 162 | var { error } = deleteResult 163 | if (error) { 164 | res.status(500).json({ error }) 165 | return 166 | } 167 | res.json(deleteResult) 168 | } catch (e) { 169 | res.status(500).json(e) 170 | } 171 | } 172 | 173 | static async save(req, res) { 174 | try { 175 | const userJwt = req.get("Authorization").slice("Bearer ".length) 176 | const userFromHeader = await User.decoded(userJwt) 177 | var { error } = userFromHeader 178 | if (error) { 179 | res.status(401).json({ error }) 180 | return 181 | } 182 | 183 | await UsersDAO.updatePreferences( 184 | userFromHeader.email, 185 | req.body.preferences, 186 | ) 187 | const userFromDB = await UsersDAO.getUser(userFromHeader.email) 188 | const updatedUser = new User(userFromDB) 189 | 190 | res.json({ 191 | auth_token: updatedUser.encoded(), 192 | info: updatedUser.toJson(), 193 | }) 194 | } catch (e) { 195 | res.status(500).json(e) 196 | } 197 | } 198 | 199 | // for internal use only 200 | static async createAdminUser(req, res) { 201 | try { 202 | const userFromBody = req.body 203 | let errors = {} 204 | if (userFromBody && userFromBody.password.length < 8) { 205 | errors.password = "Your password must be at least 8 characters." 206 | } 207 | if (userFromBody && userFromBody.name.length < 3) { 208 | errors.name = "You must specify a name of at least 3 characters." 209 | } 210 | 211 | if (Object.keys(errors).length > 0) { 212 | res.status(400).json(errors) 213 | return 214 | } 215 | 216 | const userInfo = { 217 | ...userFromBody, 218 | password: await hashPassword(userFromBody.password), 219 | } 220 | 221 | const insertResult = await UsersDAO.addUser(userInfo) 222 | if (!insertResult.success) { 223 | errors.email = insertResult.error 224 | } 225 | 226 | if (Object.keys(errors).length > 0) { 227 | res.status(400).json(errors) 228 | return 229 | } 230 | 231 | const makeAdminResponse = await UsersDAO.makeAdmin(userFromBody.email) 232 | 233 | const userFromDB = await UsersDAO.getUser(userFromBody.email) 234 | if (!userFromDB) { 235 | errors.general = "Internal error, please try again later" 236 | } 237 | 238 | if (Object.keys(errors).length > 0) { 239 | res.status(400).json(errors) 240 | return 241 | } 242 | 243 | const user = new User(userFromDB) 244 | const jwt = user.encoded() 245 | const loginResponse = await UsersDAO.loginUser(user.email, jwt) 246 | 247 | res.json({ 248 | auth_token: jwt, 249 | info: user.toJson(), 250 | }) 251 | } catch (e) { 252 | res.status(500).json(e) 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /mflix-js/test/lessons/basic-updates.spec.js: -------------------------------------------------------------------------------- 1 | import { MongoClient } from "mongodb" 2 | 3 | let theaters 4 | describe("Basic Updates", () => { 5 | /** 6 | * In this lesson, we'll discuss updating documents with the Node.js driver. 7 | * There are two different update operations we can perform - updateOne(), and 8 | * updateMany(). 9 | * 10 | * These operations both return an UpdateResult, which we'll discuss as well. 11 | */ 12 | 13 | beforeAll(async () => { 14 | try { 15 | theaters = await global.mflixClient 16 | .db(process.env.MFLIX_NS) 17 | .collection("theaters") 18 | } catch (e) { 19 | console.error( 20 | `Unable to establish a collection handle for "mflix.theaters": ${e}`, 21 | ) 22 | } 23 | }) 24 | 25 | it("Can update one document in a collection", async () => { 26 | /** 27 | * We can update a single document in a collection with updateOne(). 28 | * updateOne() takes a query predicate, to match a document, and a JSON 29 | * object of one or more update operators, describing how to update that 30 | * document. 31 | * 32 | * If the predicate matches more than one document, updateOne() will update 33 | * the first document it finds in the collection. This may be unpredictable, 34 | * so the query predicate we use should only match one document. 35 | * 36 | * In the following example, one of the theaters in our database moved 37 | * location, down the street from its original address. 38 | * 39 | * This operation should only update one of the theaters in this collection, 40 | * to change the address of that theater. It performs the following updates: 41 | * 42 | * Use $set to update the new address of this theater 43 | * Use $inc to update the new geo coordinates of this theater 44 | */ 45 | 46 | // when accessing a subdocument, we must use quotes 47 | // for example, "location.address.street1" would fail without quotes 48 | const oldTheaterAddress = await theaters.findOne({ theaterId: 8 }) 49 | 50 | // expect the address of this theater to be "14141 Aldrich Ave S" 51 | expect(oldTheaterAddress.location.address.street1).toEqual( 52 | "14141 Aldrich Ave S", 53 | ) 54 | expect(oldTheaterAddress.location.geo.coordinates).toEqual([ 55 | -93.288039, 56 | 44.747404, 57 | ]) 58 | 59 | // update a single theater document in this collection 60 | const updateOneResult = await theaters.updateOne( 61 | { theaterId: 8 }, 62 | { 63 | $set: { "location.address.street1": "14161 Aldrich Ave S" }, 64 | $inc: { 65 | "location.geo.coordinates.0": -10, 66 | "location.geo.coordinates.1": -25, 67 | }, 68 | }, 69 | ) 70 | 71 | // expect this operation to match exactly 1 theater document 72 | expect(updateOneResult.matchedCount).toEqual(1) 73 | 74 | // expect this operation to update exactly 1 theater document 75 | expect(updateOneResult.modifiedCount).toEqual(1) 76 | 77 | const newTheaterAddress = await theaters.findOne( 78 | { theaterId: 8 }, 79 | { "location.address.street1": 1 }, 80 | ) 81 | 82 | // expect the updated address of this theater to be "14161 Aldrich Ave S" 83 | expect(newTheaterAddress.location.address.street1).toEqual( 84 | "14161 Aldrich Ave S", 85 | ) 86 | expect(newTheaterAddress.location.geo.coordinates).toEqual([ 87 | -103.288039, 88 | 19.747404000000003, 89 | ]) 90 | 91 | // do some cleanup 92 | const cleanUp = await theaters.updateOne( 93 | { theaterId: 8 }, 94 | { 95 | $set: { "location.address.street1": "14141 Aldrich Ave S" }, 96 | $inc: { 97 | "location.geo.coordinates.0": 10, 98 | "location.geo.coordinates.1": 25, 99 | }, 100 | }, 101 | ) 102 | 103 | expect(cleanUp.modifiedCount).toEqual(1) 104 | }) 105 | 106 | it("Can update many documents in a collection", async () => { 107 | /** 108 | * We can update multiple documents in a collection with updateMany(). 109 | * 110 | * Like updateOne(), updateMany() takes a query predicate and a JSON object 111 | * containing one or more update operators. 112 | * 113 | * But unlike updateOne(), updateMany() will update any documents matching 114 | * the query predicate. 115 | * 116 | * In the following example, the state of Minnesota has relocated one of its 117 | * zip codes, 55111, from Minneapolis to the neighboring city of 118 | * Bloomington. 119 | * 120 | * This operation should find all the movie theaters in the zip code 55111, 121 | * and update the value of the "city" field to Bloomington. 122 | */ 123 | 124 | const oldTheaterDocuments = await (await theaters.find({ 125 | "location.address.zipcode": "55111", 126 | })).toArray() 127 | 128 | // expect all the theaters in 55111 to reside in Minneapolis 129 | oldTheaterDocuments.map(theater => { 130 | expect(theater.location.address.city).toEqual("Minneapolis") 131 | }) 132 | 133 | // same query predicate as the find() statement above 134 | const updateManyResult = await theaters.updateMany( 135 | { "location.address.zipcode": "55111" }, 136 | { $set: { "location.address.city": "Bloomington" } }, 137 | ) 138 | 139 | // expect this operation to match exactly 6 theater document 140 | expect(updateManyResult.matchedCount).toEqual(6) 141 | 142 | // expect this operation to update exactly 6 theater document 143 | expect(updateManyResult.modifiedCount).toEqual(6) 144 | 145 | const newTheaterDocuments = await (await theaters.find({ 146 | "location.address.zipcode": "55111", 147 | })).toArray() 148 | 149 | // expect all the updated theater documents to reside in Bloomington 150 | newTheaterDocuments.map(theater => { 151 | expect(theater.location.address.city).toEqual("Bloomington") 152 | }) 153 | 154 | // clean up 155 | const cleanUp = await theaters.updateMany( 156 | { "location.address.zipcode": "55111" }, 157 | { $set: { "location.address.city": "Minneapolis" } }, 158 | ) 159 | 160 | expect(cleanUp.modifiedCount).toEqual(6) 161 | }) 162 | 163 | it("Can update a document if it exists, and insert if it does not", async () => { 164 | /** 165 | * Sometimes, we want to update a document, but we're not sure if it exists 166 | * in the collection. 167 | * 168 | * We can use an "upsert" to update a document if it exists, and insert it 169 | * if it does not exist. 170 | * 171 | * In the following example, we're not sure if this theater exists in our 172 | * collection, but we want to make sure there is a document in the 173 | * collection that contains the correct data. 174 | * 175 | * This operation may do one of two things: 176 | * 177 | * If the predicate matches a document, update the theater document to 178 | * contain the correct data 179 | * If the document doesn't exist, create the desired theater document 180 | */ 181 | 182 | const newTheaterDoc = { 183 | theaterId: 954, 184 | location: { 185 | address: { 186 | street1: "570 2nd Ave", 187 | street2: null, 188 | city: "New York", 189 | state: "NY", 190 | zipcode: "10016", 191 | }, 192 | geo: { 193 | type: "Point", 194 | coordinates: [-75, 42], 195 | }, 196 | }, 197 | } 198 | 199 | const upsertResult = await theaters.updateOne( 200 | { 201 | "location.address": newTheaterDoc.location.address, 202 | }, 203 | { 204 | $set: newTheaterDoc, 205 | }, 206 | { upsert: true }, 207 | ) 208 | 209 | // expect this operation not to match any theater documents 210 | expect(upsertResult.matchedCount).toEqual(0) 211 | 212 | // expect this operation not to update any theater documents 213 | expect(upsertResult.modifiedCount).toEqual(0) 214 | 215 | // this upsertedId contains the _id of the document that we just upserted 216 | expect(upsertResult.upsertedId).not.toBeNull() 217 | console.log(upsertResult.upsertedId) 218 | 219 | // remove the document so it can be upserted again 220 | const cleanUp = await theaters.deleteOne({ 221 | "location.address": newTheaterDoc.location.address, 222 | }) 223 | 224 | expect(cleanUp.deletedCount).toEqual(1) 225 | }) 226 | }) 227 | -------------------------------------------------------------------------------- /mflix-js/src/dao/moviesDAO.js: -------------------------------------------------------------------------------- 1 | import { ObjectId } from "bson" 2 | 3 | let movies 4 | let mflix 5 | const DEFAULT_SORT = [["tomatoes.viewer.numReviews", -1]] 6 | 7 | export default class MoviesDAO { 8 | static async injectDB(conn) { 9 | if (movies) { 10 | return 11 | } 12 | try { 13 | mflix = await conn.db(process.env.MFLIX_NS) 14 | movies = await conn.db(process.env.MFLIX_NS).collection("movies") 15 | this.movies = movies // this is only for testing 16 | } catch (e) { 17 | console.error( 18 | `Unable to establish a collection handle in moviesDAO: ${e}`, 19 | ) 20 | } 21 | } 22 | 23 | /** 24 | * Retrieves the connection pool size, write concern and user roles on the 25 | * current client. 26 | * @returns {Promise} An object with configuration details. 27 | */ 28 | static async getConfiguration() { 29 | const roleInfo = await mflix.command({ connectionStatus: 1 }) 30 | const authInfo = roleInfo.authInfo.authenticatedUserRoles[0] 31 | const { poolSize, wtimeout } = movies.s.db.serverConfig.s.options 32 | let response = { 33 | poolSize, 34 | wtimeout, 35 | authInfo, 36 | } 37 | return response 38 | } 39 | 40 | /** 41 | * Finds and returns movies originating from one or more countries. 42 | * Returns a list of objects, each object contains a title and an _id. 43 | * @param {string[]} countries - The list of countries. 44 | * @returns {Promise} A promise that will resolve to a list of CountryResults. 45 | */ 46 | static async getMoviesByCountry(countries) { 47 | /** 48 | Ticket: Projection 49 | 50 | Write a query that matches movies with the countries in the "countries" 51 | list, but only returns the title and _id of each movie. 52 | 53 | Remember that in MongoDB, the $in operator can be used with a list to 54 | match one or more values of a specific field. 55 | */ 56 | 57 | let cursor 58 | try { 59 | // TODO Ticket: Projection 60 | // Find movies matching the "countries" list, but only return the title 61 | // and _id. Do not put a limit in your own implementation, the limit 62 | // here is only included to avoid sending 46000 documents down the 63 | // wire. 64 | cursor = await movies 65 | .find({ 66 | countries: { $in: countries }, 67 | }) 68 | .project({ title: 1 }) 69 | } catch (e) { 70 | console.error(`Unable to issue find command, ${e}`) 71 | return [] 72 | } 73 | 74 | return cursor.toArray() 75 | } 76 | 77 | /** 78 | * Finds and returns movies matching a given text in their title or description. 79 | * @param {string} text - The text to match with. 80 | * @returns {QueryParams} The QueryParams for text search 81 | */ 82 | static textSearchQuery(text) { 83 | const query = { $text: { $search: text } } 84 | const meta_score = { $meta: "textScore" } 85 | const sort = [["score", meta_score]] 86 | const project = { score: meta_score } 87 | 88 | return { query, project, sort } 89 | } 90 | 91 | /** 92 | * Finds and returns movies including one or more cast members. 93 | * @param {string[]} cast - The cast members to match with. 94 | * @returns {QueryParams} The QueryParams for cast search 95 | */ 96 | static castSearchQuery(cast) { 97 | const searchCast = Array.isArray(cast) ? cast : cast.split(", ") 98 | 99 | const query = { cast: { $in: searchCast } } 100 | const project = {} 101 | const sort = DEFAULT_SORT 102 | 103 | return { query, project, sort } 104 | } 105 | 106 | /** 107 | * Finds and returns movies matching a one or more genres. 108 | * @param {string[]} genre - The genres to match with. 109 | * @returns {QueryParams} The QueryParams for genre search 110 | */ 111 | static genreSearchQuery(genre) { 112 | /** 113 | Ticket: Text and Subfield Search 114 | 115 | Given an array of one or more genres, construct a query that searches 116 | MongoDB for movies with that genre. 117 | */ 118 | 119 | const searchGenre = Array.isArray(genre) ? genre : genre.split(", ") 120 | 121 | // TODO Ticket: Text and Subfield Search 122 | // Construct a query that will search for the chosen genre. 123 | const query = { 124 | genres: { $in: searchGenre }, 125 | } 126 | const project = {} 127 | const sort = DEFAULT_SORT 128 | 129 | return { query, project, sort } 130 | } 131 | 132 | /** 133 | * 134 | * @param {Object} filters - The search parameter to use in the query. Comes 135 | * in the form of `{cast: { $in: [...castMembers]}}` 136 | * @param {number} page - The page of movies to retrieve. 137 | * @param {number} moviesPerPage - The number of movies to display per page. 138 | * @returns {FacetedSearchReturn} FacetedSearchReturn 139 | */ 140 | static async facetedSearch({ 141 | filters = null, 142 | page = 0, 143 | moviesPerPage = 20, 144 | } = {}) { 145 | if (!filters || !filters.cast) { 146 | throw new Error("Must specify cast members to filter by.") 147 | } 148 | const matchStage = { $match: filters } 149 | const sortStage = { $sort: { "tomatoes.viewer.numReviews": -1 } } 150 | const countingPipeline = [matchStage, sortStage, { $count: "count" }] 151 | const skipStage = { $skip: moviesPerPage * page } 152 | const limitStage = { $limit: moviesPerPage } 153 | const facetStage = { 154 | $facet: { 155 | runtime: [ 156 | { 157 | $bucket: { 158 | groupBy: "$runtime", 159 | boundaries: [0, 60, 90, 120, 180], 160 | default: "other", 161 | output: { 162 | count: { $sum: 1 }, 163 | }, 164 | }, 165 | }, 166 | ], 167 | rating: [ 168 | { 169 | $bucket: { 170 | groupBy: "$metacritic", 171 | boundaries: [0, 50, 70, 90, 100], 172 | default: "other", 173 | output: { 174 | count: { $sum: 1 }, 175 | }, 176 | }, 177 | }, 178 | ], 179 | movies: [ 180 | { 181 | $addFields: { 182 | title: "$title", 183 | }, 184 | }, 185 | ], 186 | }, 187 | } 188 | 189 | /** 190 | Ticket: Faceted Search 191 | 192 | Please append the skipStage, limitStage, and facetStage to the queryPipeline 193 | (in that order). You can accomplish this by adding the stages directly to 194 | the queryPipeline. 195 | 196 | The queryPipeline is a Javascript array, so you can use push() or concat() 197 | to complete this task, but you might have to do something about `const`. 198 | */ 199 | 200 | const queryPipeline = [ 201 | matchStage, 202 | sortStage, 203 | skipStage, 204 | limitStage, 205 | facetStage 206 | // TODO Ticket: Faceted Search 207 | // Add the stages to queryPipeline in the correct order. 208 | ] 209 | 210 | try { 211 | const results = await (await movies.aggregate(queryPipeline)).next() 212 | const count = await (await movies.aggregate(countingPipeline)).next() 213 | return { 214 | ...results, 215 | ...count, 216 | } 217 | } catch (e) { 218 | return { error: "Results too large, be more restrictive in filter" } 219 | } 220 | } 221 | 222 | /** 223 | * Finds and returns movies by country. 224 | * Returns a list of objects, each object contains a title and an _id. 225 | * @param {Object} filters - The search parameters to use in the query. 226 | * @param {number} page - The page of movies to retrieve. 227 | * @param {number} moviesPerPage - The number of movies to display per page. 228 | * @returns {GetMoviesResult} An object with movie results and total results 229 | * that would match this query 230 | */ 231 | static async getMovies({ 232 | // here's where the default parameters are set for the getMovies method 233 | filters = null, 234 | page = 0, 235 | moviesPerPage = 20, 236 | } = {}) { 237 | let queryParams = {} 238 | if (filters) { 239 | if ("text" in filters) { 240 | queryParams = this.textSearchQuery(filters["text"]) 241 | } else if ("cast" in filters) { 242 | queryParams = this.castSearchQuery(filters["cast"]) 243 | } else if ("genre" in filters) { 244 | queryParams = this.genreSearchQuery(filters["genre"]) 245 | } 246 | } 247 | 248 | let { query = {}, project = {}, sort = DEFAULT_SORT } = queryParams 249 | let cursor 250 | try { 251 | cursor = await movies 252 | .find(query) 253 | .project(project) 254 | .sort(sort) 255 | } catch (e) { 256 | console.error(`Unable to issue find command, ${e}`) 257 | return { moviesList: [], totalNumMovies: 0 } 258 | } 259 | 260 | /** 261 | Ticket: Paging 262 | 263 | Before this method returns back to the API, use the "moviesPerPage" and 264 | "page" arguments to decide the movies to display. 265 | 266 | Paging can be implemented by using the skip() and limit() cursor methods. 267 | */ 268 | 269 | // TODO Ticket: Paging 270 | // Use the cursor to only return the movies that belong on the current page 271 | const displayCursor = cursor.limit(moviesPerPage) 272 | 273 | try { 274 | const moviesList = await displayCursor.toArray() 275 | console.log(query) 276 | const totalNumMovies = page === 0 ? await movies.countDocuments(query) : 0 277 | 278 | return { moviesList, totalNumMovies } 279 | } catch (e) { 280 | console.error( 281 | `Unable to convert cursor to array or problem counting documents, ${e}`, 282 | ) 283 | return { moviesList: [], totalNumMovies: 0 } 284 | } 285 | } 286 | 287 | /** 288 | * Gets a movie by its id 289 | * @param {string} id - The desired movie id, the _id in Mongo 290 | * @returns {MflixMovie | null} Returns either a single movie or nothing 291 | */ 292 | static async getMovieByID(id) { 293 | try { 294 | /** 295 | Ticket: Get Comments 296 | 297 | Given a movie ID, build an Aggregation Pipeline to retrieve the comments 298 | matching that movie's ID. 299 | 300 | The $match stage is already completed. You will need to add a $lookup 301 | stage that searches the `comments` collection for the correct comments. 302 | */ 303 | 304 | // TODO Ticket: Get Comments 305 | // Implement the required pipeline. 306 | const pipeline = [ 307 | { 308 | $match: { 309 | _id: ObjectId(id), 310 | }, 311 | }, 312 | ] 313 | return await movies.aggregate(pipeline).next() 314 | } catch (e) { 315 | /** 316 | Ticket: Error Handling 317 | 318 | Handle the error that occurs when an invalid ID is passed to this method. 319 | When this specific error is thrown, the method should return `null`. 320 | */ 321 | 322 | // TODO Ticket: Error Handling 323 | // Catch the InvalidId error by string matching, and then handle it. 324 | console.error(`Something went wrong in getMovieByID: ${e}`) 325 | throw e 326 | } 327 | } 328 | } 329 | 330 | /** 331 | * This is a parsed query, sort, and project bundle. 332 | * @typedef QueryParams 333 | * @property {Object} query - The specified query, transformed accordingly 334 | * @property {any[]} sort - The specified sort 335 | * @property {Object} project - The specified project, if any 336 | */ 337 | 338 | /** 339 | * Represents a single country result 340 | * @typedef CountryResult 341 | * @property {string} ObjectID - The ObjectID of the movie 342 | * @property {string} title - The title of the movie 343 | */ 344 | 345 | /** 346 | * A Movie from mflix 347 | * @typedef MflixMovie 348 | * @property {string} _id 349 | * @property {string} title 350 | * @property {number} year 351 | * @property {number} runtime 352 | * @property {Date} released 353 | * @property {string[]} cast 354 | * @property {number} metacriticd 355 | * @property {string} poster 356 | * @property {string} plot 357 | * @property {string} fullplot 358 | * @property {string|Date} lastupdated 359 | * @property {string} type 360 | * @property {string[]} languages 361 | * @property {string[]} directors 362 | * @property {string[]} writers 363 | * @property {IMDB} imdb 364 | * @property {string[]} countries 365 | * @property {string[]} rated 366 | * @property {string[]} genres 367 | * @property {string[]} comments 368 | */ 369 | 370 | /** 371 | * IMDB subdocument 372 | * @typedef IMDB 373 | * @property {number} rating 374 | * @property {number} votes 375 | * @property {number} id 376 | */ 377 | 378 | /** 379 | * Result set for getMovies method 380 | * @typedef GetMoviesResult 381 | * @property {MflixMovies[]} moviesList 382 | * @property {number} totalNumResults 383 | */ 384 | 385 | /** 386 | * Faceted Search Return 387 | * 388 | * The type of return from faceted search. It will be a single document with 389 | * 3 fields: rating, runtime, and movies. 390 | * @typedef FacetedSearchReturn 391 | * @property {object} rating 392 | * @property {object} runtime 393 | * @property {MFlixMovie[]}movies 394 | */ 395 | --------------------------------------------------------------------------------