Added first iteration for the module

This commit is contained in:
dbroqua 2020-02-10 16:13:42 +01:00
parent 6f41f71269
commit ef7ca0315b
11 changed files with 6003 additions and 0 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
/node_modules/

30
.eslintrc.js Normal file
View file

@ -0,0 +1,30 @@
module.exports = {
parser: "babel-eslint",
extends: ["airbnb-base", "prettier", "plugin:jest/recommended"],
plugins: ["prettier", "jest"],
env: {
es6: true,
browser: false,
node: true,
"jest/globals": true
},
globals: {
__DEV__: true
},
rules: {
// enable additional rules
"no-plusplus": [2, { allowForLoopAfterthoughts: true }],
"no-underscore-dangle": "off",
indent: ["error", 2, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
// Forbid the use of extraneous packages
// https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md
"import/no-extraneous-dependencies": ["error", { packageDir: "." }],
// ESLint plugin for prettier formatting
// https://github.com/prettier/eslint-plugin-prettier
"prettier/prettier": "error",
"func-names": ["error", "never"]
}
};

68
.gitignore vendored Normal file
View file

@ -0,0 +1,68 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
/reports/
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
/dist/
# Seeder info, proper to each environment
sequelize-data.json

213
README.md
View file

@ -1,2 +1,215 @@
# sequelize-middleware # sequelize-middleware
Sequelize-middleware est un module NodeJS permettant d'automatiser la création d'une API REST utilisant Sequelize comme SGBD.
Sequelize-middleware s'appuie également sur Joi pour valider les données reçues ainsi que les appels API effectuées.
## Fonctionnalités
Ce module est découpé en 3 fonctionnalités principales :
* Le `Middleware`, partie centrale du projet
* `ErrorBuilder` qui permet de surcharger la méthode `new Error()`
* `ResponseFormater` qui se charge de formater les données retournée au client final
### Middleware
Méthode par défaut, permettant de générer tout le code propre à un endpoint.
```js
import Middleware from "sequelize-middleare";
import models from "../models";
import params from "../rules/Endpoint";
const middleware = new Middleware(params, models);
middleware.getAll(req, callback);
```
#### Initilisation
Le constructeur doit recevoir 2 paramètres :
* params
* models
`params` représente le fichier des règles pour cet endpoint.
`models` fait référence au fichier `models/index.js` généré par Sequelize.
Le fichier params doit ressembler à ceci :
```js
import Joi from "@hapi/joi";
const models = require("../models"); // Facultatif
const Rules = {
model: "Areas", // Nom de la collection Sequelize
crud: { // Liste des rôles (req.user.roles) autorisés en fonction de l'action
read: ["installer", "admin", "user"],
write: ["installer", "admin"],
edit: ["installer", "admin"],
delete: ["installer", "admin"]
},
includes: [ // Liste des inclusions à faire lors d'un getOne/getAll
{
collection: "Devices", // Nom de la collection à inclure
requiredRole: ["installer", "admin", "user"], // Rôles autorisés à voir cette inclusion
required: false, // Sauf cas particulier toujours mettre false
model: models.AreasDevices, // Nom du modèle Sequelize à inclure
include: [ // Liste des sous-inclusions
{
collection: "Device",
requiredRole: ["installer", "admin", "user"]
}
]
}
],
format: { // Formatage des données en fonction du rôle de l'utilisateur (dans le cas ou l'on veut surcharger le formatage du modèle)
user: {
idRetourne: "_idSql", // Clé retournée: Clé du modèle
id: "id",
nom: "name"
}
},
itemId: "areaId", // Id de l'item dans la req.params
validate: { // Définition des des règles de valeurs acceptées en fonction du verbe
// Création d'un nouvele élément
create: Joi.object({
name: Joi.string().required(),
type: Joi.string()
.valid("OFFICE", "COMMUNAL-AREAS")
.required()
}),
// Mise à jour d'un élément
update: Joi.object({
name: Joi.string(),
type: Joi.string().valid("OFFICE", "COMMUNAL-AREAS")
}),
// Sélection d'un élément (req.params)
item: Joi.object({
areaId: Joi.number().required()
}),
// Sélection d'une liste d'éléments (req.query)
list: Joi.object({
limit: Joi.number()
.integer()
.min(1)
.max(50),
page: Joi.number()
.integer()
.min(1),
type: Joi.string().valid("OFFICE", "COMMUNAL-AREAS"),
sort: Joi.string()
.valid("id", "name", "type", "createdAt", "updatedAt")
.only(),
order: Joi.string()
.valid("asc", "desc")
.only()
})
.with("limit", "page")
.with("page", "limit")
.with("sort", "order")
.with("order", "sort")
},
removeKeys: {
// Permet de supprimer automatiquement des valeurs de requêtes
item: ["partnerId"] // Sur la sélection d'un item on supprime req.params.partnerId
},
override: {
list: { // Permet de convertir des paramètres reçus via req.query pour en générer des filtres complexes
filters: {
'category': { // On converti req.query.category
$or: [
{
categoryId: '_TERM_',
},
{
categoriesId: {
$contains: '_TERM_',
},
},
],
},
'countryId': { // On converti req.query pour dire à Sequelize que ça correspond à $Details.countryId$
'$Details.countryId$': '_TERM_',
}
},
},
create: {
// Création d'un nouvel item
body: [
// On modifie req.body
{
append: "offerId", // On rajoute un attribut offerId
from: "params", // Que l'on prends dans req.params
value: "offerId" // Et dont la clé est offerId
}
]
}
},
restrictOn: { // Permet de rajouter des restrictions en fonction des rôles
update: [ // Lors d'une mise à jour au autorise l'utilisateur à modifier uniquement les éléments donc state est égal à NEW.
{
roles: ['user'],
type: 'raw', // raw permet de forcer une valeur, sinon user/params/body/query
field: 'state',
value: 'NEW',
},
],
delete: [],
list: [],
create: []
},
};
export default Rules;
```
### ErrorBuilder
```js
import { ErrorBuilder } from "sequelize-middleare";
```
Ce module permet de remplacer la méthode `new Error()` de JS en ajoutant la possibilité de générer un code d'erreur qui sera ensuite retourné via `res.status().json()`.
Le code reçu doit être un `float` dont la partie entière représente un code HTTP valide et la partie réelle représente un code d'erreur plus détaillé de l'erreur.
Exemples d'utilistion :
```js
import { ErrorBuilder } from "sequelize-middleare";
new ErrorBuilder(406.0, "Erreur générique de type 406");
new ErrorBuilder(406.1, "Le champs mot de passe est absent");
new ErrorBuilder(406.2, "Le champs mot de passe doit contenir 8 caractères minimum");
...
```
### ResponseFormater
```js
import { ResponseFormater } from "sequelize-middleare";
```
Ce module permet de formater à la fois le json ainsi que le code http retourné au client une fois que le middleware à fini son traitement.
Exemples d'utilisation :
```js
import { ResponseFormater } from "sequelize-middleare";
router
.route("/")
.get(passport.authenticate(["jwt"]), function(req, res, next) {
middleware.getAll(req, (err, response) => {
ResponseFormater(req, res, next, err, response);
});
})
.post(passport.authenticate(["jwt"]), function(req, res, next) {
middleware.createOne(req, (err, response) => {
ResponseFormater(req, res, next, err, response);
});
});
```

7
index.js Normal file
View file

@ -0,0 +1,7 @@
import _ErrorBuilder from "./libs/ErrorBuilder";
import Middleware from "./libs/Middleware";
import _ResponseFormater from "./libs/ResponseFormater";
export default Middleware;
export const ErrorBuilder = _ErrorBuilder;
export const ResponseFormater = _ResponseFormater;

21
libs/ErrorBuilder.js Normal file
View file

@ -0,0 +1,21 @@
/**
* Classe permettant la gestion des erreurs personilisées
*/
class ErrorBuilder extends Error {
/**
* @param {Number} errorCode
* @param {Mixed} ...params
*/
constructor(errorCode, ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ErrorBuilder);
}
this.errorCode = errorCode.toFixed(1);
this.date = new Date();
}
}
export default ErrorBuilder;

