Parcourir la source

Backend improvements.

Iñigo Valentin il y a 1 an
Parent
commit
3f67ffeb55

+ 1 - 1
backend/inigovalentin/.gitignore

@@ -24,7 +24,7 @@ logs
 .node_repl_history
 .node_repl_history
 
 
 # Environment variables
 # Environment variables
-.env*
+*.env
 
 
 # SQL files
 # SQL files
 sql/
 sql/

+ 111 - 107
backend/inigovalentin/app/controllers/lang.controller.js

@@ -1,153 +1,157 @@
+/**
+ * @file Provides the model and operation for languages.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
+const logger = require('pino')()
 const db = require("../models");
 const db = require("../models");
 const Lang = db.langs;
 const Lang = db.langs;
 const Op = db.Sequelize.Op;
 const Op = db.Sequelize.Op;
+const AuthService = require("../services/auth.service.js");
+const authService = new AuthService(db);
 
 
-// Create and Save a new Lang
+/**
+ * Create and save a new language.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.create = (req, res) => {
 exports.create = (req, res) => {
+    // Check authentication
+    if (authService.validateToken(req, res).success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
     // Validate request
     // Validate request
-    if (!req.body.title) {
-        res.status(400).send({
-            message: "Content can not be empty!"
-        });
+    let missing = [];
+    if (!req.body.code) missing.push("code");
+    if (!req.body.name) missing.push("name");
+    if (missing.length > 0){
+        let message = "Missing required fields: [";
+        for (let i = 0; i < missing.length; i ++){
+            message += missing[i];
+            if (i < missing.length - 1) message += ", ";
+        }
+        message += "].";
+        res.status(400).send(message);
         return;
         return;
     }
     }
-    
+    const code = req.body.code;
+    const name = req.body.name;
+    const priority = !isNaN(req.body.priority) ? Number(req.body.priority) : 99;
+    const active = ((req.body.active + "").toLowerCase() === 'true') ? true : false;
+    // TODO: Check name unique.
+    // TODO resolve conflicting priorities.
     // Create a Lang
     // Create a Lang
-    const Lang = {
-        title: req.body.title,
-        description: req.body.description,
-        published: req.body.published ? req.body.published : false
-    };
-    
+    const Lang = {code: code, name: name, priority: priority, active: active};
     // Save Lang in the database
     // Save Lang in the database
     Lang.create(Lang)
     Lang.create(Lang)
-    .then(data => {
-        res.send(data);
-    })
+    .then(data => {res.send(data);})
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message:
-            err.message || "Some error occurred while creating the Lang."
-        });
+        logger.error("Error creating language: " + err);
+        res.status(500).send("Error creating language.");
     });
     });
 };
 };
 
 
-// Retrieve all Langs from the database.
+/**
+ * Retrieve all languages from the database.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.findAll = (req, res) => {
 exports.findAll = (req, res) => {
-    //const title = req.query.title;
-    //var condition = title ? { title: { [Op.like]: `%${title}%` } } : null;
-    
-    //Lang.findAll({ where: condition })
     Lang.findAll()
     Lang.findAll()
-    .then(data => {
-        res.send(data);
-    })
+    .then(data => {res.send(data);})
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message:
-            err.message || "Some error occurred while retrieving Langs."
-        });
+        logger.error("Error retrieving language: " + err);
+        res.status(500).send("Error retrieving languages.");
     });
     });
 };
 };
 
 
-// Find a single Lang with an id
+/**
+ * Find a single language by it's code.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.findOne = (req, res) => {
 exports.findOne = (req, res) => {
-    const id = req.params.id;
-    
-    Lang.findByPk(id)
+    const code = req.params.code;
+    Lang.findByPk(code)
     .then(data => {
     .then(data => {
-        if (data) {
-            res.send(data);
-        } else {
-            res.status(404).send({
-                message: `Cannot find Lang with id=${id}.`
-            });
-        }
+        if (data) res.send(data);
+        else res.status(404).send(`No language with code ${code}.`});
     })
     })
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message: "Error retrieving Lang with id=" + id
-        });
+        logger.error("Error retrieving language with code " + code + ": " + err);
+        res.status(500).send("Error retrieving language with code " + code + ".");
     });
     });
 };
 };
 
 
-// Update a Lang by the id in the request
+/**
+ * Update a single language by it's code.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.update = (req, res) => {
 exports.update = (req, res) => {
-    const id = req.params.id;
-    
-    Lang.update(req.body, {
-        where: { id: id }
-    })
+    // Check authentication
+    if (authService.validateToken(req, res).success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
+    const code = req.params.code;
+    Lang.update(req.body, {where: {code: code}})
     .then(num => {
     .then(num => {
-        if (num == 1) {
-            res.send({
-                message: "Lang was updated successfully."
-            });
-        } else {
-            res.send({
-                message: `Cannot update Lang with id=${id}. Maybe Lang was not found or req.body is empty!`
-            });
-        }
+        if (num == 1) res.send("Language updated successfully.");
+        else res.send(`Cannot update language with code ${id}.`);
     })
     })
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message: "Error updating Lang with id=" + id
-        });
+        logger.error("Error updating language with code " + code + ": " + err);
+        res.status(500).send("Error updating language with code " + code + ".");
     });
     });
 };
 };
 
 
-// Delete a Lang with the specified id in the request
+/**
+ * Delete a single language by it's code.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.delete = (req, res) => {
 exports.delete = (req, res) => {
-    const id = req.params.id;
-    
-    Lang.destroy({
-        where: { id: id }
-    })
+    // Check authentication
+    if (authService.validateToken(req, res).success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
+    const code = req.params.code;
+    Lang.destroy({where: {id: id}})
     .then(num => {
     .then(num => {
-        if (num == 1) {
-            res.send({
-                message: "Lang was deleted successfully!"
-            });
-        } else {
-            res.send({
-                message: `Cannot delete Lang with id=${id}. Maybe Lang was not found!`
-            });
-        }
+        if (num == 1) res.send("Language deleted successfully.");
+        else res.send(`Cannot delete language with code ${id}.`);
     })
     })
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message: "Could not delete Lang with id=" + id
-        });
+        logger.error("Error deleting language with code " + code + ": " + err);
+        res.status(500).send("Error deleting language with code " + code + ".");
     });
     });
 };
 };
 
 
-// Delete all Langs from the database.
+/**
+ * Delete all languages.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
 exports.deleteAll = (req, res) => {
 exports.deleteAll = (req, res) => {
-    Lang.destroy({
-        where: {},
-        truncate: false
-    })
-    .then(nums => {
-        res.send({ message: `${nums} Langs were deleted successfully!` });
-    })
-    .catch(err => {
-        res.status(500).send({
-            message:
-            err.message || "Some error occurred while removing all Langs."
-        });
-    });
-};
-
-// Find all published Langs
-exports.findAllPublished = (req, res) => {
-    Lang.findAll({ where: { published: true } })
-    .then(data => {
-        res.send(data);
-    })
+    // Check authentication
+    if (authService.validateToken(req, res).success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
+    Lang.destroy({where: {}, truncate: false})
+    .then(nums => {res.send({ message: `${nums} languages deleted successfully.`});})
     .catch(err => {
     .catch(err => {
-        res.status(500).send({
-            message:
-            err.message || "Some error occurred while retrieving Langs."
-        });
+        logger.error("Error deleting languages: " + err);
+        res.status(500).send("Error deleting languages.");
     });
     });
 };
 };

+ 47 - 16
backend/inigovalentin/app/controllers/license.controller.js

@@ -4,37 +4,62 @@
  * @since 4.0.0
  * @since 4.0.0
  */
  */
 
 
