mirror of
https://github.com/strapi/strapi.git
synced 2025-10-23 22:10:19 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
7e00be7057
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -3,15 +3,17 @@
|
||||
<!-- Uncomment the correct contribution type. !-->
|
||||
|
||||
My PR is a:
|
||||
<!-- 💥 Breaking change -->
|
||||
<!-- 🐛 Bug fix -->
|
||||
<!-- 💅 Enhancement -->
|
||||
<!-- 🚀 New feature -->
|
||||
- [ ] 💥 Breaking change
|
||||
- [ ] 🐛 Bug fix #issueNumber
|
||||
- [ ] 💅 Enhancement
|
||||
- [ ] 🚀 New feature
|
||||
|
||||
Main update on the:
|
||||
<!-- Admin -->
|
||||
<!-- Documentation -->
|
||||
<!-- Framework -->
|
||||
<!-- Plugin -->
|
||||
- [ ] Admin
|
||||
- [ ] Documentation
|
||||
- [ ] Framework
|
||||
- [ ] Plugin
|
||||
|
||||
<!-- Write a short description of what your PR does and link the concerned issues of your update. -->
|
||||
|
||||
<!-- ⚠️ Please link issue(s) you close / fix by using GitHub keywords https://help.github.com/articles/closing-issues-using-keywords/ !-->
|
||||
|
||||
@ -37,6 +37,16 @@ Find products having a price equal or greater than `3`.
|
||||
|
||||
`GET /products?price_gte=3`
|
||||
|
||||
#### Relations
|
||||
You can also use filters into a relation attribute which will be applied to the first level of the request.
|
||||
Find users having written a post named `Title`.
|
||||
`GET /users?posts.name=Title`
|
||||
Find posts written by a user having more than 12 years old.
|
||||
`GET /posts?author.age_gt=12`
|
||||
> Note: You can't use filter to have specific results inside relation, like "Find users and only their posts older than yesterday" as example. If you need it, you can modify or create your own service ou use [GraphQL](./graphql.md#query-api).
|
||||
|
||||
> Warning: this filter isn't available for `upload` plugin
|
||||
|
||||
### Sort
|
||||
|
||||
Sort according to a specific field.
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright@React-FullStory (https://github.com/cereallarceny/react-fullstory)
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const canUseDOM = !!(
|
||||
typeof window !== 'undefined' &&
|
||||
window.document &&
|
||||
window.document.createElement
|
||||
);
|
||||
|
||||
export const getWindowFullStory = () => window[window['_fs_namespace']];
|
||||
|
||||
class FullStory extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
window['_fs_debug'] = false;
|
||||
window['_fs_host'] = 'fullstory.com';
|
||||
window['_fs_org'] = props.org;
|
||||
window['_fs_namespace'] = 'FS';
|
||||
(function(m,n,e,t,l,o,g,y) {
|
||||
if (e in m) {
|
||||
if(m.console && m.console.log) {
|
||||
m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g = m[e]= function(a,b,s) {
|
||||
g.q ? g.q.push([a,b,s]) : g._api(a,b,s);
|
||||
};
|
||||
g.q=[];
|
||||
o = n.createElement(t);
|
||||
o.async = 1;
|
||||
o.src = `https://${window._fs_host}/s/fs.js`;
|
||||
y = n.getElementsByTagName(t)[0];
|
||||
y.parentNode.insertBefore(o,y);
|
||||
g.identify = function(i,v,s) {
|
||||
g(l,{ uid:i },s);
|
||||
|
||||
if (v) {
|
||||
g(l,v,s);
|
||||
}
|
||||
};
|
||||
g.setUserVars = function(v,s) {
|
||||
g(l,v,s);
|
||||
};
|
||||
g.event = function(i,v,s) {
|
||||
g('event',{ n:i,p:v },s);
|
||||
};
|
||||
g.shutdown = function() {
|
||||
g("rec",!1);
|
||||
};
|
||||
g.restart = function() {
|
||||
g("rec",!0);
|
||||
};
|
||||
g.consent = function(a) {
|
||||
g("consent",!arguments.length||a);
|
||||
};
|
||||
g.identifyAccount = function(i,v) {
|
||||
o = 'account';
|
||||
v = v||{};
|
||||
v.acctId = i;
|
||||
g(o,v);
|
||||
};
|
||||
g.clearUserCookie = function() {};
|
||||
})(window, document, window['_fs_namespace'], 'script', 'user');
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (!canUseDOM || !getWindowFullStory()) return false;
|
||||
|
||||
getWindowFullStory().shutdown();
|
||||
|
||||
delete getWindowFullStory();
|
||||
}
|
||||
|
||||
render() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FullStory.propTypes = {
|
||||
org: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default FullStory;
|
||||
@ -43,6 +43,7 @@ import Logout from 'components/Logout';
|
||||
import NotFoundPage from 'containers/NotFoundPage/Loadable';
|
||||
import OverlayBlocker from 'components/OverlayBlocker';
|
||||
import PluginPage from 'containers/PluginPage';
|
||||
import FullStory from 'components/FullStory';
|
||||
// Utils
|
||||
import auth from 'utils/auth';
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
@ -73,12 +74,12 @@ export class AdminPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { adminPage: { allowGa }, location: { pathname }, plugins } = this.props;
|
||||
const { adminPage: { uuid }, location: { pathname }, plugins } = this.props;
|
||||
|
||||
if (prevProps.location.pathname !== pathname) {
|
||||
this.checkLogin(this.props);
|
||||
|
||||
if (allowGa) {
|
||||
if (uuid) {
|
||||
ReactGA.pageview(pathname);
|
||||
}
|
||||
}
|
||||
@ -198,6 +199,7 @@ export class AdminPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div className={styles.adminPage}>
|
||||
{this.props.adminPage.uuid ? <FullStory org="GK708" /> : ''}
|
||||
{this.showLeftMenu() && (
|
||||
<LeftMenu
|
||||
plugins={this.retrievePlugins()}
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
allowGa: true,
|
||||
uuid: false,
|
||||
currentEnvironment: 'development',
|
||||
isLoading: true,
|
||||
layout: Map({}),
|
||||
@ -22,7 +22,7 @@ function adminPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GET_ADMIN_DATA_SUCCEEDED:
|
||||
return state
|
||||
.update('allowGa', () => action.data.allowGa)
|
||||
.update('uuid', () => action.data.uuid)
|
||||
.update('currentEnvironment', () => action.data.currentEnvironment)
|
||||
.update('layout', () => Map(action.data.layout))
|
||||
.update('strapiVersion', () => action.data.strapiVersion)
|
||||
|
||||
@ -16,13 +16,13 @@ function* getData() {
|
||||
yield call(request, `${strapi.backendURL}/users/me`, { method: 'GET' });
|
||||
}
|
||||
|
||||
const [{ allowGa }, { strapiVersion }, { currentEnvironment }, { layout }] = yield all([
|
||||
const [{ uuid }, { strapiVersion }, { currentEnvironment }, { layout }] = yield all([
|
||||
call(request, '/admin/gaConfig', { method: 'GET' }),
|
||||
call(request, '/admin/strapiVersion', { method: 'GET' }),
|
||||
call(request, '/admin/currentEnvironment', { method: 'GET' }),
|
||||
call(request, '/admin/layout', { method: 'GET' }),
|
||||
]);
|
||||
yield put(getAdminDataSucceeded({ allowGa, strapiVersion, currentEnvironment, layout }));
|
||||
yield put(getAdminDataSucceeded({ uuid, strapiVersion, currentEnvironment, layout }));
|
||||
|
||||
} catch(err) {
|
||||
console.log(err); // eslint-disable-line no-console
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
.containerFluid {
|
||||
padding: 18px 30px !important;
|
||||
> div:first-child {
|
||||
max-height: 33px;
|
||||
margin-bottom: 48px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,8 +28,7 @@ module.exports = {
|
||||
|
||||
getGaConfig: async ctx => {
|
||||
try {
|
||||
const allowGa = _.get(strapi.config, 'info.customs.allowGa', true);
|
||||
ctx.send({ allowGa });
|
||||
ctx.send({ uuid: _.get(strapi.config, 'uuid', false) });
|
||||
} catch(err) {
|
||||
ctx.badRequest(null, [{ messages: [{ id: 'An error occurred' }] }]);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ If you don't want to share your data with us, you can simply modify the `strapi`
|
||||
```json
|
||||
{
|
||||
"strapi": {
|
||||
"allowGa": false
|
||||
"uuid": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global <%= globalID %> */
|
||||
|
||||
/**
|
||||
* <%= filename %> service
|
||||
@ -9,9 +10,6 @@
|
||||
// Public dependencies.
|
||||
const _ = require('lodash');
|
||||
|
||||
// Strapi utilities.
|
||||
const utils = require('strapi-hook-bookshelf/lib/utils/');
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
@ -21,6 +19,8 @@ module.exports = {
|
||||
*/
|
||||
|
||||
fetchAll: (params) => {
|
||||
// Get model hook
|
||||
const hook = strapi.hook[<%= globalID %>.orm];
|
||||
// Convert `params` object to filters compatible with Bookshelf.
|
||||
const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params);
|
||||
// Select field to populate.
|
||||
@ -29,22 +29,18 @@ module.exports = {
|
||||
.map(ast => ast.alias);
|
||||
|
||||
return <%= globalID %>.query(function(qb) {
|
||||
_.forEach(filters.where, (where, key) => {
|
||||
if (_.isArray(where.value) && where.symbol !== 'IN') {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value])
|
||||
}
|
||||
// Generate match stage.
|
||||
hook.load().generateMatchStage(qb)(<%= globalID %>, filters);
|
||||
|
||||
if (_.has(filters, 'start')) qb.offset(filters.start);
|
||||
if (_.has(filters, 'limit')) qb.limit(filters.limit);
|
||||
if (!_.isEmpty(filters.sort)) {
|
||||
if (filters.sort.key) {
|
||||
qb.orderBy(filters.sort.key, filters.sort.order);
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
qb.orderBy(filters.sort);
|
||||
}
|
||||
});
|
||||
|
||||
if (filters.sort) {
|
||||
qb.orderBy(filters.sort.key, filters.sort.order);
|
||||
}
|
||||
|
||||
qb.offset(filters.start);
|
||||
qb.limit(filters.limit);
|
||||
}).fetchAll({
|
||||
withRelated: populate
|
||||
});
|
||||
@ -81,7 +77,7 @@ module.exports = {
|
||||
_.forEach(filters.where, (where, key) => {
|
||||
if (_.isArray(where.value)) {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value])
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
|
||||
}
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global <%= globalID %> */
|
||||
|
||||
/**
|
||||
* <%= filename %> service
|
||||
@ -9,6 +10,8 @@
|
||||
// Public dependencies.
|
||||
const _ = require('lodash');
|
||||
|
||||
const { models: { mergeStages } } = require('strapi-utils');
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
@ -17,22 +20,24 @@ module.exports = {
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
fetchAll: (params) => {
|
||||
fetchAll: (params, next, { populate } = {}) => {
|
||||
// Convert `params` object to filters compatible with Mongo.
|
||||
const filters = strapi.utils.models.convertParams('<%= globalID.toLowerCase() %>', params);
|
||||
// Select field to populate.
|
||||
const populate = <%= globalID %>.associations
|
||||
.filter(ast => ast.autoPopulate !== false)
|
||||
.map(ast => ast.alias)
|
||||
.join(' ');
|
||||
const hook = strapi.hook[<%= globalID %>.orm];
|
||||
// Generate stages.
|
||||
const populateStage = hook.load().generateLookupStage(<%= globalID %>, { whitelistedPopulate: populate }); // Nested-Population
|
||||
const matchStage = hook.load().generateMatchStage(<%= globalID %>, filters); // Nested relation filter
|
||||
const aggregateStages = mergeStages(populateStage, matchStage);
|
||||
|
||||
return <%= globalID %>
|
||||
.find()
|
||||
.where(filters.where)
|
||||
.sort(filters.sort)
|
||||
const result = <%= globalID %>.aggregate(aggregateStages)
|
||||
.skip(filters.start)
|
||||
.limit(filters.limit)
|
||||
.populate(populate);
|
||||
.limit(filters.limit);
|
||||
|
||||
if (_.has(filters, 'start')) result.skip(filters.start);
|
||||
if (_.has(filters, 'limit')) result.limit(filters.limit);
|
||||
if (!_.isEmpty(filters.sort)) result.sort(filters.sort);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -56,6 +56,7 @@ module.exports = scope => {
|
||||
'dependencies': Object.assign({}, {
|
||||
'lodash': '^4.17.5',
|
||||
'strapi': getDependencyVersion(cliPkg, 'strapi'),
|
||||
'strapi-utils': getDependencyVersion(cliPkg, 'strapi'),
|
||||
[scope.client.connector]: getDependencyVersion(cliPkg, 'strapi'),
|
||||
}, additionalsDependencies, {
|
||||
[scope.client.module]: scope.client.version
|
||||
|
||||
@ -502,7 +502,7 @@ module.exports = function(strapi) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
strapi.log.warn(`The SQL database indexes haven't been generated successfully. Please enable the debug mode for more details.`);
|
||||
strapi.log.warn('The SQL database indexes haven\'t been generated successfully. Please enable the debug mode for more details.');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -677,24 +677,11 @@ module.exports = function(strapi) {
|
||||
}
|
||||
};
|
||||
|
||||
const table = _.get(manyRelations, 'collectionName') ||
|
||||
_.map(
|
||||
_.sortBy(
|
||||
[
|
||||
collection.attributes[
|
||||
manyRelations.via
|
||||
],
|
||||
manyRelations
|
||||
],
|
||||
'collection'
|
||||
),
|
||||
table => {
|
||||
return _.snakeCase(
|
||||
// eslint-disable-next-line prefer-template
|
||||
pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via)
|
||||
);
|
||||
}
|
||||
).join('__');
|
||||
const table = _.get(manyRelations, 'collectionName')
|
||||
|| utilsModels.getCollectionName(
|
||||
collection.attributes[manyRelations.via],
|
||||
manyRelations
|
||||
);
|
||||
|
||||
await handler(table, attributes);
|
||||
}
|
||||
@ -813,24 +800,11 @@ module.exports = function(strapi) {
|
||||
strapi.plugins[details.plugin].models[details.collection]:
|
||||
strapi.models[details.collection];
|
||||
|
||||
const collectionName = _.get(details, 'collectionName') ||
|
||||
_.map(
|
||||
_.sortBy(
|
||||
[
|
||||
collection.attributes[
|
||||
details.via
|
||||
],
|
||||
details
|
||||
],
|
||||
'collection'
|
||||
),
|
||||
table => {
|
||||
return _.snakeCase(
|
||||
// eslint-disable-next-line prefer-template
|
||||
pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via)
|
||||
);
|
||||
}
|
||||
).join('__');
|
||||
const collectionName = _.get(details, 'collectionName')
|
||||
|| utilsModels.getCollectionName(
|
||||
collection.attributes[details.via],
|
||||
details,
|
||||
);
|
||||
|
||||
const relationship = _.clone(
|
||||
collection.attributes[details.via]
|
||||
|
||||
@ -35,6 +35,77 @@ module.exports = {
|
||||
return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
|
||||
},
|
||||
|
||||
generateMatchStage: function (qb) {
|
||||
return (strapiModel, filters) => {
|
||||
if (!filters) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 1st level deep filter
|
||||
if (filters.where) {
|
||||
this.generateMatchStage(qb)(strapiModel, { relations: filters.where });
|
||||
}
|
||||
|
||||
// 2nd+ level deep filter
|
||||
_.forEach(filters.relations, (value, key) => {
|
||||
if (key !== 'relations') {
|
||||
const association = strapiModel.associations.find(a => a.alias === key);
|
||||
if (!association) {
|
||||
const fieldKey = `${strapiModel.collectionName}.${key}`;
|
||||
if (_.isArray(value.value) && value.symbol !== 'IN') {
|
||||
for (const value in value.value) {
|
||||
qb[value ? 'where' : 'orWhere'](fieldKey, value.symbol, value.value[value]);
|
||||
}
|
||||
} else {
|
||||
qb.where(fieldKey, value.symbol, value.value);
|
||||
}
|
||||
} else {
|
||||
const model = association.plugin ?
|
||||
strapi.plugins[association.plugin].models[association.model || association.collection] :
|
||||
strapi.models[association.model || association.collection];
|
||||
const relationTable = model.collectionName;
|
||||
|
||||
qb.distinct();
|
||||
|
||||
if (association.nature === 'manyToMany') {
|
||||
// Join on both ends
|
||||
qb.innerJoin(
|
||||
association.tableCollectionName,
|
||||
`${association.tableCollectionName}.${strapiModel.info.name}_${strapiModel.primaryKey}`,
|
||||
`${strapiModel.collectionName}.${strapiModel.primaryKey}`,
|
||||
);
|
||||
|
||||
qb.innerJoin(
|
||||
relationTable,
|
||||
`${association.tableCollectionName}.${strapiModel.attributes[key].attribute}_${strapiModel.attributes[key].column}`,
|
||||
`${relationTable}.${model.primaryKey}`,
|
||||
);
|
||||
} else {
|
||||
const externalKey = association.type === 'collection'
|
||||
? `${relationTable}.${association.via}`
|
||||
: `${relationTable}.${model.primaryKey}`;
|
||||
|
||||
const internalKey = association.type === 'collection'
|
||||
? `${strapiModel.collectionName}.${strapiModel.primaryKey}`
|
||||
: `${strapiModel.collectionName}.${association.alias}`;
|
||||
|
||||
qb.innerJoin(relationTable, externalKey, internalKey);
|
||||
}
|
||||
|
||||
if (_.isPlainObject(value)) {
|
||||
this.generateMatchStage(qb)(
|
||||
model,
|
||||
{ relations: value.value }
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.generateMatchStage(qb)(strapiModel, { relations: value });
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
findOne: async function (params, populate) {
|
||||
const record = await this
|
||||
.forge({
|
||||
|
||||
@ -17,6 +17,8 @@ const { models: utilsModels } = require('strapi-utils');
|
||||
|
||||
// Local helpers.
|
||||
const utils = require('./utils/');
|
||||
const _utils = utils();
|
||||
|
||||
const relations = require('./relations');
|
||||
|
||||
/**
|
||||
@ -488,16 +490,16 @@ module.exports = function (strapi) {
|
||||
result.value = value;
|
||||
break;
|
||||
case '_sort':
|
||||
result.key = `sort`;
|
||||
result.key = 'sort';
|
||||
result.value = (_.toLower(value) === 'desc') ? '-' : '';
|
||||
result.value += key;
|
||||
break;
|
||||
case '_start':
|
||||
result.key = `start`;
|
||||
result.key = 'start';
|
||||
result.value = parseFloat(value);
|
||||
break;
|
||||
case '_limit':
|
||||
result.key = `limit`;
|
||||
result.key = 'limit';
|
||||
result.value = parseFloat(value);
|
||||
break;
|
||||
case '_contains':
|
||||
@ -520,6 +522,13 @@ module.exports = function (strapi) {
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
postProcessValue: (value) => {
|
||||
if (_.isArray(value)) {
|
||||
return value.map(_utils.valueToId);
|
||||
}
|
||||
return _utils.valueToId(value);
|
||||
}
|
||||
}, relations);
|
||||
|
||||
|
||||
@ -10,11 +10,156 @@ const _ = require('lodash');
|
||||
// Utils
|
||||
const { models: { getValuePrimaryKey } } = require('strapi-utils');
|
||||
|
||||
|
||||
const buildTempFieldPath = field => {
|
||||
return `__${field}`;
|
||||
};
|
||||
|
||||
const restoreRealFieldPath = (field, prefix) => {
|
||||
return `${prefix}${field}`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getModel: function (model, plugin) {
|
||||
return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
|
||||
},
|
||||
|
||||
generateLookupStage: function (strapiModel, { whitelistedPopulate = null, prefixPath = '' } = {}) {
|
||||
return strapiModel.associations
|
||||
.filter(ast => {
|
||||
if (whitelistedPopulate) {
|
||||
return _.includes(whitelistedPopulate, ast.alias);
|
||||
}
|
||||
return ast.autoPopulate;
|
||||
})
|
||||
.reduce((acc, ast) => {
|
||||
const model = ast.plugin
|
||||
? strapi.plugins[ast.plugin].models[ast.collection || ast.model]
|
||||
: strapi.models[ast.collection || ast.model];
|
||||
|
||||
const from = model.collectionName;
|
||||
const isDominantAssociation =
|
||||
(ast.dominant && ast.nature === 'manyToMany') || !!ast.model;
|
||||
|
||||
const _localField =
|
||||
!isDominantAssociation || ast.via === 'related' ? '_id' : ast.alias;
|
||||
|
||||
const localField = `${prefixPath}${_localField}`;
|
||||
|
||||
const foreignField = ast.filter
|
||||
? `${ast.via}.ref`
|
||||
: isDominantAssociation
|
||||
? '_id'
|
||||
: ast.via;
|
||||
|
||||
// Add the juncture like the `.populate()` function
|
||||
const asTempPath = buildTempFieldPath(ast.alias, prefixPath);
|
||||
const asRealPath = restoreRealFieldPath(ast.alias, prefixPath);
|
||||
acc.push({
|
||||
$lookup: {
|
||||
from,
|
||||
localField,
|
||||
foreignField,
|
||||
as: asTempPath,
|
||||
},
|
||||
});
|
||||
|
||||
// Unwind the relation's result if only one is expected
|
||||
if (ast.type === 'model') {
|
||||
acc.push({
|
||||
$unwind: {
|
||||
path: `$${asTempPath}`,
|
||||
preserveNullAndEmptyArrays: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Preserve relation field if it is empty
|
||||
acc.push({
|
||||
$addFields: {
|
||||
[asRealPath]: {
|
||||
$ifNull: [`$${asTempPath}`, null],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Remove temp field
|
||||
acc.push({
|
||||
$project: {
|
||||
[asTempPath]: 0,
|
||||
},
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
|
||||
generateMatchStage: function (strapiModel, filters, { prefixPath = '' } = {}) {
|
||||
if (!filters) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let acc = [];
|
||||
|
||||
// 1st level deep filter
|
||||
if (filters.where) {
|
||||
acc.push(
|
||||
...this.generateMatchStage(
|
||||
strapiModel,
|
||||
{ relations: filters.where },
|
||||
{ prefixPath }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 2nd+ level deep filter
|
||||
_.forEach(filters.relations, (value, key) => {
|
||||
if (key !== 'relations') {
|
||||
const nextPrefixedPath = `${prefixPath}${key}.`;
|
||||
const association = strapiModel.associations.find(a => a.alias === key);
|
||||
|
||||
if (!association) {
|
||||
acc.push({
|
||||
$match: { [`${prefixPath}${key}`]: value },
|
||||
});
|
||||
} else {
|
||||
const model = association.plugin
|
||||
? strapi.plugins[association.plugin].models[
|
||||
association.collection || association.model
|
||||
]
|
||||
: strapi.models[association.collection || association.model];
|
||||
|
||||
// Generate lookup for this relation
|
||||
acc.push(
|
||||
...this.generateLookupStage(strapiModel, {
|
||||
whitelistedPopulate: [key],
|
||||
prefixPath,
|
||||
})
|
||||
);
|
||||
|
||||
// If it's an object re-run the same function with this new value until having either a primitive value or an array.
|
||||
if (_.isPlainObject(value)) {
|
||||
acc.push(
|
||||
...this.generateMatchStage(
|
||||
model,
|
||||
{ relations: value },
|
||||
{
|
||||
prefixPath: nextPrefixedPath,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
acc.push(
|
||||
...this.generateMatchStage(strapiModel, { relations: value }, { prefixPath })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
|
||||
update: async function (params) {
|
||||
const virtualFields = [];
|
||||
const response = await this
|
||||
|
||||
@ -4,9 +4,23 @@
|
||||
* Module dependencies
|
||||
*/
|
||||
|
||||
module.exports = mongoose => {
|
||||
var Decimal = require('mongoose-float').loadType(mongoose, 2);
|
||||
var Float = require('mongoose-float').loadType(mongoose, 20);
|
||||
// Public node modules.
|
||||
const mongoose = require('mongoose');
|
||||
const Mongoose = mongoose.Mongoose;
|
||||
|
||||
/**
|
||||
* Convert MongoDB ID to the stringify version as GraphQL throws an error if not.
|
||||
*
|
||||
* Refer to: https://github.com/graphql/graphql-js/commit/3521e1429eec7eabeee4da65c93306b51308727b#diff-87c5e74dd1f7d923143e0eee611f598eR183
|
||||
*/
|
||||
mongoose.Types.ObjectId.prototype.valueOf = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
module.exports = (mongoose = new Mongoose()) => {
|
||||
|
||||
const Decimal = require('mongoose-float').loadType(mongoose, 2);
|
||||
const Float = require('mongoose-float').loadType(mongoose, 20);
|
||||
|
||||
return {
|
||||
convertType: mongooseType => {
|
||||
@ -42,5 +56,16 @@ module.exports = mongoose => {
|
||||
default:
|
||||
}
|
||||
},
|
||||
valueToId: function (value) {
|
||||
return this.isMongoId(value)
|
||||
? mongoose.Types.ObjectId(value)
|
||||
: value;
|
||||
},
|
||||
isMongoId: function (value) {
|
||||
// Here we don't use mongoose.Types.ObjectId.isValid method because it's a weird check,
|
||||
// it returns for instance true for any integer value ¯\_(ツ)_/¯
|
||||
const hexadecimal = /^[0-9A-F]+$/i;
|
||||
return hexadecimal.test(value) && value.length === 24;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.containerFluid {
|
||||
padding: 18px 30px;
|
||||
> div:first-child {
|
||||
max-height: 33px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,4 +192,4 @@
|
||||
|
||||
.padded {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.containerFluid {
|
||||
padding: 18px 30px;
|
||||
> div:first-child {
|
||||
max-height: 33px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,4 +35,4 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,4 +49,4 @@
|
||||
"npm": ">= 5.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,23 @@ module.exports = {
|
||||
}, {});
|
||||
},
|
||||
|
||||
convertToQuery: function(params) {
|
||||
const result = {};
|
||||
|
||||
_.forEach(params, (value, key) => {
|
||||
if (_.isPlainObject(value)) {
|
||||
const flatObject = this.convertToQuery(value);
|
||||
_.forEach (flatObject, (_value, _key) => {
|
||||
result[`${key}.${_key}`] = _value;
|
||||
});
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Security to avoid infinite limit.
|
||||
*
|
||||
@ -175,13 +192,15 @@ module.exports = {
|
||||
|
||||
// Plural.
|
||||
return async (ctx, next) => {
|
||||
ctx.params = this.amountLimiting(ctx.params);
|
||||
ctx.query = Object.assign(
|
||||
this.convertToParams(_.omit(ctx.params, 'where')),
|
||||
ctx.params.where,
|
||||
const queryOpts = {};
|
||||
queryOpts.params = this.amountLimiting(ctx.params);
|
||||
queryOpts.query = Object.assign(
|
||||
{},
|
||||
this.convertToParams(_.omit(queryOpts.params, 'where')),
|
||||
this.convertToQuery(queryOpts.params.where)
|
||||
);
|
||||
|
||||
return controller(ctx, next);
|
||||
return controller(Object.assign({}, ctx, queryOpts, { send: ctx.send }), next, { populate: [] });
|
||||
};
|
||||
})();
|
||||
|
||||
@ -256,8 +275,12 @@ module.exports = {
|
||||
|
||||
// Resolver can be a function. Be also a native resolver or a controller's action.
|
||||
if (_.isFunction(resolver)) {
|
||||
context.query = this.convertToParams(options);
|
||||
context.params = this.amountLimiting(options);
|
||||
context.query = Object.assign(
|
||||
{},
|
||||
this.convertToParams(_.omit(options, 'where')),
|
||||
this.convertToQuery(options.where)
|
||||
);
|
||||
|
||||
if (isController) {
|
||||
const values = await resolver.call(null, context);
|
||||
|
||||
@ -398,11 +398,11 @@ module.exports = {
|
||||
queryOpts.skip = convertedParams.start;
|
||||
|
||||
switch (association.nature) {
|
||||
case 'manyToMany': {
|
||||
case "manyToMany": {
|
||||
const arrayOfIds = (obj[association.alias] || []).map(
|
||||
related => {
|
||||
return related[ref.primaryKey] || related;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Where.
|
||||
@ -413,7 +413,6 @@ module.exports = {
|
||||
...where.where,
|
||||
}).where;
|
||||
break;
|
||||
// falls through
|
||||
}
|
||||
default:
|
||||
// Where.
|
||||
|
||||
@ -47,4 +47,4 @@
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
.containerFluid {
|
||||
padding: 18px 30px;
|
||||
> div:first-child {
|
||||
max-height: 33px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,4 +37,4 @@
|
||||
justify-content: center;
|
||||
min-height: 260px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +1,16 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
module.exports = {
|
||||
find: async function (params = {}, populate) {
|
||||
const records = await this.query(function(qb) {
|
||||
_.forEach(params.where, (where, key) => {
|
||||
if (_.isArray(where.value)) {
|
||||
for (const value in where.value) {
|
||||
qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
|
||||
}
|
||||
} else {
|
||||
qb.where(key, where.symbol, where.value);
|
||||
}
|
||||
});
|
||||
const hook = strapi.hook[this.orm];
|
||||
const records = await this.query((qb) => {
|
||||
// Generate match stage.
|
||||
hook.load().generateMatchStage(qb)(this, params);
|
||||
|
||||
if (params.start) {
|
||||
qb.offset(params.start);
|
||||
}
|
||||
|
||||
if (params.limit) {
|
||||
qb.limit(params.limit);
|
||||
}
|
||||
|
||||
if (params.sort) {
|
||||
if (_.has(params, 'start')) qb.offset(params.start);
|
||||
if (_.has(params, 'limit')) qb.limit(params.limit);
|
||||
if (!_.isEmpty(params.sort)) {
|
||||
if (params.sort.key) {
|
||||
qb.orderBy(params.sort.key, params.sort.order);
|
||||
} else {
|
||||
@ -33,7 +22,6 @@ module.exports = {
|
||||
withRelated: populate || _.keys(_.groupBy(_.reject(this.associations, { autoPopulate: false }), 'alias'))
|
||||
});
|
||||
|
||||
|
||||
return records ? records.toJSON() : records;
|
||||
},
|
||||
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const { models: { mergeStages } } = require('strapi-utils');
|
||||
|
||||
module.exports = {
|
||||
find: async function (params = {}, populate) {
|
||||
return this
|
||||
.find(params.where)
|
||||
.limit(Number(params.limit))
|
||||
.sort(params.sort)
|
||||
.skip(Number(params.skip))
|
||||
.populate(populate || this.associations.map(x => x.alias).join(' '))
|
||||
.lean();
|
||||
find: async function (filters = {}, populate) {
|
||||
const hook = strapi.hook[this.orm];
|
||||
// Generate stages.
|
||||
const populateStage = hook.load().generateLookupStage(this, { whitelistedPopulate: populate }); // Nested-Population
|
||||
const matchStage = hook.load().generateMatchStage(this, filters); // Nested relation filter
|
||||
const aggregateStages = mergeStages(populateStage, matchStage);
|
||||
|
||||
const result = this.aggregate(aggregateStages);
|
||||
|
||||
if (_.has(filters, 'start')) result.skip(filters.start);
|
||||
if (_.has(filters, 'limit')) result.limit(filters.limit);
|
||||
if (_.has(filters, 'sort')) result.sort(filters.sort);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
count: async function (params = {}) {
|
||||
|
||||
@ -23,12 +23,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"grant-koa": "^3.8.1",
|
||||
"grant-koa": "^4.2.0",
|
||||
"jsonwebtoken": "^8.1.0",
|
||||
"koa": "^2.1.0",
|
||||
"koa2-ratelimit": "^0.6.1",
|
||||
"purest": "^2.0.1",
|
||||
"request": "^2.83.0",
|
||||
"strapi-utils": "3.0.0-alpha.14.5",
|
||||
"uuid": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -55,4 +56,4 @@
|
||||
"npm": ">= 5.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,4 +39,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -42,4 +42,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -42,4 +42,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -41,4 +41,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -43,4 +43,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -43,4 +43,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -39,4 +39,4 @@
|
||||
"npm": ">= 5.3.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
@ -13,4 +13,4 @@
|
||||
"pkgcloud": "^1.5.0",
|
||||
"streamifier": "^0.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,17 +9,15 @@ const path = require('path');
|
||||
|
||||
// Public node modules.
|
||||
const _ = require('lodash');
|
||||
const pluralize = require('pluralize');
|
||||
|
||||
// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
|
||||
const isNumeric = (value) => {
|
||||
return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
|
||||
};
|
||||
// Constants
|
||||
const ORDERS = ['ASC', 'DESC'];
|
||||
|
||||
/* eslint-disable prefer-template */
|
||||
/*
|
||||
* Set of utils for models
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
@ -37,7 +35,6 @@ module.exports = {
|
||||
getPK: function (collectionIdentity, collection, models) {
|
||||
if (_.isString(collectionIdentity)) {
|
||||
const ORM = this.getORM(collectionIdentity);
|
||||
|
||||
try {
|
||||
const GraphQLFunctions = require(path.resolve(strapi.config.appPath, 'node_modules', 'strapi-' + ORM, 'lib', 'utils'));
|
||||
|
||||
@ -311,6 +308,16 @@ module.exports = {
|
||||
return _.get(strapi.models, collectionIdentity.toLowerCase() + '.orm');
|
||||
},
|
||||
|
||||
/**
|
||||
* Return table name for a collection many-to-many
|
||||
*/
|
||||
getCollectionName: (associationA, associationB) => {
|
||||
return [associationA, associationB]
|
||||
.sort((a, b) => a.collection < b.collection ? -1 : 1)
|
||||
.map(table => _.snakeCase(`${pluralize.plural(table.collection)} ${pluralize.plural(table.via)}`))
|
||||
.join('__');
|
||||
},
|
||||
|
||||
/**
|
||||
* Define associations key to models
|
||||
*/
|
||||
@ -340,7 +347,7 @@ module.exports = {
|
||||
|
||||
// Build associations object
|
||||
if (association.hasOwnProperty('collection') && association.collection !== '*') {
|
||||
definition.associations.push({
|
||||
const ast = {
|
||||
alias: key,
|
||||
type: 'collection',
|
||||
collection: association.collection,
|
||||
@ -350,7 +357,13 @@ module.exports = {
|
||||
dominant: details.dominant !== true,
|
||||
plugin: association.plugin || undefined,
|
||||
filter: details.filter,
|
||||
});
|
||||
};
|
||||
|
||||
if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
|
||||
ast.tableCollectionName = this.getCollectionName(association, details);
|
||||
}
|
||||
|
||||
definition.associations.push(ast);
|
||||
} else if (association.hasOwnProperty('model') && association.model !== '*') {
|
||||
definition.associations.push({
|
||||
alias: key,
|
||||
@ -418,9 +431,32 @@ module.exports = {
|
||||
return _.findKey(strapi.models[association.model || association.collection].attributes, {via: attribute});
|
||||
},
|
||||
|
||||
convertParams: (entity, params) => {
|
||||
mergeStages: (...stages) => {
|
||||
return _.unionWith(...stages, _.isEqual);
|
||||
},
|
||||
|
||||
convertParams: function (entity, params) {
|
||||
const { model, models, convertor, postProcessValue } = this.prepareStage(
|
||||
entity,
|
||||
params
|
||||
);
|
||||
|
||||
const _filter = this.splitPrimitiveAndRelationValues(params);
|
||||
|
||||
// Execute Steps in the given order
|
||||
return _.flow([
|
||||
this.processValues({ model, models, convertor, postProcessValue }),
|
||||
this.processPredicates({ model, models, convertor }),
|
||||
this.processGeneratedResults(),
|
||||
this.mergeWhereAndRelationPayloads()
|
||||
])(_filter);
|
||||
},
|
||||
|
||||
prepareStage: function (entity, params) {
|
||||
if (!entity) {
|
||||
throw new Error('You can\'t call the convert params method without passing the model\'s name as a first argument.');
|
||||
throw new Error(
|
||||
'You can\'t call the convert params method without passing the model\'s name as a first argument.'
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the source params (that can be sent from the ctm plugin) since it is not a filter
|
||||
@ -428,86 +464,229 @@ module.exports = {
|
||||
delete params.source;
|
||||
}
|
||||
|
||||
const model = entity.toLowerCase();
|
||||
const modelName = entity.toLowerCase();
|
||||
const models = this.getStrapiModels();
|
||||
const model = models[modelName];
|
||||
|
||||
const models = _.assign(_.clone(strapi.models), Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||
_.assign(acc, _.get(strapi.plugins[current], ['models'], {}));
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
if (!models.hasOwnProperty(model)) {
|
||||
return this.log.error(`The model ${model} can't be found.`);
|
||||
if (!model) {
|
||||
throw new Error(`The model ${modelName} can't be found.`);
|
||||
}
|
||||
|
||||
const client = models[model].client;
|
||||
const connector = models[model].orm;
|
||||
|
||||
if (!connector) {
|
||||
throw new Error(`Impossible to determine the ORM used for the model ${model}.`);
|
||||
if (!model.orm) {
|
||||
throw new Error(
|
||||
`Impossible to determine the ORM used for the model ${modelName}.`
|
||||
);
|
||||
}
|
||||
|
||||
const convertor = strapi.hook[connector].load().getQueryParams;
|
||||
const convertParams = {
|
||||
where: {},
|
||||
sort: '',
|
||||
start: 0,
|
||||
limit: 100
|
||||
const hook = strapi.hook[model.orm];
|
||||
const convertor = hook.load().getQueryParams;
|
||||
const postProcessValue = hook.load().postProcessValue || _.identity;
|
||||
|
||||
return {
|
||||
models,
|
||||
model,
|
||||
hook,
|
||||
convertor,
|
||||
postProcessValue,
|
||||
};
|
||||
},
|
||||
|
||||
_.forEach(params, (value, key) => {
|
||||
let result;
|
||||
let formattedValue;
|
||||
let modelAttributes = models[model]['attributes'];
|
||||
let fieldType;
|
||||
// Get the field type to later check if it's a string before number conversion
|
||||
if (modelAttributes[key]) {
|
||||
fieldType = modelAttributes[key]['type'];
|
||||
} else {
|
||||
// Remove the filter keyword at the end
|
||||
let splitKey = key.split('_').slice(0,-1);
|
||||
splitKey = splitKey.join('_');
|
||||
getStrapiModels: function() {
|
||||
return {
|
||||
...strapi.models,
|
||||
...Object.keys(strapi.plugins).reduce(
|
||||
(acc, pluginName) => ({
|
||||
...acc,
|
||||
..._.get(strapi.plugins[pluginName], 'models', {}),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
if (modelAttributes[splitKey]) {
|
||||
fieldType = modelAttributes[splitKey]['type'];
|
||||
}
|
||||
}
|
||||
// Check if the value is a valid candidate to be converted to a number value
|
||||
if (fieldType !== 'string') {
|
||||
formattedValue = isNumeric(value)
|
||||
? _.toNumber(value)
|
||||
: value;
|
||||
} else {
|
||||
formattedValue = value;
|
||||
}
|
||||
|
||||
if (_.includes(['_start', '_limit'], key)) {
|
||||
result = convertor(formattedValue, key);
|
||||
} else if (key === '_sort') {
|
||||
const [attr, order = 'ASC'] = formattedValue.split(':');
|
||||
result = convertor(order, key, attr);
|
||||
} else {
|
||||
const suffix = key.split('_');
|
||||
|
||||
// Mysql stores boolean as 1 or 0
|
||||
if (client === 'mysql' && _.get(models, [model, 'attributes', suffix, 'type']) === 'boolean') {
|
||||
formattedValue = value === 'true' ? '1' : '0';
|
||||
}
|
||||
|
||||
let type;
|
||||
|
||||
if (_.includes(['ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'containss', 'in'], _.last(suffix))) {
|
||||
type = `_${_.last(suffix)}`;
|
||||
key = _.dropRight(suffix).join('_');
|
||||
splitPrimitiveAndRelationValues: function(_query) {
|
||||
const result = _.reduce(
|
||||
_query,
|
||||
(acc, value, key) => {
|
||||
if (_.startsWith(key, '_')) {
|
||||
acc[key] = value;
|
||||
} else if (!_.includes(key, '.')) {
|
||||
acc.where[key] = value;
|
||||
} else {
|
||||
type = '=';
|
||||
_.set(acc.relations, this.injectRelationInKey(key), value);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
where: {},
|
||||
relations: {},
|
||||
sort: '',
|
||||
start: 0,
|
||||
limit: 100,
|
||||
}
|
||||
);
|
||||
return result;
|
||||
},
|
||||
|
||||
result = convertor(formattedValue, type, key);
|
||||
injectRelationInKey: function (key) {
|
||||
const numberOfRelations = key.match(/\./gi).length - 1;
|
||||
const relationStrings = _.times(numberOfRelations, _.constant('relations'));
|
||||
return _.chain(key)
|
||||
.split('.')
|
||||
.zip(relationStrings)
|
||||
.flatten()
|
||||
.compact()
|
||||
.join('.')
|
||||
.value();
|
||||
},
|
||||
|
||||
transformFilter: function (filter, iteratee) {
|
||||
if (!_.isArray(filter) && !_.isPlainObject(filter)) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
return _.transform(filter, (updatedFilter, value, key) => {
|
||||
const updatedValue = iteratee(value, key);
|
||||
updatedFilter[key] = this.transformFilter(updatedValue, iteratee);
|
||||
return updatedFilter;
|
||||
});
|
||||
},
|
||||
|
||||
processValues: function ({ model, models, convertor, postProcessValue }) {
|
||||
return filter => {
|
||||
let parentModel = model;
|
||||
return this.transformFilter(filter, (value, key) => {
|
||||
const field = this.getFieldFromKey(key, parentModel);
|
||||
if (!field) {
|
||||
return this.processMeta(value, key, {
|
||||
field,
|
||||
client: model.client,
|
||||
model,
|
||||
convertor,
|
||||
});
|
||||
}
|
||||
if (field.collection || field.model) {
|
||||
parentModel = models[field.collection || field.model];
|
||||
}
|
||||
return postProcessValue(
|
||||
this.processValue(value, key, { field, client: model.client, model })
|
||||
);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
getFieldFromKey: function (key, model) {
|
||||
let field;
|
||||
// Primary key is a unique case because it doesn't belong to the model's attributes
|
||||
if (key === model.primaryKey) {
|
||||
field = {
|
||||
type: 'ID', // Just in case
|
||||
};
|
||||
} else if (model.attributes[key]) {
|
||||
field = model.attributes[key];
|
||||
} else {
|
||||
// Remove the filter keyword at the end
|
||||
let splitKey = key.split('_').slice(0, -1);
|
||||
splitKey = splitKey.join('_');
|
||||
|
||||
if (model.attributes[splitKey]) {
|
||||
field = model.attributes[splitKey];
|
||||
}
|
||||
}
|
||||
|
||||
return field;
|
||||
},
|
||||
|
||||
processValue: function (value, key, { field, client }) {
|
||||
if (field.type === 'boolean' && client === 'mysql') {
|
||||
return value === 'true' ? '1' : '0';
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
processMeta: function (value, key, { convertor, model }) {
|
||||
if (_.includes(['_start', '_limit'], key)) {
|
||||
return convertor(value, key);
|
||||
} else if (key === '_sort') {
|
||||
return this.processSortMeta(value, key, { convertor, model });
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
processSortMeta: function (value, key, { convertor, model }) {
|
||||
const [attr, order = 'ASC'] = value.split(':');
|
||||
if (!_.includes(ORDERS, order)) {
|
||||
throw new Error(
|
||||
`Unkown order value: "${order}", available values are: ${ORDERS.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
const field = this.getFieldFromKey(attr, model);
|
||||
if (!field) {
|
||||
throw new Error(`Unkown field: "${attr}"`);
|
||||
}
|
||||
|
||||
return convertor(order, key, attr);
|
||||
},
|
||||
|
||||
processPredicates: function ({ model, models, convertor }) {
|
||||
return filter => {
|
||||
let parentModel = model;
|
||||
return this.transformFilter(filter, (value, key) => {
|
||||
const field = this.getFieldFromKey(key, parentModel);
|
||||
if (!field) {
|
||||
return value;
|
||||
}
|
||||
if (field.collection || field.model) {
|
||||
parentModel = models[field.collection || field.model];
|
||||
}
|
||||
return this.processCriteriaMeta(value, key, { convertor });
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
processCriteriaMeta: function (value, key, { convertor }) {
|
||||
let type = '=';
|
||||
if (key.match(/_{1}(?:ne|lte?|gte?|containss?|in)/)) {
|
||||
type = key.match(/_{1}(?:ne|lte?|gte?|containss?|in)/)[0];
|
||||
key = key.replace(type, '');
|
||||
}
|
||||
return convertor(value, type, key);
|
||||
},
|
||||
|
||||
processGeneratedResults: function() {
|
||||
return filter => {
|
||||
if (!_.isArray(filter) && !_.isPlainObject(filter)) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
_.set(convertParams, result.key, result.value);
|
||||
});
|
||||
return _.transform(filter, (updatedFilter, value, key) => {
|
||||
// Only set results for object of shape { value, key }
|
||||
if (_.has(value, 'value') && _.has(value, 'key')) {
|
||||
const cleanKey = _.replace(value.key, 'where.', '');
|
||||
_.set(updatedFilter, cleanKey, this.processGeneratedResults()(value.value));
|
||||
} else {
|
||||
updatedFilter[key] = this.processGeneratedResults()(value);
|
||||
}
|
||||
|
||||
return convertParams;
|
||||
return updatedFilter;
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
mergeWhereAndRelationPayloads: function() {
|
||||
return filter => {
|
||||
return {
|
||||
...filter, // Normally here we need to omit where key
|
||||
relations: {
|
||||
...filter.where,
|
||||
relations: filter.relations
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"knex": "^0.13.0",
|
||||
"lodash": "^4.17.5",
|
||||
"pino": "^4.7.1",
|
||||
"pluralize": "^7.0.0",
|
||||
"shelljs": "^0.7.7"
|
||||
},
|
||||
"author": {
|
||||
|
||||
@ -34,7 +34,6 @@ const watcher = (label, cmd, withSuccess = true) => {
|
||||
shell.echo('✅ Success');
|
||||
shell.echo('');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const asyncWatcher = (label, cmd, withSuccess = true, resolve) => {
|
||||
@ -88,7 +87,6 @@ if (shell.test('-e', 'admin/src/config/plugins.json') === false) {
|
||||
shell.cd('../../../');
|
||||
}
|
||||
|
||||
|
||||
watcher('📦 Linking strapi-admin', 'npm link --no-optional', false);
|
||||
|
||||
shell.cd('../strapi-generate-admin');
|
||||
@ -112,18 +110,33 @@ watcher('', 'npm install ../strapi-hook-knex');
|
||||
watcher('📦 Linking strapi-hook-bookshelf...', 'npm link');
|
||||
|
||||
shell.cd('../strapi');
|
||||
watcher('', 'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils');
|
||||
watcher(
|
||||
'',
|
||||
'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils'
|
||||
);
|
||||
watcher('📦 Linking strapi...', 'npm link');
|
||||
|
||||
shell.cd('../strapi-plugin-graphql');
|
||||
watcher('📦 Linking strapi-plugin-graphql...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-plugin-graphql...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
// Plugin services
|
||||
shell.cd('../strapi-provider-upload-local');
|
||||
watcher('📦 Linking strapi-provider-upload-local...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-provider-upload-local...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
shell.cd('../strapi-provider-email-sendmail');
|
||||
watcher('📦 Linking strapi-provider-email-sendmail...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-provider-email-sendmail...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
// Plugins with admin
|
||||
shell.cd('../strapi-plugin-email');
|
||||
@ -134,19 +147,31 @@ watcher('📦 Linking strapi-plugin-email...', 'npm link --no-optional', false)
|
||||
|
||||
shell.cd('../strapi-plugin-users-permissions');
|
||||
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
|
||||
watcher('', 'npm install ../strapi-utils --no-optional');
|
||||
shell.rm('-f', 'package-lock.json');
|
||||
watcher('📦 Linking strapi-plugin-users-permissions...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-plugin-users-permissions...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
shell.cd('../strapi-plugin-content-manager');
|
||||
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
|
||||
shell.rm('-f', 'package-lock.json');
|
||||
watcher('📦 Linking strapi-plugin-content-manager...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-plugin-content-manager...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
shell.cd('../strapi-plugin-settings-manager');
|
||||
watcher('', 'npm install ../strapi-helper-plugin --no-optional');
|
||||
shell.rm('-f', 'package-lock.json');
|
||||
watcher('📦 Linking strapi-plugin-settings-manager...', 'npm link --no-optional', false);
|
||||
|
||||
watcher(
|
||||
'📦 Linking strapi-plugin-settings-manager...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
// Plugins with admin and other plugin's dependencies
|
||||
shell.cd('../strapi-plugin-upload');
|
||||
@ -160,16 +185,32 @@ watcher('', 'npm install ../strapi-helper-plugin --no-optional');
|
||||
watcher('', 'npm install ../strapi-generate --no-optional');
|
||||
watcher('', 'npm install ../strapi-generate-api --no-optional');
|
||||
shell.rm('-f', 'package-lock.json');
|
||||
watcher('📦 Linking strapi-plugin-content-type-builder...', 'npm link --no-optional', false);
|
||||
watcher(
|
||||
'📦 Linking strapi-plugin-content-type-builder...',
|
||||
'npm link --no-optional',
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
const pluginsToBuild = ['admin', 'content-manager', 'content-type-builder', 'upload', 'email', 'users-permissions', 'settings-manager'];
|
||||
const pluginsToBuild = [
|
||||
'admin',
|
||||
'content-manager',
|
||||
'content-type-builder',
|
||||
'upload',
|
||||
'email',
|
||||
'users-permissions',
|
||||
'settings-manager'
|
||||
];
|
||||
|
||||
const buildPlugins = async () => {
|
||||
const build = (pckgName) => {
|
||||
const build = pckgName => {
|
||||
return new Promise(resolve => {
|
||||
const name = pckgName === 'admin' ? pckgName: `plugin-${pckgName}`;
|
||||
asyncWatcher(`🏗 Building ${name}...`, `cd ../strapi-${name} && IS_MONOREPO=true npm run build`, false, resolve);
|
||||
const name = pckgName === 'admin' ? pckgName : `plugin-${pckgName}`;
|
||||
asyncWatcher(
|
||||
`🏗 Building ${name}...`,
|
||||
`cd ../strapi-${name} && IS_MONOREPO=true npm run build`,
|
||||
false,
|
||||
resolve
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -178,23 +219,34 @@ const buildPlugins = async () => {
|
||||
|
||||
const setup = async () => {
|
||||
if (process.env.npm_config_build) {
|
||||
if (process.platform === 'darwin') { // Allow async build for darwin platform
|
||||
if (process.platform === 'darwin') {
|
||||
// Allow async build for darwin platform
|
||||
await buildPlugins();
|
||||
} else {
|
||||
pluginsToBuild.map(name => {
|
||||
const pluginName = name === 'admin' ? name : `plugin-${name}`;
|
||||
shell.cd(`../strapi-${pluginName}`);
|
||||
|
||||
return watcher(`🏗 Building ${pluginName}...`, 'IS_MONOREPO=true npm run build');
|
||||
return watcher(
|
||||
`🏗 Building ${pluginName}...`,
|
||||
'IS_MONOREPO=true npm run build'
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Log installation duration.
|
||||
const installationEndDate = new Date();
|
||||
const duration = (installationEndDate.getTime() - installationStartDate.getTime()) / 1000;
|
||||
const duration =
|
||||
(installationEndDate.getTime() - installationStartDate.getTime()) / 1000;
|
||||
shell.echo('✅ Strapi has been succesfully installed.');
|
||||
shell.echo(`⏳ The installation took ${Math.floor(duration / 60) > 0 ? `${Math.floor(duration / 60)} minutes and ` : ''}${Math.floor(duration % 60)} seconds.`);
|
||||
shell.echo(
|
||||
`⏳ The installation took ${
|
||||
Math.floor(duration / 60) > 0
|
||||
? `${Math.floor(duration / 60)} minutes and `
|
||||
: ''
|
||||
}${Math.floor(duration % 60)} seconds.`
|
||||
);
|
||||
};
|
||||
|
||||
setup();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user