├── .bowerrc
├── files
├── robots.txt
└── public
│ ├── core-favicon.ico
│ └── images
│ ├── login.png
│ ├── logo.png
│ └── logo-small.png
├── .mocharc.json
├── src
├── Hooks
│ └── index.js
├── responses
│ ├── parsers
│ │ └── index.js
│ ├── formaters
│ │ └── index.js
│ ├── index.js
│ ├── JSONF.js
│ ├── JSONApi.js
│ └── methods
│ │ └── index.js
├── unhandledErrorCatcher.js
├── Router
│ ├── haveAndAcceptsHtmlResponse.js
│ ├── uploader.js
│ ├── responseType.js
│ └── search.js
├── localization
│ ├── index.js
│ └── i18n.js
├── PluginManager
│ ├── isPlugin.js
│ └── index.js
├── getEnv.js
├── cron
│ └── index.js
├── express
│ ├── sessionStore.js
│ ├── publicFolders.js
│ └── index.js
├── staticConfig
│ ├── getAppBootstrapConfig.js
│ └── index.js
├── utils
│ ├── mkdirp.js
│ └── index.js
├── log
│ └── index.js
├── messages
│ └── index.js
├── Sanitizer
│ └── index.js
├── class
│ ├── Theme.js
│ ├── Controller.js
│ └── Plugin.js
└── bootstrapFunctions.js
├── test
├── testData
│ ├── invalidLogConfig
│ │ └── config
│ │ │ └── log.js
│ ├── we-plugin-post
│ │ ├── package.json
│ │ ├── server
│ │ │ ├── models
│ │ │ │ ├── instanceMethods
│ │ │ │ │ └── returnModelId.js
│ │ │ │ ├── classMethods
│ │ │ │ │ └── returnModelName.js
│ │ │ │ ├── hooks
│ │ │ │ │ └── setWananingoValue.js
│ │ │ │ ├── tag.json
│ │ │ │ ├── user.js
│ │ │ │ ├── post.js
│ │ │ │ └── hero.json
│ │ │ ├── search
│ │ │ │ ├── parsers
│ │ │ │ │ └── orWithComaParser.js
│ │ │ │ └── targets
│ │ │ │ │ └── inTitleAndText.js
│ │ │ └── controllers
│ │ │ │ └── post.js
│ │ └── plugin.js
│ ├── we-plugin-fastload
│ │ ├── package.json
│ │ └── plugin.js
│ ├── config
│ │ └── log.js
│ └── models
│ │ └── pstub.js
├── tests
│ ├── modules
│ │ ├── modelClassMethods.test.js
│ │ ├── lib.sanitizer.test.js
│ │ ├── lib.log.test.js
│ │ ├── lib.staticConfig.getAppBootstrapConfig.test.js
│ │ ├── modelHooks.test.js
│ │ ├── database.syncAllModels.test.js
│ │ ├── modelInstanceMethods.test.js
│ │ ├── plugin.fastload.test.js
│ │ ├── lib.index.test.js
│ │ ├── pluginManager.test.js
│ │ ├── lib.staticConfig.test.js
│ │ ├── lib.env.test.js
│ │ ├── lib.utils.test.js
│ │ ├── database.defaultModelDefinitionConfigs.test.js
│ │ └── lib.messages.test.js
│ └── requests
│ │ ├── routes.test.js
│ │ ├── plugin.fastload.test.js
│ │ ├── resource.test.js
│ │ ├── pluralized-resource.test.js
│ │ └── resource_jsonAPI.test.js
└── bootstrap.js
├── .nycrc
├── .jshintignore
├── .npmignore
├── .travis.yml
├── .jshintrc
├── .gitignore
├── LICENSE.md
├── .jscsrc
├── README.md
├── CHANGELOG.md
├── package.json
├── locales
├── pt-br.json
└── en-us.json
├── CONTRIBUTING.md
└── install.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | { "directory": "files/public/libs" }
--------------------------------------------------------------------------------
/files/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /admin
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "timeout": 15000,
3 | "exit": true
4 | }
--------------------------------------------------------------------------------
/src/Hooks/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('simple-hooks-callback');
--------------------------------------------------------------------------------
/test/testData/invalidLogConfig/config/log.js:
--------------------------------------------------------------------------------
1 | this will whrow error
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "reporter": [ "lcov" ],
3 | "include": [ "src" ]
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/files/public/core-favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wejs/we-core/HEAD/files/public/core-favicon.ico
--------------------------------------------------------------------------------
/files/public/images/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wejs/we-core/HEAD/files/public/images/login.png
--------------------------------------------------------------------------------
/files/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wejs/we-core/HEAD/files/public/images/logo.png
--------------------------------------------------------------------------------
/files/public/images/logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wejs/we-core/HEAD/files/public/images/logo-small.png
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "we-plugin-post",
3 | "keywords": [
4 | "wejs-plugin"
5 | ]
6 | }
--------------------------------------------------------------------------------
/test/testData/we-plugin-fastload/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "we-plugin-fastload",
3 | "keywords": [
4 | "wejs-plugin"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | .nyc_output
4 | config
5 | test
6 | files
7 | docs
8 | client/shared/libs
9 | client/shared/beforeAll
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/instanceMethods/returnModelId.js:
--------------------------------------------------------------------------------
1 | module.exports = function returnModelId () {
2 | return this.id;
3 | };
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/classMethods/returnModelName.js:
--------------------------------------------------------------------------------
1 | module.exports = function returnModelName () {
2 | return String(this);
3 | };
--------------------------------------------------------------------------------
/src/responses/parsers/index.js:
--------------------------------------------------------------------------------
1 | const { jsonAPIParser } = require('../JSONApi.js');
2 |
3 | module.exports = {
4 | 'application/vnd.api+json': jsonAPIParser
5 | };
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | .jscsrc
3 | .jshintignore
4 | .jshintrc
5 | .travis.yml
6 | .gitignore
7 | .nycrc
8 | .nyc_output
9 | config
10 | coverage
11 | *.log
12 | *.sqlite
13 | coverage
14 | files/public/images
--------------------------------------------------------------------------------
/test/testData/config/log.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | log: {
3 | level: 'warn' ,
4 | colorize: true,
5 | timestamp: true,
6 | prettyPrint: true,
7 | depth: 6,
8 | showLevel: true
9 | }
10 | }
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/hooks/setWananingoValue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple function to test modelHooks feature
3 | */
4 | module.exports = function setWananingoValue(record) {
5 | record.wananingo = true;
6 | };
--------------------------------------------------------------------------------
/src/unhandledErrorCatcher.js:
--------------------------------------------------------------------------------
1 | module.exports = function unhandledErrorCatcher(we) {
2 | process.on('unhandledRejection', error => {
3 | we.log.warn('unhandledRejection catch', {
4 | error: error
5 | });
6 | });
7 | };
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/search/parsers/orWithComaParser.js:
--------------------------------------------------------------------------------
1 | module.exports = function orWithComaParser(searchName, field, value, w) {
2 | // = [] is same of or in sequelize
3 | return w[field] = {
4 | [this.we.db.Sequelize.Op.or]: value.split(',')
5 | };
6 | };
--------------------------------------------------------------------------------
/src/responses/formaters/index.js:
--------------------------------------------------------------------------------
1 | const { jsonAPIFormater } = require('../JSONApi'),
2 | { jsonFormater } = require('../JSONF');
3 |
4 | /**
5 | * We.js core response formatters
6 | */
7 | module.exports = {
8 | json: jsonFormater,
9 | 'application/json': jsonFormater,
10 | 'application/vnd.api+json': jsonAPIFormater
11 | };
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/search/targets/inTitleAndText.js:
--------------------------------------------------------------------------------
1 | module.exports = function inTitleAndText(searchName, field, value, query, req) {
2 | req.we.router.search.parsers[field.parser](searchName, 'title', value, query.where, req);
3 | req.we.router.search.parsers[field.parser](searchName, 'text', value, query.where, req);
4 | }
--------------------------------------------------------------------------------
/test/testData/models/pstub.js:
--------------------------------------------------------------------------------
1 | /**
2 | * pstub model
3 | */
4 | module.exports = function Model(we) {
5 | var model = {
6 | definition: {
7 | name: {
8 | type: we.db.Sequelize.STRING
9 | },
10 | description: {
11 | type: we.db.Sequelize.STRING
12 | }
13 | }
14 | }
15 | return model;
16 | }
17 |
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/tag.json:
--------------------------------------------------------------------------------
1 | {
2 | "attributes": {
3 | "text": {
4 | "type": "string",
5 | "allowNull": false
6 | }
7 | },
8 | "associations": {
9 | "inPosts": {
10 | "type": "belongsToMany",
11 | "model": "post",
12 | "inverse": "tags",
13 | "through": "posts_tags"
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/user.js:
--------------------------------------------------------------------------------
1 | module.exports = function (we) {
2 | var model = {
3 | definition: {
4 | displayName: {
5 | type: we.db.Sequelize.STRING,
6 | allowNull: false
7 | }
8 | },
9 | associations: {
10 | posts: {
11 | type: 'hasMany', model: 'post', inverse: 'creator'
12 | }
13 | }
14 | };
15 |
16 | return model;
17 | };
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - v8
5 | - v10
6 |
7 | services:
8 | - mysql
9 |
10 | addons:
11 | code_climate:
12 |
13 | env:
14 | NODE_ENV: 'test'
15 |
16 | notifications:
17 | email:
18 | - alberto@wejs.org
19 |
20 | before_script:
21 | - mysql -e 'create database test;'
22 |
23 | after_success:
24 | - npm install coveralls
25 | - npm run coverage
26 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
--------------------------------------------------------------------------------
/src/Router/haveAndAcceptsHtmlResponse.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check if have or accepts html response, usefull for features like redirect on html response format
3 | */
4 | module.exports = function haveAndAcceptsHtmlResponse(req, res) {
5 | if (
6 | req.accepts('html') &&
7 | ( res.locals.responseType == 'html' ||
8 | ( req.we.config.responseTypes.indexOf('html') > -1 )
9 | )
10 | ) {
11 | return true;
12 | } else {
13 | return false;
14 | }
15 | };
--------------------------------------------------------------------------------
/src/localization/index.js:
--------------------------------------------------------------------------------
1 | const i18n = require('./i18n'),
2 | moment = require('moment');
3 |
4 | var localization = function init (we) {
5 | i18n.configure (we.config.i18n, we);
6 |
7 | we.i18n = i18n;
8 |
9 | // set default moment locale
10 | moment.locale(we.config.i18n.defaultLocale);
11 |
12 | we.events.on('we:after:load:express', function afterLoadExpress(we) {
13 | // set i18n middleware
14 | we.express.use(i18n.init);
15 | });
16 | };
17 |
18 | module.exports = localization;
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/post.js:
--------------------------------------------------------------------------------
1 | module.exports = function (we) {
2 | var model = {
3 | definition: {
4 | title: {
5 | type: we.db.Sequelize.STRING,
6 | allowNull: false
7 | },
8 | text: {
9 | type: we.db.Sequelize.TEXT
10 | },
11 | },
12 | associations: {
13 | creator: { type: 'belongsTo', model: 'user' },
14 | tags: {
15 | type: 'belongsToMany',
16 | model: 'tag',
17 | inverse: 'inPosts',
18 | through: 'posts_tags'
19 | }
20 | }
21 | };
22 |
23 | return model;
24 | };
--------------------------------------------------------------------------------
/test/tests/modules/modelClassMethods.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we;
4 |
5 | describe('modelClassMethods', function () {
6 | before(function (done) {
7 | we = helpers.getWe();
8 | done();
9 | });
10 |
11 | it('Should load model class methods from plugin', function (done) {
12 | assert(we.db.modelClassMethods.returnModelName);
13 | done();
14 | });
15 |
16 | it('Should run the model classMethod in right model', function (done) {
17 |
18 | assert(we.db.models.hero.returnModelNameccc(), 'hero');
19 |
20 | done();
21 | });
22 | });
--------------------------------------------------------------------------------
/src/PluginManager/isPlugin.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path');
2 |
3 | /**
4 | * Helper to check if a npm module is a plugin
5 | * The logic is: is plugin if the npm module package.json have the wejs-plugin keyword
6 | *
7 | * @param {String} nodeModulePath
8 | * @return {Boolean}
9 | */
10 | module.exports = function isPlugin (nodeModulePath) {
11 | let pkg;
12 | try {
13 | pkg = require( resolve(nodeModulePath, 'package.json') );
14 | } catch(e) {
15 | return false;
16 | }
17 |
18 | if (pkg.keywords && pkg.keywords.includes('wejs-plugin') ) {
19 | return true;
20 | } else {
21 | return false;
22 | }
23 | };
--------------------------------------------------------------------------------
/src/Router/uploader.js:
--------------------------------------------------------------------------------
1 | const multer = require('multer'),
2 | uuid = require('uuid');
3 |
4 | function defaultFilename (req, file, cb) {
5 | file.name = Date.now() + '_' + uuid.v1() +'.'+ file.originalname.split('.').pop();
6 | cb(null, file.name);
7 | }
8 |
9 | function getUploader(uploadConfigs) {
10 | return multer({
11 | storage: multer.diskStorage({
12 | destination: uploadConfigs.dest || uploadConfigs.destination,
13 | filename: uploadConfigs.filename || defaultFilename,
14 | }),
15 | limits: uploadConfigs.limits,
16 | fileFilter: uploadConfigs.fileFilter
17 | })
18 | .fields(uploadConfigs.fields);
19 | }
20 |
21 | module.exports = getUploader;
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/models/hero.json:
--------------------------------------------------------------------------------
1 | {
2 | "attributes": {
3 | "name": {
4 | "type": "string",
5 | "allowNull": false
6 | },
7 | "price": {
8 | "type": "DECIMAL",
9 | "size": [10, 2]
10 | },
11 | "history": {
12 | "type": "text"
13 | }
14 | },
15 | "associations": {
16 | "creator": {
17 | "type": "belongsTo",
18 | "model": "user",
19 | "required": true
20 | }
21 | },
22 | "hooks": {
23 | "afterCreate": ["setWananingoValue"]
24 | },
25 | "classMethods": {
26 | "returnModelNameccc": "returnModelName"
27 | },
28 | "instanceMethods": {
29 | "returnModelIdiii": "returnModelId"
30 | }
31 | }
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = function loadPlugin(projectPath, Plugin) {
2 | var plugin = new Plugin(__dirname);
3 |
4 | plugin.setResource({
5 | name: 'post',
6 | findAll: {
7 | search: {
8 | title: {
9 | parser: 'equal',
10 | target: {
11 | type: 'field',
12 | field: 'title'
13 | }
14 | },
15 | text: {
16 | parser: 'contains',
17 | target: {
18 | type: 'field',
19 | field: 'text'
20 | }
21 | },
22 | q: {
23 | parser: 'orWithComaParser',
24 | target: {
25 | type: 'inTitleAndText'
26 | }
27 | }
28 | }
29 | }
30 | });
31 |
32 | return plugin;
33 | };
--------------------------------------------------------------------------------
/test/tests/modules/lib.sanitizer.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we, sanitizer;
4 |
5 | describe('lib.sanitizer', function () {
6 |
7 | before(function (done) {
8 | we = helpers.getWe();
9 | sanitizer = we.sanitizer;
10 | done();
11 | });
12 |
13 |
14 | describe('sanitizeAllAttr', function(){
15 | it ('should remove uneed tags and attributes from html', function (done){
16 |
17 | var dirtyObj = {
18 | title: '
Hello!
'
19 | }
20 |
21 |
22 | var safeObject = sanitizer.sanitizeAllAttr(dirtyObj)
23 |
24 | assert.equal(safeObject.title, '
Hello!
')
25 |
26 | done()
27 | })
28 | })
29 | })
--------------------------------------------------------------------------------
/src/getEnv.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Environment variable prod | dev | test
3 | *
4 | * @type {String}
5 | */
6 |
7 | module.exports = function getEnv() {
8 | let env;
9 |
10 | // check in process.arg
11 | if (process.argv.includes('--dev')) {
12 | env = 'dev';
13 | } else if (process.argv.includes('--test')) {
14 | env = 'test';
15 | } else if (process.argv.includes('--prod')) {
16 | env = 'prod';
17 | }
18 |
19 | if (!env) {
20 | switch (process.env.NODE_ENV) {
21 | case 'production':
22 | case 'prod':
23 | env = 'prod';
24 | break;
25 | case 'test':
26 | env = 'test';
27 | break;
28 | case 'development':
29 | case 'dev':
30 | env = 'dev';
31 | break;
32 | }
33 | }
34 |
35 | return ( env || 'dev' );
36 | };
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "asi": false,
3 | "boss": true,
4 | "browser": true,
5 | "camelcase": false,
6 | "curly": false,
7 | "devel": true,
8 | "eqeqeq": false,
9 | "eqnull": true,
10 | "esversion": 6,
11 | "evil": false,
12 | "immed": false,
13 | "indent": 2,
14 | "jquery": true,
15 | "latedef": false,
16 | "laxbreak": true,
17 | "laxcomma": true,
18 | "maxcomplexity": 20,
19 | "maxdepth": 4,
20 | "maxlen": 120,
21 | "maxstatements": 30,
22 | "newcap": true,
23 | "node": true,
24 | "noempty": false,
25 | "nonew": true,
26 | "predef": [
27 | "describe",
28 | "it",
29 | "before",
30 | "beforeEach",
31 | "after",
32 | "afterEach"
33 | ],
34 | "quotmark": "single",
35 | "smarttabs": true,
36 | "strict": false,
37 | "trailing": false,
38 | "undef": true,
39 | "unused": true
40 | }
--------------------------------------------------------------------------------
/test/tests/modules/lib.log.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const helpers = require('we-test-tools').helpers;
3 | let getLogger, we;
4 |
5 | describe('lib/log', function () {
6 | before(function (done) {
7 | getLogger = require('../../../src/log');
8 | we = helpers.getWe();
9 |
10 | we.config.log.level = 'info';
11 |
12 | done();
13 | });
14 |
15 | it('should throw error if we not are avaible', function (done) {
16 | try {
17 | getLogger();
18 | } catch (e) {
19 | assert.equal(e.message, 'we instance is required for get logger instance');
20 | done();
21 | }
22 | });
23 |
24 | it('should return logger without config file', function (done) {
25 | const logger = getLogger(we);
26 |
27 | assert.equal(logger.transports[0].level, 'info');
28 |
29 | done();
30 | });
31 | });
--------------------------------------------------------------------------------
/test/testData/we-plugin-post/server/controllers/post.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | find(req, res) {
3 |
4 | res.locals.query.include.push({
5 | model: req.we.db.models.tag,
6 | as: 'tags'
7 | });
8 |
9 | return res.locals.Model
10 | .findAll(res.locals.query)
11 | .then(function count(rows) {
12 |
13 | delete res.locals.query.include;
14 |
15 | return res.locals.Model
16 | .count(res.locals.query)
17 | .then(function afterCount(count) {
18 | return {
19 | count: count,
20 | rows: rows
21 | };
22 | });
23 | })
24 | .then(function afterFindAndCount (result) {
25 | res.locals.metadata.count = result.count;
26 | res.locals.data = result.rows;
27 | res.ok();
28 | return null;
29 | })
30 | .catch(res.queryError);
31 | }
32 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Logs
3 | logs
4 | *.log
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 | .nyc_output
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
29 | node_modules
30 |
31 | bower_components
32 |
33 | # Debug log from npm
34 | npm-debug.log
35 |
36 | *.sql
37 | *.sqlite
38 | *.tmp
39 |
40 | config
41 |
42 | *.DS_Store
43 |
44 |
45 | # OS X
46 | .DS_Store
47 |
48 | # Linux
49 | *~
50 |
51 |
--------------------------------------------------------------------------------
/test/tests/modules/lib.staticConfig.getAppBootstrapConfig.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var getAppBootstrapConfig, we;
4 |
5 | describe('lib.staticConfig.getAppBootstrapConfig', function () {
6 | before(function (done) {
7 | getAppBootstrapConfig = require('../../../src/staticConfig/getAppBootstrapConfig.js');
8 | we = helpers.getWe();
9 | done();
10 | });
11 |
12 | it('should return public configurations', function (done) {
13 | var configs = getAppBootstrapConfig(we);
14 |
15 | assert.equal(configs.version, 2);
16 | assert.equal(configs.env, 'test');
17 | assert.equal(configs.client.language, 'en-us');
18 | assert.equal(configs.client.publicVars.dynamicLayout, false);
19 | assert.equal(configs.appName, 'We test');
20 | assert.equal(configs.locales[0], 'en-us');
21 |
22 |
23 | done();
24 | });
25 | });
--------------------------------------------------------------------------------
/src/cron/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const cron = {
4 | loadAndRunAllTasks(we, cb) {
5 | we.cron.loadTasks(we, (err, tasks)=> {
6 | if (err) return cb(err);
7 | if (!tasks) return cb();
8 |
9 | we.utils.async.eachSeries(tasks, (t, next)=> {
10 | t(we, next);
11 | }, cb);
12 | });
13 | },
14 | /**
15 | * Load all project and plugin tasks
16 | *
17 | * @param {Object} we We.js object
18 | * @param {Function} done callback
19 | */
20 | loadTasks(we, done) {
21 | let tasks = {};
22 | we.utils.async.each(we.pluginNames, (name, next)=> {
23 | // try to load the cron.js files
24 | try {
25 | tasks[name] = require(path.resolve(we.plugins[name].pluginPath, 'cron.js'));
26 | } catch(e) {
27 | if (e.code != 'MODULE_NOT_FOUND') {
28 | we.log.error(e);
29 | }
30 | }
31 | next();
32 | }, (err)=> {
33 | done(err, tasks);
34 | });
35 | }
36 | };
37 |
38 | module.exports = cron;
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2015 Alberto Souza
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/test/tests/modules/modelHooks.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we;
4 |
5 | describe('modelHooks', function () {
6 | before(function (done) {
7 | we = helpers.getWe();
8 | done();
9 | });
10 |
11 | it('Should load model hooks from plugin', function (done) {
12 | assert(we.db.modelHooks.setWananingoValue);
13 | done();
14 | });
15 |
16 | it('Should run the model hook in right model hook', function (done) {
17 | var hs = {
18 | name: 'Iron Man',
19 | history: 'Iron Man (Tony Stark) is a fictional superhero appearing in American comic '+
20 | 'books published by Marvel Comics,'+
21 | ' as well as its associated media. The character was created by writer and editor Stan Lee,'+
22 | ' developed by scripter Larry Lieber, and designed by artists Don Heck and Jack Kirby. '+
23 | 'He made his first appearance in Tales of Suspense #39 (cover dated March 1963)'
24 | };
25 |
26 | we.db.models.hero.create(hs)
27 | .then(function(h) {
28 | assert.equal(h.wananingo, true);
29 |
30 | done();
31 | })
32 | .catch(done);
33 | });
34 | });
--------------------------------------------------------------------------------
/test/tests/modules/database.syncAllModels.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we, syncAllModels;
4 |
5 | describe('database.syncAllModels', function () {
6 |
7 | before(function (done) {
8 | we = helpers.getWe();
9 | syncAllModels = we.db.syncAllModels;
10 | done();
11 | });
12 |
13 | it('database.syncAllModels should run db.defaultConnection.sync', function (done) {
14 | syncAllModels.bind({
15 | defaultConnection: {
16 | sync: function() {
17 | return new we.db.Sequelize.Promise(function (resolve) {
18 | resolve();
19 | });
20 | }
21 | }
22 | })(done);
23 | });
24 |
25 | it('database.syncAllModels should run db.defaultConnection.sync with'+
26 | 'forced reset if cd.resetAllData is set', function (done) {
27 | syncAllModels.bind({
28 | defaultConnection: {
29 | sync: function(opts) {
30 | assert.equal(opts.force, true);
31 | return new we.db.Sequelize.Promise(function (resolve) {
32 | resolve();
33 | });
34 | }
35 | }
36 | })({ resetAllData: true }, done);
37 | });
38 | });
--------------------------------------------------------------------------------
/src/Router/responseType.js:
--------------------------------------------------------------------------------
1 | const mime = require('mime');
2 |
3 | /**
4 | * Parse response type middleware
5 | *
6 | * @param {Object} req express.js request
7 | * @param {Object} res express.js response
8 | * @param {Function} next callback
9 | */
10 | module.exports = function responseType (req, res, next){
11 |
12 | if (!req.headers) req.headers = {};
13 |
14 | parseResponseType(req);
15 |
16 | next();
17 | };
18 |
19 | /**
20 | * Parse the response type
21 | *
22 | * with order: 1. extension, 2.responseType, 3.Accept header
23 | *
24 | * @param {Object} req express.js request
25 | * @return {String} the response type string
26 | */
27 | function parseResponseType (req) {
28 | if (req.query && req.query.responseType) {
29 | if (req.query.responseType == 'modal') {
30 | // suport for old we.js contentOnly api
31 | req.query.responseType = req.we.config.defaultResponseType;
32 | req.query.contentOnly = true;
33 | }
34 |
35 | req.headers.accept = mime.getType(req.query.responseType.toLowerCase());
36 | }
37 |
38 | if (req.accepts(req.we.config.responseTypes))
39 | return;
40 |
41 | req.headers.accept = req.we.config.defaultResponseType;
42 | }
--------------------------------------------------------------------------------
/test/tests/modules/modelInstanceMethods.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we;
4 |
5 | describe('modelInstanceMethods', function () {
6 | before(function (done) {
7 | we = helpers.getWe();
8 | done();
9 | });
10 |
11 | it('Should load model instanceMethods from plugin', function (done) {
12 | assert(we.db.modelInstanceMethods.returnModelId);
13 | done();
14 | });
15 |
16 | it('Should run the model instanceMethod in right model', function (done) {
17 | var hs = {
18 | name: 'Iron Man',
19 | history: 'Iron Man (Tony Stark) is a fictional superhero appearing in American comic '+
20 | 'books published by Marvel Comics,'+
21 | ' as well as its associated media. The character was created by writer and editor Stan Lee,'+
22 | ' developed by scripter Larry Lieber, and designed by artists Don Heck and Jack Kirby. '+
23 | 'He made his first appearance in Tales of Suspense #39 (cover dated March 1963)'
24 | };
25 |
26 | we.db.models.hero.create(hs)
27 | .then(function(h) {
28 | assert.equal(h.returnModelIdiii(), h.id);
29 |
30 | done();
31 | })
32 | .catch(done);
33 | });
34 | });
--------------------------------------------------------------------------------
/test/tests/modules/plugin.fastload.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | helpers = require('we-test-tools').helpers;
3 |
4 | let we;
5 |
6 | describe('plugin.fastload.unit', function() {
7 | before(function (done) {
8 | we = helpers.getWe();
9 | done();
10 | });
11 |
12 | it('Should load the orWithMinusParser search parser', function() {
13 | assert(we.router.search.parsers.orWithMinusParser);
14 | });
15 |
16 | it('Should load the inNameAndDescription search target', function() {
17 | assert(we.router.search.targets.inNameAndDescription);
18 | });
19 |
20 | it('Should load the dog controller', function() {
21 | assert(we.controllers.dog);
22 | });
23 |
24 | it('Should load giveVaccine modelHooks', function() {
25 | assert(we.db.modelHooks.giveVaccine);
26 | });
27 |
28 | it('Should load bark modelInstanceMethod', function() {
29 | assert(we.db.modelInstanceMethods.bark);
30 | });
31 |
32 | it('Should load jump modelClassMethod', function() {
33 | assert(we.db.modelClassMethods.jump);
34 | });
35 |
36 | it('Should load dog model config and model', function(){
37 | assert(we.db.modelsConfigs.dog);
38 | assert(we.db.models.dog);
39 | assert(we.db.models.dog.jump);
40 | });
41 | });
--------------------------------------------------------------------------------
/test/tests/modules/lib.index.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var We;
3 |
4 | describe('lib/index.js', function () {
5 | before(function (done) {
6 | We = require('../../../src/index.js');
7 | done();
8 | });
9 |
10 | it ('should delete some attrs from request and response in freeResponseMemory', function (done) {
11 | const req = {};
12 | const res = {
13 | locals: {
14 | req: true,
15 | currentUser: true,
16 | regions: true,
17 | Model: true,
18 | body: true,
19 | layoutHtml: true
20 | }
21 | };
22 |
23 | We.prototype.freeResponseMemory(req, res);
24 |
25 | assert(res.locals.regions !== true);
26 | assert(res.locals.currentUser!== true);
27 |
28 | done();
29 | });
30 |
31 | describe('We instance', function(){
32 | let we;
33 |
34 | before(function(done){
35 | we = new We();
36 | we.bootstrap(done);
37 | })
38 |
39 | it('should set response type and formater with we.responses.addResponseFormater', function (done) {
40 | we.responses.addResponseFormater('xml', function formater(){});
41 |
42 | assert(we.config.responseTypes.indexOf('xml') > -1);
43 | assert(we.responses.formaters.xml);
44 |
45 | done();
46 | });
47 | });
48 |
49 |
50 | });
--------------------------------------------------------------------------------
/test/tests/modules/pluginManager.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var path = require('path');
3 | var helpers = require('we-test-tools').helpers;
4 | var npmf = path.resolve(process.cwd(), 'node_modules');
5 | var we;
6 |
7 | describe('modulePluginManager', function () {
8 | var pluginManager;
9 |
10 | before(function (done) {
11 | we = helpers.getWe();
12 | pluginManager = we.pluginManager;
13 | done();
14 | });
15 |
16 | it('pluginManager.isPlugin should return false for async and moment', function (done) {
17 | assert.equal(false, pluginManager.isPlugin(npmf+'/async'));
18 | assert.equal(false, pluginManager.isPlugin(npmf+'/moment'));
19 | assert.equal(false, pluginManager.isPlugin('invalid string'));
20 | done();
21 | });
22 |
23 | it('pluginManager.isPlugin should return true for we-plugin-post', function (done) {
24 | assert.equal(true, pluginManager.isPlugin(npmf+'/we-plugin-post'));
25 | done();
26 | });
27 |
28 | it('pluginManager.getPluginNames should return plugin names list', function (done) {
29 | var names = pluginManager.getPluginNames();
30 | assert(names.length > 1);
31 | assert(names.indexOf('project')>-1);
32 | assert(names.indexOf('we-plugin-post')>-1) ;
33 | done();
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/src/express/sessionStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Session store loader file
3 | * only load session store modules if session are enabled
4 | */
5 | module.exports = function sessionStoreLoader(we, weExpress) {
6 | if (we.config.session) {
7 | const session = require('express-session');
8 |
9 | // - default session storage
10 | // To change the session store change the we.config.session.store
11 | // To disable session set we.config.session to null
12 | if (we.config.session && !we.config.session.store && we.db.activeConnectionConfig.dialect == 'mysql') {
13 | const c = we.db.defaultConnection.connectionManager.config;
14 |
15 | let SessionStore = require('express-mysql-session');
16 | we.config.session.store = new SessionStore({
17 | host: c.host || 'localhost',
18 | port: c.port || 3306,
19 | user: c.username,
20 | password: c.password,
21 | database: c.database
22 | });
23 | we.config.session.resave = true;
24 | we.config.session.saveUninitialized = true;
25 | }
26 |
27 | if (we.config.session) {
28 | // save the instance for reuse in plugins
29 | we.sessionStore = we.config.session.store;
30 | we.session = session(we.config.session);
31 |
32 | weExpress.use(we.session);
33 | }
34 | }
35 |
36 | };
--------------------------------------------------------------------------------
/src/staticConfig/getAppBootstrapConfig.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | /**
4 | * Get App Bootstrap configs
5 | *
6 | * @param {Object} req express request
7 | * @return {Object} configs
8 | */
9 | module.exports = function getAppBootstrapConfig(we) {
10 |
11 | let configs = {};
12 |
13 | configs.version = '2';
14 |
15 | configs.env = we.env;
16 |
17 | configs.client = {};
18 | configs.appName = we.config.appName;
19 | configs.appLogo = we.config.appLogo;
20 |
21 | configs.client.publicVars = {};
22 |
23 | // auth configs
24 | configs.auth = {
25 | cookieDomain: we.config.passport.cookieDomain,
26 | cookieName: we.config.passport.cookieName,
27 | cookieSecure: we.config.passport.cookieSecure,
28 | accessTokenTime: we.config.passport.accessTokenTime,
29 |
30 | oauth: {
31 | server: null
32 | }
33 | };
34 |
35 | // get log config
36 | configs.client.log = we.config.clientside.log;
37 |
38 | // get public vars
39 | if (we.config.clientside.publicVars) {
40 | // clone it to dont change global variable
41 | configs.client.publicVars = _.clone(we.config.clientside.publicVars);
42 | }
43 |
44 | configs.locales = we.config.i18n.locales;
45 | configs.client.language = we.config.i18n.defaultLocale;
46 |
47 | configs.structure = {};
48 |
49 | return configs;
50 | };
--------------------------------------------------------------------------------
/test/tests/requests/routes.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | request = require('supertest'),
3 | helpers = require('we-test-tools').helpers;
4 |
5 | let _, http, we;
6 |
7 | describe('routes', function() {
8 | before(function (done) {
9 | http = helpers.getHttp();
10 | we = helpers.getWe();
11 | _ = we.utils._;
12 | return done();
13 | });
14 |
15 | describe('json', function() {
16 | describe('GET /', function(){
17 | it ('should return 200', function (done) {
18 |
19 | request(http)
20 | .get('/')
21 | .set('Accept', 'application/json')
22 | .expect(200)
23 | .end(function (err, res) {
24 | if (err) throw err;
25 |
26 | assert(res.body.messages);
27 |
28 | done();
29 | });
30 |
31 | });
32 | });
33 | });
34 |
35 | describe('public folders', function(){
36 | describe('GET /public/project/images/logo.png', function(){
37 | it ('should return 200', function (done) {
38 |
39 | request(http)
40 | .get('/public/project/images/logo.png')
41 | .expect(200)
42 | .end(function (err, res) {
43 | if (err) throw err;
44 |
45 | assert(res.body);
46 | assert(!res.text);
47 | assert.equal(res.headers['content-type'], 'image/png');
48 |
49 | done();
50 | });
51 |
52 | });
53 | });
54 | })
55 | });
--------------------------------------------------------------------------------
/src/express/publicFolders.js:
--------------------------------------------------------------------------------
1 | const express = require('express'),
2 | path = require('path');
3 |
4 | module.exports = function setPublicFolderMiddlewares (we, weExpress) {
5 | const cfg = { maxAge: we.config.cache.maxage },
6 | publicRouter = express.Router();
7 |
8 | publicRouter.use((req, res, next)=> {
9 | res.header('Access-Control-Allow-Origin', '*');
10 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
11 | next();
12 | });
13 |
14 | let plugin;
15 |
16 | if (we.view && we.view.themes) {
17 | // set themes public folder
18 | for (let themeName in we.view.themes) {
19 | publicRouter.use(
20 | '/theme/' + we.view.themes[themeName].name,
21 | express.static(path.join(
22 | we.view.themes[themeName].config.themeFolder, 'files/public'
23 | ), cfg)
24 | );
25 | }
26 | }
27 |
28 | // set plugins public folder
29 | for (let pluginName in we.plugins) {
30 | plugin = we.plugins[pluginName];
31 | publicRouter.use(
32 | '/plugin/' + plugin['package.json'].name + '/files',
33 | express.static(path.join( plugin.pluginPath, 'files/public'), cfg)
34 | );
35 | }
36 |
37 | // public project folder
38 | publicRouter.use('/project', express.static(
39 | path.join(we.projectPath, 'files/public'), cfg
40 | ));
41 |
42 | we.router.publicRouter = publicRouter;
43 |
44 | weExpress.use('/public', publicRouter);
45 | };
--------------------------------------------------------------------------------
/src/responses/index.js:
--------------------------------------------------------------------------------
1 | const formaters = require('./formaters'),
2 | methods = require('./methods'),
3 | parsers = require('./parsers');
4 |
5 | // set default response formater
6 | formaters.default = formaters.json;
7 |
8 | module.exports = {
9 | formaters: formaters,
10 | methods: methods,
11 | parsers: parsers,
12 |
13 | /**
14 | * Set response formatters in current request
15 | */
16 | format(format, data, req, res) {
17 | res.format(formaters);
18 | },
19 | /**
20 | * Set custom responses in res variable
21 | */
22 | setCustomResponses(req, res, next) {
23 | for (let response in methods) {
24 | res[response] = methods[response].bind({req: req, res: res, next: next});
25 | }
26 |
27 | return next();
28 | },
29 |
30 | /**
31 | * Sort response formatters
32 | *
33 | * @param {Object} we we.js app
34 | */
35 | sortResponses(we) {
36 | const formats = Object.keys(we.responses.formaters);
37 | we.responses.formatersUnsorted = we.responses.formaters;
38 |
39 | we.responses.formaters = {};
40 |
41 | let name;
42 |
43 | for (let i = 0; i < we.config.responseTypes.length; i++) {
44 | name = we.config.responseTypes[i];
45 | we.responses.formaters[name] = we.responses.formatersUnsorted[name];
46 | }
47 |
48 | formats.forEach( (f)=> {
49 | if (!we.responses.formaters[f]) {
50 | we.responses.formaters[f] = we.responses.formatersUnsorted[f];
51 | }
52 | });
53 | }
54 | };
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | { "excludeFiles":
2 | [ "**/node_modules/**"
3 | , "coverage"
4 | , "data"
5 | , "client/shared/beforeAll/**"
6 | , "client/shared/libs/**"
7 | , "files/public/**"
8 | ]
9 | , "requireLineFeedAtFileEnd": true
10 | , "disallowMultipleLineBreaks": true
11 | , "requireMultipleVarDecl": true
12 | , "disallowEmptyBlocks": true
13 | , "disallowSpaceAfterObjectKeys": true
14 | , "disallowTrailingWhitespace": true
15 | , "requireCapitalizedConstructors": true
16 | , "requireSpacesInsideObjectBrackets": "all"
17 | , "requireSpacesInsideArrayBrackets": "all"
18 | , "validateLineBreaks": "LF"
19 | , "requireSpaceBeforeBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
20 | , "requireSpaceAfterBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
21 | , "requireSpaceAfterKeywords": [ "if", "else", "for", "while", "do", "switch", "try", "catch" ]
22 | , "requireSpaceBeforeBinaryOperators": [ "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" ]
23 | , "requireSpaceAfterBinaryOperators": [ "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" ]
24 | , "requireSpacesInConditionalExpression": true
25 | , "disallowSpaceAfterPrefixUnaryOperators": [ "++", "--", "+", "-", "~", "!" ]
26 | , "disallowSpaceBeforePostfixUnaryOperators": [ "++", "--" ]
27 | , "requireSpaceBeforeBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
28 | , "requireSpaceAfterBinaryOperators": [ "+", "-", "/", "*", "=", "==", "===", "!=", "!==" ]
29 | , "disallowKeywordsOnNewLine": [ "else" ]
30 | , "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/mkdirp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Code from mkdirp: https://github.com/substack/node-mkdirp
3 | */
4 | const path = require('path'),
5 | fs = require('fs'),
6 | _0777 = parseInt('0777', 8);
7 |
8 | module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
9 |
10 | function mkdirP (p, opts, f, made) {
11 | if (typeof opts === 'function') {
12 | f = opts;
13 | opts = {};
14 | }
15 | else if (!opts || typeof opts !== 'object') {
16 | opts = { mode: opts };
17 | }
18 |
19 | let mode = opts.mode;
20 | let xfs = opts.fs || fs;
21 |
22 | if (mode === undefined) {
23 | mode = _0777 & (~process.umask());
24 | }
25 | if (!made) made = null;
26 |
27 | let cb = f || function () {};
28 | p = path.resolve(p);
29 |
30 | xfs.mkdir(p, mode, function (er) {
31 | if (!er) {
32 | made = made || p;
33 | return cb(null, made);
34 | }
35 | switch (er.code) {
36 | case 'ENOENT':
37 | mkdirP(path.dirname(p), opts, function (er, made) {
38 | if (er) cb(er, made);
39 | else mkdirP(p, opts, cb, made);
40 | });
41 | break;
42 |
43 | // In the case of any other error, just see if there's a dir
44 | // there already. If so, then hooray! If not, then something
45 | // is borked.
46 | default:
47 | xfs.stat(p, function (er2, stat) {
48 | // if the stat fails, then that's super weird.
49 | // let the original error be the failure reason.
50 | if (er2 || !stat.isDirectory()) cb(er, made)
51 | else cb(null, made);
52 | });
53 | break;
54 | }
55 | });
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # We.js core module :green_heart:
2 |
3 | [](https://gitter.im/wejs/we?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/wejs/we-core)
4 |
5 | **Main repository: https://github.com/wejs/we **
6 |
7 | Site: [http://wejs.org](https://wejs.org)
8 |
9 | Status: **maintained**
10 |
11 | [**Changelog**](CHANGELOG.md)
12 |
13 | ## Install for develop we.js core:
14 |
15 | After install npm and node.js
16 |
17 | ```sh
18 | // clone this project
19 | git clone https://github.com/wejs/we-core.git
20 | // enter in cloned folder
21 | cd we-core
22 | // install all dependencies
23 | npm install
24 | // test
25 | npm test
26 | ```
27 |
28 | ## Development:
29 |
30 | Run `npm run cw` for compile files in src to lib folder
31 |
32 | Only edit code in **src** folder
33 |
34 | ### How to test
35 |
36 | after clone and install npm packages:
37 |
38 | ```sh
39 | npm test
40 | ```
41 |
42 | ##### For run only 'userFeature' test use:
43 |
44 | ```sh
45 | we test -g 'resourceRequests'
46 | ```
47 |
48 | ##### For run the code coverage
49 |
50 | ```sh
51 | npm run coverage
52 | ```
53 | ## V3 migration
54 |
55 | - Breaking changes in sequelize ORM: Updated to v5.x
56 | - Breaking changes in winston logger: Updated to v3.x
57 | - string npm module removed from we.utils.string and dependencies.
58 |
59 | ## V2 migration
60 |
61 | - Breaking changes in ORM: http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html
62 |
63 | ## License
64 |
65 | [the MIT license](LICENSE.md).
66 |
67 | ## Sponsored by
68 |
69 | - Linky: https://linkysystems.com
70 |
71 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log file
2 |
3 | ## Changes:
4 | - v1.8.0: New plugin manager
5 | - to update: run `we update`
6 | - v1.7.0:
7 | - Added suport for jsonapi requests in core and response format improved
8 | - v1.6.0: we-plugin-file api updated. Title, breadcrumb and metatag moved to we-plugin-view, add babel in we-core.
9 | - to update: run `we update` and install we-plugin-file-local
10 | - v1.5.0: Big update in cli and generators, Moved email features to we-plugin-email
11 | - To update run `npm install --save we-plugin-email`
12 | - v1.4.0: Split we-core in small modules and update app project to be ready to APIs
13 | - To update run `npm install --save we-plugin-editor-summernote we-plugin-url-alias we-plugin-user we-plugin-view we-plugin-widget we-plugin-acl we-plugin-auth`
14 | - And update we-core to v1.4.0 `npm install we-core`
15 | - v1.3.0: Widget feature moved to we-plugin-widget
16 | - To update run `npm install we-plugin-widget`
17 | - v1.0.0: we-core now returns one prototype and mysql modules is removed from we-core. For update your project do:
18 | - Update your global we cli and generators: `npm install we generator-wejs -g`
19 | - Update your project app.js to:
20 | ```js
21 | var We = require('we-core');
22 | // instantiate an new app
23 | var app = new We();
24 |
25 | app.go(function (err) {
26 | if (err) return console.error(err);
27 | });
28 | ```
29 | - Update your project gulpfile to:
30 | ```js
31 | var We = require('we-core');
32 | var we = new We();
33 |
34 | var projectFolder = process.cwd();
35 | var gulp = require('gulp');
36 | var weGulpTasks = require('we-gulp-tasks-default');
37 |
38 | weGulpTasks(we, gulp, projectFolder, function doneTask() {
39 | we.exit(function(){
40 | process.exit();
41 | });
42 | });
43 |
44 | ```
45 | - If you use mysql then to install **mysql** and **express-mysql-session**:
46 | `npm install --save mysql express-mysql-session`
47 | - v0.3.97: Add suport to url alias
48 | - v0.3.96: Add suport to windows 10
49 |
--------------------------------------------------------------------------------
/test/bootstrap.js:
--------------------------------------------------------------------------------
1 | const projectPath = process.cwd(),
2 | path = require('path'),
3 | deleteDir = require('rimraf'),
4 | async = require('async'),
5 | testTools = require('we-test-tools'),
6 | ncp = require('ncp').ncp,
7 | We = require('../src');
8 |
9 | let we;
10 |
11 | before(function (callback) {
12 | testTools.copyLocalSQLiteConfigIfNotExists(projectPath, callback);
13 | });
14 |
15 | // Add the stub plugin in node_modules folder:
16 | before(function (callback) {
17 | const f = path.resolve(__dirname, 'testData/we-plugin-post'),
18 | d = path.resolve(process.cwd(), 'node_modules/we-plugin-post');
19 |
20 | ncp(f, d, callback);
21 | });
22 |
23 | // add an seccond plugin with support to fast load
24 | before(function (callback) {
25 | const f = path.resolve(__dirname, 'testData/we-plugin-fastload'),
26 | d = path.resolve(process.cwd(), 'node_modules/we-plugin-fastload');
27 | ncp(f, d, callback);
28 | });
29 |
30 | // prepare we.js core and load app features:
31 | before(function (callback) {
32 | this.slow(100);
33 |
34 | we = new We({ bootstrapMode: 'test' });
35 |
36 | testTools.init({}, we);
37 |
38 | we.bootstrap({
39 | // disable access log
40 | enableRequestLog: false,
41 |
42 | i18n: {
43 | directory: path.resolve(__dirname, '..', 'config/locales'),
44 | updateFiles: true,
45 | locales: ['en-us']
46 | },
47 | themes: {}
48 | }, callback);
49 | });
50 |
51 | // start the server:
52 | before(function (callback) {
53 | we.startServer(callback);
54 | });
55 |
56 | // after all tests remove test folders and delete the database:
57 | after(function (callback) {
58 | const tempFolders = [
59 | path.resolve(process.cwd(), 'node_modules/we-plugin-post'),
60 | projectPath + '/files/config',
61 | projectPath + '/files/uploads',
62 | projectPath + '/database.sqlite',
63 | projectPath + '/files/templatesCacheBuilds.js'
64 | ];
65 |
66 | async.each(tempFolders, (folder, next)=> {
67 | deleteDir( folder, next);
68 | }, (err)=> {
69 | if (err) throw new Error(err);
70 | we.exit(callback);
71 | });
72 | });
--------------------------------------------------------------------------------
/test/tests/modules/lib.staticConfig.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert')
2 | var helpers = require('we-test-tools').helpers
3 | var fs = require('fs')
4 | var path = require('path')
5 | var staticConfig, we
6 |
7 | describe('lib.staticConfig', function () {
8 | before(function (done) {
9 | staticConfig = require('../../../src/staticConfig')
10 | we = helpers.getWe()
11 | done()
12 | });
13 |
14 | it('static configs should throw error if run without project path', function (done) {
15 | try {
16 | staticConfig()
17 | } catch (e) {
18 |
19 | assert.equal(e.message, 'project path is required for load static configs')
20 |
21 | return done()
22 | }
23 |
24 | assert(false, 'should throw error')
25 | })
26 |
27 | it('staticConfig should return we.config (cache) if staticConfigsIsLoad is true', function (done) {
28 | var w = {
29 | staticConfigsIsLoad: true,
30 | config: {
31 | isMee: true
32 | }
33 | }
34 |
35 | var wr = staticConfig(process.cwd(), w)
36 |
37 | assert.equal(wr.isMee, true)
38 |
39 | done()
40 | })
41 |
42 | it('loadPluginConfigs should return we.config (cache) if pluginConfigsIsLoad is true', function (done) {
43 | var w = {
44 | pluginConfigsIsLoad: true,
45 | config: {
46 | isMe: true
47 | }
48 | }
49 |
50 | var wr = staticConfig.loadPluginConfigs(w)
51 |
52 | assert.equal(wr.isMe, true)
53 |
54 | done()
55 | })
56 |
57 | it ('readJsonConfiguration should create the configuration.json file if not exists', function (done) {
58 | this.slow(250)
59 | var projectConfigFolder = path.resolve(process.cwd(), 'config')
60 | var cfgFilePath = path.join(we.projectConfigFolder, 'configuration.json')
61 |
62 | fs.unlink(cfgFilePath, function (err) {
63 | if (err) return done(err)
64 |
65 | var cfg = staticConfig.readJsonConfiguration(projectConfigFolder)
66 |
67 | fs.readFile(cfgFilePath, function (err, result) {
68 | if (err) return done(err)
69 |
70 | assert(typeof cfg == 'object')
71 | assert.equal(result.toString(), '{}')
72 |
73 | done()
74 | })
75 |
76 | })
77 | })
78 | });
--------------------------------------------------------------------------------
/src/log/index.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const { createLogger, transports, format } = require('winston');
3 | const { combine, timestamp, json, label, errors } = format;
4 |
5 | module.exports = function getTheLogger(we) {
6 | if (!we) throw new Error('we instance is required for get logger instance');
7 |
8 | const env = we.env,
9 | log = we.config.log,
10 | defaultAll = {
11 | level: 'info',
12 | colorize: true,
13 | timestamp: true,
14 | json: false,
15 | stringify: false,
16 | prettyPrint: true,
17 | depth: 5,
18 | showLevel: true
19 | };
20 |
21 | let configs;
22 | // if have an specific configuration for this env:
23 | if (log !== undefined && log[env]) {
24 | // Add support to set multiple log configs for diferent envs in same configuration:
25 | configs = _.defaults(log[env], defaultAll);
26 | } else if (log) {
27 | // log config without env log:
28 | configs = _.defaults(log, defaultAll);
29 | } else {
30 | // if configs not is set use the default:
31 | configs = defaultAll;
32 | }
33 |
34 | // allows to set log level with LOG_LV enviroment variable
35 | if (process.env.LOG_LV) configs.level = process.env.LOG_LV;
36 |
37 | let format;
38 |
39 | if (configs.format) {
40 | format = configs.format;
41 | } else {
42 | format = combine(
43 | errors({ stack: true }),
44 | label({label: 'wejs-app'}),
45 | timestamp(),
46 | json()
47 | );
48 | }
49 |
50 | // start one logger
51 | const logger = createLogger({
52 | level: configs.level,
53 | format: format,
54 | transports: configs.transports
55 | });
56 |
57 | if (configs.keepConsoleTransport || !configs.transports || !configs.transports.length) {
58 | // default console logger
59 | logger.add(new transports.Console(configs));
60 | }
61 | // save close method
62 | logger.closeAllLoggersAndDisconnect = closeAllLoggersAndDisconnect;
63 |
64 | return logger;
65 | };
66 |
67 | /**
68 | * Method for wait all log writes before disconnect
69 | *
70 | * @param {Object} we
71 | * @param {Function} cb
72 | */
73 | function closeAllLoggersAndDisconnect(we, cb) {
74 | setTimeout(() => {
75 | cb();
76 | }, 350);
77 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "we-core",
3 | "version": "3.1.18",
4 | "description": "We.js is a node.js framework for build real time applications, sites or blogs!",
5 | "homepage": "https://wejs.org",
6 | "main": "./src/index.js",
7 | "scripts": {
8 | "test": "NODE_ENV=test LOG_LV=info ./node_modules/.bin/mocha test/bootstrap.js test/**/*.test.js -b ",
9 | "verbose-test": "NODE_ENV=test LOG_LV=verbose ./node_modules/.bin/mocha test/bootstrap.js test/**/*.test.js -b ",
10 | "coverage": "NODE_ENV=test LOG_LV=info nyc mocha test/bootstrap.js test/**/*.test.js -b",
11 | "postversion": "git push"
12 | },
13 | "keywords": [
14 | "we.js",
15 | "wejs",
16 | "plugin",
17 | "wejs-plugin",
18 | "web",
19 | "api",
20 | "systems",
21 | "framework",
22 | "web framework"
23 | ],
24 | "repository": "wejs/we-core",
25 | "files": [
26 | "files",
27 | "src",
28 | "locales",
29 | "server",
30 | "install.js",
31 | "plugin.js"
32 | ],
33 | "author": "Alberto Souza ",
34 | "license": "MIT",
35 | "dependencies": {
36 | "async": "^3.2.0",
37 | "body-parser": "1.19.0",
38 | "compression": "1.7.4",
39 | "connect-flash": "0.1.1",
40 | "cookie-parser": "1.4.5",
41 | "cors": "2.8.5",
42 | "express": "4.17.1",
43 | "express-session": "1.17.1",
44 | "handlebars": "4.7.6",
45 | "lodash": "4.17.20",
46 | "mime": "2.4.7",
47 | "moment": "2.29.1",
48 | "morgan": "1.10.0",
49 | "pluralize": "^8.0.0",
50 | "request": "2.88.2",
51 | "sanitize-html": "1.27.5",
52 | "sequelize": "5.22.3",
53 | "serve-favicon": "~2.5.0",
54 | "simple-hooks-callback": "1.0.0",
55 | "slugify": "^1.4.0",
56 | "uuid": "^8.1.0",
57 | "winston": "^3.2.1"
58 | },
59 | "devDependencies": {
60 | "chance": "^1.1.5",
61 | "connect-sqlite3": "^0.9.11",
62 | "mocha": "7.2.0",
63 | "mysql2": "^2.1.0",
64 | "ncp": "^2.0.0",
65 | "nyc": "^15.0.1",
66 | "rimraf": "3.0.2",
67 | "sinon": "9.2.2",
68 | "sqlite3": "^4.2.0",
69 | "supertest": "4.0.2",
70 | "we-test-tools": "^1.0.0"
71 | },
72 | "engines": {
73 | "node": ">=8.0.0"
74 | },
75 | "wejs": {
76 | "plugins": {
77 | "we-plugin-post": true,
78 | "we-plugin-fastload": true
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/tests/modules/lib.env.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var getEnv;
3 |
4 | describe('getEnv', function () {
5 | before(function (done) {
6 | getEnv = require('../../../src/getEnv.js');
7 | done();
8 | });
9 |
10 | it('should return dev for unknow environment', function (done) {
11 | process.env.NODE_ENV = null;
12 | assert.equal(getEnv(), 'dev');
13 | process.env.NODE_ENV = 'test';
14 | done();
15 | });
16 |
17 | it('should return dev if --dev command option is set', function (done) {
18 |
19 | process.argv.push('--dev');
20 | assert.equal(getEnv(), 'dev');
21 | var index = process.argv.indexOf('--dev');
22 | if (index > -1) process.argv.splice(index, 1);
23 |
24 | done();
25 | });
26 |
27 | it('should return test if --test command option is set', function (done) {
28 | process.argv.push('--test');
29 | assert.equal(getEnv(), 'test');
30 |
31 | var index = process.argv.indexOf('--test');
32 | if (index > -1) process.argv.splice(index, 1);
33 |
34 | done();
35 | });
36 |
37 | it('should return prod if --prod command option is set', function (done) {
38 | process.argv.push('--prod');
39 | assert.equal(getEnv(), 'prod');
40 |
41 | var index = process.argv.indexOf('--prod');
42 | if (index > -1) process.argv.splice(index, 1);
43 |
44 | done();
45 | });
46 |
47 | it('should return prod if NODE_ENV=production', function (done) {
48 | process.env.NODE_ENV = 'production';
49 | assert.equal(getEnv(), 'prod');
50 |
51 | process.env.NODE_ENV = 'test';
52 | done();
53 | });
54 |
55 | it('should return prod if NODE_ENV=prod', function (done) {
56 | process.env.NODE_ENV = 'prod';
57 | assert.equal(getEnv(), 'prod');
58 |
59 | process.env.NODE_ENV = 'test';
60 |
61 | done();
62 | });
63 |
64 | it('should return test if NODE_ENV=test', function (done) {
65 | process.env.NODE_ENV = 'test';
66 | assert.equal(getEnv(), 'test');
67 |
68 | done();
69 | });
70 |
71 | it('should return dev if NODE_ENV=development', function (done) {
72 | process.env.NODE_ENV = 'development';
73 | assert.equal(getEnv(), 'dev');
74 |
75 | process.env.NODE_ENV = 'test';
76 |
77 | done();
78 | });
79 |
80 | it('should return dev if NODE_ENV=dev', function (done) {
81 | process.env.NODE_ENV = 'dev';
82 | assert.equal(getEnv(), 'dev');
83 |
84 | process.env.NODE_ENV = 'test';
85 |
86 | done();
87 | });
88 | });
--------------------------------------------------------------------------------
/locales/pt-br.json:
--------------------------------------------------------------------------------
1 | {
2 | "widget.delete.confirm.msg": "Vocẽ tem certeza que deseja deletar esse widget?",
3 | "Profile": "Perfil",
4 | "Widget": "Widget",
5 | "Edit": "Editar",
6 | "Delete": "Deletar",
7 | "widget.title": " ",
8 | "widget.add": "Adicionar widget",
9 | "widget.context": "Contexto do widget",
10 | "widget.visibility": "Exibir o widget: ",
11 | "widget.in-context": "No contexto atual",
12 | "widget.in-portal": "Em todas as páginas",
13 | "widget.in-session": "Nessa sessão",
14 | "widget.in-session-record": "Nos conteúdos dessa sessão",
15 | "widget.in-page": "Na página atual",
16 | "layout.select.widget.type": "Selecione o tipo de widget",
17 | "layout.edit.hide.btn": "Editar layout",
18 | "date.to": "a",
19 | "layout.edit.btn": "Editar layout",
20 | "image.remove": "remover imagem",
21 | "image.selector": "Selecionar imagem",
22 | "image.uploader.title": "Enviar imagem",
23 | "image.selector.title": "Selecionar imagem",
24 | "image.upload.selector.btn": "Selecionar para enviar",
25 | "file.image.select.btn": "selecionar imagem",
26 | "paginate.sumary": "Exibindo {{recordsLength}} de {{count}}",
27 | "widget.html.label": "Texto ou HTML customizado",
28 | "permission.link": "Permissões",
29 | "widget.update": "Atualizar widget",
30 | "widget.create": "Criar widget",
31 | "auth.register.spam": "O seu email {{email}} está marcado como spam pelo Google Recaptcha.",
32 | "Home": "Início",
33 | "Male": "Masculino",
34 | "Female": "Feminino",
35 | "updatedAt": "Atualizado em",
36 | "createdAt": "Criado em",
37 | "Create": "Criar",
38 | "widget.invalid.context": "O contexto do widget é inválido",
39 | "response.badRequest.title": "Dados inválidos",
40 | "response.badRequest.description": "Alguma informação enviada para o sistema está inválida",
41 | "response.notFound.title": "Não encontrado",
42 | "response.notFound.description": "O link ou a página que você procurava não foi encontrada no sistema",
43 | "response.forbidden.title": "Acesso restrito!",
44 | "response.forbidden.description": "Você não tem permissão no sistema para acessar essa página",
45 | "response.serveError.title": "Erro no servidor!",
46 | "response.serveError.description": "Aconteceu um erro no sistema",
47 | "auth.register.acceptTerms.required": "Você deve aceitar os nossos termos para criar uma conta no sistema",
48 | "skip.to.content.link.text": "Ir para o conteúdo principal",
49 | "role.delete.confirm.msg": "Tem certeza que deseja deletar esse perfil de usuário?",
50 | "Search": "Buscar"
51 | }
--------------------------------------------------------------------------------
/src/Router/search.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Route search parsers and targets
3 | */
4 | const moment = require('moment'),
5 | Sequelize = require('sequelize'),
6 | Op = Sequelize.Op;
7 |
8 | /**
9 | * Converts one date string to dateTime
10 | * @param {string} d date
11 | * @return {string} dateTime
12 | */
13 | function dateToDateTime(d) {
14 | if (d) {
15 | let date = moment(d);
16 | // return null if not is valid
17 | if (!date.isValid()) return null;
18 | // return data in datetime format
19 | return date.format('YYYY-MM-DD HH:mm:ss');
20 | } else {
21 | return null;
22 | }
23 | }
24 |
25 | module.exports = {
26 | parsers: {
27 | equalBoolean(searchName, field, value, w) {
28 | if (!value || value.toLowerCase() === 'false') {
29 | return w[field.target.field] = false;
30 | } else {
31 | return w[field.target.field] = true;
32 | }
33 | },
34 | equal(searchName, field, value, w) {
35 | return w[field.target.field] = value;
36 | },
37 | contains(searchName, field, value, w) {
38 | return w[field.target.field] = {
39 | [Op.like]: '%'+value+'%'
40 | };
41 | },
42 | startsWith(searchName, field, value, w) {
43 | return w[field.target.field] = {
44 | [Op.like]: value+'%'
45 | };
46 | },
47 | userSearchQuery(searchName, field, value, w) {
48 | return w[Op.or] = {
49 | email: {
50 | [Op.eq]: value
51 | },
52 | displayName: {
53 | [Op.like]: value+'%'
54 | },
55 | username: {
56 | [Op.eq]: value
57 | }
58 | };
59 | },
60 | since(searchName, field, value, w) {
61 | if (!value) return w;
62 |
63 | return w[field.target.field] = {
64 | [Op.gt]: dateToDateTime(value)
65 | };
66 | },
67 |
68 | // if user from :userId is
69 | paramIs(searchName, field, value, w, req) {
70 | return w[field.target.field] = req.params[field.param];
71 | }
72 | },
73 | targets: {
74 | field(searchName, field, value, query, req) {
75 | req.we.router.search.parsers[field.parser](searchName, field, value, query.where, req);
76 | },
77 | association(searchName, field, value, query, req) {
78 | for (let i = 0; i < query.include.length; i++) {
79 | if (query.include[i]) {
80 | if (!query.include[i].where) query.include[i].where = {};
81 | req.we.router.search.parsers[field.parser](searchName, field, value, query.include[i].where, req);
82 | // required target configuration
83 | if (field.target.required) query.include[i].required = true;
84 |
85 | break;
86 | }
87 | }
88 | }
89 | }
90 | };
--------------------------------------------------------------------------------
/test/testData/we-plugin-fastload/plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = function loadPlugin(projectPath, Plugin) {
2 | const plugin = new Plugin(__dirname);
3 |
4 | plugin.fastLoader = function fastLoader(we, done) {
5 | const Op = we.db.Sequelize.Op;
6 |
7 | // search parsers:
8 | we.router.search.parsers.orWithMinusParser = function orWithMinusParser(searchName, field, value, w) {
9 | // = [] is same of or in sequelize
10 | return w[field] = { [Op.or]: value.split(',') };
11 | };
12 |
13 | // search targets:
14 | we.router.search.targets.inNameAndDescription =
15 | function inNameAndDescription(searchName, field, value, query, req) {
16 | req.we.router.search.parsers[field.parser](searchName, 'title', value, query.where, req);
17 | req.we.router.search.parsers[field.parser](searchName, 'text', value, query.where, req);
18 | };
19 |
20 | // controllers:
21 | we.controllers.dog = new we.class.Controller({
22 | bark(req, res) {
23 | req.we.db.models.dog
24 | .findById(req.params.id)
25 | .then( (d)=> {
26 | if (!d) return res.notFound('dog.not.found');
27 |
28 | res.send({ result: d.bark() });
29 | return null;
30 | })
31 | .catch(res.queryError);
32 | }
33 | });
34 |
35 | // model hooks
36 | we.db.modelHooks.giveVaccine = function giveVaccine(record) {
37 | if (record.age > 1) {
38 | record.vaccine = record.vaccine+1;
39 | }
40 | };
41 |
42 | // model instance methods
43 | we.db.modelInstanceMethods.bark = function bark() {
44 | return 'AuAU';
45 | };
46 |
47 | // model class methods
48 | we.db.modelClassMethods.jump = function bark(dog) {
49 | return `${dog.id} jumped!`;
50 | };
51 |
52 | // JSON model
53 | we.db.modelsConfigs.dog = we.db.defineModelFromJson( {
54 | attributes: {
55 | name: {
56 | type: 'STRING'
57 | },
58 | vaccine: {
59 | type: 'INTEGER',
60 | dafaultValue: 0
61 | },
62 | age: {
63 | type: 'INTEGER',
64 | dafaultValue: 1
65 | }
66 | },
67 | options: {
68 | tableName: 'hotdog'
69 | },
70 | hooks: {
71 | beforeUpdate: [ 'giveVaccine' ]
72 | },
73 | instanceMethods: {
74 | bark: 'bark'
75 | },
76 | classMethods: {
77 | jump: 'jump'
78 | }
79 | }, we);
80 |
81 | done();
82 | };
83 |
84 | plugin.setResource({
85 | name: 'dog'
86 | });
87 |
88 | plugin.setRoutes({
89 | 'post /dog/:id/bark': {
90 | controller: 'dog',
91 | model: 'dog',
92 | action: 'bark'
93 | }
94 | });
95 |
96 | return plugin;
97 | };
--------------------------------------------------------------------------------
/src/messages/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Express request message strategy
3 | *
4 | * This module helps with system messages how will be send to users
5 | */
6 |
7 | const messenger = {
8 | setFunctionsInResponse(req, res, next) {
9 | if (!next) next = function(){};
10 | if (!res.locals.messages ) res.locals.messages = [];
11 |
12 | /**
13 | * add one message in res.messages array
14 | *
15 | * @param {String} status success, error, warning, info ... etc
16 | * @param {String} message message text , use one translatable string
17 | * @param {Object} extraData extra data to set in message
18 | */
19 | res.addMessage = function addMessage(status, message, extraData) {
20 | if (status == 'error') status = 'danger';
21 | if (status == 'warn') status = 'warning';
22 |
23 | // suport for localization ... see node-i18n
24 | if (req.__) {
25 | if (typeof message != 'string') {
26 | message = req.__(message.text, message.vars);
27 | } else {
28 | message = req.__(message);
29 | }
30 | } else {
31 | // only set texts if dont have localization configured
32 | if (typeof message != 'string') {
33 | message = message.text;
34 | } else {
35 | message = message;
36 | }
37 | }
38 |
39 | // push messages array
40 | res.locals.messages.push({
41 | status: status,
42 | message: message,
43 | extraData: (extraData || null)
44 | });
45 | };
46 |
47 | /**
48 | * Get all messages
49 | *
50 | * @return {Array} messages array
51 | */
52 | res.getMessages = function getMessages() {
53 | let messages = [];
54 |
55 | if (this.locals && this.locals.messages) {
56 | messages = this.locals.messages;
57 | }
58 |
59 | // suport to flash messages
60 | if (req.flash && req.we.config.session) {
61 | let flashMessages = this.locals.req.flash('messages');
62 | if (flashMessages) {
63 | for (let i = 0; i < flashMessages.length; i++) {
64 | messages.push(flashMessages[i]);
65 | }
66 | }
67 | }
68 |
69 | return messages;
70 | };
71 |
72 | /**
73 | * Move all locals messages to session (flash)
74 | *
75 | * This function is used in http redirects to store messages between page changes
76 | */
77 | res.moveLocalsMessagesToFlash = function moveLocalsMessagesToFlash() {
78 | if (req.flash) {
79 | const msgs = res.getMessages();
80 | if (msgs && msgs.length) req.flash('messages', msgs);
81 | }
82 | };
83 |
84 | next();
85 | }
86 | };
87 |
88 | // alias
89 | module.middleware = module.setFunctionsInResponse;
90 |
91 | module.exports = messenger;
--------------------------------------------------------------------------------
/src/responses/JSONF.js:
--------------------------------------------------------------------------------
1 | const { isEmpty, isObject } = require('lodash');
2 |
3 | /**
4 | * JSON response formatter
5 | * @type {Object}
6 | */
7 | const jsonF = {
8 | /**
9 | * JSON response format
10 | *
11 | * @param {Object} data
12 | * @param {Object} req Express.js request
13 | * @param {Object} res Express.js response
14 | * @return {Object} JS object to send with res.send
15 | */
16 | jsonFormater(req, res) {
17 | if (!res.locals.model) {
18 | if (!res.locals.data) res.locals.data = {};
19 | // set messages
20 | res.locals.data.messages = res.locals.messages;
21 | return res.send(res.locals.data);
22 | }
23 |
24 | const response = {};
25 |
26 | if (req.we.config.sendNestedModels) {
27 | response[res.locals.model] = res.locals.data;
28 | } else {
29 | response[res.locals.model] = parseRecord(req, res, res.locals.data);
30 | }
31 |
32 | // check field privacity access
33 | if (res.locals.data) {
34 | req.we.db.checkRecordsPrivacity(res.locals.data);
35 | }
36 |
37 | response.meta = res.locals.metadata;
38 |
39 | if (!isEmpty( res.locals.messages) ) {
40 | // set messages
41 | response.messages = res.locals.messages;
42 | }
43 |
44 | res.send(response);
45 | }
46 | };
47 |
48 |
49 | /**
50 | * Parse one record associations for JSON format to change association model object to association model id
51 | *
52 | * @param {Object} req Express.js request
53 | * @param {Object} res Express.js response
54 | * @param {Obejct} record Record to parse
55 | * @return {Object} returns the parsed record
56 | */
57 | function parseRecord(req, res, record) {
58 | for (var associationName in res.locals.Model.associations) {
59 | if (!record[associationName]) {
60 | if ( record.dataValues[ associationName + 'Id' ] ) {
61 | record.dataValues[ associationName ] = record[ associationName + 'Id' ];
62 | }
63 | } else {
64 | if (record.dataValues[ associationName + 'Id' ]) {
65 | record.dataValues[ associationName ] = record[ associationName + 'Id' ];
66 | } else if ( isObject(record[ associationName ] && record[ associationName ].id) ) {
67 | record.dataValues[ associationName ] = record[ associationName ].id;
68 | // if is a NxN association
69 | } else if( req.we.utils.isNNAssoc ( record[ associationName ] ) ) {
70 | record.dataValues[ associationName ] = record[ associationName ].id;
71 | } else {
72 | for (var i = record.dataValues[ associationName ].length - 1; i >= 0; i--) {
73 | record.dataValues[ associationName ][i] = record.dataValues[ associationName ][i].id;
74 | }
75 | }
76 | }
77 | }
78 | return record;
79 | }
80 |
81 | module.exports = jsonF;
--------------------------------------------------------------------------------
/src/Sanitizer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We.js sanitizer to sanitize model and text variable data
3 | *
4 | */
5 | const sanitizeHtml = require('sanitize-html');
6 |
7 | function Sanitizer (we) {
8 | this.we = we;
9 | let sanitizer = this;
10 |
11 | // after define all models add term field hooks in models how have terms
12 | we.hooks.on('we:models:set:joins', this.setSanitizeModelAttrs);
13 |
14 | /**
15 | * sequelize hook handler to sanitize all text fields with we sanitizer
16 | *
17 | * @param {record} r
18 | * @param {options} opts
19 | * @param {FuncDBBeforeUpdateAndCreateHooktion} done
20 | */
21 | this.DBBeforeUpdateAndCreateHook = function DBBeforeUpdateAndCreateHook(r) {
22 | sanitizer.sanitizeModelAttrs(r, this.name);
23 | };
24 | }
25 |
26 | /**
27 | * Set sanitizer in before create and update model hooks
28 | *
29 | * @param {Object} we we.js object
30 | * @param {Function} done callback
31 | */
32 | Sanitizer.prototype.setSanitizeModelAttrs = function setSanitizeModelAttrs (we, done) {
33 | let models = we.db.models;
34 | let sanitizer = we.sanitizer;
35 |
36 | for (let modelName in models) {
37 | // set sanitizer hook
38 | models[modelName].addHook('beforeCreate', 'sanitizeBeforeSv', sanitizer.DBBeforeUpdateAndCreateHook);
39 | models[modelName].addHook('beforeUpdate', 'sanitizeBeforeUP', sanitizer.DBBeforeUpdateAndCreateHook);
40 | }
41 |
42 | done();
43 | };
44 |
45 | /**
46 | * Sanitize one text html
47 | * @param {String} dirty html to sanitize
48 | * @return {String} sanitized html
49 | */
50 | Sanitizer.prototype.sanitize = function sanitize (dirty) {
51 | return sanitizeHtml(dirty, this.we.config.security.sanitizer);
52 | };
53 |
54 | /**
55 | * Sanitize all text attrs in one object
56 | *
57 | * @param {Object} obj sanitize obj attrs
58 | * @return {Object} return obj
59 | */
60 | Sanitizer.prototype.sanitizeAllAttr = function sanitizeAllAttr(obj){
61 | for (let prop in obj) {
62 |
63 | if (prop !== 'id') {
64 | if (typeof obj[prop] == 'string') {
65 | obj[prop] = this.sanitize(obj[prop]);
66 | }
67 | }
68 | }
69 | return obj;
70 | };
71 |
72 | /**
73 | * Sanitize all sequelize text record attrs
74 | *
75 | * @param {Object} record sequelize record to sanitize
76 | * @param {String} modelName model name
77 | * @return {Object} return obj
78 | */
79 | Sanitizer.prototype.sanitizeModelAttrs = function sanitizeModelAttrs (record, modelName) {
80 | let db = this.we.db;
81 |
82 | for (let prop in record.dataValues) {
83 | if (prop !== 'id') {
84 | if (typeof record.dataValues[prop] == 'string') {
85 | // if dont have value
86 | if (!record.getDataValue(prop)) continue;
87 | // check skip cfg, skipSanitizer
88 | if (
89 | !db.modelsConfigs[modelName] ||
90 | !db.modelsConfigs[modelName].definition[prop] ||
91 | db.modelsConfigs[modelName].definition[prop].skipSanitizer
92 | ) {
93 | continue;
94 | }
95 | // sanitize this value
96 | record.setDataValue(prop, this.sanitize(record.getDataValue(prop)));
97 | }
98 | }
99 | }
100 | return record;
101 | };
102 |
103 | module.exports = Sanitizer;
--------------------------------------------------------------------------------
/src/staticConfig/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Static configs loader
3 | */
4 |
5 | const fs = require('fs'),
6 | path = require('path'),
7 | _ = require('lodash');
8 |
9 | /**
10 | * Get project static configs
11 | *
12 | * @param {String} projectPath project path ( opcional )
13 | * @return {Object} configs
14 | */
15 | function staticConfig (projectPath, app) {
16 | if (!projectPath) throw new Error('project path is required for load static configs');
17 |
18 | // return configs if already is loaded
19 | if (app.staticConfigsIsLoad) return app.config;
20 |
21 | // - load and merge project configs
22 |
23 | let projectConfigFolder = app.projectConfigFolder;
24 |
25 | let files = [];
26 |
27 | try {
28 | files = fs.readdirSync(projectConfigFolder);
29 | } catch(e) {
30 | if (e.code != 'ENOENT') console.error('Error on load project config folder: ', e);
31 | }
32 |
33 | let file;
34 | for (let i = 0; i < files.length; i++) {
35 | if (files[i] == 'local.js') continue; // skip locals.js to load after all
36 | if (!files[i].endsWith('.js')) continue; // only accepts .js config files
37 |
38 | file = path.resolve(projectConfigFolder, files[i]);
39 | // skip dirs
40 | if (fs.lstatSync(file).isDirectory()) continue;
41 | _.merge(app.config, require(file));
42 | }
43 |
44 | let jsonConfiguration = staticConfig.readJsonConfiguration(projectConfigFolder);
45 | let localConfigFile = staticConfig.readLocalConfigFile(projectConfigFolder);
46 |
47 | // load project local config file
48 | _.merge(app.config, jsonConfiguration, localConfigFile);
49 |
50 | app.staticConfigsIsLoad = true;
51 |
52 | return app.config;
53 | }
54 |
55 | /**
56 | * Read the config/locals.js configuration file
57 | */
58 | staticConfig.readLocalConfigFile = function (projectConfigFolder) {
59 | try {
60 | // load local.js after others configs
61 | return require( path.resolve(projectConfigFolder, 'local.js') );
62 | } catch (e) {
63 | if (e.code != 'MODULE_NOT_FOUND' ) {
64 | console.error('Unknow error on load local.js config:', e);
65 | }
66 | return {};
67 | }
68 | };
69 |
70 | /**
71 | * Read JSON Configuration file and create it if not exists
72 | */
73 | staticConfig.readJsonConfiguration = function (projectConfigFolder) {
74 | const dirCFJSON = path.resolve(projectConfigFolder, 'configuration.json');
75 |
76 | try {
77 | // load configuration.json after others but before local.js
78 | return JSON.parse(fs.readFileSync(dirCFJSON));
79 | } catch (e) {
80 | if (e.code != 'ENOENT' ) {
81 | console.error('Unknow error on load config/configuration.json config:', e);
82 | } else {
83 | // if not exists create it
84 | fs.writeFileSync(dirCFJSON, '{}');
85 | console.log('The file configuration.json was created');
86 | }
87 | return {};
88 | }
89 | };
90 |
91 | staticConfig.loadPluginConfigs = function loadPluginConfigs(we) {
92 | if (we.pluginConfigsIsLoad) return we.config;
93 |
94 | const pluginManager = we.pluginManager;
95 |
96 | let pluginConfigs = {};
97 |
98 | // - load and merge plugin configs
99 | for (let pluginName in pluginManager.plugins) {
100 | _.merge(pluginConfigs, pluginManager.plugins[pluginName].configs);
101 | }
102 | // load project local config file
103 | we.config = _.merge(pluginConfigs, we.config);
104 |
105 | we.pluginConfigsIsLoad = true;
106 |
107 | return we.config;
108 | };
109 |
110 | module.exports = staticConfig;
111 |
--------------------------------------------------------------------------------
/src/express/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We.js express module
3 | */
4 |
5 | const express = require('express'),
6 | compress = require('compression'),
7 | favicon = require('serve-favicon'),
8 | bodyParser = require('body-parser'),
9 | responseTypeMD = require('../Router/responseType.js'),
10 | messages = require('../messages'),
11 | cors = require('cors');
12 |
13 | module.exports = function initExpress (we) {
14 | const weExpress = express();
15 | // express response compression middleware
16 | weExpress.use(compress());
17 | // remove uneed x-powered-by header
18 | weExpress.disable('x-powered-by');
19 | // set default vars
20 | weExpress.use(function setDefaultVars (req, res, next) {
21 | // set default req.getWe for suport with this getter
22 | req.getWe = function getWejs() { return we; };
23 | // set message functions in response
24 | messages.setFunctionsInResponse(req, res);
25 | // save a reference to appName
26 | res.locals.appName = we.config.appName || '';
27 | // set default app title
28 | res.locals.title = we.config.appName || '';
29 | // set req to be avaible in template vars
30 | Object.defineProperty(res.locals, 'req', {
31 | get: function getReq() { return req; }
32 | });
33 | // set metadata var
34 | res.locals.metadata = {};
35 | // metadata tags to print in html response
36 | res.locals.metatag = '';
37 | // set user role names array
38 | req.userRoleNames = [];
39 | // save env in locals
40 | res.locals.env = we.env;
41 | // add default is authenticated check
42 | if (!req.isAuthenticated) req.isAuthenticated = we.utils.isAuthenticated.bind(req);
43 | // set response type
44 | return responseTypeMD(req, res, ()=> {
45 | // alias targets redirect for html request
46 | if (we.plugins['we-plugin-url-alias'] && req.haveAlias && req.accepts('html')) {
47 | // is a target how have alias then redirect to it
48 | res.writeHead(307, {
49 | 'Location': req.haveAlias.alias + (req.aliasQuery || ''),
50 | 'Content-Type': 'text/plain',
51 | 'Cache-Control':'public, max-age=345600',
52 | 'Expires': new Date(Date.now() + 345600000).toUTCString()
53 | });
54 | return res.end();
55 | } else {
56 | next();
57 | }
58 | });
59 | });
60 | // send set params event
61 | we.events.emit('we:express:set:params', { we: we, express: weExpress });
62 | // custom we.js responses like: res.ok() and res.forbidden()
63 | weExpress.use(we.responses.setCustomResponses);
64 | // CORS https://github.com/troygoode/node-cors
65 | weExpress.options('*', cors(we.config.security.CORS));
66 | // favicon config
67 | if (we.config.favicon) weExpress.use(favicon(we.config.favicon));
68 |
69 | if (we.config.enableRequestLog) {
70 | const logger = require('morgan');
71 | weExpress.use(logger('dev'));
72 | }
73 |
74 | // robots .txt file middleware
75 | weExpress.get('/robots.txt', function robotsTXTmiddleware(req, res) {
76 | if (req.we.config.robotsTXT) {
77 | res.sendFile(req.we.config.robotsTXT);
78 | } else {
79 | res.notFound();
80 | }
81 | });
82 |
83 | weExpress.use(bodyParser.json(we.config.bodyParser));
84 | weExpress.use(bodyParser.json({ type: 'application/vnd.api+json' }));
85 | weExpress.use(bodyParser.urlencoded({ extended: false }));
86 |
87 | weExpress.use(we.utils.cookieParser());
88 | // set session store
89 | require('./sessionStore')(we, weExpress);
90 | // add flash middleware if session is avaible
91 | if (we.config.session) {
92 | const flash = require('connect-flash');
93 | weExpress.use(flash());
94 | }
95 |
96 | // set public folders
97 | if (!we.config.disablePublicFolder) {
98 | require('./publicFolders')(we, weExpress);
99 | }
100 | return weExpress;
101 | };
102 |
--------------------------------------------------------------------------------
/test/tests/requests/plugin.fastload.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | request = require('supertest'),
3 | helpers = require('we-test-tools').helpers,
4 | Chance = require('chance'),
5 | chance = new Chance();
6 |
7 | let _, http, we;
8 |
9 | function dogStub() {
10 | return {
11 | name: chance.name(),
12 | };
13 | }
14 |
15 | describe('plugin.fastload.requests', function() {
16 |
17 | before(function (done) {
18 | http = helpers.getHttp();
19 | we = helpers.getWe();
20 | _ = we.utils._;
21 | return done();
22 | });
23 |
24 | afterEach(function(done) {
25 | // delete old dogs
26 | we.db.models.dog.truncate()
27 | .then( ()=> {
28 | done();
29 | return null;
30 | })
31 | .catch(done);
32 | });
33 |
34 | describe('json', function() {
35 | describe('GET /dog', function(){
36 | it ('should return 3 dogs', function (done) {
37 | const data = [
38 | dogStub(),
39 | dogStub(),
40 | dogStub()
41 | ];
42 |
43 | we.db.models.dog
44 | .bulkCreate(data)
45 | .spread( ()=> {
46 | request(http)
47 | .get('/dog')
48 | .set('Accept', 'application/json')
49 | .expect(200)
50 | .end(function (err, res) {
51 | if (err) {
52 | console.error('res.text>',res.text);
53 | throw err;
54 | }
55 |
56 | for (var i = 0; i < data.length; i++) {
57 | assert(res.body.dog[i].id);
58 | }
59 |
60 | assert.equal(res.body.meta.count, data.length, `Should return 3 dogs`);
61 |
62 | done();
63 | });
64 | })
65 | .catch(done);
66 | });
67 | });
68 |
69 | it ('/dog/count should get dogs count', function (done) {
70 | const data = [
71 | dogStub(),
72 | dogStub(),
73 | dogStub()
74 | ];
75 |
76 | we.db.models.dog
77 | .bulkCreate(data)
78 | .spread( ()=> {
79 | request(http)
80 | .get('/dog/count')
81 | .set('Accept', 'application/json')
82 | .expect(200)
83 | .end( (err, res)=> {
84 | if (err) {
85 | console.error('res.text>',res.text);
86 | throw err;
87 | }
88 |
89 | assert(res.body.count, `Count should be present in response`);
90 | assert.equal(res.body.count, 3, `Count should be 3`);
91 |
92 | done();
93 | });
94 | })
95 | .catch(done);
96 | });
97 |
98 | });
99 |
100 | describe('GET /dog/:id', function(){
101 | it ('Should get one dog', function (done) {
102 | we.db.models.dog
103 | .create(dogStub())
104 | .then( (p)=> {
105 | request(http)
106 | .get('/dog/'+p.id)
107 | .set('Accept', 'application/json')
108 | .expect(200)
109 | .end( (err, res)=> {
110 | if (err) throw err;
111 |
112 | assert(res.body.dog.id);
113 | assert.equal(res.body.dog.id, p.id);
114 | assert.equal(res.body.dog.name, p.name);
115 |
116 | done();
117 | });
118 | }).catch(done);
119 | });
120 | });
121 |
122 |
123 | describe('POST /dog/:id/bark', function(){
124 | it ('Should return dog bark', function (done) {
125 | we.db.models.dog
126 | .create(dogStub())
127 | .then( (p)=> {
128 |
129 | request(http)
130 | .post('/dog/'+p.id+'/bark')
131 | .set('Accept', 'application/json')
132 | .expect(200)
133 | .end( (err, res)=> {
134 | if (err) throw err;
135 |
136 | assert(res.body.result);
137 | assert.equal(res.body.result, 'AuAU');
138 |
139 | done();
140 | });
141 |
142 | })
143 | .catch(done);
144 | });
145 | });
146 |
147 | });
148 |
--------------------------------------------------------------------------------
/src/class/Theme.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We.js Theme prototype
3 | */
4 |
5 | const _ = require('lodash'),
6 | path = require('path');
7 |
8 | module.exports = function getThemePrototype(we) {
9 | /**
10 | * We.js theme Class constructor
11 | *
12 | * @param {string} name theme npm pakage name
13 | * @param {string} projectPath project path where the theme is instaled
14 | */
15 | function Theme (name, projectPath, options) {
16 | if (!name || (typeof name !== 'string') ) {
17 | return new Error('Param name is required for instantiate a new Theme object');
18 | }
19 | if (!options) options = {};
20 |
21 | this.we = we;
22 | this.hooks = this.we.hooks;
23 | this.events = this.we.events;
24 |
25 | const self = this;
26 |
27 | this.config = {};
28 |
29 | this.projectPath = projectPath;
30 |
31 | // npm theme path / folder
32 | this.config.themeFolder = options.themeFolder || path.resolve(projectPath, 'node_modules', name);
33 |
34 | this.config.shortThemeFolder = options.themeFolder || 'node_modules' + '/' + name;
35 |
36 | // load theme module
37 | delete require.cache[require.resolve(this.config.themeFolder)];
38 | const npmModule = require(this.config.themeFolder);
39 |
40 | // always initialize all instance properties
41 | this.name = name;
42 |
43 | const packageJSONPath = this.config.themeFolder+'/package.json';
44 | delete require.cache[require.resolve(packageJSONPath)];
45 | this['package.json'] = require(packageJSONPath);
46 | // shortcut for get installed theme version
47 | this.version = this['package.json'].version;
48 |
49 | this.templates = {};
50 | this.layouts = {};
51 | this.widgets = {};
52 |
53 | this.tplsFolder = path.resolve(self.config.themeFolder, 'templates/server');
54 |
55 | _.merge(this, npmModule);
56 | }
57 |
58 | Theme.prototype.init = function init(cb) {
59 | const self = this;
60 |
61 | // if autoLoadAllTemplates not is set or is true load all template names
62 | if (!we.config.cacheThemeTemplates) {
63 | we.utils.listFilesRecursive(self.tplsFolder, (err, files)=> {
64 | if (err) return cb(err);
65 |
66 | files.filter(function filterTPL(f) {
67 | if (f.endsWith('.hbs')) return true;
68 | return false;
69 | })
70 | .forEach(function loadTPL(f) {
71 | // remove the base url and the file extension
72 | let name = f.replace(self.tplsFolder + path.sep, '')
73 | .replace('.hbs', '');
74 |
75 | // ensures that template names always have / slashes
76 | if (path.sep != '/') name = name.split(path.sep).join('/');
77 |
78 | self.templates[name] = f;
79 | });
80 |
81 | cb();
82 | });
83 | } else {
84 | cb();
85 | }
86 | };
87 | /**
88 | * Theme config object
89 | *
90 | * @type {Object}
91 | */
92 | // Theme.prototype.config = {};
93 |
94 | /**
95 | * Render one template with variables and template
96 | *
97 | * @param {object} req express js request
98 | * @param {object} res express.js response
99 | * @param {string} template the template name
100 | * @param {object} data data passed to template
101 | */
102 | Theme.prototype.render = function render(req, res, template, data) {
103 |
104 | if (!res.locals.layout ) {
105 | res.locals.layout = this.config.layoutPath;
106 | }
107 |
108 | res.view( path.resolve(this.config.templatesFolder, template), data );
109 | };
110 |
111 | /**
112 | * Get theme layout path
113 | */
114 | Theme.prototype.getThemeLayout = function getThemeSailsTemplatesFolder() {
115 | return this.config.layoutPath;
116 | };
117 |
118 | /**
119 | * Get theme email template folder
120 | */
121 | Theme.prototype.getThemeEmailTemplatesFolder = function getThemeSailsTemplatesFolder(){
122 | return path.resolve(this.config.themeFolder, this.config.emailTemplates.path);
123 | };
124 |
125 | return Theme;
126 | };
--------------------------------------------------------------------------------
/test/tests/modules/lib.utils.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var path = require('path');
4 | var utils, we;
5 |
6 | describe('lib/utils', function () {
7 | before(function (done) {
8 | utils = require('../../../src/utils');
9 | we = helpers.getWe();
10 | done();
11 | });
12 |
13 | describe('listFilesRecursive', function() {
14 | it('should list files in we-plugin-post dir', function (done) {
15 | var folder = path.resolve(process.cwd(), 'node_modules', 'we-plugin-post');
16 |
17 | utils.listFilesRecursive(folder, function(err, files){
18 | if (err) return done(err);
19 |
20 | assert.equal(files.length, 12);
21 |
22 | assert(files.indexOf(folder+'/package.json') > -1);
23 | assert(files.indexOf(folder+'/plugin.js') > -1);
24 | assert(files.indexOf(folder+'/server/controllers/post.js') > -1);
25 | assert(files.indexOf(folder+'/server/models/hero.json') > -1);
26 | assert(files.indexOf(folder+'/server/models/post.js') > -1);
27 | assert(files.indexOf(folder+'/server/models/user.js') > -1);
28 |
29 | done();
30 | });
31 | });
32 | it('should return a empty list if the dir not is found', function (done) {
33 | var folder = path.resolve(process.cwd(), 'asadasdas');
34 |
35 | utils.listFilesRecursive(folder, function(err, files){
36 | if (err) return done(err);
37 |
38 | assert.equal(files.length, 0);
39 |
40 | done();
41 | });
42 | });
43 | it('should a empty list if the dir is one file', function (done) {
44 | var folder = path.resolve(process.cwd(), 'plugin.js');
45 |
46 | utils.listFilesRecursive(folder, function(err, files) {
47 | assert(!files);
48 | assert.equal(err.code, 'ENOTDIR');
49 |
50 | done();
51 | });
52 | });
53 | });
54 |
55 | describe('isNNAssoc', function() {
56 | it ('should return true if are a belongsTo assoc', function(done) {
57 | assert(utils.isNNAssoc({
58 | associationType: 'belongsTo'
59 | }));
60 | done();
61 | });
62 | it ('should return false if are a hasMany assoc', function(done) {
63 | assert(!utils.isNNAssoc({
64 | associationType: 'hasMany'
65 | }));
66 | done();
67 | });
68 | });
69 |
70 | describe('getRedirectUrl', function() {
71 | it ('should return a valid url from body', function(done) {
72 | var req = {
73 | we: we,
74 | body: {
75 | redirectTo: '/test'
76 | }
77 | };
78 |
79 | var url = utils.getRedirectUrl(req);
80 | assert.equal(url, '/test');
81 |
82 | done();
83 | });
84 |
85 | it ('should return null to invalid url from body', function(done) {
86 | var req = {
87 | we: we,
88 | body: {
89 | redirectTo: 'http://google.com'
90 | }
91 | };
92 |
93 | var url = utils.getRedirectUrl(req);
94 | assert.equal(url, null);
95 |
96 | done();
97 | });
98 |
99 | it ('should return url from service', function(done) {
100 | we.config.services.dance = {
101 | url: '/dancee'
102 | };
103 |
104 | var req = {
105 | we: we,
106 | query: {
107 | service: 'dance'
108 | }
109 | };
110 |
111 | var url = utils.getRedirectUrl(req);
112 | assert.equal(url, '/dancee');
113 |
114 | delete we.config.services.dance;
115 |
116 | done();
117 | });
118 |
119 | it ('should return url from query.redirectTo', function(done) {
120 |
121 | var req = {
122 | we: we,
123 | query: {
124 | redirectTo: '/iooo'
125 | }
126 | };
127 |
128 | var url = utils.getRedirectUrl(req);
129 | assert.equal(url, '/iooo');
130 |
131 | done();
132 | });
133 | });
134 |
135 | describe('parseAttributes', function(){
136 | it ('should return false if are a hasMany assoc', function (done) {
137 |
138 | var str = utils.helper.parseAttributes({
139 | hash: {
140 | class: 'btn btn-default'
141 | }
142 | });
143 |
144 | assert(str.indexOf('class="btn btn-default"') > -1);
145 |
146 | done();
147 | });
148 | })
149 | });
--------------------------------------------------------------------------------
/test/tests/modules/database.defaultModelDefinitionConfigs.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var we, dmdc, contextLoader, instanceMethods;
4 |
5 | describe('database.defaultModelDefinitionConfigs', function () {
6 |
7 | before(function (done) {
8 | we = helpers.getWe();
9 | dmdc = we.db.defaultModelDefinitionConfigs;
10 | contextLoader = we.db.defaultClassMethods.contextLoader;
11 | instanceMethods = we.db.defaultInstanceMethods;
12 | done();
13 | });
14 |
15 | describe('define.classMethods', function() {
16 |
17 | it('classMethods.contextLoader should return done id dont have res.local.id', function (done) {
18 | var req = {
19 | userRoleNames: [],
20 | locals: {}
21 | };
22 | var res = {
23 | locals: { loadCurrentRecord: true }
24 | };
25 |
26 | var callback = function callback() {
27 | done();
28 | };
29 |
30 | contextLoader(req, res, callback);
31 | });
32 |
33 | it('classMethods.contextLoader should set owner if creatorId = req.user.id', function (done) {
34 | var creatorId = 2016;
35 | var recordId = 10;
36 | var req = {
37 | isAuthenticated: function() { return true; },
38 | user: { id: creatorId },
39 | userRoleNames: [],
40 | locals: {}
41 | };
42 | var res = {
43 | locals: {
44 | id: recordId, // record ID
45 | loadCurrentRecord: true
46 | },
47 | };
48 |
49 | var callback = function callback() {
50 | assert(req.userRoleNames.indexOf('owner') > -1);
51 | done();
52 | };
53 |
54 | contextLoader.bind({
55 | findOne: function(opts) {
56 | assert(opts.where.id, recordId);
57 |
58 | return new we.db.Sequelize.Promise(function(resolve){
59 | resolve({
60 | dataValues: {
61 | creatorId: creatorId
62 | },
63 | isOwner: function(id) {
64 | assert.equal(id, creatorId);
65 | return true;
66 | }
67 | });
68 | });
69 | }
70 | })(req, res, callback);
71 | });
72 | });
73 |
74 | describe('define.instanceMethods', function() {
75 | describe('isOwner', function() {
76 | it ('isOwner should return true if uid = this.creatorId', function () {
77 | assert(instanceMethods.isOwner.bind({
78 | creatorId: 2016
79 | })(2016));
80 | });
81 | it ('isOwner should return false if uid != this.creatorId', function () {
82 | assert(!instanceMethods.isOwner.bind({
83 | creatorId: 20
84 | })(2016));
85 | });
86 | });
87 |
88 | describe('getPath', function() {
89 | it ('getPath should throw error without req', function () {
90 | var error = null;
91 | try {
92 | instanceMethods.getPath();
93 | } catch(e) {
94 | error = e;
95 | }
96 |
97 | assert(error);
98 | });
99 | it ('getPath should get url from req.we.router.urlTo'
100 | // ,function () {
101 | // var req = {
102 | // we: we,
103 | // paramsArray: []
104 | // };
105 |
106 | // var url = instanceMethods.getPath.bind({
107 | // id: 11,
108 | // '$modelOptions': {
109 | // name: {
110 | // singular: 'user'
111 | // }
112 | // }
113 | // })(req);
114 |
115 | // assert(url);
116 | // assert.equal(url, '/user/11');
117 | // }
118 | );
119 | });
120 |
121 | describe('getLink', function() {
122 | it ('getLink should throw error without req', function () {
123 | var error = null;
124 | try {
125 | instanceMethods.getLink();
126 | } catch(e) {
127 | error = e;
128 | }
129 | assert(error);
130 | });
131 |
132 | it ('getLink should return url with hostname', function () {
133 | var req = { we: we };
134 |
135 | var url = instanceMethods.getLink.bind({
136 | getPath: function() {
137 | return '/path';
138 | }
139 | })(req);
140 | assert(url);
141 | assert.equal(url, we.config.hostname+'/path');
142 | });
143 | });
144 | });
145 | });
--------------------------------------------------------------------------------
/locales/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "user.profile.view": "View profile",
3 | "user.profile.edit": "Edit profile",
4 | "we.email.AccontActivationEmail.subject": "User account validation email",
5 | "widget.delete.confirm.msg": "Are you sure you want to delete this widget?",
6 | "Validation isEmail failed": "The email format is invalid",
7 | "Widget": "Widget",
8 | "Edit": "Edit",
9 | "Delete": "Delete",
10 | "widget.title": " ",
11 | "widget.context": "Widget context",
12 | "widget.add": "Add widget",
13 | "widget.sort": "Sort widgets",
14 | "widget.visibility": "Show the widget: ",
15 | "widget.in-portal": "In all pages",
16 | "widget.in-session": "In this session",
17 | "widget.in-session-record": "In contents of this session",
18 | "widget.in-page": "In current page",
19 | "widget.in-context": "In current context",
20 | "layout.select.widget.type": "Select widget type",
21 | "layout.edit.hide.btn": "Save layout",
22 | "layout.edit.btn": "Edit layout",
23 | "image.remove": "remove image",
24 | "image.selector": "Image selector",
25 | "image.uploader.title": "Upload",
26 | "image.selector.title": "Select from salved",
27 | "image.upload.selector.btn": "Select to upload",
28 | "file.image.select.btn": "select image",
29 | "paginate.sumary": "{{recordsLength}} of {{count}}",
30 | "select.option.text": "Select one option ...",
31 | "menu.find": "Menus",
32 | "menu.create": "Create menu",
33 | "form-menu-name": "Name",
34 | "form-placeholder-menu-name": " ",
35 | "form-helper-menu-name": " ",
36 | "form-menu-class": "Class",
37 | "form-placeholder-menu-class": " ",
38 | "form-helper-menu-class": " ",
39 | "link.create": "Criar link",
40 | "form-link-href": "HREF",
41 | "form-placeholder-link-href": " ",
42 | "form-helper-link-href": "Link url",
43 | "form-link-text": "Text",
44 | "form-placeholder-link-text": " ",
45 | "form-helper-link-text": " ",
46 | "form-link-title": "Title",
47 | "form-placeholder-link-title": " ",
48 | "form-helper-link-title": " ",
49 | "form-link-class": "Class",
50 | "form-placeholder-link-class": " ",
51 | "form-helper-link-class": " ",
52 | "form-link-style": "Style",
53 | "form-placeholder-link-style": " ",
54 | "form-helper-link-style": " ",
55 | "form-link-target": "Target",
56 | "form-placeholder-link-target": " ",
57 | "form-helper-link-target": " ",
58 | "form-link-rel": "Rel",
59 | "form-placeholder-link-rel": " ",
60 | "form-helper-link-rel": " ",
61 | "form-link-key": "Key",
62 | "form-placeholder-link-key": " ",
63 | "form-helper-link-key": " ",
64 | "form-link-depth": "Depth",
65 | "form-placeholder-link-depth": " ",
66 | "form-helper-link-depth": " ",
67 | "form-link-weight": "Weight",
68 | "form-placeholder-link-weight": " ",
69 | "form-helper-link-weight": " ",
70 | "form-link-parent": "Parent",
71 | "form-placeholder-link-parent": " ",
72 | "form-helper-link-parent": " ",
73 | "role.create": "Create role",
74 | "role.edit": "Edit role",
75 | "form-role-name": "Name",
76 | "form-placeholder-role-name": " ",
77 | "form-helper-role-name": " ",
78 | "form-role-description": "Description",
79 | "form-placeholder-role-description": " ",
80 | "form-helper-role-description": " ",
81 | "form-role-permissions": "Permissions",
82 | "form-placeholder-role-permissions": " ",
83 | "form-helper-role-permissions": " ",
84 | "widget.html.label": "HTML or text block",
85 | "permission.link": "Permissions",
86 | "widget.update": "Update widget",
87 | "widget.create": "Create widget",
88 | "auth.register.spam": "You email {{email}} is marked as span by Google Recaptcha",
89 | "Home": "Home",
90 | "updatedAt": "Updated at",
91 | "createdAt": "Created at",
92 | "widget.invalid.context": "Invalid widget context",
93 | "response.badRequest.title": "Bad request",
94 | "response.badRequest.description": "Something is wrong with you request or data send to server",
95 | "response.notFound.title": "Not found",
96 | "response.notFound.description": "Page not found in this system",
97 | "response.forbidden.title": "Forbidden!",
98 | "response.forbidden.description": "You dont have access to this page!",
99 | "response.serveError.title": "Server error!",
100 | "response.serveError.description": " ",
101 | "auth.register.acceptTerms.required": "You need to accept your terms to create account",
102 | "skip.to.content.link.text": "Skip to main content",
103 |
104 | "role.delete.confirm.msg": "Are you sure you want to delete this role?",
105 | "Search": "Search"
106 | }
--------------------------------------------------------------------------------
/src/class/Controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We.js default controller prototype
3 | *
4 | * All controllers is instance of this Controller prototype and have all actions defined here
5 | */
6 | const _ = require('lodash');
7 |
8 | /**
9 | * Constructor
10 | */
11 | function Controller (attrs) {
12 | for (let attr in attrs) {
13 | if (attrs[attr].bind) {
14 | this[attr] = attrs[attr].bind(this);
15 | } else {
16 | this[attr] = attrs[attr];
17 | }
18 | }
19 | }
20 |
21 | Controller.prototype = {
22 | /**
23 | * Default find action
24 | *
25 | * @param {Object} req express.js request
26 | * @param {Object} res express.js response
27 | */
28 | find(req, res) {
29 | return res.locals.Model
30 | .findAndCountAll(res.locals.query)
31 | .then(function afterFindAndCount (record) {
32 | res.locals.metadata.count = record.count;
33 | res.locals.data = record.rows;
34 | res.ok();
35 | })
36 | .catch(res.queryError);
37 | },
38 |
39 | /**
40 | * Default count action
41 | *
42 | * Built for only send count as JSON
43 | *
44 | * @param {Object} req express.js request
45 | * @param {Object} res express.js response
46 | */
47 | count(req, res) {
48 | return res.locals.Model
49 | .count(res.locals.query)
50 | .then( (count)=> {
51 | res.status(200).send({ count: count });
52 | })
53 | .catch(res.queryError);
54 | },
55 | /**
56 | * Default findOne action
57 | *
58 | * Record is preloaded in context loader by default and is avaible as res.locals.data
59 | *
60 | * @param {Object} req express.js request
61 | * @param {Object} res express.js response
62 | */
63 | findOne(req, res, next) {
64 | if (!res.locals.data) {
65 | return next();
66 | }
67 | // by default record is preloaded in context load
68 | res.ok();
69 | },
70 | /**
71 | * Create and create page actions
72 | *
73 | * @param {Object} req express.js request
74 | * @param {Object} res express.js response
75 | */
76 | create(req, res) {
77 | if (!res.locals.template) {
78 | res.locals.template = res.locals.model + '/' + 'create';
79 | }
80 |
81 | if (!res.locals.data) {
82 | res.locals.data = {};
83 | }
84 |
85 | if (req.method === 'POST') {
86 | if (req.isAuthenticated && req.isAuthenticated()) {
87 | req.body.creatorId = req.user.id;
88 | }
89 |
90 | _.merge(res.locals.data, req.body);
91 |
92 | return res.locals.Model
93 | .create(req.body)
94 | .then(function afterCreate (record) {
95 | res.locals.data = record;
96 | res.created();
97 | })
98 | .catch(res.queryError);
99 | } else {
100 | res.ok();
101 | }
102 | },
103 | /**
104 | * Edit, edit page and update action
105 | *
106 | * Record is preloaded in context loader by default and is avaible as res.locals.data
107 | *
108 | * @param {Object} req express.js request
109 | * @param {Object} res express.js response
110 | */
111 | edit(req, res) {
112 | if (!res.locals.template) {
113 | res.locals.template = res.local.model + '/' + 'edit';
114 | }
115 |
116 | let record = res.locals.data;
117 |
118 | if (req.we.config.updateMethods.indexOf(req.method) >-1) {
119 | if (!record) {
120 | return res.notFound();
121 | }
122 |
123 | record.update(req.body)
124 | .then(function reloadAssocs(n) {
125 | return n.reload()
126 | .then(function() {
127 | return n;
128 | });
129 | })
130 | .then(function afterUpdate (newRecord) {
131 | res.locals.data = newRecord;
132 | res.updated();
133 | })
134 | .catch(res.queryError);
135 | } else {
136 | res.ok();
137 | }
138 | },
139 | /**
140 | * Delete and delete action
141 | *
142 | * @param {Object} req express.js request
143 | * @param {Object} res express.js response
144 | */
145 | delete(req, res) {
146 | if (!res.locals.template) {
147 | res.locals.template = res.local.model + '/' + 'delete';
148 | }
149 |
150 | let record = res.locals.data;
151 |
152 | if (!record) {
153 | return res.notFound();
154 | }
155 |
156 | res.locals.deleteMsg = res.locals.model + '.delete.confirm.msg';
157 |
158 | if (req.method === 'POST' || req.method === 'DELETE') {
159 | record
160 | .destroy()
161 | .then(function afterDestroy () {
162 | res.locals.deleted = true;
163 | res.deleted();
164 | })
165 | .catch(res.queryError);
166 | } else {
167 | res.ok();
168 | }
169 | }
170 | };
171 |
172 | module.exports = Controller;
173 |
--------------------------------------------------------------------------------
/test/tests/modules/lib.messages.test.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var helpers = require('we-test-tools').helpers;
3 | var messages, we;
4 |
5 | describe('lib/messages', function () {
6 | before(function (done) {
7 | messages = require('../../../src/messages');
8 | we = helpers.getWe();
9 | done();
10 | });
11 |
12 | it('setFunctionsInResponse should set addMessage, getMessages and moveLocalsMessagesToFlash', function (done) {
13 | var req = {};
14 | var res = {
15 | locals: {}
16 | };
17 |
18 | messages.setFunctionsInResponse(req, res);
19 |
20 | assert(res.addMessage);
21 | assert(res.getMessages);
22 | assert(res.moveLocalsMessagesToFlash);
23 |
24 | assert(res.locals.messages);
25 |
26 | done();
27 | });
28 |
29 | describe('response with messageMethods', function(){
30 | var res, req;
31 | before(function (done) {
32 | res = { locals: {} }
33 | req = { __: we.i18n.__ }
34 |
35 | messages.setFunctionsInResponse(req, res)
36 |
37 | done()
38 | });
39 | describe('res.addMessage', function(){
40 | it ('should add one error message', function(done) {
41 | res.addMessage('error', 'we.test.message.1');
42 |
43 | assert.equal(res.locals.messages[0].status, 'danger');
44 | assert.equal(res.locals.messages[0].message, 'we.test.message.1');
45 | assert.equal(res.locals.messages[0].extraData, null);
46 |
47 | res.locals.messages = [];
48 |
49 | done();
50 | });
51 |
52 | it ('should add one warn message', function(done) {
53 | res.addMessage('warn', {
54 | text: 'we.test.message.2',
55 | vars: { uno: 'dos' }
56 | }, {
57 | field: 'name'
58 | });
59 |
60 | assert.equal(res.locals.messages[0].status, 'warning');
61 | assert.equal(res.locals.messages[0].message, 'we.test.message.2');
62 | assert.equal(res.locals.messages[0].extraData.field, 'name');
63 |
64 | res.locals.messages = [];
65 |
66 | done();
67 | });
68 |
69 | it ('should add one warn message without localization and with text param', function(done) {
70 | var res = { locals: {} };
71 | var req = {}
72 |
73 | messages.setFunctionsInResponse(req, res);
74 |
75 | res.addMessage('warn', {
76 | text: 'we.test.message.3'
77 | });
78 |
79 | assert.equal(res.locals.messages[0].status, 'warning');
80 | assert.equal(res.locals.messages[0].message, 'we.test.message.3');
81 |
82 | res.locals.messages = [];
83 |
84 | done();
85 | });
86 |
87 | it ('should add one warn message without localization', function(done) {
88 | var res = { locals: {} };
89 | var req = {}
90 |
91 | messages.setFunctionsInResponse(req, res);
92 |
93 | res.addMessage('warn', 'we.test.message.4');
94 |
95 | assert.equal(res.locals.messages[0].status, 'warning');
96 | assert.equal(res.locals.messages[0].message, 'we.test.message.4');
97 |
98 | res.locals.messages = [];
99 |
100 | done();
101 | });
102 | });
103 | describe('res.getMessages', function(){
104 | it('should get messages from locals and flash', function (done) {
105 | var req = {
106 | we: we,
107 | flash: function() {
108 | return [{
109 | status: 'error',
110 | message: 'we.test.error.message'
111 | }]
112 | }
113 | }
114 | var res = {
115 | locals: {
116 | req: req,
117 | messages: [{
118 | status: 'success',
119 | message: 'we.test.message.10'
120 | }]
121 | }
122 | }
123 |
124 | messages.setFunctionsInResponse(req, res)
125 | var msgs = res.getMessages()
126 |
127 | assert(msgs);
128 | assert.equal(msgs[0].status, 'success');
129 | assert.equal(msgs[0].message, 'we.test.message.10');
130 | assert.equal(msgs[1].status, 'error');
131 | assert.equal(msgs[1].message, 'we.test.error.message');
132 |
133 | done();
134 | });
135 | });
136 | describe('res.moveLocalsMessagesToFlash', function(){
137 | it('should run res.getMessages and set in req.flash', function (done) {
138 | var req = {
139 | we: we,
140 | flash: function(name, data) {
141 | if (!data) return [];
142 | assert.equal(name, 'messages');
143 |
144 | assert.equal(data[0].status, 'success');
145 | assert.equal(data[0].message, 'we.test.message.11');
146 |
147 | done();
148 | }
149 | }
150 | var res = {
151 | locals: {
152 | req: req,
153 | messages: [{
154 | status: 'success',
155 | message: 'we.test.message.11'
156 | }]
157 | }
158 | }
159 |
160 | messages.setFunctionsInResponse(req, res)
161 |
162 | res.moveLocalsMessagesToFlash()
163 | });
164 | });
165 | });
166 | });
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to this project
2 |
3 | Please take a moment to review this document in order to make the contribution
4 | process easy and effective for everyone involved.
5 |
6 | Following these guidelines helps to communicate that you respect the time of
7 | the developers managing and developing this open source project. In return,
8 | they should reciprocate that respect in addressing your issue or assessing
9 | patches and features.
10 |
11 |
12 | ## Using the issue tracker
13 |
14 | The issue tracker is the preferred channel for [bug reports](#bugs),
15 | [features requests](#features) and [submitting pull
16 | requests](#pull-requests), but please respect the following restrictions:
17 |
18 | * Please **do not** use the issue tracker for personal support requests (use
19 | [Stack Overflow](http://stackoverflow.com) or [Gitter](https://gitter.im/wejs/we).
20 |
21 | * Please **do not** derail or troll issues. Keep the discussion on topic and
22 | respect the opinions of others.
23 |
24 |
25 | ## Bug reports
26 |
27 | A bug is a _demonstrable problem_ that is caused by the code in the repository.
28 | Good bug reports are extremely helpful - thank you!
29 |
30 | Guidelines for bug reports:
31 |
32 | 1. **Use the GitHub issue search** — check if the issue has already been
33 | reported.
34 |
35 | 2. **Check if the issue has been fixed** — try to reproduce it using the
36 | latest `master` or development branch in the repository.
37 |
38 | A good bug report shouldn't leave others needing to chase you up for more
39 | information. Please try to be as detailed as possible in your report. What is
40 | your environment? What steps will reproduce the issue? What browser(s) and OS
41 | experience the problem? What would you expect to be the outcome? All these
42 | details will help people to fix any potential bugs.
43 |
44 | Example:
45 |
46 | > Short and descriptive example bug report title
47 | >
48 | > A summary of the issue and the browser/OS environment in which it occurs. If
49 | > suitable, include the steps required to reproduce the bug.
50 | >
51 | > 1. This is the first step
52 | > 2. This is the second step
53 | > 3. Further steps, etc.
54 | >
55 | > Any other information you want to share that is relevant to the issue being
56 | > reported. This might include the lines of code that you have identified as
57 | > causing the bug, and potential solutions (and your opinions on their
58 | > merits).
59 |
60 |
61 |
62 | ## Feature requests
63 |
64 | Feature requests are welcome. But take a moment to find out whether your idea
65 | fits with the scope and aims of the project. It's up to *you* to make a strong
66 | case to convince the project's developers of the merits of this feature. Please
67 | provide as much detail and context as possible.
68 |
69 |
70 |
71 | ## Pull requests
72 |
73 | Good pull requests - patches, improvements, new features - are a fantastic
74 | help. They should remain focused in scope and avoid containing unrelated
75 | commits.
76 |
77 | **Please ask first** before embarking on any significant pull request (e.g.
78 | implementing features, refactoring code, porting to a different language),
79 | otherwise you risk spending a lot of time working on something that someone already is working.
80 |
81 | Please adhere to the coding conventions used throughout a project (indentation,
82 | accurate comments, etc.) and any other requirements (such as test coverage).
83 |
84 | Follow this process if you'd like your work considered for inclusion in the
85 | project:
86 |
87 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
88 | and configure the remotes:
89 |
90 | ```bash
91 | # Clone your fork of the repo into the current directory
92 | git clone https://github.com//we
93 | # Navigate to the newly cloned directory
94 | cd we
95 | # Assign the original repo to a remote called "upstream"
96 | git remote add upstream https://github.com/wejs/we
97 | ```
98 |
99 | 2. If you cloned a while ago, get the latest changes from upstream:
100 |
101 | ```bash
102 | git checkout
103 | git pull upstream
104 | ```
105 |
106 | 3. Create a new topic branch (off the main project development branch) to
107 | contain your feature, change, or fix:
108 |
109 | ```bash
110 | git checkout -b
111 | ```
112 |
113 | 4. Commit your changes in logical chunks. Please adhere to these [git commit
114 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
115 | or your code is unlikely be merged into the main project. Use Git's
116 | [interactive rebase](https://help.github.com/articles/interactive-rebase)
117 | feature to tidy up your commits before making them public.
118 |
119 | 5. Locally merge (or rebase) the upstream development branch into your topic branch:
120 |
121 | ```bash
122 | git pull [--rebase] upstream
123 | ```
124 |
125 | 6. Push your topic branch up to your fork:
126 |
127 | ```bash
128 | git push origin
129 | ```
130 |
131 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
132 | with a clear title and description.
133 |
134 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to
135 | license your work under the same license as that used by the project.
136 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Libs and functions utils for project development
3 | *
4 | */
5 |
6 | const fs = require('fs'),
7 | path = require('path'),
8 | _ = require('lodash'),
9 | // single model associations
10 | singleAssociations = ['belongsTo', 'hasOne'],
11 | slugify = require('slugify');
12 |
13 | const utils = {
14 | listFilesRecursive: walk,
15 | moment: require('moment'),
16 | async: require('async'),
17 | _: _,
18 | mkdirp: require('./mkdirp.js'),
19 | cookieParser: require('cookie-parser'),
20 | mime: require('mime'),
21 | express: require('express'),
22 | slugify: slugify,
23 |
24 | /**
25 | * Strip tags from string
26 | *
27 | * @param {String} string String to cleanup
28 | * @return {String} String without tags
29 | */
30 | stripTags(string = '') {
31 | return string.replace(/<\/?[^>]+(>|$)/g, '');
32 | },
33 |
34 | /**
35 | * Strip tags and truncate
36 | *
37 | * Usage: stripTagsAndTruncate('something big', 5, '......')
38 | *
39 | * @param {String} string String to cleanup and truncate
40 | * @param {Number} length Length to truncate if it is too big
41 | * @param {String} omission default: ...
42 | * @return {String} Clean and truncated string
43 | */
44 | stripTagsAndTruncate(string, length = 200, omission = '...') {
45 | return _.truncate(utils.stripTags(string), {
46 | length: length,
47 | omission: omission,
48 | });
49 | },
50 |
51 | /**
52 | * Slugfy and truncate
53 | *
54 | * Usage: slugifyAndTruncate('something big', 5, '......')
55 | *
56 | * @param {String} string String to cleanup and truncate
57 | * @param {Number} length Length to truncate if it is too big
58 | * @param {String} omission default: ...
59 | * @param {Object} opts slugfy options
60 | * @return {String} Clean and truncated string
61 | */
62 | slugifyAndTruncate(string, length = 200, omission = '...', opts = {}) {
63 | return _.truncate(slugify(string, { ...opts, lower: true, strict: true, }), {
64 | length: length,
65 | omission: omission,
66 | });
67 | },
68 |
69 | /**
70 | * Is authenticated method usable if we-plugin-auth not is installed
71 | *
72 | * @return {Boolean} True
73 | */
74 | isAuthenticated() {
75 | if (!this.user || !this.user.id) return false;
76 | return true;
77 | },
78 | /**
79 | * Get redirect url
80 | * @param {Object} req express request
81 | * @param {[type]} res express response
82 | * @return {string|null} url or null
83 | */
84 | getRedirectUrl(req) {
85 | if (req.query) {
86 | if (req.query.service) {
87 | if (req.we.config.services && req.we.config.services[req.query.service]) {
88 | req.we.log.verbose(
89 | 'Service redirect found for service: ', req.we.config.services[req.query.service]
90 | );
91 | return req.we.config.services[req.query.service].url;
92 | }
93 | }
94 |
95 | if (req.query.redirectTo) {
96 | if (!req.we.router.isAbsoluteUrl(req.query.redirectTo))
97 | return req.query.redirectTo;
98 | }
99 | }
100 |
101 | if (req.body && req.body.redirectTo) {
102 | if (!req.we.router.isAbsoluteUrl(req.body.redirectTo))
103 | return req.body.redirectTo;
104 | }
105 |
106 | return null;
107 | },
108 |
109 | helper: {
110 | /**
111 | * Parse handlebars helper options and return attributes text
112 | *
113 | * @param {Object} options handlebars helper options
114 | * @return {String} [description]
115 | */
116 | parseAttributes(options) {
117 | let attributes = [];
118 | // pass helper attributes to link element
119 | for (let attributeName in options.hash) {
120 | if (typeof options.hash[attributeName] == 'string') {
121 | attributes.push(attributeName + '="' + options.hash[attributeName] + '"');
122 | }
123 | }
124 |
125 | return attributes.join(' ');
126 | }
127 | },
128 |
129 | /**
130 | * Check if is a NxN association
131 | *
132 | * @param {Object} association The sequelize association object
133 | * @return {Boolean}
134 | */
135 | isNNAssoc(association) {
136 | if ( singleAssociations.indexOf( association.associationType ) > -1 ) {
137 | return true;
138 | }
139 |
140 | return false;
141 | }
142 | };
143 |
144 | /**
145 | * List files in dir in parallel
146 | *
147 | * see: http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
148 | *
149 | * @param {String} dir
150 | * @param {Function} done run with error, files
151 | */
152 | function walk(dir, done) {
153 | var results = [];
154 |
155 | fs.readdir(dir, (err, list)=> {
156 | if (err) {
157 | if (err.code === 'ENOENT') {
158 | return done(null, []);
159 | } else {
160 | return done(err);
161 | }
162 | }
163 | let pending = list.length;
164 | if (!pending) return done(null, results);
165 |
166 | list.forEach( (file)=> {
167 | file = path.resolve(dir, file);
168 | fs.stat(file, (err, stat)=> {
169 | if (stat && stat.isDirectory()) {
170 | walk(file, (err, res)=> {
171 | results = results.concat(res);
172 | if (!--pending) done(null, results);
173 | });
174 | } else {
175 | results.push(file);
176 | if (!--pending) done(null, results);
177 | }
178 | });
179 | });
180 | });
181 | }
182 |
183 | module.exports = utils;
184 |
--------------------------------------------------------------------------------
/src/responses/JSONApi.js:
--------------------------------------------------------------------------------
1 | const { isArray } = require('lodash'),
2 | actions = ['edit', 'create'];
3 |
4 | let JSONApi = {
5 | jsonAPIFormater(req, res) {
6 | let response = {};
7 |
8 | // check field privacity access for users
9 | if (res.locals.model == 'user') {
10 | req.we.db.checkRecordsPrivacity(res.locals.data);
11 | }
12 |
13 | if (res.locals.action == 'find' || isArray(res.locals.data) ) {
14 | response = JSONApi.formatList (req, res);
15 | } else if (res.locals.action == 'delete') {
16 | // dont send data in delete
17 | response = {};
18 | } else if (res.locals.model) {
19 | response = JSONApi.formatItem (req, res);
20 | }
21 |
22 | response.meta = res.locals.metadata || {};
23 |
24 | // parse we.js errors to jsonapi errors
25 | JSONApi.parseErrors(response, req, res);
26 |
27 | res.send(response);
28 | },
29 |
30 | formatList(req, res) {
31 | let data = [],
32 | r = {};
33 |
34 | // skip if data is empty
35 | if (!res.locals.data) return { data: [] };
36 |
37 | data = res.locals.data
38 | .map(d => {
39 | if (d.toJSONAPI) {
40 | return d.toJSONAPI();
41 | } else {
42 | return d;
43 | }
44 | });
45 |
46 | r.data = data;
47 |
48 | return r;
49 | },
50 |
51 | formatItem(req, res) {
52 | // skip if data is empty
53 | if (!res.locals.data) return { data: [] };
54 |
55 | if (res.locals.data.toJSONAPI) {
56 | return { data: res.locals.data.toJSONAPI() };
57 | } else {
58 | return res.locals.data;
59 | }
60 | },
61 |
62 | jsonAPIParser(req, res, context) {
63 | // create and update actions
64 | if (actions.includes(context.config.action)) {
65 | // atributes is required
66 | if (!req.body.data || !req.body.data.attributes) return req.body;
67 | // change json api body to default we.js controller body compatible with sequelize
68 | for (let attr in req.body.data.attributes) {
69 | req.body[attr] = req.body.data.attributes[attr];
70 | }
71 |
72 | if (req.body.data.relationships) {
73 | JSONApi.jsonAPIParseAssoc(req, res, context);
74 | }
75 | }
76 |
77 | return req.body;
78 | },
79 |
80 | jsonAPIParseAssoc(req, res, context) {
81 | const associations = req.we.db.models[context.config.model].associations;
82 | for(let assocName in associations) {
83 | if (
84 | req.body.data.relationships[assocName] &&
85 | (
86 | req.body.data.relationships[assocName].data ||
87 | req.body.data.relationships[assocName].data == null
88 | )
89 | ) {
90 |
91 | switch(associations[assocName].associationType) {
92 | case 'BelongsTo':
93 | if (req.body.data.relationships[assocName].data) {
94 | req.body[ assocName+'Id' ] = req.body.data.relationships[assocName].data.id;
95 | } else {
96 | // is null
97 | req.body[ assocName+'Id' ] = null;
98 | }
99 |
100 | break;
101 | case 'HasMany':
102 | case 'BelongsToMany':
103 | if (
104 | req.body.data.relationships[assocName].data &&
105 | req.body.data.relationships[assocName].data.map
106 | ) {
107 | req.body[ assocName ] = req.body.data.relationships[assocName].data.map(function(d) {
108 | return d.id;
109 | });
110 | }
111 | break;
112 | }
113 | }
114 | }
115 | },
116 |
117 | parse1xNAssociation(mc, attrN, items, relationships, included, we) {
118 | if (items.toJSON) items = items.toJSON();
119 |
120 | let iid = mc.associations[attrN].model + '_' + items.id;
121 |
122 | if (we.config.JSONApi.sendSubRecordAttributes && !included[iid]) {
123 | included[iid] = {
124 | type: mc.associations[attrN].model,
125 | id: items.id
126 | };
127 |
128 | included[iid].attributes = items;
129 | }
130 |
131 | relationships[attrN] = {
132 | data: {
133 | id: items.id,
134 | type: mc.associations[attrN].model
135 | }
136 | };
137 | },
138 |
139 | parseNxNAssociation(mc, attrN, items, relationships, included, we) {
140 | let modelName = mc.associations[attrN].model;
141 |
142 | if (!relationships[attrN]) {
143 | relationships[attrN] = {
144 | data: []
145 | };
146 | }
147 |
148 | items.forEach(item => {
149 |
150 | relationships[attrN].data.push({
151 | id: item.id,
152 | type: modelName
153 | });
154 |
155 | let iid = modelName + '_' + item.id;
156 | // skyp if record already in included
157 | if (we.config.JSONApi.sendSubRecordAttributes && !included[iid]) {
158 | included[iid] = {
159 | type: modelName,
160 | id: item.id
161 | };
162 | included[iid].attributes = item;
163 | }
164 | });
165 | },
166 |
167 | /**
168 | * Format erros from res.locals.messages to jsonapi erros and set it in response
169 | */
170 | parseErrors(response, req, res) {
171 | if (!response.meta.messages) response.meta.messages = [];
172 |
173 | if (res.locals.messages) {
174 | let errors = [];
175 |
176 | // first get and format all errros:
177 | res.locals.messages.forEach(m => {
178 | if (m.status == 'danger' || m.status == 'error') {
179 | errors.push(req.we.utils._.merge({
180 | status: res.statusCode,
181 | title: m.message
182 | }, m.extraData));
183 | } else {
184 | // others messages like success goes in meta object
185 | response.meta.messages.push(m);
186 | }
187 |
188 | });
189 |
190 |
191 | if (errors && errors.length) {
192 | response.errors = errors;
193 | }
194 | }
195 | }
196 | };
197 |
198 | module.exports = JSONApi;
--------------------------------------------------------------------------------
/src/bootstrapFunctions.js:
--------------------------------------------------------------------------------
1 | const staticConfig = require('./staticConfig'),
2 | localization = require('./localization'),
3 | weExpress = require('./express');
4 |
5 | module.exports = {
6 | checkDBConnection(we, next) {
7 | we.db.checkDBConnection(we, next);
8 | },
9 | loadCoreFeatures(we, next) {
10 | we.log.verbose('loadCoreFeatures step');
11 |
12 | we.db.loadCoreModels( err => {
13 | if(err) return next(err);
14 |
15 | we.pluginManager.loadPluginsSettingsFromDB(we, err => {
16 | if (err) return next(err);
17 | // preload all plugins
18 | we.pluginManager.loadPlugins(we, (err, plugins) => {
19 | if (err) return next(err);
20 | we.plugins = plugins;
21 | next();
22 | });
23 | });
24 | });
25 | } ,
26 | loadPluginFeatures(we, next) {
27 | we.log.verbose('loadPluginFeatures step');
28 |
29 | we.pluginNames = we.pluginManager.pluginNames;
30 | // load plugin static configs, merge with old we.config and
31 | // override the defalt config
32 | we.config = staticConfig.loadPluginConfigs(we);
33 | // set add ResponseFormat here for use we.js app
34 | we.responses.addResponseFormater = (extension, formater, position)=> {
35 | position = (position === 0 || position)? position: we.config.responseTypes.length;
36 |
37 | we.config.responseTypes.splice(position, 0, extension);
38 | we.responses.formaters[extension] = formater;
39 | };
40 |
41 | we.hooks.trigger('we:before:load:plugin:features', we, (err)=> {
42 | if (err) return next(err);
43 |
44 | we.utils.async.eachSeries(we.pluginNames, (pluginName, next)=> {
45 | we.plugins[pluginName].loadFeatures(we, next);
46 | }, (err) => {
47 | if (err) return next(err);
48 |
49 | we.events.emit('we:after:load:plugins', we);
50 | we.hooks.trigger('we:after:load:plugins', we, next);
51 | });
52 | });
53 | },
54 | loadTemplateCache(we, next) {
55 | // step to plug we-plugin-view
56 | we.hooks.trigger('we-core:on:load:template:cache', we, next);
57 | },
58 | instantiateModels(we, next) {
59 |
60 | // step to define all models with sequelize
61 | we.log.verbose('instantiateModels step');
62 | we.hooks.trigger('we:models:before:instance', we, (err) => {
63 | if (err) return next(err);
64 |
65 | for (let modelName in we.db.modelsConfigs) {
66 | let mc = we.db.modelsConfigs[modelName];
67 |
68 | // all models have a link permanent
69 | mc.definition.linkPermanent = {
70 | type: we.db.Sequelize.VIRTUAL,
71 | formFieldType: null,
72 | get() {
73 | if (this.cachedLinkPermanent) return this.cachedLinkPermanent;
74 | this.cachedLinkPermanent = this.getUrlPath();
75 | return this.cachedLinkPermanent;
76 | }
77 | };
78 |
79 | // set
80 | mc.definition.metadata = {
81 | type: we.db.Sequelize.VIRTUAL,
82 | formFieldType: null
83 | };
84 |
85 | we.db.setModelClassMethods();
86 | we.db.setModelInstanceMethods();
87 |
88 | // save attrs list:
89 | mc.attributeList = Object.keys(mc.definition);
90 | // add created and updated at attrs
91 | mc.attributeList.push('createdAt');
92 | mc.attributeList.push('updatedAt');
93 | // save assoc attr names list:
94 | if (mc.associations)
95 | mc.associationNames = Object.keys(mc.associations);
96 |
97 | // define the model
98 | we.db.models[modelName] = we.db.define(
99 | modelName,
100 | mc.definition,
101 | mc.options
102 | );
103 | }
104 |
105 | // set all associations
106 | we.db.setModelAllJoins();
107 | we.db.setModelHooks();
108 |
109 | we.hooks.trigger('we:models:set:joins', we, next);
110 | });
111 | },
112 | syncModels(we, next) {
113 | we.db.defaultConnection.sync().nodeify( (err)=> {
114 | if (err) return next(err);
115 | we.hooks.trigger('we:models:ready', we, next);
116 | });
117 | },
118 |
119 | loadControllers(we, next) {
120 | we.log.verbose('loadControllers step');
121 | we.events.emit('we:after:load:controllers', we);
122 | next();
123 | },
124 | initI18n(we, next) {
125 | we.log.verbose('initI18n step');
126 | localization(we);
127 | we.events.emit('we:after:init:i18n', we);
128 | next();
129 | },
130 | installAndRegisterPlugins(we, next) {
131 | if (we.config.skipInstall) return next();
132 |
133 | we.log.verbose('installAndRegisterPluginsIfNeed step');
134 | // dont have plugins to install
135 | if (!we.pluginManager.pluginsToInstall) return next();
136 | // get plugins to install names
137 | const names = Object.keys(we.pluginManager.pluginsToInstall);
138 | we.utils.async.eachSeries(names, function onEachPlugin (name, nextPlugin) {
139 | // run install scripts
140 | we.pluginManager.installPlugin(name, function afterInstallOnePlugin (err){
141 | if (err) return nextPlugin(err);
142 | // register it
143 | we.pluginManager.registerPlugin(name, nextPlugin);
144 | });
145 | }, function afterInstallAllPlugins (err) {
146 | if (err) return next(err);
147 | next();
148 | });
149 | },
150 | setExpressApp(we, next) {
151 | // load express
152 | we.express = weExpress(we);
153 | we.events.emit('we:after:load:express', we);
154 | next();
155 | },
156 | passport(we, next) {
157 | // hook to set authentication.
158 | // if we-plugin-auth is installed, load passport here
159 | we.hooks.trigger('we-core:on:set:passport', we, next);
160 | },
161 | createDefaultFolders(we, next) {
162 | we.log.verbose('createDefaultFolders step');
163 | we.hooks.trigger('we:create:default:folders', we, next);
164 | },
165 | registerAllViewTemplates(we, next) {
166 | // hook to plugin we-plugin-view template register
167 | we.hooks.trigger('we-core:on:register:templates', we, next);
168 | },
169 | mergeRoutes(we, next) {
170 | we.log.verbose('mergeRoutes step');
171 | we.routes = {};
172 | // merge plugin routes
173 | for (let plugin in we.plugins) {
174 | we.utils._.merge(we.routes, we.plugins[plugin].routes);
175 | }
176 | // merge project routes
177 | we.utils._.merge(we.routes, we.config.routes);
178 | next();
179 | },
180 | /**
181 | * Bind all resources in App
182 | *
183 | * @param {Object} we
184 | * @param {Function} next
185 | */
186 | bindResources(we, next) {
187 | we.log.verbose('bindResources step');
188 | try {
189 | for (let resource in we.router.resources) {
190 | we.router.bindResource(we.router.resources[resource]);
191 | }
192 | next();
193 | } catch (e) {
194 | next(e);
195 | }
196 | },
197 | bindRoutes(we, next) {
198 | we.log.verbose('bindRoutes step');
199 | we.hooks.trigger('we:before:routes:bind', we, function beforeRouteBind(err) {
200 | if (err) return next(err);
201 |
202 | for (let route in we.routes) {
203 | we.router.bindRoute(we, route, we.routes[route] );
204 | }
205 |
206 | we.hooks.trigger('we:after:routes:bind', we, function afterRouteBind(err) {
207 | if (err) return next(err);
208 |
209 | // bind after router handler for run responseMethod
210 | we.express.use( (req, res, done)=> {
211 | if (res.responseMethod) return res[res.responseMethod]();
212 | done();
213 | });
214 |
215 | we.responses.sortResponses(we);
216 |
217 | next();
218 | });
219 | });
220 | }
221 | };
--------------------------------------------------------------------------------
/src/localization/i18n.js:
--------------------------------------------------------------------------------
1 | const path = require('path'),
2 | fs = require('fs'),
3 | hbs = require('handlebars');
4 |
5 | const i18n = {
6 | we: null,
7 | fileI18nConfig: null,
8 | systemSettingsEnabled: false,
9 | // file locale directory:
10 | directory: null,
11 | locales: [],
12 | defaultLocale: null,
13 |
14 | lastLocaleUpdatedTimestamp: 0,
15 |
16 | // translations methods:
17 | fns: {},
18 | // translations cache
19 | cache: {},
20 | // translations with strings only:
21 | strCache: {},
22 |
23 | configure(c, we) {
24 | this.we = we;
25 | this.fileI18nConfig = c || {};
26 |
27 | if (c.locales) i18n.setLocales(c.locales);
28 | if (c.defaultLocale) i18n.setLocale(c.defaultLocale);
29 | if (c.directory) i18n.directory = c.directory;
30 |
31 | // system settings is avaible:
32 | if (typeof we.systemSettings == 'object') {
33 | this.systemSettingsEnabled = true;
34 | }
35 |
36 | this.setHooksAndEvents();
37 |
38 | this.loadFilesIFExists();
39 | },
40 |
41 | /**
42 | * Init middleware
43 | *
44 | * @param {Object} req
45 | * @param {Object} res
46 | * @param {Function} next
47 | */
48 | init(req, res, next) {
49 | req.setLocale = (locale)=> {
50 | req.locale = locale;
51 | res.locale = locale;
52 | res.locals.locale = locale;
53 | };
54 |
55 | req.getLocale = ()=> {
56 | return req.locale;
57 | };
58 |
59 | req.setLocale(i18n.getDefaultLocale(req));
60 |
61 | req.__ = function __(t, params) {
62 | return i18n.__(t, params, req);
63 | };
64 |
65 | res.__ = req.__;
66 | res.locals.__ = req.__;
67 |
68 | next();
69 | },
70 |
71 | setHooksAndEvents() {
72 | const we = this.we;
73 | we.hooks.on('we:models:ready', (we, done)=> {
74 | i18n.loadAllTranslationsFromDB(done);
75 | });
76 |
77 | // on system settings start:
78 | we.hooks.on('system-settings:started', (we, done)=> {
79 | if (we.systemSettings) {
80 | if (we.systemSettings.defaultLocale) {
81 | this.defaultLocale = we.systemSettings.defaultLocale;
82 | }
83 |
84 | if (we.systemSettings.LLUT) {
85 | i18n.lastLocaleUpdatedTimestamp = Number(we.systemSettings.LLUT);
86 | }
87 | }
88 |
89 | done();
90 | });
91 |
92 | // on system settings change:
93 | we.events.on('system-settings:updated:after', (we)=> {
94 | if (we.systemSettings.LLUT) {
95 | if (we.systemSettings.LLUT > i18n.lastLocaleUpdatedTimestamp) {
96 | // should update ...
97 | i18n.loadTranslationChangesInDB();
98 | // set new timestamp:
99 | i18n.lastLocaleUpdatedTimestamp = Number(we.systemSettings.LLUT);
100 | }
101 | }
102 | });
103 |
104 | },
105 |
106 | /**
107 | * Set global locale
108 | * @param {String} locale locale to set as default
109 | */
110 | setLocale(locale) {
111 | this.defaultLocale = locale;
112 | },
113 |
114 | getDefaultLocale(req) {
115 | const cookieLocale = i18n.getLocaleFromCookie(req);
116 | if (cookieLocale) return cookieLocale;
117 |
118 | const queryLocale = i18n.getLocaleFromQuery(req);
119 | if (queryLocale) return queryLocale;
120 |
121 | return i18n.defaultLocale;
122 | },
123 |
124 | getLocaleFromCookie(req) {
125 | const c = i18n.fileI18nConfig.cookie;
126 |
127 | if (
128 | c &&
129 | req &&
130 | req.cookies &&
131 | req.cookies[c] &&
132 | i18n.locales.indexOf(req.cookies[c]) > -1
133 | ) {
134 | return req.cookies[c];
135 | }
136 |
137 | return null;
138 | },
139 |
140 | getLocaleFromQuery(req) {
141 | const q = i18n.fileI18nConfig.queryParameter;
142 |
143 | if (
144 | q &&
145 | req &&
146 | req.query &&
147 | req.query[q] &&
148 | i18n.locales.indexOf(req.query[q]) > -1
149 | ) {
150 | return req.query[q];
151 | }
152 |
153 | return null;
154 | },
155 |
156 | /**
157 | * Set global locales
158 | * @param {Array} locales array of locales strings
159 | */
160 | setLocales(locales) {
161 | this.locales = locales;
162 | },
163 |
164 | __(s, params, req) {
165 | if (
166 | req &&
167 | i18n.fns[req.locale] &&
168 | i18n.fns[req.locale][s]
169 | ) {
170 | return i18n.fns[req.locale][s](params);
171 | }
172 |
173 | if (
174 | i18n.defaultLocale &&
175 | i18n.fns[i18n.defaultLocale] &&
176 | i18n.fns[i18n.defaultLocale][s]
177 | ) {
178 | return i18n.fns[i18n.defaultLocale][s](params);
179 | }
180 |
181 | return s;
182 | },
183 |
184 | loadFilesIFExists() {
185 | for (let i = 0; i < this.locales.length; i++) {
186 | let d = i18n.loadFileIfExists(this.locales[i]);
187 | i18n.parseAndImportTraslations(d, this.locales[i]);
188 | }
189 | },
190 |
191 | loadFileIfExists(locale) {
192 | let file = path.join(this.directory, locale+'.json');
193 |
194 | try {
195 | let contents = fs.readFileSync(file);
196 | return JSON.parse(contents);
197 | } catch(e) {
198 | i18n.we.log.verbose('Error on parse location file:', {
199 | error: e
200 | });
201 | }
202 |
203 | return null;
204 | },
205 |
206 | parseAndImportTraslations(d, locale) {
207 | if (!d) return;
208 |
209 | if (!i18n.fns[locale]) i18n.fns[locale] = {};
210 | if (!i18n.cache[locale]) i18n.cache[locale] = {};
211 | if (!i18n.strCache[locale]) i18n.strCache[locale] = {};
212 |
213 | for (let s in d) {
214 | let t = d[s];
215 |
216 | i18n.parseAndImportTraslation({
217 | s: s,
218 | t: t,
219 | l: locale,
220 | isChanged: false
221 | }, locale);
222 | }
223 | },
224 |
225 | parseAndImportTraslation(r, locale) {
226 | if (!r || !r.s) return;
227 | if (!locale) return;
228 |
229 | if (r.t) {
230 | if (i18n.isTPLString(r.t)) {
231 | i18n.fns[locale][r.s] = hbs.compile(r.t);
232 | } else {
233 | i18n.fns[locale][r.s] = i18n.getSimpleTMethod(r.t);
234 | }
235 |
236 | i18n.strCache[locale][r.s] = r.t;
237 | } else {
238 | i18n.fns[locale][r.s] = i18n.getSimpleTMethod(r.s);
239 | }
240 |
241 | i18n.cache[locale][r.s] = r;
242 | },
243 |
244 | isTPLString(s) {
245 | // if the msg string contains {{Mustache}} patterns we render it as a mini tempalate
246 | if ((/{{.*}}/).test(s)) return true;
247 | return false;
248 | },
249 |
250 | getSimpleTMethod(t) {
251 | return function() {
252 | return t;
253 | };
254 | },
255 |
256 | loadAllTranslationsFromDB(done) {
257 | const we = this.we;
258 |
259 | let sql = `SELECT s, t, l, isChanged, updatedAt
260 | FROM t
261 | ORDER BY s, updatedAt`;
262 | we.db.defaultConnection.query(sql)
263 | .then( (r)=> {
264 |
265 | if (r && r[0] && r[0].length) {
266 | let ts = r[0];
267 |
268 | ts.forEach( (tRecord)=> {
269 | i18n.parseAndImportTraslation(tRecord, tRecord.l);
270 | });
271 | }
272 |
273 | done();
274 | })
275 | .catch( (e)=> {
276 | we.log.warn('we-core.i18n:Error on load db translations', {
277 | error: e
278 | });
279 |
280 | done();
281 | });
282 | },
283 |
284 | loadTranslationChangesInDB(done) {
285 | if (!done) done = function(){};
286 |
287 | const we = this.we;
288 | const changeTime = we.utils
289 | .moment
290 | .unix( this.lastLocaleUpdatedTimestamp )
291 | .utc()
292 | .format('YYYY/MM/DD HH:mm:ss');
293 |
294 | let sql = `SELECT
295 | s, t, l, isChanged, updatedAt
296 | FROM t
297 | WHERE updatedAt >= '${changeTime}'
298 | ORDER BY s, updatedAt`;
299 | we.db.defaultConnection.query(sql)
300 | .then( (r)=> {
301 | if (r && r[0] && r[0].length) {
302 | let ts = r[0];
303 | ts.forEach( (tRecord)=> {
304 | i18n.parseAndImportTraslation(tRecord, tRecord.l);
305 | });
306 | }
307 |
308 | done();
309 | })
310 | .catch( (e)=> {
311 | we.log.warn('we-core.i18n:Error on load db translations', {
312 | error: e
313 | });
314 |
315 | done();
316 | });
317 | },
318 |
319 | getCachedTranslations(locale) {
320 | if (locale) {
321 | return i18n.strCache[locale];
322 | } else {
323 | return i18n.strCache;
324 | }
325 | }
326 | };
327 |
328 | module.exports = i18n;
--------------------------------------------------------------------------------
/install.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs'),
2 | path = require('path');
3 |
4 | module.exports = {
5 | requirements(we, done) {
6 | done();
7 | },
8 | /**
9 | * Install function run in we.js install.
10 | *
11 | * @param {Object} we we.js object
12 | * @param {Function} done callback
13 | */
14 | install(we, done) {
15 | done();
16 | },
17 |
18 | /**
19 | * Return a list of updates
20 | *
21 | * @param {Object} we we.js object
22 | * @return {Array} a list of update objects
23 | */
24 | updates() {
25 | return [{
26 | version: '0.3.69', // your plugin version
27 | update(we, done) {
28 | const sql = 'ALTER TABLE `widgets` ADD '+
29 | ' COLUMN `inRecord` TINYINT(1) DEFAULT NULL;';
30 | we.db.defaultConnection
31 | .query(sql)
32 | .then( ()=> {
33 | done();
34 | return null;
35 | })
36 | .catch(done);
37 | }
38 | } , {
39 | version: '0.3.120',
40 | update(we, done) {
41 |
42 | we.utils.async.series([
43 | function changeWidgetTableThemeField(done) {
44 | const sql = 'ALTER TABLE `widgets` '+
45 | 'CHANGE COLUMN `theme` `theme` VARCHAR(255) NULL ;';
46 | we.db.defaultConnection
47 | .query(sql)
48 | .then( ()=> {
49 | done();
50 | return null;
51 | })
52 | .catch(done);
53 | },
54 | function addRoleIsSystemField(done) {
55 | const sql = 'ALTER TABLE `roles` ' +
56 | 'ADD COLUMN `isSystemRole` TINYINT(1) NULL DEFAULT 0 AFTER `updatedAt`;';
57 | we.db.defaultConnection
58 | .query(sql)
59 | .then( ()=> {
60 | done();
61 | return null;
62 | })
63 | .catch( (err)=> {
64 | if (err != 'SequelizeDatabaseError') {
65 | return done(err);
66 | } else {
67 | we.log.error(err);
68 | }
69 |
70 | done();
71 | return null;
72 | });
73 | }
74 | ], done);
75 | }
76 | },
77 | {
78 | version: '1.0.2',
79 | update(we, done) {
80 | we.utils.async.series([
81 | function addWidgetTablURLField(done) {
82 | var sql = 'ALTER TABLE `widgets` '+
83 | ' ADD COLUMN `path` TEXT NULL; ';
84 | we.db.defaultConnection
85 | .query(sql)
86 | .then( ()=> {
87 | done();
88 | return null;
89 | })
90 | .catch( (err)=> {
91 | if (err != 'SequelizeDatabaseError') {
92 | return done(err);
93 | } else {
94 | we.log.error(err);
95 | }
96 | done();
97 | return null;
98 | });
99 | }
100 | ], done);
101 | }
102 | },
103 | {
104 | version: '1.1.4',
105 | update(we, done) {
106 | we.utils.async.series([
107 | function updateUserTable(done) {
108 | var sql = 'ALTER TABLE `users` '+
109 | ' ADD COLUMN `roles` TEXT NULL; ';
110 | we.db.defaultConnection
111 | .query(sql)
112 | .then( ()=> {
113 | done();
114 | })
115 | .catch( (err)=> {
116 | if (err) {
117 | we.log.error(err);
118 | }
119 |
120 | done();
121 | return null;
122 | });
123 | },
124 |
125 | function migrateRolesField(done) {
126 | var sql = ' SELECT users_roles.userId, roles.name FROM users_roles '+
127 | ' LEFT JOIN roles ON roles.id=users_roles.roleId;';
128 | we.db.defaultConnection
129 | .query(sql)
130 | .spread( (results)=> {
131 | var users = {};
132 |
133 | we.utils.async.eachSeries(results, function onEachResult(r, next){
134 | if (!users[r.userId]) {
135 | users[r.userId] = {
136 | id: r.userId,
137 | roles: []
138 | };
139 | }
140 |
141 | users[r.userId].roles.push(r.name);
142 | next();
143 | }, function afterEachResult(){
144 | we.utils.async.eachSeries(users, function onUpdateEachUser(user, next){
145 |
146 | we.db.models.user
147 | .update(user, {
148 | where: {
149 | id: user.id
150 | }
151 | })
152 | .then(function afterUpdateAllUsers(r) {
153 | we.log.info('install.js:Update user data:', r);
154 | next();
155 | return null;
156 | })
157 | .catch(next);
158 | }, ()=> {
159 | done();
160 | });
161 | });
162 | // done();
163 | })
164 | .catch( (err)=> {
165 | if (err) {
166 | we.log.warn(err);
167 | }
168 | done();
169 | return null;
170 | });
171 | },
172 | function exportRoles(done) {
173 | fs.lstat(we.projectPath+'/config/roles.js', function afterCheckRolesFile(err) {
174 | if (err) {
175 |
176 | if (err.code == 'ENOENT') {
177 | const sql = 'SELECT name, permissions FROM roles;';
178 | we.db.defaultConnection.query(sql)
179 | .spread( (results)=> {
180 |
181 | for (let i = 0; i < results.length; i++) {
182 | if (results[i].permissions) {
183 |
184 | if (!we.acl.roles[results[i].name]) {
185 | // add new role if not exists
186 | we.acl.roles[results[i].name] = {
187 | name: results[i].name,
188 | permissions: []
189 | };
190 | }
191 | // export old permissions to new permission structure
192 | let permissions = results[i].permissions.split(';');
193 | we.acl.roles[results[i].name].permissions = permissions;
194 | }
195 | }
196 |
197 | we.acl.writeRolesToConfigFile(done);
198 | }).catch( (err)=> {
199 | if (err) {
200 | we.log.error(err);
201 | }
202 | done();
203 | });
204 | } else {
205 | we.log.error('we-core:update:1.1.3: unknow error on find roles.js file');
206 | done(err);
207 | }
208 | } else {
209 | we.log.info('we-core:update:1.1.3: found foles.js file in your project, skiping exportRoles');
210 | done();
211 | }
212 | });
213 |
214 | }
215 | ], done);
216 | }
217 | },
218 | {
219 | version: '1.2.6',
220 | update(we, done) {
221 | we.log.info(
222 | 'instaling we-plugin-editor-summernote in this project, editor feature now are in diferent npm modules'
223 | );
224 |
225 | let exec = require('child_process').exec, child;
226 | child = exec('npm install --save we-plugin-editor-summernote',
227 | function afterInstall(error) {
228 | we.log.info('DONE we-plugin-editor-summernote install');
229 | done(error);
230 | });
231 | }
232 | },
233 | {
234 | version: '1.8.0',
235 | update(we, done) {
236 | we.log.info(
237 | 'registering all installed plugins to work with new we.js plugin manager'
238 | );
239 |
240 | const pkg = require( path.resolve(we.projectPath, 'package.json') ),
241 | nodeModulesPath = path.resolve(we.projectPath, 'node_modules');
242 |
243 | if (!pkg.wejs) pkg.wejs = {};
244 | if (!pkg.wejs.plugins) pkg.wejs.plugins = {};
245 |
246 | for (let n in pkg.dependencies) {
247 | if (n != 'we-core' &&
248 | oldIsPlugin( path.resolve(nodeModulesPath, n))
249 | ) {
250 | pkg.wejs.plugins[n] = true;
251 | }
252 | }
253 |
254 | fs.writeFile(
255 | path.resolve(we.projectPath, 'package.json'),
256 | JSON.stringify(pkg, null, 2),
257 | { flags: 'w' },
258 | function (err) {
259 | if (err) return done(err);
260 |
261 | let We = require('./src/index.js'),
262 | weUp = new We({
263 | bootstrapMode: 'install'
264 | });
265 |
266 | weUp.bootstrap( (err)=> {
267 | if (err) return done(err);
268 | done();
269 | return null;
270 | });
271 | }
272 | );
273 |
274 | // old we.js is plugin function to check is one npm module is plugin
275 | function oldIsPlugin (nodeModulePath) {
276 | // then check if the npm module is one plugin
277 | try {
278 | if (fs.statSync( path.resolve( nodeModulePath, 'plugin.js' ) )) {
279 | return true;
280 | } else {
281 | return false;
282 | }
283 | } catch (e) {
284 | return false;
285 | }
286 | }
287 | }
288 | }
289 | ];
290 | }
291 | };
--------------------------------------------------------------------------------
/test/tests/requests/resource.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | request = require('supertest'),
3 | helpers = require('we-test-tools').helpers,
4 | Chance = require('chance'),
5 | chance = new Chance();
6 |
7 | let _, http, we;
8 |
9 | function postStub(creatorId) {
10 | return {
11 | title: chance.sentence({words: 5}),
12 | text: chance.paragraph(),
13 | creatorId: creatorId
14 | };
15 | }
16 |
17 | describe('resourceRequests', function() {
18 | before(function (done) {
19 | http = helpers.getHttp();
20 | we = helpers.getWe();
21 | _ = we.utils._;
22 | return done();
23 | });
24 |
25 | afterEach(function(done){
26 | const models = we.db.models;
27 |
28 | Promise.all([
29 | models.tag.destroy({truncate: true}),
30 | models.post.destroy({truncate: true})
31 | ])
32 | .then(function() {
33 | done();
34 | })
35 | .catch(done);
36 | });
37 | describe('json', function() {
38 | describe('GET /post', function(){
39 | it ('should get posts list', function (done) {
40 | var posts = [
41 | postStub(),
42 | postStub(),
43 | postStub()
44 | ];
45 |
46 | we.db.models.post.bulkCreate(posts)
47 | .spread(function(){
48 | request(http)
49 | .get('/post')
50 | .set('Accept', 'application/json')
51 | .expect(200)
52 | .end(function (err, res) {
53 | if (err) {
54 | console.error('res.text>',res.text);
55 | throw err;
56 | }
57 |
58 | for (var i = 0; i < posts.length; i++) {
59 | assert(res.body.post[i].id);
60 | }
61 |
62 | assert.equal(res.body.meta.count, 3);
63 |
64 | done();
65 | });
66 | })
67 | .catch(done);
68 | });
69 |
70 | it ('/post/count should get posts count', function (done) {
71 | var posts = [
72 | postStub(),
73 | postStub(),
74 | postStub()
75 | ];
76 |
77 | we.db.models.post.bulkCreate(posts)
78 | .spread(function() {
79 | request(http)
80 | .get('/post/count')
81 | .set('Accept', 'application/json')
82 | .expect(200)
83 | .end(function (err, res) {
84 | if (err) {
85 | console.error('res.text>',res.text);
86 | throw err;
87 | }
88 |
89 | assert(res.body.count, `Count should be present in response`);
90 | assert.equal(res.body.count, 3, `Count should be 3`);
91 |
92 | done();
93 | });
94 | })
95 | .catch(done);
96 | });
97 | it ('should search for posts by title', function (done) {
98 | var posts = [
99 | postStub(),
100 | postStub(),
101 | postStub()
102 | ];
103 |
104 | we.db.models.post.bulkCreate(posts)
105 | .spread(function(){
106 | request(http)
107 | .get('/post?title='+posts[1].title)
108 | .set('Accept', 'application/json')
109 | .expect(200)
110 | .end(function (err, res) {
111 | if (err) throw err;
112 |
113 | assert.equal(res.body.post.length, 1);
114 | assert(res.body.post[0].id, posts[1].title);
115 | assert.equal(res.body.post[0].title, posts[1].title);
116 | assert.equal(res.body.post[0].text, posts[1].text);
117 |
118 | assert.equal(res.body.meta.count, 1);
119 |
120 | done();
121 | });
122 | }).catch(done);
123 | });
124 |
125 |
126 | it ('should search for posts by text', function (done) {
127 | var posts = [
128 | postStub(),
129 | postStub(),
130 | postStub(),
131 | postStub()
132 | ];
133 |
134 | var searchText = ' mussum ipsum';
135 |
136 | posts[1].text += searchText;
137 | posts[2].text += searchText;
138 |
139 | we.db.models.post.bulkCreate(posts)
140 | .spread(function(){
141 | request(http)
142 | .get('/post?text='+searchText)
143 | .set('Accept', 'application/json')
144 | .expect(200)
145 | .end(function (err, res) {
146 | if (err) throw err;
147 |
148 | assert.equal(res.body.post.length, 2);
149 |
150 | res.body.post.forEach(function(p){
151 | assert(p.text.indexOf(searchText) >-1);
152 | });
153 |
154 | assert.equal(res.body.meta.count, 2);
155 |
156 | done();
157 | });
158 | }).catch(done);
159 | });
160 |
161 | it ('should search for posts by text with and and inTitleAndText, orWithComaParser search in q param', function (done) {
162 | var posts = [
163 | postStub(),
164 | postStub(),
165 | postStub(),
166 | postStub()
167 | ];
168 |
169 | var searchText = ' mussum ipsum';
170 | var searchText2 = '2222m ipsum';
171 |
172 | posts[1].title = searchText;
173 | posts[1].text = searchText;
174 |
175 | posts[2].title = searchText2;
176 | posts[2].text = searchText2;
177 |
178 | we.db.models.post.bulkCreate(posts)
179 | .spread(function(){
180 | request(http)
181 | .get('/post?q='+searchText+','+searchText2)
182 | .set('Accept', 'application/json')
183 | .expect(200)
184 | .end(function (err, res) {
185 | if (err) throw err;
186 | assert.equal(res.body.post.length, 2);
187 | assert.equal(res.body.meta.count, 2);
188 |
189 | done();
190 | });
191 | }).catch(done);
192 | });
193 | });
194 |
195 | describe('GET /post/:id', function(){
196 | it ('should get one post', function (done) {
197 | we.db.models.post.create(postStub())
198 | .then(function (p) {
199 | request(http)
200 | .get('/post/'+p.id)
201 | .set('Accept', 'application/json')
202 | .expect(200)
203 | .end(function (err, res) {
204 | if (err) throw err;
205 |
206 | assert(res.body.post.id);
207 | assert.equal(res.body.post.title, p.title);
208 | assert.equal(res.body.post.text, p.text);
209 |
210 | done();
211 | });
212 | }).catch(done);
213 | });
214 |
215 | it ('should return 404 to not found', function (done) {
216 | var info = we.log.info;
217 | we.log.info = function() {};
218 | request(http)
219 | .get('/post/123213131')
220 | .expect(404)
221 | .set('Accept', 'application/json')
222 | .end(function (err, res) {
223 | if (err) throw err;
224 |
225 | assert(!res.body.post);
226 | we.log.info = info;
227 | done();
228 | });
229 | });
230 | });
231 |
232 | describe('PUT /post/:id', function(){
233 | it ('should update one post attr', function (done) {
234 | we.db.models.post.create(postStub())
235 | .then(function (p) {
236 |
237 | var updateData = {
238 | title: 'iIIeeei'
239 | };
240 | request(http)
241 | .put('/post/'+p.id)
242 | .set('Accept', 'application/json')
243 | .send(updateData)
244 | .expect(200)
245 | .end(function (err, res) {
246 | if (err) throw err;
247 |
248 | assert(res.body.post.id);
249 | assert.equal(res.body.post.title, updateData.title);
250 | assert.equal(res.body.post.text, p.text);
251 |
252 | done();
253 | });
254 | }).catch(done);
255 | });
256 | });
257 |
258 | describe('DELETE /post/:id', function(){
259 | it ('should delete one post', function (done) {
260 | we.db.models.post.create(postStub())
261 | .then(function (p) {
262 | request(http)
263 | .delete('/post/'+p.id)
264 | .set('Accept', 'application/json')
265 | .expect(204)
266 | .end(function (err, res) {
267 | if (err) throw err;
268 |
269 | assert(!res.text);
270 |
271 | we.db.models.post.findById(p.id)
272 | .then(function(ps){
273 | assert(!ps);
274 | done();
275 | }).catch(done);
276 | });
277 | }).catch(done);
278 | });
279 | });
280 |
281 | describe('POST /post', function(){
282 | it ('should create one resource with valid data', function (done) {
283 | var p = postStub();
284 | request(http)
285 | .post('/post')
286 | .send(p)
287 | .set('Accept', 'application/json')
288 | .expect(201)
289 | .end(function (err, res) {
290 | if (err) throw err;
291 |
292 | assert(res.body.post.id);
293 | assert.equal(res.body.post.title, p.title);
294 | assert.equal(res.body.post.text, p.text);
295 |
296 | done();
297 | });
298 | });
299 |
300 | it ('should return error if not set an not null attr', function (done) {
301 | var p = postStub();
302 | p.title = null;
303 |
304 | request(http)
305 | .post('/post')
306 | .send(p)
307 | .set('Accept', 'application/json')
308 | .expect(400)
309 | .end(function (err, res) {
310 | if (err) throw err;
311 |
312 | // assert(!res.body.post.id);
313 |
314 | assert.equal(res.body.messages[0].status, 'danger');
315 | assert.equal(res.body.messages[0].message, 'post.title cannot be null');
316 |
317 | // assert.equal(res.body.post.title, p.title);
318 | // assert.equal(res.body.post.text, p.text);
319 |
320 | done();
321 | });
322 | });
323 |
324 | it ('should not redirect if html plugin not is installed and Accept have the html format', function (done) {
325 | var p = postStub();
326 | request(http)
327 | .post('/post')
328 | .send(p)
329 | .set('Accept', 'application/json, text/plain, */*')
330 | .expect(201)
331 | .end(function (err, res) {
332 | if (err) {
333 | throw err;
334 | }
335 |
336 | assert(res.body.post.id);
337 | assert.equal(res.body.post.title, p.title);
338 | assert.equal(res.body.post.text, p.text);
339 |
340 | done();
341 | });
342 | });
343 |
344 | });
345 | });
346 | });
347 |
--------------------------------------------------------------------------------
/test/tests/requests/pluralized-resource.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | request = require('supertest'),
3 | path = require('path'),
4 | Chance = require('chance'),
5 | We = require('../../../src'),
6 | chance = new Chance();
7 |
8 | let we, _, http;
9 |
10 | describe('resource.pluralized', function(){
11 |
12 | // prepare we.js core and load app features with pluralization:
13 | before(function (callback) {
14 | this.slow(100);
15 |
16 | we = new We({
17 | bootstrapMode: 'test'
18 | });
19 |
20 | we.bootstrap({
21 | // disable access log
22 | enableRequestLog: false,
23 | router: {
24 | pluralize: true
25 | },
26 | port: 9988,
27 | i18n: {
28 | directory: path.resolve(process.cwd(), 'config/locales'),
29 | updateFiles: true,
30 | locales: ['en-us']
31 | },
32 | themes: {}
33 | }, callback);
34 | });
35 |
36 | // start the server:
37 | before(function (callback) {
38 | we.startServer(callback);
39 | });
40 |
41 | before(function (done) {
42 | http = we.http;
43 | _ = we.utils._;
44 | return done();
45 | });
46 |
47 | function postStub(creatorId) {
48 | return {
49 | title: chance.sentence({words: 5}),
50 | text: chance.paragraph(),
51 | creatorId: creatorId
52 | };
53 | }
54 |
55 | describe('resourceRequests', function() {
56 | afterEach(function(done){
57 | const models = we.db.models;
58 |
59 | Promise.all([
60 | models.tag.destroy({truncate: true}),
61 | models.post.destroy({truncate: true})
62 | ])
63 | .then(function() {
64 | done();
65 | })
66 | .catch(done);
67 | });
68 | describe('json', function() {
69 | describe('GET /posts', function(){
70 | it ('should get posts list', function (done) {
71 |
72 | var posts = [
73 | postStub(),
74 | postStub(),
75 | postStub()
76 | ];
77 |
78 | we.db.models.post.bulkCreate(posts)
79 | .spread(function() {
80 | request(http)
81 | .get('/posts')
82 | .set('Accept', 'application/json')
83 | .expect(200)
84 | .end(function (err, res) {
85 | if (err) {
86 | console.error('res.text>',res.text);
87 | throw err;
88 | }
89 |
90 | for (var i = 0; i < posts.length; i++) {
91 | assert(res.body.post[i].id);
92 | }
93 |
94 | assert.equal(res.body.meta.count, 3);
95 |
96 | done();
97 | });
98 | })
99 | .catch(done);
100 | });
101 |
102 | it ('/posts/count should get posts count', function (done) {
103 | var posts = [
104 | postStub(),
105 | postStub(),
106 | postStub()
107 | ];
108 |
109 | we.db.models.post.bulkCreate(posts)
110 | .spread(function() {
111 | request(http)
112 | .get('/posts/count')
113 | .set('Accept', 'application/json')
114 | .expect(200)
115 | .end(function (err, res) {
116 | if (err) {
117 | console.error('res.text>',res.text);
118 | throw err;
119 | }
120 |
121 | assert(res.body.count, `Count should be present in response`);
122 | assert.equal(res.body.count, 3, `Count should be 3`);
123 |
124 | done();
125 | });
126 | })
127 | .catch(done);
128 | });
129 | it ('should search for posts by title', function (done) {
130 | var posts = [
131 | postStub(),
132 | postStub(),
133 | postStub()
134 | ];
135 |
136 | we.db.models.post.bulkCreate(posts)
137 | .spread(function(){
138 | request(http)
139 | .get('/posts?title='+posts[1].title)
140 | .set('Accept', 'application/json')
141 | .expect(200)
142 | .end(function (err, res) {
143 | if (err) throw err;
144 |
145 | assert.equal(res.body.post.length, 1);
146 | assert(res.body.post[0].id, posts[1].title);
147 | assert.equal(res.body.post[0].title, posts[1].title);
148 | assert.equal(res.body.post[0].text, posts[1].text);
149 |
150 | assert.equal(res.body.meta.count, 1);
151 |
152 | done();
153 | });
154 | }).catch(done);
155 | });
156 |
157 |
158 | it ('should search for posts by text', function (done) {
159 | var posts = [
160 | postStub(),
161 | postStub(),
162 | postStub(),
163 | postStub()
164 | ];
165 |
166 | var searchText = ' mussum ipsum';
167 |
168 | posts[1].text += searchText;
169 | posts[2].text += searchText;
170 |
171 | we.db.models.post.bulkCreate(posts)
172 | .spread(function(){
173 | request(http)
174 | .get('/posts?text='+searchText)
175 | .set('Accept', 'application/json')
176 | .expect(200)
177 | .end(function (err, res) {
178 | if (err) throw err;
179 |
180 | assert.equal(res.body.post.length, 2);
181 |
182 | res.body.post.forEach(function(p){
183 | assert(p.text.indexOf(searchText) >-1);
184 | })
185 |
186 | assert.equal(res.body.meta.count, 2);
187 |
188 | done();
189 | });
190 | }).catch(done);
191 | });
192 |
193 | it ('should search for posts by text with and and inTitleAndText, orWithComaParser search in q param', function (done) {
194 | var posts = [
195 | postStub(),
196 | postStub(),
197 | postStub(),
198 | postStub()
199 | ];
200 |
201 | var searchText = ' mussum ipsum';
202 | var searchText2 = '2222m ipsum';
203 |
204 | posts[1].title = searchText;
205 | posts[1].text = searchText;
206 |
207 | posts[2].title = searchText2;
208 | posts[2].text = searchText2;
209 |
210 | we.db.models.post.bulkCreate(posts)
211 | .spread(function(){
212 | request(http)
213 | .get('/posts?q='+searchText+','+searchText2)
214 | .set('Accept', 'application/json')
215 | .expect(200)
216 | .end(function (err, res) {
217 | if (err) throw err;
218 | assert.equal(res.body.post.length, 2);
219 | assert.equal(res.body.meta.count, 2);
220 |
221 | done();
222 | });
223 | }).catch(done);
224 | });
225 | });
226 |
227 | describe('GET /posts/:id', function(){
228 | it ('should get one post', function (done) {
229 | we.db.models.post
230 | .create(postStub())
231 | .then(function (p) {
232 | request(http)
233 | .get('/posts/'+p.id)
234 | .set('Accept', 'application/json')
235 | .expect(200)
236 | .end(function (err, res) {
237 | if (err) throw err;
238 |
239 | assert(res.body.post.id);
240 | assert.equal(res.body.post.title, p.title);
241 | assert.equal(res.body.post.text, p.text);
242 |
243 | done();
244 | });
245 | }).catch(done);
246 | });
247 |
248 | it ('should return 404 to not found', function (done) {
249 | var info = we.log.info;
250 | we.log.info = function() {};
251 | request(http)
252 | .get('/posts/123213131')
253 | .expect(404)
254 | .set('Accept', 'application/json')
255 | .end(function (err, res) {
256 | if (err) throw err;
257 |
258 | assert(!res.body.post);
259 | we.log.info = info;
260 | done();
261 | });
262 | });
263 | });
264 |
265 | describe('PUT /posts/:id', function(){
266 | it ('should update one post attr', function (done) {
267 | we.db.models.post.create(postStub())
268 | .then(function (p) {
269 |
270 | var updateData = {
271 | title: 'iIIeeei'
272 | };
273 | request(http)
274 | .put('/posts/'+p.id)
275 | .set('Accept', 'application/json')
276 | .send(updateData)
277 | .expect(200)
278 | .end(function (err, res) {
279 | if (err) throw err;
280 |
281 | assert(res.body.post.id);
282 | assert.equal(res.body.post.title, updateData.title);
283 | assert.equal(res.body.post.text, p.text);
284 |
285 | done();
286 | });
287 | }).catch(done);
288 | });
289 | });
290 |
291 | describe('DELETE /posts/:id', function(){
292 | it ('should delete one post', function (done) {
293 | we.db.models.post.create(postStub())
294 | .then(function (p) {
295 | request(http)
296 | .delete('/posts/'+p.id)
297 | .set('Accept', 'application/json')
298 | .expect(204)
299 | .end(function (err, res) {
300 | if (err) throw err;
301 |
302 | assert(!res.text);
303 |
304 | we.db.models.post.findById(p.id)
305 | .then(function(ps){
306 | assert(!ps);
307 | done();
308 | }).catch(done);
309 | });
310 | }).catch(done);
311 | });
312 | });
313 |
314 | describe('POST /posts', function(){
315 | it ('should create one resource with valid data', function (done) {
316 | var p = postStub();
317 | request(http)
318 | .post('/posts')
319 | .send(p)
320 | .set('Accept', 'application/json')
321 | .expect(201)
322 | .end(function (err, res) {
323 | if (err) throw err;
324 |
325 | assert(res.body.post.id);
326 | assert.equal(res.body.post.title, p.title);
327 | assert.equal(res.body.post.text, p.text);
328 |
329 | done();
330 | });
331 | });
332 |
333 | it ('should return error if not set an not null attr', function (done) {
334 | var p = postStub();
335 | p.title = null;
336 |
337 | request(http)
338 | .post('/posts')
339 | .send(p)
340 | .set('Accept', 'application/json')
341 | .expect(400)
342 | .end(function (err, res) {
343 | if (err) throw err;
344 |
345 | // assert(!res.body.post.id);
346 |
347 | assert.equal(res.body.messages[0].status, 'danger');
348 | assert.equal(res.body.messages[0].message, 'post.title cannot be null');
349 |
350 | done();
351 | });
352 | });
353 | });
354 | });
355 | });
356 |
357 | }); // end resource.pluralized
--------------------------------------------------------------------------------
/src/PluginManager/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path'),
2 | fs = require('fs');
3 | let projectPath = process.cwd();
4 |
5 | /**
6 | * Plugin manager, load, valid and store avaible plugins
7 | *
8 | * @type {Object}
9 | */
10 | function PluginManager (we) {
11 | this.we = we;
12 |
13 | projectPath = we.projectPath;
14 | // npm module folder from node_modules
15 | this.nodeModulesPath = path.resolve(projectPath, 'node_modules');
16 |
17 | this.configs = we.configs;
18 | this.plugins = {};
19 | this.pluginNames = this.getPluginsList();
20 | // a list of plugin.js files get from npm module folder
21 | this.pluginFiles = {};
22 | // array with all plugin paths
23 | this.pluginPaths = [];
24 | // plugin records from db
25 | this.records = [];
26 | // a list of plugins to install
27 | this.pluginsToInstall = {};
28 | }
29 | // flag to check if plugins already is loaded
30 | PluginManager.prototype.pluginsLoaded = false;
31 | // function to check if npm module is plugin
32 | PluginManager.prototype.isPlugin = require('./isPlugin.js');
33 | // return the name of all enabled plugins
34 | PluginManager.prototype.getPluginNames = function () {
35 | return this.pluginNames;
36 | };
37 | // load one plugin running related plugin.js file
38 | PluginManager.prototype.loadPlugin = function (pluginFile, npmModuleName, projectPath) {
39 | this.plugins[npmModuleName] = require(pluginFile)( projectPath , this.we.class.Plugin);
40 | };
41 | /**
42 | * Get plugin names list from project package.json
43 | *
44 | * @return {Array} Plugin names list
45 | */
46 | PluginManager.prototype.getPluginsList = function () {
47 | let names = [];
48 |
49 | // dev or test modules
50 | if (this.we.env != 'prod') {
51 | if (this.we.projectPackageJSON.wejs && this.we.projectPackageJSON.wejs.devPlugins) {
52 | for (let name in this.we.projectPackageJSON.wejs.devPlugins) {
53 | if (this.we.projectPackageJSON.wejs.devPlugins[name]) {
54 | names.push(name);
55 | }
56 | }
57 | }
58 | }
59 |
60 | // modules avaible in all envs
61 | if (this.we.projectPackageJSON.wejs && this.we.projectPackageJSON.wejs.plugins) {
62 | for (let name in this.we.projectPackageJSON.wejs.plugins) {
63 | if (this.we.projectPackageJSON.wejs.plugins[name]) {
64 | names.push(name);
65 | }
66 | }
67 | }
68 |
69 | //
70 | if (this.we.projectPackageJSON.name != 'we-core') {
71 | // move we-core to plugin list start
72 | let weCoreIndex = names.indexOf('we-core');
73 | if (weCoreIndex == -1) {
74 | // not is in plugins list then add before all
75 | names.unshift('we-core');
76 | } else if (weCoreIndex !== -1 && weCoreIndex > 0) {
77 | // move we-core to load after others plugins
78 | names.unshift(names.splice(weCoreIndex, 1));
79 | }
80 | }
81 |
82 | return names;
83 | };
84 |
85 | /**
86 | * Load and register all avaible plugins
87 | *
88 | * @param {object} we we.js object
89 | * @param {Function} cb callback
90 | */
91 | PluginManager.prototype.loadPlugins = function (we, done) {
92 | // only load one time
93 | if (this.pluginsLoaded) return this.plugins;
94 |
95 | let newPluginNames = we.utils._.cloneDeep(this.pluginNames);
96 |
97 | this.pluginNames.forEach(name => {
98 | // load project bellow
99 | if (name == 'project') return;
100 |
101 | // get full path
102 | let pluginPath = path.resolve(this.nodeModulesPath, name);
103 |
104 | // check if is plugin
105 | if ( !this.isPlugin(pluginPath) ) {
106 | // if not is plugin, remove from array and show log
107 | let index = newPluginNames.indexOf(name);
108 | newPluginNames.splice(index, 1);
109 |
110 | we.log.warn('pluginManager:'+name+' not is plugin');
111 | return;
112 | }
113 |
114 | // save plugin path
115 | this.pluginPaths.push(pluginPath);
116 | // resolve full plugin file path
117 | let pluginFile = path.resolve( pluginPath, 'plugin.js' );
118 | // save this plugin file
119 | this.pluginFiles[name] = pluginFile;
120 | // load plugin resources
121 | this.loadPlugin(pluginFile, name, projectPath);
122 | // check if needs to install this plugin
123 | if (!this.isInstalled(name)) {
124 | this.pluginsToInstall[name] = pluginFile;
125 | }
126 | });
127 |
128 | this.pluginNames = newPluginNames;
129 |
130 | this.pluginNames.push('project');
131 | // - then load project as plugin if it have the plugin.js file
132 | let projectFile = path.resolve(projectPath, 'plugin.js');
133 | this.pluginFiles.project = projectFile;
134 | // after all plugins load the project as plugin
135 | this.loadProjectAsPlugin();
136 | // check if needs to install the project
137 | if (!this.isInstalled('project')) {
138 | this.pluginsToInstall.project = projectFile;
139 | }
140 |
141 | // load done
142 | this.pluginsLoaded = true;
143 |
144 | done(null, this.plugins);
145 | };
146 | /**
147 | * Check if one plugin is installed
148 | *
149 | * @param {String} name plugin Name
150 | * @return {Boolean}
151 | */
152 | PluginManager.prototype.isInstalled = function (name) {
153 | for (let i = 0; i < this.records.length; i++) {
154 | if (this.records[i].name == name) {
155 | return true;
156 | }
157 | }
158 | return false;
159 | };
160 |
161 | /**
162 | * Get the plugin install.js script if is avaible
163 | * @param {String} name plugin name
164 | */
165 | PluginManager.prototype.getPluginInstallScript = function (name) {
166 | let pluginFolder;
167 | // get folder, for suport with project plugin
168 | if (name == 'project') {
169 | pluginFolder = projectPath;
170 | } else {
171 | pluginFolder = path.resolve(this.nodeModulesPath, name);
172 | }
173 | // get the install file
174 | let installFile = path.resolve(pluginFolder, 'install.js');
175 | let install;
176 | try {
177 | return install = require(installFile);
178 | } catch (e) {
179 | if (e.code == 'MODULE_NOT_FOUND') {
180 | // this plugin dont have the install file
181 | return null;
182 | }
183 | // unknow error
184 | throw(e);
185 | }
186 | };
187 |
188 | PluginManager.prototype.installPlugin = function (name, done) {
189 | let install = this.getPluginInstallScript(name);
190 | let we = this.we;
191 |
192 | // dont have the install script
193 | if (!install || !install.install) {
194 | we.log.info('Plugin '+ name + ' dont have install method');
195 | return done();
196 | }
197 | // run the install script if avaible
198 | install.install(we, function afterRunPluginInstallMethod (err) {
199 | if (err) return done(err);
200 |
201 | we.log.info('Plugin '+ name + ' installed');
202 | return done();
203 | });
204 | };
205 |
206 | PluginManager.prototype.registerPlugin = function (name, done) {
207 | let we =this.we;
208 | let pluginManager = this;
209 |
210 | let filename;
211 | if (name == 'project') {
212 | filename = 'plugin.js';
213 | } else {
214 | filename = 'node_modules/'+ name + '/plugin.js';
215 | }
216 |
217 | let install = this.getPluginInstallScript(name);
218 |
219 | let version = '0.0.0';
220 |
221 | // get version of last update
222 | if (install && install.updates) {
223 | let updates = install.updates(we);
224 | if (updates && updates.length) {
225 | version = updates[updates.length - 1].version;
226 | }
227 | }
228 |
229 | we.db.models.plugin.create({
230 | filename: filename,
231 | name: name,
232 | version: version,
233 | status: 1
234 | })
235 | .then(function (r) {
236 | we.log.info('Plugin '+name+' registered with id: '+ r.id);
237 | // push to plugin record array
238 | pluginManager.records.push(r);
239 | done();
240 | })
241 | .catch(done);
242 | };
243 |
244 | /**
245 | * Load all plugin settings from DB
246 | *
247 | * @param {Object} we we.js object
248 | * @param {Function} cb callback
249 | */
250 | PluginManager.prototype.loadPluginsSettingsFromDB = function (we, cb) {
251 | let pluginManager = this;
252 |
253 | return we.db.models.plugin
254 | .findAll({
255 | order: [
256 | ['weight', 'ASC'],
257 | ['id', 'ASC']
258 | ]
259 | })
260 | .then(function (plugins) {
261 | // move we-core to start of array if exists
262 | for (let i = 0; i < plugins.length; i++) {
263 | if (plugins[i].name == 'we-core') {
264 | plugins.unshift(plugins.splice(i, 1)[0]);
265 | break;
266 | }
267 | }
268 |
269 | pluginManager.records = plugins;
270 |
271 | cb(null, plugins);
272 | })
273 | .catch(cb);
274 | };
275 |
276 | /**
277 | * Get one plugin record from pluginManager.records array
278 | *
279 | * @param {String} name pluginName
280 | * @return {Object} sequelize plugin record
281 | */
282 | PluginManager.prototype.getPluginRecord = function (name) {
283 | for (let l = 0; l < this.records.length; l++) {
284 | if (this.records[l].name == name)
285 | return this.records[l];
286 | }
287 | return null;
288 | };
289 |
290 | PluginManager.prototype.getPluginsToUpdate = function (done) {
291 | const we = this.we;
292 | let pluginsToUpdate = [];
293 | let name, installFile, updates, pluginRecord;
294 |
295 | for (let i = 0; i < this.pluginNames.length; i++) {
296 | name = this.pluginNames[i];
297 | installFile = this.getPluginInstallScript(name);
298 |
299 | // skip if dont have updates
300 | if (!installFile || !installFile.updates) continue;
301 |
302 | updates = installFile.updates(we);
303 |
304 | if (!updates.length) continue;
305 |
306 | pluginRecord = this.getPluginRecord(name);
307 |
308 | if (!pluginRecord) continue;
309 |
310 | if (pluginRecord.version == '0.0.0') {
311 | pluginsToUpdate.push({
312 | name: name,
313 | installFile: installFile,
314 | record: pluginRecord
315 | });
316 | continue;
317 | }
318 |
319 | let firstUpdateFound = false;
320 | for (let j = 0; j < updates.length; j++) {
321 | if (firstUpdateFound) {
322 | pluginsToUpdate.push({
323 | name: name,
324 | installFile: installFile,
325 | record: pluginRecord
326 | });
327 | break;
328 | }
329 |
330 | if (!firstUpdateFound && updates[j].version == pluginRecord.version) {
331 | firstUpdateFound = true;
332 | }
333 | }
334 | }
335 |
336 | done(null, pluginsToUpdate);
337 | };
338 |
339 | PluginManager.prototype.runPluginUpdates = function (name, done) {
340 | let we = this.we;
341 | let installFile = this.getPluginInstallScript(name);
342 | let updates = installFile.updates(we);
343 | let pluginRecord = this.getPluginRecord(name);
344 |
345 | let updatesToRun = [];
346 |
347 | // get all updates to run for this plugin
348 | if (pluginRecord.version == '0.0.0') {
349 | updatesToRun = updates;
350 | } else {
351 | let firstUpdateFound = false;
352 | for (let i = 0; i < updates.length; i++) {
353 | if (firstUpdateFound) {
354 | updatesToRun.push(updates[i]);
355 | }
356 | if (!firstUpdateFound && updates[i].version == pluginRecord.version) {
357 | firstUpdateFound = true;
358 | }
359 | }
360 | }
361 |
362 | we.utils.async.eachSeries(updatesToRun, function (up, next) {
363 | // run the update fn
364 | up.update(we, function (err) {
365 | if (err) return next (err);
366 | // update the plugin version in db
367 | pluginRecord.version = up.version;
368 |
369 | return pluginRecord.save()
370 | .then(function () {
371 | we.log.info('Plugin '+name+ ' updated to: ' + up.version);
372 | next();
373 | })
374 | .catch(next);
375 | });
376 | }, done);
377 | };
378 |
379 | /**
380 | * Check is project have a plugin.js file and if yes load it as plugin
381 | */
382 | PluginManager.prototype.loadProjectAsPlugin = function () {
383 | let file = path.join( projectPath, 'plugin.js' );
384 |
385 | if (!fs.existsSync(file)) {
386 | // current project dont have plugin features if dont have the plugin.js file
387 | this.plugins.project = new this.we.class.Plugin(projectPath);
388 | return null;
389 | }
390 |
391 | // load project plugin.js file if exists
392 | this.loadPlugin(file, 'project', projectPath);
393 | };
394 |
395 | // exports pluginManager
396 | module.exports = PluginManager;
397 |
--------------------------------------------------------------------------------
/src/responses/methods/index.js:
--------------------------------------------------------------------------------
1 | const haveAndAcceptsHtmlResponse = require('../../Router/haveAndAcceptsHtmlResponse.js');
2 |
3 | module.exports = {
4 | /**
5 | * res.ok default success response
6 | *
7 | * By defalt they will get the record from res.locals.data
8 | *
9 | * @param {Object} data opctional data
10 | */
11 | ok(data) {
12 | const res = this.res,
13 | req = this.req,
14 | we = req.we;
15 |
16 | res.status(200);
17 |
18 | if (!data) {
19 | data = res.locals.data || {};
20 | } else {
21 | if (!res.locals.data) res.locals.data = data;
22 | }
23 |
24 | // use this hook in one we.js plugin to change a res.ok response
25 | we.hooks.trigger('we:before:send:okResponse', {
26 | req: req,
27 | res: res,
28 | data: data
29 | }, (err)=> {
30 | if (err) we.log.error(err);
31 |
32 | // if accepts html and have the html response format:
33 | if (haveAndAcceptsHtmlResponse(req, res)) {
34 | if (req.method == 'POST' && res.locals.action == 'edit' && res.locals.redirectTo) {
35 | return res.redirect(res.locals.redirectTo);
36 | }
37 | }
38 |
39 | res.format(we.responses.formaters);
40 | });
41 | },
42 | /**
43 | * Record created response
44 | *
45 | * By defalt they will get the record from res.locals.data
46 | *
47 | * @param {Object} data record data
48 | */
49 | created(data) {
50 | const res = this.res,
51 | req = this.req,
52 | we = req.we;
53 |
54 | res.status(201);
55 |
56 | if (!data) {
57 | data = res.locals.data || {};
58 | } else {
59 | if (!res.locals.data) res.locals.data = data;
60 | }
61 |
62 | // use this hook in one we.js plugin to change a res.ok response
63 | we.hooks.trigger('we:before:send:createdResponse', {
64 | req: req,
65 | res: res,
66 | data: data
67 | }, (err)=> {
68 | if (err) we.log.error(err);
69 |
70 | if (haveAndAcceptsHtmlResponse(req, res)) {
71 | // redirect if are one html response
72 | if (res.locals.skipRedirect) {
73 | return res.view(data);
74 | } else if (res.locals.redirectTo) {
75 | return res.redirect(res.locals.redirectTo);
76 | } else {
77 | // push id to paramsArray for use in urlTo
78 | req.paramsArray.push(res.locals.data.id);
79 | // redirect to content after create
80 | return res.redirect(we.router.urlTo(res.locals.model + '.findOne', req.paramsArray));
81 | }
82 | }
83 |
84 | res.format(we.responses.formaters);
85 | });
86 | },
87 | /**
88 | * Record updated response
89 | *
90 | * By defalt they will get the record from res.locals.data
91 | *
92 | * @param {Object} data optional data
93 | */
94 | updated(data) {
95 | const res = this.res,
96 | req = this.req,
97 | we = req.we;
98 |
99 | res.status(200);
100 |
101 | if (!data) {
102 | data = res.locals.data || {};
103 | } else {
104 | if (!res.locals.data) res.locals.data = data;
105 | }
106 |
107 | // use this hook in one we.js plugin to change a res.ok response
108 | we.hooks.trigger('we:before:send:updatedResponse', {
109 | req: req,
110 | res: res,
111 | data: data
112 | }, (err)=> {
113 | if (err) we.log.error(err);
114 |
115 | if (haveAndAcceptsHtmlResponse(req, res)) {
116 | // if is edit record use the redirectTo feature
117 | if (res.locals.redirectTo) {
118 | return res.redirect(res.locals.redirectTo);
119 | } else {
120 | // push id to paramsArray for use in urlTo
121 | req.paramsArray.push(res.locals.data.id);
122 | // redirect to content after create
123 | return res.redirect(we.router.urlTo(res.locals.model + '.findOne', req.paramsArray));
124 | }
125 | }
126 |
127 | res.format(we.responses.formaters);
128 | });
129 | },
130 | /**
131 | * Deleted response
132 | *
133 | * redirect for html responses
134 | */
135 | deleted() {
136 | const res = this.res,
137 | req = this.req,
138 | we = req.we;
139 |
140 | res.status(204);
141 |
142 | // use this hook in one we.js plugin to change a res.ok response
143 | we.hooks.trigger('we:before:send:deletedResponse', {
144 | req: req,
145 | res: res
146 | }, ()=> {
147 |
148 | if (haveAndAcceptsHtmlResponse(req, res)) {
149 | if (
150 | res.locals.redirectTo &&
151 | (we.router.urlTo(res.locals.model + '.findOne', req.paramsArray) != res.locals.redirectTo)
152 | ) {
153 | return res.redirect(res.locals.redirectTo);
154 | } else {
155 | res.locals.deleteRedirectUrl = we.router.urlTo(res.locals.model + '.find', req.paramsArray);
156 | return res.redirect((res.locals.deleteRedirectUrl || '/'));
157 | }
158 | }
159 |
160 | res.format(req.we.responses.formaters);
161 | });
162 | },
163 |
164 | /**
165 | * View response usefull if we have the we-plugin-view installed to send html pages in response
166 | *
167 | * @param {Object} data Data to send that overrides data from res.locals.data
168 | */
169 | view(data) {
170 | const req = this.req,
171 | res = this.res;
172 |
173 | if (!data) {
174 | data = res.locals.data || {};
175 | } else {
176 | if (!res.locals.data) res.locals.data = data;
177 | }
178 |
179 | if (req.haveAlias) {
180 | // is a target how have alias then redirect to it
181 | res.writeHead(307, {
182 | 'Location': req.haveAlias.alias,
183 | 'Content-Type': 'text/plain',
184 | 'Cache-Control':'public, max-age=345600',
185 | 'Expires': new Date(Date.now() + 345600000).toUTCString()
186 | });
187 |
188 | return res.send();
189 | }
190 |
191 | if (req.method && req.method == 'HEAD') {
192 | // HEAD requests dont have body data then ignore format.
193 | return res.send();
194 | }
195 |
196 | res.format(req.we.responses.formaters);
197 | },
198 |
199 | /**
200 | * Forbidden response
201 | *
202 | * @param {String} data Optional extra message
203 | */
204 | forbidden(data) {
205 | const res = this.res,
206 | req = this.req;
207 |
208 | let __ = ( res.locals.__ || req.we.i18n.__ );
209 |
210 | if (typeof data == 'string') {
211 | res.addMessage('error', {
212 | text: data
213 | });
214 |
215 | data = null;
216 | }
217 |
218 | res.status(403);
219 |
220 | res.locals.title = __('response.forbidden.title');
221 |
222 | if (haveAndAcceptsHtmlResponse(req, res)) {
223 | res.locals.layoutName = 'fullwidth';
224 | res.locals.template = '403';
225 | }
226 | // delete the data that user dont have access:
227 | delete res.locals.data;
228 | // add one message with forbidden to send in response:
229 | res.addMessage('warn', { text: 'forbidden' });
230 |
231 | res.format(req.we.responses.formaters);
232 | },
233 |
234 | /**
235 | * Not found response
236 | *
237 | * @param {String} data optional 404 error message
238 | */
239 | notFound(data) {
240 | const res = this.res,
241 | req = this.req,
242 | we = req.we;
243 |
244 | if (typeof data == 'string') {
245 | res.addMessage('error', {
246 | text: data
247 | });
248 |
249 | data = null;
250 | }
251 |
252 | res.locals.data = null;
253 |
254 | if (we.config.enable404Log) {
255 | if (we.env == 'dev') {
256 | console.trace('404', {
257 | method: req.method,
258 | path: req.path
259 | });
260 | } else {
261 | we.log.info('Not found 404 ', {
262 | url: req.url,
263 | method: req.method,
264 | query: req.query,
265 | controller: res.locals.controller,
266 | action: res.locals.action
267 | });
268 | }
269 | }
270 |
271 | res.locals.title = req.__('response.notFound.title');
272 |
273 | res.status(404);
274 |
275 | if (haveAndAcceptsHtmlResponse(req, res)) {
276 | res.locals.layoutName = 'fullwidth';
277 | res.locals.template = '404';
278 | }
279 |
280 | delete res.locals.data;
281 |
282 | res.format(we.responses.formaters);
283 | },
284 | /**
285 | * Server error response
286 | *
287 | * @param {Object} data the error
288 | */
289 | serverError(data) {
290 | const res = this.res,
291 | req = this.req;
292 |
293 | let __ = ( req.__ || res.locals.__ || req.we.i18n.__ );
294 |
295 | res.status(500);
296 |
297 | req.we.log.error('ServerError:', data);
298 |
299 | res.locals.title = __('response.serveError.title');
300 |
301 | if (data && typeof data == 'string') {
302 | res.addMessage('error', String(data));
303 | }
304 |
305 | if (haveAndAcceptsHtmlResponse(req, res)) {
306 | res.locals.template = '500';
307 | res.locals.layoutName = 'fullwidth';
308 | }
309 |
310 | delete res.locals.data;
311 |
312 | // send the response
313 | res.format(req.we.responses.formaters);
314 | },
315 |
316 | /**
317 | * bad request response
318 | *
319 | * @param {Obejct|String} data message
320 | */
321 | badRequest(data) {
322 | const res = this.res,
323 | req = this.req;
324 |
325 | res.status(400);
326 |
327 | if (req.we.env == 'dev') {
328 | console.trace('400', req.path);
329 | }
330 |
331 | if (data && typeof data == 'string') {
332 | res.addMessage('warning', String(data));
333 | }
334 |
335 | if (haveAndAcceptsHtmlResponse(req, res)) {
336 | // if is html
337 | if (!res.locals.template) res.locals.template = '400';
338 | }
339 |
340 | delete res.locals.data;
341 |
342 | // send the response
343 | res.format(req.we.responses.formaters);
344 | },
345 |
346 | /**
347 | * Sequelize query error parser
348 | *
349 | * @param {Object} err The database error
350 | */
351 | queryError(err) {
352 | const res = this.res,
353 | req = this.req,
354 | log = req.we.log;
355 |
356 | let __ = ( req.__ || res.locals.__ || req.we.i18n.__ );
357 |
358 | if (err) {
359 | // parse all sequelize validation erros for html (we-plugin-view)
360 | if (
361 | haveAndAcceptsHtmlResponse(req, res) &&
362 | err.name === 'SequelizeValidationError'
363 | ) {
364 | // query validation error ...
365 | res.locals.validationError = {};
366 |
367 | err.errors.forEach( (err)=> {
368 | if (!res.locals.validationError[err.path])
369 | res.locals.validationError[err.path] = [];
370 |
371 | res.locals.validationError[err.path].push({
372 | field: err.path,
373 | rule: err.type,
374 | message: __(err.message)
375 | });
376 | });
377 |
378 | } else if (err.name === 'SequelizeDatabaseError') {
379 | // parse sequelize database errors
380 | if (err.message) {
381 | res.addMessage('error', err.message);
382 | }
383 | } else if (typeof err == 'string') {
384 | res.addMessage('error', err);
385 | } else if (err.name != 'SequelizeValidationError') {
386 | log.error('responses.queryError:unknowError ', {
387 | path: req.path,
388 | error: {
389 | message: err.message,
390 | name: err.name,
391 | stack: err.stack
392 | }
393 | });
394 | }
395 |
396 | // default error handler, push erros to messages and let response formaters resolve how to format this messages
397 |
398 | if (err.errors) {
399 | err.errors.forEach( (e)=> {
400 | res.addMessage('error', e.message, {
401 | field: e.path,
402 | rule: e.type,
403 | errorName: err.name,
404 | value: e.value,
405 | level: 'error',
406 | code: ( e.code || err.code ) // code if avaible
407 | });
408 | });
409 | }
410 | }
411 |
412 | if (err && err.name == 'SequelizeValidationError') {
413 | res.status(400);
414 | } else {
415 | res.status(500);
416 | delete res.locals.data;
417 | }
418 |
419 | res.format(req.we.responses.formaters);
420 | },
421 |
422 | /**
423 | * We.js core redirect
424 | *
425 | * @param {String} s response status
426 | * @param {String} p path
427 | */
428 | goTo(s ,p) {
429 | // save locals messages to flash
430 | this.res.moveLocalsMessagesToFlash();
431 | // use default redirect
432 | if (p) {
433 | this.res.redirect(s, p);
434 | } else {
435 | this.res.redirect(s);
436 | }
437 | }
438 | };
--------------------------------------------------------------------------------
/src/class/Plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * We.js Plugin prototype file
3 | */
4 |
5 | const _ = require('lodash'),
6 | path = require('path'),
7 | fs = require('fs'),
8 | async = require('async');
9 |
10 | module.exports = function getPluginPrototype (we) {
11 | /**
12 | * We.js plugin Class constructor
13 | *
14 | * @param {string} name plugin npm pakage name
15 | * @param {string} projectPath project path where the plugin is instaled
16 | */
17 | function Plugin (pluginPath) {
18 | this.pluginPath = pluginPath;
19 | this.we = we;
20 |
21 | this.events = this.we.events;
22 | this.hooks = this.we.hooks;
23 | this.router = this.we.router;
24 | /**
25 | * Plugin Assets
26 | * @type {Object}
27 | */
28 | this.assets = {
29 | js: {},
30 | css: {}
31 | };
32 |
33 | this['package.json'] = require( path.join( pluginPath , 'package.json') );
34 |
35 | this.controllersPath = path.join( this.pluginPath, this.controllerFolder );
36 | this.modelsPath = path.join( this.pluginPath, this.modelFolder );
37 | this.modelHooksPath = path.join( this.pluginPath, this.modelHookFolder );
38 | this.modelInstanceMethodsPath = path.join( this.pluginPath, this.modelInstanceMethodFolder );
39 | this.modelClassMethodsPath = path.join( this.pluginPath, this.modelClassMethodFolder );
40 |
41 | this.searchParsersPath = path.join( this.pluginPath, this.searchParsersFolder );
42 | this.searchTargetsPath = path.join( this.pluginPath, this.searchTargetsFolder );
43 |
44 | this.templatesPath = this.pluginPath + '/server/templates';
45 | this.helpersPath = this.pluginPath + '/server/helpers';
46 | this.resourcesPath = this.pluginPath + '/server/resources';
47 | this.routesPath = this.pluginPath + '/server/routes';
48 |
49 | this.helpers = {};
50 | this.layouts = {};
51 | this.templates = {};
52 |
53 | /**
54 | * Default plugin config object
55 | *
56 | * @type {Object}
57 | */
58 | this.configs = {};
59 |
60 |
61 | /**
62 | * Default plugin resources
63 | *
64 | * @type {Object}
65 | */
66 | this.controllers = {};
67 | this.models = {};
68 | this.routes = {};
69 |
70 | this.appFiles = [];
71 | this.appAdminFiles = [];
72 | }
73 |
74 | /**
75 | * Default initializer function, override in plugin.js file if need
76 | *
77 | * @param {Object} we we.js object
78 | * @param {Function} cb callback
79 | */
80 | Plugin.prototype.init = function initPlugin(we, cb) { return cb(); };
81 |
82 | /**
83 | * Set plugin config
84 | * @param {Object} config
85 | */
86 | Plugin.prototype.setConfigs = function setConfigs (configs) {
87 | this.configs = configs;
88 | };
89 |
90 | // default plugin paths
91 | Plugin.prototype.controllerFolder = 'server/controllers';
92 | Plugin.prototype.modelFolder = 'server/models';
93 | Plugin.prototype.modelHookFolder = 'server/models/hooks';
94 | Plugin.prototype.modelInstanceMethodFolder = 'server/models/instanceMethods';
95 | Plugin.prototype.modelClassMethodFolder = 'server/models/classMethods';
96 | Plugin.prototype.searchParsersFolder = 'server/search/parsers';
97 | Plugin.prototype.searchTargetsFolder = 'server/search/targets';
98 |
99 | /**
100 | * Set plugin routes
101 | *
102 | * @param {object} routes
103 | */
104 | Plugin.prototype.setRoutes = function setRoutes (routes) {
105 | const routePaths = Object.keys(routes);
106 | let routePath;
107 | for (let i = routePaths.length - 1; i >= 0; i--) {
108 | routePath = routePaths[i];
109 | this.setRoute(routePath, routes[routePath]);
110 | }
111 | };
112 |
113 | /**
114 | * Set one route in plugin routes
115 | * @param {string} path route path
116 | * @param {object} configs route configs how will be avaible as res.locals
117 | */
118 | Plugin.prototype.setRoute = function setRoute (routePath, configs) {
119 | this.routes[routePath] = configs;
120 | this.routes[routePath].path = routePath;
121 | this.routes[routePath].pluginPath = this.pluginPath;
122 | };
123 |
124 | Plugin.prototype.setResource = function setResource (opts) {
125 | let router = this.we.router,
126 | fullName = (opts.namePrefix || '') + opts.name;
127 |
128 | // first save resource in name or merge route options if exists
129 | if (!router.resourcesByName[fullName]) {
130 | router.resourcesByName[fullName] = opts;
131 | } else {
132 | _.merge(router.resourcesByName[fullName], opts);
133 | }
134 |
135 | if (opts.parent) {
136 | // is subroute
137 | if (!router.resourcesByName[opts.parent]) {
138 | // parent dont are set
139 | // temporary create parent resource wit subroutes attr
140 | router.resourcesByName[opts.parent] = { subRoutes: {} };
141 | // add reference to route in parent subroutes
142 | router.resourcesByName[opts.parent].subRoutes[fullName] = router.resourcesByName[fullName];
143 | } else {
144 | // parent resource is set
145 | if (!router.resourcesByName[opts.parent].subRoutes) {
146 | // add subRoutes object if dont are set
147 | router.resourcesByName[opts.parent].subRoutes = {};
148 | }
149 | if (!router.resourcesByName[opts.parent].subRoutes[fullName]) {
150 | router.resourcesByName[opts.parent].subRoutes[fullName] = {};
151 | }
152 | // add reference to route in parent resource subroutes
153 | router.resourcesByName[opts.parent].subRoutes[fullName] = router.resourcesByName[fullName];
154 | }
155 | } else {
156 | // is route route
157 | router.resources[fullName] = router.resourcesByName[fullName];
158 | }
159 | };
160 |
161 | /**
162 | * Set plugin layouts
163 | * @param {object} layouts
164 | */
165 | Plugin.prototype.setLayouts = function setLayouts (layouts) {
166 | this.layouts = layouts;
167 | };
168 |
169 | /**
170 | * Load all we.js plugin features
171 | *
172 | * Auto load avaible for plugins, helpers ...
173 | *
174 | */
175 | Plugin.prototype.loadFeatures = function loadFeatures (we, cb) {
176 | const self = this;
177 |
178 | if (self.fastLoader) {
179 | // fast loader option for load faster plugins:
180 | self.fastLoader(we, (err)=> {
181 | if (err) return cb(err);
182 | // plugin loader hook, for allow hook extensions:
183 | we.hooks.trigger('plugin:load:features', {
184 | plugin: self, we: we
185 | }, cb);
186 | });
187 | } else {
188 | // find and load all plugin features ...
189 | async.parallel([
190 | next => self.loadSearchParsers(next),
191 | next => self.loadSearchTargets(next),
192 | next => self.loadControllers(next),
193 | next => self.loadModelHooks(next),
194 | next => self.loadInstanceMethods(next),
195 | next => self.loadClassMethods(next),
196 | next => self.loadModels(next),
197 | next => self.loadResources(next),
198 | next => self.loadRoutes(next),
199 | function loaderHookExtensor (done) {
200 | we.hooks.trigger('plugin:load:features', {
201 | plugin: self, we: we
202 | }, done);
203 | },
204 | ], cb);
205 | }
206 | };
207 |
208 | /**
209 | * Get generic featureFiles
210 | * Read feature dir, check if are an .js file and return full path and name of each file
211 | *
212 | * @param {String} fgPath feature path
213 | * @param {Function} done callback
214 | */
215 |
216 | Plugin.prototype.getGenericFeatureFiles = function getGenericFeatureFiles (fgPath, done) {
217 | fs.readdir(fgPath, (e, fileNames) => {
218 | if (e) {
219 | if (e.code !== 'ENOENT') return done(e);
220 | return done(null, []);
221 | }
222 |
223 | done(null, fileNames
224 | .filter(fileName => { return fileName.endsWith('.js'); })
225 | .map(fileName => {
226 | return {
227 | name: fileName.slice(0, -3),
228 | path: path.join(fgPath, fileName)
229 | };
230 | }));
231 | });
232 | };
233 |
234 | /**
235 | * load plugin controllers
236 | */
237 | Plugin.prototype.loadControllers = function loadControllers (done) {
238 | this.getGenericFeatureFiles(this.controllersPath, (e, modules) => {
239 | if (e) return done(e);
240 |
241 | modules.forEach(m => {
242 | let attrs = require(m.path);
243 | attrs._controllersPath = this.controllersPath;
244 | we.controllers[m.name] = new we.class.Controller(attrs);
245 | });
246 |
247 | done();
248 | });
249 | };
250 |
251 | /**
252 | * load plugin model hooks
253 | */
254 | Plugin.prototype.loadModelHooks = function loadModelHooks (done) {
255 | this.getGenericFeatureFiles(this.modelHooksPath, (e, modules) => {
256 | if (e) return done(e);
257 |
258 | modules.forEach(m => {
259 | we.db.modelHooks[m.name] = require(m.path).bind({ we: we });
260 | });
261 |
262 | done();
263 | });
264 | };
265 |
266 | /**
267 | * load plugin model instance methods
268 | */
269 | Plugin.prototype.loadInstanceMethods = function loadInstanceMethods (done) {
270 | this.getGenericFeatureFiles(this.modelInstanceMethodsPath, (e, modules) => {
271 | if (e) return done(e);
272 |
273 | modules.forEach(m => {
274 | we.db.modelInstanceMethods[m.name] = require(m.path);
275 | });
276 |
277 | done();
278 | });
279 | };
280 |
281 | /**
282 | * load plugin model class methods
283 | */
284 | Plugin.prototype.loadClassMethods = function loadClassMethods (done) {
285 | this.getGenericFeatureFiles(this.modelClassMethodsPath, (e, modules) => {
286 | if (e) return done(e);
287 |
288 | modules.forEach(m => {
289 | we.db.modelClassMethods[m.name] = require(m.path);
290 | });
291 |
292 | done();
293 | });
294 | };
295 |
296 | /**
297 | * load plugin search parsers
298 | */
299 | Plugin.prototype.loadSearchParsers = function loadSearchParsers (done) {
300 | this.getGenericFeatureFiles(this.searchParsersPath, (e, modules) => {
301 | if (e) return done(e);
302 |
303 | modules.forEach(m => {
304 | we.router.search.parsers[m.name] = require(m.path).bind({ we: we });
305 | });
306 |
307 | done();
308 | });
309 | };
310 |
311 | /**
312 | * load plugin search targets
313 | */
314 | Plugin.prototype.loadSearchTargets = function loadSearchTargets (done) {
315 | this.getGenericFeatureFiles(this.searchTargetsPath, (e, modules) => {
316 | if (e) return done(e);
317 |
318 | modules.forEach(m => {
319 | we.router.search.targets[m.name] = require(m.path).bind({ we: we });
320 | });
321 |
322 | done();
323 | });
324 | };
325 |
326 | /**
327 | * load plugin models with suport to JSON and .js file formats
328 | */
329 | Plugin.prototype.loadModels = function loadModels (done) {
330 | fs.readdir(this.modelsPath, (e, fileNames) => {
331 | if (e) {
332 | if (e.code !== 'ENOENT') return done(e);
333 | return done();
334 | }
335 |
336 | let name;
337 |
338 | fileNames.forEach(fileName => {
339 | if (fileName.endsWith('.js')) {
340 | // js model
341 | name = fileName.slice(0, -3);
342 | we.db.modelsConfigs[name] = require(path.join( this.modelsPath, fileName) )(we);
343 | } else if (fileName.endsWith('.json')) {
344 | // json model
345 | name = fileName.slice(0, -5);
346 | we.db.modelsConfigs[name] =
347 | we.db.defineModelFromJson( require(path.join(this.modelsPath, fileName)), we);
348 | }
349 | });
350 |
351 | done();
352 | });
353 | };
354 |
355 | /**
356 | * Load route resources from folder server/resources
357 | */
358 | Plugin.prototype.loadResources = function loadResources (cb) {
359 | fs.readdir(this.resourcesPath, (err, list) => {
360 | if (err) {
361 | if (err.code === 'ENOENT') return cb();
362 | return cb(err);
363 | }
364 |
365 | list
366 | .map(item => { return path.join(this.resourcesPath, item); })
367 | .forEach(p => { this.setResource( require(p) ); });
368 |
369 | cb();
370 | });
371 | };
372 |
373 | /**
374 | * Load routes from folder server/routes
375 | */
376 | Plugin.prototype.loadRoutes = function loadRoutes (cb) {
377 | fs.readdir(this.routesPath, (err, list) => {
378 | if (err) {
379 | if (err.code === 'ENOENT') return cb();
380 | return cb(err);
381 | }
382 |
383 | list
384 | .map(item => { return path.join(this.routesPath, item); })
385 | .forEach(p => { this.setRoutes( require(p) ); });
386 |
387 | cb();
388 | });
389 | };
390 |
391 | // -- Add css and js to we-plugin-view assets feature
392 |
393 | Plugin.prototype.addJs = function addJs (fileName, cfg) {
394 | this.assets.js[fileName] = cfg;
395 | };
396 |
397 | Plugin.prototype.addCss = function addCss (fileName, cfg) {
398 | this.assets.css[fileName] = cfg;
399 | };
400 |
401 | return Plugin;
402 | };
--------------------------------------------------------------------------------
/test/tests/requests/resource_jsonAPI.test.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert'),
2 | request = require('supertest'),
3 | helpers = require('we-test-tools').helpers,
4 | Chance = require('chance'),
5 | chance = new Chance();
6 |
7 | let _, http, we;
8 |
9 | function postStub (creatorId, jsonAPI) {
10 | if (jsonAPI) {
11 | return {
12 | data: {
13 | attributes: {
14 | title: chance.sentence({words: 5}),
15 | text: chance.paragraph(),
16 | creatorId: creatorId
17 | },
18 | relationships: {
19 | creator: [ { id: creatorId, type: 'user '}]
20 | }
21 | }
22 | };
23 | } else {
24 | return {
25 | title: chance.sentence({words: 5}),
26 | text: chance.paragraph(),
27 | creatorId: creatorId,
28 | tags: [{
29 | text: chance.word(),
30 | }, {
31 | text: chance.word()
32 | }]
33 | };
34 | }
35 | }
36 |
37 | describe('resourceRequests_jsonAPI', function() {
38 | let su;
39 |
40 | before(function (done) {
41 | http = helpers.getHttp();
42 | we = helpers.getWe();
43 | _ = we.utils._;
44 | return done();
45 | });
46 |
47 | before(function(done) {
48 | we.db.models.user.create({
49 | displayName: 'Testonildo'
50 | })
51 | .then(function(u) {
52 | su = u;
53 | return u;
54 | })
55 | .nodeify(done);
56 | });
57 |
58 | afterEach(function(done) {
59 | const models = we.db.models;
60 |
61 | Promise.all([
62 | models.tag.destroy({truncate: true}),
63 | models.post.destroy({truncate: true})
64 | ])
65 | .then(function() {
66 | done();
67 | })
68 | .catch(done);
69 | });
70 |
71 | describe('json', function() {
72 | describe('GET /post', function() {
73 | it ('should get posts list formated with jsonAPI', function (done) {
74 | let posts = [
75 | postStub(su.id),
76 | postStub(su.id),
77 | postStub(su.id)
78 | ];
79 |
80 | let postsByTitle = {};
81 | posts.forEach(function(p){
82 | postsByTitle[p.title] = p;
83 | });
84 |
85 | we.utils.async.eachSeries(posts, function(p, next) {
86 | we.db.models.post
87 | .create(p, {
88 | include: [{
89 | model: we.db.models.tag,
90 | as: 'tags'
91 | }]
92 | })
93 | .then(function() {
94 | next();
95 | return null;
96 | })
97 | .catch(next);
98 | }, function(err) {
99 | if (err) return done(err);
100 |
101 | request(http)
102 | .get('/post')
103 | .set('Content-Type', 'application/vnd.api+json')
104 | .set('Accept', 'application/vnd.api+json')
105 | .expect(200)
106 | .end(function (err, res) {
107 | if (err) {
108 | console.log('res.body>', res.body);
109 | throw err;
110 | }
111 |
112 | assert(res.body.data);
113 | assert(_.isArray(res.body.data) );
114 |
115 | res.body.data.forEach(function (p) {
116 |
117 | assert(p.relationships);
118 | assert(p.attributes);
119 | assert(p.id);
120 |
121 | let pn = postsByTitle[p.attributes.title];
122 | assert(pn);
123 |
124 | assert(p.id, pn.id);
125 |
126 | assert.equal(p.attributes.title, pn.title);
127 | assert.equal(p.attributes.text, pn.text);
128 |
129 | assert(p.relationships);
130 | assert(p.relationships.tags);
131 | assert.equal(p.relationships.tags.data.length, 2);
132 | });
133 |
134 | assert.equal(res.body.meta.count, 3);
135 |
136 | done();
137 | });
138 | });
139 |
140 | });
141 |
142 | it ('should search for posts by title', function (done) {
143 | let posts = [
144 | postStub(),
145 | postStub(),
146 | postStub()
147 | ];
148 |
149 | we.db.models.post.bulkCreate(posts)
150 | .spread(function(){
151 | request(http)
152 | .get('/post?title='+posts[1].title)
153 | .set('Content-Type', 'application/vnd.api+json')
154 | .set('Accept', 'application/vnd.api+json')
155 | .expect(200)
156 | .end(function (err, res) {
157 | if (err) throw err;
158 |
159 | assert.equal(res.body.data.length, 1);
160 | assert(res.body.data[0].id, posts[1].title);
161 | assert(res.body.data[0].type, 'post');
162 | assert.equal(res.body.data[0].attributes.title, posts[1].title);
163 | assert.equal(res.body.data[0].attributes.text, posts[1].text);
164 |
165 | assert.equal(res.body.meta.count, 1);
166 |
167 | done();
168 | });
169 | }).catch(done);
170 | });
171 |
172 |
173 | it ('should search for posts by text', function (done) {
174 | let posts = [
175 | postStub(),
176 | postStub(),
177 | postStub(),
178 | postStub()
179 | ];
180 |
181 | let searchText = ' mussum ipsum';
182 |
183 | posts[1].text += searchText;
184 | posts[2].text += searchText;
185 |
186 | we.db.models.post.bulkCreate(posts)
187 | .spread(function(){
188 | request(http)
189 | .get('/post?text='+searchText)
190 | .set('Content-Type', 'application/vnd.api+json')
191 | .set('Accept', 'application/vnd.api+json')
192 | .expect(200)
193 | .end(function (err, res) {
194 | if (err) throw err;
195 |
196 | assert.equal(res.body.data.length, 2);
197 |
198 | res.body.data.forEach(function(p){
199 | assert.equal(p.type, 'post')
200 | assert(p.attributes.text.indexOf(searchText) >-1)
201 | })
202 |
203 | assert.equal(res.body.meta.count, 2)
204 |
205 | done()
206 | });
207 | })
208 | .catch(done)
209 | });
210 |
211 | it ('should search for posts by text with and and inTitleAndText, orWithComaParser search in q param', function (done) {
212 | let posts = [
213 | postStub(),
214 | postStub(),
215 | postStub(),
216 | postStub()
217 | ];
218 |
219 | let searchText = ' mussum ipsum';
220 | let searchText2 = '2222m ipsum';
221 |
222 | posts[1].title = searchText;
223 | posts[1].text = searchText;
224 |
225 | posts[2].title = searchText2;
226 | posts[2].text = searchText2;
227 |
228 | we.db.models.post.bulkCreate(posts)
229 | .spread(function(){
230 | request(http)
231 | .get('/post?q='+searchText+','+searchText2)
232 | .set('Content-Type', 'application/vnd.api+json')
233 | .set('Accept', 'application/vnd.api+json')
234 | .expect(200)
235 | .end(function (err, res) {
236 | if (err) {
237 | console.log('res.text>', res.text)
238 | throw err
239 | }
240 |
241 | assert.equal(res.body.data.length, 2);
242 | assert.equal(res.body.meta.count, 2);
243 |
244 | done();
245 | });
246 | }).catch(done);
247 | });
248 | });
249 |
250 | describe('GET /post/:id', function(){
251 | it ('should get one post', function (done) {
252 | we.db.models.post.create(postStub(su.id))
253 | .then(function (p) {
254 | request(http)
255 | .get('/post/'+p.id)
256 | .set('Content-Type', 'application/vnd.api+json')
257 | .set('Accept', 'application/vnd.api+json')
258 | .expect(200)
259 | .end(function (err, res) {
260 | if (err) throw err;
261 |
262 | assert(res.body.data.id);
263 | assert.equal(res.body.data.id, p.id);
264 | assert.equal(res.body.data.type, 'post');
265 |
266 | assert.equal(res.body.data.attributes.title, p.title);
267 | assert.equal(res.body.data.attributes.text, p.text);
268 |
269 | done();
270 | });
271 | return null;
272 | })
273 | .catch(done);
274 | });
275 |
276 | it ('should return 404 to not found', function (done) {
277 | let info = we.log.info;
278 | we.log.info = function() {};
279 | request(http)
280 | .get('/post/1232131')
281 | .expect(404)
282 | .end(function (err, res) {
283 | if (err) throw err;
284 |
285 | assert(!res.body.post);
286 | we.log.info = info;
287 | done();
288 | });
289 | });
290 | });
291 |
292 | describe('PUT /post/:id', function(){
293 | it ('should update one post attr', function (done) {
294 | we.db.models.post.create(postStub())
295 | .then(function (p) {
296 |
297 | let updateData = {
298 | data: {
299 | attributes: {
300 | title: 'iIIeeei'
301 | }
302 | }
303 | };
304 | request(http)
305 | .put('/post/'+p.id)
306 | .send(updateData)
307 | .set('Content-Type', 'application/vnd.api+json')
308 | .set('Accept', 'application/vnd.api+json')
309 | .expect(200)
310 | .end(function (err, res) {
311 | if (err) throw err;
312 |
313 | assert(res.body.data.id);
314 | assert.equal(res.body.data.attributes.title, updateData.data.attributes.title);
315 | assert.equal(res.body.data.attributes.text, p.text);
316 |
317 | done();
318 | });
319 | return null;
320 | })
321 | .catch(done);
322 | });
323 | });
324 |
325 | describe('DELETE /post/:id', function(){
326 | it ('should delete one post', function (done) {
327 | we.db.models.post.create(postStub())
328 | .then(function (p) {
329 | request(http)
330 | .delete('/post/'+p.id)
331 | .set('Content-Type', 'application/vnd.api+json')
332 | .set('Accept', 'application/vnd.api+json')
333 | .expect(204)
334 | .end(function (err, res) {
335 | if (err) throw err;
336 |
337 | assert(!res.text);
338 |
339 | we.db.models.post.findById(p.id)
340 | .then(function(ps){
341 | assert(!ps);
342 | done();
343 | return null;
344 | })
345 | .catch(done);
346 | });
347 | return null;
348 | })
349 | .catch(done);
350 | });
351 | });
352 |
353 | describe('POST /post', function(){
354 | it ('should create one resource with valid data', function (done) {
355 | let p = postStub(null, true);
356 | request(http)
357 | .post('/post')
358 | .send(p)
359 | .set('Content-Type', 'application/vnd.api+json')
360 | .set('Accept', 'application/vnd.api+json')
361 | .expect(201)
362 | .end(function (err, res) {
363 | if (err) throw err;
364 |
365 | assert(res.body.data.id);
366 | assert.equal(res.body.data.type, 'post');
367 | assert.equal(res.body.data.attributes.title, p.data.attributes.title);
368 | assert.equal(res.body.data.attributes.text, p.data.attributes.text);
369 |
370 | done();
371 | });
372 | });
373 |
374 | it ('should create one resource with valid data and JSONApi post data', function (done) {
375 | let p = postStub(null, true);
376 | request(http)
377 | .post('/post')
378 | .send(p)
379 | .set('Content-Type', 'application/vnd.api+json')
380 | .set('Accept', 'application/vnd.api+json')
381 | .expect(201)
382 | .end(function (err, res) {
383 | if (err) {
384 | console.log('res.text>', res.text)
385 | throw err
386 | }
387 |
388 | assert(res.body.data.id);
389 | assert.equal(res.body.data.type, 'post');
390 | assert.equal(res.body.data.attributes.title, p.data.attributes.title);
391 | assert.equal(res.body.data.attributes.text, p.data.attributes.text);
392 |
393 | done();
394 | });
395 | });
396 |
397 | it ('should return error if not set an not null attr', function (done) {
398 | let p = postStub(null, true);
399 | p.data.attributes.title = null;
400 |
401 | request(http)
402 | .post('/post')
403 | .send(p)
404 | .set('Content-Type', 'application/vnd.api+json')
405 | .set('Accept', 'application/vnd.api+json')
406 | .expect(400)
407 | .end(function (err, res) {
408 | if (err) throw err;
409 |
410 | assert(!res.body.data.id);
411 | assert.equal(res.body.errors[0].status, 400);
412 | assert.equal(res.body.errors[0].title, 'post.title cannot be null');
413 |
414 | // assert.equal(res.body.data.attributes.title, p.data.attributes.title);
415 | // assert.equal(res.body.data.attributes.text, p.data.attributes.text);
416 |
417 | done();
418 | });
419 | });
420 | });
421 | });
422 | });
423 |
--------------------------------------------------------------------------------