From 7f87d86d6d772bb8a0c5a1cb800ffdde24623838 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Thu, 12 Mar 2020 01:55:03 -0700 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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 {