Merge branch 'develop' into features/media-lib

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-03-31 11:58:31 +02:00
commit 087920fb47
14 changed files with 237 additions and 126 deletions

View File

@ -79,16 +79,9 @@ cd strapi && yarn setup
#### 4. Start the example application
**Go to the getstarted application**
Read the `getstarted` application README [here](./examples/getstarted/README.md).
```bash
cd strapi/examples/getstarted
yarn develop
```
The server (API) is available at http://localhost:1337
The built administration panel is available at http://localhost:1337/admin
#### 5. Running the administration panel in development mode
**Start the administration panel server for development**

View File

@ -1,17 +0,0 @@
MAKEFLAGS = -j1
export NODE_ENV = test
.PHONY: test
lint:
./node_modules/.bin/eslint **/*.js
test: lint
./scripts/test.sh
docs:
mkdocs build --clean
setup:
./scripts/setup.sh

45
docker-compose.dev.yml Normal file
View File

@ -0,0 +1,45 @@
version: '3'
services:
postgres:
image: postgres
restart: always
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi
POSTGRES_DB: strapi
ports:
- '5432:5432'
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: strapi
volumes:
- mongodata:/data/db
ports:
- '27017:27017'
mysql:
image: mysql
restart: always
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_DATABASE: strapi
MYSQL_USER: strapi
MYSQL_PASSWORD: strapi
MYSQL_ROOT_HOST: '%'
MYSQL_ROOT_PASSWORD: strapi
volumes:
- mysqldata:/var/lib/mysql
ports:
- '3306:3306'
volumes:
pgdata:
mongodata:
mysqldata:

View File

@ -364,7 +364,8 @@ A `product` can be related to many `categories`, so a `category` can have many `
"categories": {
"collection": "category",
"via": "products",
"dominant": true
"dominant": true,
"collectionName": "products_categories__categories_products" // optional
}
}
}
@ -373,6 +374,9 @@ A `product` can be related to many `categories`, so a `category` can have many `
**NOTE**:
(NoSQL databases only) The `dominant` key defines which table/collection should store the array that defines the relationship. Because there are no join tables in NoSQL, this key is required for NoSQL databases (e.g. MongoDB).
**NOTE**:
(NoSQL databases only) The `collectionName` key defines the name of the join table. It has to be specified once, in the `dominant` attribute of the relation. If it is not specified, Strapi will use a generated default one. It is useful to define the name of the join table when the name generated by Strapi is too long for the database you use.
**Path —** `./api/category/models/Category.settings.json`.
```json

View File

@ -1,7 +1,80 @@
# getstarted
A quick description of getstarted.
This is an example app you can run to test your changes quickly.
Start the app with mongo
## Requirements
`DB=mongo yarn develop`
- Docker
- Docker compose
- Node
## Installation
By default once you have setup the monorepo you will be able to run the getstarted app with a sqlite DB directly.
If you wish to run the getstarted app with another database you can use the `docker-compose.dev.yml` file at the root of the directory.
### start the databases
Run the following command at the root of the monorepo
```
docker-compose -f docker-compose.dev.yml up -d
```
If you need to stop the running databases you can stop them with the following command:
```
docker-compose -f docker-compose.dev.yml stop
```
### run the getstarted app with a specific database
```
DB={dbName} yarn develop
```
The way it works is that the `getstarted` app has a specific `database.js` config file that will use the `DB` environment variable to setup the right database connection. You can look at the code [here](./config/environments/development/database.js)
**Warning**
You might have some errors while connecting to the databases.
They might be coming from a conflict between a locally running database instance and the docker instance. To avoid the errors either shutdown your local database instance or change the ports in the `./config/environments/development/database.js` and the `docker-compose.dev.yml` file.
**Example**:
`database.js`
```js
module.exports = {
connections: {
default: {
connector: 'mongoose',
settings: {
// host: 'localhost',
// database: 'strapi',
// username: 'root',
// password: 'strapi',
port: 27099,
},
options: {},
},
},
};
```
`docker-compose.dev.yml`
```yml
services:
mongo:
# image: mongo
# restart: always
# environment:
# MONGO_INITDB_ROOT_USERNAME: root
# MONGO_INITDB_ROOT_PASSWORD: strapi
# volumes:
# - mongodata:/data/db
ports:
- '27099:27017'
```

View File

@ -1,74 +0,0 @@
theme: "readthedocs"
docs_dir: ./website
site_dir: ./packages/strapi-generate-new/files/public
theme_dir: ./website/theme
site_name: "Strapi"
site_description: "Node.js framework powering API-driven web and mobile applications."
site_author: "Strapi"
site_url: "http://strapi.io/"
repo_name: "Strapi"
repo_url: "https://github.com/strapi/strapi"
markdown_extensions:
- admonition
- smarty
- sane_lists
- toc:
permalink: "#"
extra:
command: "$ npm install strapi-cli -g"
baseline: "Powering API-driven web and mobile applications."
start: "Get started"
current_version: "Current version"
version: "2.0.0"
pages:
- Home: ./index.md
- Prologue:
- Introduction: ./documentation/prologue/why.md
- Install Strapi: ./documentation/prologue/installation.md
- How it works: ./documentation/prologue/start.md
- Architecture foundations:
- Configuration: ./documentation/architecture/configuration.md
- Router: ./documentation/architecture/router.md
- Context: ./documentation/architecture/context.md
- Request: ./documentation/architecture/request.md
- Response: ./documentation/architecture/response.md
- Databases: ./documentation/architecture/databases.md
- Views: ./documentation/architecture/views.md
- Logging: ./documentation/architecture/logging.md
- Concepts:
- Authentication: ./documentation/concepts/authentication.md
- GraphQL: ./documentation/concepts/graphql.md
- JSON API: ./documentation/concepts/jsonapi.md
- WebSockets: ./documentation/concepts/websockets.md
- Internationalization: ./documentation/concepts/internationalization.md
- Scheduled tasks: ./documentation/concepts/cron.md
- Services: ./documentation/concepts/services.md
- Policies: ./documentation/concepts/policies.md
- Sessions: ./documentation/concepts/sessions.md
- SQL databases:
- Models: ./documentation/sql/models.md
- Migrations: ./documentation/sql/migrations.md
- Query builder: ./documentation/sql/queries.md
- Raw: ./documentation/sql/raw.md
- Interfaces: ./documentation/sql/interfaces.md
- SQL ORM: ./documentation/sql/orm.md
- Advanced usage:
- Security: ./documentation/advanced/security.md
- Lifecycle events: ./documentation/advanced/events.md
- Error handling: ./documentation/advanced/errors.md
- Custom generators: ./documentation/advanced/generators.md
- Custom hooks: ./documentation/advanced/hooks.md
- Legal Info:
- Governance: ./info/governance.md
- Code Of Conduct: ./info/conduct.md
- Releases: ./info/releases.md
- Security: ./info/security.md
- Trademarks: ./info/trademarks.md
- Licenses: ./info/licenses.md
- Support: ./info/support.md

View File

@ -232,7 +232,8 @@ module.exports = ({ models, target }, ctx) => {
details.isVirtual = true;
if (nature === 'manyWay') {
const joinTableName = `${definition.collectionName}__${_.snakeCase(name)}`;
const joinTableName =
details.collectionName || `${definition.collectionName}__${_.snakeCase(name)}`;
const foreignKey = `${singular(definition.collectionName)}_${definition.primaryKey}`;
@ -259,9 +260,10 @@ module.exports = ({ models, target }, ctx) => {
return collection;
};
} else {
const joinTableName =
_.get(details, 'collectionName') ||
utilsModels.getCollectionName(targetModel.attributes[details.via], details);
const joinTableName = utilsModels.getCollectionName(
targetModel.attributes[details.via],
details
);
const relationship = targetModel.attributes[details.via];
@ -647,7 +649,6 @@ module.exports = ({ models, target }, ctx) => {
'columnName'
)
);
GLOBALS[definition.globalId] = ORM.Model.extend(loadedModel);
// Expose ORM functions through the `strapi.models[xxx]`
@ -672,6 +673,14 @@ module.exports = ({ models, target }, ctx) => {
if (err instanceof TypeError || err instanceof ReferenceError) {
strapi.stopWithError(err, `Impossible to register the '${model}' model.`);
}
if (['ER_TOO_LONG_IDENT'].includes(err.code)) {
strapi.stopWithError(
err,
`A table name is too long. If it is the name of a join table automatically generated by Strapi, you can customise it by adding \`collectionName: "customName"\` in the corresponding model's attribute.
When this happens on a manyToMany relation, make sure to set this parameter on the dominant side of the relation (e.g: where \`dominant: true\` is set)`
);
}
strapi.stopWithError(err);
}
});

View File

@ -4,6 +4,7 @@ const _ = require('lodash');
const requireConnector = require('./require-connector');
const { createQuery } = require('./queries');
const { checkDuplicatedTableNames } = require('./validation/before-mounting-models');
class DatabaseManager {
constructor(strapi) {
@ -31,6 +32,8 @@ class DatabaseManager {
}
}
checkDuplicatedTableNames(this.strapi);
for (const connectorToInitialize of connectorsToInitialize) {
const connector = requireConnector(connectorToInitialize)(strapi);

View File

@ -0,0 +1,58 @@
const _ = require('lodash');
const createErrorMessage = (
modelA,
modelB
) => `Duplicated collection name: \`${modelA.model.collectionName}\`.
The same collection name can't be used for two different models.
First found in ${modelA.origin} \`${modelA.apiOrPluginName}\`, model \`${modelA.modelName}\`.
Second found in ${modelB.origin} \`${modelB.apiOrPluginName}\`, model \`${modelB.modelName}\`.`;
// Check if all collection names are unique
const checkDuplicatedTableNames = strapi => {
const modelsWithInfo = [];
_.forIn(strapi.admin.models, (model, modelName) => {
modelsWithInfo.push({
origin: 'Strapi internal',
model,
apiOrPluginName: 'admin',
modelName,
});
});
_.forIn(strapi.api, (api, apiName) => {
_.forIn(api.models, (model, modelName) => {
modelsWithInfo.push({
origin: 'API',
model,
apiOrPluginName: apiName,
modelName,
});
});
});
_.forIn(strapi.plugins, (plugin, pluginName) => {
_.forIn(plugin.models, (model, modelName) => {
modelsWithInfo.push({
origin: 'Plugin',
model,
apiOrPluginName: pluginName,
modelName,
});
});
});
modelsWithInfo.forEach(modelA => {
const similarModelFound = modelsWithInfo.find(
modelB =>
modelB.model.collectionName === modelA.model.collectionName &&
modelB.model.uid !== modelA.model.uid
);
if (similarModelFound) {
throw new Error(createErrorMessage(modelA, similarModelFound));
}
});
};
module.exports = checkDuplicatedTableNames;

View File

@ -0,0 +1,5 @@
const checkDuplicatedTableNames = require('./check-duplicated-table-names');
module.exports = {
checkDuplicatedTableNames,
};

View File

@ -11,7 +11,7 @@ const pluralize = require('pluralize');
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
const { buildQuery: buildQueryResolver } = require('./resolvers-builder');
const { convertToParams, convertToQuery } = require('./utils');
const { convertToParams, convertToQuery, nonRequired } = require('./utils');
const { toSDL } = require('./schema-definitions');
/**
@ -19,15 +19,15 @@ const { toSDL } = require('./schema-definitions');
*
* @returns {Boolean}
*/
const isPrimitiveType = _type => {
const type = _type.replace('!', '');
const isPrimitiveType = type => {
const nonRequiredType = nonRequired(type);
return (
type === 'Int' ||
type === 'Float' ||
type === 'String' ||
type === 'Boolean' ||
type === 'DateTime' ||
type === 'JSON'
nonRequiredType === 'Int' ||
nonRequiredType === 'Float' ||
nonRequiredType === 'String' ||
nonRequiredType === 'Boolean' ||
nonRequiredType === 'DateTime' ||
nonRequiredType === 'JSON'
);
};
@ -60,7 +60,8 @@ const isNotOfTypeArray = type => {
* Returns all fields of type Integer or float
*/
const isNumberType = type => {
return type === 'Int' || type === 'Float';
const nonRequiredType = nonRequired(type);
return nonRequiredType === 'Int' || nonRequiredType === 'Float';
};
/**

View File

@ -33,7 +33,7 @@ const diffResolvers = (object, base) => {
Object.keys(object).forEach(type => {
Object.keys(object[type]).forEach(resolver => {
if(type === 'Query' || type === 'Mutation') {
if (type === 'Query' || type === 'Mutation') {
if (!_.has(base, [type, resolver])) {
_.set(newObj, [type, resolver], _.get(object, [type, resolver]));
}
@ -85,6 +85,8 @@ const amountLimiting = (params = {}) => {
return params;
};
const nonRequired = type => type.replace('!', '');
module.exports = {
diffResolvers,
mergeSchemas,
@ -92,4 +94,5 @@ module.exports = {
convertToParams,
convertToQuery,
amountLimiting,
nonRequired,
};

View File

@ -279,6 +279,14 @@ module.exports = {
* Return table name for a collection many-to-many
*/
getCollectionName: (associationA, associationB) => {
if (associationA.dominant && _.has(associationA, 'collectionName')) {
return associationA.collectionName;
}
if (associationB.dominant && _.has(associationB, 'collectionName')) {
return associationB.collectionName;
}
return [associationA, associationB]
.sort((a, b) => {
if (a.collection === b.collection) {
@ -345,14 +353,14 @@ module.exports = {
};
if (infos.nature === 'manyToMany' && definition.orm === 'bookshelf') {
ast.tableCollectionName =
_.get(association, 'collectionName') || this.getCollectionName(details, association);
ast.tableCollectionName = this.getCollectionName(details, association);
}
if (infos.nature === 'manyWay' && definition.orm === 'bookshelf') {
ast.tableCollectionName = `${definition.collectionName}__${_.snakeCase(key)}`;
ast.tableCollectionName =
_.get(association, 'collectionName') ||
`${definition.collectionName}__${_.snakeCase(key)}`;
}
definition.associations.push(ast);
return;
}

View File

@ -8,7 +8,7 @@ const checkReservedFilename = require('./check-reserved-filename');
* @param {string} dir - directory from which to load configs
* @param {string} pattern - glob pattern to search for config files
*/
const laodConfigFiles = (dir, pattern = 'config/**/*.+(js|json)') =>
const loadConfigFiles = (dir, pattern = 'config/**/*.+(js|json)') =>
loadFiles(dir, pattern, {
requireFn: requireFileAndParse,
shouldUseFileNameAsKey: checkReservedFilename,
@ -18,4 +18,4 @@ const laodConfigFiles = (dir, pattern = 'config/**/*.+(js|json)') =>
},
});
module.exports = laodConfigFiles;
module.exports = loadConfigFiles;