├── clase-5
├── .gitignore
├── test
│ └── test.js
├── utils.js
├── server-with-mysql.js
├── server-with-local.js
├── middlewares
│ └── cors.js
├── routes
│ └── movies.js
├── package.json
├── app.js
├── api.http
├── schemas
│ └── movies.js
├── models
│ ├── local-file-system
│ │ └── movie.js
│ ├── mongodb
│ │ └── movie.js
│ └── mysql
│ │ └── movie.js
├── web
│ └── index.html
├── controllers
│ └── movies.js
├── movies.json
└── pnpm-lock.yaml
├── clase-4
├── models
│ ├── mysql
│ │ └── movie.js
│ ├── local-file-system
│ │ └── movie.js
│ └── mongodb
│ │ └── movie.js
├── utils.js
├── routes
│ └── movies.js
├── package.json
├── app.js
├── middlewares
│ └── cors.js
├── api.http
├── schemas
│ └── movies.js
├── web
│ └── index.html
├── controllers
│ └── movies.js
└── movies.json
├── clase-1
├── archivo.txt
├── archivo2.txt
├── cjs
│ ├── index.js
│ └── sum.js
├── mjs
│ ├── sum.mjs
│ └── index.mjs
├── 6.ls.js
├── 2.fs-stat.js
├── 7.process.js
├── 3.fs-readFile-sync.js
├── 3.fs-readFile.js
├── 4.fs-async-await-parallel.mjs
├── 1.os-info.mjs
├── 1.os-info.js
├── 9.http.js
├── 5.path.js
├── 4.fs-async-await.mjs
├── 10.free-port.js
├── 4.fs-promises.js
├── package.json
├── 4.fs-async-await.js
└── 8.ls-advanced.js
├── .gitignore
├── clase-2
├── placa.png
├── api.http
├── package.json
├── 1.http.js
├── 3.express.js
├── 2.routing.js
└── pokemon
│ └── ditto.json
├── clase-6
├── 02-http.webp
├── 01-comparativa.webp
├── 03-websockets.webp
├── .env
├── package.json
├── server
│ └── index.js
├── client
│ └── index.html
└── README.md
├── clase-3
├── package.json
├── api.http
├── schemas
│ └── movies.js
├── web
│ └── index.html
├── app.js
└── movies.json
├── package.json
└── README.md
/clase-5/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/clase-5/test/test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/clase-4/models/mysql/movie.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/clase-1/archivo.txt:
--------------------------------------------------------------------------------
1 | lorem ipsum ooooooh
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/package-lock.json
--------------------------------------------------------------------------------
/clase-2/placa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/curso-node-js/main/clase-2/placa.png
--------------------------------------------------------------------------------
/clase-6/02-http.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/curso-node-js/main/clase-6/02-http.webp
--------------------------------------------------------------------------------
/clase-1/archivo2.txt:
--------------------------------------------------------------------------------
1 | vamoacodear
2 | maikychplcok
3 | pheralb
4 | meisstykun
5 | rumertovar
6 | obsynotfound
--------------------------------------------------------------------------------
/clase-6/01-comparativa.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/curso-node-js/main/clase-6/01-comparativa.webp
--------------------------------------------------------------------------------
/clase-6/03-websockets.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/midudev/curso-node-js/main/clase-6/03-websockets.webp
--------------------------------------------------------------------------------
/clase-1/cjs/index.js:
--------------------------------------------------------------------------------
1 | // CommonJS require module
2 | const { sum } = require('./sum')
3 |
4 | console.log(sum(1, 2))
--------------------------------------------------------------------------------
/clase-1/cjs/sum.js:
--------------------------------------------------------------------------------
1 | function sum (a, b) {
2 | return a + b
3 | }
4 |
5 | // CommonJS Module Export
6 | module.exports = {
7 | sum
8 | }
--------------------------------------------------------------------------------
/clase-4/utils.js:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | const require = createRequire(import.meta.url)
3 |
4 | export const readJSON = (path) => require(path)
5 |
--------------------------------------------------------------------------------
/clase-5/utils.js:
--------------------------------------------------------------------------------
1 | import { createRequire } from 'node:module'
2 | const require = createRequire(import.meta.url)
3 |
4 | export const readJSON = (path) => require(path)
5 |
--------------------------------------------------------------------------------
/clase-5/server-with-mysql.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './app.js'
2 |
3 | import { MovieModel } from './models/mysql/movie.js'
4 |
5 | createApp({ movieModel: MovieModel })
6 |
--------------------------------------------------------------------------------
/clase-5/server-with-local.js:
--------------------------------------------------------------------------------
1 | import { createApp } from './app.js'
2 |
3 | import { MovieModel } from './models/local-file-system/movie.js'
4 |
5 | createApp({ movieModel: MovieModel })
6 |
--------------------------------------------------------------------------------
/clase-1/mjs/sum.mjs:
--------------------------------------------------------------------------------
1 | export function sum (a, b) {
2 | return a + b
3 | }
4 |
5 | export function sub (a, b) {
6 | return a - b
7 | }
8 |
9 | export function mult (a, b) {
10 | return a * b
11 | }
--------------------------------------------------------------------------------
/clase-6/.env:
--------------------------------------------------------------------------------
1 | DB_TOKEN="eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIyMDIzLTEwLTEyVDE3OjE3OjU5Ljc1MTM5NDUxMloiLCJpZCI6ImM1NmU5YjFmLTY5MjItMTFlZS04NTUzLWVhYTc3MTVhZWFmMiJ9.FRtOTGsCb_OJQeIYnPkiX-2nG3vfejJalCaaq3lJ-1A1R4jqO5XNsTjNkN0unj_9Pfky0fu-l-9NNi_Uk7jxAQ"
--------------------------------------------------------------------------------
/clase-1/mjs/index.mjs:
--------------------------------------------------------------------------------
1 | // .js -> por defecto utiliza CommonJS
2 | // .mjs -> para utilizar ES Modules
3 | // .cjs -> para utilizar CommonJS
4 |
5 | import { sum, sub, mult } from './sum.mjs'
6 |
7 | console.log(sum(1, 2))
8 | console.log(sub(1, 2))
9 | console.log(mult(1, 2))
--------------------------------------------------------------------------------
/clase-1/6.ls.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs/promises')
2 |
3 | fs.readdir('.')
4 | .then(files => {
5 | files.forEach(file => {
6 | console.log(file)
7 | })
8 | })
9 | .catch(err => {
10 | if (err) {
11 | console.error('Error al leer el directorio: ', err)
12 | return;
13 | }
14 | })
--------------------------------------------------------------------------------
/clase-2/api.http:
--------------------------------------------------------------------------------
1 | ### Recuperar información del pokemon ditto
2 | GET http://localhost:1234/pokemon/ditto
3 |
4 | ### Para crear un Pokemon
5 | POST http://localhost:1234/pokemon
6 | Content-Type: application/json
7 |
8 | {
9 | "name": "ditto",
10 | "type": "normal",
11 | "moves": [
12 | "transform"
13 | ]
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/clase-1/2.fs-stat.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs') // a partir de Node 16, se recomienda poner node:
2 |
3 | const stats = fs.statSync('./archivo.txt')
4 |
5 | console.log(
6 | stats.isFile(), // si es un fichero
7 | stats.isDirectory(), // si es un directorio
8 | stats.isSymbolicLink(), // si es un enlace simbólico
9 | stats.size // tamaño en bytes
10 | )
11 |
--------------------------------------------------------------------------------
/clase-1/7.process.js:
--------------------------------------------------------------------------------
1 | // argumentos de entrada
2 | // console.log(process.argv)
3 |
4 | // controlar el proceso y su salida
5 | // process.exit(1)
6 |
7 | // podemos controlar eventos del proceso
8 | // process.on('exit', () => {
9 | // // limpiar los recursos
10 | // })
11 |
12 | // current working directory
13 | console.log(process.cwd())
14 |
15 | // platform
16 | console.log(process.env.PEPITO)
--------------------------------------------------------------------------------
/clase-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clase-3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "cors": "2.8.5",
14 | "express": "4.18.2",
15 | "zod": "3.21.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "curso-node",
3 | "version": "1.0.0",
4 | "description": "
",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "standard": "^17.1.0"
14 | },
15 | "eslintConfig": {
16 | "extends": "standard"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/clase-1/3.fs-readFile-sync.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 |
3 | console.log('Leyendo el primer archivo...')
4 | const text = fs.readFileSync('./archivo.txt', 'utf-8')
5 | console.log('primer texto:', text)
6 |
7 | console.log('--> Hacer cosas mientras lee el archivo...')
8 |
9 | console.log('Leyendo el segundo archivo...')
10 | const secondText = fs.readFileSync('./archivo2.txt', 'utf-8')
11 | console.log('segundo texto:', secondText)
12 |
--------------------------------------------------------------------------------
/clase-4/routes/movies.js:
--------------------------------------------------------------------------------
1 | import { Router } from 'express'
2 |
3 | import { MovieController } from '../controllers/movies.js'
4 |
5 | export const moviesRouter = Router()
6 |
7 | moviesRouter.get('/', MovieController.getAll)
8 | moviesRouter.post('/', MovieController.create)
9 |
10 | moviesRouter.get('/:id', MovieController.getById)
11 | moviesRouter.delete('/:id', MovieController.delete)
12 | moviesRouter.patch('/:id', MovieController.update)
13 |
--------------------------------------------------------------------------------
/clase-4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clase-3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "type": "module",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "cors": "2.8.5",
15 | "express": "4.18.2",
16 | "mongodb": "5.7.0",
17 | "zod": "3.21.4"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/clase-1/3.fs-readFile.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs')
2 |
3 | console.log('Leyendo el primer archivo...')
4 | fs.readFile('./archivo.txt', 'utf-8', (err, text) => { // <---- ejecutas este callback
5 | console.log('primer texto:', text)
6 | })
7 |
8 | console.log('--> Hacer cosas mientras lee el archivo...')
9 |
10 | console.log('Leyendo el segundo archivo...')
11 | fs.readFile('./archivo2.txt', 'utf-8', (err, text) => {
12 | console.log('segundo texto:', text)
13 | })
14 |
--------------------------------------------------------------------------------
/clase-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clase-2",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "1.http.js",
6 | "scripts": {
7 | "dev:1": "nodemon 1.http.js",
8 | "dev:2": "nodemon 2.routing.js",
9 | "dev:3": "node --watch 3.express.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "nodemon": "^3.0.1"
16 | },
17 | "dependencies": {
18 | "express": "4.18.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/clase-4/app.js:
--------------------------------------------------------------------------------
1 | import express, { json } from 'express' // require -> commonJS
2 | import { moviesRouter } from './routes/movies.js'
3 | import { corsMiddleware } from './middlewares/cors.js'
4 |
5 | const app = express()
6 | app.use(json())
7 | app.disable('x-powered-by')
8 |
9 | app.use('/movies', moviesRouter)
10 |
11 | const PORT = process.env.PORT ?? 1234
12 |
13 | app.listen(PORT, () => {
14 | console.log(`server listening on port http://localhost:${PORT}`)
15 | })
16 |
--------------------------------------------------------------------------------
/clase-1/4.fs-async-await-parallel.mjs:
--------------------------------------------------------------------------------
1 | // Esto sólo en los módulos nativos
2 | // que no tienen promesas nativas
3 |
4 | // const { promisify } = require('node:util')
5 | // const readFilePromise = promisify(fs.readFile)
6 |
7 | import { readFile } from 'node:fs/promises'
8 |
9 | Promise.all([
10 | readFile('./archivo.txt', 'utf-8'),
11 | readFile('./archivo2.txt', 'utf-8')
12 | ]).then(([text, secondText]) => {
13 | console.log('primer texto:', text)
14 | console.log('segundo texto:', secondText)
15 | })
--------------------------------------------------------------------------------
/clase-1/1.os-info.mjs:
--------------------------------------------------------------------------------
1 | import os from 'node:os'
2 |
3 | console.log('Información del sistema operativo:')
4 | console.log('-------------------')
5 |
6 | console.log('Nombre del sistema operativo', os.platform())
7 | console.log('Versión del sistema operativo', os.release())
8 | console.log('Arquitectura', os.arch())
9 | console.log('CPUs', os.cpus())
10 | console.log('Memoria libre', os.freemem() / 1024 / 1024)
11 | console.log('Memoria total', os.totalmem() / 1024 / 1024)
12 | console.log('uptime', os.uptime() / 60 / 60)
13 |
--------------------------------------------------------------------------------
/clase-1/1.os-info.js:
--------------------------------------------------------------------------------
1 | const os = require('node:os')
2 |
3 | console.log('Información del sistema operativo:')
4 | console.log('-------------------')
5 |
6 | console.log('Nombre del sistema operativo', os.platform())
7 | console.log('Versión del sistema operativo', os.release())
8 | console.log('Arquitectura', os.arch())
9 | console.log('CPUs', os.cpus())
10 | console.log('Memoria libre', os.freemem() / 1024 / 1024)
11 | console.log('Memoria total', os.totalmem() / 1024 / 1024)
12 | console.log('uptime', os.uptime() / 60 / 60)
13 |
--------------------------------------------------------------------------------
/clase-1/9.http.js:
--------------------------------------------------------------------------------
1 | const http = require('node:http') // protocolo HTTP
2 | const { findAvailablePort } = require('./10.free-port.js')
3 |
4 | console.log(process.env)
5 |
6 | const desiredPort = process.env.PORT ?? 3000
7 |
8 | const server = http.createServer((req, res) => {
9 | console.log('request received')
10 | res.end('Hola mundo')
11 | })
12 |
13 | findAvailablePort(desiredPort).then(port => {
14 | server.listen(port, () => {
15 | console.log(`server listening on port http://localhost:${port}`)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/clase-6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clase-6",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "dev": "node --watch ./server/index.js",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@libsql/client": "^0.3.5",
16 | "dotenv": "^16.3.1",
17 | "express": "4.18.2",
18 | "morgan": "1.10.0",
19 | "socket.io": "4.7.2"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/clase-1/5.path.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path')
2 |
3 | // barra separadora de carpetas segun SO
4 | console.log(path.sep)
5 |
6 | // unir rutas con path.join
7 | const filePath = path.join('content', 'subfolder', 'test.txt')
8 | console.log(filePath)
9 |
10 | const base = path.basename('/tmp/midu-secret-files/password.txt')
11 | console.log(base)
12 |
13 | const filename = path.basename('/tmp/midu-secret-files/password.txt', '.txt')
14 | console.log(filename)
15 |
16 | const extension = path.extname('my.super.image.jpg')
17 | console.log(extension)
--------------------------------------------------------------------------------
/clase-4/middlewares/cors.js:
--------------------------------------------------------------------------------
1 | import cors from 'cors'
2 |
3 | const ACCEPTED_ORIGINS = [
4 | 'http://localhost:8080',
5 | 'http://localhost:1234',
6 | 'https://movies.com',
7 | 'https://midu.dev'
8 | ]
9 |
10 | export const corsMiddleware = ({ acceptedOrigins = ACCEPTED_ORIGINS } = {}) => cors({
11 | origin: (origin, callback) => {
12 | if (acceptedOrigins.includes(origin)) {
13 | return callback(null, true)
14 | }
15 |
16 | if (!origin) {
17 | return callback(null, true)
18 | }
19 |
20 | return callback(new Error('Not allowed by CORS'))
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/clase-5/middlewares/cors.js:
--------------------------------------------------------------------------------
1 | import cors from 'cors'
2 |
3 | const ACCEPTED_ORIGINS = [
4 | 'http://localhost:8080',
5 | 'http://localhost:1234',
6 | 'https://movies.com',
7 | 'https://midu.dev'
8 | ]
9 |
10 | export const corsMiddleware = ({ acceptedOrigins = ACCEPTED_ORIGINS } = {}) => cors({
11 | origin: (origin, callback) => {
12 | if (acceptedOrigins.includes(origin)) {
13 | return callback(null, true)
14 | }
15 |
16 | if (!origin) {
17 | return callback(null, true)
18 | }
19 |
20 | return callback(new Error('Not allowed by CORS'))
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/clase-5/routes/movies.js:
--------------------------------------------------------------------------------
1 | import { Router } from 'express'
2 | import { MovieController } from '../controllers/movies.js'
3 |
4 | export const createMovieRouter = ({ movieModel }) => {
5 | const moviesRouter = Router()
6 |
7 | const movieController = new MovieController({ movieModel })
8 |
9 | moviesRouter.get('/', movieController.getAll)
10 | moviesRouter.post('/', movieController.create)
11 |
12 | moviesRouter.get('/:id', movieController.getById)
13 | moviesRouter.delete('/:id', movieController.delete)
14 | moviesRouter.patch('/:id', movieController.update)
15 |
16 | return moviesRouter
17 | }
18 |
--------------------------------------------------------------------------------
/clase-5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clase-3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "type": "module",
7 | "scripts": {
8 | "start:mysql": "node --watch server-with-mysql.js",
9 | "start:local": "node --watch server-with-local.js",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "cors": "2.8.5",
17 | "dotenv": "16.3.1",
18 | "express": "4.18.2",
19 | "mongodb": "5.7.0",
20 | "mysql2": "^3.6.0",
21 | "zod": "3.21.4"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/clase-1/4.fs-async-await.mjs:
--------------------------------------------------------------------------------
1 | // Esto sólo en los módulos nativos
2 | // que no tienen promesas nativas
3 |
4 | // const { promisify } = require('node:util')
5 | // const readFilePromise = promisify(fs.readFile)
6 |
7 | import { readFile } from 'node:fs/promises'
8 |
9 | console.log('Leyendo el primer archivo...')
10 | const text = await readFile('./archivo.txt', 'utf-8')
11 | console.log('primer texto:', text)
12 | console.log('--> Hacer cosas mientras lee el archivo...')
13 |
14 | console.log('Leyendo el segundo archivo...')
15 | const secondText = await readFile('./archivo2.txt', 'utf-8')
16 | console.log('segundo texto:', secondText)
17 |
--------------------------------------------------------------------------------
/clase-5/app.js:
--------------------------------------------------------------------------------
1 | import express, { json } from 'express' // require -> commonJS
2 | import { createMovieRouter } from './routes/movies.js'
3 | import { corsMiddleware } from './middlewares/cors.js'
4 | import 'dotenv/config'
5 |
6 | // después
7 | export const createApp = ({ movieModel }) => {
8 | const app = express()
9 | app.use(json())
10 | app.use(corsMiddleware())
11 | app.disable('x-powered-by')
12 |
13 | app.use('/movies', createMovieRouter({ movieModel }))
14 |
15 | const PORT = process.env.PORT ?? 1234
16 |
17 | app.listen(PORT, () => {
18 | console.log(`server listening on port http://localhost:${PORT}`)
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/clase-1/10.free-port.js:
--------------------------------------------------------------------------------
1 | const net = require('node:net')
2 |
3 | function findAvailablePort (desiredPort) {
4 | return new Promise((resolve, reject) => {
5 | const server = net.createServer()
6 |
7 | server.listen(desiredPort, () => {
8 | const { port } = server.address()
9 | server.close(() => {
10 | resolve(port)
11 | })
12 | })
13 |
14 | server.on('error', (err) => {
15 | if (err.code === 'EADDRINUSE') {
16 | findAvailablePort(0).then(port => resolve(port))
17 | } else {
18 | reject(err)
19 | }
20 | })
21 | })
22 | }
23 |
24 | module.exports = { findAvailablePort }
25 |
--------------------------------------------------------------------------------
/clase-1/4.fs-promises.js:
--------------------------------------------------------------------------------
1 | // Esto sólo en los módulos nativos
2 | // que no tienen promesas nativas
3 |
4 | // const { promisify } = require('node:util')
5 | // const readFilePromise = promisify(fs.readFile)
6 |
7 | const fs = require('node:fs/promises')
8 |
9 | console.log('Leyendo el primer archivo...')
10 | fs.readFile('./archivo.txt', 'utf-8')
11 | .then(text => {
12 | console.log('primer texto:', text)
13 | })
14 |
15 | console.log('--> Hacer cosas mientras lee el archivo...')
16 |
17 | console.log('Leyendo el segundo archivo...')
18 | fs.readFile('./archivo2.txt', 'utf-8')
19 | .then(text => {
20 | console.log('segundo texto:', text)
21 | })
22 |
--------------------------------------------------------------------------------
/clase-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "curso-node",
3 | "version": "1.0.0",
4 | "description": "El mejor curso de Node por 0€",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/midudev/curso-node-js.git"
12 | },
13 | "keywords": [
14 | "curso",
15 | "node",
16 | "nodejs",
17 | "midudev"
18 | ],
19 | "author": "Miguel Ángel Durán García - midudev",
20 | "license": "ISC",
21 | "bugs": {
22 | "url": "https://github.com/midudev/curso-node-js/issues"
23 | },
24 | "homepage": "https://github.com/midudev/curso-node-js#readme",
25 | "dependencies": {
26 | "picocolors": "1.0.0"
27 | },
28 | "devDependencies": {
29 | "standard": "17.1.0"
30 | },
31 | "eslintConfig": {
32 | "extends": "standard"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/clase-3/api.http:
--------------------------------------------------------------------------------
1 | ### Recuperar todas las películas
2 | GET http://localhost:1234/movies
3 |
4 | ### Recuperar una película por id
5 | GET http://localhost:1234/movies/c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf
6 |
7 | ### Recuperar todas las películas por un género
8 | GET http://localhost:1234/movies?genre=ACTION
9 |
10 | ### Crear una película con POST
11 | POST http://localhost:1234/movies
12 | Content-Type: application/json
13 |
14 | {
15 | "sql": "SELECT * FROM users",
16 | "title": "The Godfather",
17 | "year": 1975,
18 | "director": "Francis Ford Coppola",
19 | "duration": 175,
20 | "poster": "https://img.fruugo.com/product/4/49/14441494_max.jpg",
21 | "genre": [
22 | "Crime",
23 | "Drama"
24 | ]
25 | }
26 |
27 | ### Actualizar una película
28 | PATCH http://localhost:1234/movies/dcdd0fad-a94c-4810-8acc-5f108d3b18c3
29 | Content-Type: application/json
30 |
31 | {
32 | "year": 2022
33 | }
--------------------------------------------------------------------------------
/clase-4/api.http:
--------------------------------------------------------------------------------
1 | ### Recuperar todas las películas
2 | GET http://localhost:1234/movies
3 |
4 | ### Recuperar una película por id
5 | GET http://localhost:1234/movies/64d4d8825d614a4ec5984ae3
6 |
7 | ### Recuperar todas las películas por un género
8 | GET http://localhost:1234/movies?genre=ACTION
9 |
10 | ### Crear una película con POST
11 | POST http://localhost:1234/movies
12 | Content-Type: application/json
13 |
14 | {
15 | "sql": "SELECT * FROM users",
16 | "title": "The Godfather",
17 | "year": 1975,
18 | "director": "Francis Ford Coppola",
19 | "duration": 175,
20 | "poster": "https://img.fruugo.com/product/4/49/14441494_max.jpg",
21 | "genre": [
22 | "Crime",
23 | "Drama"
24 | ]
25 | }
26 |
27 | ### Borrar una película
28 | DELETE http://localhost:1234/movies/64d4d8825d614a4ec5984ae3
29 |
30 | ### Actualizar una película
31 | PATCH http://localhost:1234/movies/64d4da882f83563ab4f40632
32 | Content-Type: application/json
33 |
34 | {
35 | "year": 2022
36 | }
--------------------------------------------------------------------------------
/clase-5/api.http:
--------------------------------------------------------------------------------
1 | ### Recuperar todas las películas
2 | GET http://localhost:1234/movies
3 |
4 | ### Recuperar una película por id
5 | GET http://localhost:1234/movies/1990df63-4da7-11ee-aedf-32323730fdc3
6 |
7 | ### Recuperar todas las películas por un género
8 | GET http://localhost:1234/movies?genre=ACTION
9 |
10 | ### Crear una película con POST
11 | POST http://localhost:1234/movies
12 | Content-Type: application/json
13 |
14 | {
15 | "sql": "SELECT * FROM users",
16 | "title": "The Godfather",
17 | "year": 1975,
18 | "director": "Francis Ford Coppola",
19 | "duration": 175,
20 | "poster": "https://img.fruugo.com/product/4/49/14441494_max.jpg",
21 | "genre": [
22 | "Crime",
23 | "Drama"
24 | ]
25 | }
26 |
27 | ### Borrar una película
28 | DELETE http://localhost:1234/movies/64d4d8825d614a4ec5984ae3
29 |
30 | ### Actualizar una película
31 | PATCH http://localhost:1234/movies/64d4da882f83563ab4f40632
32 | Content-Type: application/json
33 |
34 | {
35 | "year": 2022
36 | }
--------------------------------------------------------------------------------
/clase-4/schemas/movies.js:
--------------------------------------------------------------------------------
1 | import z from 'zod'
2 |
3 | const movieSchema = z.object({
4 | title: z.string({
5 | invalid_type_error: 'Movie title must be a string',
6 | required_error: 'Movie title is required.'
7 | }),
8 | year: z.number().int().min(1900).max(2024),
9 | director: z.string(),
10 | duration: z.number().int().positive(),
11 | rate: z.number().min(0).max(10).default(5),
12 | poster: z.string().url({
13 | message: 'Poster must be a valid URL'
14 | }),
15 | genre: z.array(
16 | z.enum(['Action', 'Adventure', 'Crime', 'Comedy', 'Drama', 'Fantasy', 'Horror', 'Thriller', 'Sci-Fi']),
17 | {
18 | required_error: 'Movie genre is required.',
19 | invalid_type_error: 'Movie genre must be an array of enum Genre'
20 | }
21 | )
22 | })
23 |
24 | export function validateMovie (input) {
25 | return movieSchema.safeParse(input)
26 | }
27 |
28 | export function validatePartialMovie (input) {
29 | return movieSchema.partial().safeParse(input)
30 | }
31 |
--------------------------------------------------------------------------------
/clase-5/schemas/movies.js:
--------------------------------------------------------------------------------
1 | import z from 'zod'
2 |
3 | const movieSchema = z.object({
4 | title: z.string({
5 | invalid_type_error: 'Movie title must be a string',
6 | required_error: 'Movie title is required.'
7 | }),
8 | year: z.number().int().min(1900).max(2024),
9 | director: z.string(),
10 | duration: z.number().int().positive(),
11 | rate: z.number().min(0).max(10).default(5),
12 | poster: z.string().url({
13 | message: 'Poster must be a valid URL'
14 | }),
15 | genre: z.array(
16 | z.enum(['Action', 'Adventure', 'Crime', 'Comedy', 'Drama', 'Fantasy', 'Horror', 'Thriller', 'Sci-Fi']),
17 | {
18 | required_error: 'Movie genre is required.',
19 | invalid_type_error: 'Movie genre must be an array of enum Genre'
20 | }
21 | )
22 | })
23 |
24 | export function validateMovie (input) {
25 | return movieSchema.safeParse(input)
26 | }
27 |
28 | export function validatePartialMovie (input) {
29 | return movieSchema.partial().safeParse(input)
30 | }
31 |
--------------------------------------------------------------------------------
/clase-2/1.http.js:
--------------------------------------------------------------------------------
1 | const http = require('node:http') // protocolo HTTP
2 | const fs = require('node:fs')
3 |
4 | const desiredPort = process.env.PORT ?? 1234
5 |
6 | const processRequest = (req, res) => {
7 | res.setHeader('Content-Type', 'text/html; charset=utf-8')
8 |
9 | if (req.url === '/') {
10 | res.end('
Mi página
')
11 | } else if (req.url === '/imagen-super-bonita.png') {
12 | fs.readFile('./placa.png', (err, data) => {
13 | if (err) {
14 | res.statusCode = 500
15 | res.end('
500 Internal Server Error
')
16 | } else {
17 | res.setHeader('Content-Type', 'image/png')
18 | res.end(data)
19 | }
20 | })
21 | } else if (req.url === '/contacto') {
22 | res.end('
Contacto
')
23 | } else {
24 | res.statusCode = 404 // Not Found
25 | res.end('
404
')
26 | }
27 | }
28 |
29 | const server = http.createServer(processRequest)
30 |
31 | server.listen(desiredPort, () => {
32 | console.log(`server listening on port http://localhost:${desiredPort}`)
33 | })
34 |
--------------------------------------------------------------------------------
/clase-3/schemas/movies.js:
--------------------------------------------------------------------------------
1 | const z = require('zod')
2 |
3 | const movieSchema = z.object({
4 | title: z.string({
5 | invalid_type_error: 'Movie title must be a string',
6 | required_error: 'Movie title is required.'
7 | }),
8 | year: z.number().int().min(1900).max(2024),
9 | director: z.string(),
10 | duration: z.number().int().positive(),
11 | rate: z.number().min(0).max(10).default(5),
12 | poster: z.string().url({
13 | message: 'Poster must be a valid URL'
14 | }),
15 | genre: z.array(
16 | z.enum(['Action', 'Adventure', 'Crime', 'Comedy', 'Drama', 'Fantasy', 'Horror', 'Thriller', 'Sci-Fi']),
17 | {
18 | required_error: 'Movie genre is required.',
19 | invalid_type_error: 'Movie genre must be an array of enum Genre'
20 | }
21 | )
22 | })
23 |
24 | function validateMovie (input) {
25 | return movieSchema.safeParse(input)
26 | }
27 |
28 | function validatePartialMovie (input) {
29 | return movieSchema.partial().safeParse(input)
30 | }
31 |
32 | module.exports = {
33 | validateMovie,
34 | validatePartialMovie
35 | }
36 |
--------------------------------------------------------------------------------
/clase-1/4.fs-async-await.js:
--------------------------------------------------------------------------------
1 | // Esto sólo en los módulos nativos
2 | // que no tienen promesas nativas
3 |
4 | // const { promisify } = require('node:util')
5 | // const readFilePromise = promisify(fs.readFile)
6 |
7 | const { readFile } = require('node:fs/promises')
8 |
9 | async function init () {
10 | console.log('Leyendo el primer archivo...')
11 | const text = await readFile('./archivo.txt', 'utf-8')
12 | console.log('primer texto:', text)
13 | console.log('--> Hacer cosas mientras lee el archivo...')
14 |
15 | console.log('Leyendo el segundo archivo...')
16 | const secondText = await readFile('./archivo2.txt', 'utf-8')
17 | console.log('segundo texto:', secondText)
18 | }
19 |
20 | init()
21 |
22 | // IIFE - Inmediatly Invoked Function Expression
23 | // ;(
24 | // async () => {
25 | // console.log('Leyendo el primer archivo...')
26 | // const text = await readFile('./archivo.txt', 'utf-8')
27 | // console.log('primer texto:', text)
28 | // console.log('--> Hacer cosas mientras lee el archivo...')
29 |
30 | // console.log('Leyendo el segundo archivo...')
31 | // const secondText = await readFile('./archivo2.txt', 'utf-8')
32 | // console.log('segundo texto:', secondText)
33 | // }
34 | // )()
35 |
36 |
--------------------------------------------------------------------------------
/clase-1/8.ls-advanced.js:
--------------------------------------------------------------------------------
1 | const fs = require('node:fs/promises')
2 | const path = require('node:path')
3 | const pc = require('picocolors')
4 |
5 | const folder = process.argv[2] ?? '.'
6 |
7 | async function ls (folder) {
8 | let files
9 | try {
10 | files = await fs.readdir(folder)
11 | } catch {
12 | console.error(pc.red(`❌ No se pudo leer el directorio ${folder}`))
13 | process.exit(1)
14 | }
15 |
16 | const filesPromises = files.map(async file => {
17 | const filePath = path.join(folder, file)
18 | let stats
19 |
20 | try {
21 | stats = await fs.stat(filePath) // status - información del archivo
22 | } catch {
23 | console.error(`No se pudo leer el archivo ${filePath}`)
24 | process.exit(1)
25 | }
26 |
27 | const isDirectory = stats.isDirectory()
28 | const fileType = isDirectory ? 'd' : 'f'
29 | const fileSize = stats.size.toString()
30 | const fileModified = stats.mtime.toLocaleString()
31 |
32 | return `${pc.bgMagenta(fileType)} ${pc.blue(file.padEnd(20))} ${pc.green(fileSize.padStart(10))} ${pc.yellow(fileModified)}`
33 | })
34 |
35 | const filesInfo = await Promise.all(filesPromises)
36 |
37 | filesInfo.forEach(fileInfo => console.log(fileInfo))
38 | }
39 |
40 | ls(folder)
41 |
--------------------------------------------------------------------------------
/clase-4/models/local-file-system/movie.js:
--------------------------------------------------------------------------------
1 | import { randomUUID } from 'node:crypto'
2 | import { readJSON } from '../../utils.js'
3 |
4 | const movies = readJSON('./movies.json')
5 |
6 | export class MovieModel {
7 | static async getAll ({ genre }) {
8 | if (genre) {
9 | return movies.filter(
10 | movie => movie.genre.some(g => g.toLowerCase() === genre.toLowerCase())
11 | )
12 | }
13 |
14 | return movies
15 | }
16 |
17 | static async getById ({ id }) {
18 | const movie = movies.find(movie => movie.id === id)
19 | return movie
20 | }
21 |
22 | static async create ({ input }) {
23 | const newMovie = {
24 | id: randomUUID(),
25 | ...input
26 | }
27 |
28 | movies.push(newMovie)
29 |
30 | return newMovie
31 | }
32 |
33 | static async delete ({ id }) {
34 | const movieIndex = movies.findIndex(movie => movie.id === id)
35 | if (movieIndex === -1) return false
36 |
37 | movies.splice(movieIndex, 1)
38 | return true
39 | }
40 |
41 | static async update ({ id, input }) {
42 | const movieIndex = movies.findIndex(movie => movie.id === id)
43 | if (movieIndex === -1) return false
44 |
45 | movies[movieIndex] = {
46 | ...movies[movieIndex],
47 | ...input
48 | }
49 |
50 | return movies[movieIndex]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/clase-5/models/local-file-system/movie.js:
--------------------------------------------------------------------------------
1 | import { randomUUID } from 'node:crypto'
2 | import { readJSON } from '../../utils.js'
3 |
4 | const movies = readJSON('./movies.json')
5 |
6 | export class MovieModel {
7 | static async getAll ({ genre }) {
8 | if (genre) {
9 | return movies.filter(
10 | movie => movie.genre.some(g => g.toLowerCase() === genre.toLowerCase())
11 | )
12 | }
13 |
14 | return movies
15 | }
16 |
17 | static async getById ({ id }) {
18 | const movie = movies.find(movie => movie.id === id)
19 | return movie
20 | }
21 |
22 | static async create ({ input }) {
23 | const newMovie = {
24 | id: randomUUID(),
25 | ...input
26 | }
27 |
28 | movies.push(newMovie)
29 |
30 | return newMovie
31 | }
32 |
33 | static async delete ({ id }) {
34 | const movieIndex = movies.findIndex(movie => movie.id === id)
35 | if (movieIndex === -1) return false
36 |
37 | movies.splice(movieIndex, 1)
38 | return true
39 | }
40 |
41 | static async update ({ id, input }) {
42 | const movieIndex = movies.findIndex(movie => movie.id === id)
43 | if (movieIndex === -1) return false
44 |
45 | movies[movieIndex] = {
46 | ...movies[movieIndex],
47 | ...input
48 | }
49 |
50 | return movies[movieIndex]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/clase-2/3.express.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const ditto = require('./pokemon/ditto.json')
3 | const path = require('path')
4 |
5 | const PORT = process.env.PORT ?? 1234
6 |
7 | const app = express()
8 | app.disable('x-powered-by')
9 |
10 | app.use(express.json())
11 |
12 | // app.use((req, res, next) => {
13 | // if (req.method !== 'POST') return next()
14 | // if (req.headers['content-type'] !== 'application/json') return next()
15 |
16 | // // solo llegan request que son POST y que tienen el header Content-Type: application/json
17 | // let body = ''
18 |
19 | // // escuchar el evento data
20 | // req.on('data', chunk => {
21 | // body += chunk.toString()
22 | // })
23 |
24 | // req.on('end', () => {
25 | // const data = JSON.parse(body)
26 | // data.timestamp = Date.now()
27 | // // mutar la request y meter la información en el req.body
28 | // req.body = data
29 | // next()
30 | // })
31 | // })
32 |
33 | app.get('/pokemon/ditto', (req, res) => {
34 | res.json(ditto)
35 | })
36 |
37 | app.post('/pokemon', (req, res) => {
38 | // req.body deberíamos guardar en bbdd
39 | res.status(201).json(req.body)
40 | })
41 |
42 | // la última a la que va a llegar
43 | app.use((req, res) => {
44 | res.status(404).send('
404
')
45 | })
46 |
47 | app.listen(PORT, () => {
48 | console.log(`server listening on port http://localhost:${PORT}`)
49 | })
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | # Curso de Node.js desde cero ❇️
6 |
7 | Curso para aprender **Node** de forma práctica
8 | **[Todos los jueves a las 18PM 🇪🇸 en Twitch](https://twitch.tv/midudev)**
9 |
10 |
11 | ## 📹 Videos con las clases
12 |
13 | - 01: [Introducción a Node.js y módulos](https://www.youtube.com/watch?v=yB4n_K7dZV8)
14 | - 02: [Creamos una API desde cero](https://www.youtube.com/watch?v=YmZE1HXjpd4)
15 | - 03: [Solución de CORS y desarrollo de API](https://www.youtube.com/watch?v=-9d3KhCqOtU)
16 | - 04: [Arquitectura MVC y despligue de API](https://www.youtube.com/watch?v=ev3Yxva4wI4)
17 | - 05: [Crea un Base de Datos MySQL y evita los hackers (buenas prácticas)](https://www.youtube.com/watch?v=eCWNQfzuuso&list=RDCMUC3aj05GEEyzdOqYM5FLSFeg&index=3)
18 | - 06: [Chat en TIEMPO REAL con Node.js, Socket.io, SQL, HTML y CSS](https://www.youtube.com/watch?v=WpbBhTx5R9Q&list=PLUofhDIg_38qm2oPOV-IRTTEKyrVBBaU7&index=6)
19 | - 07: [Aprende Autenticación de Usuario, Sesión, Cookies y JWT con Node.js](https://www.youtube.com/watch?v=UqnnhAZxRac&list=PLUofhDIg_38qm2oPOV-IRTTEKyrVBBaU7&index=7)
20 |
21 | ## Horario por países:
22 |
23 | - 18H 🇪🇸
24 | - 17H 🇮🇨
25 | - 13H 🇺🇾 🇦🇷 🇨🇱
26 | - 12H 🇵🇾 🇹🇹 🇧🇴 🇻🇪 🇩🇴
27 | - 11H 🇨🇴 🇵🇪 🇪🇨 🇵🇦
28 | - 10H 🇲🇽 🇨🇷 🇳🇮 🇸🇻 🇭🇳
29 |
--------------------------------------------------------------------------------
/clase-3/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Probar API Rest
7 |
20 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/clase-4/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Probar API Rest
7 |
20 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/clase-5/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Probar API Rest
7 |
20 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/clase-2/2.routing.js:
--------------------------------------------------------------------------------
1 | const http = require('node:http')
2 |
3 | // commonJS -> modulos clásicos de node
4 | const dittoJSON = require('./pokemon/ditto.json')
5 |
6 | const processRequest = (req, res) => {
7 | const { method, url } = req
8 |
9 | switch (method) {
10 | case 'GET':
11 | switch (url) {
12 | case '/pokemon/ditto':
13 |
14 | res.setHeader('Content-Type', 'application/json; charset=utf-8')
15 | return res.end(JSON.stringify(dittoJSON))
16 | default:
17 | res.statusCode = 404
18 | res.setHeader('Content-Type', 'text/html; charset=utf-8')
19 | return res.end('
404
')
20 | }
21 |
22 | case 'POST':
23 | switch (url) {
24 | case '/pokemon': {
25 | let body = ''
26 |
27 | // escuchar el evento data
28 | req.on('data', chunk => {
29 | body += chunk.toString()
30 | })
31 |
32 | req.on('end', () => {
33 | const data = JSON.parse(body)
34 | // llamar a una base de datos para guardar la info
35 | res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' })
36 |
37 | data.timestamp = Date.now()
38 | res.end(JSON.stringify(data))
39 | })
40 |
41 | break
42 | }
43 |
44 | default:
45 | res.statusCode = 404
46 | res.setHeader('Content-Type', 'text/plain; charset=utf-8')
47 | return res.end('404 Not Found')
48 | }
49 | }
50 | }
51 |
52 | const server = http.createServer(processRequest)
53 |
54 | server.listen(1234, () => {
55 | console.log('server listening on port http://localhost:1234')
56 | })
57 |
--------------------------------------------------------------------------------
/clase-5/controllers/movies.js:
--------------------------------------------------------------------------------
1 | import { validateMovie, validatePartialMovie } from '../schemas/movies.js'
2 |
3 | export class MovieController {
4 | constructor ({ movieModel }) {
5 | this.movieModel = movieModel
6 | }
7 |
8 | getAll = async (req, res) => {
9 | const { genre } = req.query
10 | const movies = await this.movieModel.getAll({ genre })
11 | res.json(movies)
12 | }
13 |
14 | getById = async (req, res) => {
15 | const { id } = req.params
16 | const movie = await this.movieModel.getById({ id })
17 | if (movie) return res.json(movie)
18 | res.status(404).json({ message: 'Movie not found' })
19 | }
20 |
21 | create = async (req, res) => {
22 | const result = validateMovie(req.body)
23 |
24 | if (!result.success) {
25 | // 422 Unprocessable Entity
26 | return res.status(400).json({ error: JSON.parse(result.error.message) })
27 | }
28 |
29 | const newMovie = await this.movieModel.create({ input: result.data })
30 |
31 | res.status(201).json(newMovie)
32 | }
33 |
34 | delete = async (req, res) => {
35 | const { id } = req.params
36 |
37 | const result = await this.movieModel.delete({ id })
38 |
39 | if (result === false) {
40 | return res.status(404).json({ message: 'Movie not found' })
41 | }
42 |
43 | return res.json({ message: 'Movie deleted' })
44 | }
45 |
46 | update = async (req, res) => {
47 | const result = validatePartialMovie(req.body)
48 |
49 | if (!result.success) {
50 | return res.status(400).json({ error: JSON.parse(result.error.message) })
51 | }
52 |
53 | const { id } = req.params
54 |
55 | const updatedMovie = await this.movieModel.update({ id, input: result.data })
56 |
57 | return res.json(updatedMovie)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/clase-4/controllers/movies.js:
--------------------------------------------------------------------------------
1 | import { MovieModel } from '../models/local-file-system/movie.js'
2 | // import { MovieModel } from '../models/database/movie.js'
3 | import { validateMovie, validatePartialMovie } from '../schemas/movies.js'
4 |
5 | export class MovieController {
6 | static async getAll (req, res) {
7 | const { genre } = req.query
8 | const movies = await MovieModel.getAll({ genre })
9 | res.json(movies)
10 | }
11 |
12 | static async getById (req, res) {
13 | const { id } = req.params
14 | const movie = await MovieModel.getById({ id })
15 | if (movie) return res.json(movie)
16 | res.status(404).json({ message: 'Movie not found' })
17 | }
18 |
19 | static async create (req, res) {
20 | const result = validateMovie(req.body)
21 |
22 | if (!result.success) {
23 | // 422 Unprocessable Entity
24 | return res.status(400).json({ error: JSON.parse(result.error.message) })
25 | }
26 |
27 | const newMovie = await MovieModel.create({ input: result.data })
28 |
29 | res.status(201).json(newMovie)
30 | }
31 |
32 | static async delete (req, res) {
33 | const { id } = req.params
34 |
35 | const result = await MovieModel.delete({ id })
36 |
37 | if (result === false) {
38 | return res.status(404).json({ message: 'Movie not found' })
39 | }
40 |
41 | return res.json({ message: 'Movie deleted' })
42 | }
43 |
44 | static async update (req, res) {
45 | const result = validatePartialMovie(req.body)
46 |
47 | if (!result.success) {
48 | return res.status(400).json({ error: JSON.parse(result.error.message) })
49 | }
50 |
51 | const { id } = req.params
52 |
53 | const updatedMovie = await MovieModel.update({ id, input: result.data })
54 |
55 | return res.json(updatedMovie)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/clase-4/models/mongodb/movie.js:
--------------------------------------------------------------------------------
1 | import { MongoClient, ObjectId, ServerApiVersion } from 'mongodb'
2 | const uri = 'mongodb+srv://user:???@cluster0.dhwmu.mongodb.net/?retryWrites=true&w=majority'
3 |
4 | // Create a MongoClient with a MongoClientOptions object to set the Stable API version
5 | const client = new MongoClient(uri, {
6 | serverApi: {
7 | version: ServerApiVersion.v1,
8 | strict: true,
9 | deprecationErrors: true
10 | }
11 | })
12 |
13 | async function connect () {
14 | try {
15 | await client.connect()
16 | const database = client.db('database')
17 | return database.collection('movies')
18 | } catch (error) {
19 | console.error('Error connecting to the database')
20 | console.error(error)
21 | await client.close()
22 | }
23 | }
24 |
25 | export class MovieModel {
26 | static async getAll ({ genre }) {
27 | const db = await connect()
28 |
29 | if (genre) {
30 | return db.find({
31 | genre: {
32 | $elemMatch: {
33 | $regex: genre,
34 | $options: 'i'
35 | }
36 | }
37 | }).toArray()
38 | }
39 |
40 | return db.find({}).toArray()
41 | }
42 |
43 | static async getById ({ id }) {
44 | const db = await connect()
45 | const objectId = new ObjectId(id)
46 | return db.findOne({ _id: objectId })
47 | }
48 |
49 | static async create ({ input }) {
50 | const db = await connect()
51 |
52 | const { insertedId } = await db.insertOne(input)
53 |
54 | return {
55 | id: insertedId,
56 | ...input
57 | }
58 | }
59 |
60 | static async delete ({ id }) {
61 | const db = await connect()
62 | const objectId = new ObjectId(id)
63 | const { deletedCount } = await db.deleteOne({ _id: objectId })
64 | return deletedCount > 0
65 | }
66 |
67 | static async update ({ id, input }) {
68 | const db = await connect()
69 | const objectId = new ObjectId(id)
70 |
71 | const { ok, value } = await db.findOneAndUpdate({ _id: objectId }, { $set: input }, { returnNewDocument: true })
72 |
73 | if (!ok) return false
74 |
75 | return value
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/clase-5/models/mongodb/movie.js:
--------------------------------------------------------------------------------
1 | import { MongoClient, ObjectId, ServerApiVersion } from 'mongodb'
2 | const uri = 'mongodb+srv://user:???@cluster0.dhwmu.mongodb.net/?retryWrites=true&w=majority'
3 |
4 | // Create a MongoClient with a MongoClientOptions object to set the Stable API version
5 | const client = new MongoClient(uri, {
6 | serverApi: {
7 | version: ServerApiVersion.v1,
8 | strict: true,
9 | deprecationErrors: true
10 | }
11 | })
12 |
13 | async function connect () {
14 | try {
15 | await client.connect()
16 | const database = client.db('database')
17 | return database.collection('movies')
18 | } catch (error) {
19 | console.error('Error connecting to the database')
20 | console.error(error)
21 | await client.close()
22 | }
23 | }
24 |
25 | export class MovieModel {
26 | static async getAll ({ genre }) {
27 | const db = await connect()
28 |
29 | if (genre) {
30 | return db.find({
31 | genre: {
32 | $elemMatch: {
33 | $regex: genre,
34 | $options: 'i'
35 | }
36 | }
37 | }).toArray()
38 | }
39 |
40 | return db.find({}).toArray()
41 | }
42 |
43 | static async getById ({ id }) {
44 | const db = await connect()
45 | const objectId = new ObjectId(id)
46 | return db.findOne({ _id: objectId })
47 | }
48 |
49 | static async create ({ input }) {
50 | const db = await connect()
51 |
52 | const { insertedId } = await db.insertOne(input)
53 |
54 | return {
55 | id: insertedId,
56 | ...input
57 | }
58 | }
59 |
60 | static async delete ({ id }) {
61 | const db = await connect()
62 | const objectId = new ObjectId(id)
63 | const { deletedCount } = await db.deleteOne({ _id: objectId })
64 | return deletedCount > 0
65 | }
66 |
67 | static async update ({ id, input }) {
68 | const db = await connect()
69 | const objectId = new ObjectId(id)
70 |
71 | const { ok, value } = await db.findOneAndUpdate({ _id: objectId }, { $set: input }, { returnNewDocument: true })
72 |
73 | if (!ok) return false
74 |
75 | return value
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/clase-6/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import logger from 'morgan'
3 | import dotenv from 'dotenv'
4 | import { createClient } from '@libsql/client'
5 |
6 | import { Server } from 'socket.io'
7 | import { createServer } from 'node:http'
8 |
9 | dotenv.config()
10 |
11 | const port = process.env.PORT ?? 3000
12 |
13 | const app = express()
14 | const server = createServer(app)
15 | const io = new Server(server, {
16 | connectionStateRecovery: {}
17 | })
18 |
19 | const db = createClient({
20 | url: 'libsql://cuddly-wasp-midudev.turso.io',
21 | authToken: process.env.DB_TOKEN
22 | })
23 |
24 | await db.execute(`
25 | CREATE TABLE IF NOT EXISTS messages (
26 | id INTEGER PRIMARY KEY AUTOINCREMENT,
27 | content TEXT,
28 | user TEXT
29 | )
30 | `)
31 |
32 | io.on('connection', async (socket) => {
33 | console.log('a user has connected!')
34 |
35 | socket.on('disconnect', () => {
36 | console.log('an user has disconnected')
37 | })
38 |
39 | socket.on('chat message', async (msg) => {
40 | let result
41 | const username = socket.handshake.auth.username ?? 'anonymous'
42 | console.log({ username })
43 | try {
44 | result = await db.execute({
45 | sql: 'INSERT INTO messages (content, user) VALUES (:msg, :username)',
46 | args: { msg, username }
47 | })
48 | } catch (e) {
49 | console.error(e)
50 | return
51 | }
52 |
53 | io.emit('chat message', msg, result.lastInsertRowid.toString(), username)
54 | })
55 |
56 | if (!socket.recovered) { // <- recuperase los mensajes sin conexión
57 | try {
58 | const results = await db.execute({
59 | sql: 'SELECT id, content, user FROM messages WHERE id > ?',
60 | args: [socket.handshake.auth.serverOffset ?? 0]
61 | })
62 |
63 | results.rows.forEach(row => {
64 | socket.emit('chat message', row.content, row.id.toString(), row.user)
65 | })
66 | } catch (e) {
67 | console.error(e)
68 | }
69 | }
70 | })
71 |
72 | app.use(logger('dev'))
73 |
74 | app.get('/', (req, res) => {
75 | res.sendFile(process.cwd() + '/client/index.html')
76 | })
77 |
78 | server.listen(port, () => {
79 | console.log(`Server running on port ${port}`)
80 | })
81 |
--------------------------------------------------------------------------------
/clase-5/models/mysql/movie.js:
--------------------------------------------------------------------------------
1 | import mysql from 'mysql2/promise'
2 |
3 | const DEFAULT_CONFIG = {
4 | host: 'localhost',
5 | user: 'root',
6 | port: 3306,
7 | password: '',
8 | database: 'moviesdb'
9 | }
10 | const connectionString = process.env.DATABASE_URL ?? DEFAULT_CONFIG
11 |
12 | const connection = await mysql.createConnection(connectionString)
13 |
14 | export class MovieModel {
15 | static async getAll ({ genre }) {
16 | console.log('getAll')
17 |
18 | if (genre) {
19 | const lowerCaseGenre = genre.toLowerCase()
20 |
21 | // get genre ids from database table using genre names
22 | const [genres] = await connection.query(
23 | 'SELECT id, name FROM genre WHERE LOWER(name) = ?;',
24 | [lowerCaseGenre]
25 | )
26 |
27 | // no genre found
28 | if (genres.length === 0) return []
29 |
30 | // get the id from the first genre result
31 | const [{ id }] = genres
32 |
33 | // get all movies ids from database table
34 | // la query a movie_genres
35 | // join
36 | // y devolver resultados..
37 | return []
38 | }
39 |
40 | const [movies] = await connection.query(
41 | 'SELECT title, year, director, duration, poster, rate, BIN_TO_UUID(id) id FROM movie;'
42 | )
43 |
44 | return movies
45 | }
46 |
47 | static async getById ({ id }) {
48 | const [movies] = await connection.query(
49 | `SELECT title, year, director, duration, poster, rate, BIN_TO_UUID(id) id
50 | FROM movie WHERE id = UUID_TO_BIN(?);`,
51 | [id]
52 | )
53 |
54 | if (movies.length === 0) return null
55 |
56 | return movies[0]
57 | }
58 |
59 | static async create ({ input }) {
60 | const {
61 | genre: genreInput, // genre is an array
62 | title,
63 | year,
64 | duration,
65 | director,
66 | rate,
67 | poster
68 | } = input
69 |
70 | // todo: crear la conexión de genre
71 |
72 | // crypto.randomUUID()
73 | const [uuidResult] = await connection.query('SELECT UUID() uuid;')
74 | const [{ uuid }] = uuidResult
75 |
76 | try {
77 | await connection.query(
78 | `INSERT INTO movie (id, title, year, director, duration, poster, rate)
79 | VALUES (UUID_TO_BIN("${uuid}"), ?, ?, ?, ?, ?, ?);`,
80 | [title, year, director, duration, poster, rate]
81 | )
82 | } catch (e) {
83 | // puede enviarle información sensible
84 | throw new Error('Error creating movie')
85 | // enviar la traza a un servicio interno
86 | // sendLog(e)
87 | }
88 |
89 | const [movies] = await connection.query(
90 | `SELECT title, year, director, duration, poster, rate, BIN_TO_UUID(id) id
91 | FROM movie WHERE id = UUID_TO_BIN(?);`,
92 | [uuid]
93 | )
94 |
95 | return movies[0]
96 | }
97 |
98 | static async delete ({ id }) {
99 | // ejercio fácil: crear el delete
100 | }
101 |
102 | static async update ({ id, input }) {
103 | // ejercicio fácil: crear el update
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/clase-3/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express') // require -> commonJS
2 | const crypto = require('node:crypto')
3 | const cors = require('cors')
4 |
5 | const movies = require('./movies.json')
6 | const { validateMovie, validatePartialMovie } = require('./schemas/movies')
7 |
8 | const app = express()
9 | app.use(express.json())
10 | app.use(cors({
11 | origin: (origin, callback) => {
12 | const ACCEPTED_ORIGINS = [
13 | 'http://localhost:8080',
14 | 'http://localhost:1234',
15 | 'https://movies.com',
16 | 'https://midu.dev'
17 | ]
18 |
19 | if (ACCEPTED_ORIGINS.includes(origin)) {
20 | return callback(null, true)
21 | }
22 |
23 | if (!origin) {
24 | return callback(null, true)
25 | }
26 |
27 | return callback(new Error('Not allowed by CORS'))
28 | }
29 | }))
30 | app.disable('x-powered-by') // deshabilitar el header X-Powered-By: Express
31 |
32 | // métodos normales: GET/HEAD/POST
33 | // métodos complejos: PUT/PATCH/DELETE
34 |
35 | // CORS PRE-Flight
36 | // OPTIONS
37 |
38 | // Todos los recursos que sean MOVIES se identifica con /movies
39 | app.get('/movies', (req, res) => {
40 | const { genre } = req.query
41 | if (genre) {
42 | const filteredMovies = movies.filter(
43 | movie => movie.genre.some(g => g.toLowerCase() === genre.toLowerCase())
44 | )
45 | return res.json(filteredMovies)
46 | }
47 | res.json(movies)
48 | })
49 |
50 | app.get('/movies/:id', (req, res) => {
51 | const { id } = req.params
52 | const movie = movies.find(movie => movie.id === id)
53 | if (movie) return res.json(movie)
54 | res.status(404).json({ message: 'Movie not found' })
55 | })
56 |
57 | app.post('/movies', (req, res) => {
58 | const result = validateMovie(req.body)
59 |
60 | if (!result.success) {
61 | // 422 Unprocessable Entity
62 | return res.status(400).json({ error: JSON.parse(result.error.message) })
63 | }
64 |
65 | // en base de datos
66 | const newMovie = {
67 | id: crypto.randomUUID(), // uuid v4
68 | ...result.data
69 | }
70 |
71 | // Esto no sería REST, porque estamos guardando
72 | // el estado de la aplicación en memoria
73 | movies.push(newMovie)
74 |
75 | res.status(201).json(newMovie)
76 | })
77 |
78 | app.delete('/movies/:id', (req, res) => {
79 | const { id } = req.params
80 | const movieIndex = movies.findIndex(movie => movie.id === id)
81 |
82 | if (movieIndex === -1) {
83 | return res.status(404).json({ message: 'Movie not found' })
84 | }
85 |
86 | movies.splice(movieIndex, 1)
87 |
88 | return res.json({ message: 'Movie deleted' })
89 | })
90 |
91 | app.patch('/movies/:id', (req, res) => {
92 | const result = validatePartialMovie(req.body)
93 |
94 | if (!result.success) {
95 | return res.status(400).json({ error: JSON.parse(result.error.message) })
96 | }
97 |
98 | const { id } = req.params
99 | const movieIndex = movies.findIndex(movie => movie.id === id)
100 |
101 | if (movieIndex === -1) {
102 | return res.status(404).json({ message: 'Movie not found' })
103 | }
104 |
105 | const updateMovie = {
106 | ...movies[movieIndex],
107 | ...result.data
108 | }
109 |
110 | movies[movieIndex] = updateMovie
111 |
112 | return res.json(updateMovie)
113 | })
114 |
115 | const PORT = process.env.PORT ?? 1234
116 |
117 | app.listen(PORT, () => {
118 | console.log(`server listening on port http://localhost:${PORT}`)
119 | })
120 |
--------------------------------------------------------------------------------
/clase-6/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
miduChat
8 |
9 |
57 |
58 |
140 |
141 |
142 |
143 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/clase-3/movies.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": "dcdd0fad-a94c-4810-8acc-5f108d3b18c3",
3 | "title": "The Shawshank Redemption",
4 | "year": 1994,
5 | "director": "Frank Darabont",
6 | "duration": 142,
7 | "poster": "https://i.ebayimg.com/images/g/4goAAOSwMyBe7hnQ/s-l1200.webp",
8 | "genre": ["Drama"],
9 | "rate": 9.3
10 | },
11 | {
12 | "id": "c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf",
13 | "title": "The Dark Knight",
14 | "year": 2008,
15 | "director": "Christopher Nolan",
16 | "duration": 152,
17 | "poster": "https://i.ebayimg.com/images/g/yokAAOSw8w1YARbm/s-l1200.jpg",
18 | "genre": ["Action", "Crime", "Drama"],
19 | "rate": 9.0
20 | },
21 | {
22 | "id": "5ad1a235-0d9c-410a-b32b-220d91689a08",
23 | "title": "Inception",
24 | "year": 2010,
25 | "director": "Christopher Nolan",
26 | "duration": 148,
27 | "poster": "https://m.media-amazon.com/images/I/91Rc8cAmnAL._AC_UF1000,1000_QL80_.jpg",
28 | "genre": ["Action", "Adventure", "Sci-Fi"],
29 | "rate": 8.8
30 | },
31 | {
32 | "id": "241bf55d-b649-4109-af7c-0e6890ded3fc",
33 | "title": "Pulp Fiction",
34 | "year": 1994,
35 | "director": "Quentin Tarantino",
36 | "duration": 154,
37 | "poster": "https://www.themoviedb.org/t/p/original/vQWk5YBFWF4bZaofAbv0tShwBvQ.jpg",
38 | "genre": ["Crime", "Drama"],
39 | "rate": 8.9
40 | },
41 | {
42 | "id": "9e6106f0-848b-4810-a11a-3d832a5610f9",
43 | "title": "Forrest Gump",
44 | "year": 1994,
45 | "director": "Robert Zemeckis",
46 | "duration": 142,
47 | "poster": "https://i.ebayimg.com/images/g/qR8AAOSwkvRZzuMD/s-l1600.jpg",
48 | "genre": ["Drama", "Romance"],
49 | "rate": 8.8
50 | },
51 | {
52 | "id": "7e3fd5ab-60ff-4ae2-92b6-9597f0308d1",
53 | "title": "Gladiator",
54 | "year": 2000,
55 | "director": "Ridley Scott",
56 | "duration": 155,
57 | "poster": "https://img.fruugo.com/product/0/60/14417600_max.jpg",
58 | "genre": ["Action", "Adventure", "Drama"],
59 | "rate": 8.5
60 | },
61 | {
62 | "id": "c906673b-3948-4402-ac7f-73ac3a9e3105",
63 | "title": "The Matrix",
64 | "year": 1999,
65 | "director": "Lana Wachowski",
66 | "duration": 136,
67 | "poster": "https://i.ebayimg.com/images/g/QFQAAOSwAQpfjaA6/s-l1200.jpg",
68 | "genre": ["Action", "Sci-Fi"],
69 | "rate": 8.7
70 | },
71 | {
72 | "id": "b6e03689-cccd-478e-8565-d92f40813b13",
73 | "title": "Interstellar",
74 | "year": 2014,
75 | "director": "Christopher Nolan",
76 | "duration": 169,
77 | "poster": "https://m.media-amazon.com/images/I/91obuWzA3XL._AC_UF1000,1000_QL80_.jpg",
78 | "genre": ["Adventure", "Drama", "Sci-Fi"],
79 | "rate": 8.6
80 | },
81 | {
82 | "id": "aa391090-b938-42eb-b520-86ea0aa3917b",
83 | "title": "The Lord of the Rings: The Return of the King",
84 | "year": 2003,
85 | "director": "Peter Jackson",
86 | "duration": 201,
87 | "poster": "https://i.ebayimg.com/images/g/0hoAAOSwe7peaMLW/s-l1600.jpg",
88 | "genre": ["Action", "Adventure", "Drama"],
89 | "rate": 8.9
90 | },
91 | {
92 | "id": "2e6900e2-0b48-4fb6-ad48-09c7086e54fe",
93 | "title": "The Lion King",
94 | "year": 1994,
95 | "director": "Roger Allers, Rob Minkoff",
96 | "duration": 88,
97 | "poster": "https://m.media-amazon.com/images/I/81BMmrwSFOL._AC_UF1000,1000_QL80_.jpg",
98 | "genre": ["Animation", "Adventure", "Drama"],
99 | "rate": 8.5
100 | },
101 | {
102 | "id": "04986507-b3ed-442c-8ae7-4c5df804f896",
103 | "title": "The Avengers",
104 | "year": 2012,
105 | "director": "Joss Whedon",
106 | "duration": 143,
107 | "poster": "https://img.fruugo.com/product/7/41/14532417_max.jpg",
108 | "genre": ["Action", "Adventure", "Sci-Fi"],
109 | "rate": 8.0
110 | },
111 | {
112 | "id": "7d2832f8-c70a-410e-8963-4c93bf36cc9c",
113 | "title": "Jurassic Park",
114 | "year": 1993,
115 | "director": "Steven Spielberg",
116 | "duration": 127,
117 | "poster": "https://vice-press.com/cdn/shop/products/Jurassic-Park-Editions-poster-florey.jpg?v=1654518755&width=1024",
118 | "genre": ["Adventure", "Sci-Fi"],
119 | "rate": 8.1
120 | },
121 | {
122 | "id": "ccf36f2e-8566-47f7-912d-9f4647250bc7",
123 | "title": "Titanic",
124 | "year": 1997,
125 | "director": "James Cameron",
126 | "duration": 195,
127 | "poster": "https://i.pinimg.com/originals/42/42/65/4242658e6f1b0d6322a4a93e0383108b.png",
128 | "genre": ["Drama", "Romance"],
129 | "rate": 7.8
130 | },
131 | {
132 | "id": "8fb17ae1-bdfe-45e5-a871-4772d7e526b8",
133 | "title": "The Social Network",
134 | "year": 2010,
135 | "director": "David Fincher",
136 | "duration": 120,
137 | "poster": "https://i.pinimg.com/originals/7e/37/b9/7e37b994b613e94cba64f307b1983e39.jpg",
138 | "genre": ["Biography", "Drama"],
139 | "rate": 7.7
140 | },
141 | {
142 | "id": "6a360a18-c645-4b47-9a7b-2a71babbf3e0",
143 | "title": "Avatar",
144 | "year": 2009,
145 | "director": "James Cameron",
146 | "duration": 162,
147 | "poster": "https://i.etsystatic.com/35681979/r/il/dfe3ba/3957859451/il_fullxfull.3957859451_h27r.jpg",
148 | "genre": ["Action", "Adventure", "Fantasy"],
149 | "rate": 7.8
150 | }]
--------------------------------------------------------------------------------
/clase-4/movies.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": "dcdd0fad-a94c-4810-8acc-5f108d3b18c3",
3 | "title": "The Shawshank Redemption",
4 | "year": 1994,
5 | "director": "Frank Darabont",
6 | "duration": 142,
7 | "poster": "https://i.ebayimg.com/images/g/4goAAOSwMyBe7hnQ/s-l1200.webp",
8 | "genre": ["Drama"],
9 | "rate": 9.3
10 | },
11 | {
12 | "id": "c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf",
13 | "title": "The Dark Knight",
14 | "year": 2008,
15 | "director": "Christopher Nolan",
16 | "duration": 152,
17 | "poster": "https://i.ebayimg.com/images/g/yokAAOSw8w1YARbm/s-l1200.jpg",
18 | "genre": ["Action", "Crime", "Drama"],
19 | "rate": 9.0
20 | },
21 | {
22 | "id": "5ad1a235-0d9c-410a-b32b-220d91689a08",
23 | "title": "Inception",
24 | "year": 2010,
25 | "director": "Christopher Nolan",
26 | "duration": 148,
27 | "poster": "https://m.media-amazon.com/images/I/91Rc8cAmnAL._AC_UF1000,1000_QL80_.jpg",
28 | "genre": ["Action", "Adventure", "Sci-Fi"],
29 | "rate": 8.8
30 | },
31 | {
32 | "id": "241bf55d-b649-4109-af7c-0e6890ded3fc",
33 | "title": "Pulp Fiction",
34 | "year": 1994,
35 | "director": "Quentin Tarantino",
36 | "duration": 154,
37 | "poster": "https://www.themoviedb.org/t/p/original/vQWk5YBFWF4bZaofAbv0tShwBvQ.jpg",
38 | "genre": ["Crime", "Drama"],
39 | "rate": 8.9
40 | },
41 | {
42 | "id": "9e6106f0-848b-4810-a11a-3d832a5610f9",
43 | "title": "Forrest Gump",
44 | "year": 1994,
45 | "director": "Robert Zemeckis",
46 | "duration": 142,
47 | "poster": "https://i.ebayimg.com/images/g/qR8AAOSwkvRZzuMD/s-l1600.jpg",
48 | "genre": ["Drama", "Romance"],
49 | "rate": 8.8
50 | },
51 | {
52 | "id": "7e3fd5ab-60ff-4ae2-92b6-9597f0308d1",
53 | "title": "Gladiator",
54 | "year": 2000,
55 | "director": "Ridley Scott",
56 | "duration": 155,
57 | "poster": "https://img.fruugo.com/product/0/60/14417600_max.jpg",
58 | "genre": ["Action", "Adventure", "Drama"],
59 | "rate": 8.5
60 | },
61 | {
62 | "id": "c906673b-3948-4402-ac7f-73ac3a9e3105",
63 | "title": "The Matrix",
64 | "year": 1999,
65 | "director": "Lana Wachowski",
66 | "duration": 136,
67 | "poster": "https://i.ebayimg.com/images/g/QFQAAOSwAQpfjaA6/s-l1200.jpg",
68 | "genre": ["Action", "Sci-Fi"],
69 | "rate": 8.7
70 | },
71 | {
72 | "id": "b6e03689-cccd-478e-8565-d92f40813b13",
73 | "title": "Interstellar",
74 | "year": 2014,
75 | "director": "Christopher Nolan",
76 | "duration": 169,
77 | "poster": "https://m.media-amazon.com/images/I/91obuWzA3XL._AC_UF1000,1000_QL80_.jpg",
78 | "genre": ["Adventure", "Drama", "Sci-Fi"],
79 | "rate": 8.6
80 | },
81 | {
82 | "id": "aa391090-b938-42eb-b520-86ea0aa3917b",
83 | "title": "The Lord of the Rings: The Return of the King",
84 | "year": 2003,
85 | "director": "Peter Jackson",
86 | "duration": 201,
87 | "poster": "https://i.ebayimg.com/images/g/0hoAAOSwe7peaMLW/s-l1600.jpg",
88 | "genre": ["Action", "Adventure", "Drama"],
89 | "rate": 8.9
90 | },
91 | {
92 | "id": "2e6900e2-0b48-4fb6-ad48-09c7086e54fe",
93 | "title": "The Lion King",
94 | "year": 1994,
95 | "director": "Roger Allers, Rob Minkoff",
96 | "duration": 88,
97 | "poster": "https://m.media-amazon.com/images/I/81BMmrwSFOL._AC_UF1000,1000_QL80_.jpg",
98 | "genre": ["Animation", "Adventure", "Drama"],
99 | "rate": 8.5
100 | },
101 | {
102 | "id": "04986507-b3ed-442c-8ae7-4c5df804f896",
103 | "title": "The Avengers",
104 | "year": 2012,
105 | "director": "Joss Whedon",
106 | "duration": 143,
107 | "poster": "https://img.fruugo.com/product/7/41/14532417_max.jpg",
108 | "genre": ["Action", "Adventure", "Sci-Fi"],
109 | "rate": 8.0
110 | },
111 | {
112 | "id": "7d2832f8-c70a-410e-8963-4c93bf36cc9c",
113 | "title": "Jurassic Park",
114 | "year": 1993,
115 | "director": "Steven Spielberg",
116 | "duration": 127,
117 | "poster": "https://vice-press.com/cdn/shop/products/Jurassic-Park-Editions-poster-florey.jpg?v=1654518755&width=1024",
118 | "genre": ["Adventure", "Sci-Fi"],
119 | "rate": 8.1
120 | },
121 | {
122 | "id": "ccf36f2e-8566-47f7-912d-9f4647250bc7",
123 | "title": "Titanic",
124 | "year": 1997,
125 | "director": "James Cameron",
126 | "duration": 195,
127 | "poster": "https://i.pinimg.com/originals/42/42/65/4242658e6f1b0d6322a4a93e0383108b.png",
128 | "genre": ["Drama", "Romance"],
129 | "rate": 7.8
130 | },
131 | {
132 | "id": "8fb17ae1-bdfe-45e5-a871-4772d7e526b8",
133 | "title": "The Social Network",
134 | "year": 2010,
135 | "director": "David Fincher",
136 | "duration": 120,
137 | "poster": "https://i.pinimg.com/originals/7e/37/b9/7e37b994b613e94cba64f307b1983e39.jpg",
138 | "genre": ["Biography", "Drama"],
139 | "rate": 7.7
140 | },
141 | {
142 | "id": "6a360a18-c645-4b47-9a7b-2a71babbf3e0",
143 | "title": "Avatar",
144 | "year": 2009,
145 | "director": "James Cameron",
146 | "duration": 162,
147 | "poster": "https://i.etsystatic.com/35681979/r/il/dfe3ba/3957859451/il_fullxfull.3957859451_h27r.jpg",
148 | "genre": ["Action", "Adventure", "Fantasy"],
149 | "rate": 7.8
150 | }]
--------------------------------------------------------------------------------
/clase-5/movies.json:
--------------------------------------------------------------------------------
1 | [{
2 | "id": "dcdd0fad-a94c-4810-8acc-5f108d3b18c3",
3 | "title": "The Shawshank Redemption",
4 | "year": 1994,
5 | "director": "Frank Darabont",
6 | "duration": 142,
7 | "poster": "https://i.ebayimg.com/images/g/4goAAOSwMyBe7hnQ/s-l1200.webp",
8 | "genre": ["Drama"],
9 | "rate": 9.3
10 | },
11 | {
12 | "id": "c8a7d63f-3b04-44d3-9d95-8782fd7dcfaf",
13 | "title": "The Dark Knight",
14 | "year": 2008,
15 | "director": "Christopher Nolan",
16 | "duration": 152,
17 | "poster": "https://i.ebayimg.com/images/g/yokAAOSw8w1YARbm/s-l1200.jpg",
18 | "genre": ["Action", "Crime", "Drama"],
19 | "rate": 9.0
20 | },
21 | {
22 | "id": "5ad1a235-0d9c-410a-b32b-220d91689a08",
23 | "title": "Inception",
24 | "year": 2010,
25 | "director": "Christopher Nolan",
26 | "duration": 148,
27 | "poster": "https://m.media-amazon.com/images/I/91Rc8cAmnAL._AC_UF1000,1000_QL80_.jpg",
28 | "genre": ["Action", "Adventure", "Sci-Fi"],
29 | "rate": 8.8
30 | },
31 | {
32 | "id": "241bf55d-b649-4109-af7c-0e6890ded3fc",
33 | "title": "Pulp Fiction",
34 | "year": 1994,
35 | "director": "Quentin Tarantino",
36 | "duration": 154,
37 | "poster": "https://www.themoviedb.org/t/p/original/vQWk5YBFWF4bZaofAbv0tShwBvQ.jpg",
38 | "genre": ["Crime", "Drama"],
39 | "rate": 8.9
40 | },
41 | {
42 | "id": "9e6106f0-848b-4810-a11a-3d832a5610f9",
43 | "title": "Forrest Gump",
44 | "year": 1994,
45 | "director": "Robert Zemeckis",
46 | "duration": 142,
47 | "poster": "https://i.ebayimg.com/images/g/qR8AAOSwkvRZzuMD/s-l1600.jpg",
48 | "genre": ["Drama", "Romance"],
49 | "rate": 8.8
50 | },
51 | {
52 | "id": "7e3fd5ab-60ff-4ae2-92b6-9597f0308d1",
53 | "title": "Gladiator",
54 | "year": 2000,
55 | "director": "Ridley Scott",
56 | "duration": 155,
57 | "poster": "https://img.fruugo.com/product/0/60/14417600_max.jpg",
58 | "genre": ["Action", "Adventure", "Drama"],
59 | "rate": 8.5
60 | },
61 | {
62 | "id": "c906673b-3948-4402-ac7f-73ac3a9e3105",
63 | "title": "The Matrix",
64 | "year": 1999,
65 | "director": "Lana Wachowski",
66 | "duration": 136,
67 | "poster": "https://i.ebayimg.com/images/g/QFQAAOSwAQpfjaA6/s-l1200.jpg",
68 | "genre": ["Action", "Sci-Fi"],
69 | "rate": 8.7
70 | },
71 | {
72 | "id": "b6e03689-cccd-478e-8565-d92f40813b13",
73 | "title": "Interstellar",
74 | "year": 2014,
75 | "director": "Christopher Nolan",
76 | "duration": 169,
77 | "poster": "https://m.media-amazon.com/images/I/91obuWzA3XL._AC_UF1000,1000_QL80_.jpg",
78 | "genre": ["Adventure", "Drama", "Sci-Fi"],
79 | "rate": 8.6
80 | },
81 | {
82 | "id": "aa391090-b938-42eb-b520-86ea0aa3917b",
83 | "title": "The Lord of the Rings: The Return of the King",
84 | "year": 2003,
85 | "director": "Peter Jackson",
86 | "duration": 201,
87 | "poster": "https://i.ebayimg.com/images/g/0hoAAOSwe7peaMLW/s-l1600.jpg",
88 | "genre": ["Action", "Adventure", "Drama"],
89 | "rate": 8.9
90 | },
91 | {
92 | "id": "2e6900e2-0b48-4fb6-ad48-09c7086e54fe",
93 | "title": "The Lion King",
94 | "year": 1994,
95 | "director": "Roger Allers, Rob Minkoff",
96 | "duration": 88,
97 | "poster": "https://m.media-amazon.com/images/I/81BMmrwSFOL._AC_UF1000,1000_QL80_.jpg",
98 | "genre": ["Animation", "Adventure", "Drama"],
99 | "rate": 8.5
100 | },
101 | {
102 | "id": "04986507-b3ed-442c-8ae7-4c5df804f896",
103 | "title": "The Avengers",
104 | "year": 2012,
105 | "director": "Joss Whedon",
106 | "duration": 143,
107 | "poster": "https://img.fruugo.com/product/7/41/14532417_max.jpg",
108 | "genre": ["Action", "Adventure", "Sci-Fi"],
109 | "rate": 8.0
110 | },
111 | {
112 | "id": "7d2832f8-c70a-410e-8963-4c93bf36cc9c",
113 | "title": "Jurassic Park",
114 | "year": 1993,
115 | "director": "Steven Spielberg",
116 | "duration": 127,
117 | "poster": "https://vice-press.com/cdn/shop/products/Jurassic-Park-Editions-poster-florey.jpg?v=1654518755&width=1024",
118 | "genre": ["Adventure", "Sci-Fi"],
119 | "rate": 8.1
120 | },
121 | {
122 | "id": "ccf36f2e-8566-47f7-912d-9f4647250bc7",
123 | "title": "Titanic",
124 | "year": 1997,
125 | "director": "James Cameron",
126 | "duration": 195,
127 | "poster": "https://i.pinimg.com/originals/42/42/65/4242658e6f1b0d6322a4a93e0383108b.png",
128 | "genre": ["Drama", "Romance"],
129 | "rate": 7.8
130 | },
131 | {
132 | "id": "8fb17ae1-bdfe-45e5-a871-4772d7e526b8",
133 | "title": "The Social Network",
134 | "year": 2010,
135 | "director": "David Fincher",
136 | "duration": 120,
137 | "poster": "https://i.pinimg.com/originals/7e/37/b9/7e37b994b613e94cba64f307b1983e39.jpg",
138 | "genre": ["Biography", "Drama"],
139 | "rate": 7.7
140 | },
141 | {
142 | "id": "6a360a18-c645-4b47-9a7b-2a71babbf3e0",
143 | "title": "Avatar",
144 | "year": 2009,
145 | "director": "James Cameron",
146 | "duration": 162,
147 | "poster": "https://i.etsystatic.com/35681979/r/il/dfe3ba/3957859451/il_fullxfull.3957859451_h27r.jpg",
148 | "genre": ["Action", "Adventure", "Fantasy"],
149 | "rate": 7.8
150 | }]
--------------------------------------------------------------------------------
/clase-6/README.md:
--------------------------------------------------------------------------------
1 | HTTP vs Web Sockets
2 | ➡️ https://ably.com/topic/websockets-vs-http
3 |
4 | Comparativa: 01-comparativa.webp
5 |
6 | HTTP es un protocolo de comunicación que se basa en el intercambio de mensajes entre un cliente y un servidor. Usa TCP como protocolo de transporte.
7 |
8 | El cliente realiza una petición y el servidor responde con una respuesta.
9 |
10 | Explicación: 02-http.webp
11 |
12 | Web Sockets es un protocolo de comunicación que se basa en el intercambio de mensajes entre un cliente y un servidor. Usa TCP como protocolo de transporte.
13 |
14 | La diferencia es que el cliente y el servidor pueden intercambiar mensajes en cualquier momento.
15 |
16 | Explicación: 03-websockets.webp
17 |
18 | Cuando HTTP es mejor:
19 | - Para pedir recursos
20 | - Recursos cacheables
21 | - Para REST API y uso de metodos HTTP
22 | - Sincronizar eventos
23 |
24 | Cuando Web Sockets es mejor:
25 | - Para comunicación bidireccional
26 | - Para comunicación en tiempo real
27 | - Para comunicación de alta frecuencia
28 | - Actualización con poca latencia
29 |
30 | # Instalación de Express
31 |
32 | npm install express
33 |
34 | ```javascript
35 | import express from 'express'
36 |
37 | const port = process.env.PORT ?? 3000;
38 |
39 | const app = express();
40 |
41 | app.get('/', (req, res) => {
42 | res.send('
Front Page
');
43 | });
44 |
45 | // no usar app, si no server
46 | server.listen(port, () => {
47 | console.log(`Started at ${port}`);
48 | });
49 | ```
50 |
51 | # Instalando para mejorar el logging de Express
52 |
53 | ```javascript
54 | import express from 'express'
55 | import logger from 'morgan'
56 |
57 | const port = process.env.PORT ?? 3000;
58 |
59 | const app = express();
60 | app.use(logger('dev'));
61 |
62 | app.get('/', (req, res) => {
63 | res.send('
Front Page
');
64 | });
65 |
66 | app.listen(port, () => {
67 | console.log(`Started at ${port}`);
68 | });
69 | ```
70 |
71 | # Servir nuestro HTML
72 |
73 | ```javascript
74 | app.get('/', (req, res) => {
75 | res.sendFile(process.cwd() + '/client/index.html');
76 | });
77 | ```
78 |
79 | # Crear el HTML
80 |
81 | ```html
82 |
83 |
84 |
85 |
86 |
87 |
Socket.IO chat
88 |
114 |
115 |
116 |
117 |
122 |
123 |
124 |
125 | ```
126 |
127 | # Instalando socket.io
128 |
129 | `npm install socket.io`
130 |
131 | ```js
132 | import { createServer } from 'node:http'
133 | import { Server } from 'socket.io'
134 |
135 | const server = createServer(app)
136 | const io = new Server(server)
137 |
138 | io.on('connection', (socket) => {
139 | console.log('a user connected')
140 | })
141 | ```
142 |
143 | # Cargamos socket en el client
144 |
145 | ```html
146 |
151 | ```
152 |
153 | Vemos que ahora cuando se conecta un usuario, se muestra en el servidor "a user connected". Vamos a ver también cuando se desconecta.
154 |
155 | ```js
156 | io.on('connection', (socket) => {
157 | console.log('a user connected')
158 |
159 | socket.on('disconnect', () => {
160 | console.log('user disconnected')
161 | })
162 | })
163 | ```
164 |
165 | # Mejorando la apariencia del form:
166 |
167 | ```html
168 |
203 | ```
204 |
205 | # Enviar eventos
206 |
207 | ```html
208 |
221 | ```
222 |
223 | # Escuchar evento en el servidor
224 |
225 | ```javascript
226 | io.on('connection', (socket) => {
227 | console.log('a user connected')
228 |
229 | socket.on('chat message', (msg) => {
230 | console.log('message: ' + msg)
231 | })
232 |
233 | socket.on('disconnect', () => {
234 | console.log('user disconnected')
235 | })
236 | })
237 | ```
238 |
239 | # Enviar broadcast desde el servidor
240 |
241 | Emitir el evento a todos los clientes que tenemos conectados:
242 |
243 | ```javascript
244 | io.on('connection', (socket) => {
245 | console.log('a user connected')
246 |
247 | socket.on('chat message', (msg) => {
248 | io.emit('chat message', msg) // <-----
249 | })
250 |
251 | socket.on('disconnect', () => {
252 | console.log('user disconnected')
253 | })
254 | })
255 | ```
256 |
257 | # Escuchar broadcasting en cliente
258 |
259 | ```html
260 |
268 | ```
269 |
270 | # Mejorar estilos de los mensajes
271 |
272 | ```html
273 |
288 | ```
289 |
290 | # Recuperar usuario desconectado
291 |
292 | ¿Qué pasa si el usuario se desconecta?
293 | Los mensajes se pierden.
294 |
295 | Cómo evitarlo:
296 |
297 | ```javascript
298 | const io = new Server(server, {
299 | connectionStateRecovery: {}
300 | })
301 | ```
302 |
303 | # Mostrar al usuario cuando pierde conexión
304 |
305 | ```html
306 |
316 |
317 |
328 | ```
329 |
330 | # Sincronizar con base de datos
331 |
332 | https://turso.tech/
333 |
334 | ```sh
335 | $ brew install tursodatabase/tap/turso
336 | $ turso auth login
337 | $ turso db create
338 | $ turso db show humble-spectrum
339 | ```
340 |
341 | ```sh
342 | $ npm install @libsql/client dotenv
343 | ```
344 |
345 | Crear la conexión:
346 |
347 | ```javascript
348 | import dotenv from 'dotenv'
349 | import { createClient } from "@libsql/client"
350 |
351 | dotenv.config()
352 |
353 | const db = createClient({
354 | url: "libsql://humble-spectrum-midudev.turso.io",
355 | authToken: process.env.DB_TOKEN
356 | });
357 | ```
358 |
359 | ```sh
360 | $ turso db show humble-spectrum
361 | $ turso db tokens create humble-spectrum | pbcopy
362 | ```
363 |
364 | Crear la tabla si no existe:
365 | ```javascript
366 | await db.execute(`
367 | CREATE TABLE IF NOT EXISTS messages (
368 | id INTEGER PRIMARY KEY AUTOINCREMENT,
369 | content TEXT
370 | );
371 | `);
372 | ```
373 |
374 | Guarda el mensaje cada vez que se envía:
375 | ```javascript
376 | socket.on('chat message', async (msg) => {
377 | let result;
378 | try {
379 | console.log(msg)
380 |
381 | result = await db.execute({
382 | sql: `INSERT INTO messages (content) VALUES (:content)`,
383 | args: { content: msg }
384 | });
385 | } catch (e) {
386 | console.error(e)
387 | return
388 | }
389 |
390 | io.emit('chat message', msg, result.lastInsertRowid.toString())
391 | })
392 | ```
393 |
394 | # Saber en qué mensaje se quedó el cliente
395 |
396 | ```javascript
397 | const socket = io({
398 | auth: {
399 | serverOffset: 0
400 | }
401 | })
402 |
403 | socket.on('chat message', (msg, serverOffset) => {
404 | const item = `
${msg}`;
405 | messages.insertAdjacentHTML('beforeend', item);
406 | socket.auth.serverOffset = serverOffset // <----
407 | })
408 | ```
409 |
410 | # Recuperar mensajes anteriores
411 |
412 | ```javascript
413 | io.on('connection', async (socket) => {
414 | console.log('a user connected')
415 |
416 | socket.on('chat message', async (msg) => {
417 | // ...
418 | })
419 |
420 | if (!socket.recovered) {
421 | try {
422 | const results = await db.execute({
423 | sql: `SELECT id, content FROM messages WHERE id > ?`,
424 | args: [socket.handshake.auth.serverOffset ?? 0]
425 | })
426 |
427 | results.rows.forEach(row => {
428 | socket.emit('chat message', row.content, row.id)
429 | })
430 | } catch (e) {
431 | console.error(e)
432 | }
433 | }
434 | ```
435 |
436 | # Tener usuario random
437 |
438 | `$ turso db shell humble-spectrum`
439 |
440 | ```javascript
441 | await db.execute(`
442 | CREATE TABLE IF NOT EXISTS messages (
443 | id INTEGER PRIMARY KEY AUTOINCREMENT,
444 | content TEXT,
445 | user TEXT
446 | );
447 | `);
448 | ```
449 |
450 | # Crear usuario random en el cliente:
451 |
452 | ```javascript
453 | const getUsername = async () => {
454 | const username = localStorage.getItem('username')
455 | if (username) {
456 | logMessage(`generated username ${username}`)
457 | return username
458 | }
459 |
460 | const res = await fetch('https://random-data-api.com/api/v2/users')
461 | const { username: randomUsername } = await res.json()
462 |
463 | localStorage.setItem('username', randomUsername)
464 | logMessage(`generated username ${randomUsername}`)
465 |
466 | return randomUsername
467 | }
468 |
469 | const socket = io({
470 | auth: {
471 | username: await getUsername(),
472 | serverOffset: 0
473 | }
474 | })
475 | ```
476 |
477 | # Mostrar usuario visualmente en el chat
478 |
479 | ```javascript [cliente]
480 | socket.on('chat message', (msg, serverOffset, username) => {
481 | const item = `
482 | ${msg}
enviado por ${username}
483 | `;
484 | ```
485 |
486 | # Guardar usuario en la base de datos
487 |
488 | ```javascript [servidor]
489 | socket.on('chat message', async (msg) => {
490 | let result;
491 | // ⬇️
492 | const user = socket.handshake.auth.username ?? 'anonymous'
493 |
494 | try {
495 | result = await db.execute({ // ⬇️
496 | sql: `INSERT INTO messages (content, user) VALUES (:content, :user)`,
497 | args: { content: msg, user }
498 | });
499 | catch {}
500 |
501 | io.emit('chat message', msg, result.lastInsertRowid.toString(), user) // <------
502 | ```
503 |
504 | ```javascript [servidor]
505 | if (!socket.recovered) {
506 | try {
507 | const results = await db.execute({ // ⬇️
508 | sql: `SELECT id, content, user FROM messages WHERE id > ?`,
509 | args: [socket.handshake.auth.serverOffset ?? 0]
510 | })
511 |
512 | results.rows.forEach(row => { // ⬇️
513 | socket.emit('chat message', row.content, row.id, row.user)
514 | })
515 | } catch (e) {
516 | console.error(e)
517 | }
518 | }
519 | ```
--------------------------------------------------------------------------------
/clase-2/pokemon/ditto.json:
--------------------------------------------------------------------------------
1 | {"abilities":[{"ability":{"name":"limber","url":"https://pokeapi.co/api/v2/ability/7/"},"is_hidden":false,"slot":1},{"ability":{"name":"imposter","url":"https://pokeapi.co/api/v2/ability/150/"},"is_hidden":true,"slot":3}],"base_experience":101,"forms":[{"name":"ditto","url":"https://pokeapi.co/api/v2/pokemon-form/132/"}],"game_indices":[{"game_index":76,"version":{"name":"red","url":"https://pokeapi.co/api/v2/version/1/"}},{"game_index":76,"version":{"name":"blue","url":"https://pokeapi.co/api/v2/version/2/"}},{"game_index":76,"version":{"name":"yellow","url":"https://pokeapi.co/api/v2/version/3/"}},{"game_index":132,"version":{"name":"gold","url":"https://pokeapi.co/api/v2/version/4/"}},{"game_index":132,"version":{"name":"silver","url":"https://pokeapi.co/api/v2/version/5/"}},{"game_index":132,"version":{"name":"crystal","url":"https://pokeapi.co/api/v2/version/6/"}},{"game_index":132,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"game_index":132,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"game_index":132,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"game_index":132,"version":{"name":"firered","url":"https://pokeapi.co/api/v2/version/10/"}},{"game_index":132,"version":{"name":"leafgreen","url":"https://pokeapi.co/api/v2/version/11/"}},{"game_index":132,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"game_index":132,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"game_index":132,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"game_index":132,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"game_index":132,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"game_index":132,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"game_index":132,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"game_index":132,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"game_index":132,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}}],"height":3,"held_items":[{"item":{"name":"metal-powder","url":"https://pokeapi.co/api/v2/item/234/"},"version_details":[{"rarity":5,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"rarity":5,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"rarity":5,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"rarity":5,"version":{"name":"firered","url":"https://pokeapi.co/api/v2/version/10/"}},{"rarity":5,"version":{"name":"leafgreen","url":"https://pokeapi.co/api/v2/version/11/"}},{"rarity":5,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":5,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":5,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":5,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":5,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":5,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":5,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"rarity":5,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"rarity":5,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}},{"rarity":5,"version":{"name":"x","url":"https://pokeapi.co/api/v2/version/23/"}},{"rarity":5,"version":{"name":"y","url":"https://pokeapi.co/api/v2/version/24/"}},{"rarity":5,"version":{"name":"omega-ruby","url":"https://pokeapi.co/api/v2/version/25/"}},{"rarity":5,"version":{"name":"alpha-sapphire","url":"https://pokeapi.co/api/v2/version/26/"}},{"rarity":5,"version":{"name":"sun","url":"https://pokeapi.co/api/v2/version/27/"}},{"rarity":5,"version":{"name":"moon","url":"https://pokeapi.co/api/v2/version/28/"}},{"rarity":5,"version":{"name":"ultra-sun","url":"https://pokeapi.co/api/v2/version/29/"}},{"rarity":5,"version":{"name":"ultra-moon","url":"https://pokeapi.co/api/v2/version/30/"}}]},{"item":{"name":"quick-powder","url":"https://pokeapi.co/api/v2/item/251/"},"version_details":[{"rarity":50,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":50,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":50,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":50,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":50,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":50,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":50,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"rarity":50,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"rarity":50,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}},{"rarity":50,"version":{"name":"x","url":"https://pokeapi.co/api/v2/version/23/"}},{"rarity":50,"version":{"name":"y","url":"https://pokeapi.co/api/v2/version/24/"}},{"rarity":50,"version":{"name":"omega-ruby","url":"https://pokeapi.co/api/v2/version/25/"}},{"rarity":50,"version":{"name":"alpha-sapphire","url":"https://pokeapi.co/api/v2/version/26/"}},{"rarity":50,"version":{"name":"sun","url":"https://pokeapi.co/api/v2/version/27/"}},{"rarity":50,"version":{"name":"moon","url":"https://pokeapi.co/api/v2/version/28/"}},{"rarity":50,"version":{"name":"ultra-sun","url":"https://pokeapi.co/api/v2/version/29/"}},{"rarity":50,"version":{"name":"ultra-moon","url":"https://pokeapi.co/api/v2/version/30/"}}]}],"id":132,"is_default":true,"location_area_encounters":"https://pokeapi.co/api/v2/pokemon/132/encounters","moves":[{"move":{"name":"transform","url":"https://pokeapi.co/api/v2/move/144/"},"version_group_details":[{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"red-blue","url":"https://pokeapi.co/api/v2/version-group/1/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"yellow","url":"https://pokeapi.co/api/v2/version-group/2/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"gold-silver","url":"https://pokeapi.co/api/v2/version-group/3/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"crystal","url":"https://pokeapi.co/api/v2/version-group/4/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"ruby-sapphire","url":"https://pokeapi.co/api/v2/version-group/5/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"emerald","url":"https://pokeapi.co/api/v2/version-group/6/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"firered-leafgreen","url":"https://pokeapi.co/api/v2/version-group/7/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"diamond-pearl","url":"https://pokeapi.co/api/v2/version-group/8/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"platinum","url":"https://pokeapi.co/api/v2/version-group/9/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"heartgold-soulsilver","url":"https://pokeapi.co/api/v2/version-group/10/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"black-white","url":"https://pokeapi.co/api/v2/version-group/11/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"colosseum","url":"https://pokeapi.co/api/v2/version-group/12/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"xd","url":"https://pokeapi.co/api/v2/version-group/13/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"black-2-white-2","url":"https://pokeapi.co/api/v2/version-group/14/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"x-y","url":"https://pokeapi.co/api/v2/version-group/15/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"omega-ruby-alpha-sapphire","url":"https://pokeapi.co/api/v2/version-group/16/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"sun-moon","url":"https://pokeapi.co/api/v2/version-group/17/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"ultra-sun-ultra-moon","url":"https://pokeapi.co/api/v2/version-group/18/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"lets-go-pikachu-lets-go-eevee","url":"https://pokeapi.co/api/v2/version-group/19/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"sword-shield","url":"https://pokeapi.co/api/v2/version-group/20/"}},{"level_learned_at":1,"move_learn_method":{"name":"level-up","url":"https://pokeapi.co/api/v2/move-learn-method/1/"},"version_group":{"name":"scarlet-violet","url":"https://pokeapi.co/api/v2/version-group/25/"}}]}],"name":"ditto","order":214,"past_types":[],"species":{"name":"ditto","url":"https://pokeapi.co/api/v2/pokemon-species/132/"},"sprites":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/132.png","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/132.png","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/132.png","front_shiny_female":null,"other":{"dream_world":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/dream-world/132.svg","front_female":null},"home":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/home/shiny/132.png","front_shiny_female":null},"official-artwork":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/shiny/132.png"}},"versions":{"generation-i":{"red-blue":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/back/132.png","back_gray":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/back/gray/132.png","back_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/transparent/back/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/132.png","front_gray":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/gray/132.png","front_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/red-blue/transparent/132.png"},"yellow":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/back/132.png","back_gray":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/back/gray/132.png","back_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/transparent/back/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/132.png","front_gray":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/gray/132.png","front_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-i/yellow/transparent/132.png"}},"generation-ii":{"crystal":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/back/132.png","back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/back/shiny/132.png","back_shiny_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/transparent/back/shiny/132.png","back_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/transparent/back/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/shiny/132.png","front_shiny_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/transparent/shiny/132.png","front_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/crystal/transparent/132.png"},"gold":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/back/132.png","back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/back/shiny/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/shiny/132.png","front_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/gold/transparent/132.png"},"silver":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/silver/back/132.png","back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/silver/back/shiny/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/silver/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/silver/shiny/132.png","front_transparent":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-ii/silver/transparent/132.png"}},"generation-iii":{"emerald":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/emerald/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/emerald/shiny/132.png"},"firered-leafgreen":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/firered-leafgreen/back/132.png","back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/firered-leafgreen/back/shiny/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/firered-leafgreen/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/firered-leafgreen/shiny/132.png"},"ruby-sapphire":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/ruby-sapphire/back/132.png","back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/ruby-sapphire/back/shiny/132.png","front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/ruby-sapphire/132.png","front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iii/ruby-sapphire/shiny/132.png"}},"generation-iv":{"diamond-pearl":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/diamond-pearl/back/132.png","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/diamond-pearl/back/shiny/132.png","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/diamond-pearl/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/diamond-pearl/shiny/132.png","front_shiny_female":null},"heartgold-soulsilver":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/heartgold-soulsilver/back/132.png","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/heartgold-soulsilver/back/shiny/132.png","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/heartgold-soulsilver/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/heartgold-soulsilver/shiny/132.png","front_shiny_female":null},"platinum":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/platinum/back/132.png","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/platinum/back/shiny/132.png","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/platinum/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-iv/platinum/shiny/132.png","front_shiny_female":null}},"generation-v":{"black-white":{"animated":{"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/back/132.gif","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/back/shiny/132.gif","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/132.gif","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/shiny/132.gif","front_shiny_female":null},"back_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/back/132.png","back_female":null,"back_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/back/shiny/132.png","back_shiny_female":null,"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/shiny/132.png","front_shiny_female":null}},"generation-vi":{"omegaruby-alphasapphire":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vi/omegaruby-alphasapphire/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vi/omegaruby-alphasapphire/shiny/132.png","front_shiny_female":null},"x-y":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vi/x-y/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vi/x-y/shiny/132.png","front_shiny_female":null}},"generation-vii":{"icons":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vii/icons/132.png","front_female":null},"ultra-sun-ultra-moon":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vii/ultra-sun-ultra-moon/132.png","front_female":null,"front_shiny":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-vii/ultra-sun-ultra-moon/shiny/132.png","front_shiny_female":null}},"generation-viii":{"icons":{"front_default":"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-viii/icons/132.png","front_female":null}}}},"stats":[{"base_stat":48,"effort":1,"stat":{"name":"hp","url":"https://pokeapi.co/api/v2/stat/1/"}},{"base_stat":48,"effort":0,"stat":{"name":"attack","url":"https://pokeapi.co/api/v2/stat/2/"}},{"base_stat":48,"effort":0,"stat":{"name":"defense","url":"https://pokeapi.co/api/v2/stat/3/"}},{"base_stat":48,"effort":0,"stat":{"name":"special-attack","url":"https://pokeapi.co/api/v2/stat/4/"}},{"base_stat":48,"effort":0,"stat":{"name":"special-defense","url":"https://pokeapi.co/api/v2/stat/5/"}},{"base_stat":48,"effort":0,"stat":{"name":"speed","url":"https://pokeapi.co/api/v2/stat/6/"}}],"types":[{"slot":1,"type":{"name":"normal","url":"https://pokeapi.co/api/v2/type/1/"}}],"weight":40}
--------------------------------------------------------------------------------
/clase-5/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.1'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | dependencies:
8 | cors:
9 | specifier: 2.8.5
10 | version: 2.8.5
11 | dotenv:
12 | specifier: 16.3.1
13 | version: 16.3.1
14 | express:
15 | specifier: 4.18.2
16 | version: 4.18.2
17 | mongodb:
18 | specifier: 5.7.0
19 | version: 5.7.0
20 | mysql2:
21 | specifier: ^3.6.0
22 | version: 3.6.0
23 | zod:
24 | specifier: 3.21.4
25 | version: 3.21.4
26 |
27 | packages:
28 |
29 | /@types/node@20.5.9:
30 | resolution: {integrity: sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==}
31 | dev: false
32 |
33 | /@types/webidl-conversions@7.0.0:
34 | resolution: {integrity: sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==}
35 | dev: false
36 |
37 | /@types/whatwg-url@8.2.2:
38 | resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==}
39 | dependencies:
40 | '@types/node': 20.5.9
41 | '@types/webidl-conversions': 7.0.0
42 | dev: false
43 |
44 | /accepts@1.3.8:
45 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
46 | engines: {node: '>= 0.6'}
47 | dependencies:
48 | mime-types: 2.1.35
49 | negotiator: 0.6.3
50 | dev: false
51 |
52 | /array-flatten@1.1.1:
53 | resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
54 | dev: false
55 |
56 | /body-parser@1.20.1:
57 | resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
58 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
59 | dependencies:
60 | bytes: 3.1.2
61 | content-type: 1.0.5
62 | debug: 2.6.9
63 | depd: 2.0.0
64 | destroy: 1.2.0
65 | http-errors: 2.0.0
66 | iconv-lite: 0.4.24
67 | on-finished: 2.4.1
68 | qs: 6.11.0
69 | raw-body: 2.5.1
70 | type-is: 1.6.18
71 | unpipe: 1.0.0
72 | transitivePeerDependencies:
73 | - supports-color
74 | dev: false
75 |
76 | /bson@5.4.0:
77 | resolution: {integrity: sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==}
78 | engines: {node: '>=14.20.1'}
79 | dev: false
80 |
81 | /bytes@3.1.2:
82 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
83 | engines: {node: '>= 0.8'}
84 | dev: false
85 |
86 | /call-bind@1.0.2:
87 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
88 | dependencies:
89 | function-bind: 1.1.1
90 | get-intrinsic: 1.2.1
91 | dev: false
92 |
93 | /content-disposition@0.5.4:
94 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
95 | engines: {node: '>= 0.6'}
96 | dependencies:
97 | safe-buffer: 5.2.1
98 | dev: false
99 |
100 | /content-type@1.0.5:
101 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
102 | engines: {node: '>= 0.6'}
103 | dev: false
104 |
105 | /cookie-signature@1.0.6:
106 | resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
107 | dev: false
108 |
109 | /cookie@0.5.0:
110 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
111 | engines: {node: '>= 0.6'}
112 | dev: false
113 |
114 | /cors@2.8.5:
115 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
116 | engines: {node: '>= 0.10'}
117 | dependencies:
118 | object-assign: 4.1.1
119 | vary: 1.1.2
120 | dev: false
121 |
122 | /debug@2.6.9:
123 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
124 | peerDependencies:
125 | supports-color: '*'
126 | peerDependenciesMeta:
127 | supports-color:
128 | optional: true
129 | dependencies:
130 | ms: 2.0.0
131 | dev: false
132 |
133 | /denque@2.1.0:
134 | resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
135 | engines: {node: '>=0.10'}
136 | dev: false
137 |
138 | /depd@2.0.0:
139 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
140 | engines: {node: '>= 0.8'}
141 | dev: false
142 |
143 | /destroy@1.2.0:
144 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
145 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
146 | dev: false
147 |
148 | /dotenv@16.3.1:
149 | resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
150 | engines: {node: '>=12'}
151 | dev: false
152 |
153 | /ee-first@1.1.1:
154 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
155 | dev: false
156 |
157 | /encodeurl@1.0.2:
158 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
159 | engines: {node: '>= 0.8'}
160 | dev: false
161 |
162 | /escape-html@1.0.3:
163 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
164 | dev: false
165 |
166 | /etag@1.8.1:
167 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
168 | engines: {node: '>= 0.6'}
169 | dev: false
170 |
171 | /express@4.18.2:
172 | resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
173 | engines: {node: '>= 0.10.0'}
174 | dependencies:
175 | accepts: 1.3.8
176 | array-flatten: 1.1.1
177 | body-parser: 1.20.1
178 | content-disposition: 0.5.4
179 | content-type: 1.0.5
180 | cookie: 0.5.0
181 | cookie-signature: 1.0.6
182 | debug: 2.6.9
183 | depd: 2.0.0
184 | encodeurl: 1.0.2
185 | escape-html: 1.0.3
186 | etag: 1.8.1
187 | finalhandler: 1.2.0
188 | fresh: 0.5.2
189 | http-errors: 2.0.0
190 | merge-descriptors: 1.0.1
191 | methods: 1.1.2
192 | on-finished: 2.4.1
193 | parseurl: 1.3.3
194 | path-to-regexp: 0.1.7
195 | proxy-addr: 2.0.7
196 | qs: 6.11.0
197 | range-parser: 1.2.1
198 | safe-buffer: 5.2.1
199 | send: 0.18.0
200 | serve-static: 1.15.0
201 | setprototypeof: 1.2.0
202 | statuses: 2.0.1
203 | type-is: 1.6.18
204 | utils-merge: 1.0.1
205 | vary: 1.1.2
206 | transitivePeerDependencies:
207 | - supports-color
208 | dev: false
209 |
210 | /finalhandler@1.2.0:
211 | resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
212 | engines: {node: '>= 0.8'}
213 | dependencies:
214 | debug: 2.6.9
215 | encodeurl: 1.0.2
216 | escape-html: 1.0.3
217 | on-finished: 2.4.1
218 | parseurl: 1.3.3
219 | statuses: 2.0.1
220 | unpipe: 1.0.0
221 | transitivePeerDependencies:
222 | - supports-color
223 | dev: false
224 |
225 | /forwarded@0.2.0:
226 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
227 | engines: {node: '>= 0.6'}
228 | dev: false
229 |
230 | /fresh@0.5.2:
231 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
232 | engines: {node: '>= 0.6'}
233 | dev: false
234 |
235 | /function-bind@1.1.1:
236 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
237 | dev: false
238 |
239 | /generate-function@2.3.1:
240 | resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
241 | dependencies:
242 | is-property: 1.0.2
243 | dev: false
244 |
245 | /get-intrinsic@1.2.1:
246 | resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
247 | dependencies:
248 | function-bind: 1.1.1
249 | has: 1.0.3
250 | has-proto: 1.0.1
251 | has-symbols: 1.0.3
252 | dev: false
253 |
254 | /has-proto@1.0.1:
255 | resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
256 | engines: {node: '>= 0.4'}
257 | dev: false
258 |
259 | /has-symbols@1.0.3:
260 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
261 | engines: {node: '>= 0.4'}
262 | dev: false
263 |
264 | /has@1.0.3:
265 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
266 | engines: {node: '>= 0.4.0'}
267 | dependencies:
268 | function-bind: 1.1.1
269 | dev: false
270 |
271 | /http-errors@2.0.0:
272 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
273 | engines: {node: '>= 0.8'}
274 | dependencies:
275 | depd: 2.0.0
276 | inherits: 2.0.4
277 | setprototypeof: 1.2.0
278 | statuses: 2.0.1
279 | toidentifier: 1.0.1
280 | dev: false
281 |
282 | /iconv-lite@0.4.24:
283 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
284 | engines: {node: '>=0.10.0'}
285 | dependencies:
286 | safer-buffer: 2.1.2
287 | dev: false
288 |
289 | /iconv-lite@0.6.3:
290 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
291 | engines: {node: '>=0.10.0'}
292 | dependencies:
293 | safer-buffer: 2.1.2
294 | dev: false
295 |
296 | /inherits@2.0.4:
297 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
298 | dev: false
299 |
300 | /ip@2.0.0:
301 | resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
302 | dev: false
303 |
304 | /ipaddr.js@1.9.1:
305 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
306 | engines: {node: '>= 0.10'}
307 | dev: false
308 |
309 | /is-property@1.0.2:
310 | resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
311 | dev: false
312 |
313 | /long@5.2.3:
314 | resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
315 | dev: false
316 |
317 | /lru-cache@7.18.3:
318 | resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
319 | engines: {node: '>=12'}
320 | dev: false
321 |
322 | /lru-cache@8.0.5:
323 | resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
324 | engines: {node: '>=16.14'}
325 | dev: false
326 |
327 | /media-typer@0.3.0:
328 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
329 | engines: {node: '>= 0.6'}
330 | dev: false
331 |
332 | /memory-pager@1.5.0:
333 | resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
334 | dev: false
335 | optional: true
336 |
337 | /merge-descriptors@1.0.1:
338 | resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
339 | dev: false
340 |
341 | /methods@1.1.2:
342 | resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
343 | engines: {node: '>= 0.6'}
344 | dev: false
345 |
346 | /mime-db@1.52.0:
347 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
348 | engines: {node: '>= 0.6'}
349 | dev: false
350 |
351 | /mime-types@2.1.35:
352 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
353 | engines: {node: '>= 0.6'}
354 | dependencies:
355 | mime-db: 1.52.0
356 | dev: false
357 |
358 | /mime@1.6.0:
359 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
360 | engines: {node: '>=4'}
361 | hasBin: true
362 | dev: false
363 |
364 | /mongodb-connection-string-url@2.6.0:
365 | resolution: {integrity: sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==}
366 | dependencies:
367 | '@types/whatwg-url': 8.2.2
368 | whatwg-url: 11.0.0
369 | dev: false
370 |
371 | /mongodb@5.7.0:
372 | resolution: {integrity: sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==}
373 | engines: {node: '>=14.20.1'}
374 | peerDependencies:
375 | '@aws-sdk/credential-providers': ^3.201.0
376 | '@mongodb-js/zstd': ^1.1.0
377 | kerberos: ^2.0.1
378 | mongodb-client-encryption: '>=2.3.0 <3'
379 | snappy: ^7.2.2
380 | peerDependenciesMeta:
381 | '@aws-sdk/credential-providers':
382 | optional: true
383 | '@mongodb-js/zstd':
384 | optional: true
385 | kerberos:
386 | optional: true
387 | mongodb-client-encryption:
388 | optional: true
389 | snappy:
390 | optional: true
391 | dependencies:
392 | bson: 5.4.0
393 | mongodb-connection-string-url: 2.6.0
394 | socks: 2.7.1
395 | optionalDependencies:
396 | saslprep: 1.0.3
397 | dev: false
398 |
399 | /ms@2.0.0:
400 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
401 | dev: false
402 |
403 | /ms@2.1.3:
404 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
405 | dev: false
406 |
407 | /mysql2@3.6.0:
408 | resolution: {integrity: sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==}
409 | engines: {node: '>= 8.0'}
410 | dependencies:
411 | denque: 2.1.0
412 | generate-function: 2.3.1
413 | iconv-lite: 0.6.3
414 | long: 5.2.3
415 | lru-cache: 8.0.5
416 | named-placeholders: 1.1.3
417 | seq-queue: 0.0.5
418 | sqlstring: 2.3.3
419 | dev: false
420 |
421 | /named-placeholders@1.1.3:
422 | resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}
423 | engines: {node: '>=12.0.0'}
424 | dependencies:
425 | lru-cache: 7.18.3
426 | dev: false
427 |
428 | /negotiator@0.6.3:
429 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
430 | engines: {node: '>= 0.6'}
431 | dev: false
432 |
433 | /object-assign@4.1.1:
434 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
435 | engines: {node: '>=0.10.0'}
436 | dev: false
437 |
438 | /object-inspect@1.12.3:
439 | resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
440 | dev: false
441 |
442 | /on-finished@2.4.1:
443 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
444 | engines: {node: '>= 0.8'}
445 | dependencies:
446 | ee-first: 1.1.1
447 | dev: false
448 |
449 | /parseurl@1.3.3:
450 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
451 | engines: {node: '>= 0.8'}
452 | dev: false
453 |
454 | /path-to-regexp@0.1.7:
455 | resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
456 | dev: false
457 |
458 | /proxy-addr@2.0.7:
459 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
460 | engines: {node: '>= 0.10'}
461 | dependencies:
462 | forwarded: 0.2.0
463 | ipaddr.js: 1.9.1
464 | dev: false
465 |
466 | /punycode@2.3.0:
467 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
468 | engines: {node: '>=6'}
469 | dev: false
470 |
471 | /qs@6.11.0:
472 | resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
473 | engines: {node: '>=0.6'}
474 | dependencies:
475 | side-channel: 1.0.4
476 | dev: false
477 |
478 | /range-parser@1.2.1:
479 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
480 | engines: {node: '>= 0.6'}
481 | dev: false
482 |
483 | /raw-body@2.5.1:
484 | resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
485 | engines: {node: '>= 0.8'}
486 | dependencies:
487 | bytes: 3.1.2
488 | http-errors: 2.0.0
489 | iconv-lite: 0.4.24
490 | unpipe: 1.0.0
491 | dev: false
492 |
493 | /safe-buffer@5.2.1:
494 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
495 | dev: false
496 |
497 | /safer-buffer@2.1.2:
498 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
499 | dev: false
500 |
501 | /saslprep@1.0.3:
502 | resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
503 | engines: {node: '>=6'}
504 | requiresBuild: true
505 | dependencies:
506 | sparse-bitfield: 3.0.3
507 | dev: false
508 | optional: true
509 |
510 | /send@0.18.0:
511 | resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
512 | engines: {node: '>= 0.8.0'}
513 | dependencies:
514 | debug: 2.6.9
515 | depd: 2.0.0
516 | destroy: 1.2.0
517 | encodeurl: 1.0.2
518 | escape-html: 1.0.3
519 | etag: 1.8.1
520 | fresh: 0.5.2
521 | http-errors: 2.0.0
522 | mime: 1.6.0
523 | ms: 2.1.3
524 | on-finished: 2.4.1
525 | range-parser: 1.2.1
526 | statuses: 2.0.1
527 | transitivePeerDependencies:
528 | - supports-color
529 | dev: false
530 |
531 | /seq-queue@0.0.5:
532 | resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
533 | dev: false
534 |
535 | /serve-static@1.15.0:
536 | resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
537 | engines: {node: '>= 0.8.0'}
538 | dependencies:
539 | encodeurl: 1.0.2
540 | escape-html: 1.0.3
541 | parseurl: 1.3.3
542 | send: 0.18.0
543 | transitivePeerDependencies:
544 | - supports-color
545 | dev: false
546 |
547 | /setprototypeof@1.2.0:
548 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
549 | dev: false
550 |
551 | /side-channel@1.0.4:
552 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
553 | dependencies:
554 | call-bind: 1.0.2
555 | get-intrinsic: 1.2.1
556 | object-inspect: 1.12.3
557 | dev: false
558 |
559 | /smart-buffer@4.2.0:
560 | resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
561 | engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
562 | dev: false
563 |
564 | /socks@2.7.1:
565 | resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==}
566 | engines: {node: '>= 10.13.0', npm: '>= 3.0.0'}
567 | dependencies:
568 | ip: 2.0.0
569 | smart-buffer: 4.2.0
570 | dev: false
571 |
572 | /sparse-bitfield@3.0.3:
573 | resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
574 | dependencies:
575 | memory-pager: 1.5.0
576 | dev: false
577 | optional: true
578 |
579 | /sqlstring@2.3.3:
580 | resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
581 | engines: {node: '>= 0.6'}
582 | dev: false
583 |
584 | /statuses@2.0.1:
585 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
586 | engines: {node: '>= 0.8'}
587 | dev: false
588 |
589 | /toidentifier@1.0.1:
590 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
591 | engines: {node: '>=0.6'}
592 | dev: false
593 |
594 | /tr46@3.0.0:
595 | resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
596 | engines: {node: '>=12'}
597 | dependencies:
598 | punycode: 2.3.0
599 | dev: false
600 |
601 | /type-is@1.6.18:
602 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
603 | engines: {node: '>= 0.6'}
604 | dependencies:
605 | media-typer: 0.3.0
606 | mime-types: 2.1.35
607 | dev: false
608 |
609 | /unpipe@1.0.0:
610 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
611 | engines: {node: '>= 0.8'}
612 | dev: false
613 |
614 | /utils-merge@1.0.1:
615 | resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
616 | engines: {node: '>= 0.4.0'}
617 | dev: false
618 |
619 | /vary@1.1.2:
620 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
621 | engines: {node: '>= 0.8'}
622 | dev: false
623 |
624 | /webidl-conversions@7.0.0:
625 | resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
626 | engines: {node: '>=12'}
627 | dev: false
628 |
629 | /whatwg-url@11.0.0:
630 | resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
631 | engines: {node: '>=12'}
632 | dependencies:
633 | tr46: 3.0.0
634 | webidl-conversions: 7.0.0
635 | dev: false
636 |
637 | /zod@3.21.4:
638 | resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
639 | dev: false
640 |
--------------------------------------------------------------------------------