Skip to content

Commit 44fe0ea

Browse files
committed
backend project (nodeapp) is added
1 parent 485846d commit 44fe0ea

27 files changed

+3407
-0
lines changed

nodeapp/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MONGODB_CONNSTR=mongodb://localhost/cursonode
2+
SESSION_SECRET="secret_string"

nodeapp/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# NodeApp
2+
3+
## Installation
4+
5+
Install dependencies with:
6+
7+
```sh
8+
npm install
9+
```
10+
11+
Copy environment variables example to .env:
12+
13+
```sh
14+
cp .env.example .env
15+
```
16+
17+
Review your new .env values to match your configuration.
18+
19+
On first deploy you can use the next command to initialize the database:
20+
21+
```sh
22+
npm run initDB
23+
```

nodeapp/app.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import path from 'node:path'
2+
import express from 'express'
3+
import createError from 'http-errors'
4+
import logger from 'morgan'
5+
import connectMongoose from './lib/connectMongoose.js'
6+
import * as homeController from './controllers/homeController.js'
7+
import * as loginController from './controllers/loginController.js'
8+
import * as agentsController from './controllers/agentsController.js'
9+
import * as sessionManager from './lib/sessionManager.js'
10+
import upload from './lib/uploadConfigure.js'
11+
import i18n from './lib/i18nConfigure.js'
12+
13+
await connectMongoose() // top level await thanks to ES modules
14+
console.log('Connected to MongoDB.')
15+
16+
const app = express()
17+
18+
app.set('views', 'views') // views folder
19+
app.set('view engine', 'html')
20+
app.engine('html', (await import('ejs')).__express)
21+
22+
app.locals.appName = 'NodeApp'
23+
24+
app.use(logger('dev'))
25+
app.use(express.urlencoded({ extended: false }))
26+
app.use(express.static(path.join(import.meta.dirname, 'public')))
27+
28+
/**
29+
* Application routes
30+
*/
31+
app.use(sessionManager.middleware)
32+
app.use(sessionManager.useSessionInViews)
33+
app.use(i18n.init)
34+
app.get('/', homeController.index)
35+
app.get('/login', loginController.index)
36+
app.post('/login', loginController.postLogin)
37+
app.get('/logout', loginController.logout)
38+
app.get('/agents/new', sessionManager.guard, agentsController.index)
39+
app.post('/agents/new', sessionManager.guard, upload.single('avatar') , agentsController.postNew)
40+
app.get('/agents/delete/:agentId', sessionManager.guard, agentsController.deleteAgent)
41+
42+
// Ejemplos
43+
app.get('/param_in_route/:num?', homeController.paranInRoute)
44+
app.get('/param_in_route_multiple/:product/size/:size([0-9]+)/color/:color', homeController.paranInRouteMultiple)
45+
app.get('/param_in_query', homeController.validateParamInQuery, homeController.paramInQuery)
46+
app.post('/post_with_body', homeController.postWithBody)
47+
48+
// catch 404 and send error
49+
app.use((req, res, next) => {
50+
next(createError(404))
51+
})
52+
53+
// error handler
54+
app.use((err, req, res, next) => {
55+
56+
// manage validation errors
57+
if (err.array) {
58+
err.message = 'Invalid request: ' + err.array()
59+
.map(e => `${e.location} ${e.type} "${e.path}" ${e.msg}`)
60+
.join(', ')
61+
err.status = 422
62+
}
63+
64+
res.status(err.status || 500)
65+
66+
// set locals, including error information in development
67+
res.locals.message = err.message
68+
res.locals.error = process.env.NODEAPP_ENV === 'development' ? err : {}
69+
70+
res.render('error')
71+
})
72+
73+
export default app
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Agent from '../models/Agent.js'
2+
3+
export function index(req, res, next) {
4+
res.render('new-agent')
5+
}
6+
7+
export async function postNew(req, res, next) {
8+
try {
9+
const { name, age } = req.body
10+
const userId = req.session.userId
11+
12+
// TODO validaciones
13+
14+
// creo una instancia de agente en memoria
15+
const agent = new Agent({
16+
name,
17+
age,
18+
owner: userId,
19+
avatar: req.file.filename
20+
})
21+
22+
// lo guardo en base de datos
23+
await agent.save()
24+
25+
res.redirect('/')
26+
27+
} catch (error) {
28+
next(error)
29+
}
30+
}
31+
32+
export async function deleteAgent(req, res, next) {
33+
try {
34+
const userId = req.session.userId
35+
const agentId = req.params.agentId
36+
37+
await Agent.deleteOne({ _id: agentId, owner: userId })
38+
39+
res.redirect('/')
40+
41+
} catch (error) {
42+
next(error)
43+
}
44+
}

