Version sin Vite.
This commit is contained in:
0
backend/.dockerignore
Normal file
0
backend/.dockerignore
Normal file
19
backend/Dockerfile
Normal file
19
backend/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
# Directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copiamos dependencias
|
||||
COPY package*.json ./
|
||||
|
||||
# Instalamos
|
||||
RUN npm install
|
||||
|
||||
# Copiamos el resto del código
|
||||
COPY . .
|
||||
|
||||
# Exponemos el puerto 9002 (que configuramos en index.js)
|
||||
EXPOSE 9002
|
||||
|
||||
# Arrancamos
|
||||
CMD ["npm", "run", "dev"]
|
||||
13
backend/middleware/auth.js
Normal file
13
backend/middleware/auth.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
if (!authHeader) return res.status(403).json({ error: 'No se permite el acceso sin token' });
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
// Aquí la lógica de validación de tu token
|
||||
if (token) {
|
||||
next(); // Si el token es válido, dejamos pasar
|
||||
} else {
|
||||
res.status(401).json({ error: 'Token inválido o expirado' });
|
||||
}
|
||||
};
|
||||
1532
backend/package-lock.json
generated
Normal file
1532
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
backend/package.json
Normal file
21
backend/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "blog-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.1",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"pg": "^8.11.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.3"
|
||||
}
|
||||
}
|
||||
19
backend/src/config/db.js
Normal file
19
backend/src/config/db.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { Pool } = require('pg');
|
||||
|
||||
/**
|
||||
* Configuración de la conexión a PostgreSQL
|
||||
* Host 'db' para conectar dentro de la red de Docker
|
||||
*/
|
||||
const pool = new Pool({
|
||||
user: 'josemi',
|
||||
host: 'db',
|
||||
database: 'blog_db',
|
||||
password: 'josemivi',
|
||||
port: 5432,
|
||||
});
|
||||
|
||||
pool.on('connect', () => {
|
||||
console.log('✅ Conexión establecida con PostgreSQL');
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
32
backend/src/controllers/authController.js
Normal file
32
backend/src/controllers/authController.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const pool = require('../config/db');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const login = async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
try {
|
||||
const result = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
|
||||
if (result.rows.length === 0) return res.status(400).json({ error: 'Usuario no encontrado' });
|
||||
|
||||
const user = result.rows[0];
|
||||
// NOTA: Si el hash del SQL falla, puedes usar /register para crear uno nuevo
|
||||
// Aquí comparamos contraseña plana vs hash
|
||||
const validPass = await bcrypt.compare(password, user.password);
|
||||
if (!validPass) return res.status(400).json({ error: 'Contraseña incorrecta' });
|
||||
|
||||
const token = jwt.sign({ id: user.id, username: user.username }, process.env.JWT_SECRET || 'secreto', { expiresIn: '24h' });
|
||||
res.json({ token, username: user.username });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
};
|
||||
|
||||
const register = async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
try {
|
||||
await pool.query('INSERT INTO users (username, password) VALUES ($1, $2)', [username, hash]);
|
||||
res.json({ message: 'Usuario creado exitosamente' });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
};
|
||||
|
||||
module.exports = { login, register };
|
||||
71
backend/src/controllers/postController.js
Normal file
71
backend/src/controllers/postController.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const pool = require('../config/db');
|
||||
|
||||
// Obtener todos los posts
|
||||
exports.getPosts = async (req, res) => {
|
||||
try {
|
||||
const result = await pool.query('SELECT * FROM posts ORDER BY created_at DESC');
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error obteniendo posts' });
|
||||
}
|
||||
};
|
||||
|
||||
// Login (Asegúrate de que esta función esté aquí, que es la que suele fallar)
|
||||
exports.login = async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
// Por ahora validación simple, luego puedes meter ciberseguridad real
|
||||
if (username === 'josemi' && password === 'josemivi') {
|
||||
res.json({ token: 'token-seguro-josemi-' + Date.now() });
|
||||
} else {
|
||||
res.status(401).json({ error: 'Credenciales incorrectas' });
|
||||
}
|
||||
};
|
||||
|
||||
// Contador de visitas
|
||||
exports.addView = async (req, res) => {
|
||||
try {
|
||||
await pool.query('UPDATE posts SET views = views + 1 WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error view' });
|
||||
}
|
||||
};
|
||||
|
||||
// Crear post
|
||||
exports.createPost = async (req, res) => {
|
||||
const { title, tags, content } = req.body;
|
||||
const slug = title.toLowerCase().replace(/ /g, '-');
|
||||
try {
|
||||
const result = await pool.query(
|
||||
'INSERT INTO posts (title, slug, tags, content) VALUES ($1, $2, $3, $4) RETURNING *',
|
||||
[title, slug, tags, content]
|
||||
);
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error creando post' });
|
||||
}
|
||||
};
|
||||
|
||||
// Eliminar post
|
||||
exports.deletePost = async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM posts WHERE id = $1', [req.params.id]);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error eliminando' });
|
||||
}
|
||||
};
|
||||
|
||||
// Estadísticas
|
||||
exports.getStats = async (req, res) => {
|
||||
try {
|
||||
const total = await pool.query('SELECT SUM(views) as total FROM posts');
|
||||
const top = await pool.query('SELECT title, views FROM posts ORDER BY views DESC LIMIT 5');
|
||||
res.json({
|
||||
total: total.rows[0].total || 0,
|
||||
top: top.rows
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Error stats' });
|
||||
}
|
||||
};
|
||||
48
backend/src/index.js
Normal file
48
backend/src/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const postRoutes = require('./routes/postRoutes');
|
||||
const pool = require('./config/db');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// --- INICIALIZACIÓN DE BASE DE DATOS ---
|
||||
(async () => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
// 1. Crear tablas si no existen
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, username TEXT UNIQUE, password TEXT);
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
content TEXT,
|
||||
tags TEXT,
|
||||
url TEXT,
|
||||
image_url TEXT,
|
||||
description TEXT,
|
||||
type TEXT CHECK(type IN ('POST', 'LINK')) DEFAULT 'POST',
|
||||
views INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. Migración: Asegurar que la columna 'views' existe (por si la tabla ya estaba creada)
|
||||
await client.query(`ALTER TABLE posts ADD COLUMN IF NOT EXISTS views INTEGER DEFAULT 0;`);
|
||||
|
||||
console.log('✅ Base de datos PostgreSQL sincronizada');
|
||||
} catch (err) {
|
||||
console.error('❌ Error DB:', err);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
})();
|
||||
|
||||
app.use('/posts', postRoutes);
|
||||
|
||||
const PORT = 9002;
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`🚀 Servidor backend corriendo en puerto ${PORT}`);
|
||||
});
|
||||
16
backend/src/middleware/auth.js
Normal file
16
backend/src/middleware/auth.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const verifyToken = (req, res, next) => {
|
||||
const token = req.headers['authorization'];
|
||||
if (!token) return res.status(403).json({ error: 'Token requerido' });
|
||||
|
||||
try {
|
||||
const cleanToken = token.replace('Bearer ', '');
|
||||
const decoded = jwt.verify(cleanToken, process.env.JWT_SECRET || 'secreto');
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: 'Token inválido' });
|
||||
}
|
||||
};
|
||||
module.exports = verifyToken;
|
||||
22
backend/src/routes/postRoutes.js
Normal file
22
backend/src/routes/postRoutes.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const postController = require('../controllers/postController');
|
||||
|
||||
// Middleware de verificación simple
|
||||
const verifyToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
if (!authHeader) return res.status(403).json({ error: 'Acceso denegado' });
|
||||
next();
|
||||
};
|
||||
|
||||
// RUTAS PÚBLICAS
|
||||
router.get('/', postController.getPosts);
|
||||
router.post('/login', postController.login); // <--- Aquí estaba el error si no existía en el controller
|
||||
router.post('/view/:id', postController.addView);
|
||||
|
||||
// RUTAS PROTEGIDAS
|
||||
router.get('/stats', verifyToken, postController.getStats);
|
||||
router.post('/', verifyToken, postController.createPost);
|
||||
router.delete('/:id', verifyToken, postController.deletePost);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user