From 81d214b50a0dd4ec40eb43354a70dc0ab5d320f5 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 15 Apr 2019 17:58:58 +0200 Subject: [PATCH] .. --- .gitignore | 1 + config/environments/development.js | 8 +- core/configurations.js | 58 ------ core/express.js | 120 ++++++------ core/index.js | 12 +- core/models.js | 58 +++--- core/modules.js | 58 ------ core/router.js | 6 +- middlewares/access.js | 45 +++-- modules/blog/blog.routes.js | 16 ++ modules/blog/category.model.js | 36 ++++ modules/blog/post-category.model.js | 20 ++ modules/blog/post.controller.js | 76 ++++++++ modules/blog/post.model.js | 51 +++++ modules/blog/post.service.js | 252 +++++++++++++++++++++++++ modules/post/config/routes.js | 12 -- modules/post/controllers/Post.js | 74 -------- modules/post/models/Post.js | 55 ------ modules/post/models/Post.settings.json | 20 -- modules/post/services/Post.js | 243 ------------------------ package.json | 2 +- server.js | 188 +++++++----------- yarn.lock | 10 +- 23 files changed, 643 insertions(+), 778 deletions(-) delete mode 100644 core/configurations.js delete mode 100644 core/modules.js create mode 100644 modules/blog/blog.routes.js create mode 100755 modules/blog/category.model.js create mode 100755 modules/blog/post-category.model.js create mode 100644 modules/blog/post.controller.js create mode 100644 modules/blog/post.model.js create mode 100644 modules/blog/post.service.js delete mode 100644 modules/post/config/routes.js delete mode 100644 modules/post/controllers/Post.js delete mode 100644 modules/post/models/Post.js delete mode 100644 modules/post/models/Post.settings.json delete mode 100644 modules/post/services/Post.js diff --git a/.gitignore b/.gitignore index 793bc41..d070469 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .DS_Store npm-debug.log package-lock.json +yarn.lock diff --git a/config/environments/development.js b/config/environments/development.js index ab30d83..f9426e1 100644 --- a/config/environments/development.js +++ b/config/environments/development.js @@ -1,10 +1,10 @@ module.exports = { database: { - username: 'acana_wms', - password: 'a85*MukC45.', - database: 'acana_wms', + username: 'lqdvi', + password: 'Z286y386*a', + database: 'lqdvi_v2', host: 'localhost', - dialect: 'mysql' + dialect: 'mysql', }, session: { diff --git a/core/configurations.js b/core/configurations.js deleted file mode 100644 index aa234fa..0000000 --- a/core/configurations.js +++ /dev/null @@ -1,58 +0,0 @@ -'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}`; -}; diff --git a/core/express.js b/core/express.js index 25d45d8..a4ef98e 100644 --- a/core/express.js +++ b/core/express.js @@ -14,78 +14,76 @@ const router = require('./router'); const error = require('../middlewares/error'); const access = require('../middlewares/access'); -module.exports = async function () { - /** - * Express instance - * @public - */ - const app = express(); +/** + * Express instance + * @public + */ +const app = express(); - // request logging. dev: console | production: file - //app.use(morgan(logs)); +// 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 - })); +// 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()); +// set up the response-time middleware +app.use(responseTime()); - // gzip compression - app.use(compress()); +// 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()); +// 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()); +// 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)); +// 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" + ], +})); - // Set routes - app.use('/api', await router.call(this)); +// Access validator +app.use(passport.initialize()); +passport.use('jwt', access.jwt); - // if error is not an instanceOf APIError, convert it. - app.use(error.converter); - // catch 404 and forward to error handler - app.use(error.notFound); +// Set routes +app.use('/api', router()); - // error handler, send stacktrace only during development - app.use(error.handler); +// if error is not an instanceOf APIError, convert it. +app.use(error.converter); - return app; -} \ No newline at end of file +// catch 404 and forward to error handler +app.use(error.notFound); + +// error handler, send stacktrace only during development +app.use(error.handler); + +module.exports = app; \ No newline at end of file diff --git a/core/index.js b/core/index.js index 4341893..16d59d9 100644 --- a/core/index.js +++ b/core/index.js @@ -1,10 +1,12 @@ 'use strict'; -const { nested, app } = require('./configurations'); +//const { nested, app } = require('./configurations'); const express = require('./express'); const logger = require('./logger'); -const modules = require('./modules'); const models = require('./models'); + +//const modules = require('./modules'); + //const middlewares = require('./middlewares'); //const hooks = require('./hooks'); //const plugins = require('./plugins'); @@ -12,11 +14,11 @@ const models = require('./models'); //const store = require('./store'); module.exports = { - nestedConfigurations: nested, + //nestedConfigurations: nested, express, logger, - appConfigurations: app, - modules, + //appConfigurations: app, + //modules, models, //middlewares, //hooks, diff --git a/core/models.js b/core/models.js index 4d724be..fa5a24e 100644 --- a/core/models.js +++ b/core/models.js @@ -2,6 +2,10 @@ const glob = require('glob'); const path = require('path'); const Sequelize = require('sequelize'); +const config = require('../config'); +const log = require('./logger'); + + const modulesDir = path.resolve(__dirname + '/../modules/') const basename = path.basename(__dirname); const globOptions = { @@ -11,39 +15,33 @@ const globOptions = { absolute: true, } -module.exports = async function () { - const server = this; +log.info('Configurando DB.'); - server.log.info('Configurando DB.'); +const sequelize = new Sequelize( + config.database.database, + config.database.username, + config.database.password, + config.database, { + dialect: 'mysql', + operatorAliases: false + } +); - 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; - 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); - } +glob.sync("**/*.model.js", globOptions) + .forEach(function (file) { + var model = sequelize.import(file); + log.info('Loading "' + model.name + '" model.'); + db[model.name] = model; }); - server.log.debug(db); +Object.keys(db).forEach(function (modelName) { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); - return db; -} \ No newline at end of file +module.exports = db; \ No newline at end of file diff --git a/core/modules.js b/core/modules.js deleted file mode 100644 index 0c891dd..0000000 --- a/core/modules.js +++ /dev/null @@ -1,58 +0,0 @@ -'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(); - }); - }), - - ]); -}; \ No newline at end of file diff --git a/core/router.js b/core/router.js index 02524e2..ec20d46 100644 --- a/core/router.js +++ b/core/router.js @@ -12,8 +12,7 @@ const globOptions = { absolute: true, } - -module.exports = async function () { +module.exports = function () { const router = express.Router(); router.get('/_health', (req, res, next) => { @@ -24,9 +23,8 @@ module.exports = async function () { }); }); - glob.sync("*/config/routes.js", globOptions) + glob.sync("**/*.routes.js", globOptions) .forEach(function (file) { - console.log(file); router.use('/v2', require(file)); }); diff --git a/middlewares/access.js b/middlewares/access.js index 059a8f1..ffd1410 100644 --- a/middlewares/access.js +++ b/middlewares/access.js @@ -1,29 +1,26 @@ 'use strict'; +const config = require('../config'); +const { logger } = require('../core'); + +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'), +}; -module.exports.jwt = async function() { - const config = this.config; +const jwt = async (payload, done) => { + logger.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); + } +}; - 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); -} \ No newline at end of file +module.exports.jwt = new JwtStrategy(jwtOptions, jwt); \ No newline at end of file diff --git a/modules/blog/blog.routes.js b/modules/blog/blog.routes.js new file mode 100644 index 0000000..91af2ee --- /dev/null +++ b/modules/blog/blog.routes.js @@ -0,0 +1,16 @@ +const routes = require('express').Router(); +const models = require('../../core/models'); +const postService = require('./post.service')(models.Post); +const postController = require('./post.controller')(postService); + +routes.use((req, res, next) => { + // here we can access the req.params object and make auth checks + next(); +}); + +routes.get('/posts', function (req, res) { + data = postController.find(req) + res.status(200).json(data); +}); + +module.exports = routes; \ No newline at end of file diff --git a/modules/blog/category.model.js b/modules/blog/category.model.js new file mode 100755 index 0000000..bb01518 --- /dev/null +++ b/modules/blog/category.model.js @@ -0,0 +1,36 @@ +module.exports = function (sequelize, DataTypes) { + const Category = sequelize.define('Category', { + id: { + type: DataTypes.INTEGER, + primaryKey: true + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + sort: { + // Se cambia el nombre del campo de 'order' a 'sort' porque 'order' es una palabra reservada SQL y da problemas al generar las consultas SQL. + field: 'order', // <- Chapuza!! El nombre del campo en MySQL es una palabra reservada en SQL. + type: DataTypes.INTEGER, + defaultValue: 0, + allowNull: false, + } + }, { + tableName: 'category', + freezeTableName: true, + timestamps: false + }); + + Category.beforeCreate((category) => { + category.dataValues.id = Math.floor(Math.random() * (999 - 8)) + 8; + }) + + Category.associate = function (models) { + Category.Posts = Category.belongsToMany(models.Post, { + through: models.PostCategory, + foreignKey: 'categoryId' + }); + }; + + return Category; +}; \ No newline at end of file diff --git a/modules/blog/post-category.model.js b/modules/blog/post-category.model.js new file mode 100755 index 0000000..9e28f0a --- /dev/null +++ b/modules/blog/post-category.model.js @@ -0,0 +1,20 @@ +module.exports = function (sequelize, DataTypes) { + const PostCategory = sequelize.define('PostCategory', { + postId: { + type: DataTypes.UUID, + primaryKey: true, + foreignKey: true + }, + categoryId: { + type: DataTypes.INTEGER, + primaryKey: true, + foreignKey: true + } + }, { + tableName: 'post-category', + freezeTableName: true, + timestamps: false + }); + + return PostCategory; +}; \ No newline at end of file diff --git a/modules/blog/post.controller.js b/modules/blog/post.controller.js new file mode 100644 index 0000000..354dc9c --- /dev/null +++ b/modules/blog/post.controller.js @@ -0,0 +1,76 @@ +'use strict'; + +/** + * Post.js controller + * + * @description: A set of functions called "actions" for managing `Post`. + */ + +module.exports = function (modelService) { + return { + + /** + * Retrieve post records. + * + * @return {Object|Array} + */ + + find: async (ctx) => { + if (ctx.query._q) { + return modelService.search(ctx.query); + } else { + return modelService.fetchAll(ctx.query); + } + }, + + /** + * Retrieve a post record. + * + * @return {Object} + */ + + findOne: async (ctx) => { + return modelService.fetch(ctx.params); + }, + + /** + * Count post records. + * + * @return {Number} + */ + + count: async (ctx) => { + return modelService.count(ctx.query); + }, + + /** + * Create a/an post record. + * + * @return {Object} + */ + + create: async (ctx) => { + return modelService.add(ctx.request.body); + }, + + /** + * Update a/an post record. + * + * @return {Object} + */ + + update: async (ctx, next) => { + return modelService.edit(ctx.params, ctx.request.body); + }, + + /** + * Destroy a/an post record. + * + * @return {Object} + */ + + destroy: async (ctx, next) => { + return modelService.remove(ctx.params); + } + }; +} \ No newline at end of file diff --git a/modules/blog/post.model.js b/modules/blog/post.model.js new file mode 100644 index 0000000..0999271 --- /dev/null +++ b/modules/blog/post.model.js @@ -0,0 +1,51 @@ +'use strict'; + +module.exports = function (sequelize, DataTypes) { + const Post = sequelize.define('Post', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + /*userId: { + type: DataTypes.UUID, + allowNull: true, + primaryKey: false, + unique: false, + foreignKey: true + },*/ + date: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + image: { + type: DataTypes.STRING, + defaultValue: "" + }, + title: { + type: DataTypes.STRING, + allowNull: false + }, + content: { + type: DataTypes.TEXT, + allowNull: false + }, + }, { + tableName: 'post', + freezeTableName: true, + timestamps: true, + updatedAt: false, + }); + + Post.associate = function (models) { + Post.Categories = Post.belongsToMany(models.Category, { + through: models.PostCategory, + foreignKey: 'postId' + }); + //Post.Comments = Post.hasMany(models.PostComment, { foreignKey: 'postId' }); + //Post.Reactions = Post.hasMany(models.PostReaction, { foreignKey: 'postId' }); + //Post.User = Post.belongsTo(models.User, { foreignKey: 'userId' }); + }; + return Post; +}; \ No newline at end of file diff --git a/modules/blog/post.service.js b/modules/blog/post.service.js new file mode 100644 index 0000000..b9f30d2 --- /dev/null +++ b/modules/blog/post.service.js @@ -0,0 +1,252 @@ +/* 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'); + +module.exports = function (Post) { + return { + + /** + * 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 + });*/ + + const findOptions = {} + + return Post.findAll(findOptions); + }, + + /** + * 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 + }); + } + }; +} \ No newline at end of file diff --git a/modules/post/config/routes.js b/modules/post/config/routes.js deleted file mode 100644 index d48bf1d..0000000 --- a/modules/post/config/routes.js +++ /dev/null @@ -1,12 +0,0 @@ -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; \ No newline at end of file diff --git a/modules/post/controllers/Post.js b/modules/post/controllers/Post.js deleted file mode 100644 index 6d17304..0000000 --- a/modules/post/controllers/Post.js +++ /dev/null @@ -1,74 +0,0 @@ -'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); - } -}; diff --git a/modules/post/models/Post.js b/modules/post/models/Post.js deleted file mode 100644 index f0360cd..0000000 --- a/modules/post/models/Post.js +++ /dev/null @@ -1,55 +0,0 @@ -'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) => {} -}; diff --git a/modules/post/models/Post.settings.json b/modules/post/models/Post.settings.json deleted file mode 100644 index 3b163c7..0000000 --- a/modules/post/models/Post.settings.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "connection": "default", - "collectionName": "posts", - "info": { - "name": "post", - "description": "" - }, - "options": { - "increments": true, - "timestamps": true, - "comment": "" - }, - "attributes": { - "Título": { - "default": "", - "type": "string", - "required": true - } - } -} \ No newline at end of file diff --git a/modules/post/services/Post.js b/modules/post/services/Post.js deleted file mode 100644 index ca06e1c..0000000 --- a/modules/post/services/Post.js +++ /dev/null @@ -1,243 +0,0 @@ -/* 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 - }); - } -}; diff --git a/package.json b/package.json index 51f5ace..e87778f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "nodemon": "^1.18.9" }, "dependencies": { + "args-list": "^0.3.3", "async": "^2.6.2", "body-parser": "^1.18.3", "buffer": "^5.2.1", @@ -27,7 +28,6 @@ "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", diff --git a/server.js b/server.js index 04bd0e4..f76a825 100644 --- a/server.js +++ b/server.js @@ -1,136 +1,76 @@ #!/usr/bin/env node 'use strict'; -const { EventEmitter } = require('events'); const http = require('http'); -const { toLower } = require('lodash'); -const utils = require('./utils'); +const { assign, toLower } = require('lodash'); -const { - express, - logger, - nestedConfigurations, - appConfigurations -} = require('./core'); +const config = require('./config'); +const { express, logger, models } = require('./core'); -class Server extends EventEmitter { - constructor() { - super(); +const currentState = assign({ + 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', + connections: {} +}, config); - 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); +function stop(server) { + // Destroy server and available connections. + if (server) { + server.close(); } - 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 + 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); - } + process.send('stop'); + // Kill process. + process.exit(1); } +const server = http.createServer(express); +server.on('connection', conn => { + const key = conn.remoteAddress + ':' + conn.remotePort; + currentState.connections[key] = conn; -(() => { - const server = new Server(); - server.start(); -})(); \ No newline at end of file + conn.on('close', () => { + delete currentState.connections[key]; + }); +}); + +server.on('error', err => { + if (err.code === 'EADDRINUSE') { + logger.debug(`⛔️ Server wasn't able to start properly.`); + logger.error(`The port ${err.port} is already used by another application.`); + stop(server); + return; + } + + console.error(err); +}); + +try { + models.sequelize.sync().then(() => { + // Launch server. + server.listen(currentState.port, (err) => { + if (err) { + logger.debug(`⚠️ Server wasn't able to start properly.`); + logger.error(err); + return stop(server); + } + + logger.info('Time: ' + new Date()); + logger.info('Launched in: ' + (Date.now() - currentState.launchedAt) + ' ms'); + logger.info('Environment: ' + currentState.environment); + logger.info('Process PID: ' + process.pid); + //logger.info(`Version: ${this.config.info.strapi} (node v${this.config.info.node})`); + logger.info('To shut down your server, press + C at any time'); + logger.info(`⚡️ Server: http://${currentState.host}:${currentState.port}`); + + }); + }); +} catch (err) { + logger.debug(`⛔️ Server wasn't able to start properly.`); + logger.error(err); + console.error(err); + stop(server); +} diff --git a/yarn.lock b/yarn.lock index e7f0080..1858206 100644 --- a/yarn.lock +++ b/yarn.lock @@ -119,6 +119,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +args-list@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/args-list/-/args-list-0.3.3.tgz#d3fa5b7fe49ec96efb1e99adf20e7cfacc179956" + integrity sha1-0/pbf+SeyW77Hpmt8g58+swXmVY= + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -971,11 +976,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== - execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"