212
libs/Middleware.js Normal file
View file

@ -0,0 +1,212 @@
import QueryBuilder from "./QueryBuilder";
import ErrorBuilder from "./ErrorBuilder";
class Middleware extends QueryBuilder {
/**
* Initialisation de la classe
* @param {Object} params
* @param {Object} models
*/
constructor(params, models) {
super(params, models);
this.params = params || {};
this.models = models || {};
this.includes = [];
}
/**
* Fonction permettant de créer un nouvel élément
* @param {Object} req
* @param {Function} callback
* @return {Boolean}
*/
createOne(req, callback) {
// On test les droits
if (!this._haveRight(req)) {
callback(new ErrorBuilder(401.1, "You're not allowed"));
return false;
}
// On teste le body
this._checkCreateOneValues(req, (err, value) => {
// Il manque un paramètre dans le body
if (err) {
callback(err, value);
return false;
}
this._insertItem(req, value, callback);
return true;
});
return true;
}
/**
* Fonction permettant de retourner une liste d'items
* @param {Object} req
* @param {Function} callback
*/
getAll(req, callback) {
// On spécifie le type de requête (list ou item)
req.getType = "list";
this._createQuery(req, (err, query, limit) => {
if (err) {
callback(err, query);
return false;
}
this.models[this.params.model]
.findAndCountAll(query)
.then(res => {
if (!res || res.count === 0) {
callback();
return false;
}
const rows = [];
for (let i = 0; i < res.rows.length; i += 1) {
const current = res.rows[i];
let found = false;
for (let j = 0; j < rows.length; j += 1) {
if (rows[j].id === current.id) {
found = true;
rows[j].assign({}, rows[j], current);
break;
}
}
if (!found || rows.length === 0) {
rows.push(current);
}
}
const total = rows.length;
const values = rows.slice(limit.start, limit.start + limit.limit);
callback(null, {
data: this._formatItems(req, values),
...QueryBuilder._createPagination(
req,
total,
limit.limit,
limit.start
)
});
return true;
})
.catch(callback);
return true;
});
}
/**
* Fonction permettant de retourner un item par son Id
* @param {Object} req
* @param {Function} callback
*/
getOne(req, callback) {
// On spécifie le type de requête (list ou item)
req.getType = "item";
this._createQuery(req, (err, query) => {
if (err) {
callback(err, query);
return false;
}
this.models[this.params.model]
.findOne(query)
.then(item => {
if (!item) {
callback(new ErrorBuilder(404, "Item not found"));
return false;
}
let formatedItem = item;
if (this.params.format) {
const formatRules = this.params.format[req.user.role];
if (formatRules) {
formatedItem = this._formatItem(item, formatRules);
}
}
callback(null, formatedItem);
return true;
})
.catch(callback);
return true;
});
}
/**
* Fonction permettant de mettre à jour un item
* @param {Object} req
* @param {Function} callback
* @return {Boolean}
*/
patchOne(req, callback) {
// On test les droits
if (!this._haveRight(req)) {
callback(new ErrorBuilder(401.1, "You're not allowed"));
return false;
}
// On teste les params
const { error, value } = this.params.validate.update.validate(req.body, {
abortEarly: false
});
// Un paramètre n'est pas bon dans les params
if (error) {
callback(new ErrorBuilder(406, error));
return false;
}
this.getOne(req, (err, item) => {
if (err) {
callback(err, item);
return false;
}
item
.update(value)
.then(updated => {
callback(null, updated);
})
.catch(callback);
return true;
});
return true;
}
/**
* Fonction permettant de supprimer un item
* @param {Object} req
* @param {Function} callback
* @return {Boolean}
*/
deleteOne(req, callback) {
// On test les droits
if (!this._haveRight(req)) {
callback(new ErrorBuilder(401.1, "You're not allowed"));
return false;
}
this.getOne(req, (err, item) => {
if (err) {
callback(err, item);
return false;
}
item
.destroy()
.then(() => {
callback(null, {});
})
.catch(callback);
return true;
});
return true;
}
}
export default Middleware;

