From 11aeae4dc720801e00194a3cd90b4b3753ed330f Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Tue, 10 Mar 2020 16:47:41 +0100 Subject: [PATCH 01/20] docs: add single type examples Signed-off-by: Jim LAURIE --- .../3.0.0-beta.x/content-api/api-endpoints.md | 32 +++++++++++++++++++ .../getting-started/introduction.md | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/3.0.0-beta.x/content-api/api-endpoints.md b/docs/3.0.0-beta.x/content-api/api-endpoints.md index 1e0617564e..ce3cc5b751 100644 --- a/docs/3.0.0-beta.x/content-api/api-endpoints.md +++ b/docs/3.0.0-beta.x/content-api/api-endpoints.md @@ -162,6 +162,38 @@ Here is the list of endpoints generated for each of your **Content Types**. ::: +::: tab Contact + +`Contact` **Content Type** + +
+ +| Method | Path | Description | +| :----- | :--------- | :------------------------- | +| GET | `/contact` | Get the contact content | +| PUT | `/contact` | Update the contact content | +| DELETE | `/contact` | Delete the contact content | + +
+ +::: + +::: tab About + +`About` **Content Type** + +
+ +| Method | Path | Description | +| :----- | :------- | :----------------------- | +| GET | `/about` | Get the about content | +| PUT | `/about` | Update the about content | +| DELETE | `/about` | Delete the about content | + +
+ +::: + :::: ### Collection Types diff --git a/docs/3.0.0-beta.x/getting-started/introduction.md b/docs/3.0.0-beta.x/getting-started/introduction.md index 87ba6bcc35..f9224398f5 100644 --- a/docs/3.0.0-beta.x/getting-started/introduction.md +++ b/docs/3.0.0-beta.x/getting-started/introduction.md @@ -15,7 +15,7 @@ It's the origin purpose of the project. ### Custom content structure -With the admin panel of Strapi, You can generate the admin panel in just a few clicks, and get your whole CMS setup in a few minutes. +With the admin panel of Strapi, you can generate the admin panel in just a few clicks, and get your whole CMS setup in a few minutes. ### Manage content From 6ef529b3a1a5172dfa1c826683edbd42f9ffe639 Mon Sep 17 00:00:00 2001 From: Jim LAURIE Date: Tue, 10 Mar 2020 17:01:44 +0100 Subject: [PATCH 02/20] docs: rewrite sentence Signed-off-by: Jim LAURIE --- docs/3.0.0-beta.x/getting-started/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.0.0-beta.x/getting-started/introduction.md b/docs/3.0.0-beta.x/getting-started/introduction.md index f9224398f5..fe24ef2e18 100644 --- a/docs/3.0.0-beta.x/getting-started/introduction.md +++ b/docs/3.0.0-beta.x/getting-started/introduction.md @@ -15,7 +15,7 @@ It's the origin purpose of the project. ### Custom content structure -With the admin panel of Strapi, you can generate the admin panel in just a few clicks, and get your whole CMS setup in a few minutes. +You can generate the admin panel in a few clicks and get your whole CMS setup in a few minutes. ### Manage content From 7f87d86d6d772bb8a0c5a1cb800ffdde24623838 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Thu, 12 Mar 2020 01:55:03 -0700 Subject: [PATCH 03/20] Update DO Docs per https://github.com/strapi/one-click-deploy/pull/6 Signed-off-by: Derrick Mehaffy --- docs/3.0.0-beta.x/installation/digitalocean-one-click.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md index c41cf57a44..01337aeb63 100644 --- a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md +++ b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md @@ -27,7 +27,7 @@ To create a project head over to the Strapi [listing on the marketplace](https:/ ### Step 3: Visit your app -Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets). +Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets). After the droplet has started, it will take a few more minutes to finish the Strapi installation. From here you will see the public ipv4 address that you can use to visit your Strapi application, just open that in a browser and it should ask you to create your first administrator! @@ -101,7 +101,7 @@ upstream strapi { ### Strapi -In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi`. +In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi-development`. Please note that with this application it is intially created and ran in the `development` environment to allow for creating models. **You should not use this directly in production**, it is recommended that you configure a private git repository to commit changes into and create a new application directory within the service user's home (Example: `/srv/strapi/strapi-production`). To run the new `production` or `staging` environments you can refer to the [PM2 Documentation](https://pm2.keymetrics.io/docs/usage/quick-start/#managing-processes). @@ -137,8 +137,6 @@ Strapi will automatically start if the virtual machine is rebooted, you can also ## Changing the PostgreSQL Password -Because of how the virtual machine is created, your database is setup with a long and random password, however for security you should change this password before moving into a production-like setting. - Use the following steps to change the PostgreSQL password and update Strapi's config: - Make sure you are logged into the `strapi` service user From 65a3e83ee3ad267cc2409bcb210ab41a22ad8e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Thu, 12 Mar 2020 16:05:39 +0100 Subject: [PATCH 04/20] Change error message when bookshelf db connection fails at start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Noël --- .../lib/mount-models.js | 117 +++++------------- packages/strapi/lib/Strapi.js | 44 ++----- 2 files changed, 43 insertions(+), 118 deletions(-) diff --git a/packages/strapi-connector-bookshelf/lib/mount-models.js b/packages/strapi-connector-bookshelf/lib/mount-models.js index a6a055cc7d..06aa5c2e51 100644 --- a/packages/strapi-connector-bookshelf/lib/mount-models.js +++ b/packages/strapi-connector-bookshelf/lib/mount-models.js @@ -88,10 +88,7 @@ module.exports = ({ models, target }, ctx) => { idAttribute: _.get(definition, 'options.idAttribute', 'id'), associations: [], defaults: Object.keys(definition.attributes).reduce((acc, current) => { - if ( - definition.attributes[current].type && - definition.attributes[current].default - ) { + if (definition.attributes[current].type && definition.attributes[current].default) { acc[current] = definition.attributes[current].default; } @@ -114,9 +111,7 @@ module.exports = ({ models, target }, ctx) => { const pivot = this.pivot && !omitPivot && this.pivot.attributes; // Remove pivot attributes with prefix. - _.keys(pivot).forEach( - key => delete attributes[`${PIVOT_PREFIX}${key}`] - ); + _.keys(pivot).forEach(key => delete attributes[`${PIVOT_PREFIX}${key}`]); // Add pivot attributes without prefix. const pivotAttributes = _.mapKeys( @@ -147,16 +142,10 @@ module.exports = ({ models, target }, ctx) => { } const { nature, verbose } = - utilsModels.getNature(details, name, undefined, model.toLowerCase()) || - {}; + utilsModels.getNature(details, name, undefined, model.toLowerCase()) || {}; // Build associations key - utilsModels.defineAssociations( - model.toLowerCase(), - definition, - details, - name - ); + utilsModels.defineAssociations(model.toLowerCase(), definition, details, name); let globalId; const globalName = details.model || details.collection || ''; @@ -164,10 +153,7 @@ module.exports = ({ models, target }, ctx) => { // Exclude polymorphic association. if (globalName !== '*') { globalId = details.plugin - ? _.get( - strapi.plugins, - `${details.plugin}.models.${globalName.toLowerCase()}.globalId` - ) + ? _.get(strapi.plugins, `${details.plugin}.models.${globalName.toLowerCase()}.globalId`) : _.get(strapi.models, `${globalName.toLowerCase()}.globalId`); } @@ -225,10 +211,7 @@ module.exports = ({ models, target }, ctx) => { } case 'belongsTo': { loadedModel[name] = function() { - return this.belongsTo( - GLOBALS[globalId], - _.get(details, 'columnName', name) - ); + return this.belongsTo(GLOBALS[globalId], _.get(details, 'columnName', name)); }; break; } @@ -245,13 +228,9 @@ module.exports = ({ models, target }, ctx) => { details.isVirtual = true; if (nature === 'manyWay') { - const joinTableName = `${definition.collectionName}__${_.snakeCase( - name - )}`; + const joinTableName = `${definition.collectionName}__${_.snakeCase(name)}`; - const foreignKey = `${singular(definition.collectionName)}_${ - definition.primaryKey - }`; + const foreignKey = `${singular(definition.collectionName)}_${definition.primaryKey}`; let otherKey = `${details.attribute}_${details.column}`; @@ -277,10 +256,7 @@ module.exports = ({ models, target }, ctx) => { } else { const joinTableName = _.get(details, 'collectionName') || - utilsModels.getCollectionName( - targetModel.attributes[details.via], - details - ); + utilsModels.getCollectionName(targetModel.attributes[details.via], details); const relationship = targetModel.attributes[details.via]; @@ -333,10 +309,7 @@ module.exports = ({ models, target }, ctx) => { details.via, `${definition.collectionName}` ).query(qb => { - qb.where( - _.get(model, ['attributes', details.via, 'filter'], 'field'), - name - ); + qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name); }); }; break; @@ -354,10 +327,7 @@ module.exports = ({ models, target }, ctx) => { details.via, `${definition.collectionName}` ).query(qb => { - qb.where( - _.get(model, ['attributes', details.via, 'filter'], 'field'), - name - ); + qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name); }); }; break; @@ -369,21 +339,17 @@ module.exports = ({ models, target }, ctx) => { ); const morphValues = association.related.map(id => { - let models = Object.values(strapi.models).filter( - model => model.globalId === id - ); + let models = Object.values(strapi.models).filter(model => model.globalId === id); if (models.length === 0) { - models = Object.values(strapi.components).filter( - model => model.globalId === id - ); + models = Object.values(strapi.components).filter(model => model.globalId === id); } if (models.length === 0) { models = Object.keys(strapi.plugins).reduce((acc, current) => { - const models = Object.values( - strapi.plugins[current].models - ).filter(model => model.globalId === id); + const models = Object.values(strapi.plugins[current].models).filter( + model => model.globalId === id + ); if (acc.length === 0 && models.length > 0) { acc = models; @@ -395,9 +361,7 @@ module.exports = ({ models, target }, ctx) => { if (models.length === 0) { strapi.log.error(`Impossible to register the '${model}' model.`); - strapi.log.error( - 'The collection name cannot be found for the morphTo method.' - ); + strapi.log.error('The collection name cannot be found for the morphTo method.'); strapi.stop(); } @@ -417,10 +381,7 @@ module.exports = ({ models, target }, ctx) => { related: function() { return this.morphTo( name, - ...association.related.map((id, index) => [ - GLOBALS[id], - morphValues[index], - ]) + ...association.related.map((id, index) => [GLOBALS[id], morphValues[index]]) ); }, }; @@ -434,16 +395,10 @@ module.exports = ({ models, target }, ctx) => { // Upload has many Upload_morph that morph to different model. loadedModel[name] = function() { if (verbose === 'belongsToMorph') { - return this.hasOne( - GLOBALS[options.tableName], - `${definition.collectionName}_id` - ); + return this.hasOne(GLOBALS[options.tableName], `${definition.collectionName}_id`); } - return this.hasMany( - GLOBALS[options.tableName], - `${definition.collectionName}_id` - ); + return this.hasMany(GLOBALS[options.tableName], `${definition.collectionName}_id`); }; break; } @@ -469,9 +424,7 @@ module.exports = ({ models, target }, ctx) => { return _.mapKeys(params, (value, key) => { const attr = definition.attributes[key] || {}; - return _.isPlainObject(attr) && _.isString(attr['columnName']) - ? attr['columnName'] - : key; + return _.isPlainObject(attr) && _.isString(attr['columnName']) ? attr['columnName'] : key; }); }; @@ -504,20 +457,16 @@ module.exports = ({ models, target }, ctx) => { case 'component': { const { repeatable } = attr; - const components = relations[key] - .toJSON() - .map(el => el.component); + const components = relations[key].toJSON().map(el => el.component); - attrs[key] = - repeatable === true ? components : _.first(components) || null; + attrs[key] = repeatable === true ? components : _.first(components) || null; break; } case 'dynamiczone': { attrs[key] = relations[key].toJSON().map(el => { const componentKey = Object.keys(strapi.components).find( - key => - strapi.components[key].collectionName === el.component_type + key => strapi.components[key].collectionName === el.component_type ); return { @@ -540,9 +489,7 @@ module.exports = ({ models, target }, ctx) => { if (relation) { // Extract raw JSON data. - attrs[association.alias] = relation.toJSON - ? relation.toJSON(options) - : relation; + attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation; // Retrieve opposite model. const model = strapi.getModel( @@ -553,8 +500,7 @@ module.exports = ({ models, target }, ctx) => { // Reformat data by bypassing the many-to-many relationship. switch (association.nature) { case 'oneToManyMorph': - attrs[association.alias] = - attrs[association.alias][model.collectionName] || null; + attrs[association.alias] = attrs[association.alias][model.collectionName] || null; break; case 'manyToManyMorph': attrs[association.alias] = attrs[association.alias].map( @@ -602,9 +548,7 @@ module.exports = ({ models, target }, ctx) => { if (relation) { // Extract raw JSON data. - attrs[association.alias] = relation.toJSON - ? relation.toJSON(options) - : relation; + attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation; } }); @@ -717,9 +661,10 @@ module.exports = ({ models, target }, ctx) => { await createComponentJoinTables({ definition, ORM }); } catch (err) { - strapi.log.error(`Impossible to register the '${model}' model.`); - strapi.log.error(err); - strapi.stop(); + if (err instanceof TypeError || err instanceof ReferenceError) { + strapi.stopWithError(err, `Impossible to register the '${model}' model.`); + } + strapi.stopWithError(err); } }); diff --git a/packages/strapi/lib/Strapi.js b/packages/strapi/lib/Strapi.js index 18459f2d93..235a63a991 100644 --- a/packages/strapi/lib/Strapi.js +++ b/packages/strapi/lib/Strapi.js @@ -22,10 +22,7 @@ const getPrefixedDeps = require('./utils/get-prefixed-dependencies'); const createEventHub = require('./services/event-hub'); const createWebhookRunner = require('./services/webhook-runner'); -const { - webhookModel, - createWebhookStore, -} = require('./services/webhook-store'); +const { webhookModel, createWebhookStore } = require('./services/webhook-store'); const { createCoreStore, coreStoreModel } = require('./services/core-store'); const createEntityService = require('./services/entity-service'); const createEntityValidator = require('./services/entity-validator'); @@ -133,10 +130,7 @@ class Strapi extends EventEmitter { } requireProjectBootstrap() { - const bootstrapPath = path.resolve( - this.dir, - 'config/functions/bootstrap.js' - ); + const bootstrapPath = path.resolve(this.dir, 'config/functions/bootstrap.js'); if (fse.existsSync(bootstrapPath)) { require(bootstrapPath); @@ -159,10 +153,7 @@ class Strapi extends EventEmitter { [chalk.blue('Launched in'), Date.now() - this.config.launchedAt + ' ms'], [chalk.blue('Environment'), this.config.environment], [chalk.blue('Process PID'), process.pid], - [ - chalk.blue('Version'), - `${this.config.info.strapi} (node v${this.config.info.node})`, - ] + [chalk.blue('Version'), `${this.config.info.strapi} (node v${this.config.info.node})`] ); console.log(infoTable.toString()); @@ -176,9 +167,7 @@ class Strapi extends EventEmitter { console.log(chalk.bold('One more thing...')); console.log( - chalk.grey( - 'Create your first administrator 💻 by going to the administration panel at:' - ) + chalk.grey('Create your first administrator 💻 by going to the administration panel at:') ); console.log(); @@ -194,11 +183,7 @@ class Strapi extends EventEmitter { console.log(chalk.bold('Welcome back!')); if (this.config.serveAdminPanel === true) { - console.log( - chalk.grey( - 'To manage your project 🚀, go to the administration panel at:' - ) - ); + console.log(chalk.grey('To manage your project 🚀, go to the administration panel at:')); console.log(chalk.bold(this.config.admin.url)); console.log(); } @@ -243,11 +228,7 @@ class Strapi extends EventEmitter { if ( (this.config.environment === 'development' && - _.get( - this.config.currentEnvironment, - 'server.admin.autoOpen', - true - ) !== false) || + _.get(this.config.currentEnvironment, 'server.admin.autoOpen', true) !== false) || !isInitialised ) { await utils.openBrowser.call(this); @@ -265,9 +246,7 @@ class Strapi extends EventEmitter { // handle port in use cleanly this.server.on('error', err => { if (err.code === 'EADDRINUSE') { - return this.stopWithError( - `The port ${err.port} is already used by another application.` - ); + return this.stopWithError(`The port ${err.port} is already used by another application.`); } this.log.error(err); @@ -294,8 +273,11 @@ class Strapi extends EventEmitter { }; } - stopWithError(err) { + stopWithError(err, customMessage) { this.log.debug(`⛔️ Server wasn't able to start properly.`); + if (customMessage) { + this.log.error(customMessage); + } this.log.error(err); return this.stop(); } @@ -445,9 +427,7 @@ class Strapi extends EventEmitter { } const pluginBoostraps = Object.keys(this.plugins).map(plugin => { - return execBootstrap( - _.get(this.plugins[plugin], 'config.functions.bootstrap') - ).catch(err => { + return execBootstrap(_.get(this.plugins[plugin], 'config.functions.bootstrap')).catch(err => { strapi.log.error(`Bootstrap function in plugin "${plugin}" failed`); strapi.log.error(err); strapi.stop(); From 1227bfeba41b45309d7cde127674f71c0268cb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Thu, 12 Mar 2020 10:56:37 +0100 Subject: [PATCH 05/20] add possibility to set a relation "private" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Noël --- .../test/dynamiczones/with-media.test.e2e.js | 20 +- .../controllers/ContentTypes.js | 24 +- .../controllers/validation/relations.js | 1 + .../services/schema-builder/index.js | 12 +- .../services/type-builder.js | 18 +- .../services/type-definitions.js | 53 +--- .../test/graphqlRelations.test.e2e.js | 285 +++++++++++++++--- 7 files changed, 286 insertions(+), 127 deletions(-) diff --git a/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js b/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js index 50cc30de8a..90cad39125 100644 --- a/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js +++ b/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js @@ -18,9 +18,9 @@ const uploadImg = () => { describe.each([ [ 'CONTENT MANAGER', - '/content-manager/explorer/application::withdynamiczone.withdynamiczone', + '/content-manager/explorer/application::withdynamiczonemedia.withdynamiczonemedia', ], - ['GENERATED API', '/withdynamiczones'], + ['GENERATED API', '/withdynamiczonemedias'], ])('[%s] => Not required dynamiczone', (_, path) => { beforeAll(async () => { const token = await registerAndLogin(); @@ -61,17 +61,9 @@ describe.each([ }, }); - await modelsUtils.createContentTypeWithType( - 'withdynamiczone', - 'dynamiczone', - { - components: [ - 'default.single-media', - 'default.multiple-media', - 'default.with-nested', - ], - } - ); + await modelsUtils.createContentTypeWithType('withdynamiczonemedia', 'dynamiczone', { + components: ['default.single-media', 'default.multiple-media', 'default.with-nested'], + }); rq = authRq.defaults({ baseUrl: `http://localhost:1337${path}`, @@ -82,7 +74,7 @@ describe.each([ await modelsUtils.deleteComponent('default.with-nested'); await modelsUtils.deleteComponent('default.single-media'); await modelsUtils.deleteComponent('default.multiple-media'); - await modelsUtils.deleteContentType('withdynamiczone'); + await modelsUtils.deleteContentType('withdynamiczonemedia'); }, 60000); describe('Contains components with medias', () => { diff --git a/packages/strapi-plugin-content-type-builder/controllers/ContentTypes.js b/packages/strapi-plugin-content-type-builder/controllers/ContentTypes.js index 225290f7a0..211c27f5e7 100644 --- a/packages/strapi-plugin-content-type-builder/controllers/ContentTypes.js +++ b/packages/strapi-plugin-content-type-builder/controllers/ContentTypes.js @@ -18,26 +18,20 @@ module.exports = { return ctx.send({ error }, 400); } - const contentTypeService = - strapi.plugins['content-type-builder'].services.contenttypes; + const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes; const contentTypes = Object.keys(strapi.contentTypes) .filter(uid => { if (uid.startsWith('strapi::')) return false; if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead - if ( - kind && - _.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind - ) { + if (kind && _.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind) { return false; } return true; }) - .map(uid => - contentTypeService.formatContentType(strapi.contentTypes[uid]) - ); + .map(uid => contentTypeService.formatContentType(strapi.contentTypes[uid])); ctx.send({ data: contentTypes, @@ -53,8 +47,7 @@ module.exports = { return ctx.send({ error: 'contentType.notFound' }, 404); } - const contentTypeService = - strapi.plugins['content-type-builder'].services.contenttypes; + const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes; ctx.send({ data: contentTypeService.formatContentType(contentType) }); }, @@ -71,8 +64,7 @@ module.exports = { try { strapi.reload.isWatching = false; - const contentTypeService = - strapi.plugins['content-type-builder'].services.contenttypes; + const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes; const component = await contentTypeService.createContentType({ contentType: body.contentType, @@ -112,8 +104,7 @@ module.exports = { try { strapi.reload.isWatching = false; - const contentTypeService = - strapi.plugins['content-type-builder'].services.contenttypes; + const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes; const component = await contentTypeService.editContentType(uid, { contentType: body.contentType, @@ -139,8 +130,7 @@ module.exports = { try { strapi.reload.isWatching = false; - const contentTypeService = - strapi.plugins['content-type-builder'].services.contenttypes; + const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes; const component = await contentTypeService.deleteContentType(uid); diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js index 6221592cdc..4ac7885a91 100644 --- a/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js +++ b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js @@ -35,5 +35,6 @@ module.exports = (obj, validNatures) => { .test(isValidName) .nullable(), targetColumnName: yup.string().nullable(), + private: yup.boolean().nullable(), }; }; diff --git a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js index d04817708d..d988636728 100644 --- a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js +++ b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js @@ -60,18 +60,12 @@ function createSchemaBuilder({ components, contentTypes }) { // init temporary ContentTypes Object.keys(contentTypes).forEach(key => { - tmpContentTypes.set( - contentTypes[key].uid, - createSchemaHandler(contentTypes[key]) - ); + tmpContentTypes.set(contentTypes[key].uid, createSchemaHandler(contentTypes[key])); }); // init temporary components Object.keys(components).forEach(key => { - tmpComponents.set( - components[key].uid, - createSchemaHandler(components[key]) - ); + tmpComponents.set(components[key].uid, createSchemaHandler(components[key])); }); return { @@ -120,12 +114,14 @@ function createSchemaBuilder({ components, contentTypes }) { columnName, dominant, autoPopulate, + private: isPrivate, } = attribute; const attr = { unique: unique === true ? true : undefined, columnName: columnName || undefined, configurable: configurable === false ? false : undefined, + private: isPrivate === true ? true : undefined, autoPopulate, }; diff --git a/packages/strapi-plugin-graphql/services/type-builder.js b/packages/strapi-plugin-graphql/services/type-builder.js index a718d15c12..45943e8d69 100644 --- a/packages/strapi-plugin-graphql/services/type-builder.js +++ b/packages/strapi-plugin-graphql/services/type-builder.js @@ -16,8 +16,7 @@ const GraphQLLong = require('graphql-type-long'); const Time = require('../types/time'); const { toSingular, toInputName } = require('./naming'); -const isScalarAttribute = ({ type }) => - type && !['component', 'dynamiczone'].includes(type); +const isScalarAttribute = ({ type }) => type && !['component', 'dynamiczone'].includes(type); module.exports = { /** @@ -90,9 +89,7 @@ module.exports = { typeName = action === 'update' ? `edit${_.upperFirst(toSingular(globalId))}Input` - : `${_.upperFirst(toSingular(globalId))}Input${ - required ? '!' : '' - }`; + : `${_.upperFirst(toSingular(globalId))}Input${required ? '!' : ''}`; } if (repeatable === true) { @@ -104,9 +101,7 @@ module.exports = { if (attribute.type === 'dynamiczone') { const { required } = attribute; - const unionName = `${modelName}${_.upperFirst( - _.camelCase(attributeName) - )}DynamicZone`; + const unionName = `${modelName}${_.upperFirst(_.camelCase(attributeName))}DynamicZone`; let typeName = unionName; @@ -202,9 +197,7 @@ module.exports = { addPolymorphicUnionType(definition) { const types = graphql .parse(definition) - .definitions.filter( - def => def.kind === 'ObjectTypeDefinition' && def.name.value !== 'Query' - ) + .definitions.filter(def => def.kind === 'ObjectTypeDefinition' && def.name.value !== 'Query') .map(def => def.name.value); if (types.length > 0) { @@ -250,7 +243,7 @@ module.exports = { const inputs = ` input ${inputName} { - + ${Object.keys(model.attributes) .map(attributeName => { return `${attributeName}: ${this.convertType({ @@ -278,6 +271,7 @@ module.exports = { .join('\n')} } `; + return inputs; }, diff --git a/packages/strapi-plugin-graphql/services/type-definitions.js b/packages/strapi-plugin-graphql/services/type-definitions.js index a11a55deae..0d2dd8cb86 100644 --- a/packages/strapi-plugin-graphql/services/type-definitions.js +++ b/packages/strapi-plugin-graphql/services/type-definitions.js @@ -12,12 +12,7 @@ const DynamicZoneScalar = require('../types/dynamiczoneScalar'); const { formatModelConnectionsGQL } = require('./build-aggregation'); const types = require('./type-builder'); -const { - mergeSchemas, - convertToParams, - convertToQuery, - amountLimiting, -} = require('./utils'); +const { mergeSchemas, convertToParams, convertToQuery, amountLimiting } = require('./utils'); const { toSDL, getTypeDescription } = require('./schema-definitions'); const { toSingular, toPlural } = require('./naming'); const { buildQuery, buildMutation } = require('./resolvers-builder'); @@ -60,10 +55,10 @@ const buildTypeDefObj = model => { // Change field definition for collection relations associations .filter(association => association.type === 'collection') + .filter(association => attributes[association.alias].private !== true) .forEach(association => { - typeDef[ - `${association.alias}(sort: String, limit: Int, start: Int, where: JSON)` - ] = typeDef[association.alias]; + typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] = + typeDef[association.alias]; delete typeDef[association.alias]; }); @@ -90,9 +85,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => { .forEach(attribute => { const { components } = attributes[attribute]; - const typeName = `${globalId}${_.upperFirst( - _.camelCase(attribute) - )}DynamicZone`; + const typeName = `${globalId}${_.upperFirst(_.camelCase(attribute))}DynamicZone`; if (components.length === 0) { // Create dummy type because graphql doesn't support empty ones @@ -111,9 +104,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => { return compo.globalId; }); - const unionType = `union ${typeName} = ${componentsTypeNames.join( - ' | ' - )}`; + const unionType = `union ${typeName} = ${componentsTypeNames.join(' | ')}`; schema.definition += `\n${unionType}\n`; } @@ -137,8 +128,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => { }; const buildAssocResolvers = model => { - const contentManager = - strapi.plugins['content-manager'].services['contentmanager']; + const contentManager = strapi.plugins['content-manager'].services['contentmanager']; const { primaryKey, associations = [] } = model; @@ -194,8 +184,7 @@ const buildAssocResolvers = model => { }; if ( - ((association.nature === 'manyToMany' && - association.dominant) || + ((association.nature === 'manyToMany' && association.dominant) || association.nature === 'manyWay') && _.has(obj, association.alias) // if populated ) { @@ -203,31 +192,21 @@ const buildAssocResolvers = model => { queryOpts, ['query', targetModel.primaryKey], obj[association.alias] - ? obj[association.alias] - .map(val => val[targetModel.primaryKey] || val) - .sort() + ? obj[association.alias].map(val => val[targetModel.primaryKey] || val).sort() : [] ); } else { - _.set( - queryOpts, - ['query', association.via], - obj[targetModel.primaryKey] - ); + _.set(queryOpts, ['query', association.via], obj[targetModel.primaryKey]); } } return association.model - ? strapi.plugins.graphql.services['data-loaders'].loaders[ - targetModel.uid - ].load({ + ? strapi.plugins.graphql.services['data-loaders'].loaders[targetModel.uid].load({ params, options: queryOpts, single: true, }) - : strapi.plugins.graphql.services['data-loaders'].loaders[ - targetModel.uid - ].load({ + : strapi.plugins.graphql.services['data-loaders'].loaders[targetModel.uid].load({ options: queryOpts, association, }); @@ -308,9 +287,7 @@ const buildSingleType = model => { const singularName = toSingular(modelName); - const _schema = _.cloneDeep( - _.get(strapi.plugins, 'graphql.config._schema.graphql', {}) - ); + const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {})); const globalType = _.get(_schema, ['type', model.globalId], {}); @@ -357,9 +334,7 @@ const buildCollectionType = model => { const singularName = toSingular(modelName); const pluralName = toPlural(modelName); - const _schema = _.cloneDeep( - _.get(strapi.plugins, 'graphql.config._schema.graphql', {}) - ); + const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {})); const globalType = _.get(_schema, ['type', model.globalId], {}); diff --git a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js index 35c6d346ec..3e10cc29fc 100644 --- a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js +++ b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js @@ -44,6 +44,41 @@ const labelModel = { collectionName: '', }; +const carModel = { + attributes: { + name: { + type: 'text', + }, + }, + connection: 'default', + name: 'car', + description: '', + collectionName: '', +}; + +const personModel = { + attributes: { + name: { + type: 'text', + }, + privateName: { + type: 'text', + private: true, + }, + privateCars: { + nature: 'oneToMany', + target: 'application::car.car', + dominant: false, + targetAttribute: 'person', + private: true, + }, + }, + connection: 'default', + name: 'person', + description: '', + collectionName: '', +}; + describe('Test Graphql Relations API End to End', () => { beforeAll(async () => { const token = await registerAndLogin(); @@ -59,15 +94,17 @@ describe('Test Graphql Relations API End to End', () => { modelsUtils = createModelsUtils({ rq }); - await modelsUtils.createContentTypes([documentModel, labelModel]); + await modelsUtils.createContentTypes([documentModel, labelModel, carModel, personModel]); }, 60000); - afterAll(() => modelsUtils.deleteContentTypes(['document', 'label']), 60000); + afterAll(() => modelsUtils.deleteContentTypes(['document', 'label', 'car', 'person']), 60000); describe('Test relations features', () => { let data = { labels: [], documents: [], + people: [], + cars: [], }; const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }]; const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }]; @@ -127,49 +164,46 @@ describe('Test Graphql Relations API End to End', () => { data.labels = res.body.data.labels; }); - test.each(documentsPayload)( - 'Create document linked to every labels %o', - async document => { - const res = await graphqlQuery({ - query: /* GraphQL */ ` - mutation createDocument($input: createDocumentInput) { - createDocument(input: $input) { - document { + test.each(documentsPayload)('Create document linked to every labels %o', async document => { + const res = await graphqlQuery({ + query: /* GraphQL */ ` + mutation createDocument($input: createDocumentInput) { + createDocument(input: $input) { + document { + name + labels { + id name - labels { - id - name - } } } } - `, - variables: { - input: { - data: { - ...document, - labels: data.labels.map(t => t.id), - }, + } + `, + variables: { + input: { + data: { + ...document, + labels: data.labels.map(t => t.id), }, }, - }); + }, + }); - const { body } = res; + const { body } = res; - expect(res.statusCode).toBe(200); + expect(res.statusCode).toBe(200); - expect(body).toMatchObject({ - data: { - createDocument: { - document: { - ...selectFields(document), - labels: expect.arrayContaining(data.labels.map(selectFields)), - }, + expect(body).toMatchObject({ + data: { + createDocument: { + document: { + ...selectFields(document), + labels: expect.arrayContaining(data.labels.map(selectFields)), }, }, - }); - } - ); + }, + }); + }); test('List documents with labels', async () => { const res = await graphqlQuery({ @@ -229,9 +263,7 @@ describe('Test Graphql Relations API End to End', () => { labels: expect.arrayContaining( data.labels.map(label => ({ ...selectFields(label), - documents: expect.arrayContaining( - data.documents.map(selectFields) - ), + documents: expect.arrayContaining(data.documents.map(selectFields)), })) ), }, @@ -405,5 +437,184 @@ describe('Test Graphql Relations API End to End', () => { }); } }); + + test('Create person', async () => { + const person = { + name: 'Chuck Norris', + privateName: 'Jean-Eude', + }; + const res = await graphqlQuery({ + query: /* GraphQL */ ` + mutation createPerson($input: createPersonInput) { + createPerson(input: $input) { + person { + id + name + } + } + } + `, + variables: { + input: { + data: person, + }, + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.body).toEqual({ + data: { + createPerson: { + person: { + id: expect.anything(), + name: person.name, + }, + }, + }, + }); + data.people.push(res.body.data.createPerson.person); + }); + + test("Can't list a private field", async () => { + const res = await graphqlQuery({ + query: /* GraphQL */ ` + { + people { + name + privateName + } + } + `, + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toMatchObject({ + errors: [ + { + message: 'Cannot query field "privateName" on type "Person".', + }, + ], + }); + }); + + test('Create a car linked to a person (oneToMany)', async () => { + const car = { + name: 'Peugeot 508', + person: data.people[0].id, + }; + const res = await graphqlQuery({ + query: /* GraphQL */ ` + mutation createCar($input: createCarInput) { + createCar(input: $input) { + car { + id + name + person { + id + name + } + } + } + } + `, + variables: { + input: { + data: { + ...car, + }, + }, + }, + }); + + expect(res.statusCode).toBe(200); + expect(res.body).toMatchObject({ + data: { + createCar: { + car: { + id: expect.anything(), + name: car.name, + person: data.people[0], + }, + }, + }, + }); + + data.cars.push({ id: res.body.data.createCar.car.id }); + }); + + test("Can't list a private oneToMany relation", async () => { + const res = await graphqlQuery({ + query: /* GraphQL */ ` + { + people { + name + privateCars + } + } + `, + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toMatchObject({ + errors: [ + { + message: 'Cannot query field "privateCars" on type "Person".', + }, + ], + }); + }); + + test('Edit person/cars relations removes correctly a car', async () => { + const newPerson = { + name: 'Check Norris Junior', + privateCars: [], + }; + + const mutationRes = await graphqlQuery({ + query: /* GraphQL */ ` + mutation updatePerson($input: updatePersonInput) { + updatePerson(input: $input) { + person { + id + } + } + } + `, + variables: { + input: { + where: { + id: data.people[0].id, + }, + data: { + ...newPerson, + }, + }, + }, + }); + expect(mutationRes.statusCode).toBe(200); + + const queryRes = await graphqlQuery({ + query: /* GraphQL */ ` + query($id: ID!) { + car(id: $id) { + person { + id + } + } + } + `, + variables: { + id: data.cars[0].id, + }, + }); + expect(queryRes.statusCode).toBe(200); + expect(queryRes.body).toEqual({ + data: { + car: { + person: null, + }, + }, + }); + }); }); }); From fe19bcb2eaa2b0b16d82deed98862f7bf0f022b2 Mon Sep 17 00:00:00 2001 From: Shabith Ishan Thennakone Date: Sat, 22 Feb 2020 10:54:25 +0530 Subject: [PATCH 06/20] removed text-transform from variable name --- .../admin/src/components/ListRow/Wrapper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js b/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js index 5ca247418e..19736cb12c 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js @@ -43,7 +43,6 @@ const Wrapper = styled.tr` }} p { font-weight: 500; - text-transform: capitalize; } } td:last-child { From 59c03378cb7882b32dc2e87981e327d8d1ca0759 Mon Sep 17 00:00:00 2001 From: chrisyates Date: Mon, 16 Mar 2020 07:40:11 -0500 Subject: [PATCH 07/20] Correct Platform.sh name (#5480) From 9bbb395e3ab7f143151be79fd4ac0294b16c5d94 Mon Sep 17 00:00:00 2001 From: Nikolay Tyurin Date: Mon, 16 Mar 2020 16:17:59 +0300 Subject: [PATCH 08/20] Update graphql.md (#5510) changed link in the https://www.apollographql.com/ --- docs/3.0.0-beta.x/plugins/graphql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/3.0.0-beta.x/plugins/graphql.md b/docs/3.0.0-beta.x/plugins/graphql.md index 9ccda3e80e..e89a2fca4f 100644 --- a/docs/3.0.0-beta.x/plugins/graphql.md +++ b/docs/3.0.0-beta.x/plugins/graphql.md @@ -46,7 +46,7 @@ By default, the [Shadow CRUD](#shadow-crud) feature is enabled and the GraphQL i Security limits on maximum number of items in your response by default is limited to 100, however you can change this on the following config option `amountLimit`. This should only be changed after careful consideration of the drawbacks of a large query which can cause what would basically be a DDoS (Distributed Denial of Service). And may cause abnormal load on your Strapi server, as well as your database server. -You can also enable the Apollo server tracing feature, which is supported by the playground to track the response time of each part of your query. To enable this feature just change/add the `"tracing": true` option in the GraphQL settings file. You can read more about the tracing feature from Apollo [here](https://www.apollographql.com/docs/engine/features/query-tracing.html). +You can also enable the Apollo server tracing feature, which is supported by the playground to track the response time of each part of your query. To enable this feature just change/add the `"tracing": true` option in the GraphQL settings file. You can read more about the tracing feature from Apollo [here](https://www.apollographql.com/docs/apollo-server/federation/metrics/). You can edit these configurations by creating following file. From 9990759dd5e2933f8c591596c2fd1f1671e794c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Mon, 16 Mar 2020 15:42:56 +0100 Subject: [PATCH 09/20] always populate with components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Noël --- .../lib/populate.js | 9 +- .../test/graphqlRelations.test.e2e.js | 155 +++++++++++++----- 2 files changed, 119 insertions(+), 45 deletions(-) diff --git a/packages/strapi-connector-bookshelf/lib/populate.js b/packages/strapi-connector-bookshelf/lib/populate.js index 0fabee5b39..50af973a4f 100644 --- a/packages/strapi-connector-bookshelf/lib/populate.js +++ b/packages/strapi-connector-bookshelf/lib/populate.js @@ -20,9 +20,8 @@ const populateFetch = (definition, options) => { } else if (_.isEmpty(options.withRelated)) { options.withRelated = populateComponents(definition); } else { - options.withRelated = formatPopulateOptions( - definition, - options.withRelated + options.withRelated = formatPopulateOptions(definition, options.withRelated).concat( + populateComponents(definition) ); } }; @@ -173,9 +172,7 @@ const formatPopulateOptions = (definition, withRelated) => { continue; } - const assoc = tmpModel.associations.find( - association => association.alias === part - ); + const assoc = tmpModel.associations.find(association => association.alias === part); if (!assoc) return acc; diff --git a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js index 35c6d346ec..5b2bf27eae 100644 --- a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js +++ b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js @@ -9,7 +9,25 @@ let graphqlQuery; let modelsUtils; // utils -const selectFields = doc => _.pick(doc, ['id', 'name']); +const selectFields = doc => _.pick(doc, ['id', 'name', 'color']); + +const rgbColorComponent = { + attributes: { + name: { + type: 'text', + }, + red: { + type: 'integer', + }, + green: { + type: 'integer', + }, + blue: { + type: 'integer', + }, + }, + name: 'rgbColor', +}; const documentModel = { attributes: { @@ -37,6 +55,11 @@ const labelModel = { target: 'application::document.document', targetAttribute: 'labels', }, + color: { + type: 'component', + component: 'default.rgb-color', + repeatable: false, + }, }, connection: 'default', name: 'label', @@ -59,6 +82,7 @@ describe('Test Graphql Relations API End to End', () => { modelsUtils = createModelsUtils({ rq }); + await modelsUtils.createComponent(rgbColorComponent); await modelsUtils.createContentTypes([documentModel, labelModel]); }, 60000); @@ -69,7 +93,11 @@ describe('Test Graphql Relations API End to End', () => { labels: [], documents: [], }; - const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }]; + const labelsPayload = [ + { name: 'label 1', color: null }, + { name: 'label 2', color: null }, + { name: 'labelWithColor', color: { name: 'tomato', red: 255, green: 99, blue: 71 } }, + ]; const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }]; test.each(labelsPayload)('Create label %o', async label => { @@ -79,6 +107,12 @@ describe('Test Graphql Relations API End to End', () => { createLabel(input: $input) { label { name + color { + name + red + green + blue + } } } } @@ -90,10 +124,8 @@ describe('Test Graphql Relations API End to End', () => { }, }); - const { body } = res; - expect(res.statusCode).toBe(200); - expect(body).toEqual({ + expect(res.body).toEqual({ data: { createLabel: { label, @@ -109,6 +141,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } } } `, @@ -124,52 +162,57 @@ describe('Test Graphql Relations API End to End', () => { }); // assign for later use - data.labels = res.body.data.labels; + data.labels = data.labels.concat(res.body.data.labels); }); - test.each(documentsPayload)( - 'Create document linked to every labels %o', - async document => { - const res = await graphqlQuery({ - query: /* GraphQL */ ` - mutation createDocument($input: createDocumentInput) { - createDocument(input: $input) { - document { + test.each(documentsPayload)('Create document linked to every labels %o', async document => { + const res = await graphqlQuery({ + query: /* GraphQL */ ` + mutation createDocument($input: createDocumentInput) { + createDocument(input: $input) { + document { + name + labels { + id name - labels { - id + color { name + red + green + blue } } } } - `, - variables: { - input: { - data: { - ...document, - labels: data.labels.map(t => t.id), - }, + } + `, + variables: { + input: { + data: { + ...document, + labels: data.labels.map(t => t.id), }, }, - }); + }, + }); - const { body } = res; + const { body } = res; - expect(res.statusCode).toBe(200); + // console.log('-----', JSON.stringify(res.body, null, 2)); - expect(body).toMatchObject({ - data: { - createDocument: { - document: { - ...selectFields(document), - labels: expect.arrayContaining(data.labels.map(selectFields)), - }, + expect(res.statusCode).toBe(200); + + expect(body).toMatchObject({ + data: { + createDocument: { + document: { + ...selectFields(document), + labels: expect.arrayContaining(data.labels.map(selectFields)), }, }, - }); - } - ); + }, + }); + }); test('List documents with labels', async () => { const res = await graphqlQuery({ @@ -181,6 +224,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } } } } @@ -212,6 +261,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } documents { id name @@ -229,9 +284,7 @@ describe('Test Graphql Relations API End to End', () => { labels: expect.arrayContaining( data.labels.map(label => ({ ...selectFields(label), - documents: expect.arrayContaining( - data.documents.map(selectFields) - ), + documents: expect.arrayContaining(data.documents.map(selectFields)), })) ), }, @@ -251,6 +304,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } } } } @@ -277,6 +336,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } } } } @@ -316,6 +381,12 @@ describe('Test Graphql Relations API End to End', () => { label { id name + color { + name + red + green + blue + } } } } @@ -350,6 +421,12 @@ describe('Test Graphql Relations API End to End', () => { labels { id name + color { + name + red + green + blue + } } } } From 734ce70df434680f3299bf371bda7fce94c8c749 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Wed, 18 Mar 2020 22:29:06 -0700 Subject: [PATCH 10/20] Minor fixes to DO one-click docs Signed-off-by: Derrick Mehaffy --- docs/3.0.0-beta.x/installation/digitalocean-one-click.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md index 01337aeb63..a5f0d14e3c 100644 --- a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md +++ b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md @@ -27,7 +27,11 @@ To create a project head over to the Strapi [listing on the marketplace](https:/ ### Step 3: Visit your app -Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets). After the droplet has started, it will take a few more minutes to finish the Strapi installation. +Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets). + +::: warning +After the droplet has started, it will take a few more minutes to finish the Strapi installation. +::: From here you will see the public ipv4 address that you can use to visit your Strapi application, just open that in a browser and it should ask you to create your first administrator! @@ -107,7 +111,7 @@ Please note that with this application it is intially created and ran in the `de ## Using the Service Account -By default the Strapi application will be running under a "service account", this is an account that is extremely limited into what it can do and access. The purpose of using a service account is to project your system from security threats. +By default the Strapi application will be running under a "service account", this is an account that is extremely limited into what it can do and access. The purpose of using a service account is to help protect your system from security threats. ### Accessing the service account From d64af252bf251a4229e853833f390b79891e97bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Thu, 19 Mar 2020 18:29:30 +0100 Subject: [PATCH 11/20] fix generate:model --plugin path + little refacto of code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pierre Noël --- packages/strapi-generate-api/lib/before.js | 22 +- .../strapi-generate-controller/lib/before.js | 13 +- packages/strapi-generate-model/lib/before.js | 33 +-- packages/strapi-generate-model/lib/index.js | 17 +- packages/strapi-generate-plugin/lib/before.js | 22 +- packages/strapi-generate-plugin/lib/index.js | 16 +- .../templates/controller.template | 2 +- packages/strapi-generate-policy/lib/before.js | 18 +- .../strapi-generate-service/lib/before.js | 25 +- packages/strapi-generate-service/lib/index.js | 13 +- packages/strapi-generate/lib/generate.js | 240 ++++++++++-------- packages/strapi/lib/commands/generate.js | 10 +- 12 files changed, 210 insertions(+), 221 deletions(-) diff --git a/packages/strapi-generate-api/lib/before.js b/packages/strapi-generate-api/lib/before.js index 921a988803..48989848b5 100644 --- a/packages/strapi-generate-api/lib/before.js +++ b/packages/strapi-generate-api/lib/before.js @@ -58,12 +58,6 @@ module.exports = (scope, cb) => { filePath, }); - // Humanize output. - _.defaults(scope, { - humanizeId: name, - humanizedPath: filePath, - }); - // Validate optional attribute arguments. const invalidAttributes = []; @@ -79,9 +73,7 @@ module.exports = (scope, cb) => { // Handle invalid attributes. if (!parts[1] || !parts[0]) { - invalidAttributes.push( - 'Error: Invalid attribute notation `' + attribute + '`.' - ); + invalidAttributes.push('Error: Invalid attribute notation `' + attribute + '`.'); return; } @@ -126,9 +118,7 @@ module.exports = (scope, cb) => { : _.snakeCase(pluralize(name)); // Set description - scope.description = _.has(scope.args, 'description') - ? scope.args.description - : ''; + scope.description = _.has(scope.args, 'description') ? scope.args.description : ''; // Get default connection try { @@ -137,13 +127,7 @@ module.exports = (scope, cb) => { try { scope.connection = JSON.parse( fs.readFileSync( - path.resolve( - scope.rootPath, - 'config', - 'environments', - environment, - 'database.json' - ) + path.resolve(scope.rootPath, 'config', 'environments', environment, 'database.json') ) ).defaultConnection; } catch (err) { diff --git a/packages/strapi-generate-controller/lib/before.js b/packages/strapi-generate-controller/lib/before.js index 8067b0b2cf..82ec1a232f 100644 --- a/packages/strapi-generate-controller/lib/before.js +++ b/packages/strapi-generate-controller/lib/before.js @@ -22,9 +22,12 @@ module.exports = (scope, cb) => { ); } + // Format `id`. + const name = scope.name || _.trim(_.camelCase(scope.id)); + // `scope.args` are the raw command line arguments. _.defaults(scope, { - id: _.trim(_.deburr(scope.id)), + name, api: scope.id, }); @@ -43,7 +46,7 @@ module.exports = (scope, cb) => { } else if (scope.args.extend) { filePath = `./extensions/${scope.args.extend}/controllers`; } else { - filePath = `./api/${scope.id}/controllers`; + filePath = `./api/${name}/controllers`; } // Take another pass to take advantage of the defaults absorbed in previous passes. @@ -53,12 +56,6 @@ module.exports = (scope, cb) => { filename: scope.globalID + scope.ext, }); - // Humanize output. - _.defaults(scope, { - humanizeId: _.camelCase(scope.id).toLowerCase(), - humanizedPath: '`' + scope.filePath + '`', - }); - // Trigger callback with no error to proceed. return cb(); }; diff --git a/packages/strapi-generate-model/lib/before.js b/packages/strapi-generate-model/lib/before.js index e24d0c1c5c..55ac5a0983 100644 --- a/packages/strapi-generate-model/lib/before.js +++ b/packages/strapi-generate-model/lib/before.js @@ -34,27 +34,30 @@ module.exports = (scope, cb) => { ); } + // Format `id`. + const name = scope.name || _.trim(_.camelCase(scope.id)); + // `scope.args` are the raw command line arguments. _.defaults(scope, { - id: _.trim(_.deburr(scope.id)), + name, idPluralized: pluralize.plural(_.trim(_.deburr(scope.id))), environment: process.env.NODE_ENV || 'development', }); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(_.camelCase(scope.id)), + globalID: _.upperFirst(name), ext: '.js', }); // Determine the destination path. let filePath; if (scope.args.api) { - filePath = `./api/${scope.args.api}`; + filePath = `./api/${scope.args.api}/models`; } else if (scope.args.plugin) { filePath = `./plugins/${scope.args.plugin}/models`; } else { - filePath = `./api/${scope.id}`; + filePath = `./api/${name}/models`; } // Take another pass to take advantage of the defaults absorbed in previous passes. @@ -65,12 +68,6 @@ module.exports = (scope, cb) => { filenameSettings: scope.globalID + '.settings.json', }); - // Humanize output. - _.defaults(scope, { - humanizeId: _.camelCase(scope.id).toLowerCase(), - humanizedPath: '`' + scope.filePath + '`', - }); - // Validate optional attribute arguments. const invalidAttributes = []; @@ -82,9 +79,7 @@ module.exports = (scope, cb) => { // Handle invalid attributes. if (!parts[1] || !parts[0]) { - invalidAttributes.push( - 'Error: Invalid attribute notation `' + attribute + '`.' - ); + invalidAttributes.push('Error: Invalid attribute notation `' + attribute + '`.'); return; } @@ -100,9 +95,7 @@ module.exports = (scope, cb) => { : undefined; // Set description - scope.description = _.has(scope.args, 'description') - ? scope.args.description - : undefined; + scope.description = _.has(scope.args, 'description') ? scope.args.description : undefined; // Handle invalid action arguments. // Send back invalidActions. @@ -141,13 +134,7 @@ module.exports = (scope, cb) => { scope.args.connection || JSON.parse( fs.readFileSync( - path.resolve( - scope.rootPath, - 'config', - 'environments', - scope.environment, - 'database.json' - ) + path.resolve(scope.rootPath, 'config', 'environments', scope.environment, 'database.json') ) ).defaultConnection || ''; diff --git a/packages/strapi-generate-model/lib/index.js b/packages/strapi-generate-model/lib/index.js index 3266bf096c..564116ea33 100644 --- a/packages/strapi-generate-model/lib/index.js +++ b/packages/strapi-generate-model/lib/index.js @@ -16,7 +16,10 @@ module.exports = { templatesDirectory: scope => { try { // Try to reach the path. If it fail, throw an error. - fs.accessSync(path.resolve(__dirname, '..', 'templates', scope.args.tpl), fs.constants.R_OK | fs.constants.W_OK); + fs.accessSync( + path.resolve(__dirname, '..', 'templates', scope.args.tpl), + fs.constants.R_OK | fs.constants.W_OK + ); return path.resolve(__dirname, '..', 'templates', scope.args.tpl); } catch (e) { @@ -26,11 +29,11 @@ module.exports = { }, before: require('./before'), targets: { - ':filePath/models/:filename': { - template: 'model.template' + ':filePath/:filename': { + template: 'model.template', }, - ':filePath/models/:filenameSettings': { - template: 'model.settings.template' - } - } + ':filePath/:filenameSettings': { + template: 'model.settings.template', + }, + }, }; diff --git a/packages/strapi-generate-plugin/lib/before.js b/packages/strapi-generate-plugin/lib/before.js index 67a50d3630..aba53cc2f7 100644 --- a/packages/strapi-generate-plugin/lib/before.js +++ b/packages/strapi-generate-plugin/lib/before.js @@ -22,20 +22,18 @@ module.exports = (scope, cb) => { return cb.invalid('Usage: `$ strapi generate:plugin pluginName`'); } - // `scope.args` are the raw command line arguments. - _.defaults(scope, { - id: _.trim(_.deburr(scope.id)), - }); + // Format `id`. + const name = scope.name || _.trim(_.camelCase(scope.id)); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(_.camelCase(scope.id)), + globalID: _.upperFirst(name), ext: '.js', }); // Plugin info. _.defaults(scope, { - name: scope.args.name || scope.id, + name, author: scope.author || 'A Strapi developer', email: scope.email || '', year: new Date().getFullYear(), @@ -45,22 +43,14 @@ module.exports = (scope, cb) => { // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { filename: `${scope.globalID}${scope.ext}`, - }); - - // Humanize output. - _.defaults(scope, { - humanizeId: scope.id.toLowerCase(), - humanizedPath: '`./plugins`', + filePath: './plugins', }); const pluginDir = path.resolve(scope.rootPath, 'plugins'); fs.ensureDirSync(pluginDir); // Copy the admin files. - fs.copySync( - path.resolve(__dirname, '..', 'files'), - path.resolve(pluginDir, scope.humanizeId) - ); + fs.copySync(path.resolve(__dirname, '..', 'files'), path.resolve(pluginDir, name)); // Trigger callback with no error to proceed. return cb.success(); diff --git a/packages/strapi-generate-plugin/lib/index.js b/packages/strapi-generate-plugin/lib/index.js index 9404c2cdf6..e2c0e692f0 100644 --- a/packages/strapi-generate-plugin/lib/index.js +++ b/packages/strapi-generate-plugin/lib/index.js @@ -19,42 +19,42 @@ module.exports = { templatesDirectory: path.resolve(__dirname, '..', 'templates'), before: require('./before'), targets: { - 'plugins/:humanizeId/.gitignore': { + 'plugins/:name/.gitignore': { copy: 'gitignore', }, // Use the default `controller` file as a template for // every generated controller. - 'plugins/:humanizeId/controllers/:filename': { + 'plugins/:name/controllers/:filename': { template: 'controller.template', }, // every generated controller. - 'plugins/:humanizeId/services/:filename': { + 'plugins/:name/services/:filename': { template: 'service.template', }, // Generate routes. - 'plugins/:humanizeId/config/routes.json': { + 'plugins/:name/config/routes.json': { jsonfile: routesJSON, }, // Main package. - 'plugins/:humanizeId/package.json': { + 'plugins/:name/package.json': { jsonfile: packageJSON, }, // Copy dot files. - 'plugins/:humanizeId/.editorconfig': { + 'plugins/:name/.editorconfig': { copy: 'editorconfig', }, - 'plugins/:humanizeId/.gitattributes': { + 'plugins/:name/.gitattributes': { copy: 'gitattributes', }, // Copy Markdown files with some information. - 'plugins/:humanizeId/README.md': { + 'plugins/:name/README.md': { template: 'README.md', }, }, diff --git a/packages/strapi-generate-plugin/templates/controller.template b/packages/strapi-generate-plugin/templates/controller.template index 5db3e2d6e8..6d32cf8c7f 100644 --- a/packages/strapi-generate-plugin/templates/controller.template +++ b/packages/strapi-generate-plugin/templates/controller.template @@ -3,7 +3,7 @@ /** * <%= filename %> controller * - * @description: A set of functions called "actions" of the `<%= humanizeId %>` plugin. + * @description: A set of functions called "actions" of the `<%= name %>` plugin. */ module.exports = { diff --git a/packages/strapi-generate-policy/lib/before.js b/packages/strapi-generate-policy/lib/before.js index a8649dc36b..0178dbaebb 100644 --- a/packages/strapi-generate-policy/lib/before.js +++ b/packages/strapi-generate-policy/lib/before.js @@ -18,9 +18,14 @@ const _ = require('lodash'); /* eslint-disable prefer-template */ module.exports = (scope, cb) => { if (!scope.rootPath || !scope.id) { - return cb.invalid('Usage: `$ strapi generate:policy policyName --api apiName --plugin pluginName`'); + return cb.invalid( + 'Usage: `$ strapi generate:policy policyName --api apiName --plugin pluginName`' + ); } + // Format `id`. + const name = scope.name || _.trim(_.camelCase(scope.id)); + let filePath; if (scope.args.api) { filePath = `./api/${scope.args.api}/config/policies`; @@ -32,19 +37,14 @@ module.exports = (scope, cb) => { // Determine default values based on the available scope. _.defaults(scope, { - ext: '.js' + ext: '.js', }); // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { + name, filePath, - filename: scope.id + scope.ext - }); - - // Humanize output. - _.defaults(scope, { - humanizeId: scope.id, - humanizedPath: '`' + scope.filePath + '`' + filename: name + scope.ext, }); // Trigger callback with no error to proceed. diff --git a/packages/strapi-generate-service/lib/before.js b/packages/strapi-generate-service/lib/before.js index 10e1008cfa..0304c665e6 100644 --- a/packages/strapi-generate-service/lib/before.js +++ b/packages/strapi-generate-service/lib/before.js @@ -18,19 +18,24 @@ const _ = require('lodash'); /* eslint-disable prefer-template */ module.exports = (scope, cb) => { if (!scope.rootPath || !scope.id) { - return cb.invalid('Usage: `$ strapi generate:service serviceName --api apiName --plugin pluginName`'); + return cb.invalid( + 'Usage: `$ strapi generate:service serviceName --api apiName --plugin pluginName`' + ); } + // Format `id`. + const name = scope.name || _.trim(_.camelCase(scope.id)); + // `scope.args` are the raw command line arguments. _.defaults(scope, { - id: _.trim(_.deburr(scope.id)), - api: scope.args.api || scope.id + name, + api: scope.args.api || scope.id, }); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(_.camelCase(scope.id)), - ext: '.js' + globalID: _.upperFirst(name), + ext: '.js', }); // Determine the destination path. @@ -40,20 +45,14 @@ module.exports = (scope, cb) => { } else if (scope.args.plugin) { filePath = `./plugins/${scope.args.plugin}/services`; } else { - filePath = `./api/${scope.id}/services`; + filePath = `./api/${name}/services`; } // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { rootPath: scope.rootPath, filePath, - filename: scope.globalID + scope.ext - }); - - // Humanize output. - _.defaults(scope, { - humanizeId: _.camelCase(scope.id).toLowerCase(), - humanizedPath: '`' + scope.filePath + '`' + filename: scope.globalID + scope.ext, }); // Trigger callback with no error to proceed. diff --git a/packages/strapi-generate-service/lib/index.js b/packages/strapi-generate-service/lib/index.js index d1b73b5982..87452b5f03 100644 --- a/packages/strapi-generate-service/lib/index.js +++ b/packages/strapi-generate-service/lib/index.js @@ -16,7 +16,10 @@ module.exports = { templatesDirectory: scope => { try { // Try to reach the path. If it fail, throw an error. - fs.accessSync(path.resolve(__dirname, '..', 'templates', scope.args.tpl), fs.constants.R_OK | fs.constants.W_OK); + fs.accessSync( + path.resolve(__dirname, '..', 'templates', scope.args.tpl), + fs.constants.R_OK | fs.constants.W_OK + ); return path.resolve(__dirname, '..', 'templates', scope.args.tpl); } catch (e) { @@ -26,8 +29,8 @@ module.exports = { }, before: require('./before'), targets: { - 'api/:api/services/:filename': { - template: 'service.template' - } - } + ':filePath/:filename': { + template: 'service.template', + }, + }, }; diff --git a/packages/strapi-generate/lib/generate.js b/packages/strapi-generate/lib/generate.js index 80916c7416..d47ed93544 100644 --- a/packages/strapi-generate/lib/generate.js +++ b/packages/strapi-generate/lib/generate.js @@ -30,7 +30,7 @@ function generate(generator, scope, cb) { const sb = reportback.extend(cb, { error: cb.error, invalid: cb.invalid, - alreadyExists: 'error' + alreadyExists: 'error', }); // Resolve string shorthand for generator defs @@ -38,119 +38,151 @@ function generate(generator, scope, cb) { if (typeof generator === 'string') { const generatorName = generator; generator = { - generator: generatorName + generator: generatorName, }; } // Run the generator's `before()` method proceeding. - generator.before(scope, reportback.extend({ - error: sb.error, - invalid: sb.invalid, - success: () => { + generator.before( + scope, + reportback.extend({ + error: sb.error, + invalid: sb.invalid, + success: () => { + // Process all of the generator's targets concurrently. + async.each( + Object.keys(generator.targets), + (keyPath, asyncEachCb) => { + const asyncEachSb = reportback.extend(asyncEachCb); - // Process all of the generator's targets concurrently. - async.each(Object.keys(generator.targets), (keyPath, asyncEachCb) => { - const asyncEachSb = reportback.extend(asyncEachCb); - - // Create a new scope object for this target, - // with references to the important bits of the original - // (depth will be passed-by-value, but that's what we want). - // Then generate the target, passing along a reference to - // the base `generate` method to allow for recursive generators. - const target = generator.targets[keyPath]; - if (!target) { - return asyncEachSb(new Error('Error: Invalid target: {"' + keyPath + '": ' + util.inspect(target) + '}')); - } - - // Input tolerance. - if (keyPath === '') { - keyPath = '.'; - } - - // Interpret `keyPath` using Express's parameterized route conventions, - // first parsing params, then replacing them with their proper values from scope. - const params = []; - pathRegexp(keyPath, params); - let err; - const parsedKeyPath = _.reduce(params, (memoKeyPath, param) => { - if (err) { - return false; - } - - try { - const paramMatchExpr = ':' + param.name; - let actualParamValue = scope[param.name]; - if (!actualParamValue) { - err = new Error( - 'generator error:\n' + - 'A scope variable (`' + param.name + '`) was referenced in target: `' + memoKeyPath + '`,\n' + - 'but `' + param.name + '` does not exist in the generator\'s scope.' + // Create a new scope object for this target, + // with references to the important bits of the original + // (depth will be passed-by-value, but that's what we want). + // Then generate the target, passing along a reference to + // the base `generate` method to allow for recursive generators. + const target = generator.targets[keyPath]; + if (!target) { + return asyncEachSb( + new Error( + 'Error: Invalid target: {"' + keyPath + '": ' + util.inspect(target) + '}' + ) ); - return false; } - actualParamValue = String(actualParamValue); - return memoKeyPath.replace(paramMatchExpr, actualParamValue); - } catch (e) { - err = new Error('Error: Could not parse target key ' + memoKeyPath); - err.message = e; - return false; + // Input tolerance. + if (keyPath === '') { + keyPath = '.'; + } + + // Interpret `keyPath` using Express's parameterized route conventions, + // first parsing params, then replacing them with their proper values from scope. + const params = []; + pathRegexp(keyPath, params); + let err; + const parsedKeyPath = _.reduce( + params, + (memoKeyPath, param) => { + if (err) { + return false; + } + + try { + const paramMatchExpr = ':' + param.name; + let actualParamValue = scope[param.name]; + if (!actualParamValue) { + err = new Error( + 'generator error:\n' + + 'A scope variable (`' + + param.name + + '`) was referenced in target: `' + + memoKeyPath + + '`,\n' + + 'but `' + + param.name + + "` does not exist in the generator's scope." + ); + return false; + } + actualParamValue = String(actualParamValue); + + return memoKeyPath.replace(paramMatchExpr, actualParamValue); + } catch (e) { + err = new Error('Error: Could not parse target key ' + memoKeyPath); + err.message = e; + return false; + } + }, + keyPath + ); + if (!parsedKeyPath) { + return asyncEachSb(err); + } + + // Create path from `rootPath` to `keyPath` to use as the `rootPath` + // for any generators or helpers in this target + // (use a copy so that child generators don't mutate the scope). + const targetScope = _.merge({}, scope, { + rootPath: path.resolve(scope.rootPath, parsedKeyPath), + + // Include reference to original keypath for error reporting. + keyPath, + }); + + // If `target` is an array, run each item. + if (_.isArray(target)) { + async.eachSeries( + target, + (targetItem, asyncEachSeriesCb) => { + generateTarget( + { + target: targetItem, + parent: generator, + scope: _.cloneDeep(targetScope), + recursiveGenerate: generate, + }, + asyncEachSeriesCb + ); + }, + asyncEachSb + ); + return; + } + + // Otherwise, just run the single target generator/helper. + generateTarget( + { + target, + parent: generator, + scope: targetScope, + recursiveGenerate: generate, + }, + asyncEachSb + ); + }, + + err => { + // Expose a `error` handler in generators. + if (err) { + const errorFn = + generator.error || + function defaultError(err, scope, _cb) { + return _cb(err); + }; + return errorFn(err, scope, sb); + } + + // Expose a `after` handler in generators (on success only). + const afterFn = + generator.after || + function defaultAfter(scope, _cb) { + return _cb(); + }; + return afterFn(scope, sb); } - }, keyPath); - if (!parsedKeyPath) { - return asyncEachSb(err); - } - - // Create path from `rootPath` to `keyPath` to use as the `rootPath` - // for any generators or helpers in this target - // (use a copy so that child generators don't mutate the scope). - const targetScope = _.merge({}, scope, { - rootPath: path.resolve(scope.rootPath, parsedKeyPath), - - // Include reference to original keypath for error reporting. - keyPath - }); - - // If `target` is an array, run each item. - if (_.isArray(target)) { - async.eachSeries(target, (targetItem, asyncEachSeriesCb) => { - generateTarget({ - target: targetItem, - parent: generator, - scope: _.cloneDeep(targetScope), - recursiveGenerate: generate - }, asyncEachSeriesCb); - }, asyncEachSb); - return; - } - - // Otherwise, just run the single target generator/helper. - generateTarget({ - target, - parent: generator, - scope: targetScope, - recursiveGenerate: generate - }, asyncEachSb); + ); }, - - err => { - - // Expose a `error` handler in generators. - if (err) { - const errorFn = generator.error || function defaultError(err, scope, _cb) { - return _cb(err); - }; - return errorFn(err, scope, sb); - } - - // Expose a `after` handler in generators (on success only). - const afterFn = generator.after || function defaultAfter(scope, _cb) { - return _cb(); - }; - return afterFn(scope, sb); - }); - } - })); + }) + ); } module.exports = generate; diff --git a/packages/strapi/lib/commands/generate.js b/packages/strapi/lib/commands/generate.js index 641fb2bb71..7866f02d99 100644 --- a/packages/strapi/lib/commands/generate.js +++ b/packages/strapi/lib/commands/generate.js @@ -56,14 +56,8 @@ module.exports = function(id, cliArguments) { if (scope.generatorType !== 'new') { logger.info( - 'Generated a new ' + - scope.generatorType + - ' `' + - scope.humanizeId + - '` at ' + - scope.humanizedPath + - '.' - ); // eslint-disable-line prefer-template + `Generated a new ${scope.generatorType} \`${scope.name}\` at \`${scope.filePath}\`.` + ); } process.exit(0); From 262d3e43645203a2ed9e2549ffa8083909c523f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Thu, 19 Mar 2020 18:33:13 +0100 Subject: [PATCH 12/20] Revert "fix generate:model --plugin path + little refacto of code" This reverts commit d64af252bf251a4229e853833f390b79891e97bb. --- packages/strapi-generate-api/lib/before.js | 22 +- .../strapi-generate-controller/lib/before.js | 13 +- packages/strapi-generate-model/lib/before.js | 33 ++- packages/strapi-generate-model/lib/index.js | 17 +- packages/strapi-generate-plugin/lib/before.js | 22 +- packages/strapi-generate-plugin/lib/index.js | 16 +- .../templates/controller.template | 2 +- packages/strapi-generate-policy/lib/before.js | 18 +- .../strapi-generate-service/lib/before.js | 25 +- packages/strapi-generate-service/lib/index.js | 13 +- packages/strapi-generate/lib/generate.js | 240 ++++++++---------- packages/strapi/lib/commands/generate.js | 10 +- 12 files changed, 221 insertions(+), 210 deletions(-) diff --git a/packages/strapi-generate-api/lib/before.js b/packages/strapi-generate-api/lib/before.js index 48989848b5..921a988803 100644 --- a/packages/strapi-generate-api/lib/before.js +++ b/packages/strapi-generate-api/lib/before.js @@ -58,6 +58,12 @@ module.exports = (scope, cb) => { filePath, }); + // Humanize output. + _.defaults(scope, { + humanizeId: name, + humanizedPath: filePath, + }); + // Validate optional attribute arguments. const invalidAttributes = []; @@ -73,7 +79,9 @@ module.exports = (scope, cb) => { // Handle invalid attributes. if (!parts[1] || !parts[0]) { - invalidAttributes.push('Error: Invalid attribute notation `' + attribute + '`.'); + invalidAttributes.push( + 'Error: Invalid attribute notation `' + attribute + '`.' + ); return; } @@ -118,7 +126,9 @@ module.exports = (scope, cb) => { : _.snakeCase(pluralize(name)); // Set description - scope.description = _.has(scope.args, 'description') ? scope.args.description : ''; + scope.description = _.has(scope.args, 'description') + ? scope.args.description + : ''; // Get default connection try { @@ -127,7 +137,13 @@ module.exports = (scope, cb) => { try { scope.connection = JSON.parse( fs.readFileSync( - path.resolve(scope.rootPath, 'config', 'environments', environment, 'database.json') + path.resolve( + scope.rootPath, + 'config', + 'environments', + environment, + 'database.json' + ) ) ).defaultConnection; } catch (err) { diff --git a/packages/strapi-generate-controller/lib/before.js b/packages/strapi-generate-controller/lib/before.js index 82ec1a232f..8067b0b2cf 100644 --- a/packages/strapi-generate-controller/lib/before.js +++ b/packages/strapi-generate-controller/lib/before.js @@ -22,12 +22,9 @@ module.exports = (scope, cb) => { ); } - // Format `id`. - const name = scope.name || _.trim(_.camelCase(scope.id)); - // `scope.args` are the raw command line arguments. _.defaults(scope, { - name, + id: _.trim(_.deburr(scope.id)), api: scope.id, }); @@ -46,7 +43,7 @@ module.exports = (scope, cb) => { } else if (scope.args.extend) { filePath = `./extensions/${scope.args.extend}/controllers`; } else { - filePath = `./api/${name}/controllers`; + filePath = `./api/${scope.id}/controllers`; } // Take another pass to take advantage of the defaults absorbed in previous passes. @@ -56,6 +53,12 @@ module.exports = (scope, cb) => { filename: scope.globalID + scope.ext, }); + // Humanize output. + _.defaults(scope, { + humanizeId: _.camelCase(scope.id).toLowerCase(), + humanizedPath: '`' + scope.filePath + '`', + }); + // Trigger callback with no error to proceed. return cb(); }; diff --git a/packages/strapi-generate-model/lib/before.js b/packages/strapi-generate-model/lib/before.js index 55ac5a0983..e24d0c1c5c 100644 --- a/packages/strapi-generate-model/lib/before.js +++ b/packages/strapi-generate-model/lib/before.js @@ -34,30 +34,27 @@ module.exports = (scope, cb) => { ); } - // Format `id`. - const name = scope.name || _.trim(_.camelCase(scope.id)); - // `scope.args` are the raw command line arguments. _.defaults(scope, { - name, + id: _.trim(_.deburr(scope.id)), idPluralized: pluralize.plural(_.trim(_.deburr(scope.id))), environment: process.env.NODE_ENV || 'development', }); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(name), + globalID: _.upperFirst(_.camelCase(scope.id)), ext: '.js', }); // Determine the destination path. let filePath; if (scope.args.api) { - filePath = `./api/${scope.args.api}/models`; + filePath = `./api/${scope.args.api}`; } else if (scope.args.plugin) { filePath = `./plugins/${scope.args.plugin}/models`; } else { - filePath = `./api/${name}/models`; + filePath = `./api/${scope.id}`; } // Take another pass to take advantage of the defaults absorbed in previous passes. @@ -68,6 +65,12 @@ module.exports = (scope, cb) => { filenameSettings: scope.globalID + '.settings.json', }); + // Humanize output. + _.defaults(scope, { + humanizeId: _.camelCase(scope.id).toLowerCase(), + humanizedPath: '`' + scope.filePath + '`', + }); + // Validate optional attribute arguments. const invalidAttributes = []; @@ -79,7 +82,9 @@ module.exports = (scope, cb) => { // Handle invalid attributes. if (!parts[1] || !parts[0]) { - invalidAttributes.push('Error: Invalid attribute notation `' + attribute + '`.'); + invalidAttributes.push( + 'Error: Invalid attribute notation `' + attribute + '`.' + ); return; } @@ -95,7 +100,9 @@ module.exports = (scope, cb) => { : undefined; // Set description - scope.description = _.has(scope.args, 'description') ? scope.args.description : undefined; + scope.description = _.has(scope.args, 'description') + ? scope.args.description + : undefined; // Handle invalid action arguments. // Send back invalidActions. @@ -134,7 +141,13 @@ module.exports = (scope, cb) => { scope.args.connection || JSON.parse( fs.readFileSync( - path.resolve(scope.rootPath, 'config', 'environments', scope.environment, 'database.json') + path.resolve( + scope.rootPath, + 'config', + 'environments', + scope.environment, + 'database.json' + ) ) ).defaultConnection || ''; diff --git a/packages/strapi-generate-model/lib/index.js b/packages/strapi-generate-model/lib/index.js index 564116ea33..3266bf096c 100644 --- a/packages/strapi-generate-model/lib/index.js +++ b/packages/strapi-generate-model/lib/index.js @@ -16,10 +16,7 @@ module.exports = { templatesDirectory: scope => { try { // Try to reach the path. If it fail, throw an error. - fs.accessSync( - path.resolve(__dirname, '..', 'templates', scope.args.tpl), - fs.constants.R_OK | fs.constants.W_OK - ); + fs.accessSync(path.resolve(__dirname, '..', 'templates', scope.args.tpl), fs.constants.R_OK | fs.constants.W_OK); return path.resolve(__dirname, '..', 'templates', scope.args.tpl); } catch (e) { @@ -29,11 +26,11 @@ module.exports = { }, before: require('./before'), targets: { - ':filePath/:filename': { - template: 'model.template', + ':filePath/models/:filename': { + template: 'model.template' }, - ':filePath/:filenameSettings': { - template: 'model.settings.template', - }, - }, + ':filePath/models/:filenameSettings': { + template: 'model.settings.template' + } + } }; diff --git a/packages/strapi-generate-plugin/lib/before.js b/packages/strapi-generate-plugin/lib/before.js index aba53cc2f7..67a50d3630 100644 --- a/packages/strapi-generate-plugin/lib/before.js +++ b/packages/strapi-generate-plugin/lib/before.js @@ -22,18 +22,20 @@ module.exports = (scope, cb) => { return cb.invalid('Usage: `$ strapi generate:plugin pluginName`'); } - // Format `id`. - const name = scope.name || _.trim(_.camelCase(scope.id)); + // `scope.args` are the raw command line arguments. + _.defaults(scope, { + id: _.trim(_.deburr(scope.id)), + }); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(name), + globalID: _.upperFirst(_.camelCase(scope.id)), ext: '.js', }); // Plugin info. _.defaults(scope, { - name, + name: scope.args.name || scope.id, author: scope.author || 'A Strapi developer', email: scope.email || '', year: new Date().getFullYear(), @@ -43,14 +45,22 @@ module.exports = (scope, cb) => { // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { filename: `${scope.globalID}${scope.ext}`, - filePath: './plugins', + }); + + // Humanize output. + _.defaults(scope, { + humanizeId: scope.id.toLowerCase(), + humanizedPath: '`./plugins`', }); const pluginDir = path.resolve(scope.rootPath, 'plugins'); fs.ensureDirSync(pluginDir); // Copy the admin files. - fs.copySync(path.resolve(__dirname, '..', 'files'), path.resolve(pluginDir, name)); + fs.copySync( + path.resolve(__dirname, '..', 'files'), + path.resolve(pluginDir, scope.humanizeId) + ); // Trigger callback with no error to proceed. return cb.success(); diff --git a/packages/strapi-generate-plugin/lib/index.js b/packages/strapi-generate-plugin/lib/index.js index e2c0e692f0..9404c2cdf6 100644 --- a/packages/strapi-generate-plugin/lib/index.js +++ b/packages/strapi-generate-plugin/lib/index.js @@ -19,42 +19,42 @@ module.exports = { templatesDirectory: path.resolve(__dirname, '..', 'templates'), before: require('./before'), targets: { - 'plugins/:name/.gitignore': { + 'plugins/:humanizeId/.gitignore': { copy: 'gitignore', }, // Use the default `controller` file as a template for // every generated controller. - 'plugins/:name/controllers/:filename': { + 'plugins/:humanizeId/controllers/:filename': { template: 'controller.template', }, // every generated controller. - 'plugins/:name/services/:filename': { + 'plugins/:humanizeId/services/:filename': { template: 'service.template', }, // Generate routes. - 'plugins/:name/config/routes.json': { + 'plugins/:humanizeId/config/routes.json': { jsonfile: routesJSON, }, // Main package. - 'plugins/:name/package.json': { + 'plugins/:humanizeId/package.json': { jsonfile: packageJSON, }, // Copy dot files. - 'plugins/:name/.editorconfig': { + 'plugins/:humanizeId/.editorconfig': { copy: 'editorconfig', }, - 'plugins/:name/.gitattributes': { + 'plugins/:humanizeId/.gitattributes': { copy: 'gitattributes', }, // Copy Markdown files with some information. - 'plugins/:name/README.md': { + 'plugins/:humanizeId/README.md': { template: 'README.md', }, }, diff --git a/packages/strapi-generate-plugin/templates/controller.template b/packages/strapi-generate-plugin/templates/controller.template index 6d32cf8c7f..5db3e2d6e8 100644 --- a/packages/strapi-generate-plugin/templates/controller.template +++ b/packages/strapi-generate-plugin/templates/controller.template @@ -3,7 +3,7 @@ /** * <%= filename %> controller * - * @description: A set of functions called "actions" of the `<%= name %>` plugin. + * @description: A set of functions called "actions" of the `<%= humanizeId %>` plugin. */ module.exports = { diff --git a/packages/strapi-generate-policy/lib/before.js b/packages/strapi-generate-policy/lib/before.js index 0178dbaebb..a8649dc36b 100644 --- a/packages/strapi-generate-policy/lib/before.js +++ b/packages/strapi-generate-policy/lib/before.js @@ -18,14 +18,9 @@ const _ = require('lodash'); /* eslint-disable prefer-template */ module.exports = (scope, cb) => { if (!scope.rootPath || !scope.id) { - return cb.invalid( - 'Usage: `$ strapi generate:policy policyName --api apiName --plugin pluginName`' - ); + return cb.invalid('Usage: `$ strapi generate:policy policyName --api apiName --plugin pluginName`'); } - // Format `id`. - const name = scope.name || _.trim(_.camelCase(scope.id)); - let filePath; if (scope.args.api) { filePath = `./api/${scope.args.api}/config/policies`; @@ -37,14 +32,19 @@ module.exports = (scope, cb) => { // Determine default values based on the available scope. _.defaults(scope, { - ext: '.js', + ext: '.js' }); // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { - name, filePath, - filename: name + scope.ext, + filename: scope.id + scope.ext + }); + + // Humanize output. + _.defaults(scope, { + humanizeId: scope.id, + humanizedPath: '`' + scope.filePath + '`' }); // Trigger callback with no error to proceed. diff --git a/packages/strapi-generate-service/lib/before.js b/packages/strapi-generate-service/lib/before.js index 0304c665e6..10e1008cfa 100644 --- a/packages/strapi-generate-service/lib/before.js +++ b/packages/strapi-generate-service/lib/before.js @@ -18,24 +18,19 @@ const _ = require('lodash'); /* eslint-disable prefer-template */ module.exports = (scope, cb) => { if (!scope.rootPath || !scope.id) { - return cb.invalid( - 'Usage: `$ strapi generate:service serviceName --api apiName --plugin pluginName`' - ); + return cb.invalid('Usage: `$ strapi generate:service serviceName --api apiName --plugin pluginName`'); } - // Format `id`. - const name = scope.name || _.trim(_.camelCase(scope.id)); - // `scope.args` are the raw command line arguments. _.defaults(scope, { - name, - api: scope.args.api || scope.id, + id: _.trim(_.deburr(scope.id)), + api: scope.args.api || scope.id }); // Determine default values based on the available scope. _.defaults(scope, { - globalID: _.upperFirst(name), - ext: '.js', + globalID: _.upperFirst(_.camelCase(scope.id)), + ext: '.js' }); // Determine the destination path. @@ -45,14 +40,20 @@ module.exports = (scope, cb) => { } else if (scope.args.plugin) { filePath = `./plugins/${scope.args.plugin}/services`; } else { - filePath = `./api/${name}/services`; + filePath = `./api/${scope.id}/services`; } // Take another pass to take advantage of the defaults absorbed in previous passes. _.defaults(scope, { rootPath: scope.rootPath, filePath, - filename: scope.globalID + scope.ext, + filename: scope.globalID + scope.ext + }); + + // Humanize output. + _.defaults(scope, { + humanizeId: _.camelCase(scope.id).toLowerCase(), + humanizedPath: '`' + scope.filePath + '`' }); // Trigger callback with no error to proceed. diff --git a/packages/strapi-generate-service/lib/index.js b/packages/strapi-generate-service/lib/index.js index 87452b5f03..d1b73b5982 100644 --- a/packages/strapi-generate-service/lib/index.js +++ b/packages/strapi-generate-service/lib/index.js @@ -16,10 +16,7 @@ module.exports = { templatesDirectory: scope => { try { // Try to reach the path. If it fail, throw an error. - fs.accessSync( - path.resolve(__dirname, '..', 'templates', scope.args.tpl), - fs.constants.R_OK | fs.constants.W_OK - ); + fs.accessSync(path.resolve(__dirname, '..', 'templates', scope.args.tpl), fs.constants.R_OK | fs.constants.W_OK); return path.resolve(__dirname, '..', 'templates', scope.args.tpl); } catch (e) { @@ -29,8 +26,8 @@ module.exports = { }, before: require('./before'), targets: { - ':filePath/:filename': { - template: 'service.template', - }, - }, + 'api/:api/services/:filename': { + template: 'service.template' + } + } }; diff --git a/packages/strapi-generate/lib/generate.js b/packages/strapi-generate/lib/generate.js index d47ed93544..80916c7416 100644 --- a/packages/strapi-generate/lib/generate.js +++ b/packages/strapi-generate/lib/generate.js @@ -30,7 +30,7 @@ function generate(generator, scope, cb) { const sb = reportback.extend(cb, { error: cb.error, invalid: cb.invalid, - alreadyExists: 'error', + alreadyExists: 'error' }); // Resolve string shorthand for generator defs @@ -38,151 +38,119 @@ function generate(generator, scope, cb) { if (typeof generator === 'string') { const generatorName = generator; generator = { - generator: generatorName, + generator: generatorName }; } // Run the generator's `before()` method proceeding. - generator.before( - scope, - reportback.extend({ - error: sb.error, - invalid: sb.invalid, - success: () => { - // Process all of the generator's targets concurrently. - async.each( - Object.keys(generator.targets), - (keyPath, asyncEachCb) => { - const asyncEachSb = reportback.extend(asyncEachCb); + generator.before(scope, reportback.extend({ + error: sb.error, + invalid: sb.invalid, + success: () => { - // Create a new scope object for this target, - // with references to the important bits of the original - // (depth will be passed-by-value, but that's what we want). - // Then generate the target, passing along a reference to - // the base `generate` method to allow for recursive generators. - const target = generator.targets[keyPath]; - if (!target) { - return asyncEachSb( - new Error( - 'Error: Invalid target: {"' + keyPath + '": ' + util.inspect(target) + '}' - ) - ); - } + // Process all of the generator's targets concurrently. + async.each(Object.keys(generator.targets), (keyPath, asyncEachCb) => { + const asyncEachSb = reportback.extend(asyncEachCb); - // Input tolerance. - if (keyPath === '') { - keyPath = '.'; - } + // Create a new scope object for this target, + // with references to the important bits of the original + // (depth will be passed-by-value, but that's what we want). + // Then generate the target, passing along a reference to + // the base `generate` method to allow for recursive generators. + const target = generator.targets[keyPath]; + if (!target) { + return asyncEachSb(new Error('Error: Invalid target: {"' + keyPath + '": ' + util.inspect(target) + '}')); + } - // Interpret `keyPath` using Express's parameterized route conventions, - // first parsing params, then replacing them with their proper values from scope. - const params = []; - pathRegexp(keyPath, params); - let err; - const parsedKeyPath = _.reduce( - params, - (memoKeyPath, param) => { - if (err) { - return false; - } + // Input tolerance. + if (keyPath === '') { + keyPath = '.'; + } - try { - const paramMatchExpr = ':' + param.name; - let actualParamValue = scope[param.name]; - if (!actualParamValue) { - err = new Error( - 'generator error:\n' + - 'A scope variable (`' + - param.name + - '`) was referenced in target: `' + - memoKeyPath + - '`,\n' + - 'but `' + - param.name + - "` does not exist in the generator's scope." - ); - return false; - } - actualParamValue = String(actualParamValue); - - return memoKeyPath.replace(paramMatchExpr, actualParamValue); - } catch (e) { - err = new Error('Error: Could not parse target key ' + memoKeyPath); - err.message = e; - return false; - } - }, - keyPath - ); - if (!parsedKeyPath) { - return asyncEachSb(err); - } - - // Create path from `rootPath` to `keyPath` to use as the `rootPath` - // for any generators or helpers in this target - // (use a copy so that child generators don't mutate the scope). - const targetScope = _.merge({}, scope, { - rootPath: path.resolve(scope.rootPath, parsedKeyPath), - - // Include reference to original keypath for error reporting. - keyPath, - }); - - // If `target` is an array, run each item. - if (_.isArray(target)) { - async.eachSeries( - target, - (targetItem, asyncEachSeriesCb) => { - generateTarget( - { - target: targetItem, - parent: generator, - scope: _.cloneDeep(targetScope), - recursiveGenerate: generate, - }, - asyncEachSeriesCb - ); - }, - asyncEachSb - ); - return; - } - - // Otherwise, just run the single target generator/helper. - generateTarget( - { - target, - parent: generator, - scope: targetScope, - recursiveGenerate: generate, - }, - asyncEachSb - ); - }, - - err => { - // Expose a `error` handler in generators. - if (err) { - const errorFn = - generator.error || - function defaultError(err, scope, _cb) { - return _cb(err); - }; - return errorFn(err, scope, sb); - } - - // Expose a `after` handler in generators (on success only). - const afterFn = - generator.after || - function defaultAfter(scope, _cb) { - return _cb(); - }; - return afterFn(scope, sb); + // Interpret `keyPath` using Express's parameterized route conventions, + // first parsing params, then replacing them with their proper values from scope. + const params = []; + pathRegexp(keyPath, params); + let err; + const parsedKeyPath = _.reduce(params, (memoKeyPath, param) => { + if (err) { + return false; } - ); + + try { + const paramMatchExpr = ':' + param.name; + let actualParamValue = scope[param.name]; + if (!actualParamValue) { + err = new Error( + 'generator error:\n' + + 'A scope variable (`' + param.name + '`) was referenced in target: `' + memoKeyPath + '`,\n' + + 'but `' + param.name + '` does not exist in the generator\'s scope.' + ); + return false; + } + actualParamValue = String(actualParamValue); + + return memoKeyPath.replace(paramMatchExpr, actualParamValue); + } catch (e) { + err = new Error('Error: Could not parse target key ' + memoKeyPath); + err.message = e; + return false; + } + }, keyPath); + if (!parsedKeyPath) { + return asyncEachSb(err); + } + + // Create path from `rootPath` to `keyPath` to use as the `rootPath` + // for any generators or helpers in this target + // (use a copy so that child generators don't mutate the scope). + const targetScope = _.merge({}, scope, { + rootPath: path.resolve(scope.rootPath, parsedKeyPath), + + // Include reference to original keypath for error reporting. + keyPath + }); + + // If `target` is an array, run each item. + if (_.isArray(target)) { + async.eachSeries(target, (targetItem, asyncEachSeriesCb) => { + generateTarget({ + target: targetItem, + parent: generator, + scope: _.cloneDeep(targetScope), + recursiveGenerate: generate + }, asyncEachSeriesCb); + }, asyncEachSb); + return; + } + + // Otherwise, just run the single target generator/helper. + generateTarget({ + target, + parent: generator, + scope: targetScope, + recursiveGenerate: generate + }, asyncEachSb); }, - }) - ); + + err => { + + // Expose a `error` handler in generators. + if (err) { + const errorFn = generator.error || function defaultError(err, scope, _cb) { + return _cb(err); + }; + return errorFn(err, scope, sb); + } + + // Expose a `after` handler in generators (on success only). + const afterFn = generator.after || function defaultAfter(scope, _cb) { + return _cb(); + }; + return afterFn(scope, sb); + }); + } + })); } module.exports = generate; diff --git a/packages/strapi/lib/commands/generate.js b/packages/strapi/lib/commands/generate.js index 7866f02d99..641fb2bb71 100644 --- a/packages/strapi/lib/commands/generate.js +++ b/packages/strapi/lib/commands/generate.js @@ -56,8 +56,14 @@ module.exports = function(id, cliArguments) { if (scope.generatorType !== 'new') { logger.info( - `Generated a new ${scope.generatorType} \`${scope.name}\` at \`${scope.filePath}\`.` - ); + 'Generated a new ' + + scope.generatorType + + ' `' + + scope.humanizeId + + '` at ' + + scope.humanizedPath + + '.' + ); // eslint-disable-line prefer-template } process.exit(0); From 83b326e9a557326dfa3b27ca824943ce2b718eaa Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 09:19:43 +0100 Subject: [PATCH 13/20] leftmenu alignment and border listbutton Signed-off-by: Virginie Ky --- .../lib/src/components/LeftMenuLink/Icon.js | 15 +++++++++++++++ .../lib/src/components/LeftMenuLink/index.js | 3 +++ .../lib/src/components/LeftMenuList/List.js | 3 +-- .../lib/src/components/ListButton/index.js | 1 + .../src/components/CustomLink/StyledCustomLink.js | 2 +- 5 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js new file mode 100644 index 0000000000..63a5e1010a --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js @@ -0,0 +1,15 @@ +import React from 'react'; +import styled from 'styled-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + +import colors from '../../assets/styles/colors'; + +const LeftMenuIcon = styled(({ ...props }) => )` + position: absolute; + top: calc(50% - 0.3rem); + left: 1.4rem; + font-size: 0.7rem; + color: ${colors.leftMenu.darkGrey}; +`; + +export default LeftMenuIcon; diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js index f340430f77..3bd532129c 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js @@ -2,9 +2,12 @@ import React, { memo } from 'react'; import PropTypes from 'prop-types'; import { NavLink } from 'react-router-dom'; +import Icon from './Icon'; + function LeftMenuLink({ children, to }) { return ( +