+const logger = require('pino')()
 const db = require("../models");
 const db = require("../models");
 const License = db.licenses;
 const License = db.licenses;
 const Op = db.Sequelize.Op;
 const Op = db.Sequelize.Op;
-
 const LocaleService = require('../services/locale.service.js');
 const LocaleService = require('../services/locale.service.js');
 const localeService = new LocaleService(db);
 const localeService = new LocaleService(db);
 
 
 /**
 /**
- * Create and Save a new license.
+ * Create and save a new license.
  * 
  * 
  * @param req The received request by the server.
  * @param req The received request by the server.
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.create = (req, res) => {
 exports.create = (req, res) => {
+    // Check authentication
+    if (authService.validateToken(req, res).success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
     // Validate request
     // Validate request
-    if (!req.body.title) {
-        res.status(400).send({message: "Content can not be empty!"});
+    let missing = [];
+    if (!req.body.name) missing.push("name");
+    if (!req.body.summary) missing.push("summary");
+    if (!req.body.legal) missing.push("legal");
+    if (missing.length > 0){
+        let message = "Missing required fields: [";
+        for (let i = 0; i < missing.length; i ++){
+            message += missing[i];
+            if (i < missing.length - 1) message += ", ";
+        }
+        message += "].";
+        res.status(400).send(message);
+        return;
+    }
+    const name = req.body.name;
+    const nameDb = name.toUpperCase().replace(/[^A-Z0-9]+/g, "");
+    const summary = localeService.generateLocalizedObject(req.body.summary, "LICENSE_" + nameDb + "_SUMMARY", "DB_PROJECT");
+    if (summary.valid === false){
+        res.status(400).send("Invalid summary values");
+        return;
+    }
+    const legal = localeService.generateLocalizedObject(req.body.legal, "LICENSE_" + nameDb + "_LEGAL", "DB_PROJECT");
+    if (legal.valid === false){
+        res.status(400).send("Invalid legal values");
         return;
         return;
     }
     }
-    
+    localeService.saveLocalizedObject(summary);
+    localeService.saveLocalizedObject(legal);
     // Create a License
     // Create a License
-    const License = {
-        title: req.body.title,
-        description: req.body.description,
-        published: req.body.published ? req.body.published : false
-    };
-    
-    // Save License in the database
+    const License = {name: name, summary: summary, legal: legal, icon: icon};
     License.create(License)
     License.create(License)
     .then(data => {res.send(data);})
     .then(data => {res.send(data);})
-    .catch(err => {res.status(500).send({message: err.message || "Some error occurred while creating the License."});});
+    .catch(err => {
+        logger.error("Error creating license: " + err);
+        res.status(500).send({"Error creating license."});
+    });
 };
 };
 
 
 /**
 /**
@@ -49,7 +74,10 @@ exports.findAll = (req, res) => {
         data = await localeService.localizeLicenses(data, req);
         data = await localeService.localizeLicenses(data, req);
         res.send(data);
         res.send(data);
     })
     })
-    .catch(err => {res.status(500).send({message: err.message || "Some error occurred while retrieving Licenses."});});
+    .catch(err => {
+        logger.error("Error retrieving projects: " + err);
+        res.status(500).send("Error retrieving projects.");
+    });
 };
 };
 
 
 /**
 /**
@@ -66,9 +94,12 @@ exports.findOne = (req, res) => {
             data = await localeService.localizeLicense(data, req);
             data = await localeService.localizeLicense(data, req);
             res.send(data);
             res.send(data);
         }
         }
-        else res.status(404).send({message: `Cannot find License with id=${id}.`});}
+        else res.status(404).send(`No licenses with id ${id}.`});
     })
     })
-    .catch(err => {res.status(500).send({message: "Error retrieving License with id=" + id});});
+    .catch(err => {
+        logger.error("Error retrieving license with id " + id + ": " + err);
+        res.status(500).send("Error retrieving license with id " + id + ".");
+    });
 };
 };
 
 
 /**
 /**

+ 127 - 35
backend/inigovalentin/app/controllers/project.controller.js

@@ -4,37 +4,92 @@
  * @since 4.0.0
  * @since 4.0.0
  */
  */
 
 
+const logger = require('pino')()
 const db = require("../models");
 const db = require("../models");
 const Project = db.projects;
 const Project = db.projects;
 const Op = db.Sequelize.Op;
 const Op = db.Sequelize.Op;
-
 const LocaleService = require('../services/locale.service.js');
 const LocaleService = require('../services/locale.service.js');
 const localeService = new LocaleService(db);
 const localeService = new LocaleService(db);
