Compare commits

..

No commits in common. "master" and "2.2.1" have entirely different histories.

33 changed files with 382 additions and 9469 deletions

View File

@ -13,10 +13,10 @@ jobs:
- name: Checkout branch
uses: actions/checkout@v2
- name: Install Node v18
- name: Install Node v14
uses: actions/setup-node@v2
with:
node-version: '18.x'
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- name: Install Yarn

View File

@ -54,15 +54,13 @@ module.exports = ({ env }) => ({
This will listen for any record created or updated in the article content type and set a slugified value for the slug field automatically based on the title field.
> Note: To rewrite the same field (e.g. `title` is both a reference and a slug) use `title` as the `field` and `references` value.
> Note: Compound slugs (basing the slug on multiple fields) can be achieved by passing an array of fields to the `references` property (e.g. `references: ['date','title']`).
> Note that if you want to rewrite the same field (so `title` is both a reference and a slug) then you just put `title` for both the `field` and `references` properties.
**IMPORTANT NOTE**: Make sure any sensitive data is stored in env files.
### Additional Requirement for GraphQL
Per [#35](https://github.com/ComfortablyCoding/strapi-plugin-slugify/issues/35) please ensure that the slugify plugin configuration is placed **before** the graphql plugin configuration.
Per [#35](https://github.com/ComfortablyCoding/strapi-plugin-slugify/issues/35) please ensure that the slugify plugin configuration is placed **after** the graphql plugin configuration.
## The Complete Plugin Configuration Object
@ -73,7 +71,6 @@ Per [#35](https://github.com/ComfortablyCoding/strapi-plugin-slugify/issues/35)
| contentTypes[modelName]field | The name of the field to add the slug | String | N/A | Yes |
| contentTypes[modelName]references | The name(s) of the field(s) used to build the slug. If an array of fields is set it will result in a compound slug | String or Array | N/A | Yes |
| slugifyWithCount | Duplicate strings will have their occurrence appended to the end of the slug | Boolean | false | No |
| shouldUpdateSlug | Allow the slug to be updated after initial generation. | Boolean | false | No |
| skipUndefinedReferences | Skip reference fields that have no data. Mostly applicable to compound slug | Boolean | false | No |
| slugifyOptions | The options to pass the the slugify function. All options can be found in the [slugify docs](https://github.com/sindresorhus/slugify#api) | Object | {} | No |

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "strapi-plugin-slugify",
"version": "2.3.8",
"version": "2.2.1",
"description": "A plugin for Strapi Headless CMS that provides the ability to auto slugify a field for any content type.",
"scripts": {
"lint": "eslint . --fix",
@ -26,17 +26,19 @@
"url": "https://github.com/ComfortablyCoding/strapi-plugin-slugify/issues"
},
"dependencies": {
"@sindresorhus/slugify": "1.1.0",
"@strapi/strapi": "^4.14.0",
"@strapi/utils": "^4.14.0",
"lodash": "^4.17.21",
"yup": "^0.32.9"
"@sindresorhus/slugify": "1.1.0"
},
"devDependencies": {
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-node": "^11.1.0",
"prettier": "^3.1.0"
"prettier": "^2.5.1"
},
"peerDependencies": {
"@strapi/strapi": "^4.0.7",
"@strapi/utils": "^4.0.7",
"lodash": "^4.17.21",
"yup": "^0.32.9"
},
"strapi": {
"displayName": "Slugify",
@ -45,7 +47,7 @@
"kind": "plugin"
},
"engines": {
"node": ">=18.0.0 <=20.x.x",
"node": ">=12.x.x <=16.x.x",
"npm": ">=6.0.0"
},
"keywords": [

29
server/bootstrap.js vendored Normal file
View File

@ -0,0 +1,29 @@
'use strict';
const _ = require('lodash');
const { SUPPORTED_LIFECYCLES } = require('./utils/constants');
const { getPluginService } = require('./utils/getPluginService');
module.exports = ({ strapi }) => {
const settingsService = getPluginService(strapi, 'settingsService');
const settings = settingsService.get();
// build settings structure
const normalizedSettings = settingsService.build(settings);
// reset plugin settings
settingsService.set(normalizedSettings);
// set up lifecycles
const subscribe = {
models: _.map(normalizedSettings.models, (m) => m.uid),
};
SUPPORTED_LIFECYCLES.forEach((lifecycle) => {
subscribe[lifecycle] = (ctx) => {
getPluginService(strapi, 'slugService').slugify(ctx);
};
});
strapi.db.lifecycles.subscribe(subscribe);
};

View File

@ -1,17 +0,0 @@
const { getPluginService } = require('../utils/getPluginService');
const buildSettings = async () => {
const settingsService = getPluginService('settingsService');
const settings = await settingsService.get();
// build settings structure
const normalizedSettings = settingsService.build(settings);
// reset plugin settings
await settingsService.set(normalizedSettings);
return normalizedSettings;
};
module.exports = {
buildSettings,
};

View File

@ -1,16 +0,0 @@
'use strict';
const { buildSettings } = require('./buildSettings');
const { setupLifecycles } = require('./setupLifecycles');
const { syncSlugCount } = require('./syncSlugCount');
module.exports = async () => {
const settings = await buildSettings();
if (settings.slugifyWithCount) {
// Ensure correct count used for old plugin versions and projects with existing slugs.
await syncSlugCount(settings);
}
setupLifecycles(settings);
};

View File

@ -1,18 +0,0 @@
const _ = require('lodash');
const { getPluginService } = require('../utils/getPluginService');
const setupLifecycles = (settings) => {
// set up lifecycles
const subscribe = {
models: _.map(settings.modelsByUID, (model) => model.uid),
};
['beforeCreate', 'afterCreate', 'beforeUpdate'].forEach((lifecycle) => {
subscribe[lifecycle] = (ctx) => getPluginService('slugService').slugify(ctx);
});
strapi.db.lifecycles.subscribe(subscribe);
};
module.exports = {
setupLifecycles,
};

View File

@ -1,75 +0,0 @@
'use strict';
const syncSlugCount = async (settings) => {
const entries = await strapi.entityService.findMany('plugin::slugify.slug', {
filters: { createdAt: { $gt: 1 } },
});
// if entries aready present we can skip sync
if (entries && entries.length) {
return;
}
strapi.log.info('[slugify] syncing slug count for registered content types');
const slugs = new Map();
// chec slugs in each reigistered model
for (const uid in settings.modelsByUID) {
if (!Object.hasOwnProperty.call(settings.modelsByUID, uid)) {
continue;
}
const model = settings.modelsByUID[uid];
// using db query to avoid the need to check if CT has draftAndPublish enabled
const modelEntries = await strapi.db.query(model.uid).findMany({
filters: { createdAt: { $gt: 1 } },
});
strapi.log.info(`[slugify] syncing slug count for ${model.uid}`);
for (const entry of modelEntries) {
const slug = entry[model.field];
if (!slug) {
continue;
}
const record = slugs.get(getNonAppendedSlug(slug));
if (!record) {
slugs.set(slug, { slug, count: 1 });
continue;
}
slugs.set(record.slug, { slug: record.slug, count: record.count + 1 });
}
strapi.log.info(`[slugify] sync for ${model.uid} completed`);
}
if (slugs.size) {
// create all required records
const createResponse = await strapi.db.query('plugin::slugify.slug').createMany({
data: [...slugs.values()],
});
strapi.log.info(
`[slugify] ${createResponse.count} out of ${slugs.size} slugs synced successfully`
);
} else {
strapi.log.info('[slugify] No syncable slugs found');
}
};
// removes any appended number from a slug/string if found
const getNonAppendedSlug = (slug) => {
const match = slug.match('[\\-]{1}[\\d]+$');
if (!match) {
return slug;
}
return slug.replace(match[0], '');
};
module.exports = {
syncSlugCount,
};

View File

@ -1,14 +1,17 @@
'use strict';
const schema = require('./schema');
const { pluginConfigSchema } = require('./schema');
module.exports = {
default: () => ({
contentTypes: {},
slugifyOptions: {},
slugifyWithCount: false,
shouldUpdateSlug: false,
skipUndefinedReferences: false,
}),
validator: (config) => schema.validateSync(config),
default() {
return {
contentTypes: {},
slugifyOptions: {},
slugifyWithCount: false,
skipUndefinedReferences: false,
};
},
async validator(config) {
await pluginConfigSchema.validate(config);
},
};

View File

@ -3,7 +3,7 @@
const yup = require('yup');
const _ = require('lodash');
const schema = yup.object().shape({
const pluginConfigSchema = yup.object().shape({
slugifyOptions: yup.object(),
contentTypes: yup.lazy((obj) => {
let shape = {};
@ -18,8 +18,9 @@ const schema = yup.object().shape({
return yup.object().shape(shape);
}),
slugifyWithCount: yup.bool(),
shouldUpdateSlug: yup.bool(),
skipUndefinedReferences: yup.bool(),
});
module.exports = schema;
module.exports = {
pluginConfigSchema,
};

View File

@ -1,9 +0,0 @@
'use strict';
const slugSchema = require('./slug/schema.json');
module.exports = {
slug: {
schema: slugSchema,
},
};

View File

@ -1,29 +0,0 @@
{
"kind": "collectionType",
"collectionName": "slugs",
"info": {
"singularName": "slug",
"pluralName": "slugs",
"displayName": "slug"
},
"options": {
"draftAndPublish": false,
"comment": ""
},
"pluginOptions": {
"content-manager": {
"visible": false
},
"content-type-builder": {
"visible": false
}
},
"attributes": {
"slug": {
"type": "text"
},
"count": {
"type": "integer"
}
}
}

View File

@ -1,35 +1,28 @@
'use strict';
const _ = require('lodash');
const { NotFoundError } = require('@strapi/utils/lib/errors');
const { getPluginService } = require('../utils/getPluginService');
const { transformResponse } = require('@strapi/strapi/lib/core-api/controller/transform');
const { isValidFindSlugParams } = require('../utils/isValidFindSlugParams');
const { sanitizeOutput } = require('../utils/sanitizeOutput');
const { hasRequiredModelScopes } = require('../utils/hasRequiredModelScopes');
const transform = require('../utils/transform');
module.exports = ({ strapi }) => ({
async findSlug(ctx) {
const { modelsByName } = getPluginService('settingsService').get();
const { models } = getPluginService(strapi, 'settingsService').get();
const { modelName, slug } = ctx.request.params;
const { auth } = ctx.state;
try {
isValidFindSlugParams({
modelName,
slug,
modelsByName,
});
} catch (error) {
return ctx.badRequest(error.message);
}
isValidFindSlugParams({
modelName,
slug,
models,
});
const { uid, field, contentType } = modelsByName[modelName];
const { uid, field, contentType } = models[modelName];
try {
await hasRequiredModelScopes(strapi, uid, auth);
} catch (error) {
return ctx.forbidden();
}
await hasRequiredModelScopes(strapi, uid, auth);
// add slug filter to any already existing query restrictions
let query = ctx.query || {};
@ -43,13 +36,13 @@ module.exports = ({ strapi }) => ({
query.publicationState = 'live';
}
const data = await getPluginService('slugService').findOne(uid, query);
const data = await getPluginService(strapi, 'slugService').findOne(uid, query);
if (data) {
const sanitizedEntity = await sanitizeOutput(data, contentType, auth);
ctx.body = transform.response({ data: sanitizedEntity, schema: contentType });
ctx.body = transformResponse(sanitizedEntity, {}, { contentType });
} else {
ctx.notFound();
throw new NotFoundError();
}
},
});

View File

@ -11,7 +11,7 @@ const registerGraphlQLQuery = (strapi) => {
resolversConfig: getResolversConfig(),
});
getPluginService('extension', 'graphql').use(extension);
getPluginService(strapi, 'extension', 'graphql').use(extension);
};
module.exports = {

View File

@ -3,21 +3,20 @@ const { getPluginService } = require('../utils/getPluginService');
const { isValidFindSlugParams } = require('../utils/isValidFindSlugParams');
const { hasRequiredModelScopes } = require('../utils/hasRequiredModelScopes');
const { sanitizeOutput } = require('../utils/sanitizeOutput');
const { ForbiddenError, ValidationError } = require('@strapi/utils').errors;
const getCustomTypes = (strapi, nexus) => {
const { naming } = getPluginService('utils', 'graphql');
const { toEntityResponse } = getPluginService('format', 'graphql').returnTypes;
const { modelsByUID } = getPluginService('settingsService').get();
const { naming } = getPluginService(strapi, 'utils', 'graphql');
const { toEntityResponse } = getPluginService(strapi, 'format', 'graphql').returnTypes;
const { models } = getPluginService(strapi, 'settingsService').get();
const { getEntityResponseName } = naming;
// get all types required for findSlug query
let findSlugTypes = {
response: [],
};
_.forEach(strapi.contentTypes, (contentType, uid) => {
if (modelsByUID[uid]) {
findSlugTypes.response.push(getEntityResponseName(contentType));
_.forEach(strapi.contentTypes, (value, key) => {
if (models[key]) {
findSlugTypes.response.push(getEntityResponseName(value));
}
});
@ -34,7 +33,7 @@ const getCustomTypes = (strapi, nexus) => {
t.members(...findSlugTypes.response);
},
resolveType: (ctx) => {
return getEntityResponseName(modelsByUID[ctx.info.resourceUID].contentType);
return getEntityResponseName(models[ctx.info.resourceUID].contentType);
},
});
@ -48,30 +47,23 @@ const getCustomTypes = (strapi, nexus) => {
args: {
modelName: nexus.stringArg('The model name of the content type'),
slug: nexus.stringArg('The slug to query for'),
publicationState: nexus.stringArg('The publication state of the entry'),
publicationState: nexus.stringArg('The publication state of the entry')
},
resolve: async (_parent, args, ctx) => {
const { modelsByName } = getPluginService('settingsService').get();
const { models } = getPluginService(strapi, 'settingsService').get();
const { modelName, slug, publicationState } = args;
const { auth } = ctx.state;
try {
isValidFindSlugParams({
modelName,
slug,
modelsByName,
publicationState,
});
} catch (error) {
throw new ValidationError(error.message);
}
const { uid, field, contentType } = modelsByName[modelName];
isValidFindSlugParams({
modelName,
slug,
models,
publicationState
});
try {
await hasRequiredModelScopes(strapi, uid, auth);
} catch (error) {
throw new ForbiddenError();
}
const { uid, field, contentType } = models[modelName];
await hasRequiredModelScopes(strapi, uid, auth);
// build query
let query = {
@ -83,9 +75,9 @@ const getCustomTypes = (strapi, nexus) => {
// only return published entries by default if content type has draftAndPublish enabled
if (_.get(contentType, ['options', 'draftAndPublish'], false)) {
query.publicationState = publicationState || 'live';
}
}
const data = await getPluginService('slugService').findOne(uid, query);
const data = await getPluginService(strapi, 'slugService').findOne(uid, query);
const sanitizedEntity = await sanitizeOutput(data, contentType, auth);
return toEntityResponse(sanitizedEntity, { resourceUID: uid });
},

View File

@ -1,19 +1,17 @@
'use strict';
const bootstrap = require('./bootstrap');
const config = require('./config');
const contentTypes = require('./content-types');
const controllers = require('./controllers');
const register = require('./register');
const config = require('./config');
const controllers = require('./controllers');
const routes = require('./routes');
const services = require('./services');
module.exports = {
bootstrap,
config,
contentTypes,
controllers,
register,
config,
controllers,
routes,
services,
};

View File

@ -1,15 +1,8 @@
'use strict';
const { registerGraphlQLQuery } = require('./graphql');
const { getPluginService } = require('./utils/getPluginService');
module.exports = ({ strapi }) => {
const { contentTypes } = getPluginService('settingsService').get();
// ensure we have at least one model before attempting registration
if (!Object.keys(contentTypes).length) {
return;
}
// add graphql query if present
if (strapi.plugin('graphql')) {
strapi.log.info('[slugify] graphql detected, registering queries');

View File

@ -1,7 +1,7 @@
'use strict';
const settingsService = require('./settings-service');
const slugService = require('./slug-service');
const settingsService = require('./settings-service');
module.exports = {
slugService,

View File

@ -13,8 +13,7 @@ module.exports = ({ strapi }) => ({
},
build(settings) {
// build models
settings.modelsByUID = {};
settings.modelsByName = {};
settings.models = {};
_.each(strapi.contentTypes, (contentType, uid) => {
const model = settings.contentTypes[contentType.modelName];
if (!model) {
@ -31,9 +30,7 @@ module.exports = ({ strapi }) => ({
}
let references = _.isArray(model.references) ? model.references : [model.references];
const hasReferences = references.every((referenceField) =>
isValidModelField(contentType, referenceField)
);
const hasReferences = references.every((r) => isValidModelField(contentType, r));
if (!hasReferences) {
strapi.log.warn(
`[slugify] skipping ${contentType.info.singularName} registration, invalid reference field provided.`
@ -47,8 +44,8 @@ module.exports = ({ strapi }) => ({
contentType,
references,
};
settings.modelsByUID[uid] = data;
settings.modelsByName[contentType.modelName] = data;
settings.models[uid] = data;
settings.models[contentType.modelName] = data;
});
_.omit(settings, ['contentTypes']);

View File

@ -0,0 +1,56 @@
'use strict';
const _ = require('lodash');
const { getPluginService } = require('../utils/getPluginService');
const { toSlug, toSlugWithCount } = require('../utils/slugification');
module.exports = ({ strapi }) => ({
slugify(ctx) {
const settings = getPluginService(strapi, 'settingsService').get();
const { params, model: entityModel } = ctx;
const { data } = params;
const model = settings.models[entityModel.uid];
if (!data) {
return;
}
const { field, references } = model;
// ensure the reference field has data
let referenceFieldValues = references
.filter((r) => typeof data[r] !== 'undefined' && data[r].length)
.map((r) => data[r]);
const hasUndefinedFields = referenceFieldValues.length < references.length;
if ((!settings.skipUndefinedReferences && hasUndefinedFields) || !referenceFieldValues.length) {
return;
}
referenceFieldValues = referenceFieldValues.join(' ');
if (settings.slugifyWithCount) {
data[field] = toSlugWithCount(referenceFieldValues, settings.slugifyOptions);
return;
}
data[field] = toSlug(referenceFieldValues, settings.slugifyOptions);
},
async findOne(uid, query) {
const slugs = await strapi.entityService.findMany(uid, query);
// single
if (slugs && _.isPlainObject(slugs)) {
return slugs;
}
// collection
if (slugs && _.isArray(slugs) && slugs.length) {
return slugs[0];
}
// no result
return null;
},
});

View File

@ -1,41 +0,0 @@
const slugify = require('@sindresorhus/slugify');
const buildSlug = async (string, settings) => {
let slug = slugify(string, settings.slugifyOptions);
// slugify with count
if (!settings.slugifyWithCount) {
return slug;
}
const slugEntry = await strapi.db.query('plugin::slugify.slug').findOne({
select: ['id', 'count'],
where: { slug },
});
// if no result then count is 1 and base slug is returned
if (!slugEntry) {
await strapi.entityService.create('plugin::slugify.slug', {
data: {
slug,
count: 1,
},
});
return slug;
}
const count = slugEntry.count + 1;
await strapi.entityService.update('plugin::slugify.slug', slugEntry.id, {
data: {
count,
},
});
const separator = settings.slugifyOptions.separator || '-';
return `${slug}${separator}${count}`;
};
module.exports = {
buildSlug,
};

View File

@ -1,40 +0,0 @@
'use strict';
const getReferenceFieldValues = (ctx, data, references) => {
return references
.filter((referenceField) => {
// check action specific fields
if (
referenceField === 'id' &&
(ctx.action === 'afterCreate' || ctx.action === 'beforeUpdate')
) {
return true;
}
// check general data fields
if (typeof data[referenceField] === 'undefined') {
return false;
}
if (data[referenceField] === null) {
return false;
}
if (typeof data[referenceField] === 'string' && data[referenceField].length === 0) {
return false;
}
return true;
})
.map((referenceField) => {
if (referenceField === 'id') {
return ctx.result ? ctx.result.id : ctx.params.where.id;
}
return data[referenceField];
});
};
module.exports = {
getReferenceFieldValues,
};

View File

@ -1,74 +0,0 @@
'use strict';
const _ = require('lodash');
const { getPluginService } = require('../../utils/getPluginService');
const { shouldUpdateSlug } = require('./shoudUpdateSlug');
const { getReferenceFieldValues } = require('./getReferenceFieldValues');
const { buildSlug } = require('./buildSlug');
module.exports = ({ strapi }) => ({
async slugify(ctx) {
const { params, model: entityModel } = ctx;
const settings = getPluginService('settingsService').get();
const { data } = params;
const model = settings.modelsByUID[entityModel.uid];
if (!data) {
return;
}
const { field, references } = model;
// do not add/update slug if it already has a value unless settings specify otherwise
if (!settings.shouldUpdateSlug && data[field]) {
return;
}
// ensure the reference field has data
let referenceFieldValues = getReferenceFieldValues(ctx, data, references);
// respect skip undefined fields setting
const hasUndefinedFields = referenceFieldValues.length < references.length;
if ((!settings.skipUndefinedReferences && hasUndefinedFields) || !referenceFieldValues.length) {
return;
}
// check if the slug should be updated based on the action type
let shouldUpdateSlugByAction = await shouldUpdateSlug(strapi, ctx, references);
if (!shouldUpdateSlugByAction) {
return;
}
referenceFieldValues = referenceFieldValues.join(' ');
// update slug field based on action type
const slug = await buildSlug(referenceFieldValues, settings);
if (ctx.action === 'beforeCreate' || ctx.action === 'beforeUpdate') {
data[field] = slug;
} else if (ctx.action === 'afterCreate') {
strapi.entityService.update(model.uid, ctx.result.id, {
data: {
slug,
},
});
}
},
async findOne(uid, query) {
const slugs = await strapi.entityService.findMany(uid, query);
// single
if (slugs && _.isPlainObject(slugs)) {
return slugs;
}
// collection
if (slugs && _.isArray(slugs) && slugs.length) {
return slugs[0];
}
// no result
return null;
},
});

View File

@ -1,50 +0,0 @@
'use strict';
const { getReferenceFieldValues } = require('./getReferenceFieldValues');
const shouldUpdateSlugInAfterCreate = (strapi, ctx, references) => {
if (!references.includes('id')) {
return false;
}
return true;
};
const shouldUpdateSlugInBeforeCreate = (strapi, ctx, references) => {
if (references.includes('id')) {
return false;
}
return true;
};
const shouldUpdateSlugInBeforeUpdate = async (strapi, ctx, references) => {
const record = await strapi.entityService.findOne(ctx.model.uid, ctx.params.where.id);
let currentReferenceFieldValues = getReferenceFieldValues(ctx, record, references);
let referenceFieldValues = getReferenceFieldValues(ctx, ctx.params.data, references);
// only update if reference a reference field has changed
if (JSON.stringify(referenceFieldValues) == JSON.stringify(currentReferenceFieldValues)) {
return false;
}
return true;
};
const shouldUpdateSlug = async (strapi, ctx, references) => {
let shouldUpdate = false;
if (ctx.action === 'beforeCreate') {
shouldUpdate = shouldUpdateSlugInBeforeCreate(strapi, ctx, references);
} else if (ctx.action === 'afterCreate') {
shouldUpdate = shouldUpdateSlugInAfterCreate(strapi, ctx, references);
} else if (ctx.action === 'beforeUpdate') {
shouldUpdate = shouldUpdateSlugInBeforeUpdate(strapi, ctx, references);
}
return shouldUpdate;
};
module.exports = {
shouldUpdateSlug,
};

View File

@ -0,0 +1,7 @@
'use strict';
const SUPPORTED_LIFECYCLES = ['beforeCreate', 'beforeUpdate'];
module.exports = {
SUPPORTED_LIFECYCLES,
};

View File

@ -7,7 +7,7 @@ const { pluginId } = require('./pluginId');
*
* @return service
*/
const getPluginService = (name, plugin = pluginId) => strapi.plugin(plugin).service(name);
const getPluginService = (strapi, name, plugin = pluginId) => strapi.plugin(plugin).service(name);
module.exports = {
getPluginService,

View File

@ -1,5 +1,12 @@
const hasRequiredModelScopes = (strapi, uid, auth) =>
strapi.auth.verify(auth, { scope: `${uid}.find` });
const { ForbiddenError } = require('@strapi/utils/lib/errors');
const hasRequiredModelScopes = async (strapi, uid, auth) => {
try {
await strapi.auth.verify(auth, { scope: `${uid}.find` });
} catch (e) {
throw new ForbiddenError();
}
};
module.exports = {
hasRequiredModelScopes,

View File

@ -1,4 +1,4 @@
const { ValidationError } = require('@strapi/utils').errors;
const { ValidationError } = require('@strapi/utils/lib/errors');
const _ = require('lodash');
const isValidFindSlugParams = (params) => {
@ -6,8 +6,8 @@ const isValidFindSlugParams = (params) => {
throw new ValidationError('A model and slug must be provided.');
}
const { modelName, slug, modelsByName, publicationState } = params;
const model = modelsByName[modelName];
const { modelName, slug, models, publicationState } = params;
const model = models[modelName];
if (!modelName) {
throw new ValidationError('A model name path variable is required.');
@ -18,9 +18,7 @@ const isValidFindSlugParams = (params) => {
}
if (!_.get(model, ['contentType', 'options', 'draftAndPublish'], false) && publicationState) {
throw new ValidationError(
'Filtering by publication state is only supported for content types that have Draft and Publish enabled.'
);
throw new ValidationError('Filtering by publication state is only supported for content types that have Draft and Publish enabled.')
}
// ensure valid model is passed

View File

@ -2,8 +2,7 @@
const _ = require('lodash');
const isValidModelField = (model, field) =>
_.get(model, ['attributes', field], false) || field === 'id';
const isValidModelField = (model, field) => _.get(model, ['attributes', field], false);
module.exports = {
isValidModelField,

View File

@ -1,7 +1,6 @@
const { sanitize } = require('@strapi/utils');
const { contentAPI } = require('@strapi/utils/lib/sanitize');
const sanitizeOutput = (data, contentType, auth) =>
sanitize.contentAPI.output(data, contentType, { auth });
const sanitizeOutput = (data, contentType, auth) => contentAPI.output(data, contentType, { auth });
module.exports = {
sanitizeOutput,

View File

@ -0,0 +1,12 @@
'use strict';
const slugify = require('@sindresorhus/slugify');
const slugifyWithCount = slugify.counter();
const toSlug = (string, options) => slugify(string, options);
const toSlugWithCount = (string, options) => slugifyWithCount(string, options);
module.exports = {
toSlug,
toSlugWithCount,
};

View File

@ -1,97 +0,0 @@
'use strict';
const { isNil, isPlainObject } = require('lodash/fp');
function response({ data, schema }) {
return transformResponse(data, {}, { contentType: schema });
}
// adapted from https://github.com/strapi/strapi/blob/main/packages/core/strapi/src/core-api/controller/transform.ts
function isEntry(property) {
return property === null || isPlainObject(property) || Array.isArray(property);
}
function isDZEntries(property) {
return Array.isArray(property);
}
function transformResponse(resource, meta = {}, opts = {}) {
if (isNil(resource)) {
return resource;
}
return {
data: transformEntry(resource, opts?.contentType),
meta,
};
}
function transformComponent(data, component) {
if (Array.isArray(data)) {
return data.map((datum) => transformComponent(datum, component));
}
const res = transformEntry(data, component);
if (isNil(res)) {
return res;
}
const { id, attributes } = res;
return { id, ...attributes };
}
function transformEntry(entry, type) {
if (isNil(entry)) {
return entry;
}
if (Array.isArray(entry)) {
return entry.map((singleEntry) => transformEntry(singleEntry, type));
}
if (!isPlainObject(entry)) {
throw new Error('Entry must be an object');
}
const { id, ...properties } = entry;
const attributeValues = {};
for (const key of Object.keys(properties)) {
const property = properties[key];
const attribute = type && type.attributes[key];
if (attribute && attribute.type === 'relation' && isEntry(property) && 'target' in attribute) {
const data = transformEntry(property, strapi.contentType(attribute.target));
attributeValues[key] = { data };
} else if (attribute && attribute.type === 'component' && isEntry(property)) {
attributeValues[key] = transformComponent(property, strapi.components[attribute.component]);
} else if (attribute && attribute.type === 'dynamiczone' && isDZEntries(property)) {
if (isNil(property)) {
attributeValues[key] = property;
}
attributeValues[key] = property.map((subProperty) => {
return transformComponent(subProperty, strapi.components[subProperty.__component]);
});
} else if (attribute && attribute.type === 'media' && isEntry(property)) {
const data = transformEntry(property, strapi.contentType('plugin::upload.file'));
attributeValues[key] = { data };
} else {
attributeValues[key] = property;
}
}
return {
id,
attributes: attributeValues,
// NOTE: not necessary for now
// meta: {},
};
}
module.exports = {
response,
};

9076
yarn.lock

File diff suppressed because it is too large Load Diff