mirror of
https://github.com/strapi/strapi.git
synced 2025-12-24 21:54:24 +00:00
add extension system for content-types
This commit is contained in:
parent
18da6e2188
commit
2d7454553a
11
examples/getstarted/extensions.old/index.js
Normal file
11
examples/getstarted/extensions.old/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = ({ env }) => ({
|
||||
'users-permissions': {
|
||||
contentTypes: {
|
||||
users: {
|
||||
schema: 'users-permissions/models/user.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
// provider: 'cloudinary',
|
||||
// providerOptions: {
|
||||
// cloud_name: 'cloud-name',
|
||||
// api_key: 'api-key',
|
||||
// api_secret: 'api-secret',
|
||||
// },
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
jwtSecret: process.env.JWT_SECRET || 'c4dc6f71-db45-49c6-82d0-9ca91cb93fa2',
|
||||
};
|
||||
@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
jwtSecret: process.env.JWT_SECRET || 'c4dc6f71-db45-49c6-82d0-9ca91cb93fa2',
|
||||
jwtSecret: process.env.JWT_SECRET || 'efec74b5-274a-4b6b-8077-d3ac6bfdfd89',
|
||||
};
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
{
|
||||
"collectionName": "up_roles",
|
||||
"info": {
|
||||
"name": "role",
|
||||
"description": "",
|
||||
"singularName": "role",
|
||||
"pluralName": "roles",
|
||||
"displayName": "Role"
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": false
|
||||
},
|
||||
"attributes": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"required": true,
|
||||
"configurable": false
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"configurable": false
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"unique": true,
|
||||
"configurable": false
|
||||
},
|
||||
"permissions": {
|
||||
"type": "relation",
|
||||
"relation": "oneToMany",
|
||||
"target": "plugin::users-permissions.permission",
|
||||
"mappedBy": "role",
|
||||
"configurable": false
|
||||
},
|
||||
"users": {
|
||||
"type": "relation",
|
||||
"relation": "oneToMany",
|
||||
"target": "plugin::users-permissions.user",
|
||||
"mappedBy": "role",
|
||||
"configurable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "up_users",
|
||||
"info": {
|
||||
"name": "User",
|
||||
"description": "",
|
||||
"singularName": "user",
|
||||
"pluralName": "users",
|
||||
"displayName": "User"
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": false,
|
||||
"timestamps": true
|
||||
},
|
||||
"attributes": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"unique": true,
|
||||
"configurable": false,
|
||||
"required": true
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
"minLength": 6,
|
||||
"configurable": false,
|
||||
"required": true
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"configurable": false
|
||||
},
|
||||
"password": {
|
||||
"type": "password",
|
||||
"minLength": 6,
|
||||
"configurable": false,
|
||||
"private": true
|
||||
},
|
||||
"resetPasswordToken": {
|
||||
"type": "string",
|
||||
"configurable": false,
|
||||
"private": true
|
||||
},
|
||||
"confirmationToken": {
|
||||
"type": "string",
|
||||
"configurable": false,
|
||||
"private": true
|
||||
},
|
||||
"confirmed": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"configurable": false
|
||||
},
|
||||
"blocked": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"configurable": false
|
||||
},
|
||||
"role": {
|
||||
"type": "relation",
|
||||
"relation": "manyToOne",
|
||||
"target": "plugin::users-permissions.role",
|
||||
"inversedBy": "users",
|
||||
"configurable": false
|
||||
},
|
||||
"picture": {
|
||||
"type": "media",
|
||||
"multiple": false,
|
||||
"required": false
|
||||
},
|
||||
"aaa": {
|
||||
"type": "string"
|
||||
},
|
||||
"bbbb": {
|
||||
"type": "string"
|
||||
},
|
||||
"ccc": {
|
||||
"type": "string"
|
||||
},
|
||||
"dddd": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
// 1 - load original plugin
|
||||
// 2 - load content types overwrites
|
||||
// 3 - execute plugin extensions
|
||||
|
||||
module.exports = plugin => {
|
||||
// extend article content type
|
||||
|
||||
plugin.contentTypes.article.collectionName === 'foo';
|
||||
|
||||
plugin.routes.push({
|
||||
method: 'GET',
|
||||
handler: 'myCtrl.actionA',
|
||||
});
|
||||
|
||||
return plugin;
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [];
|
||||
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const bootstrap = require('./server/bootstrap');
|
||||
const contentTypes = require('./server/content-types');
|
||||
const policies = require('./server/policies');
|
||||
const services = require('./server/services');
|
||||
const routes = require('./server/routes');
|
||||
@ -9,11 +8,9 @@ const controllers = require('./server/controllers');
|
||||
|
||||
module.exports = () => {
|
||||
return {
|
||||
register: () => {},
|
||||
bootstrap,
|
||||
routes,
|
||||
controllers,
|
||||
contentTypes,
|
||||
policies,
|
||||
services,
|
||||
};
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
const { capitalize } = require('lodash/fp');
|
||||
|
||||
const createSchemaHandler = require('./schema-handler');
|
||||
const createComponentBuilder = require('./component-builder');
|
||||
@ -33,7 +32,7 @@ module.exports = function createBuilder() {
|
||||
|
||||
let dir;
|
||||
if (contentType.plugin) {
|
||||
dir = `./extensions/${contentType.plugin}/models`;
|
||||
dir = `./extensions/${contentType.plugin}/content-types/${contentType.info.singularName}`;
|
||||
} else {
|
||||
dir = `./api/${contentType.apiName}/models`;
|
||||
}
|
||||
@ -42,7 +41,7 @@ module.exports = function createBuilder() {
|
||||
modelName: contentType.modelName,
|
||||
plugin: contentType.plugin,
|
||||
uid: contentType.uid,
|
||||
filename: capitalize(`${contentType.info.singularName}.settings.json`),
|
||||
filename: 'schema.json',
|
||||
dir: path.join(strapi.dir, dir),
|
||||
schema: contentType.__schema__,
|
||||
};
|
||||
|
||||
@ -321,7 +321,7 @@ class Strapi {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.autoReload) {
|
||||
if (this.config.get('autoReload')) {
|
||||
this.server.destroy();
|
||||
process.send('reload');
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ const strapiServerSchema = yup
|
||||
services: yup.object().required(),
|
||||
policies: yup.object().required(),
|
||||
middlewares: yup.object().required(), // may be removed later
|
||||
contentTypes: yup.array().required(),
|
||||
contentTypes: yup.object().required(),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
/**
|
||||
@ -10,7 +11,7 @@ module.exports = strapi => {
|
||||
function normalizePath(optPath) {
|
||||
const filePath = Array.isArray(optPath) ? optPath.join('/') : optPath;
|
||||
|
||||
const normalizedPath = path.normalize(filePath).replace(/^(\/?\.\.?)+/, '');
|
||||
const normalizedPath = path.normalize(filePath).replace(/^\/?(\.\/|\.\.\/)+/, '');
|
||||
|
||||
return path.join(strapi.dir, normalizedPath);
|
||||
}
|
||||
@ -44,6 +45,14 @@ module.exports = strapi => {
|
||||
const removePath = normalizePath(optPath);
|
||||
return fse.remove(removePath);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a file in strapi app
|
||||
*/
|
||||
async appendFile(optPath, data) {
|
||||
const writePath = normalizePath(optPath);
|
||||
return fs.appendFileSync(writePath, data);
|
||||
},
|
||||
};
|
||||
|
||||
return strapiFS;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const { join } = require('path');
|
||||
const { join, resolve } = require('path');
|
||||
const { existsSync } = require('fs');
|
||||
const { defaultsDeep, getOr } = require('lodash/fp');
|
||||
const { defaultsDeep, getOr, get } = require('lodash/fp');
|
||||
const { env } = require('@strapi/utils');
|
||||
const loadConfigFile = require('../app-configuration/load-config-file');
|
||||
const loadFiles = require('../../load/load-files');
|
||||
const getEnabledPlugins = require('./get-enabled-plugins');
|
||||
|
||||
const defaultPlugin = {
|
||||
@ -20,7 +21,25 @@ const defaultPlugin = {
|
||||
services: {},
|
||||
policies: {},
|
||||
middlewares: {},
|
||||
contentTypes: [],
|
||||
contentTypes: {},
|
||||
};
|
||||
|
||||
const applyUserExtension = async plugins => {
|
||||
const extensionsDir = resolve(strapi.dir, 'extensions');
|
||||
if (!existsSync(extensionsDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
|
||||
for (const pluginName in plugins) {
|
||||
const plugin = plugins[pluginName];
|
||||
for (const ctName in plugin.contentTypes) {
|
||||
const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], files);
|
||||
if (extendedSchema) {
|
||||
plugin.contentTypes[ctName].schema = extendedSchema;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formatContentTypes = plugins => {
|
||||
@ -34,7 +53,7 @@ const formatContentTypes = plugins => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatConfig = plugins => {
|
||||
const applyUserConfig = plugins => {
|
||||
const userPluginConfigPath = join(strapi.dir, 'config', 'plugins.js');
|
||||
const userPluginsConfig = existsSync(userPluginConfigPath)
|
||||
? loadConfigFile(userPluginConfigPath)
|
||||
@ -43,13 +62,18 @@ const formatConfig = plugins => {
|
||||
for (const pluginName in plugins) {
|
||||
const plugin = plugins[pluginName];
|
||||
const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
|
||||
const formattedConfig = defaultsDeep(plugin.config.default, userPluginConfig);
|
||||
const defaultConfig =
|
||||
typeof plugin.config.default === 'function'
|
||||
? plugin.config.default({ env })
|
||||
: plugin.config.default;
|
||||
|
||||
const config = defaultsDeep(defaultConfig, userPluginConfig);
|
||||
try {
|
||||
plugin.config.validator(formattedConfig);
|
||||
plugin.config.validator(config);
|
||||
} catch (e) {
|
||||
throw new Error(`Error regarding ${pluginName} config: ${e.message}`);
|
||||
}
|
||||
plugin.config = formattedConfig;
|
||||
plugin.config = config;
|
||||
}
|
||||
};
|
||||
|
||||
@ -59,12 +83,12 @@ const loadPlugins = async strapi => {
|
||||
|
||||
for (const pluginName in enabledPlugins) {
|
||||
const enabledPlugin = enabledPlugins[pluginName];
|
||||
const loadPluginServer = require(join(enabledPlugin.pathToPlugin, 'strapi-server.js'));
|
||||
const pluginServer = await loadPluginServer({ env });
|
||||
const pluginServer = loadConfigFile(join(enabledPlugin.pathToPlugin, 'strapi-server.js'));
|
||||
plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
|
||||
}
|
||||
// TODO: validate plugin format
|
||||
formatConfig(plugins);
|
||||
applyUserConfig(plugins);
|
||||
await applyUserExtension(plugins);
|
||||
formatContentTypes(plugins);
|
||||
|
||||
return plugins;
|
||||
|
||||
@ -46,19 +46,21 @@ module.exports = function(strapi) {
|
||||
}, {});
|
||||
|
||||
// Set controllers.
|
||||
strapi.controllers = Object.keys(strapi.api || []).reduce((acc, key) => {
|
||||
for (let index in strapi.api[key].controllers) {
|
||||
let controller = strapi.api[key].controllers[index];
|
||||
acc[index] = controller;
|
||||
strapi.controllers = Object.keys(strapi.api || []).reduce((acc, apiName) => {
|
||||
strapi.container.get('controllers').add(`api::${apiName}`, strapi.api[apiName].controllers);
|
||||
for (let controllerName in strapi.api[apiName].controllers) {
|
||||
let controller = strapi.api[apiName].controllers[controllerName];
|
||||
acc[controllerName] = controller;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Set services.
|
||||
strapi.services = Object.keys(strapi.api || []).reduce((acc, key) => {
|
||||
for (let index in strapi.api[key].services) {
|
||||
acc[index] = strapi.api[key].services[index];
|
||||
strapi.services = Object.keys(strapi.api || []).reduce((acc, apiName) => {
|
||||
strapi.container.get('services').add(`api::${apiName}`, strapi.api[apiName].services);
|
||||
for (let serviceName in strapi.api[apiName].services) {
|
||||
acc[serviceName] = strapi.api[apiName].services[serviceName];
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
@ -19,6 +19,17 @@ const { createContentType } = require('../domain/content-type');
|
||||
// });
|
||||
// };
|
||||
|
||||
const validateKeySameToSingularName = contentTypes => {
|
||||
for (const ctName in contentTypes) {
|
||||
const contentType = contentTypes[ctName];
|
||||
if (ctName !== contentType.schema.info.singularName) {
|
||||
throw new Error(
|
||||
`The key of the content-type should be the same as its singularName. Found ${ctName} and ${contentType.schema.info.singularName}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const contentTypesRegistry = () => {
|
||||
const contentTypes = {};
|
||||
|
||||
@ -30,13 +41,15 @@ const contentTypesRegistry = () => {
|
||||
return pickBy((ct, ctUID) => ctUID.startsWith(prefix))(contentTypes);
|
||||
},
|
||||
add(namespace, rawContentTypes) {
|
||||
rawContentTypes.forEach(rawContentType => {
|
||||
validateKeySameToSingularName(rawContentTypes);
|
||||
for (const rawCtName in rawContentTypes) {
|
||||
const rawContentType = rawContentTypes[rawCtName];
|
||||
const uid = `${namespace}.${rawContentType.schema.info.singularName}`;
|
||||
if (has(uid, contentTypes)) {
|
||||
throw new Error(`Content-Type ${uid} has already been registered.`);
|
||||
throw new Error(`Content-type ${uid} has already been registered.`);
|
||||
}
|
||||
contentTypes[uid] = createContentType(uid, rawContentType);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
|
||||
const fileModel = require('../../models/File');
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
schema: fileModel,
|
||||
},
|
||||
];
|
||||
module.exports = {
|
||||
[fileModel.info.singularName]: { schema: fileModel },
|
||||
};
|
||||
|
||||
@ -10,6 +10,6 @@ module.exports = (/* strapi, config */) => {
|
||||
services: () => {},
|
||||
policies: {},
|
||||
middlewares: {},
|
||||
contentTypes: [],
|
||||
contentTypes: {},
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,4 +2,6 @@
|
||||
|
||||
const localeModel = require('./locale');
|
||||
|
||||
module.exports = [localeModel];
|
||||
module.exports = {
|
||||
[localeModel.schema.info.singularName]: localeModel,
|
||||
};
|
||||
|
||||
@ -199,12 +199,21 @@ const addCreateLocalizationAction = contentType => {
|
||||
|
||||
const localizationRoute = createLocalizationRoute(contentType);
|
||||
|
||||
const coreApiControllerPath = `api.${apiName}.controllers.${modelName}.createLocalization`;
|
||||
const handler = createLocalizationHandler(contentType);
|
||||
|
||||
strapi.config.routes.push(localizationRoute);
|
||||
|
||||
_.set(strapi, coreApiControllerPath, handler);
|
||||
// TODO: to replace with:
|
||||
// strapi.controllers.extends(`api::${apiName}.${modelName}`, (contr) => ({
|
||||
// ...controller,
|
||||
// createLocalization = createLocalizationHandler(contentType),
|
||||
// }));
|
||||
// OR
|
||||
// strapi.api(apiName).controllers.extends(modelName, (contr) => ({
|
||||
// ...controller,
|
||||
// createLocalization = createLocalizationHandler(contentType),
|
||||
// }));
|
||||
|
||||
const controller = strapi.container.get('controllers').get(`api::${apiName}.${modelName}`);
|
||||
controller.createLocalization = createLocalizationHandler(contentType);
|
||||
};
|
||||
|
||||
const mergeCustomizer = (dest, src) => {
|
||||
|
||||
@ -37,11 +37,9 @@ module.exports = async () => {
|
||||
|
||||
strapi.reload.isWatching = false;
|
||||
|
||||
await strapi.fs.writePluginFile(
|
||||
'users-permissions',
|
||||
'config/jwt.js',
|
||||
`module.exports = {\n jwtSecret: process.env.JWT_SECRET || '${jwtSecret}'\n};`
|
||||
);
|
||||
if (!process.env.JWT_SECRET) {
|
||||
await strapi.fs.appendFile('.env', `JWT_SECRET=${jwtSecret}\n`);
|
||||
}
|
||||
|
||||
strapi.reload.isWatching = true;
|
||||
}
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
default: {},
|
||||
default: ({ env }) => ({
|
||||
jwtSecret: env('JWT_SECRET'),
|
||||
jwt: {
|
||||
expiresIn: '30d',
|
||||
},
|
||||
ratelimit: {
|
||||
interval: 60000,
|
||||
max: 10,
|
||||
},
|
||||
}),
|
||||
validator: () => {},
|
||||
};
|
||||
|
||||
@ -4,14 +4,8 @@ const permissionModel = require('../../models/Permission.settings');
|
||||
const roleModel = require('../../models/Role.settings');
|
||||
const userModel = require('../../models/User.settings');
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
schema: permissionModel,
|
||||
},
|
||||
{
|
||||
schema: roleModel,
|
||||
},
|
||||
{
|
||||
schema: userModel,
|
||||
},
|
||||
];
|
||||
module.exports = {
|
||||
[permissionModel.info.singularName]: { schema: permissionModel },
|
||||
[roleModel.info.singularName]: { schema: roleModel },
|
||||
[userModel.info.singularName]: { schema: userModel },
|
||||
};
|
||||
|
||||
@ -7,10 +7,11 @@ const services = require('./server/services');
|
||||
const routes = require('./server/routes');
|
||||
const controllers = require('./server/controllers');
|
||||
const middlewares = require('./server/middlewares');
|
||||
const config = require('./server/config');
|
||||
|
||||
module.exports = () => ({
|
||||
register: () => {},
|
||||
bootstrap,
|
||||
config,
|
||||
routes,
|
||||
controllers,
|
||||
middlewares,
|
||||
|
||||
@ -18,7 +18,7 @@ module.exports = {
|
||||
author: {
|
||||
type: 'relation',
|
||||
relation: 'manyToOne',
|
||||
target: 'plugins::users-permissions.user',
|
||||
target: 'plugin::users-permissions.user',
|
||||
targetAttribute: 'articles',
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user