├── .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 | [
](https://deepwiki.com/ErickWendel/semana-javascript-expert05)
8 |
9 | ## Preview
10 |
11 | 
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 | 
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 | 
--------------------------------------------------------------------------------
/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 |
16 |
45 |
46 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Name
68 | |
69 | Owner |
70 | Last Modified |
71 | File Size |
72 |
73 |
74 |
75 |
76 |
77 | movie Erick-NodeJS-Streams.mp4 |
78 | system_user |
79 | 27 de agosto de 2021 14:10 |
80 | 65.6 GB |
81 |
82 |
83 | content_copy Xuxa-Da-Silva-Fundamentos-JS.pdf |
84 | system_user |
85 | 27 de agosto de 2021 14:11 |
86 | 1 GB |
87 |
88 |
89 | image Jonathan-0092123213.png |
90 | system_user |
91 | 27 de agosto de 2021 14:12 |
92 | 5 MB |
93 |
94 |
95 |
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 | 
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 | 
--------------------------------------------------------------------------------
/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 |
16 |
45 |
46 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Name
68 | |
69 | Owner |
70 | Last Modified |
71 | File Size |
72 |
73 |
74 |
75 |
76 |
77 | movie Erick-NodeJS-Streams.mp4 |
78 | system_user |
79 | 27 de agosto de 2021 14:10 |
80 | 65.6 GB |
81 |
82 |
83 | content_copy Xuxa-Da-Silva-Fundamentos-JS.pdf |
84 | system_user |
85 | 27 de agosto de 2021 14:11 |
86 | 1 GB |
87 |
88 |
89 | image Jonathan-0092123213.png |
90 | system_user |
91 | 27 de agosto de 2021 14:12 |
92 | 5 MB |
93 |
94 |
95 |
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 | 
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 | 
--------------------------------------------------------------------------------
/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 |
16 |
45 |
46 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Name
68 | |
69 | Owner |
70 | Last Modified |
71 | File Size |
72 |
73 |
74 |
75 |
76 |
77 |
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 | 
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')
--------------------------------------------------------------------------------