mirror of
https://github.com/strapi/strapi.git
synced 2025-12-28 23:57:32 +00:00
Merge branch 'develop' into features/media-lib
Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
commit
087920fb47
@ -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**
|
||||
|
||||
|
||||
17
Makefile
17
Makefile
@ -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
45
docker-compose.dev.yml
Normal 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:
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
```
|
||||
|
||||
74
mkdocs.yml
74
mkdocs.yml
@ -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
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -0,0 +1,5 @@
|
||||
const checkDuplicatedTableNames = require('./check-duplicated-table-names');
|
||||
|
||||
module.exports = {
|
||||
checkDuplicatedTableNames,
|
||||
};
|
||||
@ -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';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user