Merge branch 'master' into njb/fix-strapi-new

This commit is contained in:
Nick Bolles 2018-09-18 23:32:21 -05:00 committed by GitHub
commit a739f67a17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 128 additions and 73 deletions

View File

@ -92,7 +92,7 @@ The controllers are defined in each `./api/**/controllers/` folders. Every JavaS
## Filters
Filters are a handy way to request data according to generic parameters. It makes filtering, sorting and paginating easy and reusable (eg. `GET /user?_limit=30&name=John`).
Filters are a handy way to request data according to generic parameters. It makes filtering, sorting and paginating easy and reusable (eg. `GET /users?_limit=30&name=John`).
> Please refer to the [filters' guide](../guides/filters.md) for more informations.

View File

@ -137,16 +137,16 @@ In order to do so, you'll need to allow access to other users (identified as 'Gu
### List entries (GET)
To retrieve the list of products, use the `GET /your-content-type` route.
To retrieve the list of products, use the `GET /products` route.
Generated APIs provide a handy way to filter and order queries. In that way, ordering products by price is as easy as `GET http://localhost:1337/product?_sort=price:asc`. For more informations, read the [filters documentation](../guides/filters.md)
Generated APIs provide a handy way to filter and order queries. In that way, ordering products by price is as easy as `GET http://localhost:1337/products?_sort=price:asc`. For more informations, read the [filters documentation](../guides/filters.md)
Here is an example using jQuery.
```js
$.ajax({
type: 'GET',
url: 'http://localhost:1337/product?_sort=price:asc', // Order by price.
url: 'http://localhost:1337/products?_sort=price:asc', // Order by price.
done: function(products) {
console.log('Well done, here is the list of products: ', products);
},
@ -163,7 +163,7 @@ If you want to get a specific entry, add the `id` of the wanted product at the e
```js
$.ajax({
type: 'GET',
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product.
done: function(product) {
console.log('Well done, here is the product having the `id` 123: ', product);
},
@ -182,7 +182,7 @@ jQuery example:
```js
$.ajax({
type: 'POST',
url: 'http://localhost:1337/product',
url: 'http://localhost:1337/products',
data: {
name: 'Cheese cake',
description: 'Chocolate cheese cake with ice cream',
@ -206,7 +206,7 @@ jQuery example:
```js
$.ajax({
type: 'PUT',
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product.
data: {
description: 'This is the new description'
},
@ -228,7 +228,7 @@ jQuery example:
```js
$.ajax({
type: 'DELETE',
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product.
done: function(product) {
console.log('Congrats, your product has been successfully deleted: ', product);
},

View File

@ -97,7 +97,7 @@ By default, each API request is identified as `guest` role (see permissions of `
```js
$.ajax({
type: 'GET',
url: 'http://localhost:1337/article',
url: 'http://localhost:1337/articles',
headers: {
Authorization: `Bearer ${token}`
},
@ -117,7 +117,8 @@ This action sends an email to a user with the link of you reset password page. T
#### Usage
- `email` is your user email.
- `url` is the url link that user will receive.
- `url` is the url link that user will receive. After the user triggers a new password reset,
it is used to redirect the user to the new-password form.
```js
$.ajax({
@ -125,7 +126,7 @@ $.ajax({
url: 'http://localhost:1337/auth/forgot-password',
data: {
email: 'user@strapi.io',
url: 'http://mon-site.com/rest-password'
url: 'http:/localhost:1337/admin/plugins/users-permissions/auth/reset-password'
},
done: function() {
console.log('Your user received an email');
@ -136,8 +137,6 @@ $.ajax({
});
```
> Received link url format http://mon-site.com/rest-password?code=privateCode
## Reset user password.
This action will reset the user password.
@ -165,7 +164,7 @@ $.ajax({
```
## User Object In Strapi Context
The User object is available to successfully authenticated requests.
The `user` object is available to successfully authenticated requests.
#### Usage
- The authenticated `user` object is a property of `ctx.state`.
@ -189,10 +188,9 @@ The User object is available to successfully authenticated requests.
```
## Add a new provider
To add a new provider on strapi, you will need to perform changes onto the following files:
To add a new provider on Strapi, you will need to perform changes onto the following files:
```
packages/strapi-plugin-users-permissions/services/Providers.js
@ -203,7 +201,7 @@ packages/strapi-plugin-users-permissions/admin/src/translations/en.json
We will go step by step.
### Configure your Provider request
### Configure your Provider Request
First, we need to configure our new provider onto `Provider.js` file.
Jump onto the `getProfile` function, you will see the list of currently available providers in the form of a `switch...case`.
@ -212,9 +210,10 @@ As you can see, `getProfile` take three params:
1. provider :: The name of the used provider as a string.
2. query :: The query is the result of the provider callback.
3. callback :: The callback function who will continue the internal strapi login logic.
3. callback :: The callback function who will continue the internal Strapi login logic.
Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you may encounter trying to implement your own provider.
Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you
may encounter trying to implement your own provider.
#### Configure your oauth generic information
@ -239,9 +238,11 @@ Let's take the `discord` one as an example since it's not the easier, it should
}
}
});
}
```
So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact with the REST API.
So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact
with the REST API.
To understand each value usage, and the templating syntax, I invite you to read the [Official Purest Documentation](https://github.com/simov/purest/tree/2.x)
@ -265,17 +266,21 @@ You may also want to take a look onto the numerous already made configurations [
}
```
Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve user information.
Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve
user information.
Here you see the real power of `purest`, you can simply make a get request on the desired URL, using the `access_token` from the `query` parameter to authenticate.
Here you see the real power of `purest`, you can simply make a get request on the desired URL, using the `access_token`
from the `query` parameter to authenticate.
That way, you should be able to retrieve the user info you need.
Now, you can simply call the `callback` function with the username and email of your user. That way, strapi will be able to retrieve your user from the database and log you in.
Now, you can simply call the `callback` function with the username and email of your user. That way, strapi will be able
to retrieve your user from the database and log you in.
#### Configure the new provider model onto database
Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and managed from the admin panel.
Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and
managed from the admin panel.
Into: `packages/strapi-plugin-users-permissions/config/functions/bootstrap.js`
@ -296,9 +301,8 @@ For our discord provider it will look like:
},
```
You have already done the hard part, now, we simply need to make our new provider available from the front side of our application. So let's do it!
You have already done the hard part, now, we simply need to make our new provider available from the front
side of our application. So let's do it!
<!-- #### Tests -->
<!-- TODO Add documentation about how to configure unit test for the new provider -->
@ -323,7 +327,6 @@ These two change will set up the popup message who appear on the UI when we will
That's it, now you should be able to use your new provider.
## Email templates
[See the documentation on GitHub](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/docs/email-templates.md)

View File

@ -29,11 +29,11 @@ Easily filter results according to fields values.
Find users having `John` as first name.
`GET /user?firstName=John`
`GET /users?firstName=John`
Find products having a price equal or greater than `3`.
`GET /product?price_gte=3`
`GET /products?price_gte=3`
### Sort
@ -43,8 +43,8 @@ Sort according to a specific field.
Sort users by email.
- ASC: `GET /user?_sort=email:asc`
- DESC: `GET /user?_sort=email:desc`
- ASC: `GET /users?_sort=email:asc`
- DESC: `GET /users?_sort=email:desc`
### Limit
@ -54,7 +54,7 @@ Limit the size of the returned results.
Limit the result length to 30.
`GET /user?_limit=30`
`GET /users?_limit=30`
### Start
@ -64,7 +64,7 @@ Skip a specific number of entries (especially useful for pagination).
Get the second page of results.
`GET /user?_start=10&_limit=10`
`GET /users?_start=10&_limit=10`
## Programmatic usage

View File

@ -40,7 +40,7 @@ The global policies can be associated to any routes in your project.
"routes": [
{
"method": "GET",
"path": "/car",
"path": "/cars",
"handler": "Car.find",
"config": {
"policies": [
@ -66,7 +66,7 @@ Plugins can add and expose policies into your app. For example, the plugin `Auth
"routes": [
{
"method": "GET",
"path": "/car",
"path": "/cars",
"handler": "Car.find",
"config": {
"policies": [
@ -102,7 +102,7 @@ module.exports = async (ctx, next) => {
"routes": [
{
"method": "GET",
"path": "/car",
"path": "/cars",
"handler": "Car.find",
"config": {
"policies": [

View File

@ -12,17 +12,17 @@ You have to edit the `routes.json` file in one of your APIs folders (`./api/**/c
"routes": [
{
"method": "GET",
"path": "/product",
"path": "/products",
"handler": "Product.find",
},
{
"method": ["POST", "PUT"],
"path": "/product/:id",
"path": "/products/:id",
"handler": "Product.createOrUpdate",
},
{
"method": "POST",
"path": "/product/:id/buy",
"path": "/products/:id/buy",
"handler": "Product.buy",
"config": {
"policies": ["isAuthenticated", "hasCreditCard"]
@ -33,7 +33,7 @@ You have to edit the `routes.json` file in one of your APIs folders (`./api/**/c
```
- `method` (string): Method or array of methods to hit the route (ex: `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, `PATCH`)
- `path` (string): URL starting with `/` (ex: `/product`)
- `path` (string): URL starting with `/` (ex: `/products`)
- `handler` (string): Action to executed when the route is hit following this syntax `<Controller>.<action>`
- `config`
- `policies` (array): Array of policies names or path ([see more](../guides/policies.md))
@ -48,12 +48,12 @@ The router used by Strapi allows you to create dynamic routes where you can use
"routes": [
{
"method": "GET",
"path": "/product/:category/:id",
"path": "/products/:category/:id",
"handler": "Product.findOneByCategory",
},
{
"method": "GET",
"path": "/product/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits.
"path": "/products/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits.
"handler": "Product.findOneByRegion",
}
]

View File

@ -21,42 +21,42 @@ module.exports = scope => {
const routes = {
routes: [{
method: 'GET',
path: '/' + scope.humanizeId,
path: '/' + scope.idPluralized,
handler: scope.globalID + '.find',
config: {
policies: []
}
}, {
method: 'GET',
path: '/' + scope.humanizeId + '/count',
path: '/' + scope.idPluralized + '/count',
handler: scope.globalID + '.count',
config: {
policies: []
}
}, {
method: 'GET',
path: '/' + scope.humanizeId + '/:' + tokenID,
path: '/' + scope.idPluralized + '/:' + tokenID,
handler: scope.globalID + '.findOne',
config: {
policies: []
}
}, {
method: 'POST',
path: '/' + scope.humanizeId,
path: '/' + scope.idPluralized,
handler: scope.globalID + '.create',
config: {
policies: []
}
}, {
method: 'PUT',
path: '/' + scope.humanizeId + '/:' + tokenID,
path: '/' + scope.idPluralized + '/:' + tokenID,
handler: scope.globalID + '.update',
config: {
policies: []
}
}, {
method: 'DELETE',
path: '/' + scope.humanizeId + '/:' + tokenID,
path: '/' + scope.idPluralized + '/:' + tokenID,
handler: scope.globalID + '.destroy',
config: {
policies: []
@ -67,21 +67,21 @@ module.exports = scope => {
if (scope.args.tpl && scope.args.tpl !== 'mongoose') {
routes.routes.push({
method: 'POST',
path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation',
path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation',
handler: scope.globalID + '.createRelation',
config: {
policies: []
}
}, {
method: 'PUT',
path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation',
path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation',
handler: scope.globalID + '.updateRelation',
config: {
policies: []
}
}, {
method: 'DELETE',
path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation',
path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation',
handler: scope.globalID + '.destroyRelation',
config: {
policies: []

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": true
}
}

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": false
}
}

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": false
}
}

View File

@ -461,6 +461,11 @@ module.exports = function(strapi) {
const connection = strapi.config.connections[definition.connection];
let columns = Object.keys(attributes).filter(attribute => ['string', 'text'].includes(attributes[attribute].type));
if (!columns) {
// No text columns founds, exit from creating Fulltext Index
return;
}
switch (connection.settings.client) {
case 'pg': {
// Enable extension to allow GIN indexes.

View File

@ -456,7 +456,6 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
const hashArray = split(this.props.hash, ('::'));
const valueToReplace = includes(this.props.hash, '#create') ? '#create' : '#edit';
const contentTypeName = replace(hashArray[0], valueToReplace, '');
let cbSuccess;
let dataSucces = null;
let cbFail;
@ -466,7 +465,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
// Check if the user is editing the attribute
const isAttribute = includes(hashArray[1], 'attribute');
cbSuccess = isAttribute ? () => this.editTempContentTypeAttribute(redirectToChoose) : this.createContentType;
dataSucces = isAttribute ? null : this.props.modifiedDataEdit;
dataSucces = isAttribute ? null : this.getModelWithCamelCaseName(this.props.modifiedDataEdit);
cbFail = isAttribute ? () => this.editContentTypeAttribute(redirectToChoose) : this.contentTypeEdit;
return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail);
}
@ -476,11 +475,24 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail);
}
default: {
return this.createContentType(this.props.modifiedData);
return this.createContentType(
this.getModelWithCamelCaseName(this.props.modifiedData)
);
}
}
}
getModelWithCamelCaseName = (model = {}) => {
if (isEmpty(model) || isEmpty(model.name)) {
return;
}
return {
...model,
name: camelCase(model.name),
};
}
initComponent = (props, condition) => {
if (!isEmpty(props.hash)) {
this.setState({ showModal: true });

View File

@ -30,7 +30,7 @@ module.exports = {
let options = ctx.request.body;
await strapi.plugins.email.services.send(options, config);
await strapi.plugins.email.services.email.send(options, config);
// Send 200 `ok`
ctx.send({});

View File

@ -113,14 +113,16 @@ button {
margin-left: 1rem;
}
.flexed {
.flexed,
.label {
display: flex;
}
.label {
align-items: center;
line-height: 1;
width: 15rem;
height: 5.2rem;
line-height: 5.2rem;
margin-left: 5rem;
color: #333740;
font-weight: 600;

View File

@ -211,10 +211,9 @@
"prefix": ""
}
},
{
"method": "GET",
"path": "/user",
"path": "/users",
"handler": "User.find",
"config": {
"policies": [],
@ -223,7 +222,7 @@
},
{
"method": "GET",
"path": "/user/me",
"path": "/users/me",
"handler": "User.me",
"config": {
"policies": [],
@ -232,7 +231,7 @@
},
{
"method": "GET",
"path": "/user/:_id",
"path": "/users/:_id",
"handler": "User.findOne",
"config": {
"policies": [],
@ -241,7 +240,7 @@
},
{
"method": "POST",
"path": "/user",
"path": "/users",
"handler": "User.create",
"config": {
"policies": [],
@ -250,7 +249,7 @@
},
{
"method": "PUT",
"path": "/user/:_id",
"path": "/users/:_id",
"handler": "User.update",
"config": {
"policies": [],
@ -259,7 +258,7 @@
},
{
"method": "DELETE",
"path": "/user/:_id",
"path": "/users/:_id",
"handler": "User.destroy",
"config": {
"policies": [],

View File

@ -12,7 +12,7 @@ const _ = require('lodash');
// Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate
const isNumeric = (value) => {
return !isNaN(parseFloat(value)) && isFinite(value);
return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value);
};
/* eslint-disable prefer-template */

View File

@ -382,6 +382,31 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force =
}
};
/**
* Allow dynamic config values through
* the native ES6 template string function.
*/
const regex = /\$\{[^()]*\}/g;
const excludeConfigPaths = ['info.scripts'];
const templateConfigurations = function (obj, configPath = '') {
// Allow values which looks like such as
// an ES6 literal string without parenthesis inside (aka function call).
// Exclude config with conflicting syntax (e.g. npm scripts).
return Object.keys(obj).reduce((acc, key) => {
if (isPlainObject(obj[key]) && !isString(obj[key])) {
acc[key] = templateConfigurations(obj[key], `${configPath}.${key}`);
} else if (isString(obj[key])
&& !excludeConfigPaths.includes(configPath.substr(1))
&& obj[key].match(regex) !== null) {
acc[key] = eval('`' + obj[key] + '`'); // eslint-disable-line prefer-template
} else {
acc[key] = obj[key];
}
return acc;
}, {});
};
const isAdminInDevMode = function () {
try {
fs.accessSync(path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'index.html'), fs.constants.R_OK | fs.constants.W_OK);