Merge branch 'master' into patch-1

This commit is contained in:
Jim LAURIE 2019-01-23 11:07:11 +01:00 committed by GitHub
commit 3549bc03f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 574 additions and 140 deletions

View File

@ -63,10 +63,10 @@ Do the same thing for other environments.
#### HTTP call
Now it is time to make the HTTP call. In this example we will use `request` as it is already in the list of Strapi's dependencies. Let's install it:
Now it is time to make the HTTP call. In this example we will use `axios`. Let's install it:
```
npm i request --save
npm i axios --save
```
Edit `api/yourContentType/models/YourContentType.js`:
@ -76,7 +76,7 @@ Edit `api/yourContentType/models/YourContentType.js`:
```js
'use strict';
const request = require('request');
const axios = require('axios');
/**
* Lifecycle callbacks for the `Post` model.
@ -119,5 +119,5 @@ So, to trigger an url on delete, please add `request.post(strapi.config.currentE
- `delete` action of `plugins/content-manager/services/ContentManager.js` (triggered by the Content Manager).
::: note
Do not forget to require `request` at the top of these files.
Do not forget to require `axios` at the top of these files.
:::

View File

@ -25,3 +25,4 @@
- [Migration guide from alpha.16 to alpha.17](migration-guide-alpha.16-to-alpha.17.md)
- [Migration guide from alpha.17 to alpha.18](migration-guide-alpha.17-to-alpha.18.md)
- [Migration guide from alpha.18 to alpha.19](migration-guide-alpha.18-to-alpha.19.md)
- [Migration guide from alpha.19 to alpha.20](migration-guide-alpha.19-to-alpha.20.md)

View File

@ -0,0 +1,67 @@
# Migration guide from alpha.19 to alpha.20
**Here are the major changes:**
- Fix email issue on user update
- Improve GraphQL performances
**Useful links:**
- Changelog: [https://github.com/strapi/strapi/releases/tag/v3.0.0-alpha.20](https://github.com/strapi/strapi/releases/tag/v3.0.0-alpha.20)
- GitHub diff: [https://github.com/strapi/strapi/compare/v3.0.0-alpha.19...v3.0.0-alpha.20](https://github.com/strapi/strapi/compare/v3.0.0-alpha.19...v3.0.0-alpha.20)
<br>
::: note
Feel free to [join us on Slack](http://slack.strapi.io) and ask questions about the migration process.
:::
<br>
## Getting started
Install Strapi `alpha.20` globally on your computer. To do so run `npm install strapi@3.0.0-alpha.20 -g`.
When it's done, generate a new empty project `strapi new myNewProject` (don't pay attention to the database configuration).
<br>
## Update node modules
Update the Strapi's dependencies version (move Strapi's dependencies to `3.0.0-alpha.20` version) of your project.
Run `npm install strapi@3.0.0-alpha.20 --save` to update your strapi version.
<br>
## Update the Admin
::: note
If you performed updates in the Admin, you will have to manually migrate your changes.
:::
Delete your old admin folder and replace it with the new one.
<br>
## Update the Plugins
::: note
If you did a custom update on one of the plugins, you will have to manually migrate your update.
:::
Copy the fields and relations you had in your `/plugins/users-permissions/models/User.settings.json` and `/plugins/users-permissions/config/jwt.json` file in the new one.
Then, delete your old `plugins` folder and replace it with the new one.
## Update services
For both bookshelf and mongoose, you will have to update all services of your generated API.
You will have to update one line of the `fetchAll` function.
**Mongoose** replace `.populate(populate);` by `.populate(filters.populate || populate);`.
**Bookshelf**: replace `withRelated: populate` by `withRelated: filters.populate || populate`.
<br>
That's all, you have now upgraded to Strapi `alpha.20`.

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"dependencies": {},
"devDependencies": {
"assert": "~1.3.0",

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "strapi-admin",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Strapi Admin",
"repository": {
"type": "git",
@ -31,8 +31,8 @@
},
"devDependencies": {
"sanitize.css": "^4.1.0",
"strapi-helper-plugin": "3.0.0-alpha.19",
"strapi-utils": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20",
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-admin",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate the default admin panel for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,8 +15,8 @@
"dependencies": {
"fs-extra": "^4.0.1",
"lodash": "^4.17.5",
"strapi-admin": "3.0.0-alpha.19",
"strapi-utils": "3.0.0-alpha.19"
"strapi-admin": "3.0.0-alpha.20",
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-api",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate an API for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,3 +1,4 @@
/* global <%= globalID %> */
'use strict';
/**
@ -46,7 +47,7 @@ module.exports = {
qb.offset(filters.start);
qb.limit(filters.limit);
}).fetchAll({
withRelated: populate
withRelated: filters.populate || populate
});
},

View File

@ -32,7 +32,7 @@ module.exports = {
.sort(filters.sort)
.skip(filters.start)
.limit(filters.limit)
.populate(populate);
.populate(filters.populate || populate);
},
/**

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-controller",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate a controller for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-model",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate a model for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -6,7 +6,7 @@
// Public node modules.
const _ = require('lodash');
const uuid = require('uuid/v4');
const { packageManager } = require('strapi-utils');
/**
@ -72,7 +72,7 @@ module.exports = scope => {
}],
'strapi': {
'packageManager': pkgManager,
'uuid': uuid()
'uuid': scope.uuid
},
'engines': {
"node": ">= 10.0.0",

View File

@ -5,8 +5,8 @@
*/
// Node.js core.
const path = require('path');
const { exec, execSync } = require('child_process');
const path = require('path');
// Public node modules.
const _ = require('lodash');
@ -15,6 +15,7 @@ const fs = require('fs-extra');
const npm = require('enpeem');
const ora = require('ora');
const shell = require('shelljs');
const request = require('request');
// Logger.
const { packageManager } = require('strapi-utils');
@ -32,6 +33,8 @@ module.exports = (scope, cb) => {
console.log(`The app has been connected to the database ${green('successfully')}!`);
console.log();
trackSuccess('didConnectDatabase', scope);
console.log('🏗 Application generation:');
let loader = ora('Copy dashboard').start();
@ -193,7 +196,19 @@ module.exports = (scope, cb) => {
console.log('⚡️ Start application:');
console.log(`$ ${green('strapi start')}`);
trackSuccess('didCreateProject', scope);
cb();
});
}
};
function trackSuccess(event, scope) {
request
.post('https://analytics.strapi.io/track')
.form({
event,
uuid: scope.uuid
})
.on('error', () => {});
}

View File

@ -17,6 +17,7 @@ const {cyan} = require('chalk');
const fs = require('fs-extra');
const inquirer = require('inquirer');
const shell = require('shelljs');
const uuid = require('uuid/v4');
// Logger.
const { packageManager } = require('strapi-utils');
@ -46,6 +47,7 @@ module.exports = (scope, cb) => {
// Make changes to the rootPath where the Strapi project will be created.
scope.rootPath = path.resolve(process.cwd(), scope.name || '');
scope.tmpPath = path.resolve(os.tmpdir(), `strapi${ crypto.randomBytes(6).toString('hex') }`);
scope.uuid = uuid();
// Ensure we aren't going to inadvertently delete any files.
try {

View File

@ -66,7 +66,10 @@ module.exports = {
'public/uploads': {
folder: {}
},
// Copy gitkeep into uploads directory.
'public/uploads/.gitkeep': {
copy: 'gitkeep'
},
// Empty node_modules directory.
'node_modules': {
folder: {}

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-new",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate a new Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -19,7 +19,8 @@
"listr": "^0.14.1",
"lodash": "^4.17.5",
"ora": "^2.1.0",
"strapi-utils": "3.0.0-alpha.19",
"request": "^2.88.0",
"strapi-utils": "3.0.0-alpha.20",
"uuid": "^3.1.0"
},
"scripts": {

View File

@ -80,7 +80,8 @@ $RECYCLE.BIN/
ssl
.idea
nbproject
public/uploads/*
!public/uploads/.gitkeep
############################
# Node.js

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-plugin",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate an plugin for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-policy",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate a policy for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-service",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Generate a service for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Master of ceremonies for the Strapi generators.",
"homepage": "http://strapi.io",
"keywords": [
@ -17,7 +17,7 @@
"fs-extra": "^4.0.0",
"lodash": "^4.17.5",
"reportback": "^2.0.1",
"strapi-utils": "3.0.0-alpha.19"
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-helper-plugin",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Helper for Strapi plugins development",
"engines": {
"node": ">= 10.0.0",

View File

@ -1071,6 +1071,10 @@ module.exports = function(strapi) {
result.key = 'limit';
result.value = parseFloat(value);
break;
case '_populate':
result.key = 'populate';
result.value = value;
break;
case '_contains':
case '_containss':
result.key = `where.${key}`;

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-bookshelf",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Bookshelf hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -21,8 +21,8 @@
"lodash": "^4.17.5",
"pluralize": "^6.0.0",
"rimraf": "^2.6.2",
"strapi-hook-knex": "3.0.0-alpha.19",
"strapi-utils": "3.0.0-alpha.19"
"strapi-hook-knex": "3.0.0-alpha.20",
"strapi-utils": "3.0.0-alpha.20"
},
"strapi": {
"dependencies": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-ejs",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "EJS hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-knex",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Knex hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -522,6 +522,10 @@ module.exports = function (strapi) {
result.key = 'limit';
result.value = parseFloat(value);
break;
case '_populate':
result.key = `populate`;
result.value = value;
break;
case '_contains':
result.key = `where.${key}`;
result.value = {

View File

@ -17,7 +17,7 @@ module.exports = (mongoose = new Mongoose()) => {
return this.toString();
};
return {
const fn = {
convertType: mongooseType => {
switch (mongooseType.toLowerCase()) {
case 'array':
@ -52,4 +52,6 @@ module.exports = (mongoose = new Mongoose()) => {
}
}
};
return fn;
};

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-mongoose",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Mongoose hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -20,7 +20,7 @@
"mongoose-float": "^1.0.3",
"pluralize": "^6.0.0",
"rimraf": "^2.6.2",
"strapi-utils": "3.0.0-alpha.19"
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-hook-redis",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Redis hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -19,7 +19,7 @@
"lodash": "^4.17.5",
"rimraf": "^2.6.2",
"stack-trace": "0.0.10",
"strapi-utils": "3.0.0-alpha.19"
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-lint",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Strapi eslint and prettier configurations",
"directories": {
"lib": "lib"

View File

@ -1,6 +1,6 @@
{
"name": "strapi-middleware-views",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Views middleware to enable server-side rendering for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -143,8 +143,8 @@ module.exports = {
delete: async function (params) {
// Delete entry.
return this
.remove({
[this.primaryKey]: params.id
.findOneAndDelete({
[this.primaryKey]: params.id,
});
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-manager",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "A powerful UI to easily manage your data.",
"strapi": {
"name": "Content Manager",
@ -26,7 +26,7 @@
"draft-js": "^0.10.5",
"react-select": "^1.2.1",
"showdown": "^1.8.6",
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"dependencies": {
"pluralize": "^7.0.0"

View File

@ -161,6 +161,10 @@ module.exports = {
id: params.id
});
if (!response) {
throw `This resource doesn't exist.`;
}
params[primaryKey] = response[primaryKey];
params.values = Object.keys(JSON.parse(JSON.stringify(response))).reduce((acc, current) => {
const association = (strapi.models[params.model] || strapi.plugins[source].models[params.model]).associations.filter(x => x.alias === current)[0];

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-type-builder",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Strapi plugin to create content type (API).",
"strapi": {
"name": "Content Type Builder",
@ -24,11 +24,11 @@
"dependencies": {
"immutable": "^3.8.2",
"pluralize": "^7.0.0",
"strapi-generate": "3.0.0-alpha.19",
"strapi-generate-api": "3.0.0-alpha.19"
"strapi-generate": "3.0.0-alpha.20",
"strapi-generate-api": "3.0.0-alpha.20"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi team",

View File

@ -194,18 +194,35 @@ export class HomePage extends React.Component {
}
HomePage.defaultProps = {
currentDocVersion: '',
didCheckErrors: false,
docVersions: [],
form: [],
formErrors: [],
isLoading: true,
onChange: () => {},
onClickDeleteDoc: () => {},
onConfirmDeleteDoc: () => {},
onSubmit: () => {},
onUpdateDoc: () => {},
versionToDelete: '',
};
HomePage.propTypes = {
currentDocVersion: PropTypes.string,
didCheckErrors: PropTypes.bool,
docVersions: PropTypes.array,
form: PropTypes.array,
formErrors: PropTypes.array,
getDocInfos: PropTypes.func.isRequired,
isLoading: PropTypes.bool,
onChange: PropTypes.func,
onClickDeleteDoc: PropTypes.func,
onConfirmDeleteDoc: PropTypes.func,
onSubmit: PropTypes.func,
onUpdateDoc: PropTypes.func,
versionToDelete: PropTypes.string,
};
function mapDispatchToProps(dispatch) {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-documentation",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Documentation",
@ -29,7 +29,7 @@
"swagger-ui-dist": "^3.18.3-republish2"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "soupette",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-email",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Email",
@ -22,11 +22,11 @@
"prepublishOnly": "IS_MONOREPO=true npm run build"
},
"dependencies": {
"strapi-provider-email-sendmail": "3.0.0-alpha.19"
"strapi-provider-email-sendmail": "3.0.0-alpha.20"
},
"devDependencies": {
"react-copy-to-clipboard": "5.0.1",
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-graphql",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "graphql",
@ -22,15 +22,16 @@
},
"dependencies": {
"apollo-server-koa": "^2.0.7",
"dataloader": "^1.4.0",
"glob": "^7.1.3",
"graphql": "^14.0.2",
"graphql-depth-limit": "^1.1.0",
"graphql-playground-middleware-koa": "^1.6.4",
"graphql-tools": "^3.1.1",
"graphql-type-json": "^0.2.1",
"graphql-type-datetime": "^0.2.2",
"graphql-type-json": "^0.2.1",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-alpha.19"
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"name": "A Strapi developer",

View File

@ -0,0 +1,217 @@
'use strict';
/**
* Loaders.js service
*
* @description: A set of functions similar to controller's actions to avoid code duplication.
*/
const _ = require('lodash');
const DataLoader = require('dataloader');
module.exports = {
loaders: {},
initializeLoader: function() {
this.resetLoaders();
// Create loaders for each relational field (exclude core models).
Object.keys(strapi.models)
.filter(model => model !== 'core_store')
.forEach(model => {
(strapi.models[model].associations || []).forEach(association => this.createLoader(association.collection || association.model, association.plugin));
});
// Reproduce the same pattern for each plugin.
Object.keys(strapi.plugins).forEach(plugin => {
Object.keys(strapi.plugins[plugin].models).forEach(model => {
(strapi.plugins[plugin].models[model].associations || []).forEach(association => this.createLoader(association.collection || association.model, association.plugin));
});
});
},
resetLoaders: function () {
this.loaders = {};
},
createLoader: function(model, plugin) {
const name = plugin ? `${plugin}__${model}`: model;
// Exclude polymorphic from loaders.
if (name === undefined) {
return;
}
if (this.loaders[name]) {
return this.loaders[name];
}
this.loaders[name] = new DataLoader(keys => {
return new Promise(async (resolve, reject) => {
try {
// Extract queries from keys and merge similar queries.
const { queries, map } = this.extractQueries(model, _.cloneDeep(keys));
// Run queries in parallel.
const results = await Promise.all(queries.map((query) => this.makeQuery(model, query)));
// Use to match initial queries order.
const data = this.mapData(model, keys, map, results);
resolve(data);
} catch (e) {
reject(e);
}
});
}, {
cacheKeyFn: (key) => {
return _.isObjectLike(key) ? JSON.stringify(_.cloneDeep(key)) : key;
}
});
},
mapData: function(model, originalMap, map, results) {
// Use map to re-dispatch data correctly based on initial keys.
return originalMap.map((query, index) => {
// Find the index of where we should extract the results.
const indexResults = map.findIndex(queryMap => queryMap.indexOf(index) !== -1);
const data = results[indexResults];
// Retrieving referring model.
const ref = this.retrieveModel(model, query.options.source);
if (query.single) {
// Return object instead of array for one-to-many relationship.
return data.find(entry => entry[ref.primaryKey].toString() === (query.params[ref.primaryKey] || '').toString());
}
// Generate constant for skip parameters.
// Note: we shouldn't support both way of doing this kind of things in the future.
const skip = query.options.start || query.options.skip;
// Extracting ids from original request to map with query results.
const ids = this.extractIds(query, ref);
if (!_.isArray(ids)) {
return data
.filter(entry => entry[ids.alias].toString() === ids.value.toString())
.slice(skip, skip + query.options.limit);
}
// Critical: don't touch this part until you truly understand what you're doing.
// The data array takes care of the sorting of the entries. It explains why we are looping from this array and not the `ids` array.
// Then, we're applying the `limit`, `start` and `skip` argument.
return data
.filter(entry => entry !== undefined)
.filter(entry => ids.map(id => id.toString()).includes(entry[ref.primaryKey].toString()))
.slice(skip, skip + query.options.limit);
});
},
extractIds: (query, ref) => {
if ( _.get(query.options, `query.${ref.primaryKey}`)) {
return _.get(query.options, `query.${ref.primaryKey}`);
}
// Single object to retrieve (one-to-many).
const alias = _.first(Object.keys(query.options.query));
return {
alias,
value: _.get(query.options, `query.${alias}`)
};
},
makeQuery: async function(model, query = {}) {
if (_.isEmpty(query.ids)) {
return [];
}
const ref = this.retrieveModel(model, query.options.source);
// Construct parameters object sent to the Content Manager service.
// We are faking the `start`, `skip` and `limit` argument because it doesn't make sense because we are merging different requests in one.
// Note: we're trying to avoid useless populate for performances. Please be careful if you're updating this part.
const populate = ref.associations
.filter(association => !association.dominant && _.isEmpty(association.model))
.map(association => association.alias);
const params = {
...query.options,
populate,
query: query.options.where || {},
start: 0,
skip: 0,
limit: 100,
};
params.query[query.alias] = _.uniq(query.ids.filter(x => !_.isEmpty(x)).map(x => x.toString()));
if (['id', '_id'].includes(query.alias)) {
// However, we're applying a limit based on the number of entries we've to fetch.
// We'll apply the real `skip`, `start` and `limit` parameters during the mapping above.
params.limit = params.query[query.alias].length;
}
// Run query and remove duplicated ID.
const request = await strapi.plugins['content-manager'].services['contentmanager'].fetchAll({ model }, params);
return request && request.toJSON ? request.toJSON() : request;
},
retrieveModel: function(model, source) {
// Retrieve refering model.
return source ?
strapi.plugins[source].models[model]:
strapi.models[model];
},
extractQueries: function(model, keys) {
const queries = [];
const map = [];
keys.forEach((current, index) => {
// Extract query options.
// Note: the `single` means that we've only one entry to fetch.
const { single = false, params = {}, association } = current;
const { query = {}, ...options } = current.options;
// Retrieving referring model.
const ref = this.retrieveModel(model, options.source);
// Find similar query.
const indexQueries = queries.findIndex(query => _.isEqual(query.options, options));
// Generate array of IDs to fetch.
const ids = [];
// Only one entry to fetch.
if (single) {
ids.push(params[ref.primaryKey]);
} else if (_.isArray(query[ref.primaryKey])) {
ids.push(...query[ref.primaryKey]);
} else {
ids.push(query[association.via]);
}
if (indexQueries !== -1) {
// Push to the same query the new IDs to fetch.
queries[indexQueries].ids.push(...ids);
map[indexQueries].push(index);
} else {
// Create new query in the query.
queries.push({
ids,
options,
alias: _.first(Object.keys(query)) || ref.primaryKey
});
map[queries.length - 1 > 0 ? queries.length - 1 : 0] = [];
map[queries.length - 1].push(index);
}
});
return {
queries,
map
};
}
};

View File

@ -10,6 +10,8 @@ const _ = require('lodash');
const pluralize = require('pluralize');
const policyUtils = require('strapi-utils').policy;
const Loaders = require('./Loaders');
module.exports = {
/**
* Convert parameters to valid filters parameters.
@ -31,7 +33,7 @@ module.exports = {
* @return String
*/
amountLimiting: params => {
amountLimiting: (params = {}) => {
if (params.limit && params.limit < 0) {
params.limit = 0;
} else if (params.limit && params.limit > strapi.plugins.graphql.config.amountLimit) {
@ -165,7 +167,7 @@ module.exports = {
return async (ctx, next) => {
ctx.params = {
...params,
[model.primaryKey]: ctx.params.id,
[model.primaryKey]: ctx.query[model.primaryKey],
};
// Return the controller.
@ -174,15 +176,7 @@ 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,
);
return controller(ctx, next);
};
return controller;
})();
// The controller hasn't been found.
@ -230,7 +224,9 @@ module.exports = {
),
);
return async (obj, options, { context }) => {
return async (obj, options = {}, { context }) => {
const _options = _.cloneDeep(options);
// Hack to be able to handle permissions for each query.
const ctx = Object.assign(_.clone(context), {
request: Object.assign(_.clone(context.request), {
@ -254,13 +250,33 @@ module.exports = {
return policy;
}
// Initiliase loaders for this request.
Loaders.initializeLoader();
// 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);
// Note: we've to used the Object.defineProperties to reset the prototype. It seems that the cloning the context
// cause a lost of the Object prototype.
Object.defineProperties(ctx, {
query: {
value: {
...this.convertToParams(_.omit(_options, 'where')),
..._options.where,
// Avoid population.
_populate: model.associations.filter(a => !a.dominant && _.isEmpty(a.model)).map(a => a.alias),
},
writable: true,
configurable: true
},
params: {
value: this.convertToParams(this.amountLimiting(_options)),
writable: true,
configurable: true
}
});
if (isController) {
const values = await resolver.call(null, context);
const values = await resolver.call(null, ctx);
if (ctx.body) {
return ctx.body;
@ -269,7 +285,7 @@ module.exports = {
return values && values.toJSON ? values.toJSON() : values;
}
return resolver.call(null, obj, options, context);
return resolver.call(null, obj, _options, ctx);
}
// Resolver can be a promise.

View File

@ -9,6 +9,7 @@
const _ = require('lodash');
const pluralize = require('pluralize');
const Aggregator = require('./Aggregator');
const Loaders = require('./Loaders');
const Query = require('./Query.js');
const Mutation = require('./Mutation.js');
const Types = require('./Types.js');
@ -379,20 +380,21 @@ module.exports = {
: strapi.models[params.model];
if (association.type === 'model') {
params.id = _.get(obj, [association.alias, ref.primaryKey], obj[association.alias]);
params[ref.primaryKey] = _.get(obj, [association.alias, ref.primaryKey], obj[association.alias]);
} else {
// Apply optional arguments to make more precise nested request.
const convertedParams = strapi.utils.models.convertParams(
name,
Query.convertToParams(Query.amountLimiting(options)),
);
const where = strapi.utils.models.convertParams(
name,
options.where || {},
);
// Limit, order, etc.
Object.assign(queryOpts, convertedParams);
Object.assign(queryOpts, convertedParams, { where: where.where });
// Skip.
queryOpts.skip = convertedParams.start;
@ -404,24 +406,23 @@ module.exports = {
return related[ref.primaryKey] || related;
}
);
Object.assign(queryOpts, {
...queryOpts,
query: {
[ref.primaryKey]: arrayOfIds
}
});
// Where.
queryOpts.query = strapi.utils.models.convertParams(name, {
// Construct the "where" query to only retrieve entries which are
// related to this entry.
[ref.primaryKey]: arrayOfIds,
...where.where,
}).where;
break;
}
default:
// Where.
queryOpts.query = strapi.utils.models.convertParams(name, {
// Construct the "where" query to only retrieve entries which are
// related to this entry.
[association.via]: obj[ref.primaryKey],
...where.where,
}).where;
Object.assign(queryOpts, {
...queryOpts,
query: {
[association.via]: obj[ref.primaryKey]
}
});
}
}
@ -429,20 +430,20 @@ module.exports = {
queryOpts.query.hasOwnProperty('id') &&
queryOpts.query.id.hasOwnProperty('value') &&
Array.isArray(queryOpts.query.id.value)
){
) {
queryOpts.query.id.symbol = 'IN';
}
const value = await (association.model
? resolvers.fetch(params, association.plugin, [])
: resolvers.fetchAll(params, { ...queryOpts, populate: [] }));
const loaderName = association.plugin ? `${association.plugin}__${params.model}`: params.model;
return value && value.toJSON ? value.toJSON() : value;
return association.model ?
Loaders.loaders[loaderName].load({ params, options: queryOpts, single: true }):
Loaders.loaders[loaderName].load({ options: queryOpts, association });
},
});
});
return acc;
}, initialState);
},
}
};

View File

@ -29,6 +29,7 @@ module.exports = {
modelName = '',
attributeName = '',
rootType = 'query',
action = ''
}) {
// Type
if (definition.type) {
@ -62,7 +63,7 @@ module.exports = {
break;
}
if (definition.required) {
if (definition.required && action !== 'update') {
type += '!';
}
@ -191,7 +192,6 @@ module.exports = {
return `
input ${inputName} {
${Object.keys(model.attributes)
.filter(attribute => model.attributes[attribute].private !== true)
.map(attribute => {
return `${attribute}: ${this.convertType({
definition: model.attributes[attribute],
@ -202,6 +202,20 @@ module.exports = {
})
.join('\n')}
}
input edit${inputName} {
${Object.keys(model.attributes)
.map(attribute => {
return `${attribute}: ${this.convertType({
definition: model.attributes[attribute],
modelName: globalId,
attributeName: attribute,
rootType: 'mutation',
action: 'update'
})}`;
})
.join('\n')}
}
`;
/* eslint-enable */
},
@ -224,7 +238,7 @@ module.exports = {
`;
case 'update':
return `
input ${type}${inputName} { where: InputID, data: ${inputName} }
input ${type}${inputName} { where: InputID, data: edit${inputName} }
type ${type}${payloadName} { ${pluralize.singular(name)}: ${
model.globalId
} }

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-settings-manager",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Strapi plugin to manage settings.",
"strapi": {
"name": "Settings Manager",
@ -25,7 +25,7 @@
"devDependencies": {
"flag-icon-css": "^2.8.0",
"react-select": "^1.0.0-rc.5",
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-upload",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Files Upload",
@ -22,12 +22,12 @@
"prepublishOnly": "IS_MONOREPO=true npm run build"
},
"dependencies": {
"strapi-provider-upload-local": "3.0.0-alpha.19",
"strapi-provider-upload-local": "3.0.0-alpha.20",
"stream-to-array": "^2.3.0",
"uuid": "^3.2.1"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "A Strapi developer",

View File

@ -110,7 +110,7 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
showLoaderForm = () => {
const { editPage: { modifiedData }, match: { params: { actionType } } } = this.props;
return actionType !== 'create' && get(modifiedData, ['name'], '') === '';
return actionType !== 'create' && isEmpty(modifiedData);
}
showLoaderPermissions = () => {

View File

@ -1,3 +1,5 @@
const _ = require('lodash');
module.exports = {
type: {
UsersPermissionsPermission: false // Make this type NOT queriable.
@ -63,7 +65,7 @@ module.exports = {
description: 'Update an existing role',
resolverOf: 'UsersPermissions.updateRole',
resolver: async (obj, options, ctx) => {
await strapi.plugins['users-permissions'].controllers.userspermissions.updateRole(ctx);
await strapi.plugins['users-permissions'].controllers.userspermissions.updateRole(ctx.params, ctx.body);
return { ok: true };
}
@ -76,6 +78,56 @@ module.exports = {
return { ok: true };
}
},
createUser: {
description: 'Create a new user',
resolverOf: 'User.create',
resolver: async (obj, options, { context }) => {
context.params = _.toPlainObject(options.input.where);
context.request.body = _.toPlainObject(options.input.data);
await strapi.plugins['users-permissions'].controllers.user.create(context);
return {
user: context.body.toJSON ? context.body.toJSON() : context.body
};
}
},
updateUser: {
description: 'Update an existing user',
resolverOf: 'User.update',
resolver: async (obj, options, { context }) => {
context.params = _.toPlainObject(options.input.where);
context.request.body = _.toPlainObject(options.input.data);
await strapi.plugins['users-permissions'].controllers.user.update(context);
return {
user: context.body.toJSON ? context.body.toJSON() : context.body
};
}
},
deleteUser: {
description: 'Delete an existing user',
resolverOf: 'User.destroy',
resolver: async (obj, options, { context }) => {
// Set parameters to context.
context.params = _.toPlainObject(options.input.where);
context.request.body = _.toPlainObject(options.input.data);
// Retrieve user to be able to return it because
// Bookshelf doesn't return the row once deleted.
await strapi.plugins['users-permissions'].controllers.user.findOne(context);
// Assign result to user.
const user = context.body.toJSON ? context.body.toJSON() : context.body;
// Run destroy query.
await strapi.plugins['users-permissions'].controllers.user.destroy(context);
return {
user
};
}
}
}
}

View File

@ -9,6 +9,7 @@
/* eslint-disable no-useless-escape */
const crypto = require('crypto');
const _ = require('lodash');
const emailRegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
module.exports = {
@ -326,6 +327,10 @@ module.exports = {
}
}
if (!hasAdmin) {
strapi.emit('didCreateFirstAdmin');
}
ctx.send({
jwt,
user: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken'])

View File

@ -95,6 +95,7 @@ module.exports = {
try {
const data = await strapi.plugins['users-permissions'].services.user.add(ctx.request.body);
// Send 201 `created`
ctx.created(data);
} catch(error) {
@ -120,7 +121,7 @@ module.exports = {
if (advancedConfigs.unique_email && ctx.request.body.email) {
const users = await strapi.plugins['users-permissions'].services.user.fetchAll({ email: ctx.request.body.email });
if (users && _.find(users, user => (user.id || user._id).toString() !== ctx.params._id)) {
if (users && _.find(users, user => (user.id || user._id).toString() !== (ctx.params.id || ctx.params._id))) {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken', field: ['email'] }] }] : 'Email is already taken.');
}
}
@ -140,7 +141,7 @@ module.exports = {
email: ctx.request.body.email
});
if (user !== null && (user.id || user._id).toString() !== ctx.params._id) {
if (user !== null && (user.id || user._id).toString() !== (ctx.params.id || ctx.params._id)) {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken', field: ['email'] }] }] : 'Email is already taken.');
}
}
@ -162,7 +163,7 @@ module.exports = {
destroy: async (ctx) => {
const data = await strapi.plugins['users-permissions'].services.user.remove(ctx.params);
// Send 200 `ok`
ctx.send(data);
},

View File

@ -49,5 +49,6 @@
"plugin": "users-permissions",
"configurable": false
}
}
},
"collectionName": "users-permissions_user"
}

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-users-permissions",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Protect your API with a full-authentication process based on JWT",
"strapi": {
"name": "Roles & Permissions",
@ -29,11 +29,11 @@
"koa2-ratelimit": "^0.6.1",
"purest": "^2.0.1",
"request": "^2.83.0",
"strapi-utils": "3.0.0-alpha.19",
"strapi-utils": "3.0.0-alpha.20",
"uuid": "^3.1.0"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.19"
"strapi-helper-plugin": "3.0.0-alpha.20"
},
"author": {
"name": "Strapi team",

View File

@ -109,7 +109,7 @@ module.exports = {
params.model = 'user';
params.id = (params._id || params.id);
await strapi.plugins['content-manager'].services['contentmanager'].delete(params, {source: 'users-permissions'});
return await strapi.plugins['content-manager'].services['contentmanager'].delete(params, {source: 'users-permissions'});
}
return strapi.query('user', 'users-permissions').delete(params);

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-amazon-ses",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Amazon SES provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-mailgun",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Mailgun provider for strapi email plugin",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-sendgrid",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Sendgrid provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-sendmail",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Sendmail provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-aws-s3",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "AWS S3 provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-cloudinary",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Cloudinary provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-local",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Local provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-rackspace",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Rackspace provider for strapi upload",
"main": "./lib",
"scripts": {

View File

@ -477,7 +477,7 @@ module.exports = {
formattedValue = value;
}
if (_.includes(['_start', '_limit'], key)) {
if (_.includes(['_start', '_limit', '_populate'], key)) {
result = convertor(formattedValue, key);
} else if (key === '_sort') {
const [attr, order = 'ASC'] = formattedValue.split(':');

View File

@ -1,6 +1,6 @@
{
"name": "strapi-utils",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "Shared utilities for the Strapi packages",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -58,13 +58,17 @@ module.exports = function() {
$('body').attr('back', `/`);
}
fs.writeFile(sourcePath, $.html(), (err) => {
if (err) {
return reject(err);
}
if (!strapi.config.currentEnvironment.server.production) {
fs.writeFile(sourcePath, $.html(), (err) => {
if (err) {
return reject(err);
}
resolve();
});
} else {
resolve();
});
}
});
});
});

View File

@ -1,6 +1,6 @@
{
"name": "strapi",
"version": "3.0.0-alpha.19",
"version": "3.0.0-alpha.20",
"description": "An open source solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier.",
"homepage": "http://strapi.io",
"keywords": [
@ -60,16 +60,16 @@
"rimraf": "^2.6.2",
"semver": "^5.4.1",
"stack-trace": "0.0.10",
"strapi-generate": "3.0.0-alpha.19",
"strapi-generate-admin": "3.0.0-alpha.19",
"strapi-generate-api": "3.0.0-alpha.19",
"strapi-generate-controller": "3.0.0-alpha.19",
"strapi-generate-model": "3.0.0-alpha.19",
"strapi-generate-new": "3.0.0-alpha.19",
"strapi-generate-plugin": "3.0.0-alpha.19",
"strapi-generate-policy": "3.0.0-alpha.19",
"strapi-generate-service": "3.0.0-alpha.19",
"strapi-utils": "3.0.0-alpha.19"
"strapi-generate": "3.0.0-alpha.20",
"strapi-generate-admin": "3.0.0-alpha.20",
"strapi-generate-api": "3.0.0-alpha.20",
"strapi-generate-controller": "3.0.0-alpha.20",
"strapi-generate-model": "3.0.0-alpha.20",
"strapi-generate-new": "3.0.0-alpha.20",
"strapi-generate-plugin": "3.0.0-alpha.20",
"strapi-generate-policy": "3.0.0-alpha.20",
"strapi-generate-service": "3.0.0-alpha.20",
"strapi-utils": "3.0.0-alpha.20"
},
"author": {
"email": "hi@strapi.io",