Merge branch 'front/global-improvements' of github.com:strapi/strapi into front/global-improvements

This commit is contained in:
Ky 2020-04-21 18:15:12 +02:00
commit 6b85cb28fa
83 changed files with 913 additions and 480 deletions

View File

@ -132,6 +132,12 @@ Additional settings can be set on models:
In this example, the model `Restaurant` will be accessible through the `Restaurants` global variable. The data will be stored in the `Restaurants_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json`
::: warning
If not set manually in the JSON file, Strapi will adopt the filename as `globalId`.
The `globalId` serves as a reference to your model within relations and Strapi APIs. If you chose to rename it (either by renaming your file or by changing the value of the `globalId`), you'd have to migrate your tables manually and update the references.
Please note that you should not alter Strapi's models `globalId` (plugins and core ones) since it is used directly within Strapi APIs and other models' relations.
:::
::: tip
The `connection` value can be changed whenever you want, but you should be aware that there is no automatic data migration process. Also if the new connection doesn't use the same ORM you will have to rewrite your queries.
:::
@ -159,10 +165,7 @@ The info key on the model-json states information about the model. This informat
The options key on the model-json states.
- `idAttribute`: This tells the model which attribute to expect as the unique identifier for each database row (typically an auto-incrementing primary key named 'id'). _Only valid for bookshelf._
- `idAttributeType`: Data type of `idAttribute`, accepted list of value below. _Only valid for bookshelf._
- `timestamps`: This tells the model which attributes to use for timestamps. Accepts either `boolean` or `Array` of strings where first element is create date and second element is update date. Default value when set to `true` for Bookshelf is `["created_at", "updated_at"]` and for MongoDB is `["createdAt", "updatedAt"]`.
- `uuid` : Boolean to enable UUID support on MySQL, you will need to set the `idAttributeType` to `uuid` as well and install the `bookshelf-uuid` package. To load the package you can see [this example](./configurations.md#bookshelf-mongoose).
**Path —** `User.settings.json`.

View File

@ -137,12 +137,13 @@ module.exports = {
*/
async create(data, { files } = {}) {
const entry = await strapi.query(model).create(data);
const entry = await strapi.query('restaurant').create(data);
if (files) {
// automatically uploads the files based on the entry and the model
await strapi.entityService.uploadFiles(entry, files, {
model: strapi.models.restaurant,
model: 'restaurant',
// if you are using a plugin's model you will have to add the `plugin` key (plugin: 'users-permissions')
});
return this.findOne({ id: entry.id });
}
@ -167,12 +168,13 @@ module.exports = {
*/
async update(params, data, { files } = {}) {
const entry = await strapi.query(model).update(params, data);
const entry = await strapi.query('restaurant').update(params, data);
if (files) {
// automatically uploads the files based on the entry and the model
await strapi.entityService.uploadFiles(entry, files, {
model: strapi.models.restaurant,
model: 'restaurant',
// if you are using a plugin's model you will have to add the `plugin` key (plugin: 'users-permissions')
});
return this.findOne({ id: entry.id });
}

View File

@ -0,0 +1,307 @@
# Google App Engine
In this guide we are going to:
- Create a new Strapi project
- Configure PostgreSQL for the production enviroment
- Deploy the app to Google App Engine
- Add the [Google Cloud Storage file uploading plugin](https://github.com/Lith/strapi-provider-upload-google-cloud-storage) by [@Lith](https://github.com/Lith)
### New Strapi project
:::: tabs
::: tab yarn
Use **yarn** to install the Strapi project (**recommended**). [Install yarn with these docs](https://yarnpkg.com/lang/en/docs/install/)
```bash
yarn create strapi-app my-project --quickstart
```
:::
::: tab npx
Use **npm/npx** to install the Strapi project
```bash
npx create-strapi-app my-project --quickstart
```
:::
::::
When the setup completes, register an admin user using the form which opens in the browser. This user will be only relevant in local development.
The `sqlite` database is created at `.tmp/data.db`.
Login, but don't add content types yet. Close the browser. Quit the running app.
### Initial commit
This may be a good point to commit the files in their initial state.
```bash
cd my-project
git init
git add .
git commit -m first
```
### Install the Cloud SDK CLI tool
[Cloud SDK: Command Line Interface](https://cloud.google.com/sdk/)
### New App Engine project
Create a new [App Engine](https://console.cloud.google.com/appengine/) project.
Select the region, such as `europe-west`.
- Language: Node JS
- Environment: Standard (or Flexible)
(_A note on performance and cost_: the `Standard Environment` is sufficient for development, but it may not be for production. Review the resources your application will need to determine the cost. When you sign up for Google App Engine, it offers a certain amount of free credits which will not be billed.)
Create the project. Take note of the instance identifier, which is in the form of `<instance_id>:<region>:<instance_name>`.
Check if `gcloud` lists the project:
```bash
gcloud projects list
```
Run `init` to authenticate the cli, and select current cloud project.
```bash
gcloud init
```
### Create the database (PostgreSQL)
Create the [Cloud SQL database](https://cloud.google.com/sql/docs/postgres/create-manage-databases) which the app is going to use.
Take note of the user name (default is `postgres`) and password.
The first database will be created with the name `postgres`. This cannot be deleted.
Create another database, named `strapi` for example. It may be useful to delete and and re-create this while you are experimenting with the application setup.
### Create app.yaml and .gcloudignore
Create the `app.yaml` file in the project root.
Add `app.yaml` to `.gitignore`.
The instance identifier looks like `myapi-123456:europe-west1:myapi`.
The `myapi-123456` part is the project identifier. (The number is automatically added to short project names).
The following is an example config for `Standard Environment` or `Flexible Environment`.
:::: tabs
::: tab Standard Environment
```yaml
runtime: nodejs10
instance_class: F2
env_variables:
HOST: '<project_id>.appspot.com'
NODE_ENV: 'production'
DATABASE_NAME: 'strapi'
DATABASE_USERNAME: 'postgres'
DATABASE_PASSWORD: '<password>'
INSTANCE_CONNECTION_NAME: '<instance_identifier>'
beta_settings:
cloud_sql_instances: '<instance_identifier>'
```
:::
::: tab Flexible Environment
```yaml
runtime: nodejs10
env: flex
env_variables:
HOST: '<project_id>.appspot.com'
NODE_ENV: 'production'
DATABASE_NAME: 'strapi'
DATABASE_USERNAME: 'postgres'
DATABASE_PASSWORD: '<password>'
INSTANCE_CONNECTION_NAME: '<instance_identifier>'
beta_settings:
cloud_sql_instances: '<instance_identifier>'
```
:::
::::
Create `.gcloudignore` in the project root, include `app.yaml` here as well.
```
app.yaml
.gcloudignore
.git
.gitignore
node_modules/
#!include:.gitignore
```
In the case of Strapi, the admin UI will have to be re-built after every deploy,
and so we don't deploy local build artifacts, cache files and so on by including
the `.gitignore` entries.
### Configure the database
The `PostgreSQL` database will need the `pg` package.
```bash
yarn add pg
```
[Google App Engine requires](https://cloud.google.com/sql/docs/postgres/connect-app-engine) to connect to the database using the unix socket path, not an IP and port.
Edit `database.json`, and use the socket path as `host`.
```
config/environments/production/database.json
```
```json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "bookshelf",
"settings": {
"client": "postgres",
"host": "/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}",
"database": "${process.env.DATABASE_NAME}",
"username": "${process.env.DATABASE_USERNAME}",
"password": "${process.env.DATABASE_PASSWORD}"
},
"options": {}
}
}
}
```
Edit `server.json` to pick up the deployed hostname from the `HOST` variable in `app.yaml`.
```
config/environments/production/server.json
```
```json
{
"host": "${process.env.HOST}",
"port": "${process.env.PORT || 1337}",
"production": true,
"proxy": {
"enabled": false
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": false
}
}
```
### Auto-build after deploy
After deployment, the admin UI has to be re-built. This generates the contents of the `build` folder on the server.
In `package.json`, add the `gcp-build` command to `scripts`:
```json
{
"scripts": {
"gcp-build": "strapi build"
}
}
```
### Deploy
```bash
gcloud app deploy app.yaml --project myapi-123456
```
Watch the logs:
```bash
gcloud app logs tail --project=myapi-123456 -s default
```
Open the admin page and register and admin user.
```
https://myapp-123456.appspot.com/admin/
```
### File uploading to Google Cloud Storage
[Lith/strapi-provider-upload-google-cloud-storage](https://github.com/Lith/strapi-provider-upload-google-cloud-storage)
```bash
yarn add strapi-provider-upload-google-cloud-storage
```
Deploy so that the server app includes the dependency from `package.json`.
Create a Google service account key.
<https://console.cloud.google.com/apis/credentials/serviceaccountkey>
Save the JSON credentials file.
Plugins > File Upload > Settings > Production tab
By default `localhost` is selected. Select the `Google Cloud Storage` plugin.
Copy the JSON key and set the regions.
Open the `Cloud Console > Storage > Browser` menu.
Copy the bucket name to the plugin settings, the default is the app ID, such as `myapi-123456.appspot.com`.
(Note that the `Access control` setting of the bucket has to be `Fine-grained`, which is the default.)
Click `Save`, and it's ready to go!
### Post-setup configuration
**CORS**
CORS is enabled by default, allowing `*` origin. You may want to limit the allowed origins.
```
config/environments/production/security.json
```
**Changing the admin url**
```
config/environments/production/server.json
```
```json
{
"admin": {
"path": "/dashboard"
}
}
```

View File

@ -46,6 +46,18 @@ Manual guides for deployment on various platforms, for One-click and docker plea
</InstallLink>
</div>
<div>
<InstallLink link="../deployment/google-app-engine">
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" version="1.1"><path d="M6.969 3L4.094 8.188l1.468 2.624L8.438 6h10.25L17 3zm8.75 4l2.969 4.906L13.625 21H17l5-9-2.781-5zM12 8c-2.207 0-4 1.793-4 4s1.793 4 4 4 4-1.793 4-4-1.793-4-4-4zM3.531 9.219L2 12l4.969 9H12.5l1.656-3h-5.75zM12 10c1.102 0 2 .898 2 2 0 1.102-.898 2-2 2-1.102 0-2-.898-2-2 0-1.102.898-2 2-2z" fill="#fff"/></svg>
</template>
<template #title>Google App Engine</template>
<template #description>
Manual step by step guide for deploying on GCP's App Engine
</template>
</InstallLink>
</div>
<div>
<InstallLink link="../deployment/heroku">
<template #icon>

View File

@ -19,7 +19,7 @@ yarn create strapi-app my-app --quickstart --no-run
2. Generate a plugin:
```bash
strapi generate:plugin wysiwyg
yarn run strapi generate:plugin wysiwyg
```
3. Install the needed dependencies:
@ -126,7 +126,7 @@ export default MediaLib;
**Path —** `./plugins/wysiwyg/admin/src/components/Wysiwyg/index.js`
```js
iimport React, { useState } from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import { Button } from '@buffetjs/core';
@ -332,4 +332,4 @@ export default strapi => {
};
```
And VOILA, if you create a new `collectionType` or a `singleType` with a `richtext` field you will see the implementation of [CKEditor]((https://ckeditor.com/ckeditor-5/) instead of the default WYSIWYG.
And VOILA, if you create a new `collectionType` or a `singleType` with a `richtext` field you will see the implementation of [CKEditor](https://ckeditor.com/ckeditor-5/) instead of the default WYSIWYG.

View File

@ -74,7 +74,7 @@ In some cases (with Heroku, Docker...), listening to `localhost` won't work. In
### Example
**After -** `./config/environments/**/server.js`
**Before -** `./config/environments/**/server.js`
```json
{

View File

@ -48,6 +48,10 @@ This means that you now have to configure your provider directly in the files. Y
## MongoDB Media relation changes
::: tip
The guide below only applies if you are using MongoDB/Mongoose, if you are using a Bookshelf database you can simply skip to the [rebuilding step](#rebuilding-your-administration-panel)
:::
In the media library features, We wanted to make sure media would keep their ordering. To implement this in mongo we had to change the way the media relation was built.
Previously, the `upload_file` collection was the one keeping track of the relations and the entity related to the file had not reference to it.

View File

@ -3,53 +3,30 @@
The logic of a plugin is located at its root directory `./plugins/**`. The admin panel related parts of each plugin are contained in the `/admin` folder.
The folders and files structure are the following:
<!-- ```
/plugin
└─── admin // Contains the plugin's front-end
| └─── src // Source code directory
| └─── index.js // Entry point of the plugin
| └─── pluginId.js // Name of the plugin
| └─── lifecycles.js // File in which the plugin sets the hooks to be ran in another plugin.
| |
| └─── components // Contains the list of React components used by the plugin
| └─── containers
| | └─── App // Container used by every others containers
| | └─── Initializer // This container is required, it is used to executed logic right after the plugin is mounted.
| | └─── HomePage
| | └─── action.js // List of Redux actions used by the current container
| | └─── constants.js // List of actions constants
| | └─── index.js // React component of the current container
| | └─── reducer.js // Redux reducer used by the current container
| | └─── sagas.js // List of sagas functions
| | └─── selectors.js // List of selectors
| | └─── styles.scss // Style of the current container
| |
| └─── translations // Contains the translations to make the plugin internationalized
| └─── en.json
| └─── index.js // File that exports all the plugin's translations.
| └─── fr.json
└─── config // Contains the configurations of the plugin
| └─── functions
| | └─── bootstrap.js // Asynchronous bootstrap function that runs before the app gets started
| └─── policies // Folder containing the plugin's policies
| └─── queries // Folder containing the plugin's models queries
| └─── routes.json // Contains the plugin's API routes
└─── controllers // Contains the plugin's API controllers
└─── middlewares // Contains the plugin's middlewares
└─── models // Contains the plugin's API models
└─── services // Contains the plugin's API services
``` -->
```bash
plugin/
├── config/ # Contains the configurations of the plugin
│ ├── functions/
│ │ └── bootstrap.js # Asynchronous bootstrap function that runs before the app gets started
│ ├── policies/ # Folder containing the plugin's policies
│ ├── queries/ # Folder containing the plugin's models queries
│ └── routes.json # Contains the plugin's API routes
├── controllers/ # Contains the plugin's API controllers
├── middlewares/ # Contains the plugin's middlewares
├── models/ # Contains the plugin's API models
└── services/ # Contains the plugin's API services
```
plugin/
└─── admin/ # Contains the plugin's front-end
| └─── src/ # Source code directory
| └─── index.js # Entry point of the plugin
| └─── pluginId.js # Name of the plugin
| └─── lifecycles.js # File in which the plugin sets the hooks to be ran in another plugin.
| |
| └─── components/ # Contains the list of React components used by the plugin
| └─── containers/
| | └─── App/ # Container used by every others containers
| | └─── Initializer/ # This container is required, it is used to executed logic right after the plugin is mounted.
| └─── translations/ # Contains the translations to make the plugin internationalized
| └─── en.json
| └─── index.js # File that exports all the plugin's translations.
| └─── fr.json
└─── config/ # Contains the configurations of the plugin
| └─── functions/
| | └─── bootstrap.js # Asynchronous bootstrap function that runs before the app gets started
| └─── policies/ # Folder containing the plugin's policies
| └─── queries/ # Folder containing the plugin's models queries
| └─── routes.json # Contains the plugin's API routes
└─── controllers/ # Contains the plugin's API controllers
└─── middlewares/ # Contains the plugin's middlewares
└─── models/ # Contains the plugin's API models
└─── services/ # Contains the plugin's API services
```

View File

@ -14,7 +14,3 @@ Create a development project
In a new terminal window:
1. Generate a new plugin: `cd /path/to/myDevelopmentProject && strapi generate:plugin my-plugin`
::: tip
The admin panel integration is currently not available. You can still add backend features.
:::

View File

@ -712,6 +712,10 @@ Add the language translation in `packages/strapi-plugin-users-permissions/admin/
These two change will set up the popup message that appears in the UI. That's it, now you should be able to use your new provider.
### Rebuild the Admin Panel
Please see the following [documentation](../admin-panel/customization.md#build) on rebuilding the admin panel.
## Templating emails
By default, this plugin comes with only two templates (reset password and email address confirmation) at the moment. More templates will come later. The templates use Lodash's template() method to populate the variables.

View File

@ -20,7 +20,7 @@
"model": "file",
"via": "related",
"allowedTypes": [
"images",
"images",
"files",
"videos"
],

View File

@ -1,7 +1,7 @@
{
"name": "getstarted",
"private": true,
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "A Strapi application.",
"scripts": {
"develop": "strapi develop",
@ -15,22 +15,22 @@
"mysql": "^2.17.1",
"pg": "^7.10.0",
"sqlite3": "^4.0.6",
"strapi": "3.0.0-beta.19.5",
"strapi-admin": "3.0.0-beta.19.5",
"strapi-connector-bookshelf": "3.0.0-beta.19.5",
"strapi-connector-mongoose": "3.0.0-beta.19.5",
"strapi-middleware-views": "3.0.0-beta.19.5",
"strapi-plugin-content-manager": "3.0.0-beta.19.5",
"strapi-plugin-content-type-builder": "3.0.0-beta.19.5",
"strapi-plugin-documentation": "3.0.0-beta.19.5",
"strapi-plugin-email": "3.0.0-beta.19.5",
"strapi-plugin-graphql": "3.0.0-beta.19.5",
"strapi-plugin-upload": "3.0.0-beta.19.5",
"strapi-plugin-users-permissions": "3.0.0-beta.19.5",
"strapi-provider-email-mailgun": "3.0.0-beta.19.5",
"strapi-provider-upload-aws-s3": "3.0.0-beta.19.5",
"strapi-provider-upload-cloudinary": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5"
"strapi": "3.0.0-beta.20",
"strapi-admin": "3.0.0-beta.20",
"strapi-connector-bookshelf": "3.0.0-beta.20",
"strapi-connector-mongoose": "3.0.0-beta.20",
"strapi-middleware-views": "3.0.0-beta.20",
"strapi-plugin-content-manager": "3.0.0-beta.20",
"strapi-plugin-content-type-builder": "3.0.0-beta.20",
"strapi-plugin-documentation": "3.0.0-beta.20",
"strapi-plugin-email": "3.0.0-beta.20",
"strapi-plugin-graphql": "3.0.0-beta.20",
"strapi-plugin-upload": "3.0.0-beta.20",
"strapi-plugin-users-permissions": "3.0.0-beta.20",
"strapi-provider-email-mailgun": "3.0.0-beta.20",
"strapi-provider-upload-aws-s3": "3.0.0-beta.20",
"strapi-provider-upload-cloudinary": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20"
},
"strapi": {
"uuid": "getstarted"

View File

@ -1,5 +1,5 @@
{
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"packages": [
"packages/*",
"examples/*"

View File

@ -1,6 +1,6 @@
{
"name": "create-strapi-app",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a new Strapi application.",
"license": "MIT",
"homepage": "http://strapi.io",
@ -21,7 +21,7 @@
],
"dependencies": {
"commander": "^2.20.0",
"strapi-generate-new": "3.0.0-beta.19.5"
"strapi-generate-new": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -60,6 +60,72 @@ exports[`<EventInput /> should match the snapshot 1`] = `
font-size: 1.3rem;
}
.c4 {
cursor: pointer;
margin: 0;
position: relative;
width: 14px;
font-weight: 100;
}
.c4:focus,
.c4:active {
outline: 0;
}
.c4:before {
content: '';
position: absolute;
left: 0;
top: 50%;
margin-top: calc(-14px / 2);
width: 14px;
height: 14px;
border: 1px solid rgba(16,22,34,0.15);
background-color: #fdfdfd;
border-radius: 3px;
box-sizing: border-box;
}
.c4:after {
display: none;
content: '\\f00c';
font-family: 'FontAwesome';
position: absolute;
left: 0px;
top: 55%;
margin-top: calc(-14px / 2);
width: 14px;
height: 14px;
text-align: center;
font-size: 9px;
font-weight: 400;
color: #1C5DE7;
line-height: 14px;
}
.c4:checked:after {
display: block;
}
.c4:after {
content: '\\f068';
display: block;
top: 50%;
font-size: 10px;
}
.c4:disabled {
background-color: #FAFAFB;
cursor: initial;
}
.c4 + label {
display: inline-block;
font-weight: 400;
font-size: 1.3rem;
}
.c1 {
display: -webkit-box;
display: -webkit-flex;
@ -299,8 +365,8 @@ exports[`<EventInput /> should match the snapshot 1`] = `
<input
autoComplete="off"
autoFocus={false}
checked={true}
className="c2"
checked={false}
className="c4"
id="media"
name="media"
onChange={[Function]}
@ -348,7 +414,7 @@ exports[`<EventInput /> should match the snapshot 1`] = `
autoFocus={false}
checked={false}
className="c2"
disabled={true}
disabled={false}
id="media.update"
name="media.update"
onChange={[Function]}

View File

@ -15,7 +15,7 @@ const Wrapper = styled.div`
margin-bottom: 1rem;
}
> p {
width 100%;
width: 100%;
margin-bottom: -8px;
padding-top: 10px;
font-size: 13px;

View File

@ -448,7 +448,7 @@ exports[`<Inputs /> should match the snapshot if type is events 1`] = `
autoFocus={false}
checked={false}
className="c4"
disabled={true}
disabled={false}
id="media.update"
name="media.update"
onChange={[Function]}

View File

@ -22,8 +22,7 @@ const Logout = ({ history: { push } }) => {
push({
pathname: `/plugins/content-manager/collectionType/strapi::administrator/${id}`,
search:
'?redirectUrl=/plugins/content-manager/collectionType/strapi::administrator/&_page=0&_limit=0&_sort=id',
search: '?redirectUrl=/plugins/content-manager/collectionType/strapi::administrator',
});
};
const handleGoToAdministrator = () => {

View File

@ -86,8 +86,7 @@ const Wrapper = styled.div`
}
.bordered {
border-top: 2px solid
${({ withSucessBorder }) => (withSucessBorder ? '#5a9e06' : '#1c5de7')};
border-top: 2px solid ${({ withSuccessBorder }) => (withSuccessBorder ? '#5a9e06' : '#1c5de7')};
}
.borderedSuccess {

View File

@ -3,13 +3,7 @@ import PropTypes from 'prop-types';
import { get, isEmpty, omit, set, upperFirst } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { Link, Redirect } from 'react-router-dom';
import {
auth,
Button,
getQueryParameters,
getYupInnerErrors,
request,
} from 'strapi-helper-plugin';
import { auth, Button, getQueryParameters, getYupInnerErrors, request } from 'strapi-helper-plugin';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import LogoStrapi from '../../assets/images/logo_strapi.png';
import PageTitle from '../../components/PageTitle';
@ -29,9 +23,9 @@ const AuthPage = ({
}) => {
const [reducerState, dispatch] = useReducer(reducer, initialState);
const codeRef = useRef();
const aborController = new AbortController();
const abortController = new AbortController();
const { signal } = aborController;
const { signal } = abortController;
codeRef.current = getQueryParameters(search, 'code');
useEffect(() => {
// Set the reset code provided by the url
@ -49,17 +43,11 @@ const AuthPage = ({
}
return () => {
aborController.abort();
abortController.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [authType, codeRef]);
const {
didCheckErrors,
errors,
modifiedData,
submitSuccess,
userEmail,
} = reducerState.toJS();
const { didCheckErrors, errors, modifiedData, submitSuccess, userEmail } = reducerState.toJS();
const handleChange = ({ target: { name, value } }) => {
dispatch({
type: 'ON_CHANGE',
@ -124,9 +112,7 @@ const AuthPage = ({
} else if (authType === 'forgot-password') {
formErrors = { email: formattedError[0] };
} else {
strapi.notification.error(
get(formattedError, '0.id', 'notification.error')
);
strapi.notification.error(get(formattedError, '0.id', 'notification.error'));
}
}
} catch (err) {
@ -164,7 +150,7 @@ const AuthPage = ({
return (
<>
<PageTitle title={upperFirst(authType)} />
<Wrapper authType={authType} withSucessBorder={submitSuccess}>
<Wrapper authType={authType} withSuccessBorder={submitSuccess}>
<NavTopRightWrapper>
<LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
</NavTopRightWrapper>
@ -177,9 +163,7 @@ const AuthPage = ({
)}
</div>
<div className="headerDescription">
{authType === 'register' && (
<FormattedMessage id="Auth.header.register.description" />
)}
{authType === 'register' && <FormattedMessage id="Auth.header.register.description" />}
</div>
{/* TODO Forgot success style */}
<div className="formContainer bordered">
@ -217,9 +201,7 @@ const AuthPage = ({
})}
<div
className={`${
authType === 'login'
? 'col-6 loginButton'
: 'col-12 buttonContainer'
authType === 'login' ? 'col-6 loginButton' : 'col-12 buttonContainer'
}`}
>
<Button
@ -238,15 +220,9 @@ const AuthPage = ({
</div>
<div className="linkContainer">
{authType !== 'register' && authType !== 'reset-password' && (
<Link
to={`/auth/${
authType === 'login' ? 'forgot-password' : 'login'
}`}
>
<Link to={`/auth/${authType === 'login' ? 'forgot-password' : 'login'}`}>
<FormattedMessage
id={`Auth.link.${
authType === 'login' ? 'forgot-password' : 'ready'
}`}
id={`Auth.link.${authType === 'login' ? 'forgot-password' : 'ready'}`}
/>
</Link>
)}

View File

@ -9,7 +9,7 @@ const Block = styled.div`
padding: 19px 30px 30px 30px;
box-shadow: 0 2px 4px 0 #e3e9f3;
border-radius: 3px;
line-heigth: 18px;
line-height: 18px;
a {
position: relative;
@ -213,12 +213,12 @@ const LinkWrapper = styled.a`
&:first-child {
font-size: 16px;
}
color: #919BAE;
color: #919bae;
text-overflow: ellipsis;
overflow: hidden;
}
.bold {
color: #333740
color: #333740;
font-weight: 600;
}
`;
@ -257,13 +257,4 @@ const SocialLinkWrapper = styled.div`
}
`;
export {
ALink,
Block,
Container,
LinkWrapper,
P,
Separator,
SocialLinkWrapper,
Wave,
};
export { ALink, Block, Container, LinkWrapper, P, Separator, SocialLinkWrapper, Wave };

View File

@ -5,7 +5,7 @@
*
* NOTE: while this component should technically be a stateless functional
* component (SFC), hot reloading does not currently support SFCs. If hot
* reloading is not a neccessity for you then you can refactor it and remove
* reloading is not a necessity for you then you can refactor it and remove
* the linting exception.
*/

View File

@ -1258,7 +1258,6 @@ exports[`Admin | containers | EditView should match the snapshot 1`] = `
<input
autocomplete="off"
class="c18"
disabled=""
id="media.update"
name="media.update"
tabindex="0"

View File

@ -1,6 +1,6 @@
{
"name": "strapi-admin",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Strapi Admin",
"repository": {
"type": "git",
@ -77,7 +77,7 @@
"react-router-dom": "^5.0.0",
"react-transition-group": "^2.9.0",
"react-virtualized": "^9.21.2",
"reactstrap": "^5.0.0",
"reactstrap": "8.4.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",
"redux-saga": "^0.16.0",
@ -85,8 +85,8 @@
"reselect": "^3.0.1",
"sanitize.css": "^4.1.0",
"shelljs": "^0.7.8",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20",
"style-loader": "^0.23.1",
"styled-components": "^5.0.0",
"terser-webpack-plugin": "^1.2.3",
@ -116,4 +116,4 @@
},
"license": "MIT",
"gitHead": "c85658a19b8fef0f3164c19693a45db305dc07a9"
}
}

View File

@ -71,7 +71,10 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
function count(params = {}) {
const { where } = convertRestQueryParams(params);
return model.query(buildQuery({ model, filters: { where } })).count();
return model
.query(buildQuery({ model, filters: { where } }))
.count()
.then(Number);
}
async function create(values, { transacting } = {}) {
@ -187,7 +190,8 @@ module.exports = function createQueryBuilder({ model, modelKey, strapi }) {
.query(qb => {
buildSearchQuery(qb, model, params);
})
.count();
.count()
.then(Number);
}
async function createComponents(entry, values, { transacting }) {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-connector-bookshelf",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Bookshelf hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -22,7 +22,7 @@
"lodash": "^4.17.11",
"pluralize": "^7.0.0",
"rimraf": "3.0.0",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"peerDependencies": {
"knex": "^0.20.0"

View File

@ -316,7 +316,7 @@ module.exports = ({ model, modelKey, strapi }) => {
// verify the provided ids are related to this entity.
idsToKeep.forEach(id => {
if (allIds.findIndex(currentId => currentId.toString() === id) === -1) {
if (allIds.findIndex(currentId => currentId.toString() === id.toString()) === -1) {
const err = new Error(
`Some of the provided components in ${key} are not related to the entity`
);

View File

@ -1,6 +1,6 @@
{
"name": "strapi-connector-mongoose",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Mongoose hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -20,7 +20,7 @@
"mongoose-float": "^1.0.4",
"mongoose-long": "^0.2.1",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-database",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Strapi's database layer",
"homepage": "http://strapi.io",
"main": "./lib/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-api",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate an API for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
"dependencies": {
"lodash": "^4.17.11",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-controller",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a controller for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
},
"dependencies": {
"lodash": "^4.17.11",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-model",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a model for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [
@ -16,7 +16,7 @@
"dependencies": {
"lodash": "^4.17.11",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -12,10 +12,7 @@ const { trackUsage, captureStderr } = require('./utils/usage');
const packageJSON = require('./resources/json/package.json');
const databaseJSON = require('./resources/json/database.json.js');
module.exports = async function createProject(
scope,
{ connection, dependencies }
) {
module.exports = async function createProject(scope, { connection, dependencies }) {
console.log('Creating files.');
const { rootPath } = scope;
@ -29,13 +26,12 @@ module.exports = async function createProject(
const dotFiles = await fse.readdir(join(resources, 'dot-files'));
await Promise.all(
dotFiles.map(name => {
return fse.copy(
join(resources, 'dot-files', name),
join(rootPath, `.${name}`)
);
return fse.copy(join(resources, 'dot-files', name), join(rootPath, `.${name}`));
})
);
await trackUsage({ event: 'didCopyProjectFiles', scope });
// copy templates
await fse.writeJSON(
join(rootPath, 'package.json'),
@ -51,6 +47,8 @@ module.exports = async function createProject(
}
);
await trackUsage({ event: 'didWritePackageJSON', scope });
// ensure node_modules is created
await fse.ensureDir(join(rootPath, 'node_modules'));
@ -66,11 +64,15 @@ module.exports = async function createProject(
);
})
);
await trackUsage({ event: 'didCopyConfigurationFiles', scope });
} catch (err) {
await fse.remove(scope.rootPath);
throw err;
}
await trackUsage({ event: 'willInstallProjectDependencies', scope });
const installPrefix = chalk.yellow('Installing dependencies:');
const loader = ora(installPrefix).start();
@ -93,6 +95,8 @@ module.exports = async function createProject(
loader.stop();
console.log(`Dependencies installed ${chalk.green('successfully')}.`);
await trackUsage({ event: 'didInstallProjectDependencies', scope });
} catch (error) {
loader.stop();
await trackUsage({
@ -119,9 +123,7 @@ module.exports = async function createProject(
);
console.log();
console.log(
`cd ${chalk.green(rootPath)} && ${chalk.cyan(
scope.useYarn ? 'yarn' : 'npm'
)} install`
`cd ${chalk.green(rootPath)} && ${chalk.cyan(scope.useYarn ? 'yarn' : 'npm')} install`
);
console.log();

View File

@ -71,6 +71,7 @@ function trackError({ scope, error }) {
version: scope.strapiVersion,
nodeVersion: process.version,
docker: scope.docker,
useYarn: scope.useYarn,
},
});
} catch (err) {
@ -92,6 +93,7 @@ function trackUsage({ event, scope, error }) {
node_version: process.version,
version: scope.strapiVersion,
docker: scope.docker,
useYarn: scope.useYarn,
},
});
} catch (err) {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-new",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a new Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-plugin",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate an plugin for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
"dependencies": {
"fs-extra": "^8.0.1",
"lodash": "^4.17.11",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-policy",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a policy for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
},
"dependencies": {
"lodash": "^4.17.11",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-service",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Generate a service for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
},
"dependencies": {
"lodash": "^4.17.11",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Master of ceremonies for the Strapi generators.",
"homepage": "http://strapi.io",
"keywords": [
@ -20,7 +20,7 @@
"fs-extra": "^8.0.1",
"lodash": "^4.17.11",
"reportback": "^2.0.2",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-helper-plugin",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Helper for Strapi plugins development",
"files": [
"dist"
@ -62,7 +62,7 @@
"react-intl": "^2.8.0",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"reactstrap": "^8.0.1",
"reactstrap": "8.4.1",
"styled-components": "^5.0.0",
"whatwg-fetch": "^2.0.3"
},

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { withRouter } from 'react-router';
import PropTypes from 'prop-types';
import { get, isEmpty, isNull, isObject, toLower, toString } from 'lodash';
import moment from 'moment';
import { dateToUtcTime, IcoContainer, useGlobalContext } from 'strapi-helper-plugin';
import { IcoContainer, useGlobalContext } from 'strapi-helper-plugin';
import useListView from '../../hooks/useListView';
import dateFormats from '../../utils/dateFormats';
import CustomInputCheckbox from '../CustomInputCheckbox';
@ -37,7 +37,7 @@ const getDisplayedValue = (type, value, name) => {
const date =
value && isObject(value) && value._isAMomentObject === true ? JSON.stringify(value) : value;
return dateToUtcTime(date).format(dateFormats[type]);
return moment(date).format(dateFormats[type]);
}
case 'password':
return '••••••••';

View File

@ -46,7 +46,7 @@ const getInputType = (type = '') => {
case 'uid':
return 'uid';
default:
return 'text';
return type || 'text';
}
};

View File

@ -5,6 +5,7 @@ import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import pluginId from '../../pluginId';
import IconRemove from '../../assets/images/icon_remove.svg';
import { Span } from './components';
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
@ -18,7 +19,7 @@ const Relation = ({ data, mainField, onRemove, to }) => {
<FormattedMessage id={`${pluginId}.containers.Edit.clickToJump`}>
{title => (
<Link to={to} title={title}>
<span>{data[mainField]}</span>
<Span>{data[mainField]}</Span>
</Link>
)}
</FormattedMessage>

View File

@ -97,10 +97,8 @@ const Li = styled.li`
> a:hover {
text-decoration: none;
}
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
> a {
max-width: 100%;
}
&:first-of-type {
@ -145,4 +143,12 @@ const Li = styled.li`
}
`;
export { ListShadow, ListWrapper, Li };
const Span = styled.span`
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export { ListShadow, ListWrapper, Li, Span };

View File

@ -23,6 +23,7 @@ function SelectMany({
onRemove,
options,
placeholder,
styles,
targetModel,
value,
}) {
@ -62,8 +63,7 @@ function SelectMany({
id={name}
filterOption={(candidate, input) => {
if (!isEmpty(value)) {
const isSelected =
value.findIndex(item => item.id === candidate.value.id) !== -1;
const isSelected = value.findIndex(item => item.id === candidate.value.id) !== -1;
if (isSelected) {
return false;
@ -85,6 +85,7 @@ function SelectMany({
onMenuClose={onMenuClose}
onMenuScrollToBottom={onMenuScrollToBottom}
placeholder={placeholder}
styles={styles}
value={[]}
/>
@ -130,6 +131,7 @@ SelectMany.propTypes = {
onRemove: PropTypes.func.isRequired,
options: PropTypes.array.isRequired,
placeholder: PropTypes.node.isRequired,
styles: PropTypes.object.isRequired,
targetModel: PropTypes.string.isRequired,
value: PropTypes.array,
};

View File

@ -15,6 +15,7 @@ function SelectOne({
onMenuScrollToBottom,
options,
placeholder,
styles,
value,
}) {
return (
@ -29,9 +30,8 @@ function SelectOne({
onMenuClose={onMenuClose}
onMenuScrollToBottom={onMenuScrollToBottom}
placeholder={placeholder}
value={
isNull(value) ? null : { label: get(value, [mainField], ''), value }
}
styles={styles}
value={isNull(value) ? null : { label: get(value, [mainField], ''), value }}
/>
);
}
@ -51,6 +51,7 @@ SelectOne.propTypes = {
onMenuScrollToBottom: PropTypes.func.isRequired,
options: PropTypes.array.isRequired,
placeholder: PropTypes.node.isRequired,
styles: PropTypes.object.isRequired,
value: PropTypes.object,
};

View File

@ -155,6 +155,18 @@ function SelectWrapper({
const Component = isSingle ? SelectOne : SelectMany;
const associationsLength = isArray(value) ? value.length : 0;
const customStyles = {
option: provided => {
return {
...provided,
maxWidth: '100% !important',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
},
};
return (
<Wrapper className="form-group">
<Nav>
@ -198,6 +210,7 @@ function SelectWrapper({
placeholder
)
}
styles={customStyles}
targetModel={targetModel}
value={value}
/>

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-manager",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "A powerful UI to easily manage your data.",
"strapi": {
"name": "Content Manager",
@ -25,13 +25,13 @@
"react-router-dom": "^5.0.0",
"react-select": "^3.0.4",
"react-transition-group": "^2.5.0",
"reactstrap": "^5.0.0",
"reactstrap": "8.4.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",
"reselect": "^3.0.1",
"showdown": "^1.9.0",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20",
"yup": "^0.27.0"
},
"author": {

View File

@ -1,4 +1,5 @@
import React, { createRef, useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { useClickAwayListener } from '@buffetjs/hooks';
import { Label, ErrorMessage } from '@buffetjs/styles';
import { AutoSizer, Collection } from 'react-virtualized';
import PropTypes from 'prop-types';
@ -21,13 +22,18 @@ const ComponentIconPicker = ({ error, isCreating, label, name, onChange, value }
// Edition
return !allComponentsIconAlreadyTaken.filter(icon => icon !== originalIcon).includes(ico);
});
const ref = createRef();
const ref = useRef();
const searchWrapperRef = useRef();
const [originalIcon] = useState(value);
const [showSearch, setShowSearch] = useState(false);
const [search, setSearch] = useState('');
const [icons, setIcons] = useState(initialIcons);
const toggleSearch = () => setShowSearch(prev => !prev);
useClickAwayListener(searchWrapperRef, () => {
setShowSearch(false);
});
useEffect(() => {
if (showSearch && ref.current) {
ref.current.focus();
@ -58,6 +64,11 @@ const ComponentIconPicker = ({ error, isCreating, label, name, onChange, value }
};
};
const handleChangeSearch = ({ target: { value } }) => {
setSearch(value);
setIcons(() => initialIcons.filter(icon => icon.includes(value)));
};
return (
<Wrapper error={error !== null}>
<div className="search">
@ -69,18 +80,10 @@ const ComponentIconPicker = ({ error, isCreating, label, name, onChange, value }
<FontAwesomeIcon icon="search" />
</button>
) : (
<SearchWrapper>
<SearchWrapper ref={searchWrapperRef}>
<FontAwesomeIcon icon="search" />
<button onClick={toggleSearch} type="button" />
<Search
ref={ref}
onChange={({ target: { value } }) => {
setSearch(value);
setIcons(() => initialIcons.filter(icon => icon.includes(value)));
}}
value={search}
placeholder="Search…"
/>
<Search ref={ref} onChange={handleChangeSearch} value={search} placeholder="Search…" />
<button
onClick={() => {
setSearch('');

View File

@ -503,6 +503,8 @@ const forms = {
];
items = uidItems;
} else if (type === 'json') {
items.splice(0, 1);
}
if (!ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS.includes(type)) {

View File

@ -1,6 +1,7 @@
'use strict';
const yup = require('yup');
const _ = require('lodash');
const validators = {
required: yup.boolean(),
@ -80,11 +81,34 @@ const isValidRegExpPattern = {
test: val => val === '' || new RegExp(val),
};
const isValidDefaultJSON = {
name: 'isValidDefaultJSON',
message: '${path} is not a valid JSON',
test: val => {
if (val === undefined) {
return true;
}
if (_.isNumber(val) || _.isNull(val) || _.isObject(val) || _.isArray(val)) {
return true;
}
try {
JSON.parse(val);
return true;
} catch (err) {
return false;
}
},
};
module.exports = {
validators,
areEnumValuesUnique,
isValidCollectionName,
isValidCategoryName,
isValidDefaultJSON,
isValidName,
isValidIcon,
isValidKey,

View File

@ -6,6 +6,7 @@ const yup = require('yup');
const {
validators,
areEnumValuesUnique,
isValidDefaultJSON,
isValidName,
isValidEnum,
isValidUID,
@ -112,6 +113,7 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => {
}
case 'json': {
return {
default: yup.mixed().test(isValidDefaultJSON),
required: validators.required,
unique: validators.unique,
};

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-type-builder",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Strapi plugin to create content type (API).",
"strapi": {
"name": "Content Type Builder",
@ -22,14 +22,14 @@
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-transition-group": "^2.5.0",
"reactstrap": "^5.0.0",
"reactstrap": "8.4.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",
"reselect": "^3.0.1",
"strapi-generate": "3.0.0-beta.19.5",
"strapi-generate-api": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5",
"strapi-generate": "3.0.0-beta.20",
"strapi-generate-api": "3.0.0-beta.20",
"strapi-helper-plugin": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20",
"yup": "^0.27.0"
},
"author": {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-documentation",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Documentation",
@ -28,11 +28,11 @@
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-transition-group": "^4.0.1",
"reactstrap": "^8.0.0",
"reactstrap": "8.4.1",
"redux": "^4.0.1",
"redux-immutable": "^4.0.0",
"reselect": "^4.0.0",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.20",
"swagger-ui-dist": "3.24.3"
},
"author": {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-email",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Email",
@ -13,13 +13,13 @@
},
"dependencies": {
"lodash": "^4.17.11",
"strapi-provider-email-sendmail": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-provider-email-sendmail": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20"
},
"devDependencies": {
"react-copy-to-clipboard": "5.0.1",
"rimraf": "3.0.0",
"strapi-helper-plugin": "3.0.0-beta.19.5"
"strapi-helper-plugin": "3.0.0-beta.20"
},
"author": {
"name": "Strapi team",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-graphql",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "graphql",
@ -24,7 +24,7 @@
"koa-compose": "^4.1.0",
"lodash": "4.17.11",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-utils": "3.0.0-beta.20"
},
"devDependencies": {
"cross-env": "^5.2.0",

View File

@ -44,7 +44,7 @@ const Card = ({
: getFileExtension(ext);
const handleClick = () => {
if (!isDisabled) {
if (!isDisabled || checked) {
onClick(id);
}
};

View File

@ -8,7 +8,7 @@ import Flex from '../Flex';
import Chevron from './Chevron';
const InputFilePreview = ({ file, onClick, isSlider }) => {
const fileUrl = prefixFileUrlWithBackendUrl(get(file, ['formats', 'thumbnail', 'url'], file.url));
const fileUrl = prefixFileUrlWithBackendUrl(get(file, ['formats', 'small', 'url'], file.url));
return (
<Flex

View File

@ -57,7 +57,7 @@ const List = ({
>
{(checked || canSelect) && (
<>
{isAllowed && (
{(checked || isAllowed) && (
<CardControlsWrapper leftAlign className="card-control-wrapper">
<Checkbox
name={`${id}`}

View File

@ -5,9 +5,11 @@ import {
createNewFilesToDownloadArray,
createNewFilesToUploadArray,
formatFileForEditing,
getType,
} from '../../utils';
const initialState = {
allowedTypes: [],
selectedFiles: [],
files: [],
filesToUpload: [],
@ -264,17 +266,21 @@ const reducer = (state, action) =>
break;
}
case 'TOGGLE_SELECT_ALL': {
const allowedFiles =
state.allowedTypes.length > 0
? state.files.filter(file => state.allowedTypes.includes(getType(file.mime)))
: state.files;
const comparator = (first, second) => first.id === second.id;
const isSelected =
intersectionWith(state.selectedFiles, state.files, comparator).length ===
state.files.length;
intersectionWith(state.selectedFiles, allowedFiles, comparator).length ===
allowedFiles.length;
if (isSelected) {
draftState.selectedFiles = differenceWith(state.selectedFiles, state.files, comparator);
break;
}
draftState.selectedFiles = unionWith(state.selectedFiles, state.files, comparator);
draftState.selectedFiles = unionWith(state.selectedFiles, allowedFiles, comparator);
break;
}

View File

@ -725,6 +725,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: [],
selectedFiles: [],
files: [
{
@ -750,6 +751,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
],
};
const expected = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -802,6 +804,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -848,6 +851,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
],
};
const expected = {
allowedTypes: [],
selectedFiles: [],
files: [
{
@ -879,6 +883,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -915,6 +920,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
],
};
const expected = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -967,6 +973,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -1013,6 +1020,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
],
};
const expected = {
allowedTypes: [],
selectedFiles: [
{
id: 1,
@ -1080,11 +1088,12 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
};
expect(reducer(state, action)).toEqual(expected);
});
it('should deselect all files of the current page only', () => {
it('should deselect all files of the current page only event if not allowed', () => {
const action = {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: ['video'],
selectedFiles: [
{
id: 1,
@ -1151,6 +1160,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
],
};
const expected = {
allowedTypes: ['video'],
selectedFiles: [
{
id: 1,
@ -1198,6 +1208,135 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
};
expect(reducer(state, action)).toEqual(expected);
});
it('should select the allowed files', () => {
const action = {
type: 'TOGGLE_SELECT_ALL',
};
const state = {
allowedTypes: ['image', 'file'],
selectedFiles: [],
files: [
{
id: 1,
name: 'myImage',
ext: '.png',
mime: 'image/png',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 2,
name: 'myFile',
ext: '.png',
mime: 'application/pdf',
size: 2.24,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 3,
name: 'myVideo',
ext: '.png',
mime: 'video/mp4',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 4,
name: 'mySecondImage',
ext: '.png',
mime: 'image/jpg',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
],
};
const expected = {
selectedFiles: [
{
id: 1,
name: 'myImage',
ext: '.png',
mime: 'image/png',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 2,
name: 'myFile',
ext: '.png',
mime: 'application/pdf',
size: 2.24,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 4,
name: 'mySecondImage',
ext: '.png',
mime: 'image/jpg',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
],
allowedTypes: ['image', 'file'],
files: [
{
id: 1,
name: 'myImage',
ext: '.png',
mime: 'image/png',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 2,
name: 'myFile',
ext: '.png',
mime: 'application/pdf',
size: 2.24,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 3,
name: 'myVideo',
ext: '.png',
mime: 'video/mp4',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
{
id: 4,
name: 'mySecondImage',
ext: '.png',
mime: 'image/jpg',
size: 146.25,
url: '/uploads/ba0c3352c4b14132aed3fcf3110b481c.png',
created_at: '2020-03-04T09:45:32.444Z',
updated_at: '2020-03-04T09:45:32.444Z',
},
],
};
expect(reducer(state, action)).toEqual(expected);
});
});
describe('GO_TO', () => {
@ -1574,6 +1713,7 @@ describe('UPLOAD | containers | ModalStepper | reducer', () => {
const action = { type: 'RESET_PROPS' };
const state = { test: true };
const expected = {
allowedTypes: [],
selectedFiles: [],
files: [],
filesToDownload: [],

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-upload",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Media Library",
@ -31,11 +31,11 @@
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-transition-group": "^2.5.0",
"reactstrap": "^5.0.0",
"reactstrap": "8.4.1",
"sharp": "0.24.1",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-provider-upload-local": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.20",
"strapi-provider-upload-local": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20",
"stream-to-array": "^2.3.0",
"uuid": "^3.2.1"
},

View File

@ -33,7 +33,7 @@ const combineFilters = params => {
module.exports = {
formatFileInfo({ filename, type, size }, fileInfo = {}, metas = {}) {
const ext = '.' + mime.extension(type);
const ext = '.' + mime.extension(type) || path.extname(filename);
const baseName = path.basename(filename, path.extname(filename));
const usedName = fileInfo.name || baseName;

View File

@ -249,6 +249,7 @@ module.exports = {
await strapi.plugins['users-permissions'].controllers.auth.emailConfirmation(
context,
null,
true
);
let output = context.body.toJSON ? context.body.toJSON() : context.body;

View File

@ -568,7 +568,7 @@ module.exports = {
}
},
async emailConfirmation(ctx, returnUser) {
async emailConfirmation(ctx, next, returnUser) {
const params = ctx.query;
const decodedToken = await strapi.plugins['users-permissions'].services.jwt.verify(

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-users-permissions",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Protect your API with a full-authentication process based on JWT",
"strapi": {
"name": "Roles & Permissions",
@ -29,11 +29,11 @@
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0",
"react-transition-group": "^2.5.0",
"reactstrap": "^5.0.0",
"reactstrap": "8.4.1",
"redux-saga": "^0.16.0",
"request": "^2.83.0",
"strapi-helper-plugin": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5",
"strapi-helper-plugin": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20",
"uuid": "^3.1.0"
},
"devDependencies": {

View File

@ -9,6 +9,34 @@ const request = require('request');
* @description: A set of functions similar to controller's actions to avoid code duplication.
*/
const DEFAULT_PERMISSIONS = [
{ action: 'admincallback', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{ action: 'adminregister', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{ action: 'callback', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{ action: 'connect', controller: 'auth', type: 'users-permissions', roleType: null },
{ action: 'forgotpassword', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{ action: 'register', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{
action: 'emailconfirmation',
controller: 'auth',
type: 'users-permissions',
roleType: 'public',
},
{ action: 'changepassword', controller: 'auth', type: 'users-permissions', roleType: 'public' },
{ action: 'init', controller: 'userspermissions', type: null, roleType: null },
{ action: 'me', controller: 'user', type: 'users-permissions', roleType: null },
{ action: 'autoreload', controller: null, type: null, roleType: null },
];
const isPermissionEnabled = (permission, role) =>
DEFAULT_PERMISSIONS.some(
defaultPerm =>
(defaultPerm.action === null || permission.action === defaultPerm.action) &&
(defaultPerm.controller === null || permission.controller === defaultPerm.controller) &&
(defaultPerm.type === null || permission.type === defaultPerm.type) &&
(defaultPerm.roleType === null || role.type === defaultPerm.roleType)
);
module.exports = {
async createRole(params) {
if (!params.type) {
@ -228,16 +256,17 @@ module.exports = {
},
async updatePermissions() {
// fetch all the current permissions from the database, and format them into an array of actions.
const databasePermissions = await strapi
const { primaryKey } = strapi.query('permission', 'users-permissions');
const roles = await strapi.query('role', 'users-permissions').find({}, []);
const rolesMap = roles.reduce((map, role) => ({ ...map, [role[primaryKey]]: role }), {});
const dbPermissions = await strapi
.query('permission', 'users-permissions')
.find({ _limit: -1 });
const actionsMap = databasePermissions.reduce((acc, permission) => {
acc[`${permission.type}.${permission.controller}.${permission.action}`] = permission.id;
return acc;
}, {});
const stringActions = Object.keys(actionsMap);
let permissionsFoundInDB = dbPermissions.map(
p => `${p.type}.${p.controller}.${p.action}.${p.role[primaryKey]}`
);
permissionsFoundInDB = _.uniq(permissionsFoundInDB);
// Aggregate first level actions.
const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => {
@ -265,170 +294,70 @@ module.exports = {
return acc;
}, []);
// Merge array into one.
const currentActions = appActions.concat(pluginsActions);
// Count permissions available.
const permissions = databasePermissions.length;
const actionsFoundInFiles = appActions.concat(pluginsActions);
// create permissions for each role
let permissionsFoundInFiles = actionsFoundInFiles.reduce(
(acc, action) => [...acc, ...roles.map(role => `${action}.${role[primaryKey]}`)],
[]
);
permissionsFoundInFiles = _.uniq(permissionsFoundInFiles);
// Compare to know if actions have been added or removed from controllers.
if (!_.isEqual(stringActions, currentActions) || permissions < 1) {
if (!_.isEqual(permissionsFoundInDB.sort(), permissionsFoundInFiles.sort())) {
const splitted = str => {
const [type, controller, action] = str.split('.');
const [type, controller, action, roleId] = str.split('.');
return { type, controller, action };
return { type, controller, action, roleId };
};
const defaultPolicy = (obj, role) => {
const isAdminCallback =
obj.action === 'admincallback' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isAdminRegister =
obj.action === 'adminregister' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isCallback =
obj.action === 'callback' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isConnect =
obj.action === 'connect' && obj.controller === 'auth' && obj.type === 'users-permissions';
const isPassword =
obj.action === 'forgotpassword' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isRegister =
obj.action === 'register' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isConfirmation =
obj.action === 'emailconfirmation' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isNewPassword =
obj.action === 'changepassword' &&
obj.controller === 'auth' &&
obj.type === 'users-permissions' &&
role.type === 'public';
const isInit = obj.action === 'init' && obj.controller === 'userspermissions';
const isMe =
obj.action === 'me' && obj.controller === 'user' && obj.type === 'users-permissions';
const isReload = obj.action === 'autoreload';
const enabled =
isCallback ||
isRegister ||
isInit ||
isPassword ||
isNewPassword ||
isMe ||
isReload ||
isConnect ||
isConfirmation ||
isAdminCallback ||
isAdminRegister;
return Object.assign(obj, { enabled, policy: '' });
};
// Retrieve roles
const roles = await strapi.query('role', 'users-permissions').find({}, []);
// We have to know the difference to add or remove
// the permissions entries in the database.
const toRemove = _.difference(stringActions, currentActions).map(splitted);
const toAdd = (permissions < 1
? currentActions
: _.difference(currentActions, stringActions)
).map(splitted);
// We have to know the difference to add or remove the permissions entries in the database.
const toRemove = _.difference(permissionsFoundInDB, permissionsFoundInFiles).map(splitted);
const toAdd = _.difference(permissionsFoundInFiles, permissionsFoundInDB).map(splitted);
const query = strapi.query('permission', 'users-permissions');
const createActions = role =>
Promise.all(
toAdd.map(action => {
const data = {
...defaultPolicy(action, role),
role: role.id,
};
return query.create(data);
})
);
// Execute request to update entries in database for each role.
await Promise.all([
Promise.all(roles.map(createActions)),
Promise.all(toRemove.map(action => query.delete(action))),
Promise.all(
toAdd.map(permission =>
query.create({
type: permission.type,
controller: permission.controller,
action: permission.action,
enabled: isPermissionEnabled(permission, rolesMap[permission.roleId]),
policy: '',
role: permission.roleId,
})
)
),
Promise.all(
toRemove.map(permission => {
const { type, controller, action, roleId: role } = permission;
return query.delete({ type, controller, action, role });
})
),
]);
}
},
async removeDuplicate() {
const { primaryKey } = strapi.query('permission', 'users-permissions');
// Retrieve permissions by creation date (ID or ObjectID).
const permissions = await strapi
.query('permission', 'users-permissions')
.find({ _sort: `${primaryKey}`, _limit: -1 });
const value = permissions.reduce(
(acc, permission) => {
const key = `${permission.type}.controllers.${permission.controller}.${permission.action}.${permission.role[primaryKey]}`;
const index = acc.toKeep.findIndex(element => element === key);
if (index === -1) {
acc.toKeep.push(key);
} else {
acc.toRemove.push(permission[primaryKey]);
}
return acc;
},
{
toKeep: [],
toRemove: [],
}
);
if (value.toRemove.length > 0) {
return strapi.query('permission', 'users-permissions').delete({
[`${primaryKey}_in`]: value.toRemove,
});
}
return Promise.resolve();
},
async initialize() {
const roleCount = await strapi.query('role', 'users-permissions').count();
// It has already been initialized.
if (roleCount > 0) {
await this.updatePermissions();
await this.removeDuplicate();
return;
if (roleCount === 0) {
await strapi.query('role', 'users-permissions').create({
name: 'Authenticated',
description: 'Default role given to authenticated user.',
type: 'authenticated',
});
await strapi.query('role', 'users-permissions').create({
name: 'Public',
description: 'Default role given to unauthenticated user.',
type: 'public',
});
}
// Create two first default roles.
await strapi.query('role', 'users-permissions').create({
name: 'Authenticated',
description: 'Default role given to authenticated user.',
type: 'authenticated',
});
await strapi.query('role', 'users-permissions').create({
name: 'Public',
description: 'Default role given to unauthenticated user.',
type: 'public',
});
return this.updatePermissions();
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-amazon-ses",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Amazon SES provider for strapi email",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
"main": "./lib",
"dependencies": {
"lodash": "^4.17.11",
"node-ses": "^2.2.0"
"node-ses": "^3.0.0"
},
"author": {
"email": "nikolay@tsenkov.net",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-email-mailgun",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.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-beta.19.5",
"version": "3.0.0-beta.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-beta.19.5",
"version": "3.0.0-beta.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-beta.19.5",
"version": "3.0.0-beta.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-beta.19.5",
"version": "3.0.0-beta.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-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Local provider for strapi upload",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -29,7 +29,7 @@ module.exports = {
const readStream = streamifier.createReadStream(file.buffer);
const writeStream = client.upload({
...options,
remote: file.name,
remote: file.hash,
contentType: file.mime,
});
@ -38,22 +38,21 @@ module.exports = {
writeStream.on('error', error => error && reject(error));
writeStream.on('success', result => {
remoteURL()
.then(data =>
.then(data => {
resolve(
Object.assign(file, {
name: result.name,
mime: result.contentType,
url: `${data.cdnSslUri}/${result.name}`,
})
)
)
);
})
.catch(err => console.error(err) && reject(err));
});
});
},
delete(file) {
return new Promise((resolve, reject) => {
client.removeFile(config.container, file.name, error => {
client.removeFile(config.container, file.hash, error => {
if (error) return reject(error);
return resolve();
});

View File

@ -1,6 +1,6 @@
{
"name": "strapi-provider-upload-rackspace",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "Rackspace provider for strapi upload",
"main": "./lib",
"keywords": [],

View File

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

View File

@ -154,8 +154,6 @@ describe('Deep Filtering API', () => {
},
});
console.log('res', JSON.stringify(res.body, null, 2));
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBe(1);
expect(res.body[0]).toMatchObject(data.collectors[1]);

View File

@ -1,6 +1,6 @@
{
"name": "strapi",
"version": "3.0.0-beta.19.5",
"version": "3.0.0-beta.20",
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MongoDB, MySQL, MariaDB, PostgreSQL, SQLite",
"homepage": "http://strapi.io",
"directories": {
@ -52,16 +52,16 @@
"resolve-cwd": "^3.0.0",
"rimraf": "^2.6.2",
"shelljs": "^0.8.3",
"strapi-database": "3.0.0-beta.19.5",
"strapi-generate": "3.0.0-beta.19.5",
"strapi-generate-api": "3.0.0-beta.19.5",
"strapi-generate-controller": "3.0.0-beta.19.5",
"strapi-generate-model": "3.0.0-beta.19.5",
"strapi-generate-new": "3.0.0-beta.19.5",
"strapi-generate-plugin": "3.0.0-beta.19.5",
"strapi-generate-policy": "3.0.0-beta.19.5",
"strapi-generate-service": "3.0.0-beta.19.5",
"strapi-utils": "3.0.0-beta.19.5"
"strapi-database": "3.0.0-beta.20",
"strapi-generate": "3.0.0-beta.20",
"strapi-generate-api": "3.0.0-beta.20",
"strapi-generate-controller": "3.0.0-beta.20",
"strapi-generate-model": "3.0.0-beta.20",
"strapi-generate-new": "3.0.0-beta.20",
"strapi-generate-plugin": "3.0.0-beta.20",
"strapi-generate-policy": "3.0.0-beta.20",
"strapi-generate-service": "3.0.0-beta.20",
"strapi-utils": "3.0.0-beta.20"
},
"scripts": {
"postinstall": "node lib/utils/success.js"

View File

@ -4046,7 +4046,7 @@ aws-sign2@~0.7.0:
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.6.0, aws4@^1.8.0:
aws4@1.9.1, aws4@^1.6.0, aws4@^1.8.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
@ -11352,11 +11352,6 @@ lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
lodash.isfunction@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
@ -11372,11 +11367,6 @@ lodash.isnumber@^3.0.3:
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isobject@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=
lodash.isplainobject@4.0.6, lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@ -11432,11 +11422,6 @@ lodash.throttle@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
lodash.tonumber@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash.tonumber/-/lodash.tonumber-4.0.3.tgz#0b96b31b35672793eb7f5a63ee791f1b9e9025d9"
integrity sha1-C5azGzVnJ5Prf1pj7nkfG56QJdk=
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@ -12504,15 +12489,14 @@ node-schedule@1.3.2:
long-timeout "0.1.1"
sorted-array-functions "^1.0.0"
node-ses@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/node-ses/-/node-ses-2.2.1.tgz#41b113b2a4561fc0e9ede1f72705821a172b73b0"
integrity sha512-+vfOKdvb99LgbEKmL7rTw9GbNZ71fetJ4vseYsoHkfdzRXWsBalepf7LEn5Wsg/swoLM3dzxvWfOigm9KtkYgQ==
node-ses@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/node-ses/-/node-ses-3.0.0.tgz#c228e114a434974750582721f9a120ebc3f090a7"
integrity sha512-8YJ0Gxgk77ZaOWQRqasZOyxIAOIV8T66toF0uxFEMjP/LFK6ZFxWszQoTME0tvDL0HIjk8Jx38aXYfS2HneK2Q==
dependencies:
aws4 "^1.8.0"
aws4 "1.9.1"
debug "^2.6.9"
request "^2.88.0"
xml2js "^0.4.19"
request "2.88.2"
nodemailer-fetch@1.6.0:
version "1.6.0"
@ -13615,7 +13599,7 @@ pn@^1.1.0:
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
popper.js@^1.12.9, popper.js@^1.14.4:
popper.js@^1.14.4:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
@ -14638,14 +14622,6 @@ react-outside-click-handler@^1.2.4:
object.values "^1.1.0"
prop-types "^15.7.2"
react-popper@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.8.3.tgz#0f73333137c9fb0af6ec4074d2d0585a0a0461e1"
integrity sha1-D3MzMTfJ+wr27EB00tBYWgoEYeE=
dependencies:
popper.js "^1.12.9"
prop-types "^15.6.0"
react-popper@^1.3.6:
version "1.3.7"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
@ -14823,20 +14799,7 @@ react@^16.9.0:
object-assign "^4.1.1"
prop-types "^15.6.2"
reactstrap@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-5.0.0.tgz#d8948534df4816eddfb162a51643a499388e9228"
integrity sha512-y0eju/LAK7gbEaTFfq2iW92MF7/5Qh0tc1LgYr2mg92IX8NodGc03a+I+cp7bJ0VXHAiLy0bFL9UP89oSm4cBg==
dependencies:
classnames "^2.2.3"
lodash.isfunction "^3.0.9"
lodash.isobject "^3.0.2"
lodash.tonumber "^4.0.3"
prop-types "^15.5.8"
react-popper "^0.8.3"
react-transition-group "^2.2.1"
reactstrap@^8.0.0, reactstrap@^8.0.1:
reactstrap@8.4.1:
version "8.4.1"
resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-8.4.1.tgz#c7f63b9057f58b52833061711ebe235b9ec4e3e5"
integrity sha512-oAjp9PYYUGKl7SLXwrQ1oRIrYw0MqfO2mUqYgGapFKHG2uwjEtLip5rYxtMujkGx3COjH5FX1WtcfNU4oqpH0Q==
@ -15347,7 +15310,7 @@ request@2.87.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@^2.74.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
request@2.88.2, request@^2.74.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==