Skip to content

Commit 4a6dd30

Browse files
committed
API que permite subir ficheros y los sirve de forma estática
1 parent 3b5b936 commit 4a6dd30

File tree

9 files changed

+4686
-0
lines changed

9 files changed

+4686
-0
lines changed

files-api/package-lock.json

+874
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

files-api/package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "files-api",
3+
"version": "0.1",
4+
"description": "A sample project to show how to handle files",
5+
"scripts": {
6+
"start": "node src/app.js"
7+
},
8+
"author": "Santiago Faci",
9+
"license": "GPL-2.0-only",
10+
"dependencies": {
11+
"express": "^4.21.1",
12+
"multer": "^1.4.5-lts.1",
13+
"cors": "2.8.5"
14+
}
15+
}

files-api/sample_frontend/package-lock.json

+3,617
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "movies-frontend",
3+
"version": "0.1",
4+
"source": "src/registro.html",
5+
"type": "module",
6+
"scripts": {
7+
"start": "parcel --no-cache",
8+
"build": "parcel build"
9+
},
10+
"devDependencies": {
11+
"buffer": "^6.0.3",
12+
"parcel": "latest",
13+
"process": "^0.11.10"
14+
},
15+
"dependencies": {
16+
"axios": "^1.7.7",
17+
"toastify-js": "1.12.0"
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Toastify from 'toastify-js';
2+
import 'toastify-js/src/toastify.css';
3+
4+
const notifyError = function(message) {
5+
Toastify({
6+
text: message,
7+
duration: 3000,
8+
gravity: 'top',
9+
position: 'center',
10+
style: {
11+
background: "red",
12+
},
13+
}).showToast();
14+
};
15+
16+
const notifyOk = function(message) {
17+
Toastify({
18+
text: message,
19+
duration: 3000,
20+
gravity: 'top',
21+
position: 'center',
22+
style: {
23+
background: "green",
24+
},
25+
}).showToast();
26+
};
27+
28+
module.exports = {
29+
notifyError,
30+
notifyOk
31+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const icons = new Map();
2+
icons.set('edit',
3+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">' +
4+
'<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>' +
5+
'</svg>'
6+
);
7+
icons.set('delete',
8+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">' +
9+
'<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>' +
10+
'<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>' +
11+
'</svg>'
12+
);
13+
// TODO Añadir más iconos
14+
15+
const el = function (elementId) {
16+
return document.getElementById(elementId);
17+
};
18+
19+
const icon = function(iconName) {
20+
return icons.get(iconName);
21+
};
22+
23+
const td = function(text) {
24+
return '<td>' + text + '</td>';
25+
}
26+
27+
module.exports = {
28+
el,
29+
icon,
30+
td
31+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
5+
<script type="module" src="registro.js"></script>
6+
</head>
7+
<body>
8+
<br>
9+
<div class="container">
10+
<h1>Formulario con ficheros</h1>
11+
<form id="imageForm">
12+
<div class="mb-3">
13+
<label for="title" class="form-label">Titulo</label>
14+
<input class="form-control" type="text" id="title"/>
15+
</div>
16+
<div class="mb-3">
17+
<label for="image" class="form-label">Imagen</label>
18+
<input class="form-control" type="file" id="image"/>
19+
</div>
20+
<button type="submit" class="btn btn-primary" onClick="registerImage()">Registrar</button>
21+
</form>
22+
</div>
23+
</body>
24+
</html>
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import axios from 'axios';
2+
import { notifyError, notifyOk } from './dialogUtil.js';
3+
import { el } from './documentUtil.js';
4+
5+
window.registerImage = function() {
6+
const title = el('title').value;
7+
if (title === '') {
8+
notifyError('El titulo es un campo obligatorio');
9+
return;
10+
}
11+
const imageFile = el('image').files[0];
12+
13+
// Prepara los datos del formulario para ser enviados al backend
14+
const formData = new FormData();
15+
formData.append('title', title);
16+
formData.append('image', imageFile);
17+
axios.post('http://localhost:8080/images', formData, {
18+
headers: {
19+
'Content-Type': 'multipart/form-data'
20+
}
21+
}).then((response) => {
22+
notifyOk('Los datos se han registrado correctamente');
23+
}).catch((error) => {
24+
notifyError('Se ha producido un error al enviar los datos');
25+
console.log(error);
26+
});
27+
28+
el('title').value = '';
29+
};

files-api/src/app.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const express = require('express');
2+
const multer = require('multer');
3+
const fs = require('fs');
4+
const cors = require('cors');
5+
6+
const IMAGES_PATH = './images/';
7+
const app = express();
8+
app.use(express.json());
9+
app.use(cors());
10+
// La carpeta de las imágenes se sirve estáticamente (http://localhost:8080/imagen_de_ejemplo.png)
11+
app.use(express.static(IMAGES_PATH))
12+
13+
const multerStorage = multer.diskStorage({
14+
destination: IMAGES_PATH,
15+
filename: (req, file, cb) => {
16+
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1000);
17+
const extension = file.mimetype.slice(file.mimetype.indexOf('/') + 1);
18+
cb(null, file.fieldname + '-' + uniqueSuffix + '.' + extension);
19+
}
20+
});
21+
const upload = multer({storage: multerStorage});
22+
23+
app.post('/images', upload.single('image'), (req, res) => {
24+
if (!req.file) {
25+
return res.status(400).json({
26+
message: 'No image provided'
27+
});
28+
}
29+
30+
if (!fs.existsSync(IMAGES_PATH)) {
31+
fs.mkdirSync(IMAGES_PATH);
32+
}
33+
34+
// TODO Guardar los datos y el nombre de la foto en la base de datos
35+
console.log(req.body.title);
36+
console.log(req.file.filename);
37+
38+
return res.json({
39+
message: 'Image uploaded successfully',
40+
filename: req.file.fieldname
41+
});
42+
});
43+
44+
app.listen(8080, () => {
45+
console.log('Iniciando el backend en el puerto 8080');
46+
});

0 commit comments

Comments
 (0)