├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── fullstack-notes-application ├── api │ ├── .dockerignore │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .travis.yml │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── README.md │ ├── api │ │ ├── index.js │ │ └── routes │ │ │ └── notes.js │ ├── app.js │ ├── bin │ │ └── www │ ├── jest.config.js │ ├── knexfile.js │ ├── migrations │ │ └── 20200619195837_create_notes_table.js │ ├── models │ │ ├── Note.js │ │ └── index.js │ ├── nodemon.json │ ├── package.json │ ├── services │ │ ├── index.js │ │ ├── knex.js │ │ └── notes.js │ └── tests │ │ └── e2e │ │ └── api │ │ ├── index.test.js │ │ └── routes │ │ └── notes.test.js ├── client │ ├── .browserslistrc │ ├── .dockerignore │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitignore │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── README.md │ ├── babel.config.js │ ├── jest.config.js │ ├── nginx │ │ └── default.conf │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ └── Note.vue │ │ ├── libs │ │ │ └── axios.js │ │ ├── main.js │ │ ├── router │ │ │ └── index.js │ │ ├── store │ │ │ └── index.js │ │ └── views │ │ │ ├── Create.vue │ │ │ └── Home.vue │ ├── tests │ │ └── unit │ │ │ └── note.spec.js │ ├── vue.config.js │ └── webpack.config.js ├── docker-compose.yaml ├── k8s │ ├── api-deployment.yaml │ ├── database-persistent-volume-claim.yaml │ ├── postgres-cluster-ip-service.yaml │ └── postgres-deployment.yaml ├── nginx │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── development.conf │ └── production.conf └── postgres │ ├── Dockerfile │ ├── Dockerfile.dev │ └── docker-entrypoint-initdb.d │ └── notes.sql ├── hello-kube ├── .dockerignore ├── Dockerfile ├── index.html ├── package.json ├── public │ └── favicon.ico └── src │ ├── App.vue │ ├── assets │ └── kubernetes-handbook-github.webp │ ├── components │ └── HelloKube.vue │ ├── index.css │ └── main.js ├── kubernetes-handbook-github.png └── notes-api ├── api ├── .dockerignore ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── .travis.yml ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── api │ ├── index.js │ └── routes │ │ └── notes.js ├── app.js ├── bin │ └── www ├── jest.config.js ├── knexfile.js ├── migrations │ └── 20200619195837_create_notes_table.js ├── models │ ├── Note.js │ └── index.js ├── nodemon.json ├── package.json ├── services │ ├── index.js │ ├── knex.js │ └── notes.js └── tests │ └── e2e │ └── api │ ├── index.test.js │ └── routes │ └── notes.test.js ├── docker-compose.yaml └── postgres ├── Dockerfile ├── Dockerfile.dev └── docker-entrypoint-initdb.d └── notes.sql /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: buymeacoffee.com/farhanhasin 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node,vue,vuejs 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,vue,vuejs 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | ### Vue ### 115 | # gitignore template for Vue.js projects 116 | # 117 | # Recommended template: Node.gitignore 118 | 119 | # TODO: where does this rule come from? 120 | docs/_book 121 | 122 | # TODO: where does this rule come from? 123 | test/ 124 | 125 | ### Vuejs ### 126 | # Recommended template: Node.gitignore 127 | 128 | dist/ 129 | npm-debug.log 130 | yarn-error.log 131 | 132 | ### Vite ### 133 | node_modules 134 | .DS_Store 135 | dist 136 | *.local 137 | 138 | # End of https://www.toptal.com/developers/gitignore/api/node,vue,vuejs 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Handbook Projects 2 | 3 | ![...](./kubernetes-handbook-github.png) 4 | 5 | | :bell: NOTIFICATION | 6 | |:--------------------| 7 | | There are two branches in this repository. The [master](https://github.com/fhsinchy/kubernetes-handbook-projects/tree/master/) branch contains the starter projects and the [completed](https://github.com/fhsinchy/kubernetes-handbook-projects/tree/completed/) branch contains the completed projects. | 8 | 9 | This repository holds the code for my Kubernetes Handbook article on [__freeCodecamp__](https://freecodecamp.org). In the article the readers work through __three__ projects with increasing complexity. These projects are as follows: 10 | 11 | - hello-kube - A single container Vue application. 12 | - notes-api - A multi container Express API. 13 | - fullstack-notes-application - A full-stack CRUD application with [nginx](https://hub.docker.com/_/nginx/) as a reverse proxy. 14 | 15 | ## Prerequisites 16 | 17 | - Familiarity with JavaScript. 18 | - Familiarity with the Linux Terminal. 19 | - Familiarity with Docker (suggested read: [The Docker Handbook](https://www.freecodecamp.org/news/the-docker-handbook/)). 20 | 21 | It's fine if you haven't worked with JavaScript that much. Having a basic knowledge of executing scripts with `npm` will suffice. 22 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /fullstack-notes-application/api/.env.example: -------------------------------------------------------------------------------- 1 | DB_CONNECTION=pg 2 | DB_HOST=localhost 3 | DB_PORT=5432 4 | DB_USER= 5 | DB_DATABASE=notesdb 6 | DB_PASSWORD= -------------------------------------------------------------------------------- /fullstack-notes-application/api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["prettier"], 3 | extends: [ 4 | 'airbnb-base', 5 | 'plugin:prettier/recommended' 6 | ], 7 | env: { 8 | node: true, 9 | }, 10 | rules: { 11 | "global-require": 0 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | # Edit at https://www.gitignore.io/?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # rollup.js default build output 84 | dist/ 85 | 86 | # Uncomment the public line if your project uses Gatsby 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 89 | # public 90 | 91 | # Storybook build outputs 92 | .out 93 | .storybook-out 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # Temporary folders 108 | tmp/ 109 | temp/ 110 | 111 | # Sqlite Database 112 | *.sqlite 113 | *.sqlite3 114 | 115 | # End of https://www.gitignore.io/api/node 116 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "all", 4 | singleQuote: true, 5 | printWidth: 100, 6 | tabWidth: 2 7 | }; -------------------------------------------------------------------------------- /fullstack-notes-application/api/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | before_install: 7 | - docker build -t fhsinchy/test-node-rocket -f test-Dockerfile . 8 | 9 | script: 10 | - docker run fhsinchy/test-node-rocket -------------------------------------------------------------------------------- /fullstack-notes-application/api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts 2 | 3 | WORKDIR /usr/app 4 | 5 | COPY ./package.json . 6 | RUN npm install --production 7 | 8 | COPY . . 9 | 10 | CMD [ "npm", "run", "start" ] -------------------------------------------------------------------------------- /fullstack-notes-application/api/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:lts 2 | 3 | WORKDIR /usr/app 4 | 5 | COPY ./package.json . 6 | RUN npm install 7 | 8 | COPY . . 9 | 10 | CMD [ "npm", "run", "dev" ] -------------------------------------------------------------------------------- /fullstack-notes-application/api/README.md: -------------------------------------------------------------------------------- 1 | # api 2 | 3 | This API uses [fhsinchy/node-rocket](https://github.com/fhsinchy/node-rocket) project template. 4 | 5 | ## Project setup 6 | 7 | ```bash 8 | npm run dev # starts development server 9 | npm run start # starts production server 10 | 11 | npm run test # runs the tests 12 | 13 | npm run lint # lints code 14 | ``` 15 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/api/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | const routes = Router(); 4 | 5 | routes.get('/', (req, res) => { 6 | res.status(200).json({ 7 | error: false, 8 | message: 'Bonjour, mon ami', 9 | }); 10 | }); 11 | 12 | require('./routes/notes')(routes); 13 | 14 | module.exports = routes; 15 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/api/routes/notes.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const { celebrate, Joi } = require('celebrate'); 3 | 4 | const { Note } = require('../../models'); 5 | const { NotesService } = require('../../services'); 6 | 7 | const router = Router(); 8 | 9 | module.exports = (routes) => { 10 | routes.use('/notes', router); 11 | 12 | router.get( 13 | '/', 14 | async (req, res, next) => { 15 | try { 16 | res.status(200).json({ 17 | status: 'success', 18 | message: 'All Notes.', 19 | data: { 20 | notes: await new NotesService(Note).index(), 21 | }, 22 | }); 23 | } catch (err) { 24 | next(err); 25 | } 26 | }, 27 | ); 28 | 29 | router.post( 30 | '/', 31 | celebrate({ 32 | body: Joi.object().keys({ 33 | title: Joi.string().trim().required(), 34 | content: Joi.string().trim().required(), 35 | }), 36 | }), 37 | async (req, res, next) => { 38 | try { 39 | res.status(201).json({ 40 | status: 'success', 41 | message: 'Note Created.', 42 | data: { 43 | note: await new NotesService(Note).store(req.body), 44 | }, 45 | }); 46 | } catch (err) { 47 | next(err); 48 | } 49 | }, 50 | ); 51 | 52 | router.get( 53 | '/:id', 54 | async (req, res, next) => { 55 | try { 56 | res.status(200).json({ 57 | status: 'success', 58 | message: 'Single Note.', 59 | data: { 60 | note: await new NotesService(Note).show(req.params.id), 61 | }, 62 | }); 63 | } catch (err) { 64 | next(err); 65 | } 66 | }, 67 | ); 68 | 69 | router.put( 70 | '/:id', 71 | celebrate({ 72 | body: Joi.object().keys({ 73 | title: Joi.string().trim().required(), 74 | content: Joi.string().trim().required(), 75 | }), 76 | }), 77 | async (req, res, next) => { 78 | try { 79 | res.status(200).json({ 80 | status: 'success', 81 | message: 'Note Updated.', 82 | data: { 83 | note: await new NotesService(Note).update(req.params.id, req.body), 84 | }, 85 | }); 86 | } catch (err) { 87 | next(err); 88 | } 89 | }, 90 | ); 91 | 92 | router.delete( 93 | '/:id', 94 | async (req, res, next) => { 95 | try { 96 | await new NotesService(Note).destroy(req.params.id) 97 | 98 | res.status(200).json({ 99 | status: 'success', 100 | message: 'Note Deleted.', 101 | data: null, 102 | }); 103 | } catch (err) { 104 | next(err); 105 | } 106 | }, 107 | ); 108 | }; 109 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | const cors = require('cors'); 6 | const logger = require('morgan'); 7 | const helmet = require('helmet'); 8 | const express = require('express'); 9 | const { Model } = require('objection'); 10 | const { isCelebrate } = require('celebrate'); 11 | 12 | const routes = require('./api'); 13 | const { Knex } = require('./services'); 14 | 15 | /** 16 | * ORM initialization. 17 | */ 18 | 19 | Model.knex(Knex); 20 | 21 | /** 22 | * app instance initialization. 23 | */ 24 | 25 | const app = express(); 26 | 27 | /** 28 | * Middleware registration. 29 | */ 30 | 31 | app.use(cors()); 32 | app.use(helmet()); 33 | app.use(logger('dev')); 34 | app.use(express.json()); 35 | 36 | /** 37 | * Route registration. 38 | */ 39 | 40 | app.use('/', routes); 41 | 42 | /** 43 | * 404 handler. 44 | */ 45 | 46 | app.use((req, res, next) => { 47 | const err = new Error('Not Found!'); 48 | err.status = 404; 49 | next(err); 50 | }); 51 | 52 | /** 53 | * Error handler registration. 54 | */ 55 | 56 | // eslint-disable-next-line no-unused-vars 57 | app.use((err, req, res, next) => { 58 | const status = isCelebrate(err) ? 400 : err.status || 500; 59 | const message = 60 | process.env.NODE_ENV === 'production' && err.status === 500 61 | ? 'Something Went Wrong!' 62 | : err.message; 63 | 64 | // eslint-disable-next-line no-console 65 | if (status === 500) console.log(err.stack); 66 | 67 | res.status(status).json({ 68 | status: status >= 500 ? 'error' : 'fail', 69 | message, 70 | }); 71 | }); 72 | 73 | module.exports = app; 74 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const http = require('http'); 8 | const dotenv = require('dotenv'); 9 | const app = require('../app'); 10 | 11 | dotenv.config(); 12 | 13 | /** 14 | * Get port from environment and store in Express. 15 | */ 16 | 17 | const host = process.env.HOST || 'http://127.0.0.1'; 18 | const port = process.env.PORT || 3000; 19 | app.set('port', port); 20 | 21 | /** 22 | * Create HTTP server. 23 | */ 24 | 25 | const server = http.createServer(app); 26 | 27 | /** 28 | * Listen on provided port, on all network interfaces. 29 | */ 30 | // eslint-disable-next-line no-console 31 | console.log(`app running -> ${host}:${port}`); 32 | server.listen(port); 33 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/knexfile.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | client: process.env.DB_CONNECTION, 4 | connection: { 5 | host: process.env.DB_HOST, 6 | port: process.env.DB_PORT, 7 | user: process.env.DB_USER, 8 | password: process.env.DB_PASSWORD, 9 | database: process.env.DB_DATABASE, 10 | }, 11 | }, 12 | 13 | test: { 14 | client: 'sqlite3', 15 | connection: ':memory:', 16 | useNullAsDefault: true, 17 | }, 18 | 19 | staging: { 20 | client: process.env.DB_CONNECTION, 21 | connection: { 22 | host: process.env.DB_HOST, 23 | port: process.env.DB_PORT, 24 | user: process.env.DB_USER, 25 | password: process.env.DB_PASSWORD, 26 | database: process.env.DB_DATABASE, 27 | }, 28 | }, 29 | 30 | production: { 31 | client: process.env.DB_CONNECTION, 32 | connection: { 33 | host: process.env.DB_HOST, 34 | port: process.env.DB_PORT, 35 | user: process.env.DB_USER, 36 | password: process.env.DB_PASSWORD, 37 | database: process.env.DB_DATABASE, 38 | }, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/migrations/20200619195837_create_notes_table.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names */ 2 | 3 | exports.up = function (knex) { 4 | return knex.schema.createTable('notes', (table) => { 5 | table.increments(); 6 | table.string('title').notNullable(); 7 | table.text('content').notNullable(); 8 | }); 9 | }; 10 | 11 | exports.down = function (knex) { 12 | return knex.schema.dropTable('notes'); 13 | }; 14 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/models/Note.js: -------------------------------------------------------------------------------- 1 | const { Model } = require('objection'); 2 | 3 | class Note extends Model { 4 | static get tableName() { 5 | return 'notes'; 6 | } 7 | } 8 | 9 | module.exports = Note; 10 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/models/index.js: -------------------------------------------------------------------------------- 1 | const Note = require('./Note'); 2 | 3 | module.exports = { Note }; 4 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "ignore": ["*.test.js"] 4 | } -------------------------------------------------------------------------------- /fullstack-notes-application/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "main": "bin/www", 5 | "scripts": { 6 | "knex": "knex", 7 | "start": "cross-env NODE_ENV=production node bin/www", 8 | "dev": "cross-env NODE_ENV=development nodemon bin/www", 9 | "db:migrate": "knex migrate:latest", 10 | "db:refresh": "knex migrate:rollback --all; knex migrate:latest", 11 | "lint": "eslint --ext .js --cache --fix --ignore-path .gitignore .", 12 | "test": "cross-env NODE_ENV=test jest" 13 | }, 14 | "keywords": [], 15 | "author": "Farhan Hasin Chowdhury ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@hapi/joi": "^17.1.1", 19 | "celebrate": "^12.1.1", 20 | "cors": "^2.8.5", 21 | "cross-env": "^7.0.2", 22 | "dotenv": "^8.2.0", 23 | "express": "^4.17.1", 24 | "helmet": "^3.22.0", 25 | "knex": "^0.21.1", 26 | "morgan": "^1.10.0", 27 | "objection": "^2.2.0", 28 | "pg": "^8.2.1" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^6.8.0", 32 | "eslint-config-airbnb-base": "^14.1.0", 33 | "eslint-config-prettier": "^6.11.0", 34 | "eslint-plugin-import": "^2.20.2", 35 | "eslint-plugin-prettier": "^3.1.3", 36 | "husky": "^4.2.5", 37 | "jest": "^25.5.4", 38 | "lint-staged": "^10.1.7", 39 | "nodemon": "^2.0.3", 40 | "prettier": "^2.0.5", 41 | "sqlite3": "^4.2.0", 42 | "supertest": "^4.0.2" 43 | }, 44 | "husky": { 45 | "hooks": { 46 | "pre-commit": "lint-staged" 47 | } 48 | }, 49 | "lint-staged": { 50 | "*.{js,}": "eslint --cache --fix" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/services/index.js: -------------------------------------------------------------------------------- 1 | const Knex = require('./knex'); 2 | const NotesService = require('./notes'); 3 | 4 | module.exports = { Knex, NotesService }; 5 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/services/knex.js: -------------------------------------------------------------------------------- 1 | const knex = require('knex'); 2 | 3 | const config = require('../knexfile'); 4 | 5 | module.exports = knex(config[process.env.NODE_ENV]); 6 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/services/notes.js: -------------------------------------------------------------------------------- 1 | module.exports = class NotesService { 2 | constructor(Note) { 3 | this.Note = Note; 4 | } 5 | 6 | async index() { 7 | return await this.Note.query(); 8 | } 9 | 10 | async store(params) { 11 | return await this.Note.query().insert({ 12 | title: params.title, 13 | content: params.content, 14 | }); 15 | } 16 | 17 | async show(id) { 18 | const note = await this.Note.query().findById(id); 19 | 20 | if (!note) { 21 | const err = new Error('Not Found!'); 22 | err.status = 404; 23 | throw err; 24 | } 25 | 26 | return note; 27 | } 28 | 29 | async update(id, params) { 30 | const note = await this.Note.query().findById(id).patch({ 31 | title: params.title, 32 | content: params.content, 33 | }); 34 | 35 | if (!note) { 36 | const err = new Error('Not Found!'); 37 | err.status = 404; 38 | throw err; 39 | } 40 | 41 | return note; 42 | } 43 | 44 | async destroy(id) { 45 | const note = await this.Note.query().deleteById(id); 46 | 47 | if (!note) { 48 | const err = new Error('Not Found!'); 49 | err.status = 404; 50 | throw err; 51 | } 52 | 53 | return note; 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/tests/e2e/api/index.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | const request = require('supertest'); 4 | 5 | const app = require('../../../app'); 6 | 7 | describe('GET /', () => { 8 | test('Responds with 200 status code and a message', async () => { 9 | const response = await request(app).get('/'); 10 | 11 | expect(response.status).toBe(200); 12 | expect(response.body.message).toEqual('Bonjour, mon ami'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /fullstack-notes-application/api/tests/e2e/api/routes/notes.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | const request = require('supertest'); 4 | 5 | const app = require('../../../../app'); 6 | const { Knex } = require('../../../../services'); 7 | 8 | require('dotenv').config(); 9 | 10 | beforeEach(() => { 11 | return Knex.migrate.latest(); 12 | }); 13 | 14 | afterEach(() => { 15 | return Knex.migrate.rollback(); 16 | }); 17 | 18 | afterAll(() => { 19 | return Knex.destroy(); 20 | }); 21 | 22 | describe('GET /notes', () => { 23 | test('Responds with 200 status code', async () => { 24 | const response = await request(app).get('/notes'); 25 | 26 | expect(response.status).toBe(200); 27 | expect(response.body.data); 28 | }); 29 | }); 30 | 31 | describe('POST /notes', () => { 32 | test('Creates an new note in the database', async () => { 33 | const note = { 34 | title: 'Lorem ipsum', 35 | content: 36 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 37 | }; 38 | const response = await request(app).post('/notes').send(note); 39 | 40 | expect(response.status).toBe(201); 41 | }); 42 | }); 43 | 44 | describe('GET /notes/1', () => { 45 | test('Returns a single note from the database', async () => { 46 | const note = { 47 | title: 'Lorem ipsum', 48 | content: 49 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 50 | }; 51 | await request(app).post('/notes').send(note); 52 | 53 | const response = await request(app).get('/notes/1'); 54 | 55 | expect(response.status).toBe(200); 56 | }); 57 | }); 58 | 59 | describe('PUT /notes/1', () => { 60 | test('Updates a single note in the database', async () => { 61 | const note = { 62 | title: 'Lorem ipsum', 63 | content: 64 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 65 | }; 66 | const updatedNote = { 67 | title: 'Lorem ipsum [UPDATED]', 68 | content: 69 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 70 | }; 71 | 72 | await request(app).post('/notes').send(note); 73 | 74 | const response = await request(app).put('/notes/1').send(updatedNote); 75 | 76 | expect(response.status).toBe(200); 77 | }); 78 | }); 79 | 80 | describe('DELETE /notes/1', () => { 81 | test('Deletes a single note from the database', async () => { 82 | const note = { 83 | title: 'Lorem ipsum', 84 | content: 85 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', 86 | }; 87 | 88 | await request(app).post('/notes').send(note); 89 | 90 | const response = await request(app).delete('/notes/1'); 91 | 92 | expect(response.status).toBe(200); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /fullstack-notes-application/client/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/airbnb', 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint', 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | }, 17 | overrides: [ 18 | { 19 | files: [ 20 | '**/__tests__/*.{j,t}s?(x)', 21 | '**/tests/unit/**/*.spec.{j,t}s?(x)', 22 | ], 23 | env: { 24 | jest: true, 25 | }, 26 | }, 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts as builder 2 | 3 | WORKDIR /usr/app 4 | COPY ./package.json . 5 | RUN npm install 6 | COPY . . 7 | RUN npm run build 8 | 9 | FROM nginx:stable 10 | 11 | EXPOSE 8080 12 | 13 | COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf 14 | COPY --from=builder /usr/app/dist /usr/share/nginx/html -------------------------------------------------------------------------------- /fullstack-notes-application/client/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:lts 2 | 3 | WORKDIR /usr/app 4 | 5 | COPY ./package.json . 6 | RUN npm install 7 | 8 | COPY . . 9 | 10 | CMD [ "npm", "run", "serve" ] -------------------------------------------------------------------------------- /fullstack-notes-application/client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | ## Project setup 4 | 5 | ```bash 6 | npm install 7 | ``` 8 | 9 | ### Compiles and hot-reloads for development 10 | 11 | ```bash 12 | npm run serve 13 | ``` 14 | 15 | ### Compiles and minifies for production 16 | 17 | ```bash 18 | npm run build 19 | ``` 20 | 21 | ### Run your unit tests 22 | 23 | ```bash 24 | npm run test:unit 25 | ``` 26 | 27 | ### Lints and fixes files 28 | 29 | ```bash 30 | npm run lint 31 | ``` 32 | 33 | ### Customize configuration 34 | 35 | See [Configuration Reference](https://cli.vuejs.org/config/). 36 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest', 3 | }; 4 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | 4 | location / { 5 | root /usr/share/nginx/html; 6 | index index.html index.htm; 7 | try_files $uri $uri/ /index.html; 8 | } 9 | } -------------------------------------------------------------------------------- /fullstack-notes-application/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.19.2", 13 | "core-js": "^3.6.5", 14 | "mini.css": "^3.0.1", 15 | "vue": "^2.6.11", 16 | "vue-router": "^3.2.0", 17 | "vuex": "^3.4.0" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "~4.4.0", 21 | "@vue/cli-plugin-eslint": "~4.4.0", 22 | "@vue/cli-plugin-router": "~4.4.0", 23 | "@vue/cli-plugin-unit-jest": "~4.4.0", 24 | "@vue/cli-plugin-vuex": "~4.4.0", 25 | "@vue/cli-service": "~4.4.0", 26 | "@vue/eslint-config-airbnb": "^5.0.2", 27 | "@vue/test-utils": "^1.0.3", 28 | "babel-eslint": "^10.1.0", 29 | "css-loader": "^3.6.0", 30 | "eslint": "^6.7.2", 31 | "eslint-plugin-import": "^2.20.2", 32 | "eslint-plugin-vue": "^6.2.2", 33 | "lint-staged": "^9.5.0", 34 | "vue-template-compiler": "^2.6.11" 35 | }, 36 | "gitHooks": { 37 | "pre-commit": "lint-staged" 38 | }, 39 | "lint-staged": { 40 | "*.{js,jsx,vue}": [ 41 | "vue-cli-service lint", 42 | "git add" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhsinchy/kubernetes-handbook-projects/abf31d8045ec415ef023f3e823becf0d4d125e94/fullstack-notes-application/client/public/favicon.ico -------------------------------------------------------------------------------- /fullstack-notes-application/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhsinchy/kubernetes-handbook-projects/abf31d8045ec415ef023f3e823becf0d4d125e94/fullstack-notes-application/client/src/assets/logo.png -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/components/Note.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/libs/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export default axios.create({ 4 | baseURL: '/api', 5 | }); 6 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | 6 | import 'mini.css/dist/mini-default.min.css'; 7 | 8 | Vue.config.productionTip = false; 9 | 10 | new Vue({ 11 | router, 12 | store, 13 | render: (h) => h(App), 14 | }).$mount('#app'); 15 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Home from '../views/Home.vue'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home, 12 | }, 13 | { 14 | path: '/notes/create', 15 | name: 'Create', 16 | component: () => import('../views/Create.vue'), 17 | }, 18 | ]; 19 | 20 | const router = new VueRouter({ 21 | mode: 'history', 22 | base: process.env.BASE_URL, 23 | routes, 24 | }); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import axios from '../libs/axios'; 4 | import router from '../router'; 5 | 6 | Vue.use(Vuex); 7 | 8 | export default new Vuex.Store({ 9 | state: { 10 | notes: [], 11 | }, 12 | mutations: { 13 | insertNote(state, payload) { 14 | state.notes.push(payload); 15 | }, 16 | deleteNote(state, payload) { 17 | state.notes.splice(state.notes.indexOf(payload), 1); 18 | }, 19 | }, 20 | actions: { 21 | async fetchNotes(context) { 22 | try { 23 | const response = await axios.get('/notes'); 24 | 25 | response.data.data.notes.map((note) => context.commit('insertNote', note)); 26 | } catch (error) { 27 | // eslint-disable-next-line no-console 28 | console.log(error); 29 | } 30 | }, 31 | async createNote(context, payload) { 32 | try { 33 | const response = await axios.post('/notes', payload); 34 | 35 | context.commit('insertNote', response.data.data.note); 36 | 37 | router.push('/'); 38 | } catch (error) { 39 | // eslint-disable-next-line no-console 40 | console.log(error); 41 | } 42 | }, 43 | async deleteNote(context, payload) { 44 | try { 45 | await axios.delete(`/notes/${payload.id}`); 46 | 47 | context.commit('deleteNote', payload); 48 | } catch (error) { 49 | // eslint-disable-next-line no-console 50 | console.log(error); 51 | } 52 | }, 53 | }, 54 | modules: { 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /fullstack-notes-application/client/src/views/Create.vue: -------------------------------------------------------------------------------- 1 |