+const AuthService = require("../services/auth.service.js");
+const authService = new AuthService(db);
 
 
 /**
 /**
- * Create and Save a new project.
+ * Create and save a new project.
  * 
  * 
  * @param req The received request by the server.
  * @param req The received request by the server.
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.create = (req, res) => {
 exports.create = (req, res) => {
-    // Validate request
-    if (!req.body.title) {
-        res.status(400).send({ message: "Content can not be empty!" });
+    // Check authentication
+    const validation = authService.validateToken(req, res);
+    if (validation.success === false){
+        res.status(validation.code).send(validation.message);
         return;
         return;
     }
     }
+    const user = validation.user;
     
     
+    // Validate request
+    let missing = [];
+    if (!req.body.permalink) missing.push("permalink");
+    if (!req.body.title) missing.push("title");
+    if (!req.body.header) missing.push("header");
+    if (!req.body.type) missing.push("type");
+    if (!req.body.license) missing.push("license");
+    if (missing.length > 0){
+        let message = "Missing required fields: [";
+        for (let i = 0; i < missing.length; i ++){
+            message += missing[i];
+            if (i < missing.length - 1) message += ", ";
+        }
+        message += "].";
+        res.status(400).send(message);
+        return;
+    }
+    let permalink = req.body.permalink;
+    // TODO: Validate and format permalink.
+    let idx = 0; // TODO: Obtain it.
+    const title = localeService.generateLocalizedObject(req.body.title, "PROJECT_" + permalink.toUpperCase().replace(" ", "").replace("-", "_"), "DB_PROJECT");
+    if (title.valid === false){
+        res.status(400).send("Invalid title values");
+        return;
+    }
+    const header = localeService.generateLocalizedObject(req.body.header, "PROJECT_" + permalink.toUpperCase().replace(" ", "").replace("-", "_") + "_HEADER", "DB_PROJECT");
+    if (header.valid === false){
+        res.status(400).send("Invalid header values");
+        return;
+    }
+    const text = localeService.generateLocalizedObject(req.body.text, "PROJECT_" + permalink.toUpperCase().replace(" ", "").replace("-", "_") + "_TEXT", "DB_PROJECT");
+    const comment = localeService.generateLocalizedObject(req.body.comment, "PROJECT_" + permalink.toUpperCase().replace(" ", "").replace("-", "_") + "_COMMENT", "DB_PROJECT");
+    const type = req.body.type; // TODO: Validate
+    const logo = req.body.logo;
+    const license = req.body.license; // TODO: Validate
+    const visible = ((req.body.visible + "").toLowerCase() === 'true') ? true : false;
+    localeService.saveLocalizedObject(title);
+    localeService.saveLocalizedObject(header);
+    localeService.saveLocalizedObject(text);
+    localeService.saveLocalizedObject(comment);
     // Create a Project
     // Create a Project
     const Project = {
     const Project = {
-        title: req.body.title,
-        description: req.body.description,
-        published: req.body.published ? req.body.published : false
+        permalink: permalink,
+        user: user,
+        idx: idx,
+        projectTypeId: type,
+        title: title,
+        logo: logo,
+        header: header,
+        text: text,
+        comment: comment,
+        licenseId: license,
+        visible: visible
     };
     };
     
     
     // Save Project in the database
     // Save Project in the database
     Project.create(Project)
     Project.create(Project)
     .then(data => {res.send(data);})
     .then(data => {res.send(data);})
-    .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while creating the Project." }); });
+    .catch(err => {
+        logger.error("Error creating project: " + err);
+        res.status(500).send({"Error creating project."});
+    });
 };
 };
 
 
 /**
 /**
@@ -44,33 +99,48 @@ exports.create = (req, res) => {
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.findAll = (req, res) => {
 exports.findAll = (req, res) => {
-    Project.findAll({where: {visible: true,}, include: ["license", "type", "tags", {model: ProjectUrl, include: "type"}], attributes: { exclude: ['licenseId', 'projectTypeId'] }, order: [['idx', 'ASC'], ['id', 'DESC']] })
+    Project.findAll({
+        where: {visible: true,}, 
+        include: ["license", "type", "tags", {model: ProjectUrl, include: "type"}],
+        attributes: { exclude: ['licenseId', 'projectTypeId'] },
+        order: [['idx', 'ASC'], ['id', 'DESC']]
+    })
     .then(async data => {
     .then(async data => {
         data = await localeService.localizeProjects(data, req);
         data = await localeService.localizeProjects(data, req);
         res.send(data);
         res.send(data);
     })
     })
-    .catch(err => { res.status(500).send({message: err.message || "Some error occurred while retrieving Projects."}); });
+    .catch(err => {
+        logger.error("Error retrieving projects: " + err);
+        res.status(500).send("Error retrieving projects.");
+    });
 };
 };
 
 
 /**
 /**
- * Find a single License by it's ID.
+ * Find a single project by it's ID or permalink.
  * 
  * 
  * @param req The received request by the server.
  * @param req The received request by the server.
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.findOne = (req, res) => {
 exports.findOne = (req, res) => {
     const id = req.params.id;
     const id = req.params.id;
-    Project.findOne({ where: { visible: true, [Op.or]: {permalink: id, id: id }}, include: ["license", "type", "tags", {model: ProjectUrl, include: "type"}, "project-images"], attributes: { exclude: ['licenseId', 'projectTypeId'] } })
-    //Project.findByPk(id, { where: { visible: true }, include: ["license"], attributes: {exclude: ['licenseId'] } })
+    Project.findOne({
+        where: {visible: true, [Op.or]: {permalink: id, id: id }},
+        include: ["license", "type", "tags", {model: ProjectUrl, include: "type"}, "project-images"],
+        attributes: { exclude: ['licenseId', 'projectTypeId']}
+    })
     .then(async data => {
     .then(async data => {
-        if (data) {
+        if (data){
             data = await localeService.localizeProject(data, req);
             data = await localeService.localizeProject(data, req);
             res.send(data);
             res.send(data);
         }
         }
-        else res.status(404).send({ message: `Cannot find Project with permalink or id=${id}.` });
+        else res.status(404).send(`No project with id or permalink ${id}.`});
     })
     })
-    .catch(err => { res.status(500).send({ message: "Error retrieving Project with permalink or id=" + id + ": " + err}); });
+    .catch(err => {
+        logger.error("Error retrieving project with id or permalink " + id + ": " + err);
+        res.status(500).send("Error retrieving project with id or permalink " + id + ".");
+    });
 };
 };
+
 /**
 /**
  * Update a single project by it's ID.
  * Update a single project by it's ID.
  * 
  * 
@@ -78,16 +148,22 @@ exports.findOne = (req, res) => {
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.update = (req, res) => {
 exports.update = (req, res) => {
+    // Check authentication
+    const validation = authService.validateToken(req, res);
+    if (validation.success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
     const id = req.params.id;
     const id = req.params.id;
-    
-    Project.update(req.body, {
-        where: { id: id }
-    })
+    Project.update(req.body, {where: {[Op.or]: {permalink: id, id: id }}, user: user}})
     .then(num => {
     .then(num => {
-        if (num == 1) res.send({ message: "Project was updated successfully." });
-        else res.send({ message: `Cannot update Project with id=${id}. Maybe Project was not found or req.body is empty!` });
+        if (num == 1) res.send("Project updated successfully.");
+        else res.send(`Cannot update project with id or permalink ${id}.`);
     })
     })
-    .catch(err => { res.status(500).send({ message: "Error updating Project with id=" + id }); });
+    .catch(err => {
+        logger.error("Error updating project with id or permalink " + id + ": " + err);
+        res.status(500).send("Error updating project with id or permalink " + id + ".");
+    });
 };
 };
 
 
 /**
 /**
@@ -97,14 +173,23 @@ exports.update = (req, res) => {
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.delete = (req, res) => {
 exports.delete = (req, res) => {
+    // Check authentication
+    const validation = authService.validateToken(req, res);
+    if (validation.success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
+    const user = validation.user;
     const id = req.params.id;
     const id = req.params.id;
-    
-    Project.destroy({ where: { id: id } })
+    Project.destroy({where: {[Op.or]: {permalink: id, id: id }}, user: user}})
     .then(num => {
     .then(num => {
-        if (num == 1) res.send({ message: "Project was deleted successfully!" });
-        else  res.send({ message: `Cannot delete Project with id=${id}. Maybe Project was not found!` });
+        if (num == 1) res.send({message: "Project deleted successfully."});
+        else  res.send({message: `Cannot delete project with id or permalink ${id}.`});
     })
     })
-    .catch(err => { res.status(500).send({ message: "Could not delete Project with id=" + id }); });
+    .catch(err => {
+        logger.error("Error deleting project with id or permalink " + id + ": " + err);
+        res.status(500).send("Error deleting project with id or permalink " + id + ".");
+    });
 };
 };
 
 
 /**
 /**
@@ -114,10 +199,17 @@ exports.delete = (req, res) => {
  * @param res The request to be sent by the server.
  * @param res The request to be sent by the server.
  */
  */
 exports.deleteAll = (req, res) => {
 exports.deleteAll = (req, res) => {
-    Project.destroy({
-        where: {},
-        truncate: false
-    })
-    .then(nums => { res.send({ message: `${nums} Projects were deleted successfully!` }); })
-    .catch(err => { res.status(500).send({ message: err.message || "Some error occurred while removing all Projects." }); });
+    // Check authentication
+    const validation = authService.validateToken(req, res);
+    if (validation.success === false){
+        res.status(validation.code).send(validation.message);
+        return;
+    }
+    const user = validation.user;
+    Project.destroy({where: {user: user}, truncate: false})
+    .then(nums => {res.send(`${nums} projects deleted successfully.`);})
+    .catch(err => {
+        logger.error("Error deleting projects: " + err);
+        res.status(500).send("Error deleting projects.");
+    });
 };
 };