{children}

); diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js index 185ac863d2..fa7bbe79ae 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js @@ -5,8 +5,7 @@ import colors from '../../assets/styles/colors'; const List = styled.ul` margin-bottom: 0; padding-left: 0; - max-height: ${props => - props.numberOfVisibleItems ? `${props.numberOfVisibleItems * 35}px` : null}; + max-height: 178px; overflow-y: scroll; li { position: relative; diff --git a/packages/strapi-helper-plugin/lib/src/components/ListButton/index.js b/packages/strapi-helper-plugin/lib/src/components/ListButton/index.js index 4c7d3cc035..b6c983b906 100644 --- a/packages/strapi-helper-plugin/lib/src/components/ListButton/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/ListButton/index.js @@ -4,6 +4,7 @@ const ListButton = styled.div` button { width: 100%; height: 54px; + border: 0; border-top: 1px solid #aed4fb; color: #007eff; font-weight: 500; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js b/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js index 9f482ff68d..f3851d908e 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js @@ -8,7 +8,7 @@ import styled from 'styled-components'; const StyledCustomLink = styled.div` padding-left: 15px; - padding-top: 9px; + padding-top: 10px; line-height: 0; margin-left: -3px; From 4bda9f3104332dce9cdc7e38f8e750f7bc028c1f Mon Sep 17 00:00:00 2001 From: Jelmer Visser Date: Wed, 18 Mar 2020 11:55:06 +0100 Subject: [PATCH 14/20] Leave custom resolvers alone when diff Signed-off-by: Jelmer Visser --- packages/strapi-plugin-graphql/services/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/strapi-plugin-graphql/services/utils.js b/packages/strapi-plugin-graphql/services/utils.js index a3518b2f7d..f9990ba7e6 100644 --- a/packages/strapi-plugin-graphql/services/utils.js +++ b/packages/strapi-plugin-graphql/services/utils.js @@ -33,13 +33,17 @@ const diffResolvers = (object, base) => { Object.keys(object).forEach(type => { Object.keys(object[type]).forEach(resolver => { - if (!_.has(base, [type, resolver])) { + if(type === 'Query' || type === 'Mutation') { + if (!_.has(base, [type, resolver])) { + _.set(newObj, [type, resolver], _.get(object, [type, resolver])); + } + } else { _.set(newObj, [type, resolver], _.get(object, [type, resolver])); } }); }); - return newObj; + return object; }; const convertToParams = params => { From 7f3be5eb0447627d9565fd42729fd5903c0614ad Mon Sep 17 00:00:00 2001 From: Jelmer Visser Date: Wed, 18 Mar 2020 11:57:04 +0100 Subject: [PATCH 15/20] Return newObj instead of original object Signed-off-by: Jelmer Visser --- packages/strapi-plugin-graphql/services/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-plugin-graphql/services/utils.js b/packages/strapi-plugin-graphql/services/utils.js index f9990ba7e6..1300e7d8dc 100644 --- a/packages/strapi-plugin-graphql/services/utils.js +++ b/packages/strapi-plugin-graphql/services/utils.js @@ -43,7 +43,7 @@ const diffResolvers = (object, base) => { }); }); - return object; + return newObj; }; const convertToParams = params => { From 6ac715abf7370377d7a99f5ce08f3cfd885097e5 Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 09:36:53 +0100 Subject: [PATCH 16/20] leftmenu dropdown alignment Signed-off-by: Virginie Ky --- .../lib/src/components/LeftMenuSubList/Dropdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js index 0418f98797..b2b9625359 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js @@ -11,8 +11,8 @@ const Dropdown = styled.div` button { position: relative; padding: 0 10px 1px 15px; - margin-bottom: 9px; - margin-top: 9px; + margin-bottom: 10px; + margin-top: 8px; font-weight: 600; text-transform: capitalize; &::before { @@ -32,7 +32,7 @@ const Dropdown = styled.div` padding-left: 10px; } .collapse { - margin-bottom: 2px; + margin-bottom: 10px; } &:last-of-type { margin-bottom: 0; From 12116c149bbab28ab0190198df344a88fc746984 Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 10:09:32 +0100 Subject: [PATCH 17/20] update snapshots Signed-off-by: Virginie Ky --- .../Webhooks/ListView/tests/__snapshots__/index.test.js.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap index e993002986..afa3f792a5 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap +++ b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap @@ -5,6 +5,7 @@ exports[`Admin | containers | ListView should match the snapshot 1`] = ` .c6 button { width: 100%; height: 54px; + border: 0; border-top: 1px solid #aed4fb; color: #007eff; font-weight: 500; From 541cd72e972208d845afd00b8ae10eeeb29690c4 Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 14:40:56 +0100 Subject: [PATCH 18/20] Admin leftmenu bullet icon Signed-off-by: Virginie Ky --- .../src/components/LeftMenuLink/LeftMenuIcon.js | 11 +++-------- .../lib/src/components/LeftMenuLink/Icon.js | 6 +++--- .../lib/src/components/LeftMenuList/List.js | 15 +-------------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js index 55cf65c058..fe9dfa715f 100644 --- a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js +++ b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js @@ -5,14 +5,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; const FaIcon = styled(({ small, ...props }) => )` position: absolute; - top: calc(50% - 0.9rem + 0.3rem); - left: 1.6rem; - margin-right: 1.2rem; - margin-top: ${({ small }) => (small ? '.3rem' : null)}; - font-size: ${({ small }) => (small ? '.9rem' : '1.4rem')}; - width: 1.4rem; - padding-bottom: 0.2rem; - text-align: center; + top: ${({ small }) => (small ? 'calc(50% - 0.3rem)' : 'calc(50% - 0.9rem + 0.3rem)')}; + left: ${({ small }) => (small ? '2.2rem' : '1.6rem')}; + font-size: ${({ small }) => (small ? '.5rem' : '1.2rem')}; `; const LeftMenuIcon = ({ icon }) => ; diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js index 63a5e1010a..dc51df3da4 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js @@ -6,9 +6,9 @@ import colors from '../../assets/styles/colors'; const LeftMenuIcon = styled(({ ...props }) => )` position: absolute; - top: calc(50% - 0.3rem); - left: 1.4rem; - font-size: 0.7rem; + top: calc(50% - 0.25rem); + left: 1.5rem; + font-size: 0.5rem; color: ${colors.leftMenu.darkGrey}; `; diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js index fa7bbe79ae..d3dcf6e62e 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js @@ -19,19 +19,6 @@ const List = styled.ul` padding-left: 30px; height: 34px; border-radius: 2px; - &::before { - content: '•'; - position: absolute; - top: calc(50% - 2px); - left: 15px; - font-weight: bold; - display: block; - width: 0.5em; - height: 0.5em; - color: ${colors.leftMenu.darkGrey}; - line-height: 5px; - font-size: 10px; - } p { color: ${colors.leftMenu.black}; font-size: 13px; @@ -45,7 +32,7 @@ const List = styled.ul` p { font-weight: 600; } - &::before { + svg { color: ${colors.leftMenu.black}; } } From 8d529247838b32dbf070991ad129a47ce968925e Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 15:09:06 +0100 Subject: [PATCH 19/20] apply design review Signed-off-by: Virginie Ky --- .../admin/src/components/LeftMenuLink/LeftMenuLinkContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js index 2d90cde812..eff5ff3e35 100644 --- a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js +++ b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js @@ -19,7 +19,7 @@ const LinkLabel = styled.span` display: inline-block; width: 100%; padding-right: 1rem; - padding-left: 2.6rem; + padding-left: 2.1rem; `; const LeftMenuLinkContent = ({ From 59c567aa3c935d7483b74bc543e9d65ea5b9090f Mon Sep 17 00:00:00 2001 From: Virginie Ky Date: Fri, 20 Mar 2020 16:40:55 +0100 Subject: [PATCH 20/20] clean numberOfVisibleItems prop Signed-off-by: Virginie Ky --- .../lib/src/components/LeftMenuList/index.js | 8 ++------ .../admin/src/containers/LeftMenu/index.js | 20 +++++-------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js index 6dd93c0a09..74253e06d5 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js @@ -9,7 +9,7 @@ import LeftMenuHeader from '../LeftMenuHeader'; import List from './List'; import Wrapper from './Wrapper'; -function LeftMenuList({ customLink, links, title, searchable, numberOfVisibleItems }) { +function LeftMenuList({ customLink, links, title, searchable }) { const [search, setSearch] = useState(''); const { formatMessage } = useGlobalContext(); @@ -100,9 +100,7 @@ function LeftMenuList({ customLink, links, title, searchable, numberOfVisibleIte
- - {getList().map((link, i) => renderCompo(link, i))} - + {getList().map((link, i) => renderCompo(link, i))} {Component && isValidElement() && }
@@ -114,7 +112,6 @@ LeftMenuList.defaultProps = { links: [], title: null, searchable: false, - numberOfVisibleItems: null, }; LeftMenuList.propTypes = { @@ -130,7 +127,6 @@ LeftMenuList.propTypes = { id: PropTypes.string, }), searchable: PropTypes.bool, - numberOfVisibleItems: PropTypes.number, }; export default LeftMenuList; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js index c1d538ddf7..8fc65a24c6 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js @@ -76,12 +76,8 @@ function LeftMenu({ wait }) { const canOpenModalCreateCTorComponent = () => { return ( - !Object.keys(contentTypes).some( - ct => contentTypes[ct].isTemporary === true - ) && - !Object.keys(components).some( - component => components[component].isTemporary === true - ) + !Object.keys(contentTypes).some(ct => contentTypes[ct].isTemporary === true) && + !Object.keys(components).some(component => components[component].isTemporary === true) ); }; @@ -128,9 +124,7 @@ function LeftMenu({ wait }) { }, } : null, - links: sortedContentTypesList.filter( - contentType => contentType.kind === 'collectionType' - ), + links: sortedContentTypesList.filter(contentType => contentType.kind === 'collectionType'), }, { name: 'singleTypes', @@ -149,9 +143,7 @@ function LeftMenu({ wait }) { }, } : null, - links: sortedContentTypesList.filter( - singleType => singleType.kind === 'singleType' - ), + links: sortedContentTypesList.filter(singleType => singleType.kind === 'singleType'), }, { name: 'components', @@ -177,9 +169,7 @@ function LeftMenu({ wait }) { return ( {data.map(list => { - return ( - - ); + return ; })} );