576
libs/QueryBuilder.js Normal file
View file

@ -0,0 +1,576 @@
import ErrorBuilder from "./ErrorBuilder";
/**
* Classe permettant de gérer les tables simples
*/
class QueryBuilder {
/**
* Initialisation de la classe
* @param {Object} params
* @param {Object} models
*/
constructor(params, models) {
this.params = params;
this.models = models;
this.includes = [];
}
/**
* Méthode permettant de générer le lien vers une ressource
* @param {String} href
* @param {Number} currentPage
* @param {Number} maxPage
* @param {String} page
*/
static _createLink(href, currentPage, maxPage, page) {
if (
(page === "first" && currentPage > 1) ||
(page === "last" && currentPage < maxPage) ||
(page === "prev" && currentPage > 1) ||
(page === "next" && currentPage < maxPage)
) {
let newIndex = 0;
switch (page) {
case "first":
newIndex = 1;
break;
case "last":
newIndex = maxPage;
break;
case "prev":
newIndex = currentPage - 1;
break;
case "next":
newIndex = currentPage + 1;
break;
default:
newIndex = currentPage;
break;
}
return {
href: href.replace(`page=${currentPage}`, `page=${newIndex}`),
methdod: "GET"
};
}
return undefined;
}
/**
* Méthde permettant de générer les élements de pagination
* @param {Object} req
* @param {Number} total
* @param {Number} limit
* @param {Number} skip
* @return {Object}
*/
static _createPagination(req, total, limit, skip) {
const maxPage = Math.ceil(total / limit); // Nombre total de pages
const currentPage = Math.ceil(skip / limit) + 1; // Numéro de la page courante
const href = `${req.protocol}://${req.get("host")}${req.originalUrl}`; // Lien vers la page actuelle
return {
paging: {
first: QueryBuilder._createLink(href, currentPage, maxPage, "first"),
prev: QueryBuilder._createLink(href, currentPage, maxPage, "prev"),
next: QueryBuilder._createLink(href, currentPage, maxPage, "next"),
last: QueryBuilder._createLink(href, currentPage, maxPage, "last")
},
total,
maxPage
};
}
/**
* Fonction permettant de remplacer les attributs du genre $lte pat Op.lte
* @param {Object} obj
* @return {Object}
*/
_replaceKeys(obj) {
const { Op } = this.models.Sequelize;
let newObject = {};
if (Array.isArray(obj)) {
newObject = [];
for (let i = 0; i < obj.length; i += 1) {
const value =
typeof obj[i] === "object" ? this._replaceKeys(obj[i]) : obj[i];
newObject.push(value);
}
} else {
Object.keys(obj).map(key => {
const value =
typeof obj[key] === "object" ? this._replaceKeys(obj[key]) : obj[key];
if (key.indexOf("$") === 0 && key.slice(-1) !== "$") {
switch (key) {
case "$or":
newObject[Op.or] = value;
break;
case "$lte":
newObject[Op.lte] = value;
break;
case "$gte":
newObject[Op.gte] = value;
break;
case "$contains":
newObject[Op.contains] = [value];
break;
case "$in":
newObject[Op.in] = value.split(",");
break;
default:
newObject[key] = [value];
break;
}
} else {
newObject[key] = value;
}
return true;
});
}
return newObject;
}
/**
* Fonction permettant de savoir si un utilisateur a assez de droit pour effectuer une action
* @param {Object} req
* @return {Boolean}
*/
_haveRight(req) {
let allowedRole = "all";
// eslint-disable-next-line default-case
switch (req.method) {
case "POST":
allowedRole = this.params.crud.write;
break;
case "PATCH":
case "PUT":
allowedRole = this.params.crud.edit;
break;
case "GET":
allowedRole = this.params.crud.read;
break;
case "DELETE":
allowedRole = this.params.crud.delete;
break;
}
return allowedRole === "all" || allowedRole.indexOf(req.user.role) !== -1;
}
/**
* Fonction permettant d'ajouter des restructions sur un get
* @param {Object} req
* @return {Object}
*/
_restrictOn(req) {
const where = {};
if (!this.params || !this.params.restrictOn) {
return where;
}
const _overrideWhere = restrictions => {
if (!restrictions) {
return false;
}
for (let i = 0; i < restrictions.length; i += 1) {
const restrict = restrictions[i];
const value =
restrict.type === "raw"
? restrict.value
: req[restrict.type][restrict.value];
if (restrict.roles.indexOf(req.user.role) !== -1) {
where[restrict.field] = value;
}
}
return true;
};
_overrideWhere(this.params.restrictOn[req.getType]);
switch (req.method) {
case "PATCH":
_overrideWhere(this.params.restrictOn.update);
break;
case "DELETE":
_overrideWhere(this.params.restrictOn.delete);
break;
default:
// Do nothing
}
return where;
}
/**
* Fonction permettant de surcharger des valeurs
* @param {Object} req
* @param {String} method
* @return {Object}
*/
_override(req, method) {
let override = {};
const params = this.params.override ? this.params.override[method] : {};
if (!this.params.override) {
return override;
}
if (!params) {
return override;
}
// On surcharge certains paramètres passé en query
if (params.filters) {
Object.keys(params.filters).map(column => {
if (req.query[column]) {
const value = req.query[column];
const query = JSON.parse(
JSON.stringify(params.filters[column]).replace(
new RegExp("_TERM_", "g"),
value
)
);
override = Object.assign(override, this._replaceKeys(query));
}
return true;
});
}
// On rajoute des paramètres à la requête
if (params.params) {
for (let i = 0; i < params.params.length; i += 1) {
const currentParam = params.params[i];
override[currentParam.append] =
req[currentParam.from][currentParam.value];
}
}
return override;
}
/**
* Fonction permettant de charger la liste des relations à inclures lors d'un get
* @param {Object} req
* @param {Array} include
* @return {Mixed}
*/
_setInclusions(req, include) {
const includes = [];
const listOfIncludes = include || this.params.includes;
for (let i = 0; i < listOfIncludes.length; i += 1) {
const current = listOfIncludes[i];
// const include = current.collection;
if (
!current.requiredRole ||
current.requiredRole.indexOf(req.user.role) !== -1
) {
let currentInclude = null;
if (!current.model) {
currentInclude = current.collection;
} else {
currentInclude = {
as: current.collection,
model: current.model,
required: current.required || false
};
// Pour cette inclusion il y a des filtres à appliquer
if (current.restrictOn || current.include) {
currentInclude.where = {};
if (current.include) {
currentInclude.include = this._setInclusions(
req,
current.include
);
}
// On parcours la liste des règles d'inclusion pour ce modèle
if (current.restrictOn) {
for (let j = 0; j < current.restrictOn.length; j += 1) {
const currentRestriction = current.restrictOn[j];
// Cette restriction s'applique à tout le monde (pas de field roles)
// ou alors elle s'applique juste sur une liste de groupes
if (
!currentRestriction.roles ||
currentRestriction.roles.indexOf(req.user.role) !== -1
) {
if (currentRestriction.type === "raw") {
currentInclude.where[currentRestriction.field] =
currentRestriction.value;
}
}
}
}
}
}
includes.push(currentInclude);
}
}
// Mode reccursif
if (include) {
return includes;
}
this.includes = includes;
return true;
}
/**
* Méthode interne permettant de créer un item
* @param {Object} req
* @param {Object} value
* @param {Function} callback
*/
_insertItem(req, value, callback) {
const values = {};
// On converti les 'null' en null (formData par exemple)
Object.keys(value).map(key => {
if (value[key] === "null") {
values[key] = null;
} else {
values[key] = value[key];
}
return true;
});
// Création de l'élément
this.models[this.params.model]
.create(values)
.then(item => {
let createdItem = item;
if (this.params.format) {
const formatRules = req.user
? this.params.format[req.user.role]
: null;
if (formatRules) {
createdItem = this._formatItem(item, formatRules);
}
}
callback(null, createdItem);
})
.catch(err => {
if (err.name === "SequelizeUniqueConstraintError") {
callback(new ErrorBuilder(409, "Duplicate item"));
return false;
}
callback(err);
return false;
});
}
/**
* Fonction permettant de formater un item en fonction du user
* @param {Object} item
* @param {Object} formatRule
* @return {Object}
*/
_formatItem(item, formatRule) {
const formated = {};
Object.keys(formatRule).map(key => {
switch (typeof formatRule[key]) {
case "string":
formated[key] = item[key] || null;
break;
case "object":
if (Array.isArray(item[key])) {
formated[key] = [];
for (let i = 0; i < item[key].length; i += 1) {
formated[key].push(
this._formatItem(item[key][i], formatRule[key])
);
}
} else {
formated[key] = this._formatItem(item[key], formatRule[key]);
}
break;
default:
// Do nothing
}
return true;
});
return formated;
}
/**
* Fonction permettant de formater une liste d'items en fonction du user
* @param {Object} req
* @param {Object} items
* @return {Object}
*/
_formatItems(req, items) {
if (!this.params.format) {
return items;
}
const formatRules = this.params.format[req.user.role];
if (!formatRules) {
return items;
}
const formated = [];
for (let i = 0; i < items.length; i += 1) {
formated.push(this._formatItem(items[i], formatRules));
}
return formated;
}
/**
* Méthode permettant de vérifier que les valeurs reçues dans le body sont valides
* @param {Object} req
* @param {Function} callback
*/
_checkCreateOneValues(req, callback) {
// On regarde s'il faut surcharger les valeurs du body avec des valeurs dérivées (req.user, req.params...)
if (this.params.override && this.params.override.create) {
if (this.params.override.create.body) {
for (let i = 0; i < this.params.override.create.body.length; i += 1) {
const override = this.params.override.create.body[i];
req.body[override.append] = req[override.from][override.value];
}
}
}
// On teste le body
const { error, value } = this.params.validate.create.validate(req.body, {
abortEarly: false
});
if (error) {
callback(new ErrorBuilder(406, error));
} else {
callback(null, value);
}
}
/**
* Fonction permettant de vérifier et de surcharger des valeurs lors d'un get
* @param {Object} req
* @param {Function} callback
* @return {Boolean}
*/
_createQuery(req, callback) {
this._setInclusions(req);
const query = {};
let where = {};
let order = [];
// On test les droits
if (!this._haveRight(req)) {
callback(new ErrorBuilder(401, "You're not allowed"));
return false;
}
// On teste la query (ou les params)
const toValidate = req.getType === "list" ? req.query : req.params;
const { error, value } = this.params.validate[
req.getType
].validate(toValidate, { abortEarly: false });
// On vire, pour le moment la liste des filtres un peu particuliers
let listOfIgnoredFilters = [
"limit",
"page",
"sort",
"order",
this.params.itemId
];
if (this.params.removeKeys && this.params.removeKeys[req.getType]) {
listOfIgnoredFilters = listOfIgnoredFilters.concat(
this.params.removeKeys[req.getType]
);
}
if (
this.params.override &&
this.params.override[req.getType] &&
this.params.override[req.getType].filters
) {
Object.keys(this.params.override[req.getType].filters).map(key => {
listOfIgnoredFilters.push(key);
return true;
});
}
// On rajoute les filtres autorisés
Object.keys(value).map(key => {
if (listOfIgnoredFilters.indexOf(key) === -1) {
where[key] = value[key];
}
return true;
});
// Un paramètre n'est pas bon dans la query
if (error) {
callback(new ErrorBuilder(406, error));
return false;
}
if (req.getType === "list") {
// Aucune pagination n'est passée, on set celle par défaut
if (!value.page || !value.limit) {
value.page = 1;
value.limit = 20;
}
// Un tri est spécifié
if (value.order && value.sort) {
order = [[value.sort, value.order.toUpperCase()]];
}
} else {
// On get un item. on set son id
where.id = value[this.params.itemId];
}
// S'il y a des restrictions (genre un utilisateur n'a le droit de voir que tel ou tel items)
const restrict = this._restrictOn(req);
where = Object.assign(where, restrict);
// On regarde s'il n'y a pas des valeurs à overrider
const override = this._override(req, req.getType);
where = Object.assign(where, override);
// if ( value.page) {
// query.offset = ( value.page - 1 ) * value.limit;
// query.limit = value.limit;
// }
if (order) {
query.order = order;
}
query.distinct = true; // On supprime les id en double (jointure de type hasmany)
query.where = where; // On rajoute des filtres
if (this.includes) {
query.include = this.includes; // On set la liste des modèles à inclure
}
// Hack pour faire un recherche dans les nested de type hasMany
query.subQuery = false;
if (!value.page || !value.limit) {
value.page = 1;
value.limit = 50;
}
callback(null, query, {
start: value.page * value.limit - value.limit,
limit: value.limit
});
return true;
}
}
module.exports = QueryBuilder;

53
libs/ResponseFormater.js Normal file
View file

@ -0,0 +1,53 @@
/**
* Fonction permettant de formater une réponse API
* @param {Object} req
* @param {Object} res
* @param {Functiont} next
* @param {Object} err
* @param {Object} response {code: Integer, res: Array/Object}
*/
const ResponseFormater = (req, res, next, err, response) => {
if (err) {
const code = err.errorCode || 500;
res
.status(Math.trunc(code))
.json({
code,
message: err.message
})
.end();
} else {
switch (req.method) {
case "GET":
res
.status(response ? 200 : 204)
.json(response)
.end();
break;
case "PATCH":
res
.status(200)
.json(response)
.end();
break;
case "DELETE":
res
.status(200)
.json(response)
.end();
break;
case "POST":
res
.status(201)
.json(response)
.end();
break;
default:
next(new Error("Not implemented"));
break;
}
}
};
export default ResponseFormater;

87
package.json Normal file
View file

@ -0,0 +1,87 @@
{
"name": "sequelize-middleware",
"version": "0.1.0",
"description": "Middleware to automate tasks with Sequelize",
"main": "index.js",
"scripts": {
"lint": "./node_modules/.bin/eslint . --fix",
"test": "jest --forceExit --detectOpenHandles --maxWorkers=10",
"coverage": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git@framagit.org:dbroqua/sequelize-middleware.git"
},
"author": "Damien Broqua <contact@darkou.fr>",
"license": "ISC",
"engines": {
"node": "13.x",
"yarn": "1.x"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": [
"./node_modules/.bin/eslint --fix",
"git add"
]
},
"jest": {
"verbose": true,
"roots": [
"<rootDir>",
"<rootDir>/test/"
],
"transform": {
"^.+\\.js$": "babel-jest"
},
"transformIgnorePatterns": [
"<rootDir>/node_modules/(?!(jest-test))"
],
"testEnvironment": "node",
"setupFilesAfterEnv": [
"<rootDir>/test/utils/setup.js"
],
"coverageReporters": [
"html",
"cobertura",
"jest-junit"
],
"reporters": [
"default",
"jest-junit",
[
"./node_modules/jest-html-reporter",
{
"pageTitle": "Test Report",
"outputPath": "reports/html/test-results.html",
"includeFailureMsg": true,
"includeConsoleLog": true
}
]
]
},
"nodemonConfig": {
"ext": "*.js,*.json,*yaml"
},
"dependencies": {},
"devDependencies": {
"babel-eslint": "^10.0.3",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jest": "^23.7.0",
"eslint-plugin-prettier": "^3.1.2",
"husky": "^4.2.1",
"jest": "^25.1.0",
"jest-html-reporter": "^2.8.0",
"lint-staged": "^10.0.7",
"prettier": "^1.19.1",
"sinon": "^8.1.1",
"supertest": "^4.0.2"
}
}

4735
yarn.lock Normal file

File diff suppressed because it is too large Load diff