+ 194 - 0
backend/inigovalentin/app/controllers/user.controller.js

@@ -0,0 +1,194 @@
+/**
+ * @file Provides the model and operation for users.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
+const sha1 = require('sha1');
+const jwt = require('jsonwebtoken');
+const db = require("../models");
+const User = db.users;
+const Op = db.Sequelize.Op;
+
+/**
+ * Retrieve all users from the database.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
+exports.findAll = (req, res) => {
+    User.findAll()
+    .then(async data => {
+        data = await localeService.localizeUsers(data, req);
+        res.send(data);
+    })
+    .catch(err => {res.status(500).send({message: err.message || "Some error occurred while retrieving users."});});
+};
+
+/**
+ * Find a single user by it's ID.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
+exports.findOne = (req, res) => {
+    const id = req.params.id;
+    User.findByPk(id)
+    .then(async data => {
+        if (data) {
+            data = await localeService.localizeUser(data, req);
+            res.send(data);
+        }
+        else res.status(404).send({message: `Cannot find user with id=${id}.`})
+    })
+    .catch(err => {res.status(500).send({message: "Error retrieving user with id=" + id});});
+};
+
+/**
+ * Find a single user by it's username or email.
+ * 
+ * @param req The received request by the server.
+ * @param res The request to be sent by the server.
+ */
+exports.findByUsernameOrEmail = (req, res) => {
+    const username = req.method === 'POST' ? req.body.username : req.params.username;
+    const email = req.method === 'POST' ? req.body.email : req.params.email;
+    if (!email && !username) res.status(400).send("Username or email required");
+    User.findOne({where: {[Op.or]: {username: username, email: email}}})
+    .then(async data => {
+        if (data) {
+            data = await localeService.localizeUser(data, req);
+            res.send(data);
+        }
+        else res.status(404).send({message: `Cannot find user with username ${username} or email ${email}.`})
+    })
+    .catch(err => {res.status(500).send({message: "Error retrieving user."});});
+};
+    
+    /**
+     * Update a single license by it's ID.
+     * 
+     * @param req The received request by the server.
+     * @param res The request to be sent by the server.
+     */
+    exports.update = (req, res) => {
+        const id = req.params.id;
+        License.update(req.body, {where: {id: id}})
+        .then(num => {
+            if (num == 1) res.send({message: "License was updated successfully."});
+            else res.send({message: `Cannot update License with id=${id}. Maybe License was not found or req.body is empty!`});
+        })
+        .catch(err => {res.status(500).send({message: "Error updating License with id=" + id});});
+    };
+    
+    /**
+     * Delete a single license by it's ID.
+     * 
+     * @param req The received request by the server.
+     * @param res The request to be sent by the server.
+     */
+    exports.delete = (req, res) => {
+        const id = req.params.id;
+        License.destroy({where: {id: id}})
+        .then(num => {
+            if (num == 1) res.send({message: "License was deleted successfully!"});
+            else res.send({message: `Cannot delete License with id=${id}. Maybe License was not found!`});
+        })
+        .catch(err => {res.status(500).send({message: "Could not delete License with id=" + id});});
+    };
+    
+    /**
+     * Delete all licenses.
+     * 
+     * @param req The received request by the server.
+     * @param res The request to be sent by the server.
+     */
+    exports.deleteAll = (req, res) => {
+        License.destroy({
+            where: {},
+            truncate: false
+        })
+        .then(nums => {res.send({ message: `${nums} Licenses were deleted successfully!` });})
+        .catch(err => {res.status(500).send({message: err.message || "Some error occurred while removing all Licenses."});});
+    };
+    
+    /**
+     * Create and save a new user.
+     * 
+     * @param req The received request by the server.
+     * @param res The request to be sent by the server.
+     */
+    exports.create = async (req, res) => {
+        return res.status(405).send("User creation is disabled.");
+        try {
+            // Get user input
+            const {username, firstName, lastName, email, password} = req.body;
+            
+            // Validate user input
+            if (!(email && password && username)) {res.status(400).send("Credentials required.");}
+            
+            // check if user already exist
+            // Validate if user exist in our database
+            let existing = await User.findByUsernameOrEmail({username}, {email});
+            if (existing) {return res.status(409).send("User already exists. Please login.");}
+            existing = await User.findByEmail({email});
+            if (existing) {return res.status(409).send("User already exists. Please login.");}
+            
+            //Encrypt user password
+            encryptedUserPassword = await bcrypt.hash(password, 10);
+            
+            // Create user in our database
+            const user = await User.create({
+                first_name: firstName,
+                last_name: lastName,
+                email: email.toLowerCase(),
+                password: encryptedUserPassword,
+            });
+            
+            // Create token
+            const token = jwt.sign({user_id: user.id}, 'process.env.TOKEN_KEY', {expiresIn: "5h"});
+            // save user token
+            user.token = token;
+            User.update({token: token}, {where: {id: user.id }})
+            return res.status(200).json({token: token});
+        }
+        catch (err) {
+            console.log("Error registering user:" + err);
+        }
+    };
+    
+    /**
+     * Log a user in.
+     * 
+     * @param req The received request by the server.
+     * @param res The request to be sent by the server.
+     */
+    exports.login = async (req, res) => {
+        try {
+            // Get user input
+            const email = req.body.email ? req.body.email : "";
+            const username = req.body.username ? req.body.username : "";
+            const password = req.body.password;
+            // Validate user input
+            if (!(password && (email || username))) res.status(400).send("Credentials required");
+            
+            // Validate if user exist in our database
+            const user = await User.findOne({where: {[Op.or]: {username: username, email: email}}});
+            //encryptedUserPassword = await bcrypt.hash(password, salt);
+            //dbUserPassword = await bcrypt.hash(user.password, salt);
+            //if (user && (await bcrypt.compare(password, user.password))) {
+            //if (user && (await bcrypt.hash(user.password, salt) == await bcrypt.hash(password, salt))) {
+            if (user && user.password == sha1(user.hash + password)){
+                // Create token
+                const token = jwt.sign({user_id: user.id}, 'process.env.TOKEN_KEY', {expiresIn: "5h"});
+                // Save user token
+                user.token = token;
+                User.update({token: token}, {where: {id: user.id }})
+                return res.status(200).json({token: token});
+            }
+            return res.status(400).send("Invalid Credentials");
+        }
+        catch (err) {
+            console.log("Error logging in: " + err);
+        }
+    };

