From a139ebedbc2816f70d119721c33a16992009602a Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:07:33 +0100 Subject: [PATCH 01/26] Add three dependencies Validate.js: form validation express-sanitizer: xss prevention quill delta to html: Server side rendering of Quill objects --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d3bec5..7df48f2 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "cookie-session": "1.3.2", "ejs": "2.5.8", "express": "4.16.2", + "express-sanitizer": "1.0.4", "lodash": "4.17.4", "mailgun-js": "0.21.0", "node-sass-middleware": "0.11.0", @@ -54,13 +55,15 @@ "pg": "7.4.1", "pg-hstore": "2.3.2", "qs": "6.5.0", + "quill-delta-to-html": "0.10.6", "request": "2.88.0", "request-promise": "4.2.2", "saga-logger": "0.0.8", "saga-managed-error": "1.0.0", "sequelize": "4.32.2", "serve-favicon": "2.4.5", - "shortid": "2.2.13" + "shortid": "2.2.13", + "validate.js": "0.12.0" }, "semistandard": { "globals": [ From 13d526f8d69eb1a02d3b4cdcf8e53185c01209d7 Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:07:54 +0100 Subject: [PATCH 02/26] Add projects public route --- src/authentication/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/authentication/index.js b/src/authentication/index.js index a247428..04f90aa 100755 --- a/src/authentication/index.js +++ b/src/authentication/index.js @@ -11,6 +11,7 @@ const publicRoutes = { '/connexion': ['GET'], '/contact': ['GET', 'POST'], '/membres': ['GET'], + '/projets': ['GET'], '/healthcheck': ['GET'], '/inscription': ['GET', 'POST'] }; From 9c6922a3fc3ec31a9dc718feda1270eb7c7e362c Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:08:33 +0100 Subject: [PATCH 03/26] Update model --- src/models/Machines.js | 74 ++++++++++++++++++++++++++++++++++++++++++ src/models/Projects.js | 52 +++++++++++++++++++++++++++-- src/models/Users.js | 6 +++- 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 src/models/Machines.js diff --git a/src/models/Machines.js b/src/models/Machines.js new file mode 100644 index 0000000..97b030b --- /dev/null +++ b/src/models/Machines.js @@ -0,0 +1,74 @@ +const { + attachDeleteByIds, + attachFindByIds, + attachUpdateByIds +} = require('../helpers/models'); + +const shortid = require('shortid'); + +const setClassMethods = Machines => { + Machines.findByIds = attachFindByIds(Machines); + + Machines.deleteByIds = attachDeleteByIds(Machines); + + Machines.deleteCascade = async (ids, models) => { + return Machines.deleteByIds(ids); + }; + + Machines.updateByIds = attachUpdateByIds(Machines); +}; + +module.exports = (sequelize, DataTypes) => { // NOSONAR + const Machines = sequelize.define('machines', { + + shortid: { + type: DataTypes.TEXT, + allowNull: false, + unique: 'machines_short_id' + }, + + name: { + type: DataTypes.TEXT, + allowNull: false + }, + + description: { + type: DataTypes.TEXT, + allowNull: false + }, + + // It cool be nice to store a susbset of HTML generated by a webeditor + longDescription: { + type: DataTypes.TEXT, + field: 'long_description' + }, + + createdAt: { + type: DataTypes.DATE, + field: 'created_at' + }, + + updatedAt: { + type: DataTypes.DATE, + field: 'updated_at' + }, + + deletedAt: { + type: DataTypes.DATE, + field: 'deleted_at' + } + }, { + timestamps: true, + paranoid: true, + hooks: { + beforeCreate (result, options) { + result.shortId = shortid.generate(); + } + }, + indexes: [] + }); + + setClassMethods(Machines); + + return Machines; +}; diff --git a/src/models/Projects.js b/src/models/Projects.js index 2358fda..08f6da5 100644 --- a/src/models/Projects.js +++ b/src/models/Projects.js @@ -21,9 +21,8 @@ const setClassMethods = Projects => { module.exports = (sequelize, DataTypes) => { // NOSONAR const Projects = sequelize.define('projects', { - shortid: { + shortId: { type: DataTypes.TEXT, - allowNull: false, unique: 'projects_short_id' }, @@ -32,16 +31,46 @@ module.exports = (sequelize, DataTypes) => { // NOSONAR allowNull: false }, + // Project URL key + slug: { + type: DataTypes.STRING, + allowNull: false + }, + + imageLink: { + type: DataTypes.STRING, + field: 'image_link' + }, + description: { type: DataTypes.TEXT, allowNull: false }, + // Store Quill Delta object + longDescription: { + type: DataTypes.TEXT, + field: 'long_description' + }, + + // Hold id[] of machines used in project. + // WARN: Not Database-agnostic + machines: { + type: DataTypes.ARRAY(DataTypes.SMALLINT) + }, + link: { type: DataTypes.TEXT, allowNull: false }, + // Wether or not the project can be visible publicly + privacyLevel: { + type: DataTypes.STRING, + allowNull: false, + field: 'privacy_level' + }, + createdAt: { type: DataTypes.DATE, field: 'created_at' @@ -64,10 +93,27 @@ module.exports = (sequelize, DataTypes) => { // NOSONAR result.shortId = shortid.generate(); } }, - indexes: [] + indexes: [{ + unique: true, + fields: ['slug'] + }] }); setClassMethods(Projects); + Projects.privacyLevels = { + PRIVATE: 'private', + MEMBERS: 'members', + PUBLIC: 'public' + }; + + Projects.associate = models => { + Projects.belongsToMany(models.users, { + through: 'user_projects', + as: 'users', + foreignKey: 'projectId' + }); + }; + return Projects; }; diff --git a/src/models/Users.js b/src/models/Users.js index 8c30a87..06f910b 100644 --- a/src/models/Users.js +++ b/src/models/Users.js @@ -158,7 +158,11 @@ module.exports = (sequelize, DataTypes) => { // NOSONAR setClassMethods(Users); Users.associate = models => { - Users.hasMany(models.projects); + Users.belongsToMany(models.projects, { + through: 'user_projects', + as: 'projects', + foreignKey: 'userId' + }); Users.hasMany(models.bills); Users.hasMany(models.subscribtions); Users.hasMany(models.billingdetails); From bccd9c7af04b4e0cd7cf57a5d46a5fd20535bf83 Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:08:59 +0100 Subject: [PATCH 04/26] Load express-sanitizer --- src/server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server.js b/src/server.js index 63e5477..4f60f26 100644 --- a/src/server.js +++ b/src/server.js @@ -3,6 +3,7 @@ const qs = require('qs'); const express = require('express'); const bodyParser = require('body-parser'); const path = require('path'); +const expressSanitizer = require('express-sanitizer'); const ManagedError = require('saga-managed-error'); const favicon = require('serve-favicon'); const sassMiddleware = require('node-sass-middleware'); @@ -57,6 +58,8 @@ module.exports = () => { }) ); + app.use(expressSanitizer()); + app.use(favicon('./src/static/images/favicon.ico')); app.use(express.static('./src/static/')); From c3ebc70872b1cfadfaad9193cd9d2654ec13995b Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:09:48 +0100 Subject: [PATCH 05/26] Create custom validation for project --- .../validation/project.constraints.js | 47 +++++++++++++++++++ src/resources/validation/validators.js | 31 ++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/resources/validation/project.constraints.js create mode 100644 src/resources/validation/validators.js diff --git a/src/resources/validation/project.constraints.js b/src/resources/validation/project.constraints.js new file mode 100644 index 0000000..a15ef42 --- /dev/null +++ b/src/resources/validation/project.constraints.js @@ -0,0 +1,47 @@ +/* +This file holds the validation constraints applied on a project creation form + */ + +const validators = require('./validators'); +const validate = require('validate.js'); + +validate.validators.asyncUniqueSlugValidator = validators.uniqueInTableValidator('projects', 'slug'); + +module.exports.projectConstraints = context => () => ({ + + title: { + presence: { + message: 'Le titre doit être spécifié' + } + }, + description: { + + }, + // No constraints on edition + slug: context && context === 'edit' ? {} : { + presence: { + message: 'Le slug doit être spécifié' + }, + asyncUniqueSlugValidator: { + message: 'Cette adresse est déjà occupée...' + }, + format: { + pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/, + message: 'Le slug doit respecter ce format : je-suis-un-slug' + } + }, + long_description: { + + }, + imageLink: { + + }, + link: { + + }, + privacy_level: { + presence: { + message: 'Le niveau de confidentialité doit être spécifié' + } + } +}); diff --git a/src/resources/validation/validators.js b/src/resources/validation/validators.js new file mode 100644 index 0000000..93e757a --- /dev/null +++ b/src/resources/validation/validators.js @@ -0,0 +1,31 @@ +/* +This file holds custom validators for validate.js + */ + +const models = require('../../models'); + +const validate = require('validate.js'); + +/** + * Check if a database record exists with : entityName.attribute = value + * @param {string} entityName Table name + * @param {string} attribute Column name + * @return {?string} message or undefined + */ +module.exports.uniqueInTableValidator = (entityName, attribute) => (value, attributes, attributeName, options, constraints) => { + return new validate.Promise(function (resolve, reject) { + + models[entityName].find({ + here: { + [attribute]: value + } + }) + .then((response) => { + resolve(response !== null ? attributes.message : undefined); + }) + .catch((e) => { + console.error(e); + resolve(options.message); + }); + }); +}; From 66cec2992398d0bfdda71ffa95db27d4a3cc1af1 Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:10:27 +0100 Subject: [PATCH 06/26] Create public project pages --- src/controllers/projets/index.js | 49 ++++++++++++++++++++++++++++++++ src/views/projects/index.ejs | 23 +++++++++++++++ src/views/projects/project.ejs | 33 +++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 src/controllers/projets/index.js create mode 100644 src/views/projects/index.ejs create mode 100644 src/views/projects/project.ejs diff --git a/src/controllers/projets/index.js b/src/controllers/projets/index.js new file mode 100644 index 0000000..37a1889 --- /dev/null +++ b/src/controllers/projets/index.js @@ -0,0 +1,49 @@ +const QuillDeltaToHtmlConverter = require('quill-delta-to-html').QuillDeltaToHtmlConverter; + +const models = require('../../models'); +const views = require('../../views'); + +module.exports.read = async (params, meta) => { + // Privacy level according to user identity + const privacyLevel = meta.user ? [models.projects.privacyLevels.MEMBERS, models.projects.privacyLevels.PUBLIC] : models.projects.privacyLevels.PUBLIC; + + // Asking for one particular project + if (params.id) { + const project = await models.projects.find({ + where: { + slug: params.id, + privacyLevel: privacyLevel + }, + raw: true + }); + + if (!project) { + return 404; + } + + // Convert Quill Delta object to HTML + var converter = new QuillDeltaToHtmlConverter(project.longDescription, {}); + var projectLongDescriptionHtml = converter.convert(); + + return views.render('./src/views/projects/project.ejs', { + user: meta.user, + project: project, + projectLongDescriptionHtml + }); + } + + const projects = await models.projects.findAll({ + where: { + privacyLevel: privacyLevel + }, + include: [{ + model: models.users, + as: 'users' + }] + }); + + return views.render('./src/views/projects/index.ejs', { + user: meta.user, + projects: projects + }); +}; diff --git a/src/views/projects/index.ejs b/src/views/projects/index.ejs new file mode 100644 index 0000000..7cd669a --- /dev/null +++ b/src/views/projects/index.ejs @@ -0,0 +1,23 @@ +<%- include('../layout/top', {page: 'projets'}); %> + +
+
+
+

Projets + <% if (user) { %> + | Mes projets + <% } %> +

+
+
+ +
+
+

Retrouvez ici quelques projets réalisés par les Fabiens

+
+
+ <%- include("../_partials/projectList.ejs", {projects, user}) %> +
+
+
+<%- include('../layout/bottom'); %> diff --git a/src/views/projects/project.ejs b/src/views/projects/project.ejs new file mode 100644 index 0000000..7cb2ebc --- /dev/null +++ b/src/views/projects/project.ejs @@ -0,0 +1,33 @@ +<%- include('../layout/top', {page: 'projets'}); %> +
+
+
+

+ <%= project.title %> +

+ + <% if (project.link) { %> + Voir le projet sur Github + <% } %> + +

+ <%= project.description %> +

+ +
+
+ +
+
+ <%- projectLongDescriptionHtml %> +
+
+ +
+
+ +
+
+
+
+
From 2cc2a45e0606d6b487001349b0eec5e004514d75 Mon Sep 17 00:00:00 2001 From: loic Date: Sun, 25 Nov 2018 11:13:04 +0100 Subject: [PATCH 07/26] Create menu entries --- src/views/layout/top.ejs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/views/layout/top.ejs b/src/views/layout/top.ejs index 0f3b3d3..c39a17d 100644 --- a/src/views/layout/top.ejs +++ b/src/views/layout/top.ejs @@ -25,6 +25,11 @@ + + + @@ -44,6 +49,8 @@ + + + @@ -44,6 +49,8 @@