nodeapp/controllers/homeController.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { query, validationResult } from 'express-validator'
2+
import Agent from '../models/Agent.js'
3+
4+
export async function index(req, res, next) {
5+
try {
6+
const userId = req.session.userId
7+
8+
// Filters
9+
// http://localhost:3000/?name=Smith
10+
const filterName = req.query.name
11+
// http://localhost:3000/?age=33
12+
const filterAge = req.query.age
13+
14+
// Pagination
15+
// http://localhost:3000/?limit=2&skip=2
16+
const limit = req.query.limit
17+
const skip = req.query.skip
18+
19+
// Sorting
20+
// http://localhost:3000/?sort=-name
21+
// http://localhost:3000/?sort=-age%20-name
22+
const sort = req.query.sort
23+
24+
const filter = {
25+
owner: userId,
26+
}
27+
28+
if (filterName) {
29+
filter.name = filterName
30+
}
31+
32+
if (filterAge) {
33+
filter.age = filterAge
34+
}
35+
36+
res.locals.agents = await Agent.list(filter, limit, skip, sort)
37+
38+
const now = new Date()
39+
res.locals.esPar = (now.getSeconds() % 2) === 0
40+
res.locals.segundoActual = now.getSeconds()
41+
42+
res.locals.codigo = '<script>alert("inyectado!!!")</script>'
43+
44+
res.render('home')
45+
46+
} catch (error) {
47+
next(error)
48+
}
49+
}
50+
51+
// /param_in_route/45
52+
export function paranInRoute(req, res, next) {
53+
const num = req.params.num
54+
55+
res.send('me has pasado ' + num)
56+
}
57+
58+
// /param_in_route_multiple/pantalon/size/M/color/blue
59+
export function paranInRouteMultiple(req, res, next) {
60+
const product = req.params.product
61+
const size = req.params.size
62+
const color = req.params.color
63+
64+
res.send(`quieres un ${product} de talla ${size} y color ${color}`)
65+
}
66+
67+
// /param_in_query?color=rojo
68+
export const validateParamInQuery = [
69+
query('color')
70+
// .notEmpty()
71+
.custom(value => {
72+
return ['red', 'blue'].includes(value)
73+
})
74+
.withMessage('must be red or blue'),
75+
query('talla')
76+
.isNumeric()
77+
.withMessage('must be numeric')
78+
]
79+
export function paramInQuery(req, res, next) {
80+
validationResult(req).throw()
81+
82+
const color = req.query.color
83+
84+
85+
console.log(req.query)
86+
87+
res.send(`el color es ${color}`)
88+
}
89+
90+
// POST /post_with_body
91+
export function postWithBody(req, res, next) {
92+
const { age, color } = req.body
93+
94+
res.send('ok')
95+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import User from '../models/User.js'
2+
3+
export function index(req, res, next) {
4+
res.locals.error = ''
5+
res.locals.email = ''
6+
res.render('login')
7+
}
8+
9+
export async function postLogin(req, res, next) {
10+
try {
11+
const { email, password } = req.body
12+
const redir = req.query.redir
13+
14+
// buscar el usuario en la base de datos
15+
const user = await User.findOne({ email: email })
16+
17+
// si no lo encuentro, o la contraseña no coincide --> error
18+
if (!user || !(await user.comparePassword(password))) {
19+
res.locals.error = 'Invalid credentials'
20+
res.locals.email = email
21+
res.render('login')
22+
return
23+
}
24+
25+
// si el usuario existe y la contraseña es buena --> redirect a la home
26+
req.session.userId = user.id
27+
28+
res.redirect(redir ? redir : '/')
29+
30+
} catch (error) {
31+
next(error)
32+
}
33+
}
34+
35+
export function logout(req, res, next) {
36+
req.session.regenerate(err => {
37+
if (err) {
38+
next(err)
39+
return
40+
}
41+
res.redirect('/')
42+
})
43+
}

nodeapp/initDB.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'dotenv/config'
2+
3+
import readline from 'node:readline/promises'
4+
import connectMongoose from './lib/connectMongoose.js'
5+
import Agent from './models/Agent.js'
6+
import User from './models/User.js'
7+
8+
const connection = await connectMongoose()
9+
console.log('Connected to MongoDB:', connection.name)
10+
11+
const answer = await ask('Are you sure you want to delete database collections? (n)')
12+
if (answer.toLowerCase() !== 'y') {
13+
console.log('Operation aborted.')
14+
process.exit()
15+
}
16+
17+
await initUsers()
18+
await initAgents()
19+
20+
await connection.close()
21+
22+
async function initAgents() {
23+
// delete all agents
24+
const result = await Agent.deleteMany()
25+
console.log(`Deleted ${result.deletedCount} agents.`)
26+
27+
const [admin, user] = await Promise.all([
28+
User.findOne({ email: 'admin@example.com'}),
29+
User.findOne({ email: 'user@example.com'}),
30+
])
31+
32+
// create agents
33+
const insertResult = await Agent.insertMany([
34+
{ name: 'Smith', age: 45, owner: admin._id },
35+
{ name: 'Brown', age: 33, owner: admin._id },
36+
{ name: 'Jones', age: 24, owner: user._id},
37+
])
38+
console.log(`Inserted ${insertResult.length} agents.`)
39+
}
40+
41+
async function initUsers() {
42+
// delete all users
43+
const result = await User.deleteMany()
44+
console.log(`Deleted ${result.deletedCount} users.`)
45+
46+
// create users
47+
const insertResult = await User.insertMany([
48+
{ email: 'admin@example.com', password: await User.hashPassword('1234') },
49+
{ email: 'user@example.com', password: await User.hashPassword('1234') },
50+
])
51+
console.log(`Inserted ${insertResult.length} users.`)
52+
}
53+
54+
async function ask(question) {
55+
const rl = readline.createInterface({
56+
input: process.stdin,
57+
output: process.stdout
58+
})
59+
const result = await rl.question(question)
60+
rl.close()
61+
return result
62+
}

nodeapp/lib/connectMongoose.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import mongoose from 'mongoose'
2+
3+
export default function connectMongoose() {
4+
return mongoose.connect(process.env.MONGODB_CONNSTR)
5+
.then(mongoose => mongoose.connection)
6+
}
7+

nodeapp/lib/i18nConfigure.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { I18n } from 'i18n'
2+
import path from 'node:path'
3+
import { __dirname } from './utils.js'
4+
5+
const i18n = new I18n({
6+
locales: ['en', 'es'],
7+
directory: path.join(__dirname, '..', 'locales'),
8+
defaultLocale: 'en',
9+
autoReload: true, // watch for changes in JSON files to reload locale on updates - defaults to false
10+
syncFiles: true, // sync locale information across all files - defaults to false
11+
})
12+
13+
export default i18n

0 commit comments

Comments
 (0)