+ 1 - 0
backend/inigovalentin/app/models/index.js

@@ -15,6 +15,7 @@ const db = {};
 db.Sequelize = Sequelize;
 db.Sequelize = Sequelize;
 db.sequelize = sequelize;
 db.sequelize = sequelize;
 
 
+db.users = require("./user.model.js")(sequelize, Sequelize);
 db.langs = require("./lang.model.js")(sequelize, Sequelize);
 db.langs = require("./lang.model.js")(sequelize, Sequelize);
 db.texts = require("./text.model.js")(sequelize, Sequelize);
 db.texts = require("./text.model.js")(sequelize, Sequelize);
 db.licenses = require("./license.model.js")(sequelize, Sequelize);
 db.licenses = require("./license.model.js")(sequelize, Sequelize);

+ 10 - 7
backend/inigovalentin/app/models/lang.model.js

@@ -1,12 +1,15 @@
+/**
+ * @file Provides the model for languages.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
 module.exports = (sequelize, Sequelize) => {
 module.exports = (sequelize, Sequelize) => {
     const Lang = sequelize.define("lang", {
     const Lang = sequelize.define("lang", {
       code: {allowNull: false, primaryKey: true, type: Sequelize.STRING(8)},
       code: {allowNull: false, primaryKey: true, type: Sequelize.STRING(8)},
-      name: {type: Sequelize.STRING(64)},
-      priority: {type: Sequelize.INTEGER},
-      active: {type: Sequelize.BOOLEAN},
-      default: {type: Sequelize.BOOLEAN},
-    },
-    {timestamps: false}
-    );
+      name: {allowNull: false, type: Sequelize.STRING(64)},
+      priority: {allowNull: false, type: Sequelize.INTEGER},
+      active: {allowNull: false, type: Sequelize.BOOLEAN}
+    }, {timestamps: false});
     return Lang;
     return Lang;
 }
 }

+ 9 - 5
backend/inigovalentin/app/models/license.model.js

@@ -1,12 +1,16 @@
+/**
+ * @file Provides the model for licenses.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
 module.exports = (sequelize, Sequelize) => {
 module.exports = (sequelize, Sequelize) => {
     const License = sequelize.define("license", {
     const License = sequelize.define("license", {
-      id: {allowNull: false, primaryKey: true, type: Sequelize.STRING(32)},
+      id: {allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER},
+      name: {allowNull: false, unique: true, type: Sequelize.STRING(32)},
       summary: {allowNull: false, type: Sequelize.STRING(64)},
       summary: {allowNull: false, type: Sequelize.STRING(64)},
       legal: {allowNull: false, type: Sequelize.STRING(64)},
       legal: {allowNull: false, type: Sequelize.STRING(64)},
-      logo: {type: Sequelize.STRING(64)},
       icon: {type: Sequelize.STRING(64)},
       icon: {type: Sequelize.STRING(64)},
-    },
-    {timestamps: false}
-    );
+    }, {timestamps: false});
     return License;
     return License;
 }
 }

+ 12 - 6
backend/inigovalentin/app/models/project.model.js

@@ -1,17 +1,23 @@
+/**
+ * @file Provides the model for projects.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
 module.exports = (sequelize, Sequelize) => {
 module.exports = (sequelize, Sequelize) => {
     Project = sequelize.define("project", {
     Project = sequelize.define("project", {
       id: {allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER},
       id: {allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER},
-      permalink: {type: Sequelize.STRING(64)},
-      user: {type: Sequelize.INTEGER}, // TODO REFERENCE
-      idx: {type: Sequelize.SMALLINT},
-      projectTypeId: {type: Sequelize.INTEGER, references: {model: "project-types", key: "id"}},
+      permalink: {allowNull: false, type: Sequelize.STRING(64)},
+      user: {allowNull: false, type: Sequelize.INTEGER}, // TODO REFERENCE
+      idx: {allowNull: false, type: Sequelize.SMALLINT},
+      projectTypeId: {allowNull: false, type: Sequelize.INTEGER, references: {model: "project-types", key: "id"}},
       title: {allowNull: false, type: Sequelize.STRING(64), references: {model: "texts", key: "id"}},
       title: {allowNull: false, type: Sequelize.STRING(64), references: {model: "texts", key: "id"}},
       logo: {type: Sequelize.STRING(64)},
       logo: {type: Sequelize.STRING(64)},
       header: {allowNull: false, type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
       header: {allowNull: false, type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
       text: {type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
       text: {type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
       comment: {type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
       comment: {type: Sequelize.STRING(64)},//, references: {model: "texts", key: "id"}},
-      licenseId: {type: Sequelize.STRING(32), references: {model: "licenses", key: "id"}},
-      visible: {type: Sequelize.BOOLEAN},
+      licenseId: {allowNull: false, type: Sequelize.STRING(32), references: {model: "licenses", key: "id"}},
+      visible: {allowNull: false, type: Sequelize.BOOLEAN},
     });
     });
     return Project;
     return Project;
 }
 }

+ 23 - 0
backend/inigovalentin/app/models/user.model.js

@@ -0,0 +1,23 @@
+/**
+ * @file Provides the user model.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
+module.exports = (sequelize, Sequelize) => {
+    User = sequelize.define("user", {
+        id: {allowNull: false, autoIncrement: true, primaryKey: true, type: Sequelize.INTEGER},
+        username: {allowNull: false, unique: true, type: Sequelize.STRING(32)},
+        displayname: {allowNull: false, unique: true, type: Sequelize.STRING(32)},
+        tagline: {allowNull: false, unique: true, type: Sequelize.STRING(64)},
+        email: {allowNull: false, unique: true, type: Sequelize.STRING(128)},
+        firstName: {type: Sequelize.STRING(128)},
+        lastName: {type: Sequelize.STRING(128)},
+        image: {type: Sequelize.STRING(64)},
+        password: {allowNull: false, type: Sequelize.STRING(256)},
+        salt: {allowNull: false, type: Sequelize.STRING(64)},
+        active: {allowNull: false, default: true, type: Sequelize.BOOLEAN},
+        admin: {allowNull: false, default: false, type: Sequelize.BOOLEAN},
+    });
+    return User;
+}

+ 20 - 0
backend/inigovalentin/app/routes/access.routes.js

@@ -0,0 +1,20 @@
+const db = require("../models");
+const AuthService = require('../services/auth.service.js');
+
+
+module.exports = app => {
+    
+    
+    const users = require("../controllers/user.controller.js");
+    var router = require("express").Router();
+
+    router.post("/register", users.create);
+
+    router.post("/login", function(req, res) {
+        const authService = new AuthService(db);
+        authService.login(req, res);
+    });
+
+    app.use('/', router);
+
+}

+ 3 - 0
backend/inigovalentin/app/routes/project.routes.js

@@ -1,3 +1,6 @@
+const db = require("../models");
+const AuthService = require('../services/auth.service.js');
+
 module.exports = app => {
 module.exports = app => {
     const projects = require("../controllers/project.controller.js");
     const projects = require("../controllers/project.controller.js");
     
     

+ 90 - 0
backend/inigovalentin/app/services/auth.service.js

@@ -0,0 +1,90 @@
+/**
+ * @file Provides a service to handle languages and localizations.
+ * @author Inigo Valentin
+ * @since 4.0.0
+ */
+
+const sha1 = require('sha1');
+const jwt = require('jsonwebtoken');
+const users = require("../controllers/user.controller.js");
+
+const config = process.env;
+
+/**
+ * Handles localization of elements.
+ */
+class AuthService {
+    
+    /**
+     * Database connection.
+     */
+    #db;
+    
+    /**
+     * Constructor.
+     *
+     * @param db Database connection.
+     * @constructor
+     */
+    constructor(db){
+        this.#db = db;
+    }
+    
+    /**
+     * Logs a user in.
+     * 
+     * @param req The request received by the server.
+     */
+    async login(req, res){
+        try {
+            // Get user input
+            const email = req.body.email ? req.body.email : "";
+            const username = req.body.username ? req.body.username : "";
+            const password = req.body.password;
+            // Validate user input
+            if (!(password && (email || username))) res.status(401).send("Credentials required");
+            
+            // Validate if user exist in our database
+            const user = await User.findOne({where: {[this.#db.Sequelize.Op.or]: {username: username, email: email}}});
+            if (user && user.password == sha1(user.hash + password)){
+                require('dotenv').config()
+                const {TOKEN_SECRET, TOKEN_ISSUER} = process.env;
+                const token = jwt.sign({user: user.id}, TOKEN_SECRET, {algorithm: 'HS256', expiresIn: '5h', issuer: TOKEN_ISSUER, subject: user.username})
+                res.send({token});
+            }
+            return res.status(403).send("Invalid Credentials");
+        }
+        catch (err) {
+            console.log("Error logging in: " + err);
+            return res.status(500).send("Error logging in");
+        }
+    }
+    
+    /**
+     * Validates a token in the request.
+     * 
+     * @param req The request received by the server.
+     * @return An object with four values:
+     *   - success: True if the user was authenticated, false otherwise.
+     *   - user: The ID of the authenticated user, or null if no user was authenticated.
+     *   - code: A HTTP status code that can be set on the response: 200, 401, 403 or 500.
+     *   - message: A message describing the HTTP status code.
+     */
+    validateToken(req, res) {
+        const token = req.body.token || req.query.token || req.headers["x-access-token"];
+        console.log("TOKEN: " + token);
+        if (!token) return {success: false, user: null, code: 401, message: "A token is required for authentication."};
+        try {
+            require('dotenv').config()
+            const {TOKEN_SECRET, TOKEN_ISSUER} = process.env;
+            const {exp, iss, user} = jwt.verify(token, TOKEN_SECRET);
+            if (iss === TOKEN_ISSUER && exp < Date.now()) return {success: true, user: user, code: 200, message: "OK"};
+            return {success: false, user: null, code: 403, message: "Invalid token."};;
+        }
+        catch (err) {
+            console.log("Error validating token: " + err);
+            return {success: false, user: null, code: 500, message: "Authentication error."};;
+        }
+    }
+}
+module.exports = AuthService;

+ 82 - 0
backend/inigovalentin/app/services/locale.service.js

@@ -72,6 +72,55 @@ class LocaleService {
         }
         }
         return "";
         return "";
     }
     }
+    
+    /**
+     * Generates a localized object for text storing in database.
+     * 
+     * @param obj The object with the text in multiple languages. Language codes must be keys.
+     * @param key The key to assign to the text.
+     * @param section The section identifier for the text.
+     * @return An object with those values:
+     *   - valid: True if the object is valid and can be stored in database.
+     *   - key: Identifier for the text.
+     *   - section: Identifier fot the text section.
+     *   - texts{}: An array for the text in different languages. Language codes are keys.
+     */
+    generateLocalizedObject(obj, key, section){
+        let l = {
+            valid: true,
+            key: key,
+            section: section,
+            texts: {}
+        };
+        if (!key) l.valid = false;
+        if (!section) l.valid = false;
+        l.key = key;
+        l.section = section;
+        let body;
+        try{
+            body = JSON.parse(obj);
+            for (let i = 0; i < this.#availableLanguages.length; i ++)
+                if (body[this.#availableLanguages[i]] != undefined) l.texts[this.#availableLanguages[i]] = body[this.#availableLanguages[i]];
+            if (l.texts.length == 0) l.valid = false;
+        }
+        catch(err){
+            l.valid = false;
+        }
+        return l;
+    }
+
+    /**
+     * Saves a localized object in the database.
+     * 
+     * @param obj The object, as provided by {@see generateLocalizedObject}.
+     * @return True on success, false on error.
+     */
+    async saveLocalizedObject(obj, key, section){
+        if (!obj.valid || obj.valid != true || !obj.texts || obj.texts.length < 1) return false;
+        for (const [lang, text] of Object.entries(obj.texts))
+            let res = await this.#db.sequelize.query('INSERT INTO texts (id, lang, section, text, file) VALUES (?, ?, ?, ?, ?)', {replacements: [obj.key, lang, obj.section, text, null], type: this.#db.sequelize.QueryTypes.INSERT});
+        return true;
+    }
 
 
     /**
     /**
      * Localizes a project.
      * Localizes a project.
@@ -187,6 +236,39 @@ class LocaleService {
         for (var d of data) d = await this.localizeProjectType(d, req);
         for (var d of data) d = await this.localizeProjectType(d, req);
         return data;
         return data;
     }
     }
+    
+    /**
+     * Localizes a user.
+     *
+     * Localizes all localizable items in a user, using the language provided in the request or
+     * the default one.
+     *
+     * @param data The user object to localize.
+     * @param req The request received by the server.
+     * @return The user object, with all of the fields localized.
+     */
+    async localizeUser(data, req){
+        var lang = this.selectLanguage(req);
+        // TODO
+        //data.dataValues.title = await this.#decodeText(data.dataValues.title, lang);
+        //data.dataValues.summary = await this.#decodeText(data.dataValues.summary, lang);
+        return data;
+    }
+    
+    /**
+     * Localize a list of users project types.
+     *
+     * Localize all localizable items in all users, using the language provided in the request or
+     * the default one.
+     *
+     * @param data The user object list to localize.
+     * @param req The request received by the server.
+     * @return The user list, with all of their fields localized.
+     */
+    async localizeUserss(data, req){
+        for (var d of data) d = await this.localizeUser(d, req);
+        return data;
+    }
 
 
 }
 }
 module.exports = LocaleService;
 module.exports = LocaleService;

+ 11 - 0
backend/inigovalentin/config/db.config.js

@@ -0,0 +1,11 @@
+require('dotenv').config()
+const {DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME, DB_TYPE} = process.env;
+module.exports = {
+    HOST: DB_HOST,
+    PORT: DB_PORT,
+    USER: DB_USER,
+    PASSWORD: DB_PASS,
+    DB: DB_NAME,
+    dialect: DB_TYPE,
+    pool: {max: 5, min: 0, acquire: 30000, idle: 10000}
+};

+ 20 - 0
backend/inigovalentin/env/example.env

@@ -0,0 +1,20 @@
+# This file is an example of an .env file with all the values needed by the application.
+# Copy and rename this file, set the values to the ones needed by your application, and run the
+# server with 'node --env-file=<your_file>.env app.js'.
+# Never add the modified file to version control.
+
+# Database connection parameters
+DB_HOST = "localhost"
+DB_PORT = 3306
+DB_USER = "user"
+DB_PASS = "password"
+DB_NAME = "name"
+DB_TYPE = "mysql"
+
+# Server settings
+SRV_MODE = "EXAMPLE"
+SRV_PORT = 8080
+
+# Encryption secret
+SECRET = "CHANGEME"
+TOKEN_ISSUER = "CHANGEME"

+ 225 - 1
backend/inigovalentin/package-lock.json

@@ -9,10 +9,15 @@
       "version": "0.0.1",
       "version": "0.0.1",
       "license": "GPL-3.0",
       "license": "GPL-3.0",
       "dependencies": {
       "dependencies": {
+        "bcryptjs": "^2.4.3",
         "cors": "^2.8.5",
         "cors": "^2.8.5",
+        "dotenv": "^16.4.5",
         "express": "^4.21.1",
         "express": "^4.21.1",
+        "jsonwebtoken": "^9.0.2",
         "mysql2": "^3.11.3",
         "mysql2": "^3.11.3",
-        "sequelize": "^6.37.4"
+        "pine": "^1.1.1",
+        "sequelize": "^6.37.4",
+        "sha1": "^1.1.1"
       }
       }
     },
     },
     "node_modules/@types/debug": {
     "node_modules/@types/debug": {
@@ -58,6 +63,14 @@
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
       "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
     },
     },
+    "node_modules/async": {
+      "version": "2.6.4",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+      "dependencies": {
+        "lodash": "^4.17.14"
+      }
+    },
     "node_modules/aws-ssl-profiles": {
     "node_modules/aws-ssl-profiles": {
       "version": "1.1.2",
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
       "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
@@ -66,6 +79,11 @@
         "node": ">= 6.0.0"
         "node": ">= 6.0.0"
       }
       }
     },
     },
+    "node_modules/bcryptjs": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+      "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+    },
     "node_modules/body-parser": {
     "node_modules/body-parser": {
       "version": "1.20.3",
       "version": "1.20.3",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -89,6 +107,11 @@
         "npm": "1.2.8000 || >= 1.4.16"
         "npm": "1.2.8000 || >= 1.4.16"
       }
       }
     },
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+    },
     "node_modules/bytes": {
     "node_modules/bytes": {
       "version": "3.1.2",
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -115,6 +138,27 @@
         "url": "https://github.com/sponsors/ljharb"
         "url": "https://github.com/sponsors/ljharb"
       }
       }
     },
     },
+    "node_modules/caller": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/caller/-/caller-1.1.0.tgz",
+      "integrity": "sha512-n+21IZC3j06YpCWaxmUy5AnVqhmCIM2bQtqQyy00HJlmStRt6kwDX5F9Z97pqwAB+G/tgSz6q/kUBbNyQzIubw=="
+    },
+    "node_modules/charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/colors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
+      "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==",
+      "engines": {
+        "node": ">=0.1.90"
+      }
+    },
     "node_modules/content-disposition": {
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "version": "0.5.4",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
       "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -159,6 +203,22 @@
         "node": ">= 0.10"
         "node": ">= 0.10"
       }
       }
     },
     },
+    "node_modules/crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/cycle": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
+      "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/debug": {
     "node_modules/debug": {
       "version": "2.6.9",
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -208,11 +268,30 @@
         "npm": "1.2.8000 || >= 1.4.16"
         "npm": "1.2.8000 || >= 1.4.16"
       }
       }
     },
     },
+    "node_modules/dotenv": {
+      "version": "16.4.5",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+      "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
     "node_modules/dottie": {
     "node_modules/dottie": {
       "version": "2.0.6",
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
       "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
       "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA=="
       "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA=="
     },
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
     "node_modules/ee-first": {
       "version": "1.1.1",
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -299,6 +378,14 @@
         "node": ">= 0.10.0"
         "node": ">= 0.10.0"
       }
       }
     },
     },
+    "node_modules/eyes": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+      "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
+      "engines": {
+        "node": "> 0.1.90"
+      }
+    },
     "node_modules/finalhandler": {
     "node_modules/finalhandler": {
       "version": "1.3.1",
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
       "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
@@ -473,11 +560,102 @@
       "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
       "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
       "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
     },
     },
+    "node_modules/isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
+    },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jsonwebtoken/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/lodash": {
     "node_modules/lodash": {
       "version": "4.17.21",
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
     },
+    "node_modules/lodash-node": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/lodash-node/-/lodash-node-2.4.1.tgz",
+      "integrity": "sha512-egEt8eNQp2kZWRmngahiqMoDCDCENv3uM188S7Ed5t4k3v6RrLELXC+FqLNMUnhCo7gvQX3G1V8opK/Lcslahg==",
+      "deprecated": "This package is discontinued. Use lodash@^4.0.0."
+    },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+    },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+    },
     "node_modules/long": {
     "node_modules/long": {
       "version": "5.2.3",
       "version": "5.2.3",
       "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
       "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
@@ -680,6 +858,16 @@
       "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
       "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
       "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA=="
       "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA=="
     },
     },
+    "node_modules/pine": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/pine/-/pine-1.1.1.tgz",
+      "integrity": "sha512-Xk+dW+1oCpPVjNgzg0PPrHamc7aCj9LM37Qwp8pxraWIW1JFlYExr3CBXEl0ghBG2L2cCcFp8JHPDC55Dv+7iw==",
+      "dependencies": {
+        "caller": "^1.0.0",
+        "lodash-node": "^2.4.1",
+        "winston": "^2.2.0"
+      }
+    },
     "node_modules/proxy-addr": {
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -934,6 +1122,18 @@
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
       "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
       "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
     },
     },
+    "node_modules/sha1": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
+      "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==",
+      "dependencies": {
+        "charenc": ">= 0.0.1",
+        "crypt": ">= 0.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/side-channel": {
     "node_modules/side-channel": {
       "version": "1.0.6",
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
@@ -959,6 +1159,14 @@
         "node": ">= 0.6"
         "node": ">= 0.6"
       }
       }
     },
     },
+    "node_modules/stack-trace": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+      "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/statuses": {
     "node_modules/statuses": {
       "version": "2.0.1",
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -1037,6 +1245,22 @@
         "node": ">= 0.8"
         "node": ">= 0.8"
       }
       }
     },
     },
+    "node_modules/winston": {
+      "version": "2.4.7",
+      "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.7.tgz",
+      "integrity": "sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==",
+      "dependencies": {
+        "async": "^2.6.4",
+        "colors": "1.0.x",
+        "cycle": "1.0.x",
+        "eyes": "0.1.x",
+        "isstream": "0.1.x",
+        "stack-trace": "0.0.x"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
     "node_modules/wkx": {
     "node_modules/wkx": {
       "version": "0.5.0",
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
       "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",

+ 6 - 1
backend/inigovalentin/package.json

@@ -9,9 +9,14 @@
   "author": "Iñigo Valentin",
   "author": "Iñigo Valentin",
   "license": "GPL-3.0",
   "license": "GPL-3.0",
   "dependencies": {
   "dependencies": {
+    "bcryptjs": "^2.4.3",
     "cors": "^2.8.5",
     "cors": "^2.8.5",
+    "dotenv": "^16.4.5",
     "express": "^4.21.1",
     "express": "^4.21.1",
+    "jsonwebtoken": "^9.0.2",
     "mysql2": "^3.11.3",
     "mysql2": "^3.11.3",
-    "sequelize": "^6.37.4"
+    "pine": "^1.1.1",
+    "sequelize": "^6.37.4",
+    "sha1": "^1.1.1"
   }
   }
 }
 }

+ 7 - 5
backend/inigovalentin/server.js

@@ -1,3 +1,4 @@
+require('dotenv').config()
 const express = require("express");
 const express = require("express");
 const cors = require("cors");
 const cors = require("cors");
 const app = express();
 const app = express();
@@ -16,15 +17,16 @@ db.sequelize.sync()
 ;
 ;
 
 
 // Routes
 // Routes
-app.get("/", (req, res) => {
-    res.json({message: "Welcome to Inigo Valentin"});
-});
+//app.get("/", (req, res) => {
+//    res.json({message: "Welcome to Inigo Valentin"});
+//});
 
 
+require("./app/routes/access.routes")(app);
 require("./app/routes/lang.routes")(app);
 require("./app/routes/lang.routes")(app);
 require("./app/routes/text.routes")(app);
 require("./app/routes/text.routes")(app);
 require("./app/routes/project.routes")(app);
 require("./app/routes/project.routes")(app);
 
 
-const PORT = process.env.port || 8080;
-app.listen(PORT, () => {console.log('Server running in port ${PORT}.')});
+const {SRV_PORT, SRV_MODE} = process.env;
+app.listen(SRV_PORT, () => {console.log("Server running in " + SRV_MODE + " mode in port " + SRV_PORT + ".")});
 
 
 
 

+ 4 - 2
frontend/inigovalentin/.gitignore

@@ -1,11 +1,13 @@
-# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
-
 # Compiled output
 # Compiled output
 /dist
 /dist
 /tmp
 /tmp
 /out-tsc
 /out-tsc
 /bazel-out
 /bazel-out
 
 
+# User content
+/public/img/profile/*
+/public/img/project/*
+
 # Node
 # Node
 /node_modules
 /node_modules
 npm-debug.log
 npm-debug.log