├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── buildall.sh ├── dependencies ├── package.json └── yarn.lock ├── docs ├── exams │ ├── mock_exam.docx │ └── mock_exam.pdf ├── exercises │ └── quiz-game │ │ ├── part-01.md │ │ ├── part-02.md │ │ ├── part-03.md │ │ ├── part-04.md │ │ ├── part-05.md │ │ ├── part-06.md │ │ ├── part-07.md │ │ ├── part-08.md │ │ ├── part-09.md │ │ ├── part-10.md │ │ ├── part-11.md │ │ └── part-12.md ├── img │ └── stephen-leonardi-369733-unsplash-compressed.jpg └── slides │ ├── lesson_01.pdf │ ├── lesson_01.pptx │ ├── lesson_02.pdf │ ├── lesson_02.pptx │ ├── lesson_03.pdf │ ├── lesson_03.pptx │ ├── lesson_04.pdf │ ├── lesson_04.pptx │ ├── lesson_05.pdf │ ├── lesson_05.pptx │ ├── lesson_06.pdf │ ├── lesson_06.pptx │ ├── lesson_07.pdf │ ├── lesson_07.pptx │ ├── lesson_08.pdf │ ├── lesson_08.pptx │ ├── lesson_09.pdf │ ├── lesson_09.pptx │ ├── lesson_10.pdf │ ├── lesson_10.pptx │ ├── lesson_11.pdf │ ├── lesson_11.pptx │ ├── lesson_12.pdf │ ├── lesson_12.pptx │ ├── lesson_graphql.pdf │ └── lesson_graphql.pptx ├── exercise-solutions └── quiz-game │ ├── part-01 │ ├── code.js │ ├── index.html │ └── style.css │ ├── part-02 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── index.js │ │ └── quizzes.js │ ├── tests │ │ └── quizzes-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-03 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── index.jsx │ │ └── quizzes.js │ ├── tests │ │ └── quizzes-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-04 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── index.jsx │ │ ├── match.jsx │ │ └── quizzes.js │ ├── tests │ │ ├── jest-setup.js │ │ ├── match-test.jsx │ │ └── quizzes-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-05 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── match.jsx │ │ │ └── quizzes.js │ │ └── server │ │ │ └── server.js │ ├── tests │ │ ├── jest-setup.js │ │ ├── match-test.jsx │ │ └── quizzes-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-06 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── match.jsx │ │ │ └── quizzes.js │ │ └── server │ │ │ └── server.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-08 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ └── match.jsx │ │ └── server │ │ │ ├── app.js │ │ │ ├── db │ │ │ └── quizzes.js │ │ │ ├── routes │ │ │ └── match-api.js │ │ │ └── server.js │ ├── tests │ │ ├── client │ │ │ └── match-test.jsx │ │ ├── jest-setup.js │ │ ├── mytest-utils.js │ │ └── server │ │ │ ├── db │ │ │ └── quizzes-test.js │ │ │ └── routes │ │ │ └── match-api-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-09 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── headerbar.jsx │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── login.jsx │ │ │ ├── match.jsx │ │ │ └── signup.jsx │ │ └── server │ │ │ ├── app.js │ │ │ ├── db │ │ │ ├── quizzes.js │ │ │ └── users.js │ │ │ ├── routes │ │ │ ├── auth-api.js │ │ │ └── match-api.js │ │ │ └── server.js │ ├── tests │ │ ├── client │ │ │ └── match-test.jsx │ │ ├── jest-setup.js │ │ ├── mytest-utils.js │ │ └── server │ │ │ ├── db │ │ │ └── quizzes-test.js │ │ │ └── routes │ │ │ ├── auth-api-test.js │ │ │ └── match-api-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-10 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── headerbar.jsx │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── login.jsx │ │ │ ├── match.jsx │ │ │ └── signup.jsx │ │ └── server │ │ │ ├── app.js │ │ │ ├── db │ │ │ ├── matches.js │ │ │ ├── quizzes.js │ │ │ └── users.js │ │ │ ├── routes │ │ │ ├── auth-api.js │ │ │ └── match-api.js │ │ │ └── server.js │ ├── tests │ │ ├── client │ │ │ └── match-test.jsx │ │ ├── jest-setup.js │ │ ├── mytest-utils.js │ │ └── server │ │ │ ├── db │ │ │ └── quizzes-test.js │ │ │ └── routes │ │ │ ├── auth-api-test.js │ │ │ └── match-api-test.js │ ├── webpack.config.js │ └── yarn.lock │ ├── part-11 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── headerbar.jsx │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── login.jsx │ │ │ ├── match.jsx │ │ │ └── signup.jsx │ │ └── server │ │ │ ├── app.js │ │ │ ├── db │ │ │ ├── matches.js │ │ │ ├── quizzes.js │ │ │ └── users.js │ │ │ ├── routes │ │ │ ├── auth-api.js │ │ │ └── match-api.js │ │ │ ├── server.js │ │ │ └── ws-handler.js │ ├── tests │ │ ├── client │ │ │ └── match-test.jsx │ │ ├── jest-setup.js │ │ ├── mytest-utils-ws.js │ │ ├── mytest-utils.js │ │ └── server │ │ │ ├── db │ │ │ └── quizzes-test.js │ │ │ └── routes │ │ │ ├── auth-api-test.js │ │ │ └── match-api-test.js │ ├── webpack.config.js │ └── yarn.lock │ └── part-12 │ ├── .gitignore │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── headerbar.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── login.jsx │ │ ├── match.jsx │ │ └── signup.jsx │ └── server │ │ ├── app.js │ │ ├── db │ │ ├── matches.js │ │ ├── quizzes.js │ │ └── users.js │ │ ├── routes │ │ ├── auth-api.js │ │ └── match-api.js │ │ ├── server.js │ │ └── ws-handler.js │ ├── tests │ ├── client │ │ ├── headerbar-test.jsx │ │ ├── home-test.jsx │ │ ├── login-test.jsx │ │ ├── match-test.jsx │ │ └── signup-test.jsx │ ├── jest-setup.js │ ├── mytest-utils-ws.js │ ├── mytest-utils.js │ └── server │ │ ├── db │ │ └── quizzes-test.js │ │ ├── routes │ │ ├── auth-api-test.js │ │ └── match-api-test.js │ │ └── ws-handler-test.js │ ├── webpack.config.js │ └── yarn.lock ├── extra-graphql └── forum │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── news.jsx │ │ └── user.jsx │ └── server │ │ ├── app.js │ │ ├── db-initializer.js │ │ ├── db.js │ │ ├── resolvers.js │ │ ├── schema.js │ │ └── server.js │ ├── tests │ └── server │ │ └── app-test.js │ ├── webpack.config.js │ └── yarn.lock ├── les01 ├── base-html │ ├── index.html │ └── other.html ├── cards │ ├── code.js │ ├── img │ │ ├── 2_of_spades.png │ │ ├── black_joker.png │ │ └── cover_card.jpg │ ├── index.html │ └── style.css └── silly │ └── index.html ├── les02 ├── libraries │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── index.js │ │ └── my-math.js │ ├── webpack.config.js │ └── yarn.lock ├── tic-tac-toe │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── board.js │ │ └── index.js │ ├── tests │ │ └── board-test.js │ ├── webpack.config.js │ └── yarn.lock └── unit-tests │ ├── package.json │ ├── public │ └── index.html │ ├── src │ ├── index.js │ └── my-math.js │ ├── tests │ ├── index-test.js │ └── my-math-test.js │ ├── webpack.config.js │ └── yarn.lock ├── les03 ├── puzzle15 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── index.jsx │ │ └── utils.js │ ├── tests │ │ └── utils-test.js │ ├── webpack.config.js │ └── yarn.lock ├── spa-components-js │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── container.js │ │ ├── counter.js │ │ ├── index.js │ │ └── mylib │ │ │ ├── MyComponent.js │ │ │ └── MyDOM.js │ ├── webpack.config.js │ └── yarn.lock ├── spa-components-react-hooks │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── container.jsx │ │ ├── counter.jsx │ │ └── index.jsx │ ├── webpack.config.js │ └── yarn.lock └── spa-components-react │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── container.jsx │ ├── counter.jsx │ └── index.jsx │ ├── webpack.config.js │ └── yarn.lock ├── les04 ├── connect4 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── board.jsx │ │ ├── connect4.jsx │ │ ├── index.jsx │ │ ├── info.jsx │ │ └── utils.jsx │ ├── tests │ │ ├── board-test.jsx │ │ ├── jest-setup.js │ │ └── utils-test.js │ ├── webpack.config.js │ └── yarn.lock └── state-lift-up │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── container.jsx │ ├── counter.jsx │ └── index.jsx │ ├── webpack.config.js │ └── yarn.lock ├── les05 ├── games │ ├── package.json │ ├── public │ │ ├── games │ │ │ ├── cards │ │ │ │ ├── cards.css │ │ │ │ └── img │ │ │ │ │ ├── 2_of_spades.png │ │ │ │ │ ├── black_joker.png │ │ │ │ │ └── cover_card.jpg │ │ │ └── silly │ │ │ │ └── silly.css │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── games │ │ │ ├── cards │ │ │ │ └── cards.jsx │ │ │ └── silly │ │ │ │ └── silly.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── my_home_link.jsx │ │ └── not_found.jsx │ ├── webpack.config.js │ └── yarn.lock ├── spa-routing-index-404 │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ ├── first.jsx │ │ │ ├── home.jsx │ │ │ ├── index.jsx │ │ │ ├── my_home_link.jsx │ │ │ ├── not_found.jsx │ │ │ └── second.jsx │ │ └── server │ │ │ └── server.js │ ├── webpack.config.js │ └── yarn.lock ├── spa-routing-js │ ├── package.json │ ├── public │ │ ├── index.html │ │ ├── mylib.css │ │ └── style.css │ ├── src │ │ └── index.js │ ├── webpack.config.js │ └── yarn.lock ├── spa-routing-react │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── first.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── my_home_link.jsx │ │ ├── not_found.jsx │ │ └── second.jsx │ ├── tests │ │ ├── jest-setup.js │ │ └── my_home_link-test.js │ ├── webpack.config.js │ └── yarn.lock └── spa-routing-ssr │ ├── package.json │ ├── public │ └── style.css │ ├── src │ ├── client │ │ ├── app.jsx │ │ ├── first.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── my_home_link.jsx │ │ ├── not_found.jsx │ │ └── second.jsx │ └── server │ │ ├── renderer.jsx │ │ └── server.js │ ├── webpack.config.js │ └── yarn.lock ├── les06 ├── async │ └── src │ │ ├── async.js │ │ └── callback.js └── weather │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── current.jsx │ ├── forecast.jsx │ └── index.jsx │ ├── tests │ ├── forecast-test.jsx │ └── jest-setup.js │ ├── webpack.config.js │ └── yarn.lock ├── les08 ├── server_client_separated │ ├── frontend │ │ ├── package.json │ │ ├── public │ │ │ ├── index.html │ │ │ └── style.css │ │ ├── src │ │ │ ├── client │ │ │ │ ├── book.jsx │ │ │ │ ├── create.jsx │ │ │ │ ├── edit.jsx │ │ │ │ ├── home.jsx │ │ │ │ ├── index.jsx │ │ │ │ └── not_found.jsx │ │ │ └── server │ │ │ │ └── server.js │ │ ├── webpack.config.js │ │ └── yarn.lock │ └── rest │ │ ├── package.json │ │ ├── src │ │ └── server │ │ │ ├── app.js │ │ │ ├── repository.js │ │ │ └── server.js │ │ ├── tests │ │ └── server │ │ │ └── app-test.js │ │ └── yarn.lock └── server_client_together │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── book.jsx │ │ ├── create.jsx │ │ ├── edit.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ └── not_found.jsx │ └── server │ │ ├── app.js │ │ ├── repository.js │ │ └── server.js │ ├── tests │ ├── client │ │ └── home-test.jsx │ ├── jest-setup.js │ ├── mytest-utils.js │ └── server │ │ └── app-test.js │ ├── webpack.config.js │ └── yarn.lock ├── les09 └── authentication │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── headerbar.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── login.jsx │ │ └── signup.jsx │ └── server │ │ ├── app.js │ │ ├── repository.js │ │ ├── routes.js │ │ └── server.js │ ├── tests │ └── server │ │ └── routes-test.js │ ├── webpack.config.js │ └── yarn.lock ├── les10 ├── csrf │ ├── cat.jpg │ ├── index-ajax-get.html │ ├── index-ajax-post-form.html │ ├── index-ajax-post-json.html │ ├── index-form-get.html │ ├── index-form-post.html │ └── readme.md ├── escape │ └── escaped.html └── xss │ └── react-href │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ └── index.jsx │ ├── webpack.config.js │ └── yarn.lock ├── les11 └── chat │ ├── ajax │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ └── index.jsx │ │ └── server │ │ │ ├── app.js │ │ │ └── server.js │ ├── webpack.config.js │ └── yarn.lock │ ├── server-side │ ├── package.json │ ├── public │ │ └── style.css │ ├── src │ │ └── server │ │ │ ├── app.js │ │ │ └── server.js │ └── yarn.lock │ ├── websocket-full │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── style.css │ ├── src │ │ ├── client │ │ │ └── index.jsx │ │ └── server │ │ │ ├── app.js │ │ │ └── server.js │ ├── tests │ │ ├── jest-setup.js │ │ └── server │ │ │ └── app-test.js │ ├── webpack.config.js │ └── yarn.lock │ └── websocket-rest │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── home.jsx │ │ └── index.jsx │ └── server │ │ ├── app.js │ │ └── server.js │ ├── tests │ ├── client │ │ └── home-test.jsx │ ├── jest-setup.js │ ├── mytest-utils-ws.js │ ├── mytest-utils.js │ └── server │ │ └── app-test.js │ ├── webpack.config.js │ └── yarn.lock ├── les12 └── connect4-v2 │ ├── .gitignore │ ├── package.json │ ├── public │ ├── index.html │ └── style.css │ ├── src │ ├── client │ │ ├── connect4 │ │ │ ├── ai-match.jsx │ │ │ ├── board.jsx │ │ │ ├── online-match.jsx │ │ │ ├── opponent-ai.js │ │ │ └── opponent-online.js │ │ ├── headerbar.jsx │ │ ├── home.jsx │ │ ├── index.jsx │ │ ├── login.jsx │ │ └── signup.jsx │ ├── server │ │ ├── app.js │ │ ├── db │ │ │ └── users.js │ │ ├── game │ │ │ └── match.js │ │ ├── online │ │ │ ├── active-players.js │ │ │ ├── ongoing-matches.js │ │ │ └── player-queue.js │ │ ├── routes │ │ │ ├── auth-api.js │ │ │ └── match-api.js │ │ ├── server.js │ │ └── ws │ │ │ ├── tokens.js │ │ │ └── ws-handler.js │ └── shared │ │ ├── board-state.js │ │ └── utils.js │ ├── tests │ ├── mytest-utils-ws.js │ ├── mytest-utils.js │ └── server │ │ ├── auth-test.js │ │ └── match-test.js │ ├── webpack.config.js │ └── yarn.lock ├── shared ├── jest-setup.js ├── mytest-utils-ws.js └── mytest-utils.js ├── syncdep.py └── template.html /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .idea 3 | 4 | bundle.js 5 | 6 | build 7 | 8 | ########################################################################################################### 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | 68 | # next.js build output 69 | .next 70 | /venv/ 71 | -------------------------------------------------------------------------------- /docs/exams/mock_exam.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/exams/mock_exam.docx -------------------------------------------------------------------------------- /docs/exams/mock_exam.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/exams/mock_exam.pdf -------------------------------------------------------------------------------- /docs/exercises/quiz-game/part-03.md: -------------------------------------------------------------------------------- 1 | # Quiz Game - Part 03 2 | 3 | In this exercise, the goal is to replace the `index.js` file with a `index.jsx` 4 | using *React* and *React-Dom*. 5 | The JS in such file needs to be modified to use *React*. 6 | Such libraries need to be added as well to the `package.json` file. 7 | You will also likely need to update the `index.html` file. 8 | 9 | Because JSX is not JS, you need to use *Babel* (e.g., with `react` preset) 10 | to transform the JSX code when it is bundled with *WebPack*. 11 | This means adding the proper `module:rules` in `webpack-config.js`. 12 | Then, `libraryTarget` and `library` can be removed, as no longer needed. 13 | 14 | `quizzes.js` should be left as it is, with the test cases still working correctly 15 | with *Jest*. 16 | 17 | Solutions to this exercise can be found under the *exercise-solutions* folder. 18 | -------------------------------------------------------------------------------- /docs/exercises/quiz-game/part-04.md: -------------------------------------------------------------------------------- 1 | # Quiz Game - Part 04 2 | 3 | Create a new file called `match.jsx`. 4 | Move most of the code from `index.jsx` into `match.jsx`. 5 | The component `` will deal with rendering a quiz, 6 | whereas the component `` in `index.jsx` simply 7 | uses `` and calls `ReactDOM.render()`. 8 | 9 | Create a new `match-test.jsx` test file for the `` component. 10 | You need to use *Jest* and *Enzyme*. 11 | To setup *Enzyme* in *Jest*, you can just copy&paste the `jest-setup.js` configuration 12 | file from class, and update the `package.json` accordingly. 13 | Pay particular attention on how to handle the `alert()` function in JS, 14 | which you will need to manually define in the `global` scope. 15 | Write enough tests to achieve at least 80% statement coverage for `match.jsx`. 16 | 17 | 18 | Solutions to this exercise can be found under the *exercise-solutions* folder. 19 | -------------------------------------------------------------------------------- /docs/exercises/quiz-game/part-07.md: -------------------------------------------------------------------------------- 1 | # Quiz Game - Part 07 2 | 3 | As today's class was just theoretical, there is no exercise. -------------------------------------------------------------------------------- /docs/exercises/quiz-game/part-11.md: -------------------------------------------------------------------------------- 1 | # Quiz Game - Part 11 2 | 3 | Add a WebSocket to connect to the backend. 4 | You will need to use `express-ws` to add WebSocket support for your Express app. 5 | We will just have a simple functionality of 6 | counting and displaying (e.g., in the homepage) the number of online players. 7 | Every time a player connects (does not need to log in), the counter should go up by 1. 8 | When s/he disconnects, the counter should go down by 1. 9 | For example, during development/testing, if you open the homepage with Chrome, the counter 10 | should show 1. When then you open the same page with Firefox, the counter should be 2 on 11 | both browsers (Chrome would then be automatically updated with WebSocket). 12 | When then you shut down Firefox, the counter in Chrome should go down to 1. 13 | 14 | 15 | Solutions to this exercise can be found under the *exercise-solutions* folder. -------------------------------------------------------------------------------- /docs/exercises/quiz-game/part-12.md: -------------------------------------------------------------------------------- 1 | # Quiz Game - Part 10 2 | 3 | 4 | Add enough test cases to achieve _at least_ 70% code coverage. 5 | This means that, when you run `yarn test` (which should execute `jest --coverage`), 6 | then the `% Stmts` entry for `All files` should be greater or equal than `70.0`. 7 | What kind of tests to add is up to you. 8 | 9 | 10 | Create an account on Heroku (or any other cloud-provider you prefer), and deploy this 11 | application on it. 12 | In case of Heroku, remember to have a `.gitignored` file, and possibly configure 13 | Heroku settings via the `engines` entry in the `package.json` configuration file. 14 | 15 | 16 | Solutions to this exercise can be found under the *exercise-solutions* folder. -------------------------------------------------------------------------------- /docs/img/stephen-leonardi-369733-unsplash-compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/img/stephen-leonardi-369733-unsplash-compressed.jpg -------------------------------------------------------------------------------- /docs/slides/lesson_01.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_01.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_01.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_01.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_02.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_02.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_02.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_02.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_03.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_03.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_03.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_03.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_04.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_04.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_04.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_04.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_05.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_05.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_05.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_05.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_06.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_06.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_06.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_06.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_07.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_07.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_07.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_07.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_08.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_08.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_08.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_08.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_09.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_09.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_09.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_09.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_10.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_10.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_10.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_11.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_11.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_11.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_12.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_12.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_12.pptx -------------------------------------------------------------------------------- /docs/slides/lesson_graphql.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_graphql.pdf -------------------------------------------------------------------------------- /docs/slides/lesson_graphql.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/docs/slides/lesson_graphql.pptx -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 | 12 |
13 |
14 | 15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-01/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-content: center; 5 | align-items: center; 6 | } 7 | 8 | .main-content { 9 | display: flex; 10 | flex-direction: column; 11 | min-width: 50%; 12 | align-items: center; 13 | } 14 | 15 | .heading { 16 | font-size: 4em; 17 | margin: 2rem 0; 18 | } 19 | 20 | .question { 21 | font-size: 1.5em; 22 | font-weight: bold; 23 | } 24 | 25 | .answer { 26 | background-color: #ebe9e4; 27 | margin-bottom: 1rem; 28 | padding: 1rem; 29 | border: 1px solid #787774; 30 | border-radius: 5px; 31 | width: 80%; 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/env" 6 | ] 7 | }, 8 | "devDependencies": { 9 | "@babel/cli": "7.12.10", 10 | "@babel/core": "7.12.10", 11 | "@babel/preset-env": "7.12.11", 12 | "babel-jest": "24.9.0", 13 | "jest": "24.9.0", 14 | "webpack": "5.11.1", 15 | "webpack-cli": "4.3.1", 16 | "webpack-dev-server": "3.11.1" 17 | }, 18 | "engines": { 19 | "node": "^14.0.0" 20 | }, 21 | "jest": { 22 | "collectCoverageFrom": [ 23 | "src/**/*.(js|jsx)" 24 | ], 25 | "testRegex": "tests/.*-test\\.(js|jsx)$" 26 | }, 27 | "license": "LGPL-3.0", 28 | "main": "index.js", 29 | "name": "part-02", 30 | "repository": {}, 31 | "scripts": { 32 | "build": "webpack --mode production", 33 | "dev": "webpack serve --open --mode development", 34 | "test": "jest --coverage" 35 | }, 36 | "version": "1.0.0" 37 | } -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/public/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-content: center; 5 | align-items: center; 6 | } 7 | 8 | .main-content { 9 | display: flex; 10 | flex-direction: column; 11 | min-width: 50%; 12 | align-items: center; 13 | } 14 | 15 | .heading { 16 | font-size: 4em; 17 | margin: 2rem 0; 18 | } 19 | 20 | .question { 21 | font-size: 1.5em; 22 | font-weight: bold; 23 | } 24 | 25 | .answer { 26 | background-color: #ebe9e4; 27 | margin-bottom: 1rem; 28 | padding: 1rem; 29 | border: 1px solid #787774; 30 | border-radius: 5px; 31 | width: 80%; 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/src/index.js: -------------------------------------------------------------------------------- 1 | import {getRandomQuizzes} from './quizzes'; 2 | 3 | 4 | function answerTag(prefix, answer, correct) { 5 | 6 | let onclick; 7 | 8 | if(correct) { 9 | onclick = "alert('Correct!!!'); EntryPoint.displayNewQuiz();"; 10 | } else { 11 | onclick = "alert('Wrong answer');"; 12 | } 13 | 14 | const html = "
" + prefix + answer + "
"; 15 | 16 | return html; 17 | } 18 | 19 | function displayQuiz (quiz) { 20 | 21 | let html = "

Question: \"" + quiz.question + "\"

"; 22 | html += answerTag("A: ", quiz.answers[0], quiz.indexOfRightAnswer === 0); 23 | html += answerTag("B: ", quiz.answers[1], quiz.indexOfRightAnswer === 1); 24 | html += answerTag("C: ", quiz.answers[2], quiz.indexOfRightAnswer === 2); 25 | html += answerTag("D: ", quiz.answers[3], quiz.indexOfRightAnswer === 3); 26 | 27 | const quizDiv = document.getElementById("quizDivId"); 28 | 29 | quizDiv.innerHTML = html; 30 | } 31 | 32 | export function displayNewQuiz(){ 33 | 34 | const quiz = getRandomQuizzes(1)[0]; 35 | 36 | displayQuiz(quiz); 37 | } 38 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/tests/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../src/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-02/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public'), 9 | libraryTarget: 'var', 10 | library: 'EntryPoint' 11 | }, 12 | devServer: { 13 | contentBase: './public' 14 | }, 15 | optimization: { 16 | minimize: true, 17 | minimizer: [new TerserPlugin({ 18 | extractComments: false, 19 | })] 20 | } 21 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-03/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-03/public/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-content: center; 5 | align-items: center; 6 | } 7 | 8 | .main-content { 9 | display: flex; 10 | flex-direction: column; 11 | min-width: 50%; 12 | align-items: center; 13 | } 14 | 15 | .heading { 16 | font-size: 4em; 17 | margin: 2rem 0; 18 | } 19 | 20 | .question { 21 | font-size: 1.5em; 22 | font-weight: bold; 23 | } 24 | 25 | .answer { 26 | background-color: #ebe9e4; 27 | margin-bottom: 1rem; 28 | padding: 1rem; 29 | border: 1px solid #787774; 30 | border-radius: 5px; 31 | width: 80%; 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-03/tests/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../src/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-03/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/public/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-content: center; 5 | align-items: center; 6 | } 7 | 8 | .main-content { 9 | display: flex; 10 | flex-direction: column; 11 | min-width: 50%; 12 | align-items: center; 13 | } 14 | 15 | .heading { 16 | font-size: 4em; 17 | margin: 2rem 0; 18 | } 19 | 20 | .question { 21 | font-size: 1.5em; 22 | font-weight: bold; 23 | } 24 | 25 | .answer { 26 | background-color: #ebe9e4; 27 | margin-bottom: 1rem; 28 | padding: 1rem; 29 | border: 1px solid #787774; 30 | border-radius: 5px; 31 | width: 80%; 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {Match} from "./match"; 4 | 5 | const App = () => { 6 | return ( 7 | 8 | ); 9 | }; 10 | 11 | ReactDOM.render(, document.getElementById("root")); 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/src/match.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import {getRandomQuizzes} from './quizzes'; 4 | 5 | 6 | export class Match extends React.Component{ 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = {quiz: getRandomQuizzes(1)[0]} 11 | } 12 | 13 | 14 | handleClick = (correct) => { 15 | if (correct) { 16 | alert('Correct!!!'); 17 | this.setState({quiz: getRandomQuizzes(1)[0]}) 18 | } else { 19 | alert('Wrong answer'); 20 | } 21 | }; 22 | 23 | 24 | renderAnswerTag(prefix, answer, correct) { 25 | return
this.handleClick(correct)}> {prefix + answer}
; 26 | } 27 | 28 | render() { 29 | 30 | const quiz = this.state.quiz; 31 | 32 | return ( 33 | <> 34 |

Question: {quiz.question}

35 | {this.renderAnswerTag("A: ", quiz.answers[0], quiz.indexOfRightAnswer === 0)} 36 | {this.renderAnswerTag("B: ", quiz.answers[1], quiz.indexOfRightAnswer === 1)} 37 | {this.renderAnswerTag("C: ", quiz.answers[2], quiz.indexOfRightAnswer === 2)} 38 | {this.renderAnswerTag("D: ", quiz.answers[3], quiz.indexOfRightAnswer === 3)} 39 | 40 | ); 41 | } 42 | } -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/tests/match-test.jsx: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { mount } = require('enzyme'); 3 | const {Match} = require("../src/match"); 4 | 5 | 6 | function checkQuizIsDisplayed(driver){ 7 | 8 | const questions = driver.find('.question'); 9 | expect(questions.length).toEqual(1); 10 | 11 | const answers = driver.find('.answer'); 12 | expect(answers.length).toEqual(4); 13 | } 14 | 15 | test("Test rendered quiz", ()=> { 16 | 17 | const driver = mount(); 18 | checkQuizIsDisplayed(driver); 19 | }); 20 | 21 | 22 | test("Test do answer", () => { 23 | 24 | const driver = mount(); 25 | 26 | let msg = undefined; 27 | 28 | global.alert = (s) => {msg = s}; 29 | 30 | const first = driver.find('.answer').at(0); 31 | first.simulate('click'); 32 | 33 | checkQuizIsDisplayed(driver); 34 | expect(msg).toBeDefined(); 35 | }); 36 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/tests/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../src/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-04/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/src/client/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | 5 | export class Home extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |

Quiz Game

14 | 15 |

16 | Welcome to the Quiz Game! 17 | In this game, you will get a series of questions, each one with 4 possible answers. 18 | Only 1 out of the 4 answers is correct. 19 | If you answer wrongly to any of the questions, you lose! 20 | You win if you manage to answer correctly to all questions. 21 |

22 | 23 |
24 | 25 | Play 26 | 27 |
28 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'; 4 | 5 | import {Match} from "./match"; 6 | import {Home} from "./home"; 7 | 8 | 9 | const notFound = () => { 10 | return ( 11 |
12 |

NOT FOUND: 404

13 |

14 | ERROR: the page you requested in not available. 15 |

16 |
17 | ); 18 | }; 19 | 20 | 21 | const App = () => { 22 | return ( 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | }; 34 | 35 | 36 | ReactDOM.render(, document.getElementById("root")); 37 | 38 | 39 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/src/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const path = require('path'); 4 | const app = express(); 5 | 6 | 7 | app.use(express.static('public')); 8 | 9 | 10 | app.use((req, res, next) => { 11 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')); 12 | }); 13 | 14 | const port = process.env.PORT || 8080; 15 | 16 | app.listen(port, () => { 17 | console.log('Started server on port ' + port); 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/tests/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../src/client/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-05/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-06/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-06/src/client/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | 5 | export class Home extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |

Quiz Game

14 | 15 |

16 | Welcome to the Quiz Game! 17 | In this game, you will get a series of questions, each one with 4 possible answers. 18 | Only 1 out of the 4 answers is correct. 19 | If you answer wrongly to any of the questions, you lose! 20 | You win if you manage to answer correctly to all questions. 21 |

22 | 23 |
24 | 25 | Play 26 | 27 |
28 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-06/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'; 4 | 5 | import {Match} from "./match"; 6 | import {Home} from "./home"; 7 | 8 | 9 | const notFound = () => { 10 | return ( 11 |
12 |

NOT FOUND: 404

13 |

14 | ERROR: the page you requested in not available. 15 |

16 |
17 | ); 18 | }; 19 | 20 | 21 | const App = () => { 22 | return ( 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | }; 34 | 35 | 36 | ReactDOM.render(, document.getElementById("root")); 37 | 38 | 39 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-06/src/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const path = require('path'); 4 | const app = express(); 5 | 6 | 7 | app.use(express.static('public')); 8 | 9 | 10 | app.use((req, res, next) => { 11 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')); 12 | }); 13 | 14 | const port = process.env.PORT || 8080; 15 | 16 | app.listen(port, () => { 17 | console.log('Started server on port ' + port); 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-06/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 |

Quiz Game

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/src/client/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | 5 | export class Home extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |

Quiz Game

14 | 15 |

16 | Welcome to the Quiz Game! 17 | In this game, you will get a series of questions, each one with 4 possible answers. 18 | Only 1 out of the 4 answers is correct. 19 | If you answer wrongly to any of the questions, you lose! 20 | You win if you manage to answer correctly to all questions. 21 |

22 | 23 |
24 | 25 | Play 26 | 27 |
28 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'; 4 | 5 | import {Match} from "./match"; 6 | import {Home} from "./home"; 7 | 8 | 9 | const notFound = () => { 10 | return ( 11 |
12 |

NOT FOUND: 404

13 |

14 | ERROR: the page you requested in not available. 15 |

16 |
17 | ); 18 | }; 19 | 20 | 21 | const App = () => { 22 | return ( 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | }; 34 | 35 | 36 | ReactDOM.render(, document.getElementById("root")); 37 | 38 | 39 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/src/server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const path = require('path'); 4 | 5 | const matchApi = require('./routes/match-api'); 6 | 7 | const app = express(); 8 | 9 | //to handle JSON payloads 10 | app.use(bodyParser.json()); 11 | 12 | //--- Routes ----------- 13 | app.use('/api', matchApi); 14 | 15 | app.use(express.static('public')); 16 | 17 | app.use((req, res, next) => { 18 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')); 19 | }); 20 | 21 | 22 | module.exports = app; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/src/server/routes/match-api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const {getRandomQuizzes} = require('../db/quizzes'); 3 | 4 | const router = express.Router(); 5 | 6 | 7 | 8 | router.post('/matches', (req, res) => { 9 | 10 | const payload = getRandomQuizzes(3); 11 | 12 | res.status(201).json(payload); 13 | }); 14 | 15 | 16 | 17 | 18 | module.exports = router; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | const server = require('http').Server(app); 4 | 5 | const port = process.env.PORT || 8080; 6 | 7 | app.listen(port, () => { 8 | console.log('Started server on port ' + port); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/tests/server/db/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/tests/server/routes/match-api-test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('../../../src/server/app'); 3 | 4 | 5 | test("Test create match", async () =>{ 6 | 7 | const response = await request(app).post('/api/matches'); 8 | 9 | expect(response.statusCode).toBe(201); 10 | expect(response.body.length).toBe(3); 11 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-08/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/src/server/db/users.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Here we "simulate" a database with in-memory Map. 4 | Furthermore, we do not deal with the "proper" handling of 5 | passwords. Passwords should NEVER be saved in plain text, 6 | but rather hashed with secure algorithms like BCrypt. 7 | */ 8 | 9 | const users = new Map(); 10 | 11 | 12 | function getUser(id){ 13 | 14 | return users.get(id); 15 | } 16 | 17 | function verifyUser(id, password){ 18 | 19 | const user = getUser(id); 20 | 21 | if(!user){ 22 | return false; 23 | } 24 | 25 | return user.password === password; 26 | } 27 | 28 | function createUser(id, password){ 29 | 30 | if(getUser(id)){ 31 | return false; 32 | } 33 | 34 | const user = { 35 | id: id, 36 | password: password, 37 | victories: 0, 38 | defeats: 0 39 | }; 40 | 41 | users.set(id, user); 42 | return true; 43 | } 44 | 45 | function resetAllUsers(){ 46 | users.clear(); 47 | } 48 | 49 | function reportEndOfMatch(userId, isVictory){ 50 | 51 | const user = getUser(userId); 52 | if(! user){ 53 | throw "Invalid userId: " + userId; 54 | } 55 | 56 | if(isVictory) { 57 | user.victories++; 58 | } else { 59 | user.defeats++; 60 | } 61 | } 62 | 63 | 64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch}; 65 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/src/server/routes/match-api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const {getRandomQuizzes} = require('../db/quizzes'); 3 | 4 | const router = express.Router(); 5 | 6 | 7 | 8 | router.post('/matches', (req, res) => { 9 | 10 | if (!req.user) { 11 | res.status(401).send(); 12 | return; 13 | } 14 | 15 | const payload = getRandomQuizzes(3); 16 | 17 | res.status(201).json(payload); 18 | }); 19 | 20 | 21 | 22 | 23 | module.exports = router; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | const server = require('http').Server(app); 4 | 5 | const port = process.env.PORT || 8080; 6 | 7 | server.listen(port, () => { 8 | console.log('Started server on port ' + port); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/tests/server/db/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/tests/server/routes/match-api-test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const app = require('../../../src/server/app'); 3 | 4 | 5 | test("Test create match no auth", async () =>{ 6 | 7 | const response = await request(app).post('/api/matches'); 8 | 9 | expect(response.statusCode).toBe(401); 10 | }); 11 | 12 | 13 | test("Test create match with auth", async () =>{ 14 | 15 | const user = request.agent(app); 16 | 17 | const signup = await user.post('/api/signup') 18 | .send({userId:'match_auth_foo', password:"bar"}) 19 | .set('Content-Type', 'application/json'); 20 | 21 | expect(signup.statusCode).toBe(201); 22 | 23 | const response = await user.post('/api/matches'); 24 | 25 | expect(response.statusCode).toBe(201); 26 | expect(response.body.length).toBe(3); 27 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-09/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/src/client/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | export class Home extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | componentDidMount() { 10 | if (this.props.user) { 11 | this.props.fetchAndUpdateUserInfo(); 12 | } 13 | } 14 | 15 | render() { 16 | const user = this.props.user; 17 | 18 | return ( 19 |
20 |

Play the Quiz Game

21 |

22 | Welcome to the Quiz Game! In this game, you will get a series of 23 | questions, each one with 4 possible answers. Only 1 out of the 4 24 | answers is correct. If you answer wrongly to any of the questions, you 25 | lose! You win if you manage to answer correctly to all questions. 26 |

27 | 28 | 29 | {user ? ( 30 |
31 | 32 | Play 33 | 34 |
35 |

Victories: {user.victories}

36 |

Defeats: {user.defeats}

37 |
38 |
39 | ) : ( 40 |

You need to log-in to start playing!

41 | )} 42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/src/server/db/matches.js: -------------------------------------------------------------------------------- 1 | const {reportEndOfMatch} = require("./users"); 2 | const {getRandomQuizzes} = require("./quizzes"); 3 | 4 | /** 5 | * Key -> User Id 6 | * Value -> Match info 7 | */ 8 | const matches = new Map(); 9 | 10 | let counter = 0; 11 | 12 | 13 | function createMatch(userId, numberOfQuizzes) { 14 | 15 | const ongoing = matches.get(userId); 16 | if (ongoing) { 17 | reportEndOfMatch(userId, false); 18 | } 19 | 20 | const id = counter; 21 | counter++; 22 | 23 | const match = { 24 | id: id, 25 | current: 0, 26 | quizzes: getRandomQuizzes(numberOfQuizzes), 27 | victory: false, 28 | defeat: false 29 | }; 30 | 31 | matches.set(userId, match); 32 | 33 | return match; 34 | } 35 | 36 | function getMatch(userId) { 37 | return matches.get(userId); 38 | } 39 | 40 | function removeMatch(userId){ 41 | matches.delete(userId); 42 | } 43 | 44 | 45 | module.exports = {getMatch, createMatch, removeMatch}; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/src/server/db/users.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Here we "simulate" a database with in-memory Map. 4 | Furthermore, we do not deal with the "proper" handling of 5 | passwords. Passwords should NEVER be saved in plain text, 6 | but rather hashed with secure algorithms like BCrypt. 7 | */ 8 | 9 | const users = new Map(); 10 | 11 | 12 | function getUser(id){ 13 | 14 | return users.get(id); 15 | } 16 | 17 | function verifyUser(id, password){ 18 | 19 | const user = getUser(id); 20 | 21 | if(!user){ 22 | return false; 23 | } 24 | 25 | return user.password === password; 26 | } 27 | 28 | function createUser(id, password){ 29 | 30 | if(getUser(id)){ 31 | return false; 32 | } 33 | 34 | const user = { 35 | id: id, 36 | password: password, 37 | victories: 0, 38 | defeats: 0 39 | }; 40 | 41 | users.set(id, user); 42 | return true; 43 | } 44 | 45 | function resetAllUsers(){ 46 | users.clear(); 47 | } 48 | 49 | function reportEndOfMatch(userId, isVictory){ 50 | 51 | const user = getUser(userId); 52 | if(! user){ 53 | throw "Invalid userId: " + userId; 54 | } 55 | 56 | if(isVictory) { 57 | user.victories++; 58 | } else { 59 | user.defeats++; 60 | } 61 | } 62 | 63 | 64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch}; 65 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/src/server/server.js: -------------------------------------------------------------------------------- 1 | const {app} = require('./app'); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started server on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/tests/server/db/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/tests/server/routes/match-api-test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const {app} = require('../../../src/server/app'); 3 | 4 | 5 | test("Test create match no auth", async () =>{ 6 | 7 | const response = await request(app).post('/api/matches'); 8 | 9 | expect(response.statusCode).toBe(401); 10 | }); 11 | 12 | 13 | test("Test create match with auth", async () =>{ 14 | 15 | const user = request.agent(app); 16 | 17 | const signup = await user.post('/api/signup') 18 | .send({userId:'match_auth_foo', password:"bar"}) 19 | .set('Content-Type', 'application/json'); 20 | 21 | expect(signup.statusCode).toBe(201); 22 | 23 | const response = await user.post('/api/matches'); 24 | 25 | expect(response.statusCode).toBe(201); 26 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-10/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/src/server/db/matches.js: -------------------------------------------------------------------------------- 1 | const {reportEndOfMatch} = require("./users"); 2 | const {getRandomQuizzes} = require("./quizzes"); 3 | 4 | /** 5 | * Key -> User Id 6 | * Value -> Match info 7 | */ 8 | const matches = new Map(); 9 | 10 | let counter = 0; 11 | 12 | 13 | function createMatch(userId, numberOfQuizzes) { 14 | 15 | const ongoing = matches.get(userId); 16 | if (ongoing) { 17 | reportEndOfMatch(userId, false); 18 | } 19 | 20 | const id = counter; 21 | counter++; 22 | 23 | const match = { 24 | id: id, 25 | current: 0, 26 | quizzes: getRandomQuizzes(numberOfQuizzes), 27 | victory: false, 28 | defeat: false 29 | }; 30 | 31 | matches.set(userId, match); 32 | 33 | return match; 34 | } 35 | 36 | function getMatch(userId) { 37 | return matches.get(userId); 38 | } 39 | 40 | function removeMatch(userId){ 41 | matches.delete(userId); 42 | } 43 | 44 | 45 | module.exports = {getMatch, createMatch, removeMatch}; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/src/server/db/users.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Here we "simulate" a database with in-memory Map. 4 | Furthermore, we do not deal with the "proper" handling of 5 | passwords. Passwords should NEVER be saved in plain text, 6 | but rather hashed with secure algorithms like BCrypt. 7 | */ 8 | 9 | const users = new Map(); 10 | 11 | 12 | function getUser(id){ 13 | 14 | return users.get(id); 15 | } 16 | 17 | function verifyUser(id, password){ 18 | 19 | const user = getUser(id); 20 | 21 | if(!user){ 22 | return false; 23 | } 24 | 25 | return user.password === password; 26 | } 27 | 28 | function createUser(id, password){ 29 | 30 | if(getUser(id)){ 31 | return false; 32 | } 33 | 34 | const user = { 35 | id: id, 36 | password: password, 37 | victories: 0, 38 | defeats: 0 39 | }; 40 | 41 | users.set(id, user); 42 | return true; 43 | } 44 | 45 | function resetAllUsers(){ 46 | users.clear(); 47 | } 48 | 49 | function reportEndOfMatch(userId, isVictory){ 50 | 51 | const user = getUser(userId); 52 | if(! user){ 53 | throw "Invalid userId: " + userId; 54 | } 55 | 56 | if(isVictory) { 57 | user.victories++; 58 | } else { 59 | user.defeats++; 60 | } 61 | } 62 | 63 | 64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch}; 65 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/src/server/server.js: -------------------------------------------------------------------------------- 1 | const {app} = require('./app'); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started server on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/src/server/ws-handler.js: -------------------------------------------------------------------------------- 1 | const express_ws = require('express-ws'); 2 | 3 | 4 | let ews; 5 | 6 | function init(app) { 7 | 8 | ews = express_ws(app); 9 | 10 | app.ws('/', function (socket, req) { 11 | console.log('Established a new WS connection'); 12 | 13 | broadCastCount(); 14 | 15 | //close is treated specially 16 | socket.on('close', () => { 17 | broadCastCount(); 18 | }); 19 | }); 20 | } 21 | 22 | function broadCastCount() { 23 | const n = ews.getWss().clients.size; 24 | 25 | ews.getWss().clients.forEach((client) => { 26 | 27 | const data = JSON.stringify({userCount: n}); 28 | 29 | client.send(data); 30 | }); 31 | } 32 | 33 | 34 | module.exports = {init}; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/tests/server/db/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/tests/server/routes/match-api-test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const {app} = require('../../../src/server/app'); 3 | 4 | 5 | test("Test create match no auth", async () =>{ 6 | 7 | const response = await request(app).post('/api/matches'); 8 | 9 | expect(response.statusCode).toBe(401); 10 | }); 11 | 12 | 13 | test("Test create match with auth", async () =>{ 14 | 15 | const user = request.agent(app); 16 | 17 | const signup = await user.post('/api/signup') 18 | .send({userId:'match_auth_foo', password:"bar"}) 19 | .set('Content-Type', 'application/json'); 20 | 21 | expect(signup.statusCode).toBe(201); 22 | 23 | const response = await user.post('/api/matches'); 24 | 25 | expect(response.statusCode).toBe(201); 26 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-11/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/client/index.jsx', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.jsx$/, 14 | exclude: /node_modules/, 15 | use: { 16 | loader: "babel-loader" 17 | } 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.jsx'] 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/.gitignore: -------------------------------------------------------------------------------- 1 | # Needed for running: 2 | # heroku builds:create -a 3 | # 4 | # Remember you first need to install the "builds" plugin: 5 | # heroku plugins:install heroku-builds 6 | 7 | node_modules 8 | coverage 9 | .idea 10 | .git 11 | 12 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quiz Game 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/src/server/db/matches.js: -------------------------------------------------------------------------------- 1 | const {reportEndOfMatch} = require("./users"); 2 | const {getRandomQuizzes} = require("./quizzes"); 3 | 4 | /** 5 | * Key -> User Id 6 | * Value -> Match info 7 | */ 8 | const matches = new Map(); 9 | 10 | let counter = 0; 11 | 12 | 13 | function createMatch(userId, numberOfQuizzes) { 14 | 15 | const ongoing = matches.get(userId); 16 | if (ongoing) { 17 | reportEndOfMatch(userId, false); 18 | } 19 | 20 | const id = counter; 21 | counter++; 22 | 23 | const match = { 24 | id: id, 25 | current: 0, 26 | quizzes: getRandomQuizzes(numberOfQuizzes), 27 | victory: false, 28 | defeat: false 29 | }; 30 | 31 | matches.set(userId, match); 32 | 33 | return match; 34 | } 35 | 36 | function getMatch(userId) { 37 | return matches.get(userId); 38 | } 39 | 40 | function removeMatch(userId){ 41 | matches.delete(userId); 42 | } 43 | 44 | 45 | module.exports = {getMatch, createMatch, removeMatch}; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/src/server/db/users.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Here we "simulate" a database with in-memory Map. 4 | Furthermore, we do not deal with the "proper" handling of 5 | passwords. Passwords should NEVER be saved in plain text, 6 | but rather hashed with secure algorithms like BCrypt. 7 | */ 8 | 9 | const users = new Map(); 10 | 11 | 12 | function getUser(id){ 13 | 14 | return users.get(id); 15 | } 16 | 17 | function verifyUser(id, password){ 18 | 19 | const user = getUser(id); 20 | 21 | if(!user){ 22 | return false; 23 | } 24 | 25 | return user.password === password; 26 | } 27 | 28 | function createUser(id, password){ 29 | 30 | if(getUser(id)){ 31 | return false; 32 | } 33 | 34 | const user = { 35 | id: id, 36 | password: password, 37 | victories: 0, 38 | defeats: 0 39 | }; 40 | 41 | users.set(id, user); 42 | return true; 43 | } 44 | 45 | function resetAllUsers(){ 46 | users.clear(); 47 | } 48 | 49 | function reportEndOfMatch(userId, isVictory){ 50 | 51 | const user = getUser(userId); 52 | if(! user){ 53 | throw "Invalid userId: " + userId; 54 | } 55 | 56 | if(isVictory) { 57 | user.victories++; 58 | } else { 59 | user.defeats++; 60 | } 61 | } 62 | 63 | 64 | module.exports = {getUser, verifyUser, createUser, resetAllUsers, reportEndOfMatch}; 65 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/src/server/server.js: -------------------------------------------------------------------------------- 1 | const {app} = require('./app'); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started server on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/src/server/ws-handler.js: -------------------------------------------------------------------------------- 1 | const express_ws = require('express-ws'); 2 | 3 | 4 | let ews; 5 | 6 | function init(app) { 7 | 8 | ews = express_ws(app); 9 | 10 | app.ws('/', function (socket, req) { 11 | console.log('Established a new WS connection'); 12 | 13 | broadcastCount(); 14 | 15 | //close is treated specially 16 | socket.on('close', () => { 17 | broadcastCount(); 18 | }); 19 | }); 20 | } 21 | 22 | function broadcastCount() { 23 | const n = ews.getWss().clients.size; 24 | 25 | ews.getWss().clients.forEach((client) => { 26 | 27 | const data = JSON.stringify({userCount: n}); 28 | 29 | client.send(data); 30 | }); 31 | } 32 | 33 | 34 | module.exports = {init}; -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/tests/client/home-test.jsx: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const {mount} = require('enzyme'); 3 | const {MemoryRouter} = require('react-router-dom'); 4 | 5 | 6 | const {Home} = require('../../src/client/home'); 7 | 8 | 9 | const needToLogInMsg = "You need to log-in to start playing!"; 10 | 11 | test("Test not logged in", async () => { 12 | 13 | const driver = mount(); 14 | 15 | const html = driver.html(); 16 | expect(html.includes(needToLogInMsg)).toEqual(true); 17 | }); 18 | 19 | 20 | test("Test logged in", async () => { 21 | 22 | const victories = 42; 23 | const defeats = 77; 24 | 25 | const user = {id: "Foo", victories, defeats}; 26 | const fetchAndUpdateUserInfo = () => new Promise(resolve => resolve()); 27 | 28 | const driver = mount( 29 | 30 | 31 | 32 | ); 33 | 34 | const html = driver.html(); 35 | expect(html.includes(needToLogInMsg)).toEqual(false); 36 | expect(html.includes(victories)).toEqual(true); 37 | expect(html.includes(defeats)).toEqual(true); 38 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/tests/server/db/quizzes-test.js: -------------------------------------------------------------------------------- 1 | const {getRandomQuizzes} = require("../../../src/server/db/quizzes"); 2 | 3 | 4 | test("Test invalid n", () =>{ 5 | 6 | expect(() => getRandomQuizzes(-1)).toThrow(); 7 | expect(() => getRandomQuizzes(0)).toThrow(); 8 | expect(() => getRandomQuizzes(100000000)).toThrow(); 9 | }); 10 | 11 | 12 | test("Test get 1", () => { 13 | 14 | const quizzes = getRandomQuizzes(1); 15 | 16 | expect(quizzes.length).toBe(1); 17 | expect(quizzes[0].question).toBeDefined(); 18 | expect(quizzes[0].answers).toBeDefined(); 19 | expect(quizzes[0].answers.length).toBe(4); 20 | }); 21 | 22 | test("Test get 2", () => { 23 | 24 | for(let i=0; i<100; i++) { 25 | const quizzes = getRandomQuizzes(2); 26 | 27 | expect(quizzes.length).toBe(2); 28 | expect(quizzes[0].question).not.toBe(quizzes[1].question); 29 | } 30 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/tests/server/routes/match-api-test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const {app} = require('../../../src/server/app'); 3 | 4 | 5 | test("Test create match no auth", async () =>{ 6 | 7 | const response = await request(app).post('/api/matches'); 8 | 9 | expect(response.statusCode).toBe(401); 10 | }); 11 | 12 | 13 | test("Test create match with auth", async () =>{ 14 | 15 | const user = request.agent(app); 16 | 17 | const signup = await user.post('/api/signup') 18 | .send({userId:'match_auth_foo', password:"bar"}) 19 | .set('Content-Type', 'application/json'); 20 | 21 | expect(signup.statusCode).toBe(201); 22 | 23 | const response = await user.post('/api/matches'); 24 | 25 | expect(response.statusCode).toBe(201); 26 | }); -------------------------------------------------------------------------------- /exercise-solutions/quiz-game/part-12/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /extra-graphql/forum/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My News 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /extra-graphql/forum/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputName{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /extra-graphql/forum/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'; 4 | 5 | import {Home} from "./home"; 6 | import {User} from "./user"; 7 | import {News} from "./news"; 8 | 9 | const NotFound = () => { 10 | 11 | return ( 12 |
13 |

NOT FOUND: 404

14 |

15 | ERROR: the page you requested in not available. 16 |

17 |
18 | ); 19 | }; 20 | 21 | const App = () => { 22 | 23 | return ( 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /extra-graphql/forum/src/server/app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const { ApolloServer, graphiqlExpress} = require('apollo-server-express'); 4 | 5 | const resolvers = require('./resolvers'); 6 | const typeDefs = require('./schema'); 7 | 8 | /* 9 | We still are going to use Express... Apollo will just add to it a 10 | specific endpoint to handle GraphQL requests 11 | */ 12 | 13 | const app = express(); 14 | 15 | /* 16 | A GraphQL API is defined by: 17 | 1) a schema, specifying what is available 18 | 2) resolvers: to map from schema to our domain model (eg, data in databases) 19 | */ 20 | const apollo = new ApolloServer({ typeDefs, resolvers }); 21 | apollo.applyMiddleware({ app , path:"/graphql"}); 22 | 23 | 24 | //needed to server static files, like HTML, CSS and JS. 25 | app.use(express.static('public')); 26 | 27 | 28 | app.use((req, res, next) => { 29 | res.sendFile(path.resolve(__dirname, '..', '..', 'public', 'index.html')); 30 | }); 31 | 32 | module.exports = app; -------------------------------------------------------------------------------- /extra-graphql/forum/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | const {initDB} = require('./db-initializer'); 3 | 4 | initDB(); 5 | 6 | const port = process.env.PORT || 8080; 7 | 8 | app.listen(port, () => { 9 | console.log('Server started on port ' + port); 10 | }); 11 | 12 | -------------------------------------------------------------------------------- /extra-graphql/forum/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | optimization: { 26 | minimize: true, 27 | minimizer: [new TerserPlugin({ 28 | extractComments: false, 29 | })] 30 | } 31 | }; -------------------------------------------------------------------------------- /les01/base-html/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Just a Simple Example 6 | 7 | 8 | 9 |

10 | Just Another Page 11 |

12 | 13 | 14 |

15 | Some Lore Ipsum (a placeholder text): 16 | 17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 18 | ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 19 | laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in 20 | voluptate velit esse cillum dolore eu fugiat nulla pariatur. 21 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 22 | deserunt mollit anim id est laborum. 23 | 24 |

25 | 26 |

27 | Return to Home. 28 |

29 | 30 | 31 | -------------------------------------------------------------------------------- /les01/cards/img/2_of_spades.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/2_of_spades.png -------------------------------------------------------------------------------- /les01/cards/img/black_joker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/black_joker.png -------------------------------------------------------------------------------- /les01/cards/img/cover_card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les01/cards/img/cover_card.jpg -------------------------------------------------------------------------------- /les01/cards/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0px; 3 | background-color: rgb(243, 239, 231); 4 | color: rgb(71, 53, 25); 5 | text-align: center; 6 | font-size: 1.2em; 7 | } 8 | 9 | 10 | .card { 11 | margin: 20px; 12 | height: 300px; 13 | border: 2px solid black; 14 | border-radius: 5px; 15 | cursor: pointer; 16 | } 17 | 18 | .selectedCard{ 19 | box-shadow: 10px 10px 30px 1px blue; 20 | } 21 | 22 | .outputMessage{ 23 | background-color: green; 24 | color: black; 25 | } 26 | 27 | .backcover:hover{ 28 | box-shadow: 10px 10px 30px 1px yellow; 29 | } 30 | 31 | .fakeLink{ 32 | color: blue; 33 | cursor: pointer; 34 | text-decoration: underline; 35 | } -------------------------------------------------------------------------------- /les02/libraries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "dependencies": { 4 | "lodash": "4.17.20" 5 | }, 6 | "description": "", 7 | "devDependencies": { 8 | "webpack": "5.11.1", 9 | "webpack-cli": "4.3.1", 10 | "webpack-dev-server": "3.11.1" 11 | }, 12 | "engines": { 13 | "node": "^14.0.0" 14 | }, 15 | "keywords": [], 16 | "license": "LGPL-3.0", 17 | "name": "basics", 18 | "private": true, 19 | "scripts": { 20 | "build": "webpack --mode production", 21 | "dev": "webpack serve --open --mode development", 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | }, 24 | "version": "1.0.0" 25 | } -------------------------------------------------------------------------------- /les02/libraries/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | When we "import" the default object exported by a library, we can give it any name 3 | we want, eg., "_" in this case. 4 | Such name is then use as a variable in the rest of this module 5 | */ 6 | import _ from 'lodash'; 7 | 8 | /* 9 | A library/module could export several different objects/functions. 10 | In such case, a common approach is to "import" an object definition 11 | where each element we want to use from that module is declared. 12 | This creates variables associated to those names, which then can be 13 | used directly. 14 | */ 15 | import {computeMin, computeMax, computeMean, computeSum} from "./my-math.js"; 16 | 17 | 18 | function initValues() { 19 | 20 | const array = [8,1,5,1,1,2,3]; 21 | 22 | const min = computeMin(array); 23 | const max = computeMax(array); 24 | const mean = computeMean(array); 25 | const sum = computeSum(array); 26 | 27 | const arrayString = "[" + _.join(array, ", ") + "]"; 28 | 29 | document.getElementById("arrayStringId").innerHTML = arrayString; 30 | document.getElementById("minId").innerHTML = min; 31 | document.getElementById("maxId").innerHTML = max; 32 | document.getElementById("meanId").innerHTML = mean; 33 | document.getElementById("sumId").innerHTML = sum; 34 | }; 35 | 36 | 37 | initValues(); 38 | -------------------------------------------------------------------------------- /les02/libraries/src/my-math.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | 4 | export function computeMin(array){ 5 | return _.min(array); 6 | } 7 | 8 | export function computeMax(array) { 9 | return _.max(array); 10 | } 11 | 12 | export function computeMean(array){ 13 | return _.mean(array); 14 | } 15 | 16 | export function computeSum(array) { 17 | return _.sum(array); 18 | } -------------------------------------------------------------------------------- /les02/libraries/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'public') 9 | }, 10 | devServer: { 11 | contentBase: './public' 12 | }, 13 | optimization: { 14 | /* 15 | This is done to avoid creation of unwanted license file for the dependencies 16 | */ 17 | minimize: true, 18 | minimizer: [new TerserPlugin({ 19 | extractComments: false, 20 | })] 21 | } 22 | }; -------------------------------------------------------------------------------- /les02/tic-tac-toe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/env" 6 | ] 7 | }, 8 | "description": "", 9 | "devDependencies": { 10 | "@babel/cli": "7.12.10", 11 | "@babel/core": "7.12.10", 12 | "@babel/preset-env": "7.12.11", 13 | "babel-jest": "24.9.0", 14 | "jest": "24.9.0", 15 | "webpack": "5.11.1", 16 | "webpack-cli": "4.3.1", 17 | "webpack-dev-server": "3.11.1" 18 | }, 19 | "engines": { 20 | "node": "^14.0.0" 21 | }, 22 | "jest": { 23 | "collectCoverageFrom": [ 24 | "src/**/*.(js|jsx)" 25 | ], 26 | "testRegex": "tests/.*-test\\.(js|jsx)$" 27 | }, 28 | "keywords": [], 29 | "license": "LGPL-3.0", 30 | "main": "index.js", 31 | "name": "tic-tac-toe", 32 | "scripts": { 33 | "build": "webpack --mode production", 34 | "dev": "webpack serve --open --mode development", 35 | "test": "jest --coverage" 36 | }, 37 | "version": "1.0.0" 38 | } -------------------------------------------------------------------------------- /les02/tic-tac-toe/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tic-Tac-Toe 7 | 8 | 9 | 10 |

Tic-Tac-Toe Game

11 | 12 | Tic-Tac-Toe is a very common example in Computer Science / Software Engineering. 13 | Here, we start with a very simplified version where two users play on the same screen. 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 |
New Match
39 |
40 | 41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /les02/tic-tac-toe/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 30px; 3 | } 4 | 5 | 6 | .cell { 7 | float: left; 8 | background: white; 9 | border: 1px solid gray; 10 | font-size: 24px; 11 | font-weight: bold; 12 | text-align: center; 13 | height: 30px; 14 | width: 30px; 15 | margin-right: -1px; 16 | margin-top: -1px; 17 | } 18 | 19 | 20 | .game { 21 | margin-top: 50px; 22 | display: flex; 23 | flex-direction: row; 24 | } 25 | 26 | .game-info { 27 | margin-left: 20px; 28 | } 29 | 30 | 31 | .btn { 32 | background-color: rgb(207, 205, 200); 33 | font-size: 1em; 34 | text-align: center; 35 | vertical-align: middle; 36 | border: 2px solid black; 37 | border-radius: 5px; 38 | margin: 5px; 39 | margin-top: 20px; 40 | padding: 3px; 41 | cursor: pointer; 42 | } 43 | -------------------------------------------------------------------------------- /les02/tic-tac-toe/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | devServer: { 12 | contentBase: './public' 13 | }, 14 | optimization: { 15 | minimize: true, 16 | minimizer: [new TerserPlugin({ 17 | extractComments: false, 18 | })] 19 | } 20 | }; -------------------------------------------------------------------------------- /les02/unit-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/env" 6 | ] 7 | }, 8 | "description": "", 9 | "devDependencies": { 10 | "@babel/cli": "7.12.10", 11 | "@babel/core": "7.12.10", 12 | "@babel/preset-env": "7.12.11", 13 | "@webpack-cli/serve": "^1.2.2", 14 | "babel-jest": "24.9.0", 15 | "jest": "24.9.0", 16 | "webpack": "4.41.2", 17 | "webpack-cli": "3.3.10", 18 | "webpack-dev-server": "3.9.0" 19 | }, 20 | "engines": { 21 | "node": "^14.0.0" 22 | }, 23 | "jest": { 24 | "collectCoverageFrom": [ 25 | "src/**/*.(js|jsx)" 26 | ], 27 | "testRegex": "tests/.*-test\\.(js|jsx)$" 28 | }, 29 | "keywords": [], 30 | "license": "LGPL-3.0", 31 | "main": "index.js", 32 | "name": "unit-tests", 33 | "scripts": { 34 | "build": "webpack --mode production", 35 | "dev": "webpack-dev-server --open --mode development", 36 | "test": "jest --coverage" 37 | }, 38 | "version": "1.0.0" 39 | } 40 | -------------------------------------------------------------------------------- /les02/unit-tests/src/my-math.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | 4 | export function computeMin(array){ 5 | return _.min(array); 6 | } 7 | 8 | export function computeMax(array) { 9 | return _.max(array); 10 | } 11 | 12 | export function computeMean(array){ 13 | return _.mean(array); 14 | } 15 | 16 | export function computeSum(array) { 17 | return _.sum(array); 18 | } -------------------------------------------------------------------------------- /les02/unit-tests/tests/index-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tests are run on NodeJS, which uses "require()" to handle modules and 3 | not "import" 4 | */ 5 | const index = require('../src/index.js'); 6 | 7 | /* 8 | With Jest included in package.json, and properly configured, then 9 | in these files we can use the method "test" to write unit tests. 10 | It takes 2 inputs: 11 | - the name of the test 12 | - a function that is going to be executed, representing the code 13 | of the test 14 | 15 | We use "expect(result).toBe(expectedValue)" 16 | to write check on whether the "result" we get from testing the code 17 | is the expected one. If it is not, then the test fails. 18 | */ 19 | 20 | 21 | test('Not a string', () => { 22 | expect(index.validateInput(1)).toBe(false); 23 | }); 24 | 25 | test('Invalid character', () => { 26 | expect(index.validateInput("1 <")).toBe(false); 27 | }); 28 | 29 | 30 | 31 | test('Single element', () => { 32 | expect(index.validateInput("1")).toBe(true); 33 | }); 34 | 35 | test('Multi-elements', () => { 36 | expect(index.validateInput("1, 2")).toBe(true); 37 | }); 38 | 39 | test('Negative value', () => { 40 | expect(index.validateInput("-1")).toBe(true); 41 | }); 42 | 43 | test('Complex example', () => { 44 | expect(index.validateInput(" -1,2, -3 , 1 ")).toBe(true); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /les02/unit-tests/tests/my-math-test.js: -------------------------------------------------------------------------------- 1 | const math = require('../src/my-math.js'); 2 | 3 | 4 | const array = [8,1,5,1,1,2,3]; 5 | 6 | test('Min', () => { 7 | expect(math.computeMin(array)).toEqual(1); 8 | }); 9 | 10 | 11 | test('Max', () => { 12 | expect(math.computeMax(array)).toEqual(8); 13 | }); 14 | 15 | test('Mean', () => { 16 | expect(math.computeMean(array)).toEqual(3); 17 | }); 18 | 19 | test('Sum', () => { 20 | expect(math.computeSum(array)).toEqual(21); 21 | }); -------------------------------------------------------------------------------- /les02/unit-tests/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | /* 11 | The code from bundle.js will be executed when HTML loads it. 12 | But, by default, we cannot access it. To be able to access it from 13 | other JS in the page, we need to export it as a module. 14 | In this case, the module will be called 'EntryPoint', which is a 'var'. 15 | Note: this is just an example. In general, we will not need to do something 16 | like this, as all the code we will run is from bundle.js itself 17 | 18 | NOTE: this does not work yet in WebPack 5 (regression bug)... so need to use v4. 19 | Anyway, this the only case in the course in which are going to do something like this 20 | */ 21 | libraryTarget: 'var', 22 | library: 'EntryPoint' 23 | }, 24 | devServer: { 25 | contentBase: './public' 26 | }, 27 | optimization: { 28 | minimize: true, 29 | minimizer: [new TerserPlugin({ 30 | extractComments: false, 31 | })] 32 | } 33 | }; -------------------------------------------------------------------------------- /les03/puzzle15/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 Puzzle Game 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /les03/puzzle15/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: green; 3 | } 4 | 5 | #root{ 6 | margin: 30px; 7 | } 8 | 9 | .cell { 10 | float: left; 11 | background: white; 12 | border: 1px solid gray; 13 | font-size: 24px; 14 | font-weight: bold; 15 | text-align: center; 16 | line-height: 30px; 17 | height: 30px; 18 | width: 30px; 19 | margin-right: -1px; 20 | margin-top: -1px; 21 | } 22 | 23 | .index { 24 | float: left; 25 | font-size: 12px; 26 | text-align: center; 27 | width: 30px; 28 | border: 1px solid transparent; 29 | margin-right: -1px; 30 | margin-top: -1px; 31 | } 32 | 33 | 34 | .cell-row:after { 35 | clear: both; 36 | content: ""; 37 | display: table; 38 | } 39 | 40 | 41 | .game { 42 | display: flex; 43 | flex-direction: row; 44 | } 45 | 46 | .game-info { 47 | margin-top: 30px; 48 | margin-left: 20px; 49 | font-weight: bold; 50 | } 51 | 52 | 53 | .btn { 54 | width: 90px; 55 | background-color: rgb(207, 205, 200); 56 | font-size: 1em; 57 | text-align: center; 58 | padding: 5px; 59 | border: 2px solid black; 60 | border-radius: 5px; 61 | margin: 5px; 62 | cursor: pointer; 63 | } 64 | -------------------------------------------------------------------------------- /les03/puzzle15/tests/utils-test.js: -------------------------------------------------------------------------------- 1 | const utils = require("../src/utils"); 2 | 3 | 4 | test("Create matrix", () => { 5 | 6 | const r = 2; 7 | const c = 3; 8 | const v = "foo"; 9 | 10 | const m = utils.createMatrix(r, c, v); 11 | 12 | expect(m.length).toEqual(r); 13 | 14 | for(let i=0; i{ 26 | 27 | const before = "before"; 28 | const after = "after"; 29 | 30 | const m = utils.createMatrix(4, 7, before); 31 | const copy = utils.cloneMatrix(m); 32 | 33 | expect(copy[2][3]).toEqual(before); 34 | 35 | copy[2][3] = after; 36 | 37 | expect(copy[2][3]).toEqual(after); 38 | 39 | //if deep-cloning, m must not have changed 40 | expect(m[2][3]).toEqual(before); 41 | }); -------------------------------------------------------------------------------- /les03/puzzle15/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les03/spa-components-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "description": "", 4 | "devDependencies": { 5 | "webpack": "5.11.1", 6 | "webpack-cli": "4.3.1", 7 | "webpack-dev-server": "3.11.1" 8 | }, 9 | "engines": { 10 | "node": "^14.0.0" 11 | }, 12 | "keywords": [], 13 | "license": "LGPL-3.0", 14 | "main": "index.js", 15 | "name": "spa-components-js", 16 | "scripts": { 17 | "build": "webpack --mode production", 18 | "dev": "webpack serve --open --mode development", 19 | "test": "echo \"Error: no test specified\" && exit 1" 20 | }, 21 | "version": "1.0.0" 22 | } -------------------------------------------------------------------------------- /les03/spa-components-js/public/style.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | height: 20px; 3 | width: 20px; 4 | background-color: gray; 5 | font-size: 1em; 6 | border: 2px solid black; 7 | border-radius: 5px; 8 | margin: 5px; 9 | margin-bottom: 20px; 10 | cursor: pointer; 11 | text-align: center; 12 | display: inline-block; 13 | } -------------------------------------------------------------------------------- /les03/spa-components-js/src/container.js: -------------------------------------------------------------------------------- 1 | import {MyComponent} from "./mylib/MyComponent"; 2 | import {Counter} from "./counter"; 3 | 4 | 5 | export class Container extends MyComponent { 6 | 7 | constructor(n) { 8 | super(); 9 | this.children = Array(n); 10 | 11 | for (let i = 0; i < n; i++) { 12 | this.children[i] = new Counter(i); 13 | } 14 | } 15 | 16 | render() { 17 | 18 | return ( 19 | "
" + 20 | "

Counters

" + 21 | this.children.map(e => e.render()).join("") + 22 | "
" 23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /les03/spa-components-js/src/counter.js: -------------------------------------------------------------------------------- 1 | import {MyComponent} from "./mylib/MyComponent"; 2 | 3 | 4 | export class Counter extends MyComponent { 5 | 6 | constructor(name) { 7 | super(); 8 | this.name = name; 9 | this.state = {value: 0}; 10 | } 11 | 12 | increase() { 13 | this.setState({value: this.state.value + 1}); 14 | } 15 | 16 | decrease() { 17 | this.setState({value: this.state.value - 1}); 18 | } 19 | 20 | render() { 21 | 22 | const name = this.name ? this.name : "Default"; 23 | 24 | return ( 25 | "
" + 26 | "
Counter for " + name + ": " + this.state.value + "
" + 27 | "
+
" + 28 | "
-
" + 29 | "
" 30 | ); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /les03/spa-components-js/src/index.js: -------------------------------------------------------------------------------- 1 | import {MyComponent} from "./mylib/MyComponent"; 2 | import {render} from "./mylib/MyDOM"; 3 | import {Container} from "./container"; 4 | 5 | class App extends MyComponent { 6 | 7 | constructor() { 8 | super(); 9 | 10 | this.container = new Container(3); 11 | this.children = [this.container]; 12 | } 13 | 14 | render() { 15 | return ( 16 | "
" + 17 | "

Example of Single-Page Application Components with Vanilla JavaScript

" + 18 | this.container.render() + 19 | "
" 20 | ); 21 | } 22 | } 23 | 24 | 25 | render(new App(), document.getElementById("root")); -------------------------------------------------------------------------------- /les03/spa-components-js/src/mylib/MyComponent.js: -------------------------------------------------------------------------------- 1 | import {update} from "./MyDOM" 2 | 3 | let id_counter = 0; 4 | 5 | export class MyComponent { 6 | 7 | constructor() { 8 | //each component will have a unique id 9 | this.id = "MyComponent_id_" + id_counter; 10 | id_counter++; 11 | } 12 | 13 | setState(state) { 14 | 15 | this.state = state; 16 | 17 | //every time there is a state change, we render the components 18 | update(); 19 | } 20 | 21 | doesMatchId(id) { 22 | return this.id === id; 23 | } 24 | 25 | /* 26 | In the generated HTML for the component, we will need to be able to 27 | register event handlers. 28 | But those handlers need to refer to the specific component they belong to. 29 | So, the generated JS in the page somehow needs to find the right component to 30 | use its state. 31 | We will use a global support function registered in MyDOM.js. 32 | 33 | This works, but it has limitations: the method must not have input parameters, 34 | and should be a method belonging to the class of the component. 35 | */ 36 | methodHandler(method) { 37 | 38 | const name = method.name; 39 | 40 | return "'MyDOM_callMethodOnComponent(\"" + this.id + "\", \"" + name + "\")'"; 41 | } 42 | } -------------------------------------------------------------------------------- /les03/spa-components-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | devServer: { 12 | contentBase: './public' 13 | }, 14 | optimization: { 15 | minimize: true, 16 | minimizer: [new TerserPlugin({ 17 | extractComments: false, 18 | })] 19 | } 20 | }; -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/react" 6 | ] 7 | }, 8 | "dependencies": { 9 | "react": "16.12.0", 10 | "react-dom": "16.12.0" 11 | }, 12 | "description": "", 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "babel-loader": "8.2.2", 17 | "webpack": "5.11.1", 18 | "webpack-cli": "4.3.1", 19 | "webpack-dev-server": "3.11.1" 20 | }, 21 | "engines": { 22 | "node": "^14.0.0" 23 | }, 24 | "keywords": [], 25 | "license": "LGPL-3.0", 26 | "main": "index.js", 27 | "name": "spa-components-react-hooks", 28 | "scripts": { 29 | "build": "webpack --mode production", 30 | "dev": "webpack serve --open --mode development", 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | }, 33 | "version": "1.0.0" 34 | } -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example of Single-Page Application Components with React Hooks 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/public/style.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | height: 20px; 3 | width: 20px; 4 | background-color: gray; 5 | font-size: 1em; 6 | border: 2px solid black; 7 | border-radius: 5px; 8 | margin: 5px; 9 | margin-bottom: 20px; 10 | cursor: pointer; 11 | text-align: center; 12 | display: inline-block; 13 | } -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/src/container.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Counter} from "./counter" 3 | 4 | export class Container extends React.Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | 11 | render() { 12 | 13 | const n = this.props.ncounters ? this.props.ncounters : 1; 14 | 15 | return ( 16 |
17 |

Counters

18 | { 19 | Array.from(Array(n)) 20 | .map((e,i) => ) 21 | } 22 |
23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/src/counter.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from "react"; 2 | 3 | export function Counter(props) { 4 | 5 | /* 6 | - new state, with default value 0 7 | - useState will return an array with two values 8 | - the first array value will be saved in a variable called 'count' 9 | - the second array value will be saved in a variable called 'setCount' 10 | */ 11 | const [count, setCount] = useState(0); 12 | 13 | const name = props.name ? props.name : "Default"; 14 | 15 | return ( 16 |
17 |
Counter for {name}: {count}
18 |
setCount(count + 1)}>+
19 |
setCount(count - 1)}>-
20 |
21 | ); 22 | } 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {Container} from "./container"; 4 | 5 | const App = () => { 6 | 7 | return( 8 |
9 |

Example of Single-Page Application Components with React Hooks

10 | {/* 11 | This will render the HTML from the Container component with 12 | 3 Counter components inside it. 13 | We simply call the name of component inside a tag. 14 | */} 15 | 16 |
17 | ); 18 | }; 19 | 20 | /* 21 | This is where we bind our generated HTML via React to the .html file. 22 | Changes in the state of the React Components will automatically trigger 23 | the re-rendering of the HTML. 24 | */ 25 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les03/spa-components-react-hooks/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les03/spa-components-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "plugins": [ 5 | "@babel/plugin-proposal-class-properties" 6 | ], 7 | "presets": [ 8 | "@babel/react" 9 | ] 10 | }, 11 | "dependencies": { 12 | "react": "16.12.0", 13 | "react-dom": "16.12.0" 14 | }, 15 | "description": "", 16 | "devDependencies": { 17 | "@babel/core": "7.12.10", 18 | "@babel/plugin-proposal-class-properties": "7.12.1", 19 | "@babel/preset-react": "7.12.10", 20 | "babel-loader": "8.2.2", 21 | "webpack": "5.11.1", 22 | "webpack-cli": "4.3.1", 23 | "webpack-dev-server": "3.11.1" 24 | }, 25 | "engines": { 26 | "node": "^14.0.0" 27 | }, 28 | "keywords": [], 29 | "license": "LGPL-3.0", 30 | "main": "index.js", 31 | "name": "spa-components-react", 32 | "scripts": { 33 | "build": "webpack --mode production", 34 | "dev": "webpack serve --open --mode development", 35 | "test": "echo \"Error: no test specified\" && exit 1" 36 | }, 37 | "version": "1.0.0" 38 | } -------------------------------------------------------------------------------- /les03/spa-components-react/public/style.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | height: 20px; 3 | width: 20px; 4 | background-color: gray; 5 | font-size: 1em; 6 | border: 2px solid black; 7 | border-radius: 5px; 8 | margin: 5px; 9 | margin-bottom: 20px; 10 | cursor: pointer; 11 | text-align: center; 12 | display: inline-block; 13 | } -------------------------------------------------------------------------------- /les04/connect4/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Connect Four Game 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /les04/connect4/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: green; 3 | } 4 | 5 | #root{ 6 | margin: 30px; 7 | } 8 | 9 | .cell { 10 | float: left; 11 | background: white; 12 | border: 1px solid gray; 13 | font-size: 24px; 14 | font-weight: bold; 15 | text-align: center; 16 | line-height: 30px; 17 | height: 30px; 18 | width: 30px; 19 | margin-right: -1px; 20 | margin-top: -1px; 21 | } 22 | 23 | .index { 24 | float: left; 25 | font-size: 12px; 26 | text-align: center; 27 | width: 30px; 28 | border: 1px solid transparent; 29 | margin-right: -1px; 30 | margin-top: -1px; 31 | } 32 | 33 | 34 | .cell-row:after { 35 | clear: both; 36 | content: ""; 37 | display: table; 38 | } 39 | 40 | 41 | .game { 42 | display: flex; 43 | flex-direction: row; 44 | } 45 | 46 | .game-info { 47 | margin-top: 30px; 48 | margin-left: 20px; 49 | font-weight: bold; 50 | } 51 | 52 | 53 | .btn { 54 | width: 90px; 55 | background-color: rgb(207, 205, 200); 56 | font-size: 1em; 57 | text-align: center; 58 | padding: 5px; 59 | border: 2px solid black; 60 | border-radius: 5px; 61 | margin: 5px; 62 | cursor: pointer; 63 | } 64 | -------------------------------------------------------------------------------- /les04/connect4/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {Connect4} from "./connect4"; 4 | 5 | const App = () => { 6 | return( 7 |
8 |

Connect Four Game

9 | 10 |
11 | ); 12 | }; 13 | 14 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les04/connect4/src/info.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /* 4 | The info panel has no state... the state belongs to the parent, 5 | and it is passed here as props. 6 | So no need to have a React.Component class. 7 | Can just have a function that takes as input props. 8 | */ 9 | export const Info = (props) =>{ 10 | 11 | const res = props.result; 12 | 13 | let msg; 14 | if(res === 0){ 15 | const player = props.xIsNext ? "X" : "O"; 16 | msg = "Next Player is: " + player; 17 | } else if(res === 1){ 18 | msg = "X Won!" 19 | } else if(res === 2){ 20 | msg = "O Won!" 21 | } else if(res === 3){ 22 | msg = "The Game Ended in a Tie!" 23 | } else { 24 | throw "Invalid result code: " + res; 25 | } 26 | 27 | return( 28 |
29 |
{msg}
30 |
New Match
31 |
32 | ); 33 | }; -------------------------------------------------------------------------------- /les04/connect4/src/utils.jsx: -------------------------------------------------------------------------------- 1 | 2 | export function createMatrix(rows, columns, value){ 3 | return Array(rows).fill(null).map(() => Array(columns).fill(value)); 4 | } 5 | 6 | 7 | export function cloneMatrix(matrix){ 8 | return matrix.map(e => e.slice()); 9 | } 10 | -------------------------------------------------------------------------------- /les04/connect4/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les04/connect4/tests/utils-test.js: -------------------------------------------------------------------------------- 1 | const utils = require("../src/utils"); 2 | 3 | 4 | test("Create matrix", () => { 5 | 6 | const r = 2; 7 | const c = 3; 8 | const v = "foo"; 9 | 10 | const m = utils.createMatrix(r, c, v); 11 | 12 | expect(m.length).toEqual(r); 13 | 14 | for(let i=0; i{ 26 | 27 | const before = "before"; 28 | const after = "after"; 29 | 30 | const m = utils.createMatrix(4, 7, before); 31 | const copy = utils.cloneMatrix(m); 32 | 33 | expect(copy[2][3]).toEqual(before); 34 | 35 | copy[2][3] = after; 36 | 37 | expect(copy[2][3]).toEqual(after); 38 | 39 | //if deep-cloning, m must not have changed 40 | expect(m[2][3]).toEqual(before); 41 | }); -------------------------------------------------------------------------------- /les04/connect4/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les04/state-lift-up/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "plugins": [ 5 | "@babel/plugin-proposal-class-properties" 6 | ], 7 | "presets": [ 8 | "@babel/env", 9 | "@babel/react" 10 | ] 11 | }, 12 | "dependencies": { 13 | "react": "16.12.0", 14 | "react-dom": "16.12.0" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "7.12.10", 18 | "@babel/plugin-proposal-class-properties": "7.12.1", 19 | "@babel/preset-env": "7.12.11", 20 | "@babel/preset-react": "7.12.10", 21 | "babel-loader": "8.2.2", 22 | "webpack": "5.11.1", 23 | "webpack-cli": "4.3.1", 24 | "webpack-dev-server": "3.11.1" 25 | }, 26 | "engines": { 27 | "node": "^14.0.0" 28 | }, 29 | "license": "LGPL-3.0", 30 | "main": "index.js", 31 | "name": "state-lift-up", 32 | "scripts": { 33 | "build": "webpack --mode production", 34 | "dev": "webpack serve --open --mode development", 35 | "test": "echo \"Error: no test specified\" && exit 1" 36 | }, 37 | "version": "1.0.0" 38 | } -------------------------------------------------------------------------------- /les04/state-lift-up/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example of Shared State Lifted Up in Ancestor Components 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /les04/state-lift-up/public/style.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | height: 20px; 3 | width: 20px; 4 | background-color: gray; 5 | font-size: 1em; 6 | border: 2px solid black; 7 | border-radius: 5px; 8 | margin: 5px; 9 | margin-bottom: 20px; 10 | cursor: pointer; 11 | text-align: center; 12 | display: inline-block; 13 | } -------------------------------------------------------------------------------- /les04/state-lift-up/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {Container} from "./container"; 4 | 5 | const App = () => { 6 | 7 | return( 8 |
9 |

Example of Shared State Lifted Up in Ancestor Components

10 | 11 |
12 | ); 13 | }; 14 | 15 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les04/state-lift-up/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les05/games/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "plugins": [ 5 | "@babel/plugin-proposal-class-properties" 6 | ], 7 | "presets": [ 8 | "@babel/react" 9 | ] 10 | }, 11 | "dependencies": { 12 | "react": "16.12.0", 13 | "react-dom": "16.12.0", 14 | "react-router-dom": "5.2.0" 15 | }, 16 | "description": "", 17 | "devDependencies": { 18 | "@babel/core": "7.12.10", 19 | "@babel/plugin-proposal-class-properties": "7.12.1", 20 | "@babel/preset-react": "7.12.10", 21 | "babel-loader": "8.2.2", 22 | "webpack": "5.11.1", 23 | "webpack-cli": "4.3.1", 24 | "webpack-dev-server": "3.11.1" 25 | }, 26 | "engines": { 27 | "node": "^14.0.0" 28 | }, 29 | "keywords": [], 30 | "license": "LGPL-3.0", 31 | "main": "index.js", 32 | "name": "games", 33 | "scripts": { 34 | "build": "webpack --mode production", 35 | "dev": "webpack serve --open --mode development", 36 | "test": "echo \"Error: no test specified\" && exit 1" 37 | }, 38 | "version": "1.0.0" 39 | } -------------------------------------------------------------------------------- /les05/games/public/games/cards/cards.css: -------------------------------------------------------------------------------- 1 | .cardsCard { 2 | margin: 20px; 3 | height: 300px; 4 | background-color: rgb(207, 205, 200); 5 | font-size: 1em; 6 | border: 2px solid black; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | .cardsSelectedCard{ 12 | box-shadow: 10px 10px 30px 1px blue; 13 | } 14 | 15 | .cardsCard:hover:not(.cardsUncovered) { 16 | box-shadow: 10px 10px 30px 1px yellow; 17 | } 18 | 19 | .cardsUncovered{ 20 | cursor: default; 21 | } 22 | 23 | .cardsOutputMessage{ 24 | background-color: green; 25 | } 26 | 27 | .cardsFakeLink{ 28 | color: blue; 29 | cursor: pointer; 30 | text-decoration: underline; 31 | } -------------------------------------------------------------------------------- /les05/games/public/games/cards/img/2_of_spades.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/2_of_spades.png -------------------------------------------------------------------------------- /les05/games/public/games/cards/img/black_joker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/black_joker.png -------------------------------------------------------------------------------- /les05/games/public/games/cards/img/cover_card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les05/games/public/games/cards/img/cover_card.jpg -------------------------------------------------------------------------------- /les05/games/public/games/silly/silly.css: -------------------------------------------------------------------------------- 1 | .sillyBtn{ 2 | border: 2px solid rgb(30,30,30); 3 | border-radius: 5px; 4 | background-color: rgb(207, 205, 200); 5 | color: rgb(71, 53, 25); 6 | margin: 5px; 7 | padding: 3px; 8 | display: inline-block; 9 | text-align: center; 10 | cursor: pointer; 11 | } 12 | -------------------------------------------------------------------------------- /les05/games/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Games 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /les05/games/public/style.css: -------------------------------------------------------------------------------- 1 | @import url("games/cards/cards.css"); 2 | @import url("games/silly/silly.css"); 3 | 4 | 5 | html, body { 6 | margin: 0px; 7 | background-color: rgb(243, 239, 231); 8 | color: rgb(71, 53, 25); 9 | font-size: 1.2em; 10 | position: relative; 11 | } 12 | 13 | #root{ 14 | margin: 30px; 15 | } -------------------------------------------------------------------------------- /les05/games/src/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from 'react-router-dom' 3 | 4 | //Note the use of "alias" in webpack.config.js to handle "~" 5 | import {MyHomeLink} from "~/my_home_link"; 6 | 7 | 8 | export const Home = () => { 9 | 10 | return( 11 |
12 |

Which Game Do You Want to Play?

13 | 14 |
    15 |
  • Silly
  • 16 |
  • Cards
  • 17 |
18 |
19 | ); 20 | }; -------------------------------------------------------------------------------- /les05/games/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom' 4 | 5 | import {Home} from "./home"; 6 | import {Silly} from "./games/silly/silly"; 7 | import {Cards} from "./games/cards/cards"; 8 | import {NotFound} from "./not_found"; 9 | 10 | 11 | const App = () => { 12 | 13 | return ( 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les05/games/src/my_home_link.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | export const MyHomeLink = () =>{ 5 | 6 | return( 7 |
8 | Link back to Home. 9 |
10 | ); 11 | }; -------------------------------------------------------------------------------- /les05/games/src/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const NotFound = () =>{ 5 | 6 | return( 7 |
8 |

NOT FOUND: 404

9 |

10 | ERROR: the page you requested in not available. 11 |

12 | 13 |
14 | ); 15 | 16 | }; -------------------------------------------------------------------------------- /les05/games/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | devServer: { 12 | contentBase: './public' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: "babel-loader" 21 | } 22 | } 23 | ] 24 | }, 25 | resolve: { 26 | extensions: ['.js', '.jsx'], 27 | alias: { 28 | ['~']: path.resolve(__dirname + '/src') 29 | } 30 | }, 31 | optimization: { 32 | minimize: true, 33 | minimizer: [new TerserPlugin({ 34 | extractComments: false, 35 | })] 36 | } 37 | 38 | }; -------------------------------------------------------------------------------- /les05/spa-routing-index-404/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/react" 6 | ] 7 | }, 8 | "dependencies": { 9 | "react": "16.12.0", 10 | "react-dom": "16.12.0", 11 | "react-router-dom": "5.2.0" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "7.12.10", 15 | "@babel/preset-react": "7.12.10", 16 | "babel-loader": "8.2.2", 17 | "webpack": "5.11.1", 18 | "webpack-cli": "4.3.1", 19 | "webpack-dev-server": "3.11.1" 20 | }, 21 | "engines": { 22 | "node": "^14.0.0" 23 | }, 24 | "license": "LGPL-3.0", 25 | "main": "index.js", 26 | "name": "spa-routing-index-404", 27 | "scripts": { 28 | "build": "webpack --mode production", 29 | "dev": "yarn build && yarn start", 30 | "start": "node src/server/server.js" 31 | }, 32 | "version": "1.0.0" 33 | } -------------------------------------------------------------------------------- /les05/spa-routing-index-404/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Router Examples 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /les05/spa-routing-index-404/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 30px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /les05/spa-routing-index-404/src/client/first.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const First = () =>{ 5 | 6 | return( 7 |
8 |

First Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-index-404/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom' 4 | 5 | import {Home} from "./home"; 6 | import {First} from "./first"; 7 | import {Second} from "./second"; 8 | import {NotFound} from "./not_found"; 9 | 10 | const App = () => { 11 | 12 | return ( 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les05/spa-routing-index-404/src/client/my_home_link.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | export const MyHomeLink = () =>{ 5 | 6 | return( 7 |
8 | Link back to Home. 9 |
10 | ); 11 | }; -------------------------------------------------------------------------------- /les05/spa-routing-index-404/src/client/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const NotFound = () =>{ 5 | 6 | return( 7 |
8 |

NOT FOUND: 404

9 |

10 | ERROR: the page you requested in not available. 11 |

12 | 13 |
14 | ); 15 | 16 | }; -------------------------------------------------------------------------------- /les05/spa-routing-index-404/src/client/second.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const Second = () =>{ 5 | 6 | return( 7 |
8 |

Second Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-index-404/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les05/spa-routing-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "description": "", 4 | "devDependencies": { 5 | "webpack": "5.11.1", 6 | "webpack-cli": "4.3.1", 7 | "webpack-dev-server": "3.11.1" 8 | }, 9 | "engines": { 10 | "node": "^14.0.0" 11 | }, 12 | "keywords": [], 13 | "license": "LGPL-3.0", 14 | "main": "index.js", 15 | "name": "spa-routing-js", 16 | "scripts": { 17 | "build": "webpack --mode production", 18 | "dev": "webpack serve --open --mode development", 19 | "test": "echo \"Error: no test specified\" && exit 1" 20 | }, 21 | "version": "1.0.0" 22 | } -------------------------------------------------------------------------------- /les05/spa-routing-js/public/mylib.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .spaPage{ 4 | display: none 5 | } 6 | 7 | .spaLink{ 8 | color: blue; 9 | cursor: pointer; 10 | text-decoration: underline; 11 | } -------------------------------------------------------------------------------- /les05/spa-routing-js/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 30px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /les05/spa-routing-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | devServer: { 12 | contentBase: './public' 13 | }, 14 | optimization: { 15 | minimize: true, 16 | minimizer: [new TerserPlugin({ 17 | extractComments: false, 18 | })] 19 | } 20 | }; -------------------------------------------------------------------------------- /les05/spa-routing-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Router Examples 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /les05/spa-routing-react/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 30px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /les05/spa-routing-react/src/first.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const First = () =>{ 5 | 6 | return( 7 |
8 |

First Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-react/src/my_home_link.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | export const MyHomeLink = () =>{ 5 | 6 | return( 7 |
8 | Link back to Home. 9 |
10 | ); 11 | }; -------------------------------------------------------------------------------- /les05/spa-routing-react/src/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const NotFound = () =>{ 5 | 6 | return( 7 |
8 |

NOT FOUND: 404

9 |

10 | ERROR: the page you requested in not available. 11 |

12 | 13 |
14 | ); 15 | 16 | }; -------------------------------------------------------------------------------- /les05/spa-routing-react/src/second.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const Second = () =>{ 5 | 6 | return( 7 |
8 |

Second Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-react/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les05/spa-routing-react/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les05/spa-routing-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "presets": [ 5 | "@babel/env", 6 | "@babel/react" 7 | ] 8 | }, 9 | "dependencies": { 10 | "react": "16.12.0", 11 | "react-dom": "16.12.0", 12 | "react-router-dom": "5.2.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/cli": "7.12.10", 16 | "@babel/core": "7.12.10", 17 | "@babel/preset-env": "7.12.11", 18 | "@babel/preset-react": "7.12.10", 19 | "babel-loader": "8.2.2", 20 | "webpack": "5.11.1", 21 | "webpack-cli": "4.3.1", 22 | "webpack-dev-server": "3.11.1" 23 | }, 24 | "engines": { 25 | "node": "^14.0.0" 26 | }, 27 | "license": "LGPL-3.0", 28 | "main": "index.js", 29 | "name": "spa-routing-ssr", 30 | "scripts": { 31 | "build": "yarn build:client && yarn build:server", 32 | "build:client": "webpack --mode production", 33 | "build:server": "babel ./src -d build", 34 | "dev": "yarn build && yarn start", 35 | "start": "node build/server/server.js" 36 | }, 37 | "version": "1.0.0" 38 | } -------------------------------------------------------------------------------- /les05/spa-routing-ssr/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 30px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Switch, Route} from 'react-router-dom' 3 | 4 | import {Home} from "./home"; 5 | import {First} from "./first"; 6 | import {Second} from "./second"; 7 | import {NotFound} from "./not_found"; 8 | 9 | 10 | export const App = () => { 11 | 12 | return ( 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | ); 22 | 23 | }; -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/first.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const First = () =>{ 5 | 6 | return( 7 |
8 |

First Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter} from 'react-router-dom' 4 | 5 | import {App} from "./app" 6 | 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | , document.getElementById("root")); -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/my_home_link.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | 4 | export const MyHomeLink = () =>{ 5 | 6 | return( 7 |
8 | Link back to Home. 9 |
10 | ); 11 | }; -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const NotFound = () =>{ 5 | 6 | return( 7 |
8 |

NOT FOUND: 404

9 |

10 | ERROR: the page you requested in not available. 11 |

12 | 13 |
14 | ); 15 | 16 | }; -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/client/second.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {MyHomeLink} from "./my_home_link"; 3 | 4 | export const Second = () =>{ 5 | 6 | return( 7 |
8 |

Second Page

9 |

10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed 11 | do eiusmod tempor incididunt ut labore et dolore magna aliqua. 12 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 13 | nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 15 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 16 | deserunt mollit anim id est laborum. 17 |

18 | 19 |
20 | ); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/server/renderer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {renderToString} from 'react-dom/server' 3 | import {StaticRouter} from 'react-router-dom' 4 | import {App} from "../client/app" 5 | 6 | 7 | export const serverSideRender = (url) => { 8 | 9 | /* 10 | Here must use StaticRouter instead of BrowserRouter, as we 11 | are not in a browser 12 | */ 13 | const content = renderToString( 14 | 15 | 16 | 17 | ); 18 | 19 | const html = ` 20 | 21 | 22 | 23 | 24 | 25 | React Router Examples With Server-Side-Rendering (SSR) 26 | 27 | 28 | 29 |
${content}
30 | 31 | 32 | 33 | 34 | 35 | `; 36 | 37 | return html; 38 | }; -------------------------------------------------------------------------------- /les05/spa-routing-ssr/src/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const path = require('path'); 4 | const app = express(); 5 | 6 | const Renderer = require('./renderer'); 7 | 8 | 9 | app.get("/", (req, res) => { 10 | res.send(Renderer.serverSideRender(req.url)); 11 | }); 12 | 13 | app.use(express.static('public')); 14 | 15 | 16 | app.use((req, res) => { 17 | res.send(Renderer.serverSideRender(req.url)); 18 | }); 19 | 20 | const port = process.env.PORT || 8080; 21 | 22 | app.listen(port, () => { 23 | console.log('Started server on port ' + port); 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /les05/spa-routing-ssr/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les06/weather/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Weather App 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /les06/weather/public/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: rgb(230, 242, 255); 3 | } 4 | 5 | #root{ 6 | margin: 30px; 7 | } 8 | 9 | #weatherTable{ 10 | border-collapse: collapse; 11 | width: 50%; 12 | } 13 | #weatherTable td, #weatherTable th { 14 | border: 1px solid rgb(0, 64, 128); 15 | padding: 6px; 16 | } 17 | 18 | #weatherTable tr:nth-child(even){background-color: rgb(77, 166, 255);} 19 | 20 | #weatherTable tr:hover {background-color: #ddd;} 21 | 22 | #weatherTable th { 23 | padding-top: 12px; 24 | padding-bottom: 12px; 25 | text-align: left; 26 | background-color: rgb(51, 153, 255); 27 | color: white; 28 | } 29 | .mainCities{ 30 | font-size: 20px; 31 | text-decoration-color: rgb(0, 64, 128); 32 | } 33 | 34 | .form-control{ 35 | width: 44%; 36 | height: 25px; 37 | } 38 | 39 | .submitBtn{ 40 | height: 30px; 41 | margin-left: 10px; 42 | font-size: 20px; 43 | cursor: pointer; 44 | } 45 | 46 | .forecastMsg{ 47 | margin-top: 15px; 48 | margin-bottom: 15px; 49 | font-size: 20px; 50 | } 51 | -------------------------------------------------------------------------------- /les06/weather/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les06/weather/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Book App 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #e6ccb3; 3 | } 4 | 5 | #root { 6 | margin: 30px; 7 | } 8 | 9 | .btn { 10 | background-color: #cb9767; 11 | margin: 2px; 12 | border: none; 13 | border-radius: 20%; 14 | color: #f8f2ec; 15 | padding: 12px 16px; 16 | font-size: 16px; 17 | cursor: pointer; 18 | } 19 | 20 | .btn:hover { 21 | background-color: #c48a54; 22 | } 23 | 24 | .allBooks { 25 | width: 60%; 26 | margin-top: 20px; 27 | padding: 0px; 28 | } 29 | 30 | .oneBook { 31 | padding: 4px; 32 | } 33 | 34 | .bookInput { 35 | width: 50%; 36 | padding: 5px; 37 | display: block; 38 | margin-bottom: 15px; 39 | } 40 | 41 | .inputTitle { 42 | color: #85582e; 43 | display: inline; 44 | } -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/src/client/create.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Book from "./book"; 3 | 4 | export class Create extends React.Component{ 5 | 6 | constructor(props){ 7 | super(props); 8 | } 9 | 10 | onOk = async (author, title, year, bookId) => { 11 | 12 | const url = "http://localhost:8081/books"; 13 | 14 | //note: here bookId is ignored 15 | const payload = {author, title, year}; 16 | 17 | let response; 18 | 19 | try { 20 | response = await fetch(url, { 21 | method: "post", 22 | headers: { 23 | 'Content-Type': 'application/json' 24 | }, 25 | body: JSON.stringify(payload) 26 | }); 27 | } catch (err) { 28 | return false; 29 | } 30 | 31 | return response.status === 201; 32 | }; 33 | 34 | 35 | render(){ 36 | 37 | return( 38 |
39 |

Create a New Book

40 | 47 |
48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom' 4 | 5 | import {Home} from "./home"; 6 | import {Create} from "./create" 7 | import {Edit} from "./edit"; 8 | import {NotFound} from "./not_found"; 9 | 10 | const App = () => { 11 | 12 | return ( 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/src/client/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const NotFound = () =>{ 4 | 5 | return( 6 |
7 |

NOT FOUND: 404

8 |

9 | ERROR: the page you requested in not available. 10 |

11 |
12 | ); 13 | 14 | }; -------------------------------------------------------------------------------- /les08/server_client_separated/frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | libraryTarget: 'var', 11 | library: 'EntryPoint' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx$/, 17 | exclude: /node_modules/, 18 | use: { 19 | loader: "babel-loader" 20 | } 21 | } 22 | ] 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.jsx'] 26 | }, 27 | devServer: { 28 | contentBase: './public' 29 | }, 30 | optimization: { 31 | minimize: true, 32 | minimizer: [new TerserPlugin({ 33 | extractComments: false, 34 | })] 35 | } 36 | }; -------------------------------------------------------------------------------- /les08/server_client_separated/rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "dependencies": { 4 | "cors": "2.8.5", 5 | "express": "4.17.1" 6 | }, 7 | "description": "", 8 | "devDependencies": { 9 | "jest": "24.9.0", 10 | "supertest": "6.0.1" 11 | }, 12 | "engines": { 13 | "node": "^14.0.0" 14 | }, 15 | "jest": { 16 | "collectCoverageFrom": [ 17 | "src/**/*.(js|jsx)" 18 | ], 19 | "testEnvironment": "node", 20 | "testRegex": "tests/.*-test\\.(js|jsx)$" 21 | }, 22 | "keywords": [], 23 | "license": "LGPL-3.0", 24 | "main": "src/server/server.js", 25 | "name": "rest", 26 | "scripts": { 27 | "start": "node src/server/server.js", 28 | "test": "jest --coverage" 29 | }, 30 | "version": "1.0.0" 31 | } -------------------------------------------------------------------------------- /les08/server_client_separated/rest/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | const repository = require("./repository"); 3 | 4 | const port = process.env.PORT || 8081; 5 | 6 | 7 | app.listen(port, () => { 8 | repository.initWithSomeBooks(); 9 | console.log('Started RESTful API on port ' + port); 10 | }); 11 | 12 | -------------------------------------------------------------------------------- /les08/server_client_together/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Book App 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /les08/server_client_together/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #e6ccb3; 3 | } 4 | 5 | #root { 6 | margin: 30px; 7 | } 8 | 9 | .btn { 10 | background-color: #cb9767; 11 | margin: 2px; 12 | border: none; 13 | border-radius: 20%; 14 | color: #f8f2ec; 15 | padding: 12px 16px; 16 | font-size: 16px; 17 | cursor: pointer; 18 | } 19 | 20 | .btn:hover { 21 | background-color: #c48a54; 22 | } 23 | 24 | .allBooks { 25 | width: 60%; 26 | margin-top: 20px; 27 | padding: 0px; 28 | } 29 | 30 | .oneBook { 31 | padding: 4px; 32 | } 33 | 34 | .bookInput { 35 | width: 50%; 36 | padding: 5px; 37 | display: block; 38 | margin-bottom: 15px; 39 | } 40 | 41 | .inputTitle { 42 | color: #85582e; 43 | display: inline; 44 | } -------------------------------------------------------------------------------- /les08/server_client_together/src/client/create.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Book from "./book"; 3 | 4 | export class Create extends React.Component{ 5 | 6 | constructor(props){ 7 | super(props); 8 | } 9 | 10 | onOk = async (author, title, year, bookId) => { 11 | 12 | const url = "/api/books"; 13 | 14 | //note: here bookId is ignored 15 | const payload = {author, title, year}; 16 | 17 | let response; 18 | 19 | try { 20 | response = await fetch(url, { 21 | method: "post", 22 | headers: { 23 | 'Content-Type': 'application/json' 24 | }, 25 | body: JSON.stringify(payload) 26 | }); 27 | } catch (err) { 28 | return false; 29 | } 30 | 31 | return response.status === 201; 32 | }; 33 | 34 | 35 | render(){ 36 | 37 | return( 38 |
39 |

