.
This commit is contained in:
commit
94156bed0e
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.idea
|
||||
*.log
|
||||
node_modules
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
19
config/environments/development.js
Normal file
19
config/environments/development.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
database: {
|
||||
username: 'acana_wms',
|
||||
password: 'a85*MukC45.',
|
||||
database: 'acana_wms',
|
||||
host: 'localhost',
|
||||
dialect: 'mysql'
|
||||
},
|
||||
|
||||
session: {
|
||||
secret_token: process.env.SECRET_TOKEN || "B57J=7B`NQ$y98|~5;hc715bo09^5oz8NR+]n9r~215B91Nd9P%25_N6r!GHcOKp|18y5-73Dr5^@9k7n]5l<-41D1o",
|
||||
token_expires_in: 86400000
|
||||
},
|
||||
|
||||
server: {
|
||||
hostname: process.env.HOSTNAME || '127.0.0.1',
|
||||
port: process.env.PORT || 1337
|
||||
}
|
||||
}
|
||||
19
config/environments/production.js
Normal file
19
config/environments/production.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
database: {
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOSTNAME,
|
||||
dialect: 'mysql',
|
||||
},
|
||||
|
||||
session: {
|
||||
secret_token: process.env.SECRET_TOKEN || "B57J=7B`NQ$y98|~5;hc715bo09^5oz8NR+]n9r~215B91Nd9P%25_N6r!GHcOKp|18y5-73Dr5^@9k7n]5l<-41D1o",
|
||||
token_expires_in: 86400000
|
||||
},
|
||||
|
||||
server: {
|
||||
hostname: process.env.HOSTNAME || '127.0.0.1',
|
||||
port: process.env.PORT || 80
|
||||
}
|
||||
}
|
||||
10
config/index.js
Normal file
10
config/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
const merge = require('lodash/merge');
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
const config = {
|
||||
env: NODE_ENV,
|
||||
debug: NODE_ENV === 'development',
|
||||
};
|
||||
|
||||
module.exports = merge({}, config, require(`./environments/${NODE_ENV}.js`))
|
||||
|
||||
58
core/configurations.js
Normal file
58
core/configurations.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
// Dependencies.
|
||||
const glob = require('glob');
|
||||
const { get, upperFirst, camelCase } = require('lodash');
|
||||
const utils = require('../utils');
|
||||
const models = require('./models');
|
||||
|
||||
module.exports.nested = function() {
|
||||
return Promise.all([
|
||||
|
||||
// Load root configurations.
|
||||
new Promise((resolve, reject) => {
|
||||
glob('./config/index.js', {
|
||||
cwd: this.config.appPath,
|
||||
dot: true
|
||||
}, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
utils.loadConfig.call(this, files).then(resolve).catch(reject);
|
||||
});
|
||||
})
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
module.exports.app = async function() {
|
||||
|
||||
// Set connections.
|
||||
this.connections = {};
|
||||
|
||||
// Set current environment config.
|
||||
this.config.currentEnvironment = this.config.environments[this.config.environment] || {};
|
||||
|
||||
// default settings
|
||||
this.config.port = get(this.config.currentEnvironment, 'server.port') || this.config.port;
|
||||
this.config.host = get(this.config.currentEnvironment, 'server.host') || this.config.host;
|
||||
|
||||
// Set current URL
|
||||
this.config.url = getURLFromSegments({
|
||||
hostname: this.config.host,
|
||||
port: this.config.port
|
||||
});
|
||||
|
||||
// Set models
|
||||
this.models = models.call(this);
|
||||
|
||||
};
|
||||
|
||||
const getURLFromSegments = function ({ hostname, port, ssl = false }) {
|
||||
const protocol = ssl ? 'https' : 'http';
|
||||
const defaultPort = ssl ? 443 : 80;
|
||||
const portString = (port === undefined || parseInt(port) === defaultPort) ? '' : `:${port}`;
|
||||
|
||||
return `${protocol}://${hostname}${portString}`;
|
||||
};
|
||||
91
core/express.js
Normal file
91
core/express.js
Normal file
@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
//const morgan = require('morgan');
|
||||
const bodyParser = require('body-parser');
|
||||
const compress = require('compression');
|
||||
const responseTime = require('response-time');
|
||||
const methodOverride = require('method-override');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const passport = require('passport');
|
||||
|
||||
const router = require('./router');
|
||||
const error = require('../middlewares/error');
|
||||
const access = require('../middlewares/access');
|
||||
|
||||
module.exports = async function () {
|
||||
/**
|
||||
* Express instance
|
||||
* @public
|
||||
*/
|
||||
const app = express();
|
||||
|
||||
// request logging. dev: console | production: file
|
||||
//app.use(morgan(logs));
|
||||
|
||||
// parse body params and attache them to req.body
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
|
||||
// set up the response-time middleware
|
||||
app.use(responseTime());
|
||||
|
||||
// gzip compression
|
||||
app.use(compress());
|
||||
|
||||
// lets you use HTTP verbs such as PUT or DELETE
|
||||
// in places where the client doesn't support it
|
||||
app.use(methodOverride());
|
||||
|
||||
// secure apps by setting various HTTP headers
|
||||
app.use(helmet());
|
||||
|
||||
|
||||
// enable CORS - Cross Origin Resource Sharing
|
||||
app.use(cors({
|
||||
exposeHeaders: [
|
||||
"WWW-Authenticate",
|
||||
"Server-Authorization"
|
||||
],
|
||||
maxAge: 31536000,
|
||||
credentials: true,
|
||||
allowMethods: [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"HEAD"
|
||||
],
|
||||
allowHeaders: [
|
||||
"Content-Type",
|
||||
"Authorization",
|
||||
"X-Frame-Options",
|
||||
"Origin"
|
||||
],
|
||||
}));
|
||||
|
||||
|
||||
// Access validator
|
||||
app.use(passport.initialize());
|
||||
passport.use('jwt', access.jwt.call(this));
|
||||
|
||||
|
||||
// Set routes
|
||||
app.use('/api', await router.call(this));
|
||||
|
||||
// if error is not an instanceOf APIError, convert it.
|
||||
app.use(error.converter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(error.notFound);
|
||||
|
||||
// error handler, send stacktrace only during development
|
||||
app.use(error.handler);
|
||||
|
||||
return app;
|
||||
}
|
||||
26
core/index.js
Normal file
26
core/index.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const { nested, app } = require('./configurations');
|
||||
const express = require('./express');
|
||||
const logger = require('./logger');
|
||||
const modules = require('./modules');
|
||||
const models = require('./models');
|
||||
//const middlewares = require('./middlewares');
|
||||
//const hooks = require('./hooks');
|
||||
//const plugins = require('./plugins');
|
||||
// const admin = require('./admin');
|
||||
//const store = require('./store');
|
||||
|
||||
module.exports = {
|
||||
nestedConfigurations: nested,
|
||||
express,
|
||||
logger,
|
||||
appConfigurations: app,
|
||||
modules,
|
||||
models,
|
||||
//middlewares,
|
||||
//hooks,
|
||||
//plugins,
|
||||
// admin,
|
||||
//store
|
||||
};
|
||||
34
core/logger.js
Normal file
34
core/logger.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.json(),
|
||||
transports: [
|
||||
//
|
||||
// - Write to all logs with level `info` and below to `combined.log`
|
||||
// - Write all logs error (and below) to `error.log`.
|
||||
//
|
||||
new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'combined.log' }),
|
||||
],
|
||||
});
|
||||
|
||||
//
|
||||
// If we're not in production then log to the `console` with the format:
|
||||
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
|
||||
//
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: winston.format.simple(),
|
||||
}));
|
||||
}
|
||||
|
||||
logger.stream = {
|
||||
write: (message) => {
|
||||
logger.info(message.trim());
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = logger;
|
||||
49
core/models.js
Normal file
49
core/models.js
Normal file
@ -0,0 +1,49 @@
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
|
||||
const modulesDir = path.resolve(__dirname + '/../modules/')
|
||||
const basename = path.basename(__dirname);
|
||||
const globOptions = {
|
||||
cwd: modulesDir,
|
||||
nocase: true,
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
}
|
||||
|
||||
module.exports = async function () {
|
||||
const server = this;
|
||||
|
||||
server.log.info('Configurando DB.');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
server.config.database.database,
|
||||
server.config.database.username,
|
||||
server.config.database.password,
|
||||
server.config.database, {
|
||||
dialect: 'mysql',
|
||||
operatorAliases: false
|
||||
}
|
||||
);
|
||||
|
||||
const db = {};
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
glob.sync("**/*.model.js", globOptions)
|
||||
.forEach(function (file) {
|
||||
var model = sequelize.import(file);
|
||||
log.info('Loading "' + model.name + '" model.');
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(function (modelName) {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
server.log.debug(db);
|
||||
|
||||
return db;
|
||||
}
|
||||
58
core/modules.js
Normal file
58
core/modules.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
// Dependencies.
|
||||
const glob = require('glob');
|
||||
const { setWith, merge, get, isObject, isFunction } = require('lodash');
|
||||
|
||||
const optionalPath = path => {
|
||||
return path
|
||||
.replace(/(\.settings|.json|.js)/g, '')
|
||||
.split('/')
|
||||
.slice(1, path.split('/').length - 1)
|
||||
.join('.')
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const aggregatePath = path => {
|
||||
return path
|
||||
.replace(/(\.settings|.json|.js)/g, '')
|
||||
.split('/')
|
||||
.slice(1)
|
||||
.join('.')
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const setConfig = function (ctx, path, type, loader) {
|
||||
const objPath = type === 'optional' ?
|
||||
optionalPath(path) :
|
||||
aggregatePath(path);
|
||||
|
||||
// Load value.
|
||||
const value = loader(path);
|
||||
// Merge doesn't work for none-object value and function.
|
||||
const obj = isObject(value) && !isFunction(value) ? merge(get(ctx, objPath), value) : value;
|
||||
|
||||
// Assignation.
|
||||
return setWith(ctx, objPath, obj, Object);
|
||||
};
|
||||
|
||||
module.exports = function() {
|
||||
return Promise.all([
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
// Load configurations.
|
||||
glob('./modules/*/!(config)/*.*(js|json)', {
|
||||
cwd: this.config.appPath
|
||||
}, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
files.map(p => setConfig(this, p, 'aggregate', this.loadFile));
|
||||
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
|
||||
]);
|
||||
};
|
||||
37
core/router.js
Normal file
37
core/router.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
const modulesDir = path.resolve(__dirname + '/../modules/')
|
||||
const globOptions = {
|
||||
cwd: modulesDir,
|
||||
nocase: true,
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
}
|
||||
|
||||
|
||||
module.exports = async function () {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/_health', (req, res, next) => {
|
||||
res.json({
|
||||
code: 200,
|
||||
message: 'success',
|
||||
description: 'Welcome, this is the API for the application.'
|
||||
});
|
||||
});
|
||||
|
||||
glob.sync("*/config/routes.js", globOptions)
|
||||
.forEach(function (file) {
|
||||
console.log(file);
|
||||
router.use('/v2', require(file));
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
||||
|
||||
29
middlewares/access.js
Normal file
29
middlewares/access.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
|
||||
module.exports.jwt = async function() {
|
||||
const config = this.config;
|
||||
|
||||
const JwtStrategy = require('passport-jwt').Strategy;
|
||||
const { ExtractJwt } = require('passport-jwt');
|
||||
//const User = this.models.User;
|
||||
|
||||
const jwtOptions = {
|
||||
secretOrKey: config.session.secret_token,
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer'),
|
||||
};
|
||||
|
||||
const jwt = async (payload, done) => {
|
||||
this.log.info(payload);
|
||||
try {
|
||||
//const user = await User.findById(payload.sub);
|
||||
//if (user) return done(null, user);
|
||||
return done(null, false);
|
||||
} catch (error) {
|
||||
return done(error, false);
|
||||
}
|
||||
};
|
||||
|
||||
return new JwtStrategy(jwtOptions, jwt);
|
||||
}
|
||||
59
middlewares/error.js
Normal file
59
middlewares/error.js
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const httpStatus = require('http-status');
|
||||
const expressValidation = require('express-validation');
|
||||
const APIError = require('../utils/APIError');
|
||||
|
||||
/**
|
||||
* Error handler. Send stacktrace only during development
|
||||
* @public
|
||||
*/
|
||||
const handler = (err, req, res, next) => {
|
||||
const response = {
|
||||
code: err.status,
|
||||
message: err.message || httpStatus[err.status],
|
||||
errors: err.errors,
|
||||
stack: err.stack,
|
||||
};
|
||||
|
||||
res.status(err.status);
|
||||
res.json(response);
|
||||
};
|
||||
exports.handler = handler;
|
||||
|
||||
/**
|
||||
* If error is not an instanceOf APIError, convert it.
|
||||
* @public
|
||||
*/
|
||||
exports.converter = (err, req, res, next) => {
|
||||
let convertedError = err;
|
||||
|
||||
if (err instanceof expressValidation.ValidationError) {
|
||||
convertedError = new APIError({
|
||||
message: 'Error de validación',
|
||||
errors: err.errors,
|
||||
status: err.status,
|
||||
stack: err.stack,
|
||||
});
|
||||
} else if (!(err instanceof APIError)) {
|
||||
convertedError = new APIError({
|
||||
message: err.message,
|
||||
status: err.status,
|
||||
stack: err.stack,
|
||||
});
|
||||
}
|
||||
|
||||
return handler(convertedError, req, res);
|
||||
};
|
||||
|
||||
/**
|
||||
* Catch 404 and forward to error handler
|
||||
* @public
|
||||
*/
|
||||
exports.notFound = (req, res, next) => {
|
||||
const err = new APIError({
|
||||
message: 'Not found',
|
||||
status: httpStatus.NOT_FOUND,
|
||||
});
|
||||
return handler(err, req, res);
|
||||
};
|
||||
12
modules/post/config/routes.js
Normal file
12
modules/post/config/routes.js
Normal file
@ -0,0 +1,12 @@
|
||||
const routes = require('express').Router();
|
||||
|
||||
routes.use((req, res, next) => {
|
||||
// here we can access the req.params object and make auth checks
|
||||
next();
|
||||
});
|
||||
|
||||
routes.get('/posts', function (req, res) {
|
||||
res.status(200).json({ message: 'Connected!' });
|
||||
});
|
||||
|
||||
module.exports = routes;
|
||||
74
modules/post/controllers/Post.js
Normal file
74
modules/post/controllers/Post.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Post.js controller
|
||||
*
|
||||
* @description: A set of functions called "actions" for managing `Post`.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Retrieve post records.
|
||||
*
|
||||
* @return {Object|Array}
|
||||
*/
|
||||
|
||||
find: async (ctx) => {
|
||||
if (ctx.query._q) {
|
||||
return strapi.services.post.search(ctx.query);
|
||||
} else {
|
||||
return strapi.services.post.fetchAll(ctx.query);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a post record.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
findOne: async (ctx) => {
|
||||
return strapi.services.post.fetch(ctx.params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Count post records.
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
|
||||
count: async (ctx) => {
|
||||
return strapi.services.post.count(ctx.query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a/an post record.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
create: async (ctx) => {
|
||||
return strapi.services.post.add(ctx.request.body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a/an post record.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
update: async (ctx, next) => {
|
||||
return strapi.services.post.edit(ctx.params, ctx.request.body) ;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy a/an post record.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
destroy: async (ctx, next) => {
|
||||
return strapi.services.post.remove(ctx.params);
|
||||
}
|
||||
};
|
||||
55
modules/post/models/Post.js
Normal file
55
modules/post/models/Post.js
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Lifecycle callbacks for the `Post` model.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// Before saving a value.
|
||||
// Fired before an `insert` or `update` query.
|
||||
// beforeSave: async (model, attrs, options) => {},
|
||||
|
||||
// After saving a value.
|
||||
// Fired after an `insert` or `update` query.
|
||||
// afterSave: async (model, response, options) => {},
|
||||
|
||||
// Before fetching a value.
|
||||
// Fired before a `fetch` operation.
|
||||
// beforeFetch: async (model, columns, options) => {},
|
||||
|
||||
// After fetching a value.
|
||||
// Fired after a `fetch` operation.
|
||||
// afterFetch: async (model, response, options) => {},
|
||||
|
||||
// Before fetching all values.
|
||||
// Fired before a `fetchAll` operation.
|
||||
// beforeFetchAll: async (model, columns, options) => {},
|
||||
|
||||
// After fetching all values.
|
||||
// Fired after a `fetchAll` operation.
|
||||
// afterFetchAll: async (model, response, options) => {},
|
||||
|
||||
// Before creating a value.
|
||||
// Fired before an `insert` query.
|
||||
// beforeCreate: async (model, attrs, options) => {},
|
||||
|
||||
// After creating a value.
|
||||
// Fired after an `insert` query.
|
||||
// afterCreate: async (model, attrs, options) => {},
|
||||
|
||||
// Before updating a value.
|
||||
// Fired before an `update` query.
|
||||
// beforeUpdate: async (model, attrs, options) => {},
|
||||
|
||||
// After updating a value.
|
||||
// Fired after an `update` query.
|
||||
// afterUpdate: async (model, attrs, options) => {},
|
||||
|
||||
// Before destroying a value.
|
||||
// Fired before a `delete` query.
|
||||
// beforeDestroy: async (model, attrs, options) => {},
|
||||
|
||||
// After destroying a value.
|
||||
// Fired after a `delete` query.
|
||||
// afterDestroy: async (model, attrs, options) => {}
|
||||
};
|
||||
20
modules/post/models/Post.settings.json
Normal file
20
modules/post/models/Post.settings.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"connection": "default",
|
||||
"collectionName": "posts",
|
||||
"info": {
|
||||
"name": "post",
|
||||
"description": ""
|
||||
},
|
||||
"options": {
|
||||
"increments": true,
|
||||
"timestamps": true,
|
||||
"comment": ""
|
||||
},
|
||||
"attributes": {
|
||||
"Título": {
|
||||
"default": "",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
243
modules/post/services/Post.js
Normal file
243
modules/post/services/Post.js
Normal file
@ -0,0 +1,243 @@
|
||||
/* global Post */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Post.js service
|
||||
*
|
||||
* @description: A set of functions similar to controller's actions to avoid code duplication.
|
||||
*/
|
||||
|
||||
// Public dependencies.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Strapi utilities.
|
||||
//const utils = require('strapi-hook-bookshelf/lib/utils/');
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Promise to fetch all posts.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
fetchAll: (params) => {
|
||||
// Convert `params` object to filters compatible with Bookshelf.
|
||||
const filters = strapi.utils.models.convertParams('post', params);
|
||||
// Select field to populate.
|
||||
const populate = Post.associations
|
||||
.filter(ast => ast.autoPopulate !== false)
|
||||
.map(ast => ast.alias);
|
||||
|
||||
return Post.query(function(qb) {
|
||||
_.forEach(filters.where, (where, key) => {
|
||||
if (_.isArray(where.value) && where.symbol !== 'IN' && where.symbol !== 'NOT IN') {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value])
|
||||
}
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
}
|
||||
});
|
||||
|
||||
if (filters.sort) {
|
||||
qb.orderBy(filters.sort.key, filters.sort.order);
|
||||
}
|
||||
|
||||
qb.offset(filters.start);
|
||||
qb.limit(filters.limit);
|
||||
}).fetchAll({
|
||||
withRelated: filters.populate || populate
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to fetch a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
fetch: (params) => {
|
||||
// Select field to populate.
|
||||
const populate = Post.associations
|
||||
.filter(ast => ast.autoPopulate !== false)
|
||||
.map(ast => ast.alias);
|
||||
|
||||
return Post.forge(_.pick(params, 'id')).fetch({
|
||||
withRelated: populate
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to count a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
count: (params) => {
|
||||
// Convert `params` object to filters compatible with Bookshelf.
|
||||
const filters = strapi.utils.models.convertParams('post', params);
|
||||
|
||||
return Post.query(function(qb) {
|
||||
_.forEach(filters.where, (where, key) => {
|
||||
if (_.isArray(where.value)) {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
|
||||
}
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
}
|
||||
});
|
||||
}).count();
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to add a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
add: async (values) => {
|
||||
// Extract values related to relational data.
|
||||
const relations = _.pick(values, Post.associations.map(ast => ast.alias));
|
||||
const data = _.omit(values, Post.associations.map(ast => ast.alias));
|
||||
|
||||
// Create entry with no-relational data.
|
||||
const entry = await Post.forge(data).save();
|
||||
|
||||
// Create relational data and return the entry.
|
||||
return Post.updateRelations({ id: entry.id , values: relations });
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to edit a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
edit: async (params, values) => {
|
||||
// Extract values related to relational data.
|
||||
const relations = _.pick(values, Post.associations.map(ast => ast.alias));
|
||||
const data = _.omit(values, Post.associations.map(ast => ast.alias));
|
||||
|
||||
// Create entry with no-relational data.
|
||||
const entry = await Post.forge(params).save(data);
|
||||
|
||||
// Create relational data and return the entry.
|
||||
return Post.updateRelations(Object.assign(params, { values: relations }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to remove a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
remove: async (params) => {
|
||||
params.values = {};
|
||||
Post.associations.map(association => {
|
||||
switch (association.nature) {
|
||||
case 'oneWay':
|
||||
case 'oneToOne':
|
||||
case 'manyToOne':
|
||||
case 'oneToManyMorph':
|
||||
params.values[association.alias] = null;
|
||||
break;
|
||||
case 'oneToMany':
|
||||
case 'manyToMany':
|
||||
case 'manyToManyMorph':
|
||||
params.values[association.alias] = [];
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
||||
await Post.updateRelations(params);
|
||||
|
||||
return Post.forge(params).destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to search a/an post.
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
search: async (params) => {
|
||||
// Convert `params` object to filters compatible with Bookshelf.
|
||||
const filters = strapi.utils.models.convertParams('post', params);
|
||||
// Select field to populate.
|
||||
const populate = Post.associations
|
||||
.filter(ast => ast.autoPopulate !== false)
|
||||
.map(ast => ast.alias);
|
||||
|
||||
const associations = Post.associations.map(x => x.alias);
|
||||
const searchText = Object.keys(Post._attributes)
|
||||
.filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute))
|
||||
.filter(attribute => ['string', 'text'].includes(Post._attributes[attribute].type));
|
||||
|
||||
const searchNoText = Object.keys(Post._attributes)
|
||||
.filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute))
|
||||
.filter(attribute => !['string', 'text', 'boolean', 'integer', 'decimal', 'float'].includes(Post._attributes[attribute].type));
|
||||
|
||||
const searchInt = Object.keys(Post._attributes)
|
||||
.filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute))
|
||||
.filter(attribute => ['integer', 'decimal', 'float'].includes(Post._attributes[attribute].type));
|
||||
|
||||
const searchBool = Object.keys(Post._attributes)
|
||||
.filter(attribute => attribute !== Post.primaryKey && !associations.includes(attribute))
|
||||
.filter(attribute => ['boolean'].includes(Post._attributes[attribute].type));
|
||||
|
||||
const query = (params._q || '').replace(/[^a-zA-Z0-9.-\s]+/g, '');
|
||||
|
||||
return Post.query(qb => {
|
||||
// Search in columns which are not text value.
|
||||
searchNoText.forEach(attribute => {
|
||||
qb.orWhereRaw(`LOWER(${attribute}) LIKE '%${_.toLower(query)}%'`);
|
||||
});
|
||||
|
||||
if (!_.isNaN(_.toNumber(query))) {
|
||||
searchInt.forEach(attribute => {
|
||||
qb.orWhereRaw(`${attribute} = ${_.toNumber(query)}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (query === 'true' || query === 'false') {
|
||||
searchBool.forEach(attribute => {
|
||||
qb.orWhereRaw(`${attribute} = ${_.toNumber(query === 'true')}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Search in columns with text using index.
|
||||
switch (Post.client) {
|
||||
case 'mysql':
|
||||
qb.orWhereRaw(`MATCH(${searchText.join(',')}) AGAINST(? IN BOOLEAN MODE)`, `*${query}*`);
|
||||
break;
|
||||
case 'pg': {
|
||||
const searchQuery = searchText.map(attribute =>
|
||||
_.toLower(attribute) === attribute
|
||||
? `to_tsvector(${attribute})`
|
||||
: `to_tsvector('${attribute}')`
|
||||
);
|
||||
|
||||
qb.orWhereRaw(`${searchQuery.join(' || ')} @@ to_tsquery(?)`, query);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.sort) {
|
||||
qb.orderBy(filters.sort.key, filters.sort.order);
|
||||
}
|
||||
|
||||
if (filters.skip) {
|
||||
qb.offset(_.toNumber(filters.skip));
|
||||
}
|
||||
|
||||
if (filters.limit) {
|
||||
qb.limit(_.toNumber(filters.limit));
|
||||
}
|
||||
}).fetchAll({
|
||||
withRelated: populate
|
||||
});
|
||||
}
|
||||
};
|
||||
52
package.json
Normal file
52
package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "lqdvi-api2",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"author": "Rodax Software",
|
||||
"license": "ISC",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development nodemon server.js",
|
||||
"start:prod": "NODE_ENV=production pm2 start server.js -n 'api' -i 0",
|
||||
"lint": "eslint **/*.js --quiet",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@wopr.rodax-software.com:lqdvi/app2-api.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^4.3.0",
|
||||
"nodemon": "^1.18.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^2.6.2",
|
||||
"body-parser": "^1.18.3",
|
||||
"buffer": "^5.2.1",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"events": "^3.0.0",
|
||||
"express": "^4.16.4",
|
||||
"express-validation": "^1.0.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"fs-extra": "^7.0.1",
|
||||
"helmet": "^3.16.0",
|
||||
"http": "^0.0.0",
|
||||
"http-status": "^1.3.2",
|
||||
"lodash": "^4.17.11",
|
||||
"method-override": "^3.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"node-fetch": "^2.3.0",
|
||||
"os": "^0.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"path": "^0.12.7",
|
||||
"pino": "^4.7.1",
|
||||
"response-time": "^2.3.2",
|
||||
"sequelize": "^5.3.5",
|
||||
"vm": "^0.1.0",
|
||||
"winston": "^3.2.1"
|
||||
}
|
||||
}
|
||||
136
server.js
Normal file
136
server.js
Normal file
@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const http = require('http');
|
||||
const { toLower } = require('lodash');
|
||||
const utils = require('./utils');
|
||||
|
||||
const {
|
||||
express,
|
||||
logger,
|
||||
nestedConfigurations,
|
||||
appConfigurations
|
||||
} = require('./core');
|
||||
|
||||
class Server extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.setMaxListeners(100);
|
||||
|
||||
// Logger.
|
||||
this.log = logger;
|
||||
|
||||
// Default configurations.
|
||||
this.config = {
|
||||
launchedAt: Date.now(),
|
||||
appPath: process.cwd(),
|
||||
host: process.env.HOST || process.env.HOSTNAME || 'localhost',
|
||||
port: process.env.PORT || 1337,
|
||||
environment: toLower(process.env.NODE_ENV) || 'development',
|
||||
environments: {},
|
||||
};
|
||||
|
||||
// Bind context functions.
|
||||
this.loadFile = utils.loadFile.bind(this);
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.emit('server:init');
|
||||
await nestedConfigurations.call(this);
|
||||
await appConfigurations.call(this);
|
||||
|
||||
/*await Promise.all([
|
||||
modules.call(this),
|
||||
]).then(results => {
|
||||
this.modules = results[0];
|
||||
});*/
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
|
||||
// Emit starting event.
|
||||
this.emit('server:starting');
|
||||
|
||||
await this.init();
|
||||
|
||||
// Expose `express`.
|
||||
this.app = await express.call(this);
|
||||
|
||||
// Mount the HTTP server.
|
||||
this.server = http.createServer(this.app);
|
||||
await this.enhancer.call(this);
|
||||
|
||||
// Launch server.
|
||||
this.server.listen(this.config.port, async (err) => {
|
||||
|
||||
if (err) {
|
||||
this.log.debug(`⚠️ Server wasn't able to start properly.`);
|
||||
this.log.error(err);
|
||||
return this.stop();
|
||||
}
|
||||
|
||||
this.log.info('Time: ' + new Date());
|
||||
this.log.info('Launched in: ' + (Date.now() - this.config.launchedAt) + ' ms');
|
||||
this.log.info('Environment: ' + this.config.environment);
|
||||
this.log.info('Process PID: ' + process.pid);
|
||||
//this.log.info(`Version: ${this.config.info.strapi} (node v${this.config.info.node})`);
|
||||
this.log.info('To shut down your server, press <CTRL> + C at any time');
|
||||
this.log.info(`⚡️ Server: ${this.config.url}`);
|
||||
|
||||
// Emit started event.
|
||||
this.emit('server:started');
|
||||
});
|
||||
} catch (err) {
|
||||
this.log.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
this.log.error(err);
|
||||
console.log(err);
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async enhancer() {
|
||||
this.connections = {};
|
||||
|
||||
this.server.on('connection', conn => {
|
||||
const key = conn.remoteAddress + ':' + conn.remotePort;
|
||||
this.connections[key] = conn;
|
||||
|
||||
conn.on('close', () => {
|
||||
delete this.connections[key];
|
||||
});
|
||||
});
|
||||
|
||||
this.server.on('error', err => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
this.log.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
this.log.error(`The port ${err.port} is already used by another application.`);
|
||||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.emit('server:stoping');
|
||||
|
||||
// Destroy server and available connections.
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
process.send('stop');
|
||||
// Kill process.
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(() => {
|
||||
const server = new Server();
|
||||
server.start();
|
||||
})();
|
||||
56
utils/APIError.js
Normal file
56
utils/APIError.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const httpStatus = require('http-status');
|
||||
|
||||
/**
|
||||
* @extends Error
|
||||
*/
|
||||
class ExtendableError extends Error {
|
||||
constructor({
|
||||
message,
|
||||
errors,
|
||||
status,
|
||||
isPublic,
|
||||
stack,
|
||||
}) {
|
||||
super(message);
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
this.errors = errors;
|
||||
this.status = status;
|
||||
this.isPublic = isPublic;
|
||||
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
|
||||
this.stack = stack;
|
||||
// Error.captureStackTrace(this, this.constructor.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an API error.
|
||||
* @extends ExtendableError
|
||||
*/
|
||||
class APIError extends ExtendableError {
|
||||
/**
|
||||
* Creates an API error.
|
||||
* @param {string} message - Error message.
|
||||
* @param {number} status - HTTP status code of error.
|
||||
* @param {boolean} isPublic - Whether the message should be visible to user or not.
|
||||
*/
|
||||
constructor({
|
||||
message,
|
||||
errors,
|
||||
stack,
|
||||
status = httpStatus.INTERNAL_SERVER_ERROR,
|
||||
isPublic = false,
|
||||
}) {
|
||||
super({
|
||||
message,
|
||||
errors,
|
||||
status,
|
||||
isPublic,
|
||||
stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APIError;
|
||||
136
utils/index.js
Normal file
136
utils/index.js
Normal file
@ -0,0 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable import/order */
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* eslint-disable prefer-template */
|
||||
// Dependencies.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { map } = require('async'); // eslint-disable-line import/order
|
||||
const { setWith, merge, get, difference, intersection, isObject, isFunction } = require('lodash');
|
||||
const os = require('os');
|
||||
const vm = require('vm');
|
||||
const fetch = require('node-fetch');
|
||||
const Buffer = require('buffer').Buffer;
|
||||
const crypto = require('crypto');
|
||||
|
||||
module.exports = {
|
||||
loadFile: function (url) {
|
||||
// Clear cache.
|
||||
delete require.cache[require.resolve(path.resolve(this.config.appPath, url))];
|
||||
// Require without cache.
|
||||
return require(path.resolve(this.config.appPath, url));
|
||||
},
|
||||
|
||||
setConfig: function (ctx, path, type, loader) {
|
||||
const objPath = type === 'optional' ?
|
||||
this.optionalPath(path) :
|
||||
this.aggregatePath(path);
|
||||
|
||||
// Load value.
|
||||
const value = loader(path);
|
||||
// Merge doesn't work for none-object value and function.
|
||||
const obj = isObject(value) && !isFunction(value) ? merge(get(ctx, objPath), value) : value;
|
||||
|
||||
// Assignation.
|
||||
return setWith(ctx, objPath, obj, Object);
|
||||
},
|
||||
|
||||
optionalPath: path => {
|
||||
return path
|
||||
.replace(/(\.settings|.json|.js)/g, '')
|
||||
.split('/')
|
||||
.slice(1, path.split('/').length - 1)
|
||||
.join('.')
|
||||
.toLowerCase();
|
||||
},
|
||||
|
||||
aggregatePath: path => {
|
||||
return path
|
||||
.replace(/(\.settings|.json|.js)/g, '')
|
||||
.split('/')
|
||||
.slice(1)
|
||||
.join('.')
|
||||
.toLowerCase();
|
||||
},
|
||||
|
||||
loadConfig: function (files, shouldBeAggregated = false) {
|
||||
const aggregate = files.filter(p => {
|
||||
if (shouldBeAggregated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (intersection(p.split('/').map(p => p.replace('.json', '')), ['environments', 'database', 'security', 'request', 'response', 'server']).length === 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
p.indexOf('config/functions') !== -1 ||
|
||||
p.indexOf('config/policies') !== -1 ||
|
||||
p.indexOf('config/locales') !== -1 ||
|
||||
p.indexOf('config/hook') !== -1 ||
|
||||
p.indexOf('config/middleware') !== -1 ||
|
||||
p.indexOf('config/language') !== -1 ||
|
||||
p.indexOf('config/queries') !== -1 ||
|
||||
p.indexOf('config/layout') !== -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const optional = difference(files, aggregate);
|
||||
|
||||
return Promise.all([
|
||||
new Promise((resolve, reject) => {
|
||||
map(aggregate, p =>
|
||||
module.exports.setConfig(this, p, 'aggregate', this.loadFile)
|
||||
);
|
||||
|
||||
resolve();
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
map(optional, p =>
|
||||
module.exports.setConfig(this, p, 'optional', this.loadFile)
|
||||
);
|
||||
|
||||
resolve();
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
/*usage: async function () {
|
||||
try {
|
||||
if (this.config.uuid) {
|
||||
const publicKey = fs.readFileSync(path.resolve(__dirname, 'resources', 'key.pub'));
|
||||
const options = {
|
||||
timeout: 1500
|
||||
};
|
||||
|
||||
const [usage, signedHash, required] = await Promise.all([
|
||||
fetch('https://strapi.io/assets/images/usage.gif', options),
|
||||
fetch('https://strapi.io/hash.txt', options),
|
||||
fetch('https://strapi.io/required.txt', options)
|
||||
]).catch(err => {});
|
||||
|
||||
if (usage.status === 200 && signedHash.status === 200) {
|
||||
const code = Buffer.from(await usage.text(), 'base64').toString();
|
||||
const hash = crypto.createHash('sha512').update(code).digest('hex');
|
||||
const dependencies = Buffer.from(await required.text(), 'base64').toString();
|
||||
|
||||
const verifier = crypto.createVerify('RSA-SHA256').update(hash);
|
||||
|
||||
if (verifier.verify(publicKey, await signedHash.text(), 'hex')) {
|
||||
return new Promise(resolve => {
|
||||
vm.runInNewContext(code)(this.config.uuid, exposer(dependencies), resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent.
|
||||
}
|
||||
},*/
|
||||
|
||||
};
|
||||
509
utils/models.js
Normal file
509
utils/models.js
Normal file
@ -0,0 +1,509 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
// Node.js core
|
||||
const path = require('path');
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
|
||||
const isNumeric = (value) => {
|
||||
return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
|
||||
};
|
||||
|
||||
/* eslint-disable prefer-template */
|
||||
/*
|
||||
* Set of utils for models
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Initialize to prevent some mistakes
|
||||
*/
|
||||
|
||||
initialize: cb => {
|
||||
cb();
|
||||
},
|
||||
|
||||
/**
|
||||
* Find primary key per ORM
|
||||
*/
|
||||
|
||||
getPK: function (collectionIdentity, collection, models) {
|
||||
if (_.isString(collectionIdentity)) {
|
||||
const ORM = this.getORM(collectionIdentity);
|
||||
try {
|
||||
const GraphQLFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));
|
||||
|
||||
if (!_.isUndefined(GraphQLFunctions)) {
|
||||
return GraphQLFunctions.getPK(collectionIdentity, collection, models || strapi.models);
|
||||
}
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the value based on the primary key
|
||||
*/
|
||||
|
||||
getValuePrimaryKey: (value, defaultKey) => {
|
||||
return value[defaultKey] || value.id || value._id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find primary key per ORM
|
||||
*/
|
||||
|
||||
getCount: function (collectionIdentity) {
|
||||
if (_.isString(collectionIdentity)) {
|
||||
const ORM = this.getORM(collectionIdentity);
|
||||
|
||||
try {
|
||||
const ORMFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));
|
||||
|
||||
if (!_.isUndefined(ORMFunctions)) {
|
||||
return ORMFunctions.getCount(collectionIdentity);
|
||||
}
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find relation nature with verbose
|
||||
*/
|
||||
|
||||
getNature: (association, key, models, currentModelName) => {
|
||||
try {
|
||||
const types = {
|
||||
current: '',
|
||||
other: ''
|
||||
};
|
||||
|
||||
if (_.isUndefined(models)) {
|
||||
models = association.plugin ? strapi.plugins[association.plugin].models : strapi.models;
|
||||
}
|
||||
|
||||
if ((association.hasOwnProperty('collection') && association.collection === '*') || (association.hasOwnProperty('model') && association.model === '*')) {
|
||||
if (association.model) {
|
||||
types.current = 'morphToD';
|
||||
} else {
|
||||
types.current = 'morphTo';
|
||||
}
|
||||
|
||||
const flattenedPluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||
Object.keys(strapi.plugins[current].models).forEach((model) => {
|
||||
acc[`${current}_${model}`] = strapi.plugins[current].models[model];
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const allModels = _.merge({}, strapi.models, flattenedPluginsModels);
|
||||
|
||||
// We have to find if they are a model linked to this key
|
||||
_.forIn(allModels, model => {
|
||||
_.forIn(model.attributes, attribute => {
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.model === currentModelName) {
|
||||
if (attribute.hasOwnProperty('collection')) {
|
||||
types.other = 'collection';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
} else if (attribute.hasOwnProperty('model')) {
|
||||
types.other = 'model';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
|
||||
const relatedAttribute = models[association.collection].attributes[association.via];
|
||||
|
||||
if (!relatedAttribute) {
|
||||
throw new Error(`The attribute \`${association.via}\` is missing in the model ${_.upperFirst(association.collection)} ${association.plugin ? '(plugin - ' + association.plugin + ')' : '' }`);
|
||||
}
|
||||
|
||||
types.current = 'collection';
|
||||
|
||||
if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && relatedAttribute.hasOwnProperty('via')) {
|
||||
types.other = 'collection';
|
||||
} else if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.collection !== '*' && !relatedAttribute.hasOwnProperty('via')) {
|
||||
types.other = 'collectionD';
|
||||
} else if (relatedAttribute.hasOwnProperty('model') && relatedAttribute.model !== '*') {
|
||||
types.other = 'model';
|
||||
} else if (relatedAttribute.hasOwnProperty('collection') || relatedAttribute.hasOwnProperty('model')) {
|
||||
types.other = 'morphTo';
|
||||
}
|
||||
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
|
||||
types.current = 'modelD';
|
||||
|
||||
// We have to find if they are a model linked to this key
|
||||
const model = models[association.model];
|
||||
const attribute = model.attributes[association.via];
|
||||
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection') && attribute.collection !== '*') {
|
||||
types.other = 'collection';
|
||||
} else if (attribute.hasOwnProperty('model') && attribute.model !== '*') {
|
||||
types.other = 'model';
|
||||
} else if (attribute.hasOwnProperty('collection') || attribute.hasOwnProperty('model')) {
|
||||
types.other = 'morphTo';
|
||||
}
|
||||
} else if (association.hasOwnProperty('model')) {
|
||||
types.current = 'model';
|
||||
|
||||
// We have to find if they are a model linked to this key
|
||||
_.forIn(models, model => {
|
||||
_.forIn(model.attributes, attribute => {
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key) {
|
||||
if (attribute.hasOwnProperty('collection')) {
|
||||
types.other = 'collection';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
} else if (attribute.hasOwnProperty('model')) {
|
||||
types.other = 'modelD';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (association.hasOwnProperty('collection')) {
|
||||
types.current = 'collectionD';
|
||||
|
||||
// We have to find if they are a model linked to this key
|
||||
_.forIn(models, model => {
|
||||
_.forIn(model.attributes, attribute => {
|
||||
if (attribute.hasOwnProperty('via') && attribute.via === key) {
|
||||
if (attribute.hasOwnProperty('collection')) {
|
||||
types.other = 'collection';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
} else if (attribute.hasOwnProperty('model')) {
|
||||
types.other = 'modelD';
|
||||
|
||||
// Break loop
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (types.current === 'collection' && types.other === 'morphTo') {
|
||||
return {
|
||||
nature: 'manyToManyMorph',
|
||||
verbose: 'morphMany'
|
||||
};
|
||||
} else if (types.current === 'collection' && types.other === 'morphToD') {
|
||||
return {
|
||||
nature: 'manyToOneMorph',
|
||||
verbose: 'morphMany'
|
||||
};
|
||||
} else if (types.current === 'modelD' && types.other === 'morphTo') {
|
||||
return {
|
||||
nature: 'oneToManyMorph',
|
||||
verbose: 'morphOne'
|
||||
};
|
||||
} else if (types.current === 'modelD' && types.other === 'morphToD') {
|
||||
return {
|
||||
nature: 'oneToOneMorph',
|
||||
verbose: 'morphOne'
|
||||
};
|
||||
} else if (types.current === 'morphToD' && types.other === 'collection') {
|
||||
return {
|
||||
nature: 'oneMorphToMany',
|
||||
verbose: 'belongsToMorph'
|
||||
};
|
||||
} else if (types.current === 'morphToD' && types.other === 'model') {
|
||||
return {
|
||||
nature: 'oneMorphToOne',
|
||||
verbose: 'belongsToMorph'
|
||||
};
|
||||
} else if (types.current === 'morphTo' && (types.other === 'model' || association.hasOwnProperty('model'))) {
|
||||
return {
|
||||
nature: 'manyMorphToOne',
|
||||
verbose: 'belongsToManyMorph'
|
||||
};
|
||||
} else if (types.current === 'morphTo' && (types.other === 'collection' || association.hasOwnProperty('collection'))) {
|
||||
return {
|
||||
nature: 'manyMorphToMany',
|
||||
verbose: 'belongsToManyMorph'
|
||||
};
|
||||
} else if (types.current === 'modelD' && types.other === 'model') {
|
||||
return {
|
||||
nature: 'oneToOne',
|
||||
verbose: 'belongsTo'
|
||||
};
|
||||
} else if (types.current === 'model' && types.other === 'modelD') {
|
||||
return {
|
||||
nature: 'oneToOne',
|
||||
verbose: 'hasOne'
|
||||
};
|
||||
} else if ((types.current === 'model' || types.current === 'modelD') && types.other === 'collection') {
|
||||
return {
|
||||
nature: 'manyToOne',
|
||||
verbose: 'belongsTo'
|
||||
};
|
||||
} else if (types.current === 'modelD' && types.other === 'collection') {
|
||||
return {
|
||||
nature: 'oneToMany',
|
||||
verbose: 'hasMany'
|
||||
};
|
||||
} else if (types.current === 'collection' && types.other === 'model') {
|
||||
return {
|
||||
nature: 'oneToMany',
|
||||
verbose: 'hasMany'
|
||||
};
|
||||
} else if (types.current === 'collection' && types.other === 'collection') {
|
||||
return {
|
||||
nature: 'manyToMany',
|
||||
verbose: 'belongsToMany'
|
||||
};
|
||||
} else if (types.current === 'collectionD' && types.other === 'collection' || types.current === 'collection' && types.other === 'collectionD') {
|
||||
return {
|
||||
nature: 'manyToMany',
|
||||
verbose: 'belongsToMany'
|
||||
};
|
||||
} else if (types.current === 'collectionD' && types.other === '') {
|
||||
return {
|
||||
nature: 'manyWay',
|
||||
verbose: 'belongsToMany'
|
||||
};
|
||||
} else if (types.current === 'model' && types.other === '') {
|
||||
return {
|
||||
nature: 'oneWay',
|
||||
verbose: 'belongsTo'
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
strapi.log.error(`Something went wrong in the model \`${_.upperFirst(currentModelName)}\` with the attribute \`${key}\``);
|
||||
strapi.log.error(e);
|
||||
strapi.stop();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return ORM used for this collection.
|
||||
*/
|
||||
|
||||
getORM: collectionIdentity => {
|
||||
return _.get(strapi.models, collectionIdentity.toLowerCase() + '.orm');
|
||||
},
|
||||
|
||||
/**
|
||||
* Define associations key to models
|
||||
*/
|
||||
|
||||
defineAssociations: function (model, definition, association, key) {
|
||||
try {
|
||||
// Initialize associations object
|
||||
if (definition.associations === undefined) {
|
||||
definition.associations = [];
|
||||
}
|
||||
|
||||
// Exclude non-relational attribute
|
||||
if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Get relation nature
|
||||
let details;
|
||||
const globalName = association.model || association.collection || '';
|
||||
const infos = this.getNature(association, key, undefined, model.toLowerCase());
|
||||
|
||||
if (globalName !== '*') {
|
||||
details = association.plugin ?
|
||||
_.get(strapi.plugins, `${association.plugin}.models.${globalName}.attributes.${association.via}`, {}):
|
||||
_.get(strapi.models, `${globalName}.attributes.${association.via}`, {});
|
||||
}
|
||||
|
||||
// Build associations object
|
||||
if (association.hasOwnProperty('collection') && association.collection !== '*') {
|
||||
definition.associations.push({
|
||||
alias: key,
|
||||
type: 'collection',
|
||||
collection: association.collection,
|
||||
via: association.via || undefined,
|
||||
nature: infos.nature,
|
||||
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||
dominant: details.dominant !== true,
|
||||
plugin: association.plugin || undefined,
|
||||
filter: details.filter,
|
||||
});
|
||||
} else if (association.hasOwnProperty('model') && association.model !== '*') {
|
||||
definition.associations.push({
|
||||
alias: key,
|
||||
type: 'model',
|
||||
model: association.model,
|
||||
via: association.via || undefined,
|
||||
nature: infos.nature,
|
||||
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||
dominant: details.dominant !== true,
|
||||
plugin: association.plugin || undefined,
|
||||
filter: details.filter,
|
||||
});
|
||||
} else if (association.hasOwnProperty('collection') || association.hasOwnProperty('model')) {
|
||||
const pluginsModels = Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||
Object.keys(strapi.plugins[current].models).forEach((entity) => {
|
||||
Object.keys(strapi.plugins[current].models[entity].attributes).forEach((attribute) => {
|
||||
const attr = strapi.plugins[current].models[entity].attributes[attribute];
|
||||
|
||||
if (
|
||||
(attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
|
||||
strapi.plugins[current].models[entity].globalId !== definition.globalId
|
||||
) {
|
||||
acc.push(strapi.plugins[current].models[entity].globalId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const appModels = Object.keys(strapi.models).reduce((acc, entity) => {
|
||||
Object.keys(strapi.models[entity].attributes).forEach((attribute) => {
|
||||
const attr = strapi.models[entity].attributes[attribute];
|
||||
|
||||
if (
|
||||
(attr.collection || attr.model || '').toLowerCase() === model.toLowerCase() &&
|
||||
strapi.models[entity].globalId !== definition.globalId
|
||||
) {
|
||||
acc.push(strapi.models[entity].globalId);
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const models = _.uniq(appModels.concat(pluginsModels));
|
||||
|
||||
definition.associations.push({
|
||||
alias: key,
|
||||
type: association.model ? 'model' : 'collection',
|
||||
related: models,
|
||||
nature: infos.nature,
|
||||
autoPopulate: _.get(association, 'autoPopulate', true),
|
||||
filter: association.filter,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
strapi.log.error(`Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``);
|
||||
strapi.log.error(e);
|
||||
strapi.stop();
|
||||
}
|
||||
},
|
||||
|
||||
getVia: (attribute, association) => {
|
||||
return _.findKey(strapi.models[association.model || association.collection].attributes, {via: attribute});
|
||||
},
|
||||
|
||||
convertParams: (entity, params) => {
|
||||
if (!entity) {
|
||||
throw new Error('You can\'t call the convert params method without passing the model\'s name as a first argument.');
|
||||
}
|
||||
|
||||
// Remove the source params (that can be sent from the ctm plugin) since it is not a filter
|
||||
if (params.source) {
|
||||
delete params.source;
|
||||
}
|
||||
|
||||
const model = entity.toLowerCase();
|
||||
|
||||
const models = _.assign(_.clone(strapi.models), Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||
_.assign(acc, _.get(strapi.plugins[current], ['models'], {}));
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
if (!models.hasOwnProperty(model)) {
|
||||
return this.log.error(`The model ${model} can't be found.`);
|
||||
}
|
||||
|
||||
const client = models[model].client;
|
||||
const connector = models[model].orm;
|
||||
|
||||
if (!connector) {
|
||||
throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
|
||||
}
|
||||
|
||||
const convertor = strapi.hook[connector].load().getQueryParams;
|
||||
const convertParams = {
|
||||
where: {},
|
||||
sort: '',
|
||||
start: 0,
|
||||
limit: 100
|
||||
};
|
||||
|
||||
_.forEach(params, (value, key) => {
|
||||
let result;
|
||||
let formattedValue;
|
||||
let modelAttributes = models[model]['attributes'];
|
||||
let fieldType;
|
||||
// Get the field type to later check if it's a string before number conversion
|
||||
if (modelAttributes[key]) {
|
||||
fieldType = modelAttributes[key]['type'];
|
||||
} else {
|
||||
// Remove the filter keyword at the end
|
||||
let splitKey = key.split('_').slice(0,-1);
|
||||
splitKey = splitKey.join('_');
|
||||
if (modelAttributes[splitKey]) {
|
||||
fieldType = modelAttributes[splitKey]['type'];
|
||||
}
|
||||
}
|
||||
// Check if the value is a valid candidate to be converted to a number value
|
||||
if (fieldType !== 'string') {
|
||||
formattedValue = isNumeric(value)
|
||||
? _.toNumber(value)
|
||||
: value;
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
if (_.includes(['_start', '_limit', '_populate'], key)) {
|
||||
result = convertor(formattedValue, key);
|
||||
} else if (key === '_sort') {
|
||||
const [attr, order = 'ASC'] = formattedValue.split(':');
|
||||
result = convertor(order, key, attr);
|
||||
} else {
|
||||
const suffix = key.split('_');
|
||||
// Mysql stores boolean as 1 or 0
|
||||
if (client === 'mysql' && _.get(models, [model, 'attributes', suffix, 'type']) === 'boolean') {
|
||||
formattedValue = value.toString() === 'true' ? '1' : '0';
|
||||
}
|
||||
|
||||
let type;
|
||||
|
||||
if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in', 'nin'], _.last(suffix))) {
|
||||
type = `_${_.last(suffix)}`;
|
||||
key = _.dropRight(suffix).join('_');
|
||||
} else {
|
||||
type = '=';
|
||||
}
|
||||
|
||||
result = convertor(formattedValue, type, key);
|
||||
}
|
||||
|
||||
_.set(convertParams, result.key, result.value);
|
||||
});
|
||||
|
||||
return convertParams;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user