├── .gitattributes ├── .gitignore ├── .gitmodules ├── README.md ├── aulas ├── aula01 │ ├── README.md │ ├── gdrive-webapi │ │ ├── certificates │ │ │ ├── cert.pem │ │ │ └── key.pem │ │ ├── jest.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── fileHelper.js │ │ │ ├── index.js │ │ │ ├── logger.js │ │ │ └── routes.js │ │ └── test │ │ │ └── unit │ │ │ ├── fileHelper.test.js │ │ │ └── routes.test.js │ └── gdrive-webapp │ │ ├── .gitignore │ │ ├── README.md │ │ ├── demo.png │ │ ├── package-lock.json │ │ ├── package.json │ │ └── public │ │ ├── app.js │ │ ├── img │ │ └── profile.png │ │ ├── index.html │ │ └── styles.css ├── aula02 │ ├── README.md │ ├── gdrive-webapi │ │ ├── certificates │ │ │ ├── cert.pem │ │ │ └── key.pem │ │ ├── jest.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── fileHelper.js │ │ │ ├── index.js │ │ │ ├── logger.js │ │ │ ├── routes.js │ │ │ └── uploadHandler.js │ │ └── test │ │ │ ├── _util │ │ │ └── testUtil.js │ │ │ ├── integration │ │ │ ├── mocks │ │ │ │ └── semanajsexpert.png │ │ │ └── routes.test.js │ │ │ └── unit │ │ │ ├── fileHelper.test.js │ │ │ ├── routes.test.js │ │ │ └── uploadHandler.test.js │ └── gdrive-webapp │ │ ├── .gitignore │ │ ├── README.md │ │ ├── demo.png │ │ ├── package-lock.json │ │ ├── package.json │ │ └── public │ │ ├── app.js │ │ ├── img │ │ └── profile.png │ │ ├── index.html │ │ └── styles.css └── aula03 │ ├── README.md │ ├── gdrive-webapi │ ├── .gitignore │ ├── certificates │ │ ├── cert.pem │ │ └── key.pem │ ├── jest.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── fileHelper.js │ │ ├── index.js │ │ ├── logger.js │ │ ├── routes.js │ │ └── uploadHandler.js │ └── test │ │ ├── _util │ │ └── testUtil.js │ │ ├── integration │ │ ├── mocks │ │ │ └── semanajsexpert.png │ │ └── routes.test.js │ │ └── unit │ │ ├── fileHelper.test.js │ │ ├── routes.test.js │ │ └── uploadHandler.test.js │ └── gdrive-webapp │ ├── .gitignore │ ├── README.md │ ├── certificates │ ├── cert.pem │ └── key.pem │ ├── demo.png │ ├── package-lock.json │ ├── package.json │ └── public │ ├── app.js │ ├── img │ └── profile.png │ ├── index.html │ ├── src │ ├── appController.js │ ├── connectionManager.js │ ├── dragAndDropManager.js │ └── viewManager.js │ └── styles.css ├── certificates ├── README.md ├── cert.pem ├── certificate-generator.sh ├── key.pem └── sslproblem.png ├── resources └── demo.gif └── welcome.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-detectable=true 2 | *.html linguist-detectable=false 3 | *.css linguist-detectable=false 4 | *.sh linguist-detectable=false 5 | *.pem linguist-detectable=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 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 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "initial-template"] 2 | path = initial-template 3 | url = https://github.com/ErickWendel/semanajsexpert-gdrive-template.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Clone - JS Expert Week 5.0 2 | 3 | Welcome to the fifth Javascript Expert Week. This is the starting code to start our journey. 4 | 5 | Tag this project with a star 🌟 6 | 7 | [Ask DeepWiki.com](https://deepwiki.com/ErickWendel/semana-javascript-expert05) 8 | 9 | ## Preview 10 | 11 | ![](./resources/demo.gif) 12 | 13 | 14 | ## Checklist Features 15 | 16 | - Web API 17 | - [] It should list downloaded files 18 | - [] It must stream files and save them in disk 19 | - [] It should notify about progress of storing files to disk 20 | - [] It must allow uploading of files in image, video or audio format 21 | - [] It must reach 100% code coverage in tests 22 | 23 | - Web App 24 | - [] Should list downloaded files 25 | - [] Should allow uploading of files of any size 26 | - [] Must have upload function via button 27 | - [] Should display upload progress 28 | - [] Must have drag and drop upload function 29 | 30 | 31 | ## Challenges for you to extend this project 32 | 33 | 1. *Backend*: Save the file to AWS or any storage service 34 | - Our project today stores files on disk. the challenge is you via Stream, uploading to some cloud service 35 | - As a plus, keep 100% code coverage, that is, create tests for your new feature 36 | 2. *Frontend*: Add frontend tests and achieve 100% code coverage 37 | - You learned how to test in the backend. Use the same process to create frontend unit tests with Jest 38 | - If you have any doubts, go to [example](https://github.com/ErickWendel/tdd-frontend-example) and leave a star! 39 | 3. *Infrastructure*: Publish application with your custom SSL in virtual machine 40 | - You learned how to generate local SSL, the challenge is for you to create a certificate (it can be with *Let's Encrypt*) and add it to your application 41 | 42 | ## Layout Credits <3 43 | 44 | - The Layout was adapted from the project by the Brazilian [Leonardo Santo](https://github.com/leoespsanto) available on [codepen](https://codepen.io/leoespsanto/pen/KZMMKG). 45 | 46 | ## FAQ 47 | - `NODE_OPTIONS` is not a system recognized command, what to do? 48 | - If you are on Windows, the way to create environment variables is different. You must use the word `set` before the command. 49 | - Ex: ` "test": "set NODE_OPTIONS=--experimental-vm-modules && npx jest --runInBand",` 50 | 51 | - SSL certificate is invalid, what to do? 52 | - This error happens because I generated a certificate linked to the user of my machine. 53 | - You can click on proceed in the browser and use the invalid certificate and the project will continue working, but if you want to generate your own, I wrote the step by step in [./certificates](./certificates) 54 | 55 | - I ran `npm test` but nothing happens, what to do? 56 | - Check your Node.js version. We are using version 16.8. Go to [node.js website](https://nodejs.org) and download the latest version. 57 | -------------------------------------------------------------------------------- /aulas/aula01/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Clone - Semana JS Expert 5.0 2 | 3 | Seja bem vindo(a) à quinta Semana Javascript Expert. Este é o código inicial para iniciar nossa jornada. 4 | 5 | Marque esse projeto com uma estrela 🌟 6 | 7 | ## Preview 8 | 9 | ![](./resources/demo.gif) 10 | 11 | 12 | ## Checklist Features 13 | 14 | - Web API 15 | - [x] Deve listar arquivos baixados 16 | - [ ] Deve receber stream de arquivos e salvar em disco 17 | - [ ] Deve notificar sobre progresso de armazenamento de arquivos em disco 18 | - [ ] Deve permitir upload de arquivos em formato image, video ou audio 19 | - [ ] Deve atingir 100% de cobertura de código em testes 20 | 21 | - Web App 22 | - [] Deve listar arquivos baixados 23 | - [] Deve permitir fazer upload de arquivos de qualquer tamanho 24 | - [] Deve ter função de upload via botão 25 | - [] Deve exibir progresso de upload 26 | - [] Deve ter função de upload via drag and drop 27 | 28 | 29 | 30 | ## Desafios para alunos pós projeto 31 | 32 | 1. *Backend*: Salvar o arquivo na AWS ou qualquer serviço de storage 33 | - Nosso projeto hoje armazena arquivos em disco. o desafio é você via Stream, fazer upload para algum serviço na nuvem 34 | - Como plus, manter 100% de code coverage, ou seja, crie testes para sua nova feature 35 | 2. *Frontend*: Adicionar testes no frontend e alcançar 100% de code coverage 36 | - Você aprendeu como fazer testes no backend. Usar o mesmo processo para criar testes unitários no frontend com Jest 37 | - Caso tenha duvidas, acesse o [exemplo](https://github.com/ErickWendel/tdd-frontend-example) e deixe uma estrela! 38 | 3. *Infraestrutura*: Publicar aplicação com seu SSL customizado em máquina virtual 39 | - Você aprendeu a gerar SSL local, o desafio é você criar um certificado (pode ser com o *Let's Encrypt*) e adicionar na sua aplicação 40 | 41 | ### Considerações 42 | - Tire suas dúvidas sobre os desafios em nossa comunidade, o objetivo é você aprender de forma divertida. Surgiu dúvidas? Pergunte por lá! 43 | 44 | - Ao completar qualquer um dos desafios, envie no canal **#desafios** da comunidade no **Discord** 45 | 46 | ## Créditos ao Layout <3 47 | 48 | - O Layout foi adaptado a partir do projeto do brasileiro [Leonardo Santo](https://github.com/leoespsanto) disponibilizado no [codepen](https://codepen.io/leoespsanto/pen/KZMMKG). -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/certificates/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdzCCAt+gAwIBAgIRAPzlKLsx59gDukI5AIpXqBwwDQYJKoZIhvcNAQELBQAw 3 | gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqZXJp 4 | Y2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5kZWwpMTowOAYDVQQD 5 | DDFta2NlcnQgZXJpY2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5k 6 | ZWwpMB4XDTIxMDkwMzE4MTIxNVoXDTIzMTIwMzE4MTIxNVowXjEnMCUGA1UEChMe 7 | bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCplcmlja3dl 8 | bmRlbEBFcmlja1dlbmRlbHNNQlAgKEVyaWNrIFdlbmRlbCkwggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQD3K1QBNBa5kbKwAFEj76QJNErcJoUGh9g7LX0b 10 | SbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/sP2xZLDmxkMufG6QL7fw64to 11 | BRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZRzJDPAUGhkLfswaFfqDemp6U1 12 | KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIcOpGEhgwKA4QYawuIgGUcSbys 13 | z/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv5pjO3X70WNRPb0WbszoPLzKO 14 | Ckj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLPSHBTLqEPAgMBAAGjfDB6MA4G 15 | A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQt 16 | MxAhQKNc8uLS9BClcrjiEtdOqTAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACH 17 | BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJjILvMJ 18 | jUhqj99UGS5rdz0ksz3H0Mkn/ffDh+xMd6QHyUia0RQblolL4sjH+ZeZZWpcDfnb 19 | jQ9VN2zpPvZGPE4QykfAwYTK5mUxF24LwBKMelAmTc1xdxqx9AB93hocnxeKZKEt 20 | /Hk7U62ozssJyTmfA2TUrDuXKwNWiTpiazLoy7FaHkqe3dvmRJkjqgYkebHY1PtL 21 | CJB6IPe3CRpayc/AZGtd7GiBVlsMl4wDiKE4lYGb11943cZgcUwbuGHTdye8wf3g 22 | 1dB84AkXJSBEd8Kc7hokTuTdBlBWKWSUln7wHTmLfZ6jEuh6bDI3cn8IU5bAnAG7 23 | JmBe/i4zDlsA0zdhVM0y/MFL+o1m+znWYKApneuRWH9QULe4A9J74J7eaD5x4quv 24 | NmUDGGa5HyLcPM5WfJeJknAURE4wlT+T5C36X5DERCbrGsXiikcUqXJ8PHudm3qb 25 | gtitoc5+LEOC9TrTcInpym/qN7rbBMJd57kfGNBA9stg7cPAQsgxHZmR1g== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3K1QBNBa5kbKw 3 | AFEj76QJNErcJoUGh9g7LX0bSbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/ 4 | sP2xZLDmxkMufG6QL7fw64toBRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZR 5 | zJDPAUGhkLfswaFfqDemp6U1KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIc 6 | OpGEhgwKA4QYawuIgGUcSbysz/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv 7 | 5pjO3X70WNRPb0WbszoPLzKOCkj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLP 8 | SHBTLqEPAgMBAAECggEAYcoxtTwvlYHQiZPGhopEgyO+TnWrAddzNI6dxdMW885m 9 | grdClU8DCn0qsLYrN0fmA8CeDmvFc/kOFVOoO6wT1WoxoE24zLEkfFwkzqXbwfvm 10 | S8vWnjAjTZ6yaoRMe7suenkcXIQSC0pFgv2FeXEMz2hbbH97YkZCplwDoBGJshjv 11 | CdVgykGokzhvhp05xm+oayX/zEyxw7jp9o6ZpeTs2dphj9pXXcEkg8uR5CxyummF 12 | nSqwJZxmZ2HtEQAL7yZfli0vVbTlBa9L4JokS7xoqur15B1hiQIJBYZMJXHHr5tH 13 | YQfVP0zKGtWMIQNRYfjYKn3bYt4yqmHR9wBpV2SFOQKBgQD6DGquLcAt1M2VAUSd 14 | NSQ4WByADiXbw8R/CDdjAhHJ45Jz/3qDeRUYQ6YK9pDBpVjZdEKIm2SNG2tyrY87 15 | EhTQDuoyklQ/zFA5gdCQeikGVBidSHTarUxnHCgoNVPsk2pL7cVZnd7oIUkcGs+H 16 | kR8cqo/gJpHX17gZhp9oXEo2CwKBgQD9DV4h4Hm7aB58Vve5JvnNDG8pQP8FEd+5 17 | P6BUfJBovxFeWXOTPtq8QU2xTL1NHrimq1KG2iTj+11FSS+4zFaDJAZUYQ4tgn4o 18 | OKexDcJ6JzC8XOk974WE7StqjX6n1LqTqWMQ3Bnd3gEibjNzLVmHluh+SN3CLooI 19 | RjK6Yie3jQKBgCzJ6pX2dfT/qC9ngb3TFgDNr5U0c42Q3HKQqzMd3MfX7pS+j1hb 20 | aO7mtyhBkB5PmsGgtIY5p2IrJiztb7l5/KZj9YlHcrXWyAv098HZT93lVF9f6iZ9 21 | YjEZ9wt0ueqnYSPmnDH4OERGKg1RtBipYvREjO7umbMa3cwctBMCbPyPAoGBAKbW 22 | zm5Ves0Vq6vdBvz69o27mfrAEKN+Elwn2AR8EBYPi1sCbRHyyfJ+t8Oizdhv3dx9 23 | bi7c2p+5VdhdlWooxw01jjrJtrhIpfbMy7sPUF6LQjWeqGUea5Clcg+RdKUgu1ap 24 | wlgWVbOTMHpL3/4bM0ETPPwt/I+PcZBdAAsktfztAoGBAOO91AnDsTrFiNtU2IVv 25 | 9tc83nFttWmSmoaiz5JeXN98Fo0e8l/uLT3mKcD7ue3lh2qr6CxKZdlfopiFsCi3 26 | s0IuN7mSgStbZdK0km0vu1+N0cqMR+mvqCbYNk4cW690snXsWY3uVVJpDfRlwUDu 27 | HH0k++iNL4d/6FE571Ua8IUK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/jest.config.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | */ 4 | 5 | export default { 6 | clearMocks: true, 7 | restoreMocks: true, 8 | // collectCoverage: true, 9 | coverageDirectory: "coverage", 10 | coverageProvider: "v8", 11 | coverageReporters: [ 12 | "text", 13 | "lcov" 14 | ], 15 | testEnvironment: "node", 16 | coverageThreshold: { 17 | global: { 18 | branches: 100, 19 | functions: 100, 20 | lines: 100, 21 | statements: 100 22 | } 23 | }, 24 | watchPathIgnorePatterns: [ 25 | "node_modules" 26 | ], 27 | transformIgnorePatterns: ["node_modules"], 28 | collectCoverageFrom: [ 29 | "src/**/*.js", "!src/**/index.js" 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "mkdir -p downloads && node src/index.js", 9 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand", 10 | "test:watch": "NODE_OPTIONS=--experimental-vm-modules npx jest --watch --runInBand", 11 | "test:cov": "NODE_OPTIONS=--experimental-vm-modules npx jest --no-cache --runInBand --coverage" 12 | }, 13 | "keywords": [], 14 | "author": "erickwendel", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "jest": "^27.1.0" 18 | }, 19 | "dependencies": { 20 | "pino": "6.8", 21 | "pino-pretty": "5.1", 22 | "pretty-bytes": "5.6", 23 | "socket.io": "4.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/src/fileHelper.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import prettyBytes from 'pretty-bytes' 3 | 4 | export default class FileHelper { 5 | static async getFilesStatus(downloadsFolder) { 6 | const currentFiles = await fs.promises.readdir(downloadsFolder) 7 | const statuses = await Promise 8 | .all( 9 | currentFiles 10 | .map( 11 | file => fs.promises.stat(`${downloadsFolder}/${file}`) 12 | ) 13 | ) 14 | const filesStatuses = [] 15 | for (const fileIndex in currentFiles) { 16 | const { birthtime, size } = statuses[fileIndex] 17 | filesStatuses.push({ 18 | size: prettyBytes(size), 19 | file: currentFiles[fileIndex], 20 | lastModified: birthtime, 21 | owner: process.env.USER 22 | }) 23 | } 24 | 25 | return filesStatuses 26 | } 27 | } -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/src/index.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | import fs from 'fs' 3 | import { logger } from './logger.js' 4 | import { Server } from 'socket.io' 5 | import Routes from './routes.js' 6 | 7 | const PORT = process.env.PORT || 3000 8 | 9 | const localHostSSL = { 10 | key: fs.readFileSync('./certificates/key.pem'), 11 | cert: fs.readFileSync('./certificates/cert.pem'), 12 | } 13 | const routes = new Routes() 14 | const server = https.createServer( 15 | localHostSSL, 16 | routes.handler.bind(routes) 17 | ) 18 | 19 | const io = new Server(server, { 20 | cors: { 21 | origin: '*', 22 | credentials: false 23 | } 24 | }) 25 | 26 | routes.setSocketInstance(io) 27 | 28 | io.on("connection", (socket) => logger.info(`someone connected: ${socket.id}`)) 29 | 30 | const startServer = () => { 31 | const { address, port } = server.address() 32 | logger.info(`app running at https://${address}:${port}`) 33 | } 34 | 35 | server.listen(PORT, startServer) -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/src/logger.js: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | const logger = pino({ 3 | prettyPrint: { 4 | ignore: 'pid,hostname' 5 | } 6 | }) 7 | 8 | export { 9 | logger, 10 | } -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/src/routes.js: -------------------------------------------------------------------------------- 1 | import FileHelper from "./fileHelper.js" 2 | import { logger } from "./logger.js" 3 | import { dirname, resolve } from 'path' 4 | import { fileURLToPath } from 'url' 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)) 7 | const defaultDownloadsFolder = resolve(__dirname, '../', "downloads") 8 | 9 | export default class Routes { 10 | io 11 | constructor(downloadsFolder = defaultDownloadsFolder) { 12 | this.downloadsFolder = downloadsFolder 13 | this.fileHelper = FileHelper 14 | } 15 | 16 | setSocketInstance(io) { 17 | this.io = io 18 | } 19 | 20 | async defaultRoute(request, response) { 21 | response.end('hello world') 22 | } 23 | 24 | async options(request, response) { 25 | response.writeHead(204) 26 | response.end() 27 | } 28 | 29 | async post(request, response) { 30 | logger.info('post') 31 | response.end() 32 | } 33 | 34 | async get(request, response) { 35 | const files = await this.fileHelper.getFilesStatus(this.downloadsFolder) 36 | 37 | response.writeHead(200) 38 | response.end(JSON.stringify(files)) 39 | } 40 | 41 | handler(request, response) { 42 | response.setHeader('Access-Control-Allow-Origin', '*') 43 | const chosen = this[request.method.toLowerCase()] || this.defaultRoute 44 | 45 | return chosen.apply(this, [request, response]) 46 | } 47 | } -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/test/unit/fileHelper.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | jest 6 | } from '@jest/globals' 7 | import fs from 'fs' 8 | import FileHelper from '../../src/fileHelper.js' 9 | 10 | import Routes from './../../src/routes.js' 11 | 12 | describe('#FileHelper', () => { 13 | 14 | describe('#getFileStatus', () => { 15 | test('it should return files statuses in correct format', async () => { 16 | const statMock = { 17 | dev: 16777220, 18 | mode: 33188, 19 | nlink: 1, 20 | uid: 501, 21 | gid: 20, 22 | rdev: 0, 23 | blksize: 4096, 24 | ino: 214187433, 25 | size: 188188, 26 | blocks: 368, 27 | atimeMs: 1630702590337.3582, 28 | mtimeMs: 1630702588444.2876, 29 | ctimeMs: 1630702588452.0754, 30 | birthtimeMs: 1630702588443.3276, 31 | atime: '2021-09-03T20:56:30.337Z', 32 | mtime: '2021-09-03T20:56:28.444Z', 33 | ctime: '2021-09-03T20:56:28.452Z', 34 | birthtime: '2021-09-03T20:56:28.443Z' 35 | } 36 | 37 | const mockUser = 'erickwendel' 38 | process.env.USER = mockUser 39 | const filename = 'file.png' 40 | 41 | jest.spyOn(fs.promises, fs.promises.readdir.name) 42 | .mockResolvedValue([filename]) 43 | 44 | jest.spyOn(fs.promises, fs.promises.stat.name) 45 | .mockResolvedValue(statMock) 46 | 47 | const result = await FileHelper.getFilesStatus("/tmp") 48 | 49 | const expectedResult = [ 50 | { 51 | size: "188 kB", 52 | lastModified: statMock.birthtime, 53 | owner: mockUser, 54 | file: filename 55 | } 56 | ] 57 | 58 | expect(fs.promises.stat).toHaveBeenCalledWith(`/tmp/${filename}`) 59 | expect(result).toMatchObject(expectedResult) 60 | }) 61 | }) 62 | }) -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapi/test/unit/routes.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | jest 6 | } from '@jest/globals' 7 | import Routes from './../../src/routes.js' 8 | 9 | describe('#Routes test suite', () => { 10 | const defaultParams = { 11 | request: { 12 | headers: { 13 | 'Content-Type': 'multipart/form-data' 14 | }, 15 | method: '', 16 | body: {} 17 | }, 18 | response: { 19 | setHeader: jest.fn(), 20 | writeHead: jest.fn(), 21 | end: jest.fn() 22 | }, 23 | values: () => Object.values(defaultParams) 24 | } 25 | 26 | describe('#setSocketInstance', () => { 27 | test('setSocket should store io instance', () => { 28 | const routes = new Routes() 29 | const ioObj = { 30 | to: (id) => ioObj, 31 | emit: (event, message) => { } 32 | } 33 | 34 | routes.setSocketInstance(ioObj) 35 | expect(routes.io).toStrictEqual(ioObj) 36 | }) 37 | }) 38 | 39 | describe('#handler', () => { 40 | 41 | 42 | test('given an inexistent route it should choose default route', async () => { 43 | const routes = new Routes() 44 | const params = { 45 | ...defaultParams 46 | } 47 | 48 | params.request.method = 'inexistent' 49 | await routes.handler(...params.values()) 50 | expect(params.response.end).toHaveBeenCalledWith('hello world') 51 | }) 52 | 53 | test('it should set any request with CORS enabled', async () => { 54 | const routes = new Routes() 55 | const params = { 56 | ...defaultParams 57 | } 58 | 59 | params.request.method = 'inexistent' 60 | await routes.handler(...params.values()) 61 | expect(params.response.setHeader) 62 | .toHaveBeenCalledWith('Access-Control-Allow-Origin', '*') 63 | }) 64 | 65 | test('given method OPTIONS it should choose options route', async () => { 66 | const routes = new Routes() 67 | const params = { 68 | ...defaultParams 69 | } 70 | 71 | params.request.method = 'OPTIONS' 72 | await routes.handler(...params.values()) 73 | expect(params.response.writeHead).toHaveBeenCalledWith(204) 74 | expect(params.response.end).toHaveBeenCalled() 75 | }) 76 | 77 | test('given method POST it should choose post route', async () => { 78 | const routes = new Routes() 79 | const params = { 80 | ...defaultParams 81 | } 82 | 83 | params.request.method = 'POST' 84 | jest.spyOn(routes, routes.post.name).mockResolvedValue() 85 | 86 | await routes.handler(...params.values()) 87 | expect(routes.post).toHaveBeenCalled() 88 | 89 | }) 90 | test('given method GET it should choose get route', async () => { 91 | const routes = new Routes() 92 | const params = { 93 | ...defaultParams 94 | } 95 | jest.spyOn(routes, routes.get.name).mockResolvedValue() 96 | 97 | params.request.method = 'GET' 98 | await routes.handler(...params.values()) 99 | expect(routes.get).toHaveBeenCalled() 100 | 101 | }) 102 | }) 103 | 104 | describe('#get', () => { 105 | test('given method GET it should list all files downloaded', async () => { 106 | const routes = new Routes() 107 | const params = { 108 | ...defaultParams 109 | } 110 | 111 | const filesStatusesMock = [ 112 | { 113 | size: "188 kB", 114 | lastModified: '2021-09-03T20:56:28.443Z', 115 | owner: 'erickwendel', 116 | file: 'file.txt' 117 | } 118 | ] 119 | jest.spyOn(routes.fileHelper, routes.fileHelper.getFilesStatus.name) 120 | .mockResolvedValue(filesStatusesMock) 121 | 122 | params.request.method = 'GET' 123 | await routes.handler(...params.values()) 124 | 125 | 126 | expect(params.response.writeHead).toHaveBeenCalledWith(200) 127 | expect(params.response.end).toHaveBeenCalledWith(JSON.stringify(filesStatusesMock)) 128 | 129 | }) 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 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 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Template - Semana JS Expert 5.0 2 | 3 | 4 | Seja bem vindo(a) à quarta Semana Javascript Expert.Este é o código inicial para iniciar nossa jornada. 5 | 6 | Marque esse projeto com uma estrela 🌟 7 | 8 | ## Preview 9 | ### Página principal 10 | ![](./demo.png) -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula01/gdrive-webapp/demo.png -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@erickwendel/gdrive-webapp", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "http-server": "^0.12.3" 13 | } 14 | }, 15 | "node_modules/async": { 16 | "version": "2.6.3", 17 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 18 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 19 | "dependencies": { 20 | "lodash": "^4.17.14" 21 | } 22 | }, 23 | "node_modules/basic-auth": { 24 | "version": "1.1.0", 25 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 26 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", 27 | "engines": { 28 | "node": ">= 0.6" 29 | } 30 | }, 31 | "node_modules/call-bind": { 32 | "version": "1.0.2", 33 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 34 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 35 | "dependencies": { 36 | "function-bind": "^1.1.1", 37 | "get-intrinsic": "^1.0.2" 38 | }, 39 | "funding": { 40 | "url": "https://github.com/sponsors/ljharb" 41 | } 42 | }, 43 | "node_modules/colors": { 44 | "version": "1.4.0", 45 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 46 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 47 | "engines": { 48 | "node": ">=0.1.90" 49 | } 50 | }, 51 | "node_modules/corser": { 52 | "version": "2.0.1", 53 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 54 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 55 | "engines": { 56 | "node": ">= 0.4.0" 57 | } 58 | }, 59 | "node_modules/debug": { 60 | "version": "3.2.7", 61 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 62 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 63 | "dependencies": { 64 | "ms": "^2.1.1" 65 | } 66 | }, 67 | "node_modules/ecstatic": { 68 | "version": "3.3.2", 69 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 70 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 71 | "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", 72 | "dependencies": { 73 | "he": "^1.1.1", 74 | "mime": "^1.6.0", 75 | "minimist": "^1.1.0", 76 | "url-join": "^2.0.5" 77 | }, 78 | "bin": { 79 | "ecstatic": "lib/ecstatic.js" 80 | } 81 | }, 82 | "node_modules/eventemitter3": { 83 | "version": "4.0.7", 84 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 85 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 86 | }, 87 | "node_modules/follow-redirects": { 88 | "version": "1.14.3", 89 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 90 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", 91 | "funding": [ 92 | { 93 | "type": "individual", 94 | "url": "https://github.com/sponsors/RubenVerborgh" 95 | } 96 | ], 97 | "engines": { 98 | "node": ">=4.0" 99 | }, 100 | "peerDependenciesMeta": { 101 | "debug": { 102 | "optional": true 103 | } 104 | } 105 | }, 106 | "node_modules/function-bind": { 107 | "version": "1.1.1", 108 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 109 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 110 | }, 111 | "node_modules/get-intrinsic": { 112 | "version": "1.1.1", 113 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 114 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 115 | "dependencies": { 116 | "function-bind": "^1.1.1", 117 | "has": "^1.0.3", 118 | "has-symbols": "^1.0.1" 119 | }, 120 | "funding": { 121 | "url": "https://github.com/sponsors/ljharb" 122 | } 123 | }, 124 | "node_modules/has": { 125 | "version": "1.0.3", 126 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 127 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 128 | "dependencies": { 129 | "function-bind": "^1.1.1" 130 | }, 131 | "engines": { 132 | "node": ">= 0.4.0" 133 | } 134 | }, 135 | "node_modules/has-symbols": { 136 | "version": "1.0.2", 137 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 138 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 139 | "engines": { 140 | "node": ">= 0.4" 141 | }, 142 | "funding": { 143 | "url": "https://github.com/sponsors/ljharb" 144 | } 145 | }, 146 | "node_modules/he": { 147 | "version": "1.2.0", 148 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 149 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 150 | "bin": { 151 | "he": "bin/he" 152 | } 153 | }, 154 | "node_modules/http-proxy": { 155 | "version": "1.18.1", 156 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 157 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 158 | "dependencies": { 159 | "eventemitter3": "^4.0.0", 160 | "follow-redirects": "^1.0.0", 161 | "requires-port": "^1.0.0" 162 | }, 163 | "engines": { 164 | "node": ">=8.0.0" 165 | } 166 | }, 167 | "node_modules/http-server": { 168 | "version": "0.12.3", 169 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 170 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 171 | "dependencies": { 172 | "basic-auth": "^1.0.3", 173 | "colors": "^1.4.0", 174 | "corser": "^2.0.1", 175 | "ecstatic": "^3.3.2", 176 | "http-proxy": "^1.18.0", 177 | "minimist": "^1.2.5", 178 | "opener": "^1.5.1", 179 | "portfinder": "^1.0.25", 180 | "secure-compare": "3.0.1", 181 | "union": "~0.5.0" 182 | }, 183 | "bin": { 184 | "hs": "bin/http-server", 185 | "http-server": "bin/http-server" 186 | }, 187 | "engines": { 188 | "node": ">=6" 189 | } 190 | }, 191 | "node_modules/lodash": { 192 | "version": "4.17.21", 193 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 194 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 195 | }, 196 | "node_modules/mime": { 197 | "version": "1.6.0", 198 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 199 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 200 | "bin": { 201 | "mime": "cli.js" 202 | }, 203 | "engines": { 204 | "node": ">=4" 205 | } 206 | }, 207 | "node_modules/minimist": { 208 | "version": "1.2.5", 209 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 210 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 211 | }, 212 | "node_modules/mkdirp": { 213 | "version": "0.5.5", 214 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 215 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 216 | "dependencies": { 217 | "minimist": "^1.2.5" 218 | }, 219 | "bin": { 220 | "mkdirp": "bin/cmd.js" 221 | } 222 | }, 223 | "node_modules/ms": { 224 | "version": "2.1.3", 225 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 226 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 227 | }, 228 | "node_modules/object-inspect": { 229 | "version": "1.11.0", 230 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 231 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", 232 | "funding": { 233 | "url": "https://github.com/sponsors/ljharb" 234 | } 235 | }, 236 | "node_modules/opener": { 237 | "version": "1.5.2", 238 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 239 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 240 | "bin": { 241 | "opener": "bin/opener-bin.js" 242 | } 243 | }, 244 | "node_modules/portfinder": { 245 | "version": "1.0.28", 246 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 247 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 248 | "dependencies": { 249 | "async": "^2.6.2", 250 | "debug": "^3.1.1", 251 | "mkdirp": "^0.5.5" 252 | }, 253 | "engines": { 254 | "node": ">= 0.12.0" 255 | } 256 | }, 257 | "node_modules/qs": { 258 | "version": "6.10.1", 259 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 260 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 261 | "dependencies": { 262 | "side-channel": "^1.0.4" 263 | }, 264 | "engines": { 265 | "node": ">=0.6" 266 | }, 267 | "funding": { 268 | "url": "https://github.com/sponsors/ljharb" 269 | } 270 | }, 271 | "node_modules/requires-port": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 274 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 275 | }, 276 | "node_modules/secure-compare": { 277 | "version": "3.0.1", 278 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 279 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 280 | }, 281 | "node_modules/side-channel": { 282 | "version": "1.0.4", 283 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 284 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 285 | "dependencies": { 286 | "call-bind": "^1.0.0", 287 | "get-intrinsic": "^1.0.2", 288 | "object-inspect": "^1.9.0" 289 | }, 290 | "funding": { 291 | "url": "https://github.com/sponsors/ljharb" 292 | } 293 | }, 294 | "node_modules/union": { 295 | "version": "0.5.0", 296 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 297 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 298 | "dependencies": { 299 | "qs": "^6.4.0" 300 | }, 301 | "engines": { 302 | "node": ">= 0.8.0" 303 | } 304 | }, 305 | "node_modules/url-join": { 306 | "version": "2.0.5", 307 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 308 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 309 | } 310 | }, 311 | "dependencies": { 312 | "async": { 313 | "version": "2.6.3", 314 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 315 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 316 | "requires": { 317 | "lodash": "^4.17.14" 318 | } 319 | }, 320 | "basic-auth": { 321 | "version": "1.1.0", 322 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 323 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 324 | }, 325 | "call-bind": { 326 | "version": "1.0.2", 327 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 328 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 329 | "requires": { 330 | "function-bind": "^1.1.1", 331 | "get-intrinsic": "^1.0.2" 332 | } 333 | }, 334 | "colors": { 335 | "version": "1.4.0", 336 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 337 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 338 | }, 339 | "corser": { 340 | "version": "2.0.1", 341 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 342 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" 343 | }, 344 | "debug": { 345 | "version": "3.2.7", 346 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 347 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 348 | "requires": { 349 | "ms": "^2.1.1" 350 | } 351 | }, 352 | "ecstatic": { 353 | "version": "3.3.2", 354 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 355 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 356 | "requires": { 357 | "he": "^1.1.1", 358 | "mime": "^1.6.0", 359 | "minimist": "^1.1.0", 360 | "url-join": "^2.0.5" 361 | } 362 | }, 363 | "eventemitter3": { 364 | "version": "4.0.7", 365 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 366 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 367 | }, 368 | "follow-redirects": { 369 | "version": "1.14.3", 370 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 371 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" 372 | }, 373 | "function-bind": { 374 | "version": "1.1.1", 375 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 376 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 377 | }, 378 | "get-intrinsic": { 379 | "version": "1.1.1", 380 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 381 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 382 | "requires": { 383 | "function-bind": "^1.1.1", 384 | "has": "^1.0.3", 385 | "has-symbols": "^1.0.1" 386 | } 387 | }, 388 | "has": { 389 | "version": "1.0.3", 390 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 391 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 392 | "requires": { 393 | "function-bind": "^1.1.1" 394 | } 395 | }, 396 | "has-symbols": { 397 | "version": "1.0.2", 398 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 399 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" 400 | }, 401 | "he": { 402 | "version": "1.2.0", 403 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 404 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 405 | }, 406 | "http-proxy": { 407 | "version": "1.18.1", 408 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 409 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 410 | "requires": { 411 | "eventemitter3": "^4.0.0", 412 | "follow-redirects": "^1.0.0", 413 | "requires-port": "^1.0.0" 414 | } 415 | }, 416 | "http-server": { 417 | "version": "0.12.3", 418 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 419 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 420 | "requires": { 421 | "basic-auth": "^1.0.3", 422 | "colors": "^1.4.0", 423 | "corser": "^2.0.1", 424 | "ecstatic": "^3.3.2", 425 | "http-proxy": "^1.18.0", 426 | "minimist": "^1.2.5", 427 | "opener": "^1.5.1", 428 | "portfinder": "^1.0.25", 429 | "secure-compare": "3.0.1", 430 | "union": "~0.5.0" 431 | } 432 | }, 433 | "lodash": { 434 | "version": "4.17.21", 435 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 436 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 437 | }, 438 | "mime": { 439 | "version": "1.6.0", 440 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 441 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 442 | }, 443 | "minimist": { 444 | "version": "1.2.5", 445 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 446 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 447 | }, 448 | "mkdirp": { 449 | "version": "0.5.5", 450 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 451 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 452 | "requires": { 453 | "minimist": "^1.2.5" 454 | } 455 | }, 456 | "ms": { 457 | "version": "2.1.3", 458 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 459 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 460 | }, 461 | "object-inspect": { 462 | "version": "1.11.0", 463 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 464 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" 465 | }, 466 | "opener": { 467 | "version": "1.5.2", 468 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 469 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" 470 | }, 471 | "portfinder": { 472 | "version": "1.0.28", 473 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 474 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 475 | "requires": { 476 | "async": "^2.6.2", 477 | "debug": "^3.1.1", 478 | "mkdirp": "^0.5.5" 479 | } 480 | }, 481 | "qs": { 482 | "version": "6.10.1", 483 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 484 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 485 | "requires": { 486 | "side-channel": "^1.0.4" 487 | } 488 | }, 489 | "requires-port": { 490 | "version": "1.0.0", 491 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 492 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 493 | }, 494 | "secure-compare": { 495 | "version": "3.0.1", 496 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 497 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 498 | }, 499 | "side-channel": { 500 | "version": "1.0.4", 501 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 502 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 503 | "requires": { 504 | "call-bind": "^1.0.0", 505 | "get-intrinsic": "^1.0.2", 506 | "object-inspect": "^1.9.0" 507 | } 508 | }, 509 | "union": { 510 | "version": "0.5.0", 511 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 512 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 513 | "requires": { 514 | "qs": "^6.4.0" 515 | } 516 | }, 517 | "url-join": { 518 | "version": "2.0.5", 519 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 520 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "npx http-server public" 9 | }, 10 | "keywords": [], 11 | "author": "erickwendel", 12 | "license": "ISC", 13 | "dependencies": { 14 | "http-server": "^0.12.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/public/app.js: -------------------------------------------------------------------------------- 1 | console.debug('hello world!') -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/public/img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula01/gdrive-webapp/public/img/profile.png -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS Expert Drive | Semana JS Expert 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
67 | Name 68 | OwnerLast ModifiedFile Size
movie Erick-NodeJS-Streams.mp4system_user27 de agosto de 2021 14:1065.6 GB
content_copy Xuxa-Da-Silva-Fundamentos-JS.pdfsystem_user27 de agosto de 2021 14:111 GB
image Jonathan-0092123213.pngsystem_user27 de agosto de 2021 14:125 MB
96 |
97 |
98 | 122 | 123 | -------------------------------------------------------------------------------- /aulas/aula01/gdrive-webapp/public/styles.css: -------------------------------------------------------------------------------- 1 | .card, 2 | .card-panel { 3 | padding: 15px 20px; 4 | box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14); 5 | } 6 | 7 | .container-fluid { 8 | padding: 1rem 2.5rem; 9 | margin: auto; 10 | } 11 | 12 | .row { 13 | margin: 0 -0.75rem; 14 | } 15 | 16 | .main { 17 | position: absolute; 18 | width: calc(100% - 250px); 19 | top: 125px; 20 | margin-left: 250px; 21 | } 22 | 23 | .subheader { 24 | color: rgba(0, 0, 0, 0.54); 25 | font-weight: 500; 26 | } 27 | 28 | /* nav */ 29 | nav { 30 | box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3); 31 | } 32 | 33 | nav ul li { 34 | text-align: center; 35 | } 36 | 37 | nav ul.right { 38 | padding-right: 12px; 39 | } 40 | 41 | nav ul.right li { 42 | max-width: 48px; 43 | } 44 | 45 | nav ul a { 46 | padding: 0 12px; 47 | } 48 | 49 | nav ul a img { 50 | height: 32px; 51 | width: 32px; 52 | vertical-align: middle; 53 | margin-left: -5px; 54 | } 55 | 56 | .nav-wrapper { 57 | padding-left: 12px; 58 | } 59 | 60 | .nav-wrapper ul a:hover { 61 | background-color: transparent; 62 | } 63 | 64 | .nav-wrapper .title { 65 | font-size: 1.4rem; 66 | } 67 | 68 | .nav-wrapper .btn-flat { 69 | background-color: #4285f4 !important; 70 | font-size: 13px; 71 | font-weight: 500; 72 | height: 30px; 73 | line-height: 30px; 74 | width: 94px; 75 | } 76 | 77 | .nav-2, 78 | .nav-2 i { 79 | height: 56px !important; 80 | line-height: 56px !important; 81 | min-height: 56px !important; 82 | } 83 | 84 | .search-wrapper { 85 | margin: 10px auto 0 170px; 86 | width: calc(100% - 450px); 87 | max-width: 650px; 88 | height: 46px; 89 | position: fixed; 90 | } 91 | 92 | .search-wrapper i { 93 | color: #757575; 94 | position: absolute; 95 | font-size: 24px; 96 | top: 5px; 97 | left: 24px; 98 | line-height: 38px !important; 99 | } 100 | 101 | input[type=search]:not(.browser-default) { 102 | display: block; 103 | padding: 11px 8px 11px 72px; 104 | width: 100%; 105 | background: #f5f5f5; 106 | height: 24px; 107 | border: none; 108 | font-size: 16px; 109 | outline: none; 110 | border-radius: 2px; 111 | color: #757575; 112 | } 113 | 114 | input[type=search]:focus { 115 | border-bottom: none !important; 116 | box-shadow: none !important; 117 | } 118 | 119 | input[type=search]::placeholder { 120 | color: #757575; 121 | } 122 | 123 | /* sidenav */ 124 | .side-nav.floating { 125 | width: 250px; 126 | padding: 60px 8px 0 !important; 127 | height: calc(100% - 130px); 128 | left: initial; 129 | right: initial; 130 | top: 125px; 131 | transform: initial; 132 | z-index: auto; 133 | margin: 0.5rem 0 1rem 0; 134 | border-radius: 2px; 135 | background: transparent; 136 | box-shadow: none; 137 | } 138 | 139 | .side-nav .divider { 140 | margin: 8px 0; 141 | } 142 | 143 | .side-nav .active { 144 | background-color: rgba(0, 0, 0, 0.05); 145 | } 146 | 147 | .side-nav .active a { 148 | color: #212121; 149 | font-weight: 500; 150 | } 151 | 152 | .side-nav .subheader { 153 | line-height: 24px; 154 | height: 32px; 155 | margin: 0; 156 | padding: 4px 16px; 157 | color: #616161; 158 | font-weight: normal; 159 | font-size: 13px; 160 | } 161 | 162 | .side-nav li>a, 163 | .side-nav li>a>i.material-icons { 164 | height: 40px; 165 | line-height: 40px; 166 | } 167 | 168 | .side-nav li>a>i.material-icons { 169 | margin-right: 24px; 170 | } 171 | 172 | .side-nav li>a { 173 | padding: 0 16px; 174 | font-weight: normal; 175 | font-size: 13px; 176 | color: #616161; 177 | } 178 | 179 | .side-nav li>a:hover { 180 | border-radius: 2px; 181 | } 182 | 183 | /* folders */ 184 | .folder { 185 | width: 185px; 186 | display: inline-block; 187 | margin: 15px 20px 15px 0; 188 | font-weight: 500; 189 | } 190 | 191 | .folder i { 192 | color: rgba(0, 0, 0, 0.54); 193 | margin-top: -3px; 194 | } 195 | 196 | .material-icons.red600 { 197 | color: #fb3b00; 198 | } 199 | 200 | .material-icons.yellow600 { 201 | color: #d0da08; 202 | } 203 | 204 | .modal { 205 | word-wrap: normal; 206 | 207 | } 208 | #progress-bar { 209 | width: 100%; 210 | } 211 | #output { 212 | font-size: 20px; 213 | word-wrap: break-word; 214 | } 215 | 216 | /* drag n drop */ 217 | 218 | .drop-area { 219 | border: 2px dashed #ccc; 220 | border-radius: 20px; 221 | width: 100%; 222 | margin: 50px auto; 223 | padding: 20px; 224 | } 225 | .drop-area.highlight { 226 | border-color: purple; 227 | } 228 | #fileElem { 229 | display: none; 230 | } -------------------------------------------------------------------------------- /aulas/aula02/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Clone - Semana JS Expert 5.0 2 | 3 | Seja bem vindo(a) à quinta Semana Javascript Expert. Este é o código inicial para iniciar nossa jornada. 4 | 5 | Marque esse projeto com uma estrela 🌟 6 | 7 | ## Preview 8 | 9 | ![](./resources/demo.gif) 10 | 11 | 12 | ## Checklist Features 13 | 14 | - Web API 15 | - [x] Deve listar arquivos baixados 16 | - [x] Deve receber stream de arquivos e salvar em disco 17 | - [x] Deve notificar sobre progresso de armazenamento de arquivos em disco 18 | - [x] Deve permitir upload de arquivos em formato image, video ou audio 19 | - [x] Deve atingir 100% de cobertura de código em testes 20 | 21 | - Web App 22 | - [] Deve listar arquivos baixados 23 | - [] Deve permitir fazer upload de arquivos de qualquer tamanho 24 | - [] Deve ter função de upload via botão 25 | - [] Deve exibir progresso de upload 26 | - [] Deve ter função de upload via drag and drop 27 | 28 | 29 | 30 | ## Desafios para alunos pós projeto 31 | 32 | 1. *Backend*: Salvar o arquivo na AWS ou qualquer serviço de storage 33 | - Nosso projeto hoje armazena arquivos em disco. o desafio é você via Stream, fazer upload para algum serviço na nuvem 34 | - Como plus, manter 100% de code coverage, ou seja, crie testes para sua nova feature 35 | 2. *Frontend*: Adicionar testes no frontend e alcançar 100% de code coverage 36 | - Você aprendeu como fazer testes no backend. Usar o mesmo processo para criar testes unitários no frontend com Jest 37 | - Caso tenha duvidas, acesse o [exemplo](https://github.com/ErickWendel/tdd-frontend-example) e deixe uma estrela! 38 | 3. *Infraestrutura*: Publicar aplicação com seu SSL customizado em máquina virtual 39 | - Você aprendeu a gerar SSL local, o desafio é você criar um certificado (pode ser com o *Let's Encrypt*) e adicionar na sua aplicação 40 | 41 | ### Considerações 42 | - Tire suas dúvidas sobre os desafios em nossa comunidade, o objetivo é você aprender de forma divertida. Surgiu dúvidas? Pergunte por lá! 43 | 44 | - Ao completar qualquer um dos desafios, envie no canal **#desafios** da comunidade no **Discord** 45 | 46 | ## Créditos ao Layout <3 47 | 48 | - O Layout foi adaptado a partir do projeto do brasileiro [Leonardo Santo](https://github.com/leoespsanto) disponibilizado no [codepen](https://codepen.io/leoespsanto/pen/KZMMKG). -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/certificates/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdzCCAt+gAwIBAgIRAPzlKLsx59gDukI5AIpXqBwwDQYJKoZIhvcNAQELBQAw 3 | gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqZXJp 4 | Y2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5kZWwpMTowOAYDVQQD 5 | DDFta2NlcnQgZXJpY2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5k 6 | ZWwpMB4XDTIxMDkwMzE4MTIxNVoXDTIzMTIwMzE4MTIxNVowXjEnMCUGA1UEChMe 7 | bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCplcmlja3dl 8 | bmRlbEBFcmlja1dlbmRlbHNNQlAgKEVyaWNrIFdlbmRlbCkwggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQD3K1QBNBa5kbKwAFEj76QJNErcJoUGh9g7LX0b 10 | SbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/sP2xZLDmxkMufG6QL7fw64to 11 | BRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZRzJDPAUGhkLfswaFfqDemp6U1 12 | KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIcOpGEhgwKA4QYawuIgGUcSbys 13 | z/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv5pjO3X70WNRPb0WbszoPLzKO 14 | Ckj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLPSHBTLqEPAgMBAAGjfDB6MA4G 15 | A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQt 16 | MxAhQKNc8uLS9BClcrjiEtdOqTAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACH 17 | BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJjILvMJ 18 | jUhqj99UGS5rdz0ksz3H0Mkn/ffDh+xMd6QHyUia0RQblolL4sjH+ZeZZWpcDfnb 19 | jQ9VN2zpPvZGPE4QykfAwYTK5mUxF24LwBKMelAmTc1xdxqx9AB93hocnxeKZKEt 20 | /Hk7U62ozssJyTmfA2TUrDuXKwNWiTpiazLoy7FaHkqe3dvmRJkjqgYkebHY1PtL 21 | CJB6IPe3CRpayc/AZGtd7GiBVlsMl4wDiKE4lYGb11943cZgcUwbuGHTdye8wf3g 22 | 1dB84AkXJSBEd8Kc7hokTuTdBlBWKWSUln7wHTmLfZ6jEuh6bDI3cn8IU5bAnAG7 23 | JmBe/i4zDlsA0zdhVM0y/MFL+o1m+znWYKApneuRWH9QULe4A9J74J7eaD5x4quv 24 | NmUDGGa5HyLcPM5WfJeJknAURE4wlT+T5C36X5DERCbrGsXiikcUqXJ8PHudm3qb 25 | gtitoc5+LEOC9TrTcInpym/qN7rbBMJd57kfGNBA9stg7cPAQsgxHZmR1g== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3K1QBNBa5kbKw 3 | AFEj76QJNErcJoUGh9g7LX0bSbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/ 4 | sP2xZLDmxkMufG6QL7fw64toBRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZR 5 | zJDPAUGhkLfswaFfqDemp6U1KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIc 6 | OpGEhgwKA4QYawuIgGUcSbysz/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv 7 | 5pjO3X70WNRPb0WbszoPLzKOCkj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLP 8 | SHBTLqEPAgMBAAECggEAYcoxtTwvlYHQiZPGhopEgyO+TnWrAddzNI6dxdMW885m 9 | grdClU8DCn0qsLYrN0fmA8CeDmvFc/kOFVOoO6wT1WoxoE24zLEkfFwkzqXbwfvm 10 | S8vWnjAjTZ6yaoRMe7suenkcXIQSC0pFgv2FeXEMz2hbbH97YkZCplwDoBGJshjv 11 | CdVgykGokzhvhp05xm+oayX/zEyxw7jp9o6ZpeTs2dphj9pXXcEkg8uR5CxyummF 12 | nSqwJZxmZ2HtEQAL7yZfli0vVbTlBa9L4JokS7xoqur15B1hiQIJBYZMJXHHr5tH 13 | YQfVP0zKGtWMIQNRYfjYKn3bYt4yqmHR9wBpV2SFOQKBgQD6DGquLcAt1M2VAUSd 14 | NSQ4WByADiXbw8R/CDdjAhHJ45Jz/3qDeRUYQ6YK9pDBpVjZdEKIm2SNG2tyrY87 15 | EhTQDuoyklQ/zFA5gdCQeikGVBidSHTarUxnHCgoNVPsk2pL7cVZnd7oIUkcGs+H 16 | kR8cqo/gJpHX17gZhp9oXEo2CwKBgQD9DV4h4Hm7aB58Vve5JvnNDG8pQP8FEd+5 17 | P6BUfJBovxFeWXOTPtq8QU2xTL1NHrimq1KG2iTj+11FSS+4zFaDJAZUYQ4tgn4o 18 | OKexDcJ6JzC8XOk974WE7StqjX6n1LqTqWMQ3Bnd3gEibjNzLVmHluh+SN3CLooI 19 | RjK6Yie3jQKBgCzJ6pX2dfT/qC9ngb3TFgDNr5U0c42Q3HKQqzMd3MfX7pS+j1hb 20 | aO7mtyhBkB5PmsGgtIY5p2IrJiztb7l5/KZj9YlHcrXWyAv098HZT93lVF9f6iZ9 21 | YjEZ9wt0ueqnYSPmnDH4OERGKg1RtBipYvREjO7umbMa3cwctBMCbPyPAoGBAKbW 22 | zm5Ves0Vq6vdBvz69o27mfrAEKN+Elwn2AR8EBYPi1sCbRHyyfJ+t8Oizdhv3dx9 23 | bi7c2p+5VdhdlWooxw01jjrJtrhIpfbMy7sPUF6LQjWeqGUea5Clcg+RdKUgu1ap 24 | wlgWVbOTMHpL3/4bM0ETPPwt/I+PcZBdAAsktfztAoGBAOO91AnDsTrFiNtU2IVv 25 | 9tc83nFttWmSmoaiz5JeXN98Fo0e8l/uLT3mKcD7ue3lh2qr6CxKZdlfopiFsCi3 26 | s0IuN7mSgStbZdK0km0vu1+N0cqMR+mvqCbYNk4cW690snXsWY3uVVJpDfRlwUDu 27 | HH0k++iNL4d/6FE571Ua8IUK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/jest.config.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | */ 4 | 5 | export default { 6 | clearMocks: true, 7 | restoreMocks: true, 8 | // collectCoverage: true, 9 | coverageDirectory: "coverage", 10 | coverageProvider: "v8", 11 | coverageReporters: [ 12 | "text", 13 | "lcov" 14 | ], 15 | testEnvironment: "node", 16 | coverageThreshold: { 17 | global: { 18 | branches: 100, 19 | functions: 100, 20 | lines: 100, 21 | statements: 100 22 | } 23 | }, 24 | watchPathIgnorePatterns: [ 25 | "node_modules" 26 | ], 27 | transformIgnorePatterns: ["node_modules"], 28 | collectCoverageFrom: [ 29 | "src/**/*.js", "!src/**/index.js" 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "mkdir -p downloads && node src/index.js", 9 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand", 10 | "test:watch": "NODE_OPTIONS=--experimental-vm-modules npx jest --watch --runInBand", 11 | "test:cov": "NODE_OPTIONS=--experimental-vm-modules npx jest --no-cache --runInBand --coverage" 12 | }, 13 | "keywords": [], 14 | "author": "erickwendel", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "jest": "^27.1.0" 18 | }, 19 | "dependencies": { 20 | "busboy": "^0.3.1", 21 | "form-data": "4.0", 22 | "pino": "6.8", 23 | "pino-pretty": "5.1", 24 | "pretty-bytes": "5.6", 25 | "socket.io": "4.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/src/fileHelper.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import prettyBytes from 'pretty-bytes' 3 | 4 | export default class FileHelper { 5 | static async getFilesStatus(downloadsFolder) { 6 | const currentFiles = await fs.promises.readdir(downloadsFolder) 7 | const statuses = await Promise 8 | .all( 9 | currentFiles 10 | .map( 11 | file => fs.promises.stat(`${downloadsFolder}/${file}`) 12 | ) 13 | ) 14 | const filesStatuses = [] 15 | for (const fileIndex in currentFiles) { 16 | const { birthtime, size } = statuses[fileIndex] 17 | filesStatuses.push({ 18 | size: prettyBytes(size), 19 | file: currentFiles[fileIndex], 20 | lastModified: birthtime, 21 | owner: process.env.USER 22 | }) 23 | } 24 | 25 | return filesStatuses 26 | } 27 | } -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/src/index.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | import fs from 'fs' 3 | import { logger } from './logger.js' 4 | import { Server } from 'socket.io' 5 | import Routes from './routes.js' 6 | 7 | const PORT = process.env.PORT || 3000 8 | 9 | const localHostSSL = { 10 | key: fs.readFileSync('./certificates/key.pem'), 11 | cert: fs.readFileSync('./certificates/cert.pem'), 12 | } 13 | const routes = new Routes() 14 | const server = https.createServer( 15 | localHostSSL, 16 | routes.handler.bind(routes) 17 | ) 18 | 19 | const io = new Server(server, { 20 | cors: { 21 | origin: '*', 22 | credentials: false 23 | } 24 | }) 25 | 26 | routes.setSocketInstance(io) 27 | 28 | io.on("connection", (socket) => logger.info(`someone connected: ${socket.id}`)) 29 | 30 | const startServer = () => { 31 | const { address, port } = server.address() 32 | logger.info(`app running at https://${address}:${port}`) 33 | } 34 | 35 | server.listen(PORT, startServer) -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/src/logger.js: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | const logger = pino({ 3 | prettyPrint: { 4 | ignore: 'pid,hostname' 5 | } 6 | }) 7 | 8 | export { 9 | logger, 10 | } -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/src/routes.js: -------------------------------------------------------------------------------- 1 | import FileHelper from "./fileHelper.js" 2 | import { logger } from "./logger.js" 3 | import { dirname, resolve } from 'path' 4 | import { fileURLToPath, parse } from 'url' 5 | import UploadHandler from "./uploadHandler.js" 6 | import { pipeline } from "stream/promises" 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)) 9 | const defaultDownloadsFolder = resolve(__dirname, '../', "downloads") 10 | 11 | export default class Routes { 12 | constructor(downloadsFolder = defaultDownloadsFolder) { 13 | this.downloadsFolder = downloadsFolder 14 | this.fileHelper = FileHelper 15 | this.io = {} 16 | } 17 | 18 | setSocketInstance(io) { 19 | this.io = io 20 | } 21 | 22 | async defaultRoute(request, response) { 23 | response.end('hello world') 24 | } 25 | 26 | async options(request, response) { 27 | response.writeHead(204) 28 | response.end() 29 | } 30 | 31 | async post(request, response) { 32 | const { headers } = request 33 | 34 | const { query: { socketId } } = parse(request.url, true) 35 | const uploadHandler = new UploadHandler({ 36 | socketId, 37 | io: this.io, 38 | downloadsFolder: this.downloadsFolder 39 | }) 40 | 41 | const onFinish = (response) => () => { 42 | response.writeHead(200) 43 | const data = JSON.stringify({ result: 'Files uploaded with success! ' }) 44 | response.end(data) 45 | } 46 | 47 | const busboyInstance = uploadHandler.registerEvents( 48 | headers, 49 | onFinish(response) 50 | ) 51 | 52 | await pipeline( 53 | request, 54 | busboyInstance 55 | ) 56 | 57 | logger.info('Request finished with success!') 58 | } 59 | 60 | async get(request, response) { 61 | const files = await this.fileHelper.getFilesStatus(this.downloadsFolder) 62 | 63 | response.writeHead(200) 64 | response.end(JSON.stringify(files)) 65 | } 66 | 67 | handler(request, response) { 68 | response.setHeader('Access-Control-Allow-Origin', '*') 69 | const chosen = this[request.method.toLowerCase()] || this.defaultRoute 70 | 71 | return chosen.apply(this, [request, response]) 72 | } 73 | } -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/src/uploadHandler.js: -------------------------------------------------------------------------------- 1 | import Busboy from 'busboy' 2 | import fs from 'fs' 3 | import { pipeline } from 'stream/promises' 4 | import { logger } from './logger.js' 5 | export default class UploadHandler { 6 | constructor({ io, socketId, downloadsFolder, messageTimeDelay = 200 }) { 7 | this.io = io 8 | this.socketId = socketId 9 | this.downloadsFolder = downloadsFolder 10 | this.ON_UPLOAD_EVENT = 'file-upload' 11 | this.messageTimeDelay = messageTimeDelay 12 | } 13 | 14 | canExecute(lastExecution) { 15 | return (Date.now() - lastExecution) >= this.messageTimeDelay 16 | } 17 | 18 | handleFileBytes(filename) { 19 | this.lastMessageSent = Date.now() 20 | 21 | async function* handleData(source) { 22 | let processedAlready = 0 23 | 24 | for await (const chunk of source) { 25 | yield chunk 26 | 27 | processedAlready += chunk.length 28 | if (!this.canExecute(this.lastMessageSent)) { 29 | continue; 30 | } 31 | 32 | this.lastMessageSent = Date.now() 33 | 34 | this.io.to(this.socketId).emit(this.ON_UPLOAD_EVENT, { processedAlready, filename }) 35 | logger.info(`File [${filename}] got ${processedAlready} bytes to ${this.socketId}`) 36 | } 37 | } 38 | 39 | return handleData.bind(this) 40 | } 41 | async onFile(fieldname, file, filename) { 42 | const saveTo = `${this.downloadsFolder}/${filename}` 43 | 44 | await pipeline( 45 | // 1o passo, pegar uma readable stream! 46 | file, 47 | // 2o passo, filtrar, converter, transformar dados! 48 | this.handleFileBytes.apply(this, [filename]), 49 | // 3o passo, é saida do processo, uma writable stream! 50 | fs.createWriteStream(saveTo) 51 | ) 52 | 53 | logger.info(`File [${filename}] finished`) 54 | } 55 | 56 | registerEvents(headers, onFinish) { 57 | const busboy = new Busboy({ headers }) 58 | busboy.on("file", this.onFile.bind(this)) 59 | busboy.on("finish", onFinish) 60 | 61 | return busboy 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/_util/testUtil.js: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals' 2 | import { Readable, Writable, Transform } from 'stream' 3 | export default class TestUtil { 4 | 5 | // 00:01 6 | // 00:02 7 | // 00:03 8 | 9 | 10 | 11 | static mockDateNow(mockImplementationPeriods) { 12 | 13 | const now = jest.spyOn(global.Date, global.Date.now.name) 14 | 15 | mockImplementationPeriods.forEach(time => { 16 | now.mockReturnValueOnce(time); 17 | }) 18 | 19 | } 20 | 21 | static getTimeFromDate(dateString) { 22 | return new Date(dateString).getTime() 23 | } 24 | 25 | 26 | static generateReadableStream(data) { 27 | return new Readable({ 28 | objectMode: true, 29 | read() { 30 | for (const item of data) { 31 | this.push(item) 32 | } 33 | 34 | this.push(null) 35 | } 36 | }) 37 | } 38 | static generateWritableStream(onData) { 39 | return new Writable({ 40 | objectMode: true, 41 | write(chunk, encondig, cb) { 42 | onData(chunk) 43 | 44 | cb(null, chunk) 45 | } 46 | }) 47 | } 48 | 49 | static generateTransformStream(onData) { 50 | // async function *(source) { 51 | // for await(const chunk of data) { 52 | // yield chunk 53 | // } 54 | // } 55 | 56 | return new Transform({ 57 | objectMode: true, 58 | transform(chunk, enconding, cb) { 59 | onData(chunk) 60 | cb(null, chunk) 61 | } 62 | }) 63 | } 64 | } -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/integration/mocks/semanajsexpert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula02/gdrive-webapi/test/integration/mocks/semanajsexpert.png -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/integration/routes.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | beforeAll, 6 | afterAll, 7 | jest 8 | } from '@jest/globals' 9 | import fs from 'fs' 10 | import FileHelper from '../../src/fileHelper.js' 11 | 12 | import Routes from './../../src/routes.js' 13 | import FormData from 'form-data' 14 | import TestUtil from '../_util/testUtil.js' 15 | import { logger } from '../../src/logger.js' 16 | import { tmpdir } from 'os' 17 | import { join } from 'path' 18 | 19 | describe('#Routes Integration Test', () => { 20 | let defaultDownloadsFolder = '' 21 | beforeAll(async () => { 22 | defaultDownloadsFolder = await fs.promises.mkdtemp(join(tmpdir(), 'downloads-')) 23 | }) 24 | 25 | afterAll(async () => { 26 | await fs.promises.rm(defaultDownloadsFolder, { recursive: true }) 27 | }) 28 | 29 | beforeEach(() => { 30 | jest.spyOn(logger, 'info') 31 | .mockImplementation() 32 | }) 33 | 34 | describe('#getFileStatus', () => { 35 | const ioObj = { 36 | to: (id) => ioObj, 37 | emit: (event, message) => { } 38 | } 39 | 40 | test('should upload file to the folder', async () => { 41 | const filename = 'semanajsexpert.png' 42 | const fileStream = fs.createReadStream(`./test/integration/mocks/${filename}`) 43 | const response = TestUtil.generateWritableStream(() => { }) 44 | 45 | const form = new FormData() 46 | form.append('photo', fileStream) 47 | 48 | 49 | const defaultParams = { 50 | request: Object.assign(form, { 51 | headers: form.getHeaders(), 52 | method: 'POST', 53 | url: '?socketId=10' 54 | }), 55 | 56 | response: Object.assign(response, { 57 | setHeader: jest.fn(), 58 | writeHead: jest.fn(), 59 | end: jest.fn() 60 | }), 61 | values: () => Object.values(defaultParams) 62 | } 63 | 64 | const routes = new Routes(defaultDownloadsFolder) 65 | routes.setSocketInstance(ioObj) 66 | const dirBeforeRan = await fs.promises.readdir(defaultDownloadsFolder) 67 | expect(dirBeforeRan).toEqual([]) 68 | await routes.handler(...defaultParams.values()) 69 | const dirAfterRan = await fs.promises.readdir(defaultDownloadsFolder) 70 | expect(dirAfterRan).toEqual([filename]) 71 | 72 | expect(defaultParams.response.writeHead).toHaveBeenCalledWith(200) 73 | const expectedResult = JSON.stringify({ result: 'Files uploaded with success! ' }) 74 | expect(defaultParams.response.end).toHaveBeenCalledWith(expectedResult) 75 | 76 | }) 77 | }) 78 | }) -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/unit/fileHelper.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | jest 6 | } from '@jest/globals' 7 | import fs from 'fs' 8 | import FileHelper from '../../src/fileHelper.js' 9 | 10 | import Routes from './../../src/routes.js' 11 | 12 | describe('#FileHelper', () => { 13 | 14 | describe('#getFileStatus', () => { 15 | test('it should return files statuses in correct format', async () => { 16 | const statMock = { 17 | dev: 16777220, 18 | mode: 33188, 19 | nlink: 1, 20 | uid: 501, 21 | gid: 20, 22 | rdev: 0, 23 | blksize: 4096, 24 | ino: 214187433, 25 | size: 188188, 26 | blocks: 368, 27 | atimeMs: 1630702590337.3582, 28 | mtimeMs: 1630702588444.2876, 29 | ctimeMs: 1630702588452.0754, 30 | birthtimeMs: 1630702588443.3276, 31 | atime: '2021-09-03T20:56:30.337Z', 32 | mtime: '2021-09-03T20:56:28.444Z', 33 | ctime: '2021-09-03T20:56:28.452Z', 34 | birthtime: '2021-09-03T20:56:28.443Z' 35 | } 36 | 37 | const mockUser = 'erickwendel' 38 | process.env.USER = mockUser 39 | const filename = 'file.png' 40 | 41 | jest.spyOn(fs.promises, fs.promises.readdir.name) 42 | .mockResolvedValue([filename]) 43 | 44 | jest.spyOn(fs.promises, fs.promises.stat.name) 45 | .mockResolvedValue(statMock) 46 | 47 | const result = await FileHelper.getFilesStatus("/tmp") 48 | 49 | const expectedResult = [ 50 | { 51 | size: "188 kB", 52 | lastModified: statMock.birthtime, 53 | owner: mockUser, 54 | file: filename 55 | } 56 | ] 57 | 58 | expect(fs.promises.stat).toHaveBeenCalledWith(`/tmp/${filename}`) 59 | expect(result).toMatchObject(expectedResult) 60 | }) 61 | }) 62 | }) -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/unit/routes.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | beforeEach, 5 | expect, 6 | jest 7 | } from '@jest/globals' 8 | import { logger } from '../../src/logger.js' 9 | import UploadHandler from '../../src/uploadHandler.js' 10 | import TestUtil from '../_util/testUtil.js' 11 | import Routes from './../../src/routes.js' 12 | 13 | describe('#Routes test suite', () => { 14 | beforeEach(() => { 15 | jest.spyOn(logger, 'info') 16 | .mockImplementation() 17 | }) 18 | 19 | const request = TestUtil.generateReadableStream(['some file bytes']) 20 | const response = TestUtil.generateWritableStream(() => { }) 21 | 22 | const defaultParams = { 23 | request: Object.assign(request, { 24 | headers: { 25 | 'Content-Type': 'multipart/form-data' 26 | }, 27 | method: '', 28 | body: {} 29 | }), 30 | response: Object.assign(response, { 31 | setHeader: jest.fn(), 32 | writeHead: jest.fn(), 33 | end: jest.fn() 34 | }), 35 | values: () => Object.values(defaultParams) 36 | } 37 | 38 | describe('#setSocketInstance', () => { 39 | test('setSocket should store io instance', () => { 40 | const routes = new Routes() 41 | const ioObj = { 42 | to: (id) => ioObj, 43 | emit: (event, message) => { } 44 | } 45 | 46 | routes.setSocketInstance(ioObj) 47 | expect(routes.io).toStrictEqual(ioObj) 48 | }) 49 | }) 50 | 51 | describe('#handler', () => { 52 | 53 | 54 | test('given an inexistent route it should choose default route', async () => { 55 | const routes = new Routes() 56 | const params = { 57 | ...defaultParams 58 | } 59 | 60 | params.request.method = 'inexistent' 61 | await routes.handler(...params.values()) 62 | expect(params.response.end).toHaveBeenCalledWith('hello world') 63 | }) 64 | 65 | test('it should set any request with CORS enabled', async () => { 66 | const routes = new Routes() 67 | const params = { 68 | ...defaultParams 69 | } 70 | 71 | params.request.method = 'inexistent' 72 | await routes.handler(...params.values()) 73 | expect(params.response.setHeader) 74 | .toHaveBeenCalledWith('Access-Control-Allow-Origin', '*') 75 | }) 76 | 77 | test('given method OPTIONS it should choose options route', async () => { 78 | const routes = new Routes() 79 | const params = { 80 | ...defaultParams 81 | } 82 | 83 | params.request.method = 'OPTIONS' 84 | await routes.handler(...params.values()) 85 | expect(params.response.writeHead).toHaveBeenCalledWith(204) 86 | expect(params.response.end).toHaveBeenCalled() 87 | }) 88 | 89 | test('given method POST it should choose post route', async () => { 90 | const routes = new Routes() 91 | const params = { 92 | ...defaultParams 93 | } 94 | 95 | params.request.method = 'POST' 96 | jest.spyOn(routes, routes.post.name).mockResolvedValue() 97 | 98 | await routes.handler(...params.values()) 99 | expect(routes.post).toHaveBeenCalled() 100 | 101 | }) 102 | test('given method GET it should choose get route', async () => { 103 | const routes = new Routes() 104 | const params = { 105 | ...defaultParams 106 | } 107 | jest.spyOn(routes, routes.get.name).mockResolvedValue() 108 | 109 | params.request.method = 'GET' 110 | await routes.handler(...params.values()) 111 | expect(routes.get).toHaveBeenCalled() 112 | 113 | }) 114 | }) 115 | 116 | describe('#get', () => { 117 | test('given method GET it should list all files downloaded', async () => { 118 | const routes = new Routes() 119 | const params = { 120 | ...defaultParams 121 | } 122 | 123 | const filesStatusesMock = [ 124 | { 125 | size: "188 kB", 126 | lastModified: '2021-09-03T20:56:28.443Z', 127 | owner: 'erickwendel', 128 | file: 'file.txt' 129 | } 130 | ] 131 | jest.spyOn(routes.fileHelper, routes.fileHelper.getFilesStatus.name) 132 | .mockResolvedValue(filesStatusesMock) 133 | 134 | params.request.method = 'GET' 135 | await routes.handler(...params.values()) 136 | 137 | 138 | expect(params.response.writeHead).toHaveBeenCalledWith(200) 139 | expect(params.response.end).toHaveBeenCalledWith(JSON.stringify(filesStatusesMock)) 140 | 141 | }) 142 | }) 143 | 144 | describe('#post', () => { 145 | test('it should validate post route workflow', async () => { 146 | const routes = new Routes('/tmp') 147 | const options = { 148 | ...defaultParams 149 | } 150 | options.request.method = 'POST' 151 | options.request.url = '?socketId=10' 152 | 153 | 154 | jest.spyOn( 155 | UploadHandler.prototype, 156 | UploadHandler.prototype.registerEvents.name 157 | ).mockImplementation((headers, onFinish) => { 158 | const writable = TestUtil.generateWritableStream(() => {}) 159 | writable.on("finish", onFinish) 160 | 161 | return writable 162 | }) 163 | 164 | await routes.handler(...options.values()) 165 | 166 | expect(UploadHandler.prototype.registerEvents).toHaveBeenCalled() 167 | expect(options.response.writeHead).toHaveBeenCalledWith(200) 168 | 169 | const expectedResult = JSON.stringify({ result: 'Files uploaded with success! ' }) 170 | expect(options.response.end).toHaveBeenCalledWith(expectedResult) 171 | 172 | }) 173 | }) 174 | }) 175 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapi/test/unit/uploadHandler.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | beforeEach, 6 | jest 7 | } from '@jest/globals' 8 | import fs from 'fs' 9 | import { resolve } from 'path' 10 | import { pipeline } from 'stream/promises' 11 | import { logger } from '../../src/logger.js' 12 | import UploadHandler from '../../src/uploadHandler.js' 13 | import TestUtil from '../_util/testUtil.js' 14 | import Routes from './../../src/routes.js' 15 | 16 | describe('#UploadHandler test suite', () => { 17 | const ioObj = { 18 | to: (id) => ioObj, 19 | emit: (event, message) => { } 20 | } 21 | beforeEach(() => { 22 | jest.spyOn(logger, 'info') 23 | .mockImplementation() 24 | }) 25 | 26 | 27 | describe('#registerEvents', () => { 28 | test('should call onFile and onFinish functions on Busboy instance', () => { 29 | const uploadHandler = new UploadHandler({ 30 | io: ioObj, 31 | socketId: '01' 32 | }) 33 | 34 | jest.spyOn(uploadHandler, uploadHandler.onFile.name) 35 | .mockResolvedValue() 36 | 37 | const headers = { 38 | 'content-type': 'multipart/form-data; boundary=' 39 | } 40 | const onFinish = jest.fn() 41 | const busboyInstance = uploadHandler.registerEvents(headers, onFinish) 42 | 43 | const fileStream = TestUtil.generateReadableStream(['chunk', 'of', 'data']) 44 | busboyInstance.emit('file', 'fieldname', fileStream, 'filename.txt') 45 | 46 | busboyInstance.listeners("finish")[0].call() 47 | 48 | expect(uploadHandler.onFile).toHaveBeenCalled() 49 | expect(onFinish).toHaveBeenCalled() 50 | }) 51 | 52 | }) 53 | 54 | describe('#onFile', () => { 55 | test('given a stream file it should save it on disk', async () => { 56 | const chunks = ['hey', 'dude'] 57 | const downloadsFolder = '/tmp' 58 | const handler = new UploadHandler({ 59 | io: ioObj, 60 | socketId: '01', 61 | downloadsFolder 62 | }) 63 | 64 | const onData = jest.fn() 65 | 66 | jest.spyOn(fs, fs.createWriteStream.name) 67 | .mockImplementation(() => TestUtil.generateWritableStream(onData)) 68 | 69 | const onTransform = jest.fn() 70 | jest.spyOn(handler, handler.handleFileBytes.name) 71 | .mockImplementation(() => TestUtil.generateTransformStream(onTransform)) 72 | 73 | const params = { 74 | fieldname: 'video', 75 | file: TestUtil.generateReadableStream(chunks), 76 | filename: 'mockFile.mov' 77 | } 78 | await handler.onFile(...Object.values(params)) 79 | 80 | expect(onData.mock.calls.join()).toEqual(chunks.join()) 81 | expect(onTransform.mock.calls.join()).toEqual(chunks.join()) 82 | 83 | const expectedFilename = resolve(handler.downloadsFolder, params.filename) 84 | expect(fs.createWriteStream).toHaveBeenCalledWith(expectedFilename) 85 | 86 | 87 | }) 88 | }) 89 | 90 | 91 | describe('#handleFileBytes', () => { 92 | test('should call emit function and it is a transform stream', async () => { 93 | jest.spyOn(ioObj, ioObj.to.name) 94 | jest.spyOn(ioObj, ioObj.emit.name) 95 | 96 | const handler = new UploadHandler({ 97 | io: ioObj, 98 | socketId: '01' 99 | }) 100 | 101 | jest.spyOn(handler, handler.canExecute.name) 102 | .mockReturnValueOnce(true) 103 | 104 | const messages = ['hello'] 105 | const source = TestUtil.generateReadableStream(messages) 106 | const onWrite = jest.fn() 107 | const target = TestUtil.generateWritableStream(onWrite) 108 | 109 | await pipeline( 110 | source, 111 | handler.handleFileBytes("filename.txt"), 112 | target 113 | ) 114 | 115 | expect(ioObj.to).toHaveBeenCalledTimes(messages.length) 116 | expect(ioObj.emit).toHaveBeenCalledTimes(messages.length) 117 | 118 | // se o handleFileBytes for um transofrm stream, nosso pipeline 119 | // vai continuar o processo, passando os dados para frente 120 | // e chamar nossa função no target a cada chunk 121 | expect(onWrite).toBeCalledTimes(messages.length) 122 | expect(onWrite.mock.calls.join()).toEqual(messages.join()) 123 | 124 | }) 125 | 126 | test('given message timerDelay as 2secs it should emit only two messages during 2 seconds period', async () => { 127 | jest.spyOn(ioObj, ioObj.emit.name) 128 | 129 | 130 | const day = '2021-07-01 01:01' 131 | const twoSecondsPeriod = 2000 132 | 133 | // Date.now do this.lastMessageSent em handleBytes 134 | const onFirstLastMessageSent = TestUtil.getTimeFromDate(`${day}:00`) 135 | 136 | // -> hello chegou 137 | const onFirstCanExecute = TestUtil.getTimeFromDate(`${day}:02`) 138 | const onSecondUpdateLastMessageSent = onFirstCanExecute 139 | 140 | // -> segundo hello, está fora da janela de tempo! 141 | const onSecondCanExecute = TestUtil.getTimeFromDate(`${day}:03`) 142 | 143 | // -> world 144 | const onThirdCanExecute = TestUtil.getTimeFromDate(`${day}:04`) 145 | 146 | 147 | TestUtil.mockDateNow( 148 | [ 149 | onFirstLastMessageSent, 150 | onFirstCanExecute, 151 | onSecondUpdateLastMessageSent, 152 | onSecondCanExecute, 153 | onThirdCanExecute, 154 | ] 155 | ) 156 | 157 | const messages = ["hello", "hello", "world"] 158 | const filename = 'filename.avi' 159 | const expectedMessageSent = 2 160 | 161 | const source = TestUtil.generateReadableStream(messages) 162 | const handler = new UploadHandler({ 163 | messageTimeDelay: twoSecondsPeriod, 164 | io: ioObj, 165 | socketId: '01', 166 | }) 167 | 168 | await pipeline( 169 | source, 170 | handler.handleFileBytes(filename) 171 | ) 172 | 173 | 174 | expect(ioObj.emit).toHaveBeenCalledTimes(expectedMessageSent) 175 | 176 | const [firstCallResult, secondCallResult] = ioObj.emit.mock.calls 177 | 178 | expect(firstCallResult).toEqual([handler.ON_UPLOAD_EVENT, { processedAlready: "hello".length, filename }]) 179 | expect(secondCallResult).toEqual([handler.ON_UPLOAD_EVENT, { processedAlready: messages.join("").length, filename }]) 180 | }) 181 | }) 182 | 183 | describe('#canExecute', () => { 184 | test('should return true when time is later than specified delay', () => { 185 | const timerDelay = 1000 186 | const uploadHandler = new UploadHandler({ 187 | io: {}, 188 | socketId: '', 189 | messageTimeDelay: timerDelay 190 | }) 191 | 192 | const tickNow = TestUtil.getTimeFromDate('2021-07-01 00:00:03') 193 | TestUtil.mockDateNow([tickNow]) 194 | 195 | const lastExecution = TestUtil.getTimeFromDate('2021-07-01 00:00:00') 196 | 197 | const result = uploadHandler.canExecute(lastExecution) 198 | expect(result).toBeTruthy() 199 | 200 | }) 201 | test('should return false when time isnt later than specified delay', () => { 202 | const timerDelay = 3000 203 | const uploadHandler = new UploadHandler({ 204 | io: {}, 205 | socketId: '', 206 | messageTimeDelay: timerDelay 207 | }) 208 | 209 | const now = TestUtil.getTimeFromDate('2021-07-01 00:00:02') 210 | TestUtil.mockDateNow([now]) 211 | 212 | const lastExecution = TestUtil.getTimeFromDate('2021-07-01 00:00:01') 213 | 214 | const result = uploadHandler.canExecute(lastExecution) 215 | expect(result).toBeFalsy() 216 | }) 217 | 218 | }) 219 | }) -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 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 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Template - Semana JS Expert 5.0 2 | 3 | 4 | Seja bem vindo(a) à quarta Semana Javascript Expert.Este é o código inicial para iniciar nossa jornada. 5 | 6 | Marque esse projeto com uma estrela 🌟 7 | 8 | ## Preview 9 | ### Página principal 10 | ![](./demo.png) -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula02/gdrive-webapp/demo.png -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@erickwendel/gdrive-webapp", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "http-server": "^0.12.3" 13 | } 14 | }, 15 | "node_modules/async": { 16 | "version": "2.6.3", 17 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 18 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 19 | "dependencies": { 20 | "lodash": "^4.17.14" 21 | } 22 | }, 23 | "node_modules/basic-auth": { 24 | "version": "1.1.0", 25 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 26 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", 27 | "engines": { 28 | "node": ">= 0.6" 29 | } 30 | }, 31 | "node_modules/call-bind": { 32 | "version": "1.0.2", 33 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 34 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 35 | "dependencies": { 36 | "function-bind": "^1.1.1", 37 | "get-intrinsic": "^1.0.2" 38 | }, 39 | "funding": { 40 | "url": "https://github.com/sponsors/ljharb" 41 | } 42 | }, 43 | "node_modules/colors": { 44 | "version": "1.4.0", 45 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 46 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 47 | "engines": { 48 | "node": ">=0.1.90" 49 | } 50 | }, 51 | "node_modules/corser": { 52 | "version": "2.0.1", 53 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 54 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 55 | "engines": { 56 | "node": ">= 0.4.0" 57 | } 58 | }, 59 | "node_modules/debug": { 60 | "version": "3.2.7", 61 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 62 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 63 | "dependencies": { 64 | "ms": "^2.1.1" 65 | } 66 | }, 67 | "node_modules/ecstatic": { 68 | "version": "3.3.2", 69 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 70 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 71 | "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", 72 | "dependencies": { 73 | "he": "^1.1.1", 74 | "mime": "^1.6.0", 75 | "minimist": "^1.1.0", 76 | "url-join": "^2.0.5" 77 | }, 78 | "bin": { 79 | "ecstatic": "lib/ecstatic.js" 80 | } 81 | }, 82 | "node_modules/eventemitter3": { 83 | "version": "4.0.7", 84 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 85 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 86 | }, 87 | "node_modules/follow-redirects": { 88 | "version": "1.14.3", 89 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 90 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", 91 | "funding": [ 92 | { 93 | "type": "individual", 94 | "url": "https://github.com/sponsors/RubenVerborgh" 95 | } 96 | ], 97 | "engines": { 98 | "node": ">=4.0" 99 | }, 100 | "peerDependenciesMeta": { 101 | "debug": { 102 | "optional": true 103 | } 104 | } 105 | }, 106 | "node_modules/function-bind": { 107 | "version": "1.1.1", 108 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 109 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 110 | }, 111 | "node_modules/get-intrinsic": { 112 | "version": "1.1.1", 113 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 114 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 115 | "dependencies": { 116 | "function-bind": "^1.1.1", 117 | "has": "^1.0.3", 118 | "has-symbols": "^1.0.1" 119 | }, 120 | "funding": { 121 | "url": "https://github.com/sponsors/ljharb" 122 | } 123 | }, 124 | "node_modules/has": { 125 | "version": "1.0.3", 126 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 127 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 128 | "dependencies": { 129 | "function-bind": "^1.1.1" 130 | }, 131 | "engines": { 132 | "node": ">= 0.4.0" 133 | } 134 | }, 135 | "node_modules/has-symbols": { 136 | "version": "1.0.2", 137 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 138 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 139 | "engines": { 140 | "node": ">= 0.4" 141 | }, 142 | "funding": { 143 | "url": "https://github.com/sponsors/ljharb" 144 | } 145 | }, 146 | "node_modules/he": { 147 | "version": "1.2.0", 148 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 149 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 150 | "bin": { 151 | "he": "bin/he" 152 | } 153 | }, 154 | "node_modules/http-proxy": { 155 | "version": "1.18.1", 156 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 157 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 158 | "dependencies": { 159 | "eventemitter3": "^4.0.0", 160 | "follow-redirects": "^1.0.0", 161 | "requires-port": "^1.0.0" 162 | }, 163 | "engines": { 164 | "node": ">=8.0.0" 165 | } 166 | }, 167 | "node_modules/http-server": { 168 | "version": "0.12.3", 169 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 170 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 171 | "dependencies": { 172 | "basic-auth": "^1.0.3", 173 | "colors": "^1.4.0", 174 | "corser": "^2.0.1", 175 | "ecstatic": "^3.3.2", 176 | "http-proxy": "^1.18.0", 177 | "minimist": "^1.2.5", 178 | "opener": "^1.5.1", 179 | "portfinder": "^1.0.25", 180 | "secure-compare": "3.0.1", 181 | "union": "~0.5.0" 182 | }, 183 | "bin": { 184 | "hs": "bin/http-server", 185 | "http-server": "bin/http-server" 186 | }, 187 | "engines": { 188 | "node": ">=6" 189 | } 190 | }, 191 | "node_modules/lodash": { 192 | "version": "4.17.21", 193 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 194 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 195 | }, 196 | "node_modules/mime": { 197 | "version": "1.6.0", 198 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 199 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 200 | "bin": { 201 | "mime": "cli.js" 202 | }, 203 | "engines": { 204 | "node": ">=4" 205 | } 206 | }, 207 | "node_modules/minimist": { 208 | "version": "1.2.5", 209 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 210 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 211 | }, 212 | "node_modules/mkdirp": { 213 | "version": "0.5.5", 214 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 215 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 216 | "dependencies": { 217 | "minimist": "^1.2.5" 218 | }, 219 | "bin": { 220 | "mkdirp": "bin/cmd.js" 221 | } 222 | }, 223 | "node_modules/ms": { 224 | "version": "2.1.3", 225 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 226 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 227 | }, 228 | "node_modules/object-inspect": { 229 | "version": "1.11.0", 230 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 231 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", 232 | "funding": { 233 | "url": "https://github.com/sponsors/ljharb" 234 | } 235 | }, 236 | "node_modules/opener": { 237 | "version": "1.5.2", 238 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 239 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 240 | "bin": { 241 | "opener": "bin/opener-bin.js" 242 | } 243 | }, 244 | "node_modules/portfinder": { 245 | "version": "1.0.28", 246 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 247 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 248 | "dependencies": { 249 | "async": "^2.6.2", 250 | "debug": "^3.1.1", 251 | "mkdirp": "^0.5.5" 252 | }, 253 | "engines": { 254 | "node": ">= 0.12.0" 255 | } 256 | }, 257 | "node_modules/qs": { 258 | "version": "6.10.1", 259 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 260 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 261 | "dependencies": { 262 | "side-channel": "^1.0.4" 263 | }, 264 | "engines": { 265 | "node": ">=0.6" 266 | }, 267 | "funding": { 268 | "url": "https://github.com/sponsors/ljharb" 269 | } 270 | }, 271 | "node_modules/requires-port": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 274 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 275 | }, 276 | "node_modules/secure-compare": { 277 | "version": "3.0.1", 278 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 279 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 280 | }, 281 | "node_modules/side-channel": { 282 | "version": "1.0.4", 283 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 284 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 285 | "dependencies": { 286 | "call-bind": "^1.0.0", 287 | "get-intrinsic": "^1.0.2", 288 | "object-inspect": "^1.9.0" 289 | }, 290 | "funding": { 291 | "url": "https://github.com/sponsors/ljharb" 292 | } 293 | }, 294 | "node_modules/union": { 295 | "version": "0.5.0", 296 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 297 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 298 | "dependencies": { 299 | "qs": "^6.4.0" 300 | }, 301 | "engines": { 302 | "node": ">= 0.8.0" 303 | } 304 | }, 305 | "node_modules/url-join": { 306 | "version": "2.0.5", 307 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 308 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 309 | } 310 | }, 311 | "dependencies": { 312 | "async": { 313 | "version": "2.6.3", 314 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 315 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 316 | "requires": { 317 | "lodash": "^4.17.14" 318 | } 319 | }, 320 | "basic-auth": { 321 | "version": "1.1.0", 322 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 323 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 324 | }, 325 | "call-bind": { 326 | "version": "1.0.2", 327 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 328 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 329 | "requires": { 330 | "function-bind": "^1.1.1", 331 | "get-intrinsic": "^1.0.2" 332 | } 333 | }, 334 | "colors": { 335 | "version": "1.4.0", 336 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 337 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 338 | }, 339 | "corser": { 340 | "version": "2.0.1", 341 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 342 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" 343 | }, 344 | "debug": { 345 | "version": "3.2.7", 346 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 347 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 348 | "requires": { 349 | "ms": "^2.1.1" 350 | } 351 | }, 352 | "ecstatic": { 353 | "version": "3.3.2", 354 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 355 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 356 | "requires": { 357 | "he": "^1.1.1", 358 | "mime": "^1.6.0", 359 | "minimist": "^1.1.0", 360 | "url-join": "^2.0.5" 361 | } 362 | }, 363 | "eventemitter3": { 364 | "version": "4.0.7", 365 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 366 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 367 | }, 368 | "follow-redirects": { 369 | "version": "1.14.3", 370 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 371 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" 372 | }, 373 | "function-bind": { 374 | "version": "1.1.1", 375 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 376 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 377 | }, 378 | "get-intrinsic": { 379 | "version": "1.1.1", 380 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 381 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 382 | "requires": { 383 | "function-bind": "^1.1.1", 384 | "has": "^1.0.3", 385 | "has-symbols": "^1.0.1" 386 | } 387 | }, 388 | "has": { 389 | "version": "1.0.3", 390 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 391 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 392 | "requires": { 393 | "function-bind": "^1.1.1" 394 | } 395 | }, 396 | "has-symbols": { 397 | "version": "1.0.2", 398 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 399 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" 400 | }, 401 | "he": { 402 | "version": "1.2.0", 403 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 404 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 405 | }, 406 | "http-proxy": { 407 | "version": "1.18.1", 408 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 409 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 410 | "requires": { 411 | "eventemitter3": "^4.0.0", 412 | "follow-redirects": "^1.0.0", 413 | "requires-port": "^1.0.0" 414 | } 415 | }, 416 | "http-server": { 417 | "version": "0.12.3", 418 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 419 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 420 | "requires": { 421 | "basic-auth": "^1.0.3", 422 | "colors": "^1.4.0", 423 | "corser": "^2.0.1", 424 | "ecstatic": "^3.3.2", 425 | "http-proxy": "^1.18.0", 426 | "minimist": "^1.2.5", 427 | "opener": "^1.5.1", 428 | "portfinder": "^1.0.25", 429 | "secure-compare": "3.0.1", 430 | "union": "~0.5.0" 431 | } 432 | }, 433 | "lodash": { 434 | "version": "4.17.21", 435 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 436 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 437 | }, 438 | "mime": { 439 | "version": "1.6.0", 440 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 441 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 442 | }, 443 | "minimist": { 444 | "version": "1.2.5", 445 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 446 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 447 | }, 448 | "mkdirp": { 449 | "version": "0.5.5", 450 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 451 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 452 | "requires": { 453 | "minimist": "^1.2.5" 454 | } 455 | }, 456 | "ms": { 457 | "version": "2.1.3", 458 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 459 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 460 | }, 461 | "object-inspect": { 462 | "version": "1.11.0", 463 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 464 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" 465 | }, 466 | "opener": { 467 | "version": "1.5.2", 468 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 469 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" 470 | }, 471 | "portfinder": { 472 | "version": "1.0.28", 473 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 474 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 475 | "requires": { 476 | "async": "^2.6.2", 477 | "debug": "^3.1.1", 478 | "mkdirp": "^0.5.5" 479 | } 480 | }, 481 | "qs": { 482 | "version": "6.10.1", 483 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 484 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 485 | "requires": { 486 | "side-channel": "^1.0.4" 487 | } 488 | }, 489 | "requires-port": { 490 | "version": "1.0.0", 491 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 492 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 493 | }, 494 | "secure-compare": { 495 | "version": "3.0.1", 496 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 497 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 498 | }, 499 | "side-channel": { 500 | "version": "1.0.4", 501 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 502 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 503 | "requires": { 504 | "call-bind": "^1.0.0", 505 | "get-intrinsic": "^1.0.2", 506 | "object-inspect": "^1.9.0" 507 | } 508 | }, 509 | "union": { 510 | "version": "0.5.0", 511 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 512 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 513 | "requires": { 514 | "qs": "^6.4.0" 515 | } 516 | }, 517 | "url-join": { 518 | "version": "2.0.5", 519 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 520 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "npx http-server public" 9 | }, 10 | "keywords": [], 11 | "author": "erickwendel", 12 | "license": "ISC", 13 | "dependencies": { 14 | "http-server": "^0.12.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/public/app.js: -------------------------------------------------------------------------------- 1 | console.debug('hello world!') -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/public/img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula02/gdrive-webapp/public/img/profile.png -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS Expert Drive | Semana JS Expert 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
67 | Name 68 | OwnerLast ModifiedFile Size
movie Erick-NodeJS-Streams.mp4system_user27 de agosto de 2021 14:1065.6 GB
content_copy Xuxa-Da-Silva-Fundamentos-JS.pdfsystem_user27 de agosto de 2021 14:111 GB
image Jonathan-0092123213.pngsystem_user27 de agosto de 2021 14:125 MB
96 |
97 |
98 | 122 | 123 | -------------------------------------------------------------------------------- /aulas/aula02/gdrive-webapp/public/styles.css: -------------------------------------------------------------------------------- 1 | .card, 2 | .card-panel { 3 | padding: 15px 20px; 4 | box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14); 5 | } 6 | 7 | .container-fluid { 8 | padding: 1rem 2.5rem; 9 | margin: auto; 10 | } 11 | 12 | .row { 13 | margin: 0 -0.75rem; 14 | } 15 | 16 | .main { 17 | position: absolute; 18 | width: calc(100% - 250px); 19 | top: 125px; 20 | margin-left: 250px; 21 | } 22 | 23 | .subheader { 24 | color: rgba(0, 0, 0, 0.54); 25 | font-weight: 500; 26 | } 27 | 28 | /* nav */ 29 | nav { 30 | box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3); 31 | } 32 | 33 | nav ul li { 34 | text-align: center; 35 | } 36 | 37 | nav ul.right { 38 | padding-right: 12px; 39 | } 40 | 41 | nav ul.right li { 42 | max-width: 48px; 43 | } 44 | 45 | nav ul a { 46 | padding: 0 12px; 47 | } 48 | 49 | nav ul a img { 50 | height: 32px; 51 | width: 32px; 52 | vertical-align: middle; 53 | margin-left: -5px; 54 | } 55 | 56 | .nav-wrapper { 57 | padding-left: 12px; 58 | } 59 | 60 | .nav-wrapper ul a:hover { 61 | background-color: transparent; 62 | } 63 | 64 | .nav-wrapper .title { 65 | font-size: 1.4rem; 66 | } 67 | 68 | .nav-wrapper .btn-flat { 69 | background-color: #4285f4 !important; 70 | font-size: 13px; 71 | font-weight: 500; 72 | height: 30px; 73 | line-height: 30px; 74 | width: 94px; 75 | } 76 | 77 | .nav-2, 78 | .nav-2 i { 79 | height: 56px !important; 80 | line-height: 56px !important; 81 | min-height: 56px !important; 82 | } 83 | 84 | .search-wrapper { 85 | margin: 10px auto 0 170px; 86 | width: calc(100% - 450px); 87 | max-width: 650px; 88 | height: 46px; 89 | position: fixed; 90 | } 91 | 92 | .search-wrapper i { 93 | color: #757575; 94 | position: absolute; 95 | font-size: 24px; 96 | top: 5px; 97 | left: 24px; 98 | line-height: 38px !important; 99 | } 100 | 101 | input[type=search]:not(.browser-default) { 102 | display: block; 103 | padding: 11px 8px 11px 72px; 104 | width: 100%; 105 | background: #f5f5f5; 106 | height: 24px; 107 | border: none; 108 | font-size: 16px; 109 | outline: none; 110 | border-radius: 2px; 111 | color: #757575; 112 | } 113 | 114 | input[type=search]:focus { 115 | border-bottom: none !important; 116 | box-shadow: none !important; 117 | } 118 | 119 | input[type=search]::placeholder { 120 | color: #757575; 121 | } 122 | 123 | /* sidenav */ 124 | .side-nav.floating { 125 | width: 250px; 126 | padding: 60px 8px 0 !important; 127 | height: calc(100% - 130px); 128 | left: initial; 129 | right: initial; 130 | top: 125px; 131 | transform: initial; 132 | z-index: auto; 133 | margin: 0.5rem 0 1rem 0; 134 | border-radius: 2px; 135 | background: transparent; 136 | box-shadow: none; 137 | } 138 | 139 | .side-nav .divider { 140 | margin: 8px 0; 141 | } 142 | 143 | .side-nav .active { 144 | background-color: rgba(0, 0, 0, 0.05); 145 | } 146 | 147 | .side-nav .active a { 148 | color: #212121; 149 | font-weight: 500; 150 | } 151 | 152 | .side-nav .subheader { 153 | line-height: 24px; 154 | height: 32px; 155 | margin: 0; 156 | padding: 4px 16px; 157 | color: #616161; 158 | font-weight: normal; 159 | font-size: 13px; 160 | } 161 | 162 | .side-nav li>a, 163 | .side-nav li>a>i.material-icons { 164 | height: 40px; 165 | line-height: 40px; 166 | } 167 | 168 | .side-nav li>a>i.material-icons { 169 | margin-right: 24px; 170 | } 171 | 172 | .side-nav li>a { 173 | padding: 0 16px; 174 | font-weight: normal; 175 | font-size: 13px; 176 | color: #616161; 177 | } 178 | 179 | .side-nav li>a:hover { 180 | border-radius: 2px; 181 | } 182 | 183 | /* folders */ 184 | .folder { 185 | width: 185px; 186 | display: inline-block; 187 | margin: 15px 20px 15px 0; 188 | font-weight: 500; 189 | } 190 | 191 | .folder i { 192 | color: rgba(0, 0, 0, 0.54); 193 | margin-top: -3px; 194 | } 195 | 196 | .material-icons.red600 { 197 | color: #fb3b00; 198 | } 199 | 200 | .material-icons.yellow600 { 201 | color: #d0da08; 202 | } 203 | 204 | .modal { 205 | word-wrap: normal; 206 | 207 | } 208 | #progress-bar { 209 | width: 100%; 210 | } 211 | #output { 212 | font-size: 20px; 213 | word-wrap: break-word; 214 | } 215 | 216 | /* drag n drop */ 217 | 218 | .drop-area { 219 | border: 2px dashed #ccc; 220 | border-radius: 20px; 221 | width: 100%; 222 | margin: 50px auto; 223 | padding: 20px; 224 | } 225 | .drop-area.highlight { 226 | border-color: purple; 227 | } 228 | #fileElem { 229 | display: none; 230 | } -------------------------------------------------------------------------------- /aulas/aula03/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Clone - Semana JS Expert 5.0 2 | 3 | Seja bem vindo(a) à quinta Semana Javascript Expert. Este é o código inicial para iniciar nossa jornada. 4 | 5 | Marque esse projeto com uma estrela 🌟 6 | 7 | ## Preview 8 | 9 | ![](./resources/demo.gif) 10 | 11 | 12 | ## Checklist Features 13 | 14 | - Web API 15 | - [x] Deve listar arquivos baixados 16 | - [x] Deve receber stream de arquivos e salvar em disco 17 | - [x] Deve notificar sobre progresso de armazenamento de arquivos em disco 18 | - [x] Deve permitir upload de arquivos em formato image, video ou audio 19 | - [x] Deve atingir 100% de cobertura de código em testes 20 | 21 | - Web App 22 | - [x] Deve listar arquivos baixados 23 | - [x] Deve permitir fazer upload de arquivos de qualquer tamanho 24 | - [x] Deve ter função de upload via botão 25 | - [x] Deve exibir progresso de upload 26 | - [x] Deve ter função de upload via drag and drop 27 | 28 | 29 | 30 | ## Desafios para alunos pós projeto 31 | 32 | 1. *Backend*: Salvar o arquivo na AWS ou qualquer serviço de storage 33 | - Nosso projeto hoje armazena arquivos em disco. o desafio é você via Stream, fazer upload para algum serviço na nuvem 34 | - Como plus, manter 100% de code coverage, ou seja, crie testes para sua nova feature 35 | 2. *Frontend*: Adicionar testes no frontend e alcançar 100% de code coverage 36 | - Você aprendeu como fazer testes no backend. Usar o mesmo processo para criar testes unitários no frontend com Jest 37 | - Caso tenha duvidas, acesse o [exemplo](https://github.com/ErickWendel/tdd-frontend-example) e deixe uma estrela! 38 | 3. *Infraestrutura*: Publicar aplicação com seu SSL customizado em máquina virtual 39 | - Você aprendeu a gerar SSL local, o desafio é você criar um certificado (pode ser com o *Let's Encrypt*) e adicionar na sua aplicação 40 | 41 | ### Considerações 42 | - Tire suas dúvidas sobre os desafios em nossa comunidade, o objetivo é você aprender de forma divertida. Surgiu dúvidas? Pergunte por lá! 43 | 44 | - Ao completar qualquer um dos desafios, envie no canal **#desafios** da comunidade no **Discord** 45 | 46 | ## Créditos ao Layout <3 47 | 48 | - O Layout foi adaptado a partir do projeto do brasileiro [Leonardo Santo](https://github.com/leoespsanto) disponibilizado no [codepen](https://codepen.io/leoespsanto/pen/KZMMKG). -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | downloads/* 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 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 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/certificates/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdzCCAt+gAwIBAgIRAPzlKLsx59gDukI5AIpXqBwwDQYJKoZIhvcNAQELBQAw 3 | gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqZXJp 4 | Y2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5kZWwpMTowOAYDVQQD 5 | DDFta2NlcnQgZXJpY2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5k 6 | ZWwpMB4XDTIxMDkwMzE4MTIxNVoXDTIzMTIwMzE4MTIxNVowXjEnMCUGA1UEChMe 7 | bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCplcmlja3dl 8 | bmRlbEBFcmlja1dlbmRlbHNNQlAgKEVyaWNrIFdlbmRlbCkwggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQD3K1QBNBa5kbKwAFEj76QJNErcJoUGh9g7LX0b 10 | SbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/sP2xZLDmxkMufG6QL7fw64to 11 | BRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZRzJDPAUGhkLfswaFfqDemp6U1 12 | KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIcOpGEhgwKA4QYawuIgGUcSbys 13 | z/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv5pjO3X70WNRPb0WbszoPLzKO 14 | Ckj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLPSHBTLqEPAgMBAAGjfDB6MA4G 15 | A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQt 16 | MxAhQKNc8uLS9BClcrjiEtdOqTAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACH 17 | BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJjILvMJ 18 | jUhqj99UGS5rdz0ksz3H0Mkn/ffDh+xMd6QHyUia0RQblolL4sjH+ZeZZWpcDfnb 19 | jQ9VN2zpPvZGPE4QykfAwYTK5mUxF24LwBKMelAmTc1xdxqx9AB93hocnxeKZKEt 20 | /Hk7U62ozssJyTmfA2TUrDuXKwNWiTpiazLoy7FaHkqe3dvmRJkjqgYkebHY1PtL 21 | CJB6IPe3CRpayc/AZGtd7GiBVlsMl4wDiKE4lYGb11943cZgcUwbuGHTdye8wf3g 22 | 1dB84AkXJSBEd8Kc7hokTuTdBlBWKWSUln7wHTmLfZ6jEuh6bDI3cn8IU5bAnAG7 23 | JmBe/i4zDlsA0zdhVM0y/MFL+o1m+znWYKApneuRWH9QULe4A9J74J7eaD5x4quv 24 | NmUDGGa5HyLcPM5WfJeJknAURE4wlT+T5C36X5DERCbrGsXiikcUqXJ8PHudm3qb 25 | gtitoc5+LEOC9TrTcInpym/qN7rbBMJd57kfGNBA9stg7cPAQsgxHZmR1g== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3K1QBNBa5kbKw 3 | AFEj76QJNErcJoUGh9g7LX0bSbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/ 4 | sP2xZLDmxkMufG6QL7fw64toBRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZR 5 | zJDPAUGhkLfswaFfqDemp6U1KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIc 6 | OpGEhgwKA4QYawuIgGUcSbysz/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv 7 | 5pjO3X70WNRPb0WbszoPLzKOCkj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLP 8 | SHBTLqEPAgMBAAECggEAYcoxtTwvlYHQiZPGhopEgyO+TnWrAddzNI6dxdMW885m 9 | grdClU8DCn0qsLYrN0fmA8CeDmvFc/kOFVOoO6wT1WoxoE24zLEkfFwkzqXbwfvm 10 | S8vWnjAjTZ6yaoRMe7suenkcXIQSC0pFgv2FeXEMz2hbbH97YkZCplwDoBGJshjv 11 | CdVgykGokzhvhp05xm+oayX/zEyxw7jp9o6ZpeTs2dphj9pXXcEkg8uR5CxyummF 12 | nSqwJZxmZ2HtEQAL7yZfli0vVbTlBa9L4JokS7xoqur15B1hiQIJBYZMJXHHr5tH 13 | YQfVP0zKGtWMIQNRYfjYKn3bYt4yqmHR9wBpV2SFOQKBgQD6DGquLcAt1M2VAUSd 14 | NSQ4WByADiXbw8R/CDdjAhHJ45Jz/3qDeRUYQ6YK9pDBpVjZdEKIm2SNG2tyrY87 15 | EhTQDuoyklQ/zFA5gdCQeikGVBidSHTarUxnHCgoNVPsk2pL7cVZnd7oIUkcGs+H 16 | kR8cqo/gJpHX17gZhp9oXEo2CwKBgQD9DV4h4Hm7aB58Vve5JvnNDG8pQP8FEd+5 17 | P6BUfJBovxFeWXOTPtq8QU2xTL1NHrimq1KG2iTj+11FSS+4zFaDJAZUYQ4tgn4o 18 | OKexDcJ6JzC8XOk974WE7StqjX6n1LqTqWMQ3Bnd3gEibjNzLVmHluh+SN3CLooI 19 | RjK6Yie3jQKBgCzJ6pX2dfT/qC9ngb3TFgDNr5U0c42Q3HKQqzMd3MfX7pS+j1hb 20 | aO7mtyhBkB5PmsGgtIY5p2IrJiztb7l5/KZj9YlHcrXWyAv098HZT93lVF9f6iZ9 21 | YjEZ9wt0ueqnYSPmnDH4OERGKg1RtBipYvREjO7umbMa3cwctBMCbPyPAoGBAKbW 22 | zm5Ves0Vq6vdBvz69o27mfrAEKN+Elwn2AR8EBYPi1sCbRHyyfJ+t8Oizdhv3dx9 23 | bi7c2p+5VdhdlWooxw01jjrJtrhIpfbMy7sPUF6LQjWeqGUea5Clcg+RdKUgu1ap 24 | wlgWVbOTMHpL3/4bM0ETPPwt/I+PcZBdAAsktfztAoGBAOO91AnDsTrFiNtU2IVv 25 | 9tc83nFttWmSmoaiz5JeXN98Fo0e8l/uLT3mKcD7ue3lh2qr6CxKZdlfopiFsCi3 26 | s0IuN7mSgStbZdK0km0vu1+N0cqMR+mvqCbYNk4cW690snXsWY3uVVJpDfRlwUDu 27 | HH0k++iNL4d/6FE571Ua8IUK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/jest.config.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property, visit: 3 | */ 4 | 5 | export default { 6 | clearMocks: true, 7 | restoreMocks: true, 8 | // collectCoverage: true, 9 | coverageDirectory: "coverage", 10 | coverageProvider: "v8", 11 | coverageReporters: [ 12 | "text", 13 | "lcov" 14 | ], 15 | testEnvironment: "node", 16 | coverageThreshold: { 17 | global: { 18 | branches: 100, 19 | functions: 100, 20 | lines: 100, 21 | statements: 100 22 | } 23 | }, 24 | watchPathIgnorePatterns: [ 25 | "node_modules" 26 | ], 27 | transformIgnorePatterns: ["node_modules"], 28 | collectCoverageFrom: [ 29 | "src/**/*.js", "!src/**/index.js" 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapi", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "mkdir -p downloads && node src/index.js", 9 | "prod": "mkdir -p downloads && NODE_ENV=production node src/index.js", 10 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand", 11 | "test:watch": "NODE_OPTIONS=--experimental-vm-modules npx jest --watch --runInBand", 12 | "test:cov": "NODE_OPTIONS=--experimental-vm-modules npx jest --no-cache --runInBand --coverage" 13 | }, 14 | "keywords": [], 15 | "author": "erickwendel", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "jest": "^27.1.0" 19 | }, 20 | "dependencies": { 21 | "busboy": "^0.3.1", 22 | "form-data": "4.0", 23 | "pino": "6.8", 24 | "pino-pretty": "5.1", 25 | "pretty-bytes": "5.6", 26 | "socket.io": "4.1" 27 | }, 28 | "engines": { 29 | "node": "16" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/src/fileHelper.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import prettyBytes from 'pretty-bytes' 3 | 4 | export default class FileHelper { 5 | static async getFilesStatus(downloadsFolder) { 6 | const currentFiles = await fs.promises.readdir(downloadsFolder) 7 | const statuses = await Promise 8 | .all( 9 | currentFiles 10 | .map( 11 | file => fs.promises.stat(`${downloadsFolder}/${file}`) 12 | ) 13 | ) 14 | const filesStatuses = [] 15 | for (const fileIndex in currentFiles) { 16 | const { birthtime, size } = statuses[fileIndex] 17 | filesStatuses.push({ 18 | size: prettyBytes(size), 19 | file: currentFiles[fileIndex], 20 | lastModified: birthtime, 21 | owner: process.env.USER 22 | }) 23 | } 24 | 25 | return filesStatuses 26 | } 27 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/src/index.js: -------------------------------------------------------------------------------- 1 | import https from 'https' 2 | import http from 'http' 3 | 4 | import fs from 'fs' 5 | import { logger } from './logger.js' 6 | import { Server } from 'socket.io' 7 | import Routes from './routes.js' 8 | 9 | const PORT = process.env.PORT || 3000 10 | 11 | const isProduction = process.env.NODE_ENV === "production" 12 | process.env.USER = process.env.USER ?? "system_user" 13 | 14 | const localHostSSL = { 15 | key: fs.readFileSync('./certificates/key.pem'), 16 | cert: fs.readFileSync('./certificates/cert.pem'), 17 | } 18 | 19 | const protocol = isProduction ? http : https 20 | const sslConfig = isProduction ? {} : localHostSSL 21 | 22 | 23 | const routes = new Routes() 24 | const server = protocol.createServer( 25 | sslConfig, 26 | routes.handler.bind(routes) 27 | ) 28 | 29 | const io = new Server(server, { 30 | cors: { 31 | origin: '*', 32 | credentials: false 33 | } 34 | }) 35 | 36 | routes.setSocketInstance(io) 37 | 38 | io.on("connection", (socket) => logger.info(`someone connected: ${socket.id}`)) 39 | 40 | const startServer = () => { 41 | const { address, port } = server.address() 42 | const protocol = isProduction ? "http" : "https" 43 | logger.info(`app running at ${protocol}://${address}:${port}`) 44 | } 45 | 46 | server.listen(PORT, startServer) -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/src/logger.js: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | const logger = pino({ 3 | prettyPrint: { 4 | ignore: 'pid,hostname' 5 | } 6 | }) 7 | 8 | export { 9 | logger, 10 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/src/routes.js: -------------------------------------------------------------------------------- 1 | import FileHelper from "./fileHelper.js" 2 | import { logger } from "./logger.js" 3 | import { dirname, resolve } from 'path' 4 | import { fileURLToPath, parse } from 'url' 5 | import UploadHandler from "./uploadHandler.js" 6 | import { pipeline } from "stream/promises" 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)) 9 | const defaultDownloadsFolder = resolve(__dirname, '../', "downloads") 10 | 11 | export default class Routes { 12 | constructor(downloadsFolder = defaultDownloadsFolder) { 13 | this.downloadsFolder = downloadsFolder 14 | this.fileHelper = FileHelper 15 | this.io = {} 16 | } 17 | 18 | setSocketInstance(io) { 19 | this.io = io 20 | } 21 | 22 | async defaultRoute(request, response) { 23 | response.end('hello world') 24 | } 25 | 26 | async options(request, response) { 27 | response.writeHead(204) 28 | response.end() 29 | } 30 | 31 | async post(request, response) { 32 | const { headers } = request 33 | 34 | const { query: { socketId } } = parse(request.url, true) 35 | const uploadHandler = new UploadHandler({ 36 | socketId, 37 | io: this.io, 38 | downloadsFolder: this.downloadsFolder 39 | }) 40 | 41 | const onFinish = (response) => () => { 42 | response.writeHead(200) 43 | const data = JSON.stringify({ result: 'Files uploaded with success! ' }) 44 | response.end(data) 45 | } 46 | 47 | const busboyInstance = uploadHandler.registerEvents( 48 | headers, 49 | onFinish(response) 50 | ) 51 | 52 | await pipeline( 53 | request, 54 | busboyInstance 55 | ) 56 | 57 | logger.info('Request finished with success!') 58 | } 59 | 60 | async get(request, response) { 61 | const files = await this.fileHelper.getFilesStatus(this.downloadsFolder) 62 | 63 | response.writeHead(200) 64 | response.end(JSON.stringify(files)) 65 | } 66 | 67 | handler(request, response) { 68 | response.setHeader('Access-Control-Allow-Origin', '*') 69 | const chosen = this[request.method.toLowerCase()] || this.defaultRoute 70 | 71 | return chosen.apply(this, [request, response]) 72 | } 73 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/src/uploadHandler.js: -------------------------------------------------------------------------------- 1 | import Busboy from 'busboy' 2 | import fs from 'fs' 3 | import { pipeline } from 'stream/promises' 4 | import { logger } from './logger.js' 5 | export default class UploadHandler { 6 | constructor({ io, socketId, downloadsFolder, messageTimeDelay = 200 }) { 7 | this.io = io 8 | this.socketId = socketId 9 | this.downloadsFolder = downloadsFolder 10 | this.ON_UPLOAD_EVENT = 'file-upload' 11 | this.messageTimeDelay = messageTimeDelay 12 | } 13 | 14 | canExecute(lastExecution) { 15 | return (Date.now() - lastExecution) >= this.messageTimeDelay 16 | } 17 | 18 | handleFileBytes(filename) { 19 | this.lastMessageSent = Date.now() 20 | 21 | async function* handleData(source) { 22 | let processedAlready = 0 23 | 24 | for await (const chunk of source) { 25 | yield chunk 26 | 27 | processedAlready += chunk.length 28 | if (!this.canExecute(this.lastMessageSent)) { 29 | continue; 30 | } 31 | 32 | this.lastMessageSent = Date.now() 33 | 34 | this.io.to(this.socketId).emit(this.ON_UPLOAD_EVENT, { processedAlready, filename }) 35 | logger.info(`File [${filename}] got ${processedAlready} bytes to ${this.socketId}`) 36 | } 37 | } 38 | 39 | return handleData.bind(this) 40 | } 41 | async onFile(fieldname, file, filename) { 42 | const saveTo = `${this.downloadsFolder}/${filename}` 43 | 44 | await pipeline( 45 | // 1o passo, pegar uma readable stream! 46 | file, 47 | // 2o passo, filtrar, converter, transformar dados! 48 | this.handleFileBytes.apply(this, [filename]), 49 | // 3o passo, é saida do processo, uma writable stream! 50 | fs.createWriteStream(saveTo) 51 | ) 52 | 53 | logger.info(`File [${filename}] finished`) 54 | } 55 | 56 | registerEvents(headers, onFinish) { 57 | const busboy = new Busboy({ headers }) 58 | busboy.on("file", this.onFile.bind(this)) 59 | busboy.on("finish", onFinish) 60 | 61 | return busboy 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/_util/testUtil.js: -------------------------------------------------------------------------------- 1 | import { jest } from '@jest/globals' 2 | import { Readable, Writable, Transform } from 'stream' 3 | export default class TestUtil { 4 | 5 | // 00:01 6 | // 00:02 7 | // 00:03 8 | 9 | 10 | 11 | static mockDateNow(mockImplementationPeriods) { 12 | 13 | const now = jest.spyOn(global.Date, global.Date.now.name) 14 | 15 | mockImplementationPeriods.forEach(time => { 16 | now.mockReturnValueOnce(time); 17 | }) 18 | 19 | } 20 | 21 | static getTimeFromDate(dateString) { 22 | return new Date(dateString).getTime() 23 | } 24 | 25 | 26 | static generateReadableStream(data) { 27 | return new Readable({ 28 | objectMode: true, 29 | read() { 30 | for (const item of data) { 31 | this.push(item) 32 | } 33 | 34 | this.push(null) 35 | } 36 | }) 37 | } 38 | static generateWritableStream(onData) { 39 | return new Writable({ 40 | objectMode: true, 41 | write(chunk, encondig, cb) { 42 | onData(chunk) 43 | 44 | cb(null, chunk) 45 | } 46 | }) 47 | } 48 | 49 | static generateTransformStream(onData) { 50 | // async function *(source) { 51 | // for await(const chunk of data) { 52 | // yield chunk 53 | // } 54 | // } 55 | 56 | return new Transform({ 57 | objectMode: true, 58 | transform(chunk, enconding, cb) { 59 | onData(chunk) 60 | cb(null, chunk) 61 | } 62 | }) 63 | } 64 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/integration/mocks/semanajsexpert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula03/gdrive-webapi/test/integration/mocks/semanajsexpert.png -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/integration/routes.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | beforeAll, 6 | afterAll, 7 | jest 8 | } from '@jest/globals' 9 | import fs from 'fs' 10 | import FileHelper from '../../src/fileHelper.js' 11 | 12 | import Routes from './../../src/routes.js' 13 | import FormData from 'form-data' 14 | import TestUtil from '../_util/testUtil.js' 15 | import { logger } from '../../src/logger.js' 16 | import { tmpdir } from 'os' 17 | import { join } from 'path' 18 | 19 | describe('#Routes Integration Test', () => { 20 | let defaultDownloadsFolder = '' 21 | beforeAll(async () => { 22 | defaultDownloadsFolder = await fs.promises.mkdtemp(join(tmpdir(), 'downloads-')) 23 | }) 24 | 25 | afterAll(async () => { 26 | await fs.promises.rm(defaultDownloadsFolder, { recursive: true }) 27 | }) 28 | 29 | beforeEach(() => { 30 | jest.spyOn(logger, 'info') 31 | .mockImplementation() 32 | }) 33 | 34 | describe('#getFileStatus', () => { 35 | const ioObj = { 36 | to: (id) => ioObj, 37 | emit: (event, message) => { } 38 | } 39 | 40 | test('should upload file to the folder', async () => { 41 | const filename = 'semanajsexpert.png' 42 | const fileStream = fs.createReadStream(`./test/integration/mocks/${filename}`) 43 | const response = TestUtil.generateWritableStream(() => { }) 44 | 45 | const form = new FormData() 46 | form.append('photo', fileStream) 47 | 48 | 49 | const defaultParams = { 50 | request: Object.assign(form, { 51 | headers: form.getHeaders(), 52 | method: 'POST', 53 | url: '?socketId=10' 54 | }), 55 | 56 | response: Object.assign(response, { 57 | setHeader: jest.fn(), 58 | writeHead: jest.fn(), 59 | end: jest.fn() 60 | }), 61 | values: () => Object.values(defaultParams) 62 | } 63 | 64 | const routes = new Routes(defaultDownloadsFolder) 65 | routes.setSocketInstance(ioObj) 66 | const dirBeforeRan = await fs.promises.readdir(defaultDownloadsFolder) 67 | expect(dirBeforeRan).toEqual([]) 68 | await routes.handler(...defaultParams.values()) 69 | const dirAfterRan = await fs.promises.readdir(defaultDownloadsFolder) 70 | expect(dirAfterRan).toEqual([filename]) 71 | 72 | expect(defaultParams.response.writeHead).toHaveBeenCalledWith(200) 73 | const expectedResult = JSON.stringify({ result: 'Files uploaded with success! ' }) 74 | expect(defaultParams.response.end).toHaveBeenCalledWith(expectedResult) 75 | 76 | }) 77 | }) 78 | }) -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/unit/fileHelper.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | jest 6 | } from '@jest/globals' 7 | import fs from 'fs' 8 | import FileHelper from '../../src/fileHelper.js' 9 | 10 | import Routes from './../../src/routes.js' 11 | 12 | describe('#FileHelper', () => { 13 | 14 | describe('#getFileStatus', () => { 15 | test('it should return files statuses in correct format', async () => { 16 | const statMock = { 17 | dev: 16777220, 18 | mode: 33188, 19 | nlink: 1, 20 | uid: 501, 21 | gid: 20, 22 | rdev: 0, 23 | blksize: 4096, 24 | ino: 214187433, 25 | size: 188188, 26 | blocks: 368, 27 | atimeMs: 1630702590337.3582, 28 | mtimeMs: 1630702588444.2876, 29 | ctimeMs: 1630702588452.0754, 30 | birthtimeMs: 1630702588443.3276, 31 | atime: '2021-09-03T20:56:30.337Z', 32 | mtime: '2021-09-03T20:56:28.444Z', 33 | ctime: '2021-09-03T20:56:28.452Z', 34 | birthtime: '2021-09-03T20:56:28.443Z' 35 | } 36 | 37 | const mockUser = 'erickwendel' 38 | process.env.USER = mockUser 39 | const filename = 'file.png' 40 | 41 | jest.spyOn(fs.promises, fs.promises.readdir.name) 42 | .mockResolvedValue([filename]) 43 | 44 | jest.spyOn(fs.promises, fs.promises.stat.name) 45 | .mockResolvedValue(statMock) 46 | 47 | const result = await FileHelper.getFilesStatus("/tmp") 48 | 49 | const expectedResult = [ 50 | { 51 | size: "188 kB", 52 | lastModified: statMock.birthtime, 53 | owner: mockUser, 54 | file: filename 55 | } 56 | ] 57 | 58 | expect(fs.promises.stat).toHaveBeenCalledWith(`/tmp/${filename}`) 59 | expect(result).toMatchObject(expectedResult) 60 | }) 61 | }) 62 | }) -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/unit/routes.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | beforeEach, 5 | expect, 6 | jest 7 | } from '@jest/globals' 8 | import { logger } from '../../src/logger.js' 9 | import UploadHandler from '../../src/uploadHandler.js' 10 | import TestUtil from '../_util/testUtil.js' 11 | import Routes from './../../src/routes.js' 12 | 13 | describe('#Routes test suite', () => { 14 | beforeEach(() => { 15 | jest.spyOn(logger, 'info') 16 | .mockImplementation() 17 | }) 18 | 19 | const request = TestUtil.generateReadableStream(['some file bytes']) 20 | const response = TestUtil.generateWritableStream(() => { }) 21 | 22 | const defaultParams = { 23 | request: Object.assign(request, { 24 | headers: { 25 | 'Content-Type': 'multipart/form-data' 26 | }, 27 | method: '', 28 | body: {} 29 | }), 30 | response: Object.assign(response, { 31 | setHeader: jest.fn(), 32 | writeHead: jest.fn(), 33 | end: jest.fn() 34 | }), 35 | values: () => Object.values(defaultParams) 36 | } 37 | 38 | describe('#setSocketInstance', () => { 39 | test('setSocket should store io instance', () => { 40 | const routes = new Routes() 41 | const ioObj = { 42 | to: (id) => ioObj, 43 | emit: (event, message) => { } 44 | } 45 | 46 | routes.setSocketInstance(ioObj) 47 | expect(routes.io).toStrictEqual(ioObj) 48 | }) 49 | }) 50 | 51 | describe('#handler', () => { 52 | 53 | 54 | test('given an inexistent route it should choose default route', async () => { 55 | const routes = new Routes() 56 | const params = { 57 | ...defaultParams 58 | } 59 | 60 | params.request.method = 'inexistent' 61 | await routes.handler(...params.values()) 62 | expect(params.response.end).toHaveBeenCalledWith('hello world') 63 | }) 64 | 65 | test('it should set any request with CORS enabled', async () => { 66 | const routes = new Routes() 67 | const params = { 68 | ...defaultParams 69 | } 70 | 71 | params.request.method = 'inexistent' 72 | await routes.handler(...params.values()) 73 | expect(params.response.setHeader) 74 | .toHaveBeenCalledWith('Access-Control-Allow-Origin', '*') 75 | }) 76 | 77 | test('given method OPTIONS it should choose options route', async () => { 78 | const routes = new Routes() 79 | const params = { 80 | ...defaultParams 81 | } 82 | 83 | params.request.method = 'OPTIONS' 84 | await routes.handler(...params.values()) 85 | expect(params.response.writeHead).toHaveBeenCalledWith(204) 86 | expect(params.response.end).toHaveBeenCalled() 87 | }) 88 | 89 | test('given method POST it should choose post route', async () => { 90 | const routes = new Routes() 91 | const params = { 92 | ...defaultParams 93 | } 94 | 95 | params.request.method = 'POST' 96 | jest.spyOn(routes, routes.post.name).mockResolvedValue() 97 | 98 | await routes.handler(...params.values()) 99 | expect(routes.post).toHaveBeenCalled() 100 | 101 | }) 102 | test('given method GET it should choose get route', async () => { 103 | const routes = new Routes() 104 | const params = { 105 | ...defaultParams 106 | } 107 | jest.spyOn(routes, routes.get.name).mockResolvedValue() 108 | 109 | params.request.method = 'GET' 110 | await routes.handler(...params.values()) 111 | expect(routes.get).toHaveBeenCalled() 112 | 113 | }) 114 | }) 115 | 116 | describe('#get', () => { 117 | test('given method GET it should list all files downloaded', async () => { 118 | const routes = new Routes() 119 | const params = { 120 | ...defaultParams 121 | } 122 | 123 | const filesStatusesMock = [ 124 | { 125 | size: "188 kB", 126 | lastModified: '2021-09-03T20:56:28.443Z', 127 | owner: 'erickwendel', 128 | file: 'file.txt' 129 | } 130 | ] 131 | jest.spyOn(routes.fileHelper, routes.fileHelper.getFilesStatus.name) 132 | .mockResolvedValue(filesStatusesMock) 133 | 134 | params.request.method = 'GET' 135 | await routes.handler(...params.values()) 136 | 137 | 138 | expect(params.response.writeHead).toHaveBeenCalledWith(200) 139 | expect(params.response.end).toHaveBeenCalledWith(JSON.stringify(filesStatusesMock)) 140 | 141 | }) 142 | }) 143 | 144 | describe('#post', () => { 145 | test('it should validate post route workflow', async () => { 146 | const routes = new Routes('/tmp') 147 | const options = { 148 | ...defaultParams 149 | } 150 | options.request.method = 'POST' 151 | options.request.url = '?socketId=10' 152 | 153 | 154 | jest.spyOn( 155 | UploadHandler.prototype, 156 | UploadHandler.prototype.registerEvents.name 157 | ).mockImplementation((headers, onFinish) => { 158 | const writable = TestUtil.generateWritableStream(() => {}) 159 | writable.on("finish", onFinish) 160 | 161 | return writable 162 | }) 163 | 164 | await routes.handler(...options.values()) 165 | 166 | expect(UploadHandler.prototype.registerEvents).toHaveBeenCalled() 167 | expect(options.response.writeHead).toHaveBeenCalledWith(200) 168 | 169 | const expectedResult = JSON.stringify({ result: 'Files uploaded with success! ' }) 170 | expect(options.response.end).toHaveBeenCalledWith(expectedResult) 171 | 172 | }) 173 | }) 174 | }) 175 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapi/test/unit/uploadHandler.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | describe, 3 | test, 4 | expect, 5 | beforeEach, 6 | jest 7 | } from '@jest/globals' 8 | import fs from 'fs' 9 | import { resolve } from 'path' 10 | import { pipeline } from 'stream/promises' 11 | import { logger } from '../../src/logger.js' 12 | import UploadHandler from '../../src/uploadHandler.js' 13 | import TestUtil from '../_util/testUtil.js' 14 | import Routes from './../../src/routes.js' 15 | 16 | describe('#UploadHandler test suite', () => { 17 | const ioObj = { 18 | to: (id) => ioObj, 19 | emit: (event, message) => { } 20 | } 21 | beforeEach(() => { 22 | jest.spyOn(logger, 'info') 23 | .mockImplementation() 24 | }) 25 | 26 | 27 | describe('#registerEvents', () => { 28 | test('should call onFile and onFinish functions on Busboy instance', () => { 29 | const uploadHandler = new UploadHandler({ 30 | io: ioObj, 31 | socketId: '01' 32 | }) 33 | 34 | jest.spyOn(uploadHandler, uploadHandler.onFile.name) 35 | .mockResolvedValue() 36 | 37 | const headers = { 38 | 'content-type': 'multipart/form-data; boundary=' 39 | } 40 | const onFinish = jest.fn() 41 | const busboyInstance = uploadHandler.registerEvents(headers, onFinish) 42 | 43 | const fileStream = TestUtil.generateReadableStream(['chunk', 'of', 'data']) 44 | busboyInstance.emit('file', 'fieldname', fileStream, 'filename.txt') 45 | 46 | busboyInstance.listeners("finish")[0].call() 47 | 48 | expect(uploadHandler.onFile).toHaveBeenCalled() 49 | expect(onFinish).toHaveBeenCalled() 50 | }) 51 | 52 | }) 53 | 54 | describe('#onFile', () => { 55 | test('given a stream file it should save it on disk', async () => { 56 | const chunks = ['hey', 'dude'] 57 | const downloadsFolder = '/tmp' 58 | const handler = new UploadHandler({ 59 | io: ioObj, 60 | socketId: '01', 61 | downloadsFolder 62 | }) 63 | 64 | const onData = jest.fn() 65 | 66 | jest.spyOn(fs, fs.createWriteStream.name) 67 | .mockImplementation(() => TestUtil.generateWritableStream(onData)) 68 | 69 | const onTransform = jest.fn() 70 | jest.spyOn(handler, handler.handleFileBytes.name) 71 | .mockImplementation(() => TestUtil.generateTransformStream(onTransform)) 72 | 73 | const params = { 74 | fieldname: 'video', 75 | file: TestUtil.generateReadableStream(chunks), 76 | filename: 'mockFile.mov' 77 | } 78 | await handler.onFile(...Object.values(params)) 79 | 80 | expect(onData.mock.calls.join()).toEqual(chunks.join()) 81 | expect(onTransform.mock.calls.join()).toEqual(chunks.join()) 82 | 83 | const expectedFilename = resolve(handler.downloadsFolder, params.filename) 84 | expect(fs.createWriteStream).toHaveBeenCalledWith(expectedFilename) 85 | 86 | 87 | }) 88 | }) 89 | 90 | 91 | describe('#handleFileBytes', () => { 92 | test('should call emit function and it is a transform stream', async () => { 93 | jest.spyOn(ioObj, ioObj.to.name) 94 | jest.spyOn(ioObj, ioObj.emit.name) 95 | 96 | const handler = new UploadHandler({ 97 | io: ioObj, 98 | socketId: '01' 99 | }) 100 | 101 | jest.spyOn(handler, handler.canExecute.name) 102 | .mockReturnValueOnce(true) 103 | 104 | const messages = ['hello'] 105 | const source = TestUtil.generateReadableStream(messages) 106 | const onWrite = jest.fn() 107 | const target = TestUtil.generateWritableStream(onWrite) 108 | 109 | await pipeline( 110 | source, 111 | handler.handleFileBytes("filename.txt"), 112 | target 113 | ) 114 | 115 | expect(ioObj.to).toHaveBeenCalledTimes(messages.length) 116 | expect(ioObj.emit).toHaveBeenCalledTimes(messages.length) 117 | 118 | // se o handleFileBytes for um transofrm stream, nosso pipeline 119 | // vai continuar o processo, passando os dados para frente 120 | // e chamar nossa função no target a cada chunk 121 | expect(onWrite).toBeCalledTimes(messages.length) 122 | expect(onWrite.mock.calls.join()).toEqual(messages.join()) 123 | 124 | }) 125 | 126 | test('given message timerDelay as 2secs it should emit only two messages during 2 seconds period', async () => { 127 | jest.spyOn(ioObj, ioObj.emit.name) 128 | 129 | 130 | const day = '2021-07-01 01:01' 131 | const twoSecondsPeriod = 2000 132 | 133 | // Date.now do this.lastMessageSent em handleBytes 134 | const onFirstLastMessageSent = TestUtil.getTimeFromDate(`${day}:00`) 135 | 136 | // -> hello chegou 137 | const onFirstCanExecute = TestUtil.getTimeFromDate(`${day}:02`) 138 | const onSecondUpdateLastMessageSent = onFirstCanExecute 139 | 140 | // -> segundo hello, está fora da janela de tempo! 141 | const onSecondCanExecute = TestUtil.getTimeFromDate(`${day}:03`) 142 | 143 | // -> world 144 | const onThirdCanExecute = TestUtil.getTimeFromDate(`${day}:04`) 145 | 146 | 147 | TestUtil.mockDateNow( 148 | [ 149 | onFirstLastMessageSent, 150 | onFirstCanExecute, 151 | onSecondUpdateLastMessageSent, 152 | onSecondCanExecute, 153 | onThirdCanExecute, 154 | ] 155 | ) 156 | 157 | const messages = ["hello", "hello", "world"] 158 | const filename = 'filename.avi' 159 | const expectedMessageSent = 2 160 | 161 | const source = TestUtil.generateReadableStream(messages) 162 | const handler = new UploadHandler({ 163 | messageTimeDelay: twoSecondsPeriod, 164 | io: ioObj, 165 | socketId: '01', 166 | }) 167 | 168 | await pipeline( 169 | source, 170 | handler.handleFileBytes(filename) 171 | ) 172 | 173 | 174 | expect(ioObj.emit).toHaveBeenCalledTimes(expectedMessageSent) 175 | 176 | const [firstCallResult, secondCallResult] = ioObj.emit.mock.calls 177 | 178 | expect(firstCallResult).toEqual([handler.ON_UPLOAD_EVENT, { processedAlready: "hello".length, filename }]) 179 | expect(secondCallResult).toEqual([handler.ON_UPLOAD_EVENT, { processedAlready: messages.join("").length, filename }]) 180 | }) 181 | }) 182 | 183 | describe('#canExecute', () => { 184 | test('should return true when time is later than specified delay', () => { 185 | const timerDelay = 1000 186 | const uploadHandler = new UploadHandler({ 187 | io: {}, 188 | socketId: '', 189 | messageTimeDelay: timerDelay 190 | }) 191 | 192 | const tickNow = TestUtil.getTimeFromDate('2021-07-01 00:00:03') 193 | TestUtil.mockDateNow([tickNow]) 194 | 195 | const lastExecution = TestUtil.getTimeFromDate('2021-07-01 00:00:00') 196 | 197 | const result = uploadHandler.canExecute(lastExecution) 198 | expect(result).toBeTruthy() 199 | 200 | }) 201 | test('should return false when time isnt later than specified delay', () => { 202 | const timerDelay = 3000 203 | const uploadHandler = new UploadHandler({ 204 | io: {}, 205 | socketId: '', 206 | messageTimeDelay: timerDelay 207 | }) 208 | 209 | const now = TestUtil.getTimeFromDate('2021-07-01 00:00:02') 210 | TestUtil.mockDateNow([now]) 211 | 212 | const lastExecution = TestUtil.getTimeFromDate('2021-07-01 00:00:01') 213 | 214 | const result = uploadHandler.canExecute(lastExecution) 215 | expect(result).toBeFalsy() 216 | }) 217 | 218 | }) 219 | }) -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 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 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/README.md: -------------------------------------------------------------------------------- 1 | # Google Drive Template - Semana JS Expert 5.0 2 | 3 | 4 | Seja bem vindo(a) à quarta Semana Javascript Expert.Este é o código inicial para iniciar nossa jornada. 5 | 6 | Marque esse projeto com uma estrela 🌟 7 | 8 | ## Preview 9 | ### Página principal 10 | ![](./demo.png) -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/certificates/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdzCCAt+gAwIBAgIRAPzlKLsx59gDukI5AIpXqBwwDQYJKoZIhvcNAQELBQAw 3 | gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqZXJp 4 | Y2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5kZWwpMTowOAYDVQQD 5 | DDFta2NlcnQgZXJpY2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5k 6 | ZWwpMB4XDTIxMDkwMzE4MTIxNVoXDTIzMTIwMzE4MTIxNVowXjEnMCUGA1UEChMe 7 | bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCplcmlja3dl 8 | bmRlbEBFcmlja1dlbmRlbHNNQlAgKEVyaWNrIFdlbmRlbCkwggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQD3K1QBNBa5kbKwAFEj76QJNErcJoUGh9g7LX0b 10 | SbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/sP2xZLDmxkMufG6QL7fw64to 11 | BRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZRzJDPAUGhkLfswaFfqDemp6U1 12 | KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIcOpGEhgwKA4QYawuIgGUcSbys 13 | z/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv5pjO3X70WNRPb0WbszoPLzKO 14 | Ckj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLPSHBTLqEPAgMBAAGjfDB6MA4G 15 | A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQt 16 | MxAhQKNc8uLS9BClcrjiEtdOqTAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACH 17 | BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJjILvMJ 18 | jUhqj99UGS5rdz0ksz3H0Mkn/ffDh+xMd6QHyUia0RQblolL4sjH+ZeZZWpcDfnb 19 | jQ9VN2zpPvZGPE4QykfAwYTK5mUxF24LwBKMelAmTc1xdxqx9AB93hocnxeKZKEt 20 | /Hk7U62ozssJyTmfA2TUrDuXKwNWiTpiazLoy7FaHkqe3dvmRJkjqgYkebHY1PtL 21 | CJB6IPe3CRpayc/AZGtd7GiBVlsMl4wDiKE4lYGb11943cZgcUwbuGHTdye8wf3g 22 | 1dB84AkXJSBEd8Kc7hokTuTdBlBWKWSUln7wHTmLfZ6jEuh6bDI3cn8IU5bAnAG7 23 | JmBe/i4zDlsA0zdhVM0y/MFL+o1m+znWYKApneuRWH9QULe4A9J74J7eaD5x4quv 24 | NmUDGGa5HyLcPM5WfJeJknAURE4wlT+T5C36X5DERCbrGsXiikcUqXJ8PHudm3qb 25 | gtitoc5+LEOC9TrTcInpym/qN7rbBMJd57kfGNBA9stg7cPAQsgxHZmR1g== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3K1QBNBa5kbKw 3 | AFEj76QJNErcJoUGh9g7LX0bSbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/ 4 | sP2xZLDmxkMufG6QL7fw64toBRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZR 5 | zJDPAUGhkLfswaFfqDemp6U1KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIc 6 | OpGEhgwKA4QYawuIgGUcSbysz/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv 7 | 5pjO3X70WNRPb0WbszoPLzKOCkj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLP 8 | SHBTLqEPAgMBAAECggEAYcoxtTwvlYHQiZPGhopEgyO+TnWrAddzNI6dxdMW885m 9 | grdClU8DCn0qsLYrN0fmA8CeDmvFc/kOFVOoO6wT1WoxoE24zLEkfFwkzqXbwfvm 10 | S8vWnjAjTZ6yaoRMe7suenkcXIQSC0pFgv2FeXEMz2hbbH97YkZCplwDoBGJshjv 11 | CdVgykGokzhvhp05xm+oayX/zEyxw7jp9o6ZpeTs2dphj9pXXcEkg8uR5CxyummF 12 | nSqwJZxmZ2HtEQAL7yZfli0vVbTlBa9L4JokS7xoqur15B1hiQIJBYZMJXHHr5tH 13 | YQfVP0zKGtWMIQNRYfjYKn3bYt4yqmHR9wBpV2SFOQKBgQD6DGquLcAt1M2VAUSd 14 | NSQ4WByADiXbw8R/CDdjAhHJ45Jz/3qDeRUYQ6YK9pDBpVjZdEKIm2SNG2tyrY87 15 | EhTQDuoyklQ/zFA5gdCQeikGVBidSHTarUxnHCgoNVPsk2pL7cVZnd7oIUkcGs+H 16 | kR8cqo/gJpHX17gZhp9oXEo2CwKBgQD9DV4h4Hm7aB58Vve5JvnNDG8pQP8FEd+5 17 | P6BUfJBovxFeWXOTPtq8QU2xTL1NHrimq1KG2iTj+11FSS+4zFaDJAZUYQ4tgn4o 18 | OKexDcJ6JzC8XOk974WE7StqjX6n1LqTqWMQ3Bnd3gEibjNzLVmHluh+SN3CLooI 19 | RjK6Yie3jQKBgCzJ6pX2dfT/qC9ngb3TFgDNr5U0c42Q3HKQqzMd3MfX7pS+j1hb 20 | aO7mtyhBkB5PmsGgtIY5p2IrJiztb7l5/KZj9YlHcrXWyAv098HZT93lVF9f6iZ9 21 | YjEZ9wt0ueqnYSPmnDH4OERGKg1RtBipYvREjO7umbMa3cwctBMCbPyPAoGBAKbW 22 | zm5Ves0Vq6vdBvz69o27mfrAEKN+Elwn2AR8EBYPi1sCbRHyyfJ+t8Oizdhv3dx9 23 | bi7c2p+5VdhdlWooxw01jjrJtrhIpfbMy7sPUF6LQjWeqGUea5Clcg+RdKUgu1ap 24 | wlgWVbOTMHpL3/4bM0ETPPwt/I+PcZBdAAsktfztAoGBAOO91AnDsTrFiNtU2IVv 25 | 9tc83nFttWmSmoaiz5JeXN98Fo0e8l/uLT3mKcD7ue3lh2qr6CxKZdlfopiFsCi3 26 | s0IuN7mSgStbZdK0km0vu1+N0cqMR+mvqCbYNk4cW690snXsWY3uVVJpDfRlwUDu 27 | HH0k++iNL4d/6FE571Ua8IUK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula03/gdrive-webapp/demo.png -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@erickwendel/gdrive-webapp", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "http-server": "^0.12.3" 13 | } 14 | }, 15 | "node_modules/async": { 16 | "version": "2.6.3", 17 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 18 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 19 | "dependencies": { 20 | "lodash": "^4.17.14" 21 | } 22 | }, 23 | "node_modules/basic-auth": { 24 | "version": "1.1.0", 25 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 26 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", 27 | "engines": { 28 | "node": ">= 0.6" 29 | } 30 | }, 31 | "node_modules/call-bind": { 32 | "version": "1.0.2", 33 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 34 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 35 | "dependencies": { 36 | "function-bind": "^1.1.1", 37 | "get-intrinsic": "^1.0.2" 38 | }, 39 | "funding": { 40 | "url": "https://github.com/sponsors/ljharb" 41 | } 42 | }, 43 | "node_modules/colors": { 44 | "version": "1.4.0", 45 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 46 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 47 | "engines": { 48 | "node": ">=0.1.90" 49 | } 50 | }, 51 | "node_modules/corser": { 52 | "version": "2.0.1", 53 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 54 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", 55 | "engines": { 56 | "node": ">= 0.4.0" 57 | } 58 | }, 59 | "node_modules/debug": { 60 | "version": "3.2.7", 61 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 62 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 63 | "dependencies": { 64 | "ms": "^2.1.1" 65 | } 66 | }, 67 | "node_modules/ecstatic": { 68 | "version": "3.3.2", 69 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 70 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 71 | "deprecated": "This package is unmaintained and deprecated. See the GH Issue 259.", 72 | "dependencies": { 73 | "he": "^1.1.1", 74 | "mime": "^1.6.0", 75 | "minimist": "^1.1.0", 76 | "url-join": "^2.0.5" 77 | }, 78 | "bin": { 79 | "ecstatic": "lib/ecstatic.js" 80 | } 81 | }, 82 | "node_modules/eventemitter3": { 83 | "version": "4.0.7", 84 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 85 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 86 | }, 87 | "node_modules/follow-redirects": { 88 | "version": "1.14.3", 89 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 90 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", 91 | "funding": [ 92 | { 93 | "type": "individual", 94 | "url": "https://github.com/sponsors/RubenVerborgh" 95 | } 96 | ], 97 | "engines": { 98 | "node": ">=4.0" 99 | }, 100 | "peerDependenciesMeta": { 101 | "debug": { 102 | "optional": true 103 | } 104 | } 105 | }, 106 | "node_modules/function-bind": { 107 | "version": "1.1.1", 108 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 109 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 110 | }, 111 | "node_modules/get-intrinsic": { 112 | "version": "1.1.1", 113 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 114 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 115 | "dependencies": { 116 | "function-bind": "^1.1.1", 117 | "has": "^1.0.3", 118 | "has-symbols": "^1.0.1" 119 | }, 120 | "funding": { 121 | "url": "https://github.com/sponsors/ljharb" 122 | } 123 | }, 124 | "node_modules/has": { 125 | "version": "1.0.3", 126 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 127 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 128 | "dependencies": { 129 | "function-bind": "^1.1.1" 130 | }, 131 | "engines": { 132 | "node": ">= 0.4.0" 133 | } 134 | }, 135 | "node_modules/has-symbols": { 136 | "version": "1.0.2", 137 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 138 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", 139 | "engines": { 140 | "node": ">= 0.4" 141 | }, 142 | "funding": { 143 | "url": "https://github.com/sponsors/ljharb" 144 | } 145 | }, 146 | "node_modules/he": { 147 | "version": "1.2.0", 148 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 149 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 150 | "bin": { 151 | "he": "bin/he" 152 | } 153 | }, 154 | "node_modules/http-proxy": { 155 | "version": "1.18.1", 156 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 157 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 158 | "dependencies": { 159 | "eventemitter3": "^4.0.0", 160 | "follow-redirects": "^1.0.0", 161 | "requires-port": "^1.0.0" 162 | }, 163 | "engines": { 164 | "node": ">=8.0.0" 165 | } 166 | }, 167 | "node_modules/http-server": { 168 | "version": "0.12.3", 169 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 170 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 171 | "dependencies": { 172 | "basic-auth": "^1.0.3", 173 | "colors": "^1.4.0", 174 | "corser": "^2.0.1", 175 | "ecstatic": "^3.3.2", 176 | "http-proxy": "^1.18.0", 177 | "minimist": "^1.2.5", 178 | "opener": "^1.5.1", 179 | "portfinder": "^1.0.25", 180 | "secure-compare": "3.0.1", 181 | "union": "~0.5.0" 182 | }, 183 | "bin": { 184 | "hs": "bin/http-server", 185 | "http-server": "bin/http-server" 186 | }, 187 | "engines": { 188 | "node": ">=6" 189 | } 190 | }, 191 | "node_modules/lodash": { 192 | "version": "4.17.21", 193 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 194 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 195 | }, 196 | "node_modules/mime": { 197 | "version": "1.6.0", 198 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 199 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 200 | "bin": { 201 | "mime": "cli.js" 202 | }, 203 | "engines": { 204 | "node": ">=4" 205 | } 206 | }, 207 | "node_modules/minimist": { 208 | "version": "1.2.5", 209 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 210 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 211 | }, 212 | "node_modules/mkdirp": { 213 | "version": "0.5.5", 214 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 215 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 216 | "dependencies": { 217 | "minimist": "^1.2.5" 218 | }, 219 | "bin": { 220 | "mkdirp": "bin/cmd.js" 221 | } 222 | }, 223 | "node_modules/ms": { 224 | "version": "2.1.3", 225 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 226 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 227 | }, 228 | "node_modules/object-inspect": { 229 | "version": "1.11.0", 230 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 231 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", 232 | "funding": { 233 | "url": "https://github.com/sponsors/ljharb" 234 | } 235 | }, 236 | "node_modules/opener": { 237 | "version": "1.5.2", 238 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 239 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", 240 | "bin": { 241 | "opener": "bin/opener-bin.js" 242 | } 243 | }, 244 | "node_modules/portfinder": { 245 | "version": "1.0.28", 246 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 247 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 248 | "dependencies": { 249 | "async": "^2.6.2", 250 | "debug": "^3.1.1", 251 | "mkdirp": "^0.5.5" 252 | }, 253 | "engines": { 254 | "node": ">= 0.12.0" 255 | } 256 | }, 257 | "node_modules/qs": { 258 | "version": "6.10.1", 259 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 260 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 261 | "dependencies": { 262 | "side-channel": "^1.0.4" 263 | }, 264 | "engines": { 265 | "node": ">=0.6" 266 | }, 267 | "funding": { 268 | "url": "https://github.com/sponsors/ljharb" 269 | } 270 | }, 271 | "node_modules/requires-port": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 274 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 275 | }, 276 | "node_modules/secure-compare": { 277 | "version": "3.0.1", 278 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 279 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 280 | }, 281 | "node_modules/side-channel": { 282 | "version": "1.0.4", 283 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 284 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 285 | "dependencies": { 286 | "call-bind": "^1.0.0", 287 | "get-intrinsic": "^1.0.2", 288 | "object-inspect": "^1.9.0" 289 | }, 290 | "funding": { 291 | "url": "https://github.com/sponsors/ljharb" 292 | } 293 | }, 294 | "node_modules/union": { 295 | "version": "0.5.0", 296 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 297 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 298 | "dependencies": { 299 | "qs": "^6.4.0" 300 | }, 301 | "engines": { 302 | "node": ">= 0.8.0" 303 | } 304 | }, 305 | "node_modules/url-join": { 306 | "version": "2.0.5", 307 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 308 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 309 | } 310 | }, 311 | "dependencies": { 312 | "async": { 313 | "version": "2.6.3", 314 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", 315 | "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", 316 | "requires": { 317 | "lodash": "^4.17.14" 318 | } 319 | }, 320 | "basic-auth": { 321 | "version": "1.1.0", 322 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", 323 | "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=" 324 | }, 325 | "call-bind": { 326 | "version": "1.0.2", 327 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 328 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 329 | "requires": { 330 | "function-bind": "^1.1.1", 331 | "get-intrinsic": "^1.0.2" 332 | } 333 | }, 334 | "colors": { 335 | "version": "1.4.0", 336 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 337 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 338 | }, 339 | "corser": { 340 | "version": "2.0.1", 341 | "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", 342 | "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" 343 | }, 344 | "debug": { 345 | "version": "3.2.7", 346 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 347 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 348 | "requires": { 349 | "ms": "^2.1.1" 350 | } 351 | }, 352 | "ecstatic": { 353 | "version": "3.3.2", 354 | "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", 355 | "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", 356 | "requires": { 357 | "he": "^1.1.1", 358 | "mime": "^1.6.0", 359 | "minimist": "^1.1.0", 360 | "url-join": "^2.0.5" 361 | } 362 | }, 363 | "eventemitter3": { 364 | "version": "4.0.7", 365 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 366 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 367 | }, 368 | "follow-redirects": { 369 | "version": "1.14.3", 370 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", 371 | "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" 372 | }, 373 | "function-bind": { 374 | "version": "1.1.1", 375 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 376 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 377 | }, 378 | "get-intrinsic": { 379 | "version": "1.1.1", 380 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 381 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 382 | "requires": { 383 | "function-bind": "^1.1.1", 384 | "has": "^1.0.3", 385 | "has-symbols": "^1.0.1" 386 | } 387 | }, 388 | "has": { 389 | "version": "1.0.3", 390 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 391 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 392 | "requires": { 393 | "function-bind": "^1.1.1" 394 | } 395 | }, 396 | "has-symbols": { 397 | "version": "1.0.2", 398 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 399 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" 400 | }, 401 | "he": { 402 | "version": "1.2.0", 403 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 404 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 405 | }, 406 | "http-proxy": { 407 | "version": "1.18.1", 408 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 409 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 410 | "requires": { 411 | "eventemitter3": "^4.0.0", 412 | "follow-redirects": "^1.0.0", 413 | "requires-port": "^1.0.0" 414 | } 415 | }, 416 | "http-server": { 417 | "version": "0.12.3", 418 | "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", 419 | "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", 420 | "requires": { 421 | "basic-auth": "^1.0.3", 422 | "colors": "^1.4.0", 423 | "corser": "^2.0.1", 424 | "ecstatic": "^3.3.2", 425 | "http-proxy": "^1.18.0", 426 | "minimist": "^1.2.5", 427 | "opener": "^1.5.1", 428 | "portfinder": "^1.0.25", 429 | "secure-compare": "3.0.1", 430 | "union": "~0.5.0" 431 | } 432 | }, 433 | "lodash": { 434 | "version": "4.17.21", 435 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 436 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 437 | }, 438 | "mime": { 439 | "version": "1.6.0", 440 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 441 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 442 | }, 443 | "minimist": { 444 | "version": "1.2.5", 445 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 446 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 447 | }, 448 | "mkdirp": { 449 | "version": "0.5.5", 450 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 451 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 452 | "requires": { 453 | "minimist": "^1.2.5" 454 | } 455 | }, 456 | "ms": { 457 | "version": "2.1.3", 458 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 459 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 460 | }, 461 | "object-inspect": { 462 | "version": "1.11.0", 463 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", 464 | "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" 465 | }, 466 | "opener": { 467 | "version": "1.5.2", 468 | "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", 469 | "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" 470 | }, 471 | "portfinder": { 472 | "version": "1.0.28", 473 | "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", 474 | "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", 475 | "requires": { 476 | "async": "^2.6.2", 477 | "debug": "^3.1.1", 478 | "mkdirp": "^0.5.5" 479 | } 480 | }, 481 | "qs": { 482 | "version": "6.10.1", 483 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", 484 | "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", 485 | "requires": { 486 | "side-channel": "^1.0.4" 487 | } 488 | }, 489 | "requires-port": { 490 | "version": "1.0.0", 491 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 492 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" 493 | }, 494 | "secure-compare": { 495 | "version": "3.0.1", 496 | "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", 497 | "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=" 498 | }, 499 | "side-channel": { 500 | "version": "1.0.4", 501 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 502 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 503 | "requires": { 504 | "call-bind": "^1.0.0", 505 | "get-intrinsic": "^1.0.2", 506 | "object-inspect": "^1.9.0" 507 | } 508 | }, 509 | "union": { 510 | "version": "0.5.0", 511 | "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", 512 | "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", 513 | "requires": { 514 | "qs": "^6.4.0" 515 | } 516 | }, 517 | "url-join": { 518 | "version": "2.0.5", 519 | "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", 520 | "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=" 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@erickwendel/gdrive-webapp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "npx http-server public", 9 | "dev": "npx http-server public --cert certificates/cert.pem --key certificates/key.pem --ssl -o" 10 | }, 11 | "keywords": [], 12 | "author": "erickwendel", 13 | "license": "ISC", 14 | "dependencies": { 15 | "http-server": "^0.12.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/app.js: -------------------------------------------------------------------------------- 1 | import AppController from "./src/appController.js"; 2 | import ConnectionManager from "./src/connectionManager.js"; 3 | import DragAndDropManager from "./src/dragAndDropManager.js"; 4 | import ViewManager from "./src/viewManager.js"; 5 | 6 | const API_URL = "https://0.0.0.0:3000" 7 | // const API_URL = "https://gdrive-webapi-ew.herokuapp.com/" 8 | 9 | const appController = new AppController({ 10 | viewManager: new ViewManager(), 11 | dragAndDropManager: new DragAndDropManager(), 12 | connectionManager: new ConnectionManager({ 13 | apiUrl: API_URL, 14 | }) 15 | }) 16 | 17 | try { 18 | await appController.initialize() 19 | } catch (error) { 20 | console.error('error on initializing', error) 21 | } 22 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/aulas/aula03/gdrive-webapp/public/img/profile.png -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS Expert Drive | Semana JS Expert 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 46 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | 66 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
67 | Name 68 | OwnerLast ModifiedFile Size
78 |
79 |
80 | 104 | 105 | -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/src/appController.js: -------------------------------------------------------------------------------- 1 | export default class AppController { 2 | constructor({ connectionManager, viewManager, dragAndDropManager }) { 3 | this.connectionManager = connectionManager 4 | this.viewManager = viewManager 5 | this.dragAndDropManager = dragAndDropManager 6 | 7 | this.uploadingFiles = new Map() 8 | } 9 | 10 | async initialize() { 11 | this.viewManager.configureFileBtnClick() 12 | this.viewManager.configureModal() 13 | this.viewManager.configureOnFileChange(this.onFileChange.bind(this)) 14 | this.dragAndDropManager.initialize({ 15 | onDropHandler: this.onFileChange.bind(this) 16 | }) 17 | 18 | this.connectionManager.configureEvents({ 19 | onProgress: this.onProgress.bind(this) 20 | }) 21 | 22 | this.viewManager.updateStatus(0); 23 | 24 | await this.updateCurrentFiles() 25 | } 26 | 27 | 28 | 29 | async onProgress({ processedAlready, filename }) { 30 | console.debug({ processedAlready, filename }) 31 | 32 | const file = this.uploadingFiles.get(filename) 33 | const alreadyProcessed = Math.ceil(processedAlready / file.size * 100) 34 | this.updateProgress(file, alreadyProcessed) 35 | 36 | if (alreadyProcessed < 98) return; 37 | 38 | return this.updateCurrentFiles() 39 | } 40 | 41 | updateProgress(file, percent) { 42 | const uploadingFiles = this.uploadingFiles 43 | file.percent = percent 44 | 45 | const total = [...uploadingFiles.values()] 46 | .map(({ percent }) => percent ?? 0) 47 | .reduce((total, current) => total + current, 0) 48 | 49 | this.viewManager.updateStatus(total) 50 | } 51 | 52 | async onFileChange(files) { 53 | // aqui tem um bug conhecido, se no meio do upload 54 | // voce fazer outro upload, ele vai fechar o modal e iniciar do zero 55 | this.uploadingFiles.clear() 56 | 57 | this.viewManager.openModal() 58 | this.viewManager.updateStatus(0) 59 | 60 | const requests = [] 61 | for (const file of files) { 62 | this.uploadingFiles.set(file.name, file) 63 | requests.push(this.connectionManager.uploadFile(file)) 64 | } 65 | 66 | await Promise.all(requests) 67 | this.viewManager.updateStatus(100) 68 | 69 | setTimeout(() => this.viewManager.closeModal(), 1000); 70 | await this.updateCurrentFiles() 71 | } 72 | 73 | async updateCurrentFiles() { 74 | const files = await this.connectionManager.currentFiles() 75 | this.viewManager.updateCurrentFiles(files) 76 | } 77 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/src/connectionManager.js: -------------------------------------------------------------------------------- 1 | export default class ConnectionManager { 2 | constructor({ apiUrl }) { 3 | this.apiUrl = apiUrl 4 | 5 | this.ioClient = io.connect(apiUrl, { withCredentials: false }) 6 | this.socketId = '' 7 | } 8 | 9 | configureEvents({ onProgress }) { 10 | this.ioClient.on('connect', this.onConnect.bind(this)) 11 | this.ioClient.on('file-upload', onProgress) 12 | } 13 | 14 | onConnect(msg) { 15 | console.log('connected!', this.ioClient.id) 16 | this.socketId = this.ioClient.id 17 | 18 | } 19 | 20 | async uploadFile(file) { 21 | const formData = new FormData() 22 | formData.append('files', file) 23 | 24 | const respose = await fetch(`${this.apiUrl}?socketId=${this.socketId}`, { 25 | method:'POST', 26 | body: formData 27 | }) 28 | 29 | return respose.json() 30 | 31 | } 32 | 33 | async currentFiles() { 34 | const files = (await (await fetch(this.apiUrl)).json()) 35 | return files 36 | } 37 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/src/dragAndDropManager.js: -------------------------------------------------------------------------------- 1 | export default class DragAndDropManager { 2 | constructor() { 3 | this.dropArea = document.getElementById('dropArea') 4 | this.onDropHandler = () => { } 5 | } 6 | initialize({ onDropHandler }) { 7 | this.onDropHandler = onDropHandler 8 | 9 | this.disableDragAnDropEvents() 10 | this.enableHighLightOnDrag() 11 | this.enableDrop() 12 | 13 | } 14 | 15 | disableDragAnDropEvents() { 16 | const events = [ 17 | 'dragenter', 18 | 'dragover', 19 | 'dragleave', 20 | 'drop' 21 | ] 22 | 23 | const preventDefaults = (e) => { 24 | e.preventDefault() 25 | e.stopPropagation() 26 | } 27 | events.forEach(eventName => { 28 | this.dropArea.addEventListener(eventName, preventDefaults, false) 29 | document.body.addEventListener(eventName, preventDefaults, false) 30 | }) 31 | } 32 | 33 | enableHighLightOnDrag() { 34 | const events = ['dragenter', 'dragover'] 35 | const hightlight = (e) => { 36 | this.dropArea.classList.add('highlight') 37 | this.dropArea.classList.add('drop-area') 38 | } 39 | 40 | events.forEach(eventName => { 41 | this.dropArea.addEventListener(eventName, hightlight, false) 42 | }) 43 | } 44 | 45 | enableDrop(e) { 46 | const drop = (e) => { 47 | this.dropArea.classList.remove('drop-area') 48 | 49 | const files = e.dataTransfer.files 50 | return this.onDropHandler(files) 51 | } 52 | 53 | this.dropArea.addEventListener('drop', drop, false) 54 | } 55 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/src/viewManager.js: -------------------------------------------------------------------------------- 1 | export default class ViewManager { 2 | constructor() { 3 | this.tbody = document.getElementById('tbody') 4 | this.newFileBtn = document.getElementById('newFileBtn') 5 | this.fileElem = document.getElementById('fileElem') 6 | this.progressModal = document.getElementById('progressModal') 7 | this.progressBar = document.getElementById('progressBar') 8 | this.output = document.getElementById('output') 9 | 10 | 11 | this.formatter = new Intl.DateTimeFormat('pt', { 12 | locale: 'pt-br', 13 | month:'long', 14 | day:'numeric', 15 | year: 'numeric', 16 | hour: '2-digit', 17 | minute: '2-digit' 18 | }) 19 | 20 | this.modalInstance = {} 21 | } 22 | 23 | configureModal() { 24 | this.modalInstance = M.Modal.init(this.progressModal, { 25 | opacity: 0, 26 | dismissable: false, 27 | // isso aqui libera para você clicar na tela 28 | // mesmo com o modal aberto 29 | onOpenEnd() { 30 | this.$overlay[0].remove() 31 | } 32 | }) 33 | } 34 | 35 | openModal() { 36 | this.modalInstance.open() 37 | } 38 | 39 | closeModal() { 40 | this.modalInstance.close() 41 | } 42 | 43 | updateStatus(size) { 44 | this.output.innerHTML = `Uploading in ${Math.floor(size)}%` 45 | this.progressBar.value = size 46 | } 47 | 48 | configureOnFileChange(fn) { 49 | this.fileElem.onchange = (e) => fn(e.target.files) 50 | } 51 | 52 | 53 | configureFileBtnClick() { 54 | this.newFileBtn.onclick = () => this.fileElem.click() 55 | } 56 | 57 | getIcon(file) { 58 | return file.match(/\.mp4/i) ? 'movie' 59 | : file.match(/\.jp|png/i) ? 'image' : 'content_copy' 60 | } 61 | 62 | makeIcon(file) { 63 | const icon = this.getIcon(file) 64 | const colors = { 65 | image: 'yellow600', 66 | movie: 'red600', 67 | file: '' 68 | } 69 | 70 | return ` 71 | ${icon} 72 | ` 73 | } 74 | 75 | updateCurrentFiles(files) { 76 | const template = (item) => ` 77 | 78 | ${this.makeIcon(item.file)} ${item.file} 79 | ${item.owner} 80 | ${this.formatter.format(new Date(item.lastModified))} 81 | ${item.size} 82 | 83 | ` 84 | 85 | this.tbody.innerHTML = files.map(template).join('') 86 | } 87 | } -------------------------------------------------------------------------------- /aulas/aula03/gdrive-webapp/public/styles.css: -------------------------------------------------------------------------------- 1 | .card, 2 | .card-panel { 3 | padding: 15px 20px; 4 | box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14); 5 | } 6 | 7 | .container-fluid { 8 | padding: 1rem 2.5rem; 9 | margin: auto; 10 | } 11 | 12 | .row { 13 | margin: 0 -0.75rem; 14 | } 15 | 16 | .main { 17 | position: absolute; 18 | width: calc(100% - 250px); 19 | top: 125px; 20 | margin-left: 250px; 21 | } 22 | 23 | .subheader { 24 | color: rgba(0, 0, 0, 0.54); 25 | font-weight: 500; 26 | } 27 | 28 | /* nav */ 29 | nav { 30 | box-shadow: 0 1px 8px rgba(0, 0, 0, 0.3); 31 | } 32 | 33 | nav ul li { 34 | text-align: center; 35 | } 36 | 37 | nav ul.right { 38 | padding-right: 12px; 39 | } 40 | 41 | nav ul.right li { 42 | max-width: 48px; 43 | } 44 | 45 | nav ul a { 46 | padding: 0 12px; 47 | } 48 | 49 | nav ul a img { 50 | height: 32px; 51 | width: 32px; 52 | vertical-align: middle; 53 | margin-left: -5px; 54 | } 55 | 56 | .nav-wrapper { 57 | padding-left: 12px; 58 | } 59 | 60 | .nav-wrapper ul a:hover { 61 | background-color: transparent; 62 | } 63 | 64 | .nav-wrapper .title { 65 | font-size: 1.4rem; 66 | } 67 | 68 | .nav-wrapper .btn-flat { 69 | background-color: #4285f4 !important; 70 | font-size: 13px; 71 | font-weight: 500; 72 | height: 30px; 73 | line-height: 30px; 74 | width: 94px; 75 | } 76 | 77 | .nav-2, 78 | .nav-2 i { 79 | height: 56px !important; 80 | line-height: 56px !important; 81 | min-height: 56px !important; 82 | } 83 | 84 | .search-wrapper { 85 | margin: 10px auto 0 170px; 86 | width: calc(100% - 450px); 87 | max-width: 650px; 88 | height: 46px; 89 | position: fixed; 90 | } 91 | 92 | .search-wrapper i { 93 | color: #757575; 94 | position: absolute; 95 | font-size: 24px; 96 | top: 5px; 97 | left: 24px; 98 | line-height: 38px !important; 99 | } 100 | 101 | input[type=search]:not(.browser-default) { 102 | display: block; 103 | padding: 11px 8px 11px 72px; 104 | width: 100%; 105 | background: #f5f5f5; 106 | height: 24px; 107 | border: none; 108 | font-size: 16px; 109 | outline: none; 110 | border-radius: 2px; 111 | color: #757575; 112 | } 113 | 114 | input[type=search]:focus { 115 | border-bottom: none !important; 116 | box-shadow: none !important; 117 | } 118 | 119 | input[type=search]::placeholder { 120 | color: #757575; 121 | } 122 | 123 | /* sidenav */ 124 | .side-nav.floating { 125 | width: 250px; 126 | padding: 60px 8px 0 !important; 127 | height: calc(100% - 130px); 128 | left: initial; 129 | right: initial; 130 | top: 125px; 131 | transform: initial; 132 | z-index: auto; 133 | margin: 0.5rem 0 1rem 0; 134 | border-radius: 2px; 135 | background: transparent; 136 | box-shadow: none; 137 | } 138 | 139 | .side-nav .divider { 140 | margin: 8px 0; 141 | } 142 | 143 | .side-nav .active { 144 | background-color: rgba(0, 0, 0, 0.05); 145 | } 146 | 147 | .side-nav .active a { 148 | color: #212121; 149 | font-weight: 500; 150 | } 151 | 152 | .side-nav .subheader { 153 | line-height: 24px; 154 | height: 32px; 155 | margin: 0; 156 | padding: 4px 16px; 157 | color: #616161; 158 | font-weight: normal; 159 | font-size: 13px; 160 | } 161 | 162 | .side-nav li>a, 163 | .side-nav li>a>i.material-icons { 164 | height: 40px; 165 | line-height: 40px; 166 | } 167 | 168 | .side-nav li>a>i.material-icons { 169 | margin-right: 24px; 170 | } 171 | 172 | .side-nav li>a { 173 | padding: 0 16px; 174 | font-weight: normal; 175 | font-size: 13px; 176 | color: #616161; 177 | } 178 | 179 | .side-nav li>a:hover { 180 | border-radius: 2px; 181 | } 182 | 183 | /* folders */ 184 | .folder { 185 | width: 185px; 186 | display: inline-block; 187 | margin: 15px 20px 15px 0; 188 | font-weight: 500; 189 | } 190 | 191 | .folder i { 192 | color: rgba(0, 0, 0, 0.54); 193 | margin-top: -3px; 194 | } 195 | 196 | .material-icons.red600 { 197 | color: #fb3b00; 198 | } 199 | 200 | .material-icons.yellow600 { 201 | color: #d0da08; 202 | } 203 | 204 | .modal { 205 | word-wrap: normal; 206 | 207 | } 208 | #progress-bar { 209 | width: 100%; 210 | } 211 | #output { 212 | font-size: 20px; 213 | word-wrap: break-word; 214 | } 215 | 216 | /* drag n drop */ 217 | 218 | .drop-area { 219 | border: 2px dashed #ccc; 220 | border-radius: 20px; 221 | width: 100%; 222 | margin: 50px auto; 223 | padding: 20px; 224 | } 225 | .drop-area.highlight { 226 | border-color: purple; 227 | } 228 | #fileElem { 229 | display: none; 230 | } -------------------------------------------------------------------------------- /certificates/README.md: -------------------------------------------------------------------------------- 1 | # Problemas com Certificado Inválido 2 | 3 | Você verá uma mensagem igual à abaixo quando usando estes certificados: 4 | 5 | ![](./sslproblem.png) 6 | 7 | Isso acontece pois o certificado que gerei, foi atrelado ao meu usuário. Mas não se preocupe, você pode clicar em avançado no browser e prosseguir para à aplicação. 8 | 9 | ## Você pode também gerar sua própria chave se necessário. 10 | 11 | - Para gerar sua própria chave você precisa: 12 | - Instalar o [MKCert](https://github.com/FiloSottile/mkcert) 13 | 14 | - Colocar seu usuario como usuário válido para certificado, com o comando 15 | `mkcert -install` 16 | - Gerar a Key e o Cert: 17 | `mkcert -key-file key.pem -cert-file cert.pem 0.0.0.0 localhost 127.0.0.1 ::1` -------------------------------------------------------------------------------- /certificates/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEdzCCAt+gAwIBAgIRAPzlKLsx59gDukI5AIpXqBwwDQYJKoZIhvcNAQELBQAw 3 | gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqZXJp 4 | Y2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5kZWwpMTowOAYDVQQD 5 | DDFta2NlcnQgZXJpY2t3ZW5kZWxARXJpY2tXZW5kZWxzTUJQIChFcmljayBXZW5k 6 | ZWwpMB4XDTIxMDkwMzE4MTIxNVoXDTIzMTIwMzE4MTIxNVowXjEnMCUGA1UEChMe 7 | bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCplcmlja3dl 8 | bmRlbEBFcmlja1dlbmRlbHNNQlAgKEVyaWNrIFdlbmRlbCkwggEiMA0GCSqGSIb3 9 | DQEBAQUAA4IBDwAwggEKAoIBAQD3K1QBNBa5kbKwAFEj76QJNErcJoUGh9g7LX0b 10 | SbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/sP2xZLDmxkMufG6QL7fw64to 11 | BRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZRzJDPAUGhkLfswaFfqDemp6U1 12 | KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIcOpGEhgwKA4QYawuIgGUcSbys 13 | z/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv5pjO3X70WNRPb0WbszoPLzKO 14 | Ckj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLPSHBTLqEPAgMBAAGjfDB6MA4G 15 | A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQt 16 | MxAhQKNc8uLS9BClcrjiEtdOqTAyBgNVHREEKzApgglsb2NhbGhvc3SHBAAAAACH 17 | BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAJjILvMJ 18 | jUhqj99UGS5rdz0ksz3H0Mkn/ffDh+xMd6QHyUia0RQblolL4sjH+ZeZZWpcDfnb 19 | jQ9VN2zpPvZGPE4QykfAwYTK5mUxF24LwBKMelAmTc1xdxqx9AB93hocnxeKZKEt 20 | /Hk7U62ozssJyTmfA2TUrDuXKwNWiTpiazLoy7FaHkqe3dvmRJkjqgYkebHY1PtL 21 | CJB6IPe3CRpayc/AZGtd7GiBVlsMl4wDiKE4lYGb11943cZgcUwbuGHTdye8wf3g 22 | 1dB84AkXJSBEd8Kc7hokTuTdBlBWKWSUln7wHTmLfZ6jEuh6bDI3cn8IU5bAnAG7 23 | JmBe/i4zDlsA0zdhVM0y/MFL+o1m+znWYKApneuRWH9QULe4A9J74J7eaD5x4quv 24 | NmUDGGa5HyLcPM5WfJeJknAURE4wlT+T5C36X5DERCbrGsXiikcUqXJ8PHudm3qb 25 | gtitoc5+LEOC9TrTcInpym/qN7rbBMJd57kfGNBA9stg7cPAQsgxHZmR1g== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /certificates/certificate-generator.sh: -------------------------------------------------------------------------------- 1 | # instalação no mac 2 | # https://github.com/FiloSottile/mkcert 3 | # brew install mkcert nss 4 | # mkcert -install 5 | 6 | 7 | mkcert -key-file key.pem -cert-file cert.pem 0.0.0.0 localhost 127.0.0.1 ::1 8 | 9 | -------------------------------------------------------------------------------- /certificates/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3K1QBNBa5kbKw 3 | AFEj76QJNErcJoUGh9g7LX0bSbXhqdSg6OZdeYMKejiXbC3TWFPrBBhvs+WTt3j/ 4 | sP2xZLDmxkMufG6QL7fw64toBRQrUeFo+27OZSKaVic1T9+d0YT6QnlbTQuiU7ZR 5 | zJDPAUGhkLfswaFfqDemp6U1KJD5Ju54JVmGdJ/1YbQS53tU0MCsiNjU1zyZcSIc 6 | OpGEhgwKA4QYawuIgGUcSbysz/eXocbNSvc7w8rBJd2yuyup6sO6ciBv5YeG1FIv 7 | 5pjO3X70WNRPb0WbszoPLzKOCkj9alIcnWe7qHCcXir442i+RqpPsA1TL1K08DLP 8 | SHBTLqEPAgMBAAECggEAYcoxtTwvlYHQiZPGhopEgyO+TnWrAddzNI6dxdMW885m 9 | grdClU8DCn0qsLYrN0fmA8CeDmvFc/kOFVOoO6wT1WoxoE24zLEkfFwkzqXbwfvm 10 | S8vWnjAjTZ6yaoRMe7suenkcXIQSC0pFgv2FeXEMz2hbbH97YkZCplwDoBGJshjv 11 | CdVgykGokzhvhp05xm+oayX/zEyxw7jp9o6ZpeTs2dphj9pXXcEkg8uR5CxyummF 12 | nSqwJZxmZ2HtEQAL7yZfli0vVbTlBa9L4JokS7xoqur15B1hiQIJBYZMJXHHr5tH 13 | YQfVP0zKGtWMIQNRYfjYKn3bYt4yqmHR9wBpV2SFOQKBgQD6DGquLcAt1M2VAUSd 14 | NSQ4WByADiXbw8R/CDdjAhHJ45Jz/3qDeRUYQ6YK9pDBpVjZdEKIm2SNG2tyrY87 15 | EhTQDuoyklQ/zFA5gdCQeikGVBidSHTarUxnHCgoNVPsk2pL7cVZnd7oIUkcGs+H 16 | kR8cqo/gJpHX17gZhp9oXEo2CwKBgQD9DV4h4Hm7aB58Vve5JvnNDG8pQP8FEd+5 17 | P6BUfJBovxFeWXOTPtq8QU2xTL1NHrimq1KG2iTj+11FSS+4zFaDJAZUYQ4tgn4o 18 | OKexDcJ6JzC8XOk974WE7StqjX6n1LqTqWMQ3Bnd3gEibjNzLVmHluh+SN3CLooI 19 | RjK6Yie3jQKBgCzJ6pX2dfT/qC9ngb3TFgDNr5U0c42Q3HKQqzMd3MfX7pS+j1hb 20 | aO7mtyhBkB5PmsGgtIY5p2IrJiztb7l5/KZj9YlHcrXWyAv098HZT93lVF9f6iZ9 21 | YjEZ9wt0ueqnYSPmnDH4OERGKg1RtBipYvREjO7umbMa3cwctBMCbPyPAoGBAKbW 22 | zm5Ves0Vq6vdBvz69o27mfrAEKN+Elwn2AR8EBYPi1sCbRHyyfJ+t8Oizdhv3dx9 23 | bi7c2p+5VdhdlWooxw01jjrJtrhIpfbMy7sPUF6LQjWeqGUea5Clcg+RdKUgu1ap 24 | wlgWVbOTMHpL3/4bM0ETPPwt/I+PcZBdAAsktfztAoGBAOO91AnDsTrFiNtU2IVv 25 | 9tc83nFttWmSmoaiz5JeXN98Fo0e8l/uLT3mKcD7ue3lh2qr6CxKZdlfopiFsCi3 26 | s0IuN7mSgStbZdK0km0vu1+N0cqMR+mvqCbYNk4cW690snXsWY3uVVJpDfRlwUDu 27 | HH0k++iNL4d/6FE571Ua8IUK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /certificates/sslproblem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/certificates/sslproblem.png -------------------------------------------------------------------------------- /resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickWendel/semana-javascript-expert05/d98217c4bcd89069506dfc63f40c0bcae76d2f2a/resources/demo.gif -------------------------------------------------------------------------------- /welcome.js: -------------------------------------------------------------------------------- 1 | console.info('Hello World') --------------------------------------------------------------------------------