Create a New Book

40 | 47 |
48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /les08/server_client_together/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import {BrowserRouter, Switch, Route} from 'react-router-dom'; 4 | 5 | import {Home} from "./home"; 6 | import {Create} from "./create" 7 | import {Edit} from "./edit"; 8 | import {NotFound} from "./not_found"; 9 | 10 | const App = () => { 11 | 12 | return ( 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | }; 25 | 26 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les08/server_client_together/src/client/not_found.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const NotFound = () =>{ 4 | 5 | return( 6 |
7 |

NOT FOUND: 404

8 |

9 | ERROR: the page you requested in not available. 10 |

11 |
12 | ); 13 | 14 | }; -------------------------------------------------------------------------------- /les08/server_client_together/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | const repository = require("./repository"); 3 | 4 | 5 | 6 | const port = process.env.PORT || 8080; 7 | 8 | app.listen(port, () => { 9 | repository.initWithSomeBooks(); 10 | console.log('Started server on port ' + port); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /les08/server_client_together/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les08/server_client_together/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | libraryTarget: 'var', 11 | library: 'EntryPoint' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx$/, 17 | exclude: /node_modules/, 18 | use: { 19 | loader: "babel-loader" 20 | } 21 | } 22 | ] 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.jsx'] 26 | }, 27 | devServer: { 28 | contentBase: './public' 29 | }, 30 | optimization: { 31 | minimize: true, 32 | minimizer: [new TerserPlugin({ 33 | extractComments: false, 34 | })] 35 | } 36 | }; -------------------------------------------------------------------------------- /les09/authentication/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SPA-Bank 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /les09/authentication/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started server on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /les09/authentication/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | optimization: { 26 | minimize: true, 27 | minimizer: [new TerserPlugin({ 28 | extractComments: false, 29 | })] 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /les10/csrf/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcuri82/web_development_and_api_design/41abcbdfc34216e4c6a59b249a4e6fa77c733322/les10/csrf/cat.jpg -------------------------------------------------------------------------------- /les10/csrf/index-ajax-post-json.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Cute Cat!!! 10 | 11 | 12 | 13 |

What a Cute Cat!!!

14 | 15 | 16 | 17 | 18 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /les10/csrf/index-form-post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Cute Cat!!! 10 | 11 | 12 | 13 |

What a Cute Cat!!!

14 | 15 | 16 | 17 | 18 | 19 |
25 | 26 | 27 |
28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /les10/escape/escaped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | How to represent the symbols of a tag with attribute without 6 | getting them interpreted as HTML tags? 7 | 8 |
9 | 10 | For example: 11 | 12 |
13 | 14 | Foo 15 | 16 |
17 | 18 | vs. 19 | 20 |
21 | 22 | <a href="foo">Foo</a> 23 | 24 | 25 |
26 |
27 |
28 |
29 | 30 | However, what to escape depends on the context: 31 |
32 | 33 |
34 | "<p>" 35 |
36 | 37 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /les10/xss/react-href/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "babel": { 4 | "plugins": [ 5 | "@babel/plugin-proposal-class-properties" 6 | ], 7 | "presets": [ 8 | "@babel/react" 9 | ] 10 | }, 11 | "dependencies": { 12 | "react": "16.12.0", 13 | "react-dom": "16.12.0" 14 | }, 15 | "description": "", 16 | "devDependencies": { 17 | "@babel/core": "7.12.10", 18 | "@babel/plugin-proposal-class-properties": "7.12.1", 19 | "@babel/preset-react": "7.12.10", 20 | "babel-loader": "8.2.2", 21 | "webpack": "5.11.1", 22 | "webpack-cli": "4.3.1", 23 | "webpack-dev-server": "3.11.1" 24 | }, 25 | "engines": { 26 | "node": "^14.0.0" 27 | }, 28 | "keywords": [], 29 | "license": "LGPL-3.0", 30 | "main": "index.js", 31 | "name": "xss-react-href", 32 | "scripts": { 33 | "build": "webpack --mode production", 34 | "dev": "webpack serve --open --mode development", 35 | "test": "echo \"Error: no test specified\" && exit 1" 36 | }, 37 | "version": "1.0.0" 38 | } -------------------------------------------------------------------------------- /les10/xss/react-href/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | XSS in React 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /les10/xss/react-href/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputLink{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /les10/xss/react-href/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | devServer: { 26 | contentBase: './public' 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; -------------------------------------------------------------------------------- /les11/chat/ajax/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AJAX-based Chat 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /les11/chat/ajax/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputName{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /les11/chat/ajax/src/server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const app = express(); 4 | 5 | //to handle JSON payloads 6 | app.use(bodyParser.json()); 7 | 8 | //needed to server static files, like HTML, CSS and JS. 9 | app.use(express.static('public')); 10 | 11 | let counter = 0; 12 | 13 | const messages = []; 14 | 15 | 16 | app.get('/api/messages', (req, res) => { 17 | 18 | const since = req.query["since"]; 19 | 20 | const data = messages; 21 | 22 | if (since !== undefined && since !== null) { 23 | res.json(data.filter(m => m.id > since)); 24 | } else { 25 | res.json(data); 26 | } 27 | }); 28 | 29 | 30 | 31 | app.post('/api/messages', (req, res) => { 32 | 33 | const dto = req.body; 34 | 35 | const id = counter++; 36 | 37 | messages.push({id:id, author: dto.author, text: dto.text}); 38 | 39 | res.status(201); //created 40 | res.send(); 41 | }); 42 | 43 | 44 | module.exports = app; -------------------------------------------------------------------------------- /les11/chat/ajax/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started Chat on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /les11/chat/ajax/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | optimization: { 26 | minimize: true, 27 | minimizer: [new TerserPlugin({ 28 | extractComments: false, 29 | })] 30 | } 31 | }; -------------------------------------------------------------------------------- /les11/chat/server-side/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrea Arcuri", 3 | "dependencies": { 4 | "cors": "2.8.5", 5 | "express": "4.17.1" 6 | }, 7 | "description": "", 8 | "engines": { 9 | "node": "^14.0.0" 10 | }, 11 | "keywords": [], 12 | "license": "LGPL-3.0", 13 | "main": "index.js", 14 | "name": "server-side", 15 | "scripts": { 16 | "dev": "npm run start", 17 | "start": "node src/server/server.js", 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "version": "1.0.0" 21 | } -------------------------------------------------------------------------------- /les11/chat/server-side/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputName{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /les11/chat/server-side/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started Chat on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket-based Chat 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputName{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/src/server/server.js: -------------------------------------------------------------------------------- 1 | const app = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | 6 | app.listen(port, () => { 7 | console.log('Started Chat on port ' + port); 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/tests/server/app-test.js: -------------------------------------------------------------------------------- 1 | const app = require('../../src/server/app'); 2 | 3 | test("TODO", async () =>{ 4 | //TODO 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /les11/chat/websocket-full/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | optimization: { 26 | minimize: true, 27 | minimizer: [new TerserPlugin({ 28 | extractComments: false, 29 | })] 30 | } 31 | }; -------------------------------------------------------------------------------- /les11/chat/websocket-rest/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket-based Chat 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /les11/chat/websocket-rest/public/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0px; 3 | background-color: lightgrey; 4 | padding-left: 20px; 5 | } 6 | 7 | .inputName{ 8 | display: inline-block; 9 | margin-left: 10px; 10 | } 11 | 12 | .btn{ 13 | border: 2px solid black; 14 | width: 45px; 15 | text-align: center; 16 | cursor: pointer; 17 | } 18 | -------------------------------------------------------------------------------- /les11/chat/websocket-rest/src/client/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import {Home} from './home'; 5 | 6 | ReactDOM.render(, document.getElementById("root")); -------------------------------------------------------------------------------- /les11/chat/websocket-rest/src/server/server.js: -------------------------------------------------------------------------------- 1 | const {app} = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | 6 | app.listen(port, () => { 7 | console.log('Started Chat on port ' + port); 8 | }); 9 | 10 | -------------------------------------------------------------------------------- /les11/chat/websocket-rest/tests/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /les11/chat/websocket-rest/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public'), 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'] 24 | }, 25 | optimization: { 26 | minimize: true, 27 | minimizer: [new TerserPlugin({ 28 | extractComments: false, 29 | })] 30 | } 31 | }; -------------------------------------------------------------------------------- /les12/connect4-v2/.gitignore: -------------------------------------------------------------------------------- 1 | # Needed for running: 2 | # heroku builds:create -a 3 | # 4 | # Remember you first need to install the "builds" plugin: 5 | # heroku plugins:install heroku-builds 6 | 7 | node_modules 8 | coverage 9 | .idea 10 | .git 11 | 12 | -------------------------------------------------------------------------------- /les12/connect4-v2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Connect Four Game 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/client/connect4/ai-match.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Board} from "./board"; 3 | import {OpponentAI} from "./opponent-ai"; 4 | 5 | 6 | export class AiMatch extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | 11 | this.refToBoard = React.createRef(); 12 | this.opponent = new OpponentAI(); 13 | } 14 | 15 | 16 | render() { 17 | 18 | return ( 19 |
20 | 23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/client/connect4/opponent-ai.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class OpponentAI{ 4 | 5 | 6 | playNext(lastInsertedColumn, board){ 7 | 8 | const state = board.getBoardState(); 9 | 10 | if(state.isGameFinished()){ 11 | //nothing to do 12 | return; 13 | } 14 | 15 | /* 16 | Just simulate a delay for the AI, instead of doing 17 | an insertion immediately. 18 | */ 19 | const delay = 1000 * Math.random(); 20 | setTimeout(() => this.handleNextMove(board), delay); 21 | } 22 | 23 | /* 24 | WARNING: 25 | what happens if a user start a new game before the callback 26 | of the setTimeout is executed? 27 | We would have to handle such case. 28 | */ 29 | 30 | handleNextMove(board){ 31 | 32 | /* 33 | For simplicity, the "AI" will just choose at random 34 | among the available columns 35 | */ 36 | const state = board.getBoardState(); 37 | 38 | const options = state.freeColumns(); 39 | const chosen = options[Math.floor(options.length * Math.random())]; 40 | 41 | const copy = state.copy(); 42 | copy.selectColumn(chosen); 43 | 44 | board.setBoardState(copy); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/client/home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | export class Home extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | const userId = this.props.userId; 11 | 12 | return ( 13 |
14 |
15 |

Connect Four Game

16 | 17 |

18 | Welcome to the Connect Four Game! In this game, you alternate with 19 | an opponent in adding coins (X or O) into a 6x7 grid. The goal of 20 | the game is to align 4 of your coins, either horizontally, 21 | vertically or diagonally. 22 |

23 |

24 | You can play either against an AI, or against other players online. 25 | However, to play online, you need to create an account and be logged 26 | in. 27 |

28 |
29 | 30 |
31 | 32 | AI Match 33 | 34 | {userId ? ( 35 | 36 | Online Match 37 | 38 | ) : ( 39 |
Online Match
40 | )} 41 |
42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/db/users.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Here we "simulate" a database with in-memory Map. 4 | Furthermore, we do not deal with the "proper" handling of 5 | passwords. Passwords should NEVER be saved in plain text, 6 | but rather hashed with secure algorithms like BCrypt. 7 | */ 8 | 9 | const users = new Map(); 10 | 11 | 12 | function getUser(id){ 13 | 14 | return users.get(id); 15 | } 16 | 17 | function verifyUser(id, password){ 18 | 19 | const user = getUser(id); 20 | 21 | if(!user){ 22 | return false; 23 | } 24 | 25 | return user.password === password; 26 | } 27 | 28 | function createUser(id, password){ 29 | 30 | if(getUser(id)){ 31 | return false; 32 | } 33 | 34 | const user = { 35 | id: id, 36 | password: password 37 | }; 38 | 39 | users.set(id, user); 40 | return true; 41 | } 42 | 43 | function resetAllUsers(){ 44 | users.clear(); 45 | } 46 | 47 | 48 | module.exports = {getUser, verifyUser, createUser, resetAllUsers}; 49 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/online/active-players.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | For each active player that is currently online, we need to keep 4 | track of their WS sockets. 5 | */ 6 | 7 | const socketToUser = new Map(); 8 | 9 | const userToSocket = new Map(); 10 | 11 | 12 | function registerSocket(socket, userId){ 13 | 14 | socketToUser.set(socket.id, userId); 15 | userToSocket.set(userId, socket); 16 | } 17 | 18 | function removeSocket(socketId){ 19 | 20 | const userId = socketToUser.get(socketId); 21 | socketToUser.delete(socketId); 22 | userToSocket.delete(userId); 23 | } 24 | 25 | 26 | function removeUser(userId){ 27 | 28 | const socketId = userToSocket.get(userId).id; 29 | userToSocket.delete(userId); 30 | socketToUser.delete(socketId); 31 | } 32 | 33 | 34 | function isActive(userId){ 35 | return userToSocket.has(userId); 36 | } 37 | 38 | function getSocket(userId){ 39 | return userToSocket.get(userId); 40 | } 41 | 42 | function getUser(socketId){ 43 | return socketToUser.get(socketId); 44 | } 45 | 46 | module.exports = {registerSocket, removeSocket, removeUser, isActive, getSocket, getUser}; -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/online/ongoing-matches.js: -------------------------------------------------------------------------------- 1 | const Match = require('../game/match'); 2 | 3 | 4 | /* 5 | Map from user id to ongoing match 6 | */ 7 | const userIdToMatch = new Map(); 8 | 9 | /* 10 | Map from match id to ongoing match 11 | */ 12 | const matchIdToMatch = new Map(); 13 | 14 | 15 | function startMatch(firstId, secondId){ 16 | 17 | const match = new Match(firstId, secondId, deleteMatch); 18 | 19 | console.log("Starting a new match: '"+firstId+"' vs. '"+secondId+"', id = " + match.matchId); 20 | 21 | userIdToMatch.set(firstId, match); 22 | userIdToMatch.set(secondId, match); 23 | matchIdToMatch.set(match.matchId, match); 24 | 25 | match.start(); 26 | } 27 | 28 | function deleteMatch(matchId){ 29 | const match = matchIdToMatch.get(matchId); 30 | if(!match){ 31 | return; 32 | } 33 | 34 | match.playerIds.forEach(id => userIdToMatch.delete(id)); 35 | matchIdToMatch.delete(match.matchId); 36 | } 37 | 38 | function forfeit(userId){ 39 | 40 | const match = userIdToMatch.get(userId); 41 | if(!match){ 42 | return; 43 | } 44 | 45 | match.playerIds.forEach(id => userIdToMatch.delete(id)); 46 | matchIdToMatch.delete(match.matchId); 47 | 48 | match.sendForfeit(userId); 49 | } 50 | 51 | 52 | module.exports = {startMatch, forfeit}; -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/online/player-queue.js: -------------------------------------------------------------------------------- 1 | /* 2 | When a user wants to play, it needs an opponent. 3 | So, we need at least 2 players. 4 | A user has to wait until an opponent is available. 5 | 6 | If we always match a user against the first available opponent, then 7 | we would not need a queue, as such queue will at most contain a single user 8 | at any point in time. 9 | 10 | We could be more sophisticated. For example, we could try to match players 11 | with same skills (computed based on number of past victories). 12 | And, so, let some users wait on the queue for some seconds until a better opponent appears, 13 | even if the queue is not empty. 14 | However, to keep it simple, here we match against the first available opponent. 15 | */ 16 | 17 | const queue = []; 18 | 19 | 20 | function addUser(id){ 21 | 22 | if(queue.includes(id)){ 23 | return false; 24 | } 25 | 26 | queue.push(id); 27 | return true; 28 | } 29 | 30 | 31 | function size(){ 32 | return queue.length; 33 | } 34 | 35 | function hasUser(id){ 36 | return queue.includes(id); 37 | } 38 | 39 | function takeUser(){ 40 | 41 | if(queue.length === 0){ 42 | return null; 43 | } 44 | 45 | return queue.shift(); 46 | } 47 | 48 | 49 | module.exports = {addUser, size, takeUser, hasUser}; 50 | 51 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/server.js: -------------------------------------------------------------------------------- 1 | const {app} = require("./app"); 2 | 3 | const port = process.env.PORT || 8080; 4 | 5 | app.listen(port, () => { 6 | console.log('Started NodeJS server on port ' + port); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /les12/connect4-v2/src/server/ws/tokens.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | 3 | /* 4 | Map from random tokens to userId 5 | */ 6 | const tokens = new Map(); 7 | 8 | 9 | const randomId = () => { 10 | /* 11 | A byte is composed of 8 bits. 12 | 10 bytes are 80 bits. 13 | There are 2^80 possible numbers, which is a huge amount. 14 | We want to send such bits as a string. 15 | We do it in "hex" format. 16 | A hex is representing 16 values, 0-9 and A-F, using 4 bits (2^4=16). 17 | So, we need 2 hex characters per byte. 18 | This means that 10 random bytes can be encoded with a a string of 19 | length 20 containing only 0-9 and A-F symbols. 20 | */ 21 | 22 | return crypto.randomBytes(10).toString('hex'); 23 | }; 24 | 25 | 26 | /* 27 | For a given user, associate a new random token. 28 | */ 29 | const createToken = (userId) =>{ 30 | 31 | const t = randomId(); 32 | 33 | tokens.set(t, userId); 34 | 35 | return t; 36 | }; 37 | 38 | 39 | /* 40 | A token can be used only once. 41 | If a user needs to re-authenticate a WebSocket, 42 | a new token needs to be generated. 43 | */ 44 | const consumeToken = (t) => { 45 | 46 | const userId = tokens.get(t); 47 | 48 | tokens.delete(t); 49 | 50 | return userId; 51 | }; 52 | 53 | 54 | module.exports = {createToken, consumeToken}; -------------------------------------------------------------------------------- /les12/connect4-v2/src/shared/utils.js: -------------------------------------------------------------------------------- 1 | 2 | function createMatrix (rows, columns, value){ 3 | return Array(rows).fill(null).map(() => Array(columns).fill(value)); 4 | } 5 | 6 | 7 | function cloneMatrix(matrix){ 8 | if(!matrix){ 9 | return null; 10 | } 11 | return matrix.map(e => e.slice()); 12 | } 13 | 14 | 15 | module.exports = {createMatrix, cloneMatrix}; -------------------------------------------------------------------------------- /les12/connect4-v2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | 4 | 5 | module.exports = { 6 | entry: './src/client/index.jsx', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: "babel-loader" 18 | } 19 | } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['.js', '.jsx'], 24 | alias: { 25 | ['~']: path.resolve(__dirname + '/src') 26 | } 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new TerserPlugin({ 31 | extractComments: false, 32 | })] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /shared/jest-setup.js: -------------------------------------------------------------------------------- 1 | // Origin: shared/jest-setup.js 2 | 3 | const {configure } = require('enzyme'); 4 | const jsdom = require('jsdom'); 5 | const Adapter = require('enzyme-adapter-react-16'); 6 | 7 | 8 | /* 9 | To be able to use Enzyme with Jest, we need this file, with this 10 | exact name. 11 | Here, we setup a virtual HTML page (on the server) in which our components 12 | will be "mounted" with Enzyme. 13 | JSDOM is used for such virtual pages on the server. 14 | Note the overriding of some global variables to point to JSDOM. 15 | */ 16 | 17 | export function setUpDomEnvironment(url) { 18 | const { JSDOM } = jsdom; 19 | const dom = new JSDOM('', {url: url}); 20 | const { window } = dom; 21 | 22 | global.window = window; 23 | global.document = window.document; 24 | global.navigator = {userAgent: 'node.js'}; 25 | copyProps(window, global); 26 | 27 | configure({ adapter: new Adapter() }); 28 | } 29 | 30 | function copyProps(src, target) { 31 | const props = Object.getOwnPropertyNames(src) 32 | .filter(prop => typeof target[prop] === 'undefined') 33 | .map(prop => Object.getOwnPropertyDescriptor(src, prop)); 34 | Object.defineProperties(target, props); 35 | } 36 | 37 | setUpDomEnvironment('http://localhost:80/'); 38 | 39 | 40 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TODO 7 | 8 | 9 | 10 | 11 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------