Merge pull request #10859 from strapi/v4/content-api

v4/content api
This commit is contained in:
Alexandre BODIN 2021-09-08 16:48:44 +02:00 committed by GitHub
commit 2d0f6af9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 3115 additions and 2867 deletions

View File

@ -1,5 +0,0 @@
{
"features-routes": {
"enabled": true
}
}

View File

@ -1,23 +0,0 @@
'use strict';
// eslint-disable-next-line node/no-extraneous-require
const { features } = require('@strapi/strapi/lib/utils/ee');
const routes = require('./routes');
module.exports = strapi => ({
beforeInitialize() {
strapi.config.middleware.load.before.unshift('features-routes');
},
initialize() {
loadFeaturesRoutes();
},
});
const loadFeaturesRoutes = () => {
for (const [feature, getFeatureRoutes] of Object.entries(routes)) {
if (features.isEnabled(feature)) {
strapi.admin.routes.push(...getFeatureRoutes); // TODO
}
}
};

View File

@ -1,7 +1,6 @@
'use strict';
module.exports = {
// TODO: update load middleware to not load the admin middleware from here
bootstrap: require('./bootstrap'),
routes: require('./routes'),
services: require('./services'),

View File

@ -1,28 +0,0 @@
'use strict';
module.exports = [
{
method: 'POST',
path: '/roles',
handler: 'role.create',
config: {
policies: [],
},
},
{
method: 'DELETE',
path: '/roles/:id',
handler: 'role.deleteOne',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/roles/batch-delete',
handler: 'role.deleteMany',
config: {
policies: [],
},
},
];

View File

@ -0,0 +1,65 @@
'use strict';
// eslint-disable-next-line node/no-extraneous-require
const { features } = require('@strapi/strapi/lib/utils/ee');
const featuresRoutes = require('./features-routes');
const getFeaturesRoutes = () => {
return Object.entries(featuresRoutes).flatMap(([featureName, featureRoutes]) => {
if (features.isEnabled(featureName)) {
return featureRoutes;
}
});
};
module.exports = [
{
method: 'POST',
path: '/roles',
handler: 'role.create',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['admin::roles.create'],
},
},
],
},
},
{
method: 'DELETE',
path: '/roles/:id',
handler: 'role.deleteOne',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['admin::roles.delete'],
},
},
],
},
},
{
method: 'POST',
path: '/roles/batch-delete',
handler: 'role.deleteMany',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['admin::roles.delete'],
},
},
],
},
},
...getFeaturesRoutes(),
];

View File

@ -95,7 +95,7 @@ describe('Provider Login', () => {
test('It should fail with a public request', async () => {
const res = await requests.public.get('/admin/providers/options');
expect(res.status).toBe(hasSSO ? 403 : 404);
expect(res.status).toBe(hasSSO ? 401 : 404);
});
test('It should fail with an authenticated request (restricted user)', async () => {
@ -133,7 +133,7 @@ describe('Provider Login', () => {
test('It should fail with a public request', async () => {
const res = await requests.public.put('/admin/providers/options', { body: newOptions });
expect(res.status).toBe(hasSSO ? 403 : 405);
expect(res.status).toBe(hasSSO ? 401 : 405);
});
test('It should fail with an authenticated request (restricted user)', async () => {

View File

@ -149,10 +149,6 @@ async function copyAdmin(dest) {
await fs.ensureDir(path.resolve(dest, 'config'));
await fs.copy(path.resolve(adminPath, 'admin'), path.resolve(dest, 'admin'));
await fs.copy(
path.resolve(adminPath, 'server', 'config', 'layout.js'),
path.resolve(dest, 'config', 'layout.js')
);
// Copy package.json
await fs.copy(path.resolve(adminPath, 'package.json'), path.resolve(dest, 'package.json'));

View File

@ -1,5 +0,0 @@
{
"auth": {
"enabled": true
}
}

View File

@ -1,41 +0,0 @@
'use strict';
module.exports = strapi => ({
initialize() {
const passportMiddleware = strapi.admin.services.passport.init();
strapi.app.use(passportMiddleware);
strapi.app.use(async (ctx, next) => {
if (
ctx.request.header.authorization &&
ctx.request.header.authorization.split(' ')[0] === 'Bearer'
) {
const token = ctx.request.header.authorization.split(' ')[1];
const { payload, isValid } = strapi.admin.services.token.decodeJwtToken(token);
if (isValid) {
// request is made by an admin
const admin = await strapi
.query('admin::user')
.findOne({ where: { id: payload.id }, populate: ['roles'] });
if (!admin || !(admin.isActive === true)) {
return ctx.forbidden('Invalid credentials');
}
ctx.state.admin = admin;
ctx.state.user = admin;
ctx.state.userAbility = await strapi.admin.services.permission.engine.generateUserAbility(
admin
);
ctx.state.isAuthenticatedAdmin = true;
return next();
}
}
return next();
});
},
});

View File

@ -1,6 +1,9 @@
'use strict';
const forgotPasswordTemplate = require('./email-templates/forgot-password');
module.exports = {
layout: require('./layout'),
...require('./settings'),
forgotPassword: {
emailTemplate: forgotPasswordTemplate,
},
};

View File

@ -1,10 +0,0 @@
'use strict';
module.exports = {
administrator: {
actions: {
create: 'Admin.create',
update: 'Admin.update',
},
},
};

View File

@ -52,15 +52,13 @@ describe('Admin Controller', () => {
describe('information', () => {
beforeAll(() => {
global.strapi = {
app: {
env: 'development',
},
config: {
get: jest.fn(
(key, value) =>
({
autoReload: undefined,
'info.strapi': '1.0.0',
environment: 'development',
}[key] || value)
),
},
@ -71,7 +69,11 @@ describe('Admin Controller', () => {
test('Returns application information', async () => {
const result = await adminController.information();
expect(global.strapi.config.get).toHaveBeenCalledTimes(2);
expect(global.strapi.config.get.mock.calls).toEqual([
['environment'],
['autoReload', false],
['info.strapi', null],
]);
expect(result.data).toBeDefined();
expect(result.data).toStrictEqual({
currentEnvironment: 'development',

View File

@ -41,7 +41,7 @@ module.exports = {
},
async information() {
const currentEnvironment = strapi.app.env;
const currentEnvironment = strapi.config.get('environment');
const autoReload = strapi.config.get('autoReload', false);
const strapiVersion = strapi.config.get('info.strapi', null);
const nodeVersion = process.version;

View File

@ -2,7 +2,7 @@
module.exports = (ctx, next) => {
if (!ctx.state.isAuthenticatedAdmin) {
throw strapi.errors.forbidden();
return ctx.unauthorized();
}
return next();

View File

@ -3,7 +3,57 @@
// const permissionsFieldsToPropertiesMigration = require('../migrations/permissions-fields-to-properties');
/**
* Tries to authenticated admin user and calls next.
* @param {KoaContext} ctx
* @param {Middleware} next
* @returns {undefined}
*/
const authMiddleware = async (ctx, next) => {
if (!ctx.request.header.authorization) {
return next();
}
if (
ctx.request.header.authorization &&
ctx.request.header.authorization.split(' ')[0] === 'Bearer'
) {
const token = ctx.request.header.authorization.split(' ')[1];
const { payload, isValid } = strapi.admin.services.token.decodeJwtToken(token);
if (isValid) {
const admin = await strapi
.query('admin::user')
.findOne({ where: { id: payload.id }, populate: ['roles'] });
if (!admin || !(admin.isActive === true)) {
return ctx.unauthorized('Invalid credentials');
}
// TODO: use simple user & isAuthenticated
ctx.state.admin = admin;
ctx.state.user = admin;
ctx.state.userAbility = await strapi.admin.services.permission.engine.generateUserAbility(
admin
);
ctx.state.isAuthenticatedAdmin = true;
return next();
}
}
ctx.unauthorized('Invalid credentials');
};
module.exports = () => {
const passportMiddleware = strapi.admin.services.passport.init();
strapi.server.api('admin').use(passportMiddleware);
strapi.server.api('admin').use(authMiddleware);
// FIXME: to implement
// strapi.db.migrations.register(permissionsFieldsToPropertiesMigration);
};

View File

@ -1,17 +1,6 @@
'use strict';
module.exports = [
{
method: 'GET',
path: '/plugins',
handler: 'admin.plugins',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['admin::marketplace.read'] } },
],
},
},
{
method: 'GET',
path: '/init',
@ -30,6 +19,17 @@ module.exports = [
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'GET',
path: '/plugins',
handler: 'admin.plugins',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['admin::marketplace.read'] } },
],
},
},
{
method: 'POST',
path: '/plugins/install',

View File

@ -44,7 +44,7 @@ describe('Authenticated User', () => {
body: {},
});
expect(res.statusCode).toBe(403);
expect(res.statusCode).toBe(401);
});
});
@ -57,7 +57,7 @@ describe('Authenticated User', () => {
body: {},
});
expect(res.statusCode).toBe(403);
expect(res.statusCode).toBe(401);
});
test('Fails when trying to edit roles', async () => {

View File

@ -2,7 +2,6 @@
coverage
node_modules
stats.json
config/layout.json
package-lock.json

View File

@ -12,10 +12,19 @@ module.exports = async (ctx, next) => {
}
const target = ct.plugin === 'admin' ? strapi.admin : strapi.plugin(ct.plugin);
const { route } = ctx.state;
if (typeof route.handler !== 'string') {
return next();
}
const [, action] = route.handler.split('.');
const configPath =
ct.plugin === 'admin'
? ['server.admin.layout', ct.modelName, 'actions', ctx.request.route.action]
: ['plugin', ct.plugin, 'layout', ct.modelName, 'actions', ctx.request.route.action];
? ['server.admin.layout', ct.modelName, 'actions', action]
: ['plugin', ct.plugin, 'layout', ct.modelName, 'actions', action];
const actionConfig = strapi.config.get(configPath);

View File

@ -0,0 +1,321 @@
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/content-types',
handler: 'content-types.findContentTypes',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/content-types-settings',
handler: 'content-types.findContentTypesSettings',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/content-types/:uid/configuration',
handler: 'content-types.findContentTypeConfiguration',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/content-types/:uid/configuration',
handler: 'content-types.updateContentTypeConfiguration',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'GET',
path: '/components',
handler: 'components.findComponents',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/components/:uid/configuration',
handler: 'components.findComponentConfiguration',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/components/:uid/configuration',
handler: 'components.updateComponentConfiguration',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/uid/generate',
handler: 'uid.generateUID',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/uid/check-availability',
handler: 'uid.checkUIDAvailability',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/relations/:model/:targetField',
handler: 'relations.find',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: {
actions: [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.update',
],
hasAtLeastOne: true,
},
},
],
},
},
{
method: 'GET',
path: '/single-types/:model',
handler: 'single-types.find',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'PUT',
path: '/single-types/:model',
handler: 'single-types.createOrUpdate',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: {
actions: [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.update',
],
hasAtLeastOne: true,
},
},
],
},
},
{
method: 'DELETE',
path: '/single-types/:model',
handler: 'single-types.delete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
{
method: 'POST',
path: '/single-types/:model/actions/publish',
handler: 'single-types.publish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/single-types/:model/actions/unpublish',
handler: 'single-types.unpublish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model/:id/:targetField',
handler: 'collection-types.previewManyRelations',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model',
handler: 'collection-types.find',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model',
handler: 'collection-types.create',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.create'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model/:id',
handler: 'collection-types.findOne',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'PUT',
path: '/collection-types/:model/:id',
handler: 'collection-types.update',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.update'] },
},
],
},
},
{
method: 'DELETE',
path: '/collection-types/:model/:id',
handler: 'collection-types.delete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/:id/actions/publish',
handler: 'collection-types.publish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/:id/actions/unpublish',
handler: 'collection-types.unpublish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/actions/bulkDelete',
handler: 'collection-types.bulkDelete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
],
};

View File

@ -1,318 +1,5 @@
'use strict';
module.exports = [
{
method: 'GET',
path: '/content-types',
handler: 'content-types.findContentTypes',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/content-types-settings',
handler: 'content-types.findContentTypesSettings',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/content-types/:uid/configuration',
handler: 'content-types.findContentTypeConfiguration',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/content-types/:uid/configuration',
handler: 'content-types.updateContentTypeConfiguration',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'GET',
path: '/components',
handler: 'components.findComponents',
config: {
policies: [],
},
},
{
method: 'GET',
path: '/components/:uid/configuration',
handler: 'components.findComponentConfiguration',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/components/:uid/configuration',
handler: 'components.updateComponentConfiguration',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/uid/generate',
handler: 'uid.generateUID',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/uid/check-availability',
handler: 'uid.checkUIDAvailability',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/relations/:model/:targetField',
handler: 'relations.find',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: {
actions: [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.update',
],
hasAtLeastOne: true,
},
},
],
},
},
{
method: 'GET',
path: '/single-types/:model',
handler: 'single-types.find',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'PUT',
path: '/single-types/:model',
handler: 'single-types.createOrUpdate',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: {
actions: [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.update',
],
hasAtLeastOne: true,
},
},
],
},
},
{
method: 'DELETE',
path: '/single-types/:model',
handler: 'single-types.delete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
{
method: 'POST',
path: '/single-types/:model/actions/publish',
handler: 'single-types.publish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/single-types/:model/actions/unpublish',
handler: 'single-types.unpublish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model/:id/:targetField',
handler: 'collection-types.previewManyRelations',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model',
handler: 'collection-types.find',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model',
handler: 'collection-types.create',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.create'] },
},
],
},
},
{
method: 'GET',
path: '/collection-types/:model/:id',
handler: 'collection-types.findOne',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.read'] },
},
],
},
},
{
method: 'PUT',
path: '/collection-types/:model/:id',
handler: 'collection-types.update',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.update'] },
},
],
},
},
{
method: 'DELETE',
path: '/collection-types/:model/:id',
handler: 'collection-types.delete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/:id/actions/publish',
handler: 'collection-types.publish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/:id/actions/unpublish',
handler: 'collection-types.unpublish',
config: {
policies: [
'routing',
'plugin::content-manager.has-draft-and-publish',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.publish'] },
},
],
},
},
{
method: 'POST',
path: '/collection-types/:model/actions/bulkDelete',
handler: 'collection-types.bulkDelete',
config: {
policies: [
'routing',
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
options: { actions: ['plugin::content-manager.explorer.delete'] },
},
],
},
},
];
module.exports = {
admin: require('./admin'),
};

View File

@ -0,0 +1,176 @@
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/reserved-names',
handler: 'builder.getReservedNames',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/content-types',
handler: 'content-types.getContentTypes',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/content-types/:uid',
handler: 'content-types.getContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'POST',
path: '/content-types',
handler: 'content-types.createContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/content-types/:uid',
handler: 'content-types.updateContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/content-types/:uid',
handler: 'content-types.deleteContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/components',
handler: 'components.getComponents',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/components/:uid',
handler: 'components.getComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'POST',
path: '/components',
handler: 'components.createComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/components/:uid',
handler: 'components.updateComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/components/:uid',
handler: 'components.deleteComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/component-categories/:name',
handler: 'component-categories.editCategory',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/component-categories/:name',
handler: 'component-categories.deleteCategory',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
],
};

View File

@ -0,0 +1,27 @@
'use strict';
module.exports = {
type: 'content-api',
routes: [
{
method: 'GET',
path: '/content-types',
handler: 'content-types.getContentTypes',
},
{
method: 'GET',
path: '/content-types/:uid',
handler: 'content-types.getContentType',
},
{
method: 'GET',
path: '/components',
handler: 'components.getComponents',
},
{
method: 'GET',
path: '/components/:uid',
handler: 'components.getComponent',
},
],
};

View File

@ -1,173 +1,6 @@
'use strict';
module.exports = [
{
method: 'GET',
path: '/reserved-names',
handler: 'builder.getReservedNames',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/content-types',
handler: 'content-types.getContentTypes',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/content-types/:uid',
handler: 'content-types.getContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'POST',
path: '/content-types',
handler: 'content-types.createContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/content-types/:uid',
handler: 'content-types.updateContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/content-types/:uid',
handler: 'content-types.deleteContentType',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/components',
handler: 'components.getComponents',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'GET',
path: '/components/:uid',
handler: 'components.getComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'POST',
path: '/components',
handler: 'components.createComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/components/:uid',
handler: 'components.updateComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/components/:uid',
handler: 'components.deleteComponent',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'PUT',
path: '/component-categories/:name',
handler: 'component-categories.editCategory',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
{
method: 'DELETE',
path: '/component-categories/:name',
handler: 'component-categories.deleteCategory',
config: {
policies: [
{
name: 'admin::hasPermissions',
options: { actions: ['plugin::content-type-builder.read'] },
},
],
},
},
];
module.exports = {
admin: require('./admin'),
'content-api': require('./content-api'),
};

View File

@ -2,7 +2,6 @@
const _ = require('lodash');
const { getOr } = require('lodash/fp');
const strapiGenerators = require('@strapi/generators');
const { nameToSlug, contentTypes: contentTypesUtils } = require('@strapi/utils');
const { formatAttributes, replaceTemporaryUIDs } = require('../utils/attributes');
@ -127,6 +126,7 @@ const createContentType = async ({ contentType, components = [] }, options = {})
* @param {string} name
*/
const generateAPI = ({ name, kind = 'collectionType' }) => {
const strapiGenerators = require('@strapi/generators');
return strapiGenerators.generate('api', { id: nameToSlug(name), kind }, { dir: strapi.dir });
};

View File

@ -227,15 +227,7 @@ describe('Content Type Builder - Content types', () => {
// create data
for (let i = 0; i < 2; i++) {
const res = await rq({
method: 'POST',
url: `/test-collections`,
body: {
title: 'Test',
},
});
expect(res.statusCode).toBe(200);
await strapi.query(uid).create({ data: { title: 'Test' } });
}
const updateRes = await rq({

View File

@ -29,7 +29,6 @@ const toAssocs = data => {
});
};
// TODO: handle programmatic defaults
const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
const { attributes } = metadata;
@ -42,6 +41,7 @@ const toRow = (metadata, data = {}, { withDefaults = false } = {}) => {
if (types.isScalar(attribute.type)) {
const field = createField(attribute);
// TODO: move application level default to entity service
if (_.isUndefined(data[attributeName])) {
if (!_.isUndefined(attribute.default) && withDefaults) {
if (typeof attribute.default === 'function') {
@ -654,7 +654,10 @@ const createEntityManager = db => {
.where(joinTable.on || {})
.execute();
if (['oneToOne', 'oneToMany'].includes(attribute.relation)) {
if (
isBidirectional(attribute) &&
['oneToOne', 'oneToMany'].includes(attribute.relation)
) {
await this.createQueryBuilder(joinTable.name)
.delete()
.where({ [inverseJoinColumn.name]: toIds(data[attributeName]) })

View File

@ -10,6 +10,7 @@ const { isNil, pick } = require('lodash/fp');
module.exports = {
async send(ctx) {
let options = ctx.request.body;
try {
await strapi
.plugin('email')

View File

@ -0,0 +1,35 @@
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'POST',
path: '/',
handler: 'email.send',
config: {},
},
{
method: 'POST',
path: '/test',
handler: 'email.test',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } },
],
},
},
{
method: 'GET',
path: '/settings',
handler: 'email.getSettings',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } },
],
},
},
],
};

View File

@ -0,0 +1,12 @@
'use strict';
module.exports = {
type: 'content-api',
routes: [
{
method: 'POST',
path: '/',
handler: 'email.send',
},
],
};

View File

@ -1,49 +1,6 @@
'use strict';
module.exports = [
{
method: 'POST',
path: '/',
handler: 'Email.send',
config: {
policies: [],
description: 'Send an email',
tag: {
plugin: 'email',
name: 'Email',
},
},
},
{
method: 'POST',
path: '/test',
handler: 'Email.test',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } },
],
description: 'Send an test email',
tag: {
plugin: 'email',
name: 'Email',
},
},
},
{
method: 'GET',
path: '/settings',
handler: 'Email.getSettings',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{ name: 'admin::hasPermissions', options: { actions: ['plugin::email.settings.read'] } },
],
description: 'Get the email settings',
tag: {
plugin: 'email',
name: 'Email',
},
},
},
];
module.exports = {
admin: require('./admin'),
'content-api': require('./content-api'),
};

View File

@ -1,25 +1,24 @@
'use strict';
const Koa = require('koa');
const Router = require('koa-router');
const _ = require('lodash');
const { createLogger } = require('@strapi/logger');
const { Database } = require('@strapi/database');
const loadConfiguration = require('./core/app-configuration');
const { createHTTPServer } = require('./server');
const { createContainer } = require('./container');
const utils = require('./utils');
const initializeMiddlewares = require('./middlewares');
const createStrapiFs = require('./services/fs');
const createEventHub = require('./services/event-hub');
const { createServer } = require('./services/server');
const createWebhookRunner = require('./services/webhook-runner');
const { webhookModel, createWebhookStore } = require('./services/webhook-store');
const { createCoreStore, coreStoreModel } = require('./services/core-store');
const createEntityService = require('./services/entity-service');
const entityValidator = require('./services/entity-validator');
const createTelemetry = require('./services/metrics');
const createContentAPI = require('./services/content-api');
const createUpdateNotifier = require('./utils/update-notifier');
const createStartupLogger = require('./utils/startup-logger');
const ee = require('./utils/ee');
@ -54,16 +53,15 @@ class Strapi {
this.container.register('modules', modulesRegistry(this));
this.container.register('plugins', pluginsRegistry(this));
this.container.register('apis', apisRegistry(this));
this.container.register('content-api', createContentAPI(this));
this.isLoaded = false;
this.reload = this.reload();
this.app = new Koa();
this.router = new Router();
this.server = createHTTPServer(this, this.app);
this.server = createServer(this);
this.fs = createStrapiFs(this);
this.eventHub = createEventHub();
this.startupLogger = createStartupLogger(this);
this.app.proxy = this.config.get('server.proxy');
this.log = createLogger(this.config.get('logger', {}));
createUpdateNotifier(this).notify();
@ -123,9 +121,6 @@ class Strapi {
await this.load();
}
this.app.use(this.router.routes()).use(this.router.allowedMethods());
// Launch server.
await this.listen();
return this;
@ -268,15 +263,6 @@ class Strapi {
}
async load() {
this.app.use(async (ctx, next) => {
if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) {
ctx.set('strapi', 'You are so French!');
ctx.status = 204;
} else {
await next();
}
});
await Promise.all([
this.loadPlugins(),
this.loadAdmin(),
@ -332,11 +318,12 @@ class Strapi {
this.telemetry = createTelemetry(this);
// Initialize middlewares.
await initializeMiddlewares.call(this);
await initializeMiddlewares(this);
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
this.isLoaded = true;
return this;
}
@ -403,7 +390,7 @@ class Strapi {
await execLifecycle(this.config.get(configPath));
// admin
await this.admin[lifecycleName]();
await this.admin[lifecycleName](this);
}
getModel(uid) {

View File

@ -1,7 +1,6 @@
'use strict';
const { green } = require('chalk');
// eslint-disable-next-line node/no-extraneous-require
const strapiAdmin = require('@strapi/admin');
const { getConfigUrls } = require('@strapi/utils');
const loadConfiguration = require('../core/app-configuration');

View File

@ -1,6 +1,5 @@
'use strict';
// eslint-disable-next-line node/no-extraneous-require
const strapiAdmin = require('@strapi/admin');
const { getOr } = require('lodash/fp');
const { getConfigUrls, getAbsoluteServerUrl } = require('@strapi/utils');

View File

@ -11,6 +11,10 @@ module.exports = function(strapi) {
});
});
_.forEach(strapi.admin.middlewares, (middleware, middlewareName) => {
strapi.middleware[middlewareName] = middleware;
});
_.forEach(strapi.plugins, plugin => {
_.forEach(plugin.middlewares, (middleware, middlewareName) => {
strapi.middleware[middlewareName] = middleware;

View File

@ -2,10 +2,8 @@
// Dependencies.
const path = require('path');
const fs = require('fs-extra');
const _ = require('lodash');
const glob = require('../../load/glob');
const findPackagePath = require('../../load/package-path');
/**
* Load middlewares
@ -23,12 +21,6 @@ module.exports = async function(strapi) {
await loaders.loadInternalMiddlewares(middlewares);
// local middleware
await loaders.loadLocalMiddlewares(appPath, middlewares);
// plugins middlewares
await loaders.loadPluginsMiddlewares(strapi.plugins, middlewares);
// local plugin middlewares
await loaders.loadLocalPluginsMiddlewares(appPath, middlewares);
// load admin middlewares
await loaders.loadAdminMiddlewares(middlewares);
return middlewares;
};
@ -55,45 +47,6 @@ const createLoaders = strapi => {
const loadLocalMiddlewares = (appPath, middlewares) =>
loadMiddlewaresInDir(path.resolve(appPath, 'middlewares'), middlewares);
const loadPluginsMiddlewares = async (plugins, middlewares) => {
for (const pluginName in plugins) {
const pluginMiddlewares = plugins[pluginName].middlewares;
for (const middlewareName in pluginMiddlewares) {
middlewares[middlewareName] = {
loaded: false,
...pluginMiddlewares[middlewareName],
};
}
}
};
const loadLocalPluginsMiddlewares = async (appPath, middlewares) => {
const pluginsDir = path.resolve(appPath, 'plugins');
if (!fs.existsSync(pluginsDir)) return;
const pluginsNames = await fs.readdir(pluginsDir);
for (let pluginFolder of pluginsNames) {
// ignore files
const stat = await fs.stat(path.resolve(pluginsDir, pluginFolder));
if (!stat.isDirectory()) continue;
const dir = path.resolve(pluginsDir, pluginFolder, 'middlewares');
await loadMiddlewaresInDir(dir, middlewares);
}
};
const loadAdminMiddlewares = async middlewares => {
const middlewaresDir = 'middlewares';
const dir = path.resolve(findPackagePath(`@strapi/admin`), middlewaresDir);
await loadMiddlewaresInDir(dir, middlewares);
// load ee admin middlewares if they exist
if (process.env.STRAPI_DISABLE_EE !== 'true' && strapi.EE) {
await loadMiddlewaresInDir(`${dir}/../ee/${middlewaresDir}`, middlewares);
}
};
const loadMiddlewareDependencies = async (packages, middlewares) => {
for (let packageName of packages) {
const baseDir = path.dirname(require.resolve(`@strapi/middleware-${packageName}`));
@ -128,9 +81,6 @@ const createLoaders = strapi => {
return {
loadInternalMiddlewares,
loadLocalMiddlewares,
loadPluginsMiddlewares,
loadLocalPluginsMiddlewares,
loadMiddlewareDependencies,
loadAdminMiddlewares,
};
};

View File

@ -76,6 +76,7 @@ const getEnabledPlugins = async strapi => {
const userPluginsConfig = existsSync(userPluginConfigPath)
? loadConfigFile(userPluginConfigPath)
: {};
_.forEach(userPluginsConfig, (declaration, pluginName) => {
validatePluginName(pluginName);
declaredPlugins[pluginName] = toDetailedDeclaration(declaration);
@ -86,6 +87,7 @@ const getEnabledPlugins = async strapi => {
p => !declaredPluginsResolves.includes(p.pathToPlugin),
installedPlugins
);
const enabledPlugins = pipe(
defaultsDeep(declaredPlugins),
defaultsDeep(installedPluginsNotAlreadyUsed),

View File

@ -87,6 +87,7 @@ const applyUserConfig = plugins => {
const loadPlugins = async strapi => {
const plugins = {};
const enabledPlugins = await getEnabledPlugins(strapi);
for (const pluginName in enabledPlugins) {
@ -94,6 +95,7 @@ const loadPlugins = async strapi => {
const pluginServer = loadConfigFile(join(enabledPlugin.pathToPlugin, 'strapi-server.js'));
plugins[pluginName] = defaultsDeep(defaultPlugin, pluginServer);
}
// TODO: validate plugin format
applyUserConfig(plugins);
await applyUserExtension(plugins);

View File

@ -17,8 +17,8 @@ const pluginsRegistry = strapi => {
throw new Error(`Plugin ${name} has already been registered.`);
}
const moduleInstance = strapi.container.get('modules').add(`plugin::${name}`, pluginConfig);
plugins[name] = moduleInstance;
const pluginModule = strapi.container.get('modules').add(`plugin::${name}`, pluginConfig);
plugins[name] = pluginModule;
return plugins[name];
},

View File

@ -65,18 +65,18 @@ module.exports = strapi => {
*/
initialize() {
this.delegator = delegate(strapi.app.context, 'response');
this.delegator = delegate(strapi.server.app.context, 'response');
this.createResponses();
strapi.errors = Boom;
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
try {
// App logic.
await next();
} catch (error) {
// emit error if configured
if (strapi.config.get('server.emitErrors', false)) {
strapi.app.emit('error', error, ctx);
strapi.server.app.emit('error', error, ctx);
}
// Log error.
@ -92,10 +92,10 @@ module.exports = strapi => {
}
});
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
await next();
// Empty body is considered as `notFound` response.
if (_.isNil(ctx.body) && _.isNil(ctx.status)) {
if (_.isNil(ctx.body) && (_.isNil(ctx.status) || ctx.status === 404)) {
ctx.notFound();
}
});
@ -104,7 +104,7 @@ module.exports = strapi => {
// Custom function to avoid ctx.body repeat
createResponses() {
boomMethods.forEach(method => {
strapi.app.response[method] = function(msg, ...rest) {
strapi.server.app.response[method] = function(msg, ...rest) {
const boomError = Boom[method](msg, ...rest) || {};
const { status, body } = formatBoomPayload(boomError);
@ -119,17 +119,17 @@ module.exports = strapi => {
this.delegator.method(method);
});
strapi.app.response.send = function(data, status = 200) {
strapi.server.app.response.send = function(data, status = 200) {
this.status = status;
this.body = data;
};
strapi.app.response.created = function(data) {
strapi.server.app.response.created = function(data) {
this.status = 201;
this.body = data;
};
strapi.app.response.deleted = function(data) {
strapi.server.app.response.deleted = function(data) {
if (_.isNil(data)) {
this.status = 204;
} else {

View File

@ -30,7 +30,7 @@ module.exports = strapi => {
keepHeadersOnError,
} = Object.assign({}, defaults, strapi.config.get('middleware.settings.cors'));
strapi.app.use(
strapi.server.use(
cors({
origin: async function(ctx) {
let originList;

View File

@ -22,7 +22,7 @@ module.exports = strapi => {
const { dir } = strapi;
const { maxAge, path: faviconPath } = strapi.config.middleware.settings.favicon;
strapi.app.use(
strapi.server.use(
favicon(resolve(dir, faviconPath), {
maxAge,
})

View File

@ -13,7 +13,7 @@ module.exports = strapi => {
initialize() {
const { options = {} } = strapi.config.middleware.settings.gzip;
strapi.app.use(compress(options));
strapi.server.use(compress(options));
},
};
};

View File

@ -4,6 +4,6 @@ const helmet = require('koa-helmet');
module.exports = strapi => ({
initialize() {
strapi.app.use(helmet(strapi.config.middleware.settings.helmet));
strapi.server.use(helmet(strapi.config.middleware.settings.helmet));
},
});

View File

@ -15,13 +15,13 @@ const requiredMiddlewares = [
'favicon',
];
module.exports = async function() {
module.exports = async function(strapi) {
/** Utils */
const middlewareConfig = this.config.middleware;
const middlewareConfig = strapi.config.middleware;
// check if a middleware exists
const middlewareExists = key => {
return !isUndefined(this.middleware[key]);
return !isUndefined(strapi.middleware[key]);
};
// check if a middleware is enabled
@ -33,26 +33,26 @@ module.exports = async function() {
};
// list of enabled middlewares
const enabledMiddlewares = Object.keys(this.middleware).filter(middlewareEnabled);
const enabledMiddlewares = Object.keys(strapi.middleware).filter(middlewareEnabled);
// Method to initialize middlewares and emit an event.
const initialize = middlewareKey => {
if (this.middleware[middlewareKey].loaded === true) return;
if (strapi.middleware[middlewareKey].loaded === true) return;
const module = this.middleware[middlewareKey].load;
const module = strapi.middleware[middlewareKey].load;
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(`(middleware: ${middlewareKey}) is taking too long to load.`),
middlewareConfig.timeout || 1000
);
this.middleware[middlewareKey] = merge(this.middleware[middlewareKey], module);
strapi.middleware[middlewareKey] = merge(strapi.middleware[middlewareKey], module);
Promise.resolve()
.then(() => module.initialize())
.then(() => module.initialize(strapi))
.then(() => {
clearTimeout(timeout);
this.middleware[middlewareKey].loaded = true;
strapi.middleware[middlewareKey].loaded = true;
resolve();
})
.catch(err => {
@ -72,9 +72,9 @@ module.exports = async function() {
// Run beforeInitialize of every middleware
await Promise.all(
enabledMiddlewares.map(key => {
const { beforeInitialize } = this.middleware[key].load;
const { beforeInitialize } = strapi.middleware[key].load;
if (typeof beforeInitialize === 'function') {
return beforeInitialize();
return beforeInitialize(strapi);
}
})
);

View File

@ -14,7 +14,7 @@ module.exports = strapi => {
initialize() {
const { whiteList, blackList } = strapi.config.middleware.settings.ip;
strapi.app.use(
strapi.server.use(
ip({
whitelist: whiteList,
blacklist: blackList,

View File

@ -19,14 +19,14 @@ module.exports = strapi => {
*/
initialize() {
locale(strapi.app);
locale(strapi.server.app);
const { defaultLocale, modes, cookieName } = strapi.config.middleware.settings.language;
const directory = resolve(strapi.config.appPath, strapi.config.paths.config, 'locales');
strapi.app.use(
i18n(strapi.app, {
strapi.server.use(
i18n(strapi.server.app, {
directory,
locales: strapi.config.get('middleware.settings.language.locales', []),
defaultLocale,

View File

@ -23,9 +23,9 @@ module.exports = strapi => {
* Initialize the hook
*/
initialize() {
strapi.app.context.log = strapi.log;
strapi.server.app.context.log = strapi.log;
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
const start = Date.now();
await next();
const delta = Math.ceil(Date.now() - start);

View File

@ -37,7 +37,7 @@ module.exports = strapi => {
* Initialize the hook
*/
initialize() {
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
// disable for graphql
// TODO: find a better way later
if (ctx.url === '/graphql') {
@ -66,7 +66,10 @@ module.exports = strapi => {
}
});
addQsParser(strapi.app, strapi.config.get('middleware.settings.parser.queryStringParser'));
addQsParser(
strapi.server.app,
strapi.config.get('middleware.settings.parser.queryStringParser')
);
},
};
};

View File

@ -3,7 +3,7 @@
module.exports = strapi => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
await next();
ctx.set(

View File

@ -33,6 +33,7 @@ module.exports = strapi => {
const serveIndexPage = async (ctx, next) => {
// defer rendering of strapi index page
await next();
if (ctx.body != null || ctx.status !== 404) return;
ctx.url = 'index.html';
@ -61,38 +62,64 @@ module.exports = strapi => {
ctx.body = body;
};
strapi.router.get('/', serveIndexPage);
strapi.router.get('/index.html', serveIndexPage);
strapi.router.get(
'/assets/images/(.*)',
serveStatic(path.resolve(__dirname, 'assets/images'), { maxage: maxAge, defer: true })
);
strapi.server.routes([
{
method: 'GET',
path: '/',
handler: serveIndexPage,
},
{
method: 'GET',
path: '/index.html',
handler: serveIndexPage,
},
{
method: 'GET',
path: '/assets/images/(.*)',
handler: serveStatic(path.resolve(__dirname, 'assets/images'), {
maxage: maxAge,
defer: true,
}),
},
{
method: 'GET',
path: '/(.*)',
handler: koaStatic(staticDir, {
maxage: maxAge,
defer: true,
}),
},
]);
}
// serve files in public folder unless a sub router renders something else
strapi.router.get(
'/(.*)',
koaStatic(staticDir, {
maxage: maxAge,
defer: true,
})
);
if (!strapi.config.serveAdminPanel) return;
const buildDir = path.resolve(strapi.dir, 'build');
const serveAdmin = ctx => {
const serveAdmin = async (ctx, next) => {
await next();
if (ctx.method !== 'HEAD' && ctx.method !== 'GET') {
return;
}
if (ctx.body != null || ctx.status !== 404) {
return;
}
ctx.type = 'html';
ctx.body = fs.createReadStream(path.join(buildDir + '/index.html'));
};
strapi.router.get(
`${strapi.config.admin.path}/*`,
serveStatic(buildDir, { maxage: maxAge, defer: false, index: 'index.html' })
);
strapi.router.get(`${strapi.config.admin.path}`, serveAdmin);
strapi.router.get(`${strapi.config.admin.path}/*`, serveAdmin);
strapi.server.routes([
{
method: 'GET',
path: `${strapi.config.admin.path}/:path*`,
handler: [
serveAdmin,
serveStatic(buildDir, { maxage: maxAge, defer: false, index: 'index.html' }),
],
},
]);
},
};
};

View File

@ -11,7 +11,7 @@ module.exports = strapi => {
*/
initialize() {
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
const start = Date.now();
await next();

View File

@ -5,7 +5,7 @@ const _ = require('lodash');
module.exports = strapi => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
strapi.server.use(async (ctx, next) => {
await next();
const responseFn = strapi.config.get(['functions', 'responses', ctx.status]);

View File

@ -1,42 +1,67 @@
'use strict';
/**
* Module dependencies
*/
// Public node modules.
const _ = require('lodash');
const Router = require('koa-router');
const createEndpointComposer = require('./utils/compose-endpoint');
const { toLower } = require('lodash/fp');
const createRouteScopeGenerator = namespace => route => {
const prefix = namespace.endsWith('::') ? namespace : `${namespace}.`;
if (typeof route.handler === 'string') {
const [controller, action] = route.handler.split('.');
_.defaultsDeep(route.config, {
auth: {
scope: `${prefix}${controller}.${toLower(action)}`,
},
});
}
};
module.exports = strapi => {
const composeEndpoint = createEndpointComposer(strapi);
const registerAdminRoutes = () => {
const router = new Router({ prefix: '/admin' });
const generateRouteScope = createRouteScopeGenerator(`admin::`);
for (const route of strapi.admin.routes) {
composeEndpoint(route, { pluginName: 'admin', router });
}
strapi.admin.routes.forEach(route => {
generateRouteScope(route);
route.info = { pluginName: 'admin' };
});
strapi.app.use(router.routes()).use(router.allowedMethods());
strapi.server.routes({
type: 'admin',
prefix: '/admin',
routes: strapi.admin.routes,
});
};
const registerPluginRoutes = () => {
for (const pluginName in strapi.plugins) {
const plugin = strapi.plugins[pluginName];
const router = new Router({ prefix: `/${pluginName}` });
const generateRouteScope = createRouteScopeGenerator(`plugin::${pluginName}`);
for (const route of plugin.routes || []) {
const hasPrefix = _.has(route.config, 'prefix');
composeEndpoint(route, {
pluginName,
router: hasPrefix ? strapi.router : router,
if (Array.isArray(plugin.routes)) {
plugin.routes.forEach(route => {
generateRouteScope(route);
route.info = { pluginName };
});
strapi.server.routes({
type: 'admin',
prefix: `/${pluginName}`,
routes: plugin.routes,
});
} else {
_.forEach(plugin.routes, router => {
router.type = router.type || 'admin';
router.prefix = `/${pluginName}`;
router.routes.forEach(route => {
generateRouteScope(route);
route.info = { pluginName };
});
strapi.server.routes(router);
});
}
strapi.app.use(router.routes()).use(router.allowedMethods());
}
};
@ -44,28 +69,26 @@ module.exports = strapi => {
for (const apiName in strapi.api) {
const api = strapi.api[apiName];
_.forEach(api.routes, routeInfo => {
// nested router
if (_.has(routeInfo, 'routes')) {
const router = new Router({ prefix: routeInfo.prefix });
const generateRouteScope = createRouteScopeGenerator(`api::${apiName}`);
for (const route of routeInfo.routes || []) {
composeEndpoint(route, { apiName, router });
}
_.forEach(api.routes, router => {
// TODO: remove once auth setup
// pass meta down to compose endpoint
router.type = 'content-api';
router.routes.forEach(route => {
generateRouteScope(route);
route.info = { apiName };
});
strapi.router.use(router.routes()).use(router.allowedMethods());
} else {
composeEndpoint(routeInfo, { apiName, router: strapi.router });
}
return strapi.server.routes(router);
});
}
};
return {
initialize() {
strapi.router.prefix(strapi.config.get('middleware.settings.router.prefix', ''));
registerAPIRoutes();
registerAdminRoutes();
registerAPIRoutes();
registerPluginRoutes();
},
};

View File

@ -1,169 +0,0 @@
'use strict';
const _ = require('lodash');
const compose = require('koa-compose');
const { yup, policy: policyUtils } = require('@strapi/utils');
const policyOrMiddlewareSchema = yup.lazy(value => {
if (typeof value === 'string') {
return yup.string().required();
}
if (typeof value === 'function') {
return yup.mixed().isFunction();
}
return yup.object({
name: yup.string().required(),
options: yup.object().notRequired(), // any options
});
});
const routeSchema = yup.object({
method: yup
.string()
.oneOf(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'ALL'])
.required(),
path: yup.string().required(),
handler: yup.lazy(value => {
if (typeof value === 'string') {
return yup.string().required();
}
return yup
.mixed()
.isFunction()
.required();
}),
config: yup
.object({
policies: yup
.array()
.of(policyOrMiddlewareSchema)
.notRequired(),
middlwares: yup
.array()
.of(policyOrMiddlewareSchema)
.notRequired(),
})
.notRequired(),
});
const validateRouteConfig = routeConfig => {
try {
return routeSchema.validateSync(routeConfig, {
strict: true,
abortEarly: false,
stripUnknown: true,
});
} catch (error) {
console.error(error);
throw new Error('Invalid route config');
}
};
module.exports = strapi => {
const routerChecker = createRouteChecker(strapi);
return (routeConfig, { pluginName, router, apiName }) => {
validateRouteConfig(routeConfig);
try {
const middlewares = resolveMiddlewares(routeConfig);
const { method, endpoint, policies, action } = routerChecker(routeConfig, {
pluginName,
apiName,
});
if (_.isUndefined(action) || !_.isFunction(action)) {
return strapi.log.warn(
`Ignored attempt to bind route '${routeConfig.method} ${routeConfig.path}' to unknown controller/action.`
);
}
router[method](endpoint, compose([...policies, ...middlewares, action]));
} catch (error) {
throw new Error(
`Error creating endpoint ${routeConfig.method} ${routeConfig.path}: ${error.message}`
);
}
};
};
const resolveMiddlewares = route => {
const middlewaresConfig = _.get(route, 'config.middlewares', []);
return middlewaresConfig.map(middlewareConfig => {
if (typeof middlewareConfig === 'function') {
return middlewareConfig;
}
const middleware = strapi.middleware(middlewareConfig);
if (!middleware) {
throw new Error(`Middleware ${middlewareConfig} not found.`);
}
return middleware;
});
};
const getMethod = route => _.trim(_.toLower(route.method));
const getEndpoint = route => _.trim(route.path);
const createRouteChecker = strapi => {
return (value, { pluginName, apiName }) => {
const method = getMethod(value);
const endpoint = getEndpoint(value);
// Define controller and action names.
const [controllerName, actionName] = _.trim(value.handler).split('.');
const controllerKey = _.toLower(controllerName);
let controller;
if (pluginName) {
if (pluginName === 'admin') {
controller = strapi.admin.controllers[controllerKey];
} else {
controller = strapi.plugin(pluginName).controller(controllerKey);
}
} else {
controller = strapi.container.get('controllers').get(`api::${apiName}.${controllerKey}`);
}
if (!_.isFunction(controller[actionName])) {
strapi.stopWithError(
`Error creating endpoint ${method} ${endpoint}: handler not found "${controllerKey}.${actionName}"`
);
}
const action = controller[actionName].bind(controller);
const { bodyPolicy } = policyUtils;
const globalPolicy = policyUtils.globalPolicy({
controller: controllerKey,
action: actionName,
method,
endpoint,
plugin: pluginName,
});
const policyOption = _.get(value, 'config.policies', []);
const routePolicies = policyOption.map(policyConfig => {
return policyUtils.get(policyConfig, { pluginName, apiName });
});
// Init policies array.
const policies = [globalPolicy, ...routePolicies, bodyPolicy];
return {
method,
endpoint,
policies,
action,
};
};
};

View File

@ -27,9 +27,12 @@ describe('Session middleware', () => {
);
const mockStrapi = {
app: {
server: {
app: {
use: jest.fn(),
context: {},
},
use: jest.fn(),
context: {},
},
config: {
appPath: __dirname,

View File

@ -93,7 +93,7 @@ module.exports = strapi => {
return {
initialize() {
strapi.app.keys = strapi.config.get('middleware.settings.session.secretKeys');
strapi.server.app.keys = strapi.config.get('middleware.settings.session.secretKeys');
if (
_.has(strapi.config.middleware.settings.session, 'client') &&
@ -112,8 +112,8 @@ module.exports = strapi => {
strapi.config.middleware.settings.session
);
strapi.app.use(session(options, strapi.app));
strapi.app.use((ctx, next) => {
strapi.server.use(session(options, strapi.server.app));
strapi.server.use((ctx, next) => {
ctx.state = ctx.state || {};
ctx.state.session = ctx.session || {};
@ -127,8 +127,8 @@ module.exports = strapi => {
) {
const options = _.assign(strapi.config.middleware.settings.session);
strapi.app.use(session(options, strapi.app));
strapi.app.use((ctx, next) => {
strapi.server.use(session(options, strapi.server.app));
strapi.server.use((ctx, next) => {
ctx.state = ctx.state || {};
ctx.state.session = ctx.session || {};

View File

@ -0,0 +1,71 @@
'use strict';
const { strict: assert } = require('assert');
const { has } = require('lodash/fp');
class UnauthorizedError extends Error {}
class ForbiddenError extends Error {}
const INVALID_STRATEGY_MSG =
'Invalid auth strategy. Expecting an object with properties {name: string, authenticate: function, verify: function}';
const validStrategy = strategy => {
assert(has('name', strategy), INVALID_STRATEGY_MSG);
assert(typeof strategy.name === 'string', INVALID_STRATEGY_MSG);
assert(has('authenticate', strategy), INVALID_STRATEGY_MSG);
assert(typeof strategy.authenticate === 'function', INVALID_STRATEGY_MSG);
assert(has('verify', strategy), INVALID_STRATEGY_MSG);
assert(typeof strategy.verify === 'function', INVALID_STRATEGY_MSG);
};
const createAuthentication = () => {
const strategies = [];
return {
register(strategy) {
validStrategy(strategy);
strategies.push(strategy);
return () => {
strategies.splice(strategies.indexOf(strategy), 1);
};
},
async authenticate(ctx, next) {
for (const strategy of strategies) {
const result = await strategy.authenticate(ctx);
const { authenticated = false, credentials } = result || {};
if (authenticated) {
ctx.state.auth = { strategy, credentials };
return next();
}
}
return next();
},
async verify(auth, config = {}) {
if (config.public) {
return undefined;
}
if (!auth) {
throw new UnauthorizedError();
}
return await auth.strategy.verify(auth, config);
},
};
};
module.exports = () => {
return {
auth: createAuthentication(),
errors: {
UnauthorizedError,
ForbiddenError,
},
};
};

View File

@ -21,7 +21,7 @@ describe('metrics', () => {
strapi: '0.0.0',
},
},
app: {
server: {
use,
},
});
@ -43,7 +43,7 @@ describe('metrics', () => {
strapi: '0.0.0',
},
},
app: {
server: {
use,
},
});
@ -63,7 +63,7 @@ describe('metrics', () => {
strapi: '0.0.0',
},
},
app: {
server: {
use() {},
},
});
@ -96,7 +96,7 @@ describe('metrics', () => {
strapi: '0.0.0',
},
},
app: {
server: {
use() {},
},
});

View File

@ -34,7 +34,7 @@ const createTelemetryInstance = strapi => {
const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
crons.push(pingCron);
strapi.app.use(createMiddleware({ sendEvent }));
strapi.server.use(createMiddleware({ sendEvent }));
}
if (strapi.EE === true && ee.isEE === true) {

View File

@ -0,0 +1,13 @@
'use strict';
const { createAPI } = require('./api');
const createAdminAPI = strapi => {
const opts = {
prefix: '', // '/admin';
};
return createAPI(strapi, opts);
};
module.exports = { createAdminAPI };

View File

@ -0,0 +1,32 @@
'use strict';
const Router = require('@koa/router');
const { createRouteManager } = require('./routing');
const createAPI = (strapi, opts = {}) => {
const { prefix, defaultPolicies } = opts;
const api = new Router({ prefix });
const routeManager = createRouteManager(strapi, { defaultPolicies });
return {
use(fn) {
api.use(fn);
return this;
},
routes(routes) {
routeManager.addRoutes(routes, api);
return this;
},
mount(router) {
router.use(api.routes(), api.allowedMethods());
return this;
},
};
};
module.exports = { createAPI };

View File

@ -0,0 +1,69 @@
'use strict';
const { toLower, castArray, trim } = require('lodash/fp');
const compose = require('koa-compose');
const { resolveMiddlewares } = require('./middleware');
const { resolvePolicies } = require('./policy');
const getMethod = route => trim(toLower(route.method));
const getPath = route => trim(route.path);
const routeInfoMiddleware = route => (ctx, next) => {
ctx.state.route = route;
return next();
};
module.exports = strapi => {
return (route, { pluginName, router, apiName }) => {
try {
const method = getMethod(route);
const path = getPath(route);
const middlewares = resolveMiddlewares(route);
const policies = resolvePolicies(route, { pluginName, apiName });
const action = getAction(route, { pluginName, apiName }, strapi);
const routeHandler = compose([
routeInfoMiddleware(route),
...policies,
...middlewares,
...castArray(action),
]);
router[method](path, routeHandler);
} catch (error) {
throw new Error(`Error creating endpoint ${route.method} ${route.path}: ${error.message}`);
}
};
};
const getController = (name, { pluginName, apiName }, strapi) => {
if (pluginName) {
if (pluginName === 'admin') {
return strapi.controller(`admin::${name}`);
}
return strapi.plugin(pluginName).controller(name);
} else if (apiName) {
return strapi.controller(`api::${apiName}.${name}`);
}
return strapi.controller(name);
};
const getAction = ({ handler }, { pluginName, apiName }, strapi) => {
if (Array.isArray(handler) || typeof handler === 'function') {
return handler;
}
const [controllerName, actionName] = trim(handler).split('.');
const controller = getController(toLower(controllerName), { pluginName, apiName }, strapi);
if (typeof controller[actionName] !== 'function') {
throw new Error(`Handler not found "${handler}"`);
}
return controller[actionName].bind(controller);
};

View File

@ -0,0 +1,52 @@
'use strict';
const { prop } = require('lodash/fp');
const { createAPI } = require('./api');
const getAuthConfig = prop('config.auth');
const createAuthPolicy = strapi => async (ctx, next) => {
const { auth, route } = ctx.state;
if (!route) {
return ctx.unauthorized();
}
try {
await strapi.container.get('content-api').auth.verify(auth, getAuthConfig(route));
return next();
} catch (error) {
const { errors } = strapi.container.get('content-api');
if (error instanceof errors.UnauthorizedError) {
return ctx.unauthorized();
}
if (error instanceof errors.ForbiddenError) {
return ctx.forbidden();
}
throw error;
}
};
const createContentAPI = strapi => {
const opts = {
prefix: strapi.config.get('api.prefix', '/api'),
defaultPolicies: [createAuthPolicy(strapi)],
};
const api = createAPI(strapi, opts);
// implement auth providers
api.use((ctx, next) => {
return strapi.container.get('content-api').auth.authenticate(ctx, next);
});
return api;
};
module.exports = {
createContentAPI,
};

View File

@ -0,0 +1,112 @@
'use strict';
const Koa = require('koa');
const Router = require('@koa/router');
const { createHTTPServer } = require('./http-server');
const { createRouteManager } = require('./routing');
const { createAdminAPI } = require('./admin-api');
const { createContentAPI } = require('./content-api');
const healthCheck = async (ctx, next) => {
if (ctx.request.url === '/_health' && ['HEAD', 'GET'].includes(ctx.request.method)) {
ctx.set('strapi', 'You are so French!');
ctx.status = 204;
} else {
await next();
}
};
/**
* @typedef Server
*
* @property {Koa} app
* @property {http.Server} app
*/
/**
*
* @param {Strapi} strapi
* @returns {Server}
*/
const createServer = strapi => {
const app = new Koa({
proxy: strapi.config.get('server.proxy'),
});
const router = new Router({
// FIXME: this prefix can break the admin if not specified in the admin url
prefix: strapi.config.get('middleware.settings.router.prefix', ''),
});
const routeManager = createRouteManager(strapi);
const httpServer = createHTTPServer(strapi, app);
const apis = {
'content-api': createContentAPI(strapi),
admin: createAdminAPI(strapi),
};
// init health check
app.use(healthCheck);
const state = {
mounted: false,
};
return {
app,
router,
httpServer,
api(name) {
return apis[name];
},
use(...args) {
app.use(...args);
return this;
},
routes(routes) {
if (routes.type) {
const api = apis[routes.type];
if (!api) {
throw new Error(`API ${routes.type} not found. Possible APIs are ${Object.keys(apis)}`);
}
apis[routes.type].routes(routes);
return this;
}
routeManager.addRoutes(routes, router);
return this;
},
mount() {
state.mounted = true;
Object.values(apis).forEach(api => api.mount(router));
app.use(router.routes()).use(router.allowedMethods());
return this;
},
listen(...args) {
if (!state.mounted) {
this.mount();
}
return httpServer.listen(...args);
},
async destroy() {
await httpServer.destroy();
},
};
};
module.exports = {
createServer,
};

View File

@ -0,0 +1,28 @@
'use strict';
const { propOr } = require('lodash/fp');
const getMiddlewareConfig = propOr([], 'config.middlewares');
const resolveMiddlewares = route => {
const middlewaresConfig = getMiddlewareConfig(route);
return middlewaresConfig.map(middlewareConfig => {
if (typeof middlewareConfig === 'function') {
return middlewareConfig;
}
// TODO: this won't work until we have the new middleware formats
const middleware = strapi.middleware(middlewareConfig);
if (!middleware) {
throw new Error(`Middleware ${middlewareConfig} not found.`);
}
return middleware;
});
};
module.exports = {
resolveMiddlewares,
};

View File

@ -0,0 +1,19 @@
'use strict';
const { propOr } = require('lodash/fp');
const policy = require('@strapi/utils/lib/policy');
const { bodyPolicy } = policy;
const getPoliciesConfig = propOr([], 'config.policies');
const resolvePolicies = (route, opts = {}) => {
const policiesConfig = getPoliciesConfig(route);
const policies = policiesConfig.map(policyName => policy.get(policyName, opts));
return [...policies, bodyPolicy];
};
module.exports = {
resolvePolicies,
};

View File

@ -0,0 +1,111 @@
'use strict';
const Router = require('@koa/router');
const _ = require('lodash');
const { has } = require('lodash/fp');
const { yup } = require('@strapi/utils');
const createEndpointComposer = require('./compose-endpoint');
const policyOrMiddlewareSchema = yup.lazy(value => {
if (typeof value === 'string') {
return yup.string().required();
}
if (typeof value === 'function') {
return yup.mixed().isFunction();
}
return yup.object({
name: yup.string().required(),
options: yup.object().notRequired(), // any options
});
});
const routeSchema = yup.object({
method: yup
.string()
.oneOf(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'ALL'])
.required(),
path: yup.string().required(),
handler: yup.lazy(value => {
if (typeof value === 'string') {
return yup.string().required();
}
if (Array.isArray(value)) {
return yup.array().required();
}
return yup
.mixed()
.isFunction()
.required();
}),
config: yup
.object({
policies: yup
.array()
.of(policyOrMiddlewareSchema)
.notRequired(),
middlwares: yup
.array()
.of(policyOrMiddlewareSchema)
.notRequired(),
})
.notRequired(),
});
const validateRouteConfig = routeConfig => {
try {
return routeSchema.validateSync(routeConfig, {
strict: true,
abortEarly: false,
stripUnknown: true,
});
} catch (error) {
throw new Error('Invalid route config', error.message);
}
};
const createRouteManager = (strapi, opts = {}) => {
const composeEndpoint = createEndpointComposer(strapi);
const createRoute = (route, router) => {
validateRouteConfig(route);
if (opts.defaultPolicies) {
if (has('config.policies', route)) {
route.config.policies.unshift(...opts.defaultPolicies);
} else {
_.set(route, 'config.policies', [...opts.defaultPolicies]);
}
}
composeEndpoint(route, { ...route.info, router });
};
const addRoutes = (routes, router) => {
if (Array.isArray(routes)) {
routes.forEach(route => createRoute(route, router));
} else if (routes.routes) {
const subRouter = new Router({ prefix: routes.prefix });
routes.routes.forEach(route => {
const hasPrefix = has('prefix', route.config);
createRoute(route, hasPrefix ? router : subRouter);
});
return router.use(subRouter.routes(), subRouter.allowedMethods());
}
};
return {
addRoutes,
};
};
module.exports = {
validateRouteConfig,
createRouteManager,
};

View File

@ -14,10 +14,11 @@
},
"dependencies": {
"@koa/cors": "^3.0.0",
"@koa/router": "10.1.1",
"@strapi/admin": "3.6.8",
"@strapi/database": "3.6.8",
"@strapi/generators": "3.6.8",
"@strapi/generate-new": "3.6.8",
"@strapi/generators": "3.6.8",
"@strapi/logger": "3.6.8",
"@strapi/plugin-content-manager": "3.6.8",
"@strapi/plugin-content-type-builder": "3.6.8",
@ -52,7 +53,7 @@
"koa-i18n": "^2.1.0",
"koa-ip": "^2.0.0",
"koa-locale": "~1.3.0",
"koa-router": "^7.4.0",
"koa-mount": "4.0.0",
"koa-session": "^6.2.0",
"koa-static": "^5.0.0",
"lodash": "4.17.21",

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -56,7 +56,7 @@ describe('Core API - Basic + compo', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -55,7 +55,7 @@ describe('Core API - Basic + compo', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -57,7 +57,7 @@ describe('Core API - Basic + compo + draftAndPublish', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -56,7 +56,7 @@ describe('Core API - Basic + compo + draftAndPublish', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -54,7 +54,7 @@ describe('Core API - Basic + draftAndPublish', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,7 @@ const _ = require('lodash');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -55,7 +55,7 @@ describe('Core API - Basic + dz', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -48,7 +48,7 @@ describe('Core API - Basic', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -40,8 +40,8 @@ describe('Non repeatable and Not required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -40,8 +40,8 @@ describe('Non repeatable and Not required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -38,8 +38,8 @@ describe('Non repeatable and Not required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -38,8 +38,8 @@ describe('Non repeatable and required component', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withcomponents');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withcomponents');
});
afterAll(async () => {

View File

@ -4,7 +4,10 @@
const _ = require('lodash');
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request');
const {
createContentAPIRequest,
transformToRESTResource,
} = require('../../../../test/helpers/request');
const builder = createTestBuilder();
const data = {
@ -89,7 +92,7 @@ describe('Deep Filtering API', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
Object.assign(
data,

View File

@ -2,7 +2,7 @@
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -87,8 +87,8 @@ describe('Not required dynamiczone', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withdynamiczones');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withdynamiczones');
});
afterAll(async () => {

View File

@ -5,7 +5,7 @@ const path = require('path');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
@ -77,10 +77,10 @@ describe('Not required dynamiczone', () => {
strapi = await createStrapiInstance();
baseRq = await createAuthRequest({ strapi });
baseRq = await createContentAPIRequest({ strapi });
rq = await createAuthRequest({ strapi });
rq.setURLPrefix('/withdynamiczonemedias');
rq = await createContentAPIRequest({ strapi });
rq.setURLPrefix('/api/withdynamiczonemedias');
});
afterAll(async () => {

View File

@ -2,7 +2,7 @@
// Helpers.
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../test/helpers/request');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const modelsUtils = require('../../../../test/helpers/models');
@ -29,7 +29,7 @@ describe('Create Strapi API End to End', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -4,7 +4,10 @@
const _ = require('lodash');
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request');
const {
createContentAPIRequest,
transformToRESTResource,
} = require('../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
@ -89,7 +92,7 @@ describe('Filtering API', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
Object.assign(
data,

View File

@ -1,7 +1,7 @@
'use strict';
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../test/helpers/request');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const builder = createTestBuilder();
@ -152,7 +152,7 @@ describe('Publication State', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
Object.assign(data, builder.sanitizedFixtures(strapi));
});

View File

@ -3,7 +3,10 @@
// Test an API with all the possible filed types and simple filtering (no deep filtering, no relations)
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const { createAuthRequest, transformToRESTResource } = require('../../../../test/helpers/request');
const {
createContentAPIRequest,
transformToRESTResource,
} = require('../../../../test/helpers/request');
const builder = createTestBuilder();
let rq;
@ -127,7 +130,7 @@ describe('Search query', () => {
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
data.bed = builder.sanitizedFixturesFor(bedModel.name, strapi);
});

View File

@ -2,7 +2,7 @@
// Helpers.
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../test/helpers/request');
const { createContentAPIRequest } = require('../../../../test/helpers/request');
const { createTestBuilder } = require('../../../../test/helpers/builder');
const builder = createTestBuilder();
@ -26,7 +26,8 @@ describe('Content Manager single types', () => {
await builder.addContentType(model).build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
rq = await createContentAPIRequest({ strapi });
});
afterAll(async () => {

View File

@ -1,7 +1,7 @@
'use strict';
const uploadController = require('./upload');
const upload = require('./upload');
module.exports = {
upload: uploadController,
upload,
};

View File

@ -38,18 +38,11 @@ module.exports = {
getSettings: resolveControllerMethod('getSettings'),
async upload(ctx) {
const isUploadDisabled = _.get(strapi.plugins, 'upload.config.enabled', true) === false;
if (isUploadDisabled) {
throw strapi.errors.badRequest(null, {
errors: [{ id: 'Upload.status.disabled', message: 'File upload is disabled' }],
});
}
const {
query: { id },
request: { files: { files } = {} },
} = ctx;
const controller = resolveController(ctx);
if (id && (_.isEmpty(files) || files.size === 0)) {

View File

@ -7,7 +7,7 @@ const { getService } = require('../../utils');
const sanitize = (data, options = {}) => {
return sanitizeEntity(data, {
model: strapi.getModel('file', 'upload'),
model: strapi.getModel('plugin::upload.file'),
...options,
});
};

View File

@ -3,7 +3,6 @@
const { resolve } = require('path');
const range = require('koa-range');
const koaStatic = require('koa-static');
const _ = require('lodash');
module.exports = {
defaults: { upload: { enabled: true } },
@ -15,7 +14,7 @@ module.exports = {
);
const staticDir = resolve(strapi.dir, configPublicPath);
strapi.app.on('error', err => {
strapi.server.app.on('error', err => {
if (err.code === 'EPIPE') {
// when serving audio or video the browsers sometimes close the connection to go to range requests instead.
// This causes koa to emit a write EPIPE error. We can ignore it.
@ -23,16 +22,18 @@ module.exports = {
return;
}
strapi.app.onerror(err);
strapi.server.app.onerror(err);
});
const localServerConfig =
_.get(strapi, 'plugins.upload.config.providerOptions.localServer') || {};
strapi.router.get(
'/uploads/(.*)',
range,
koaStatic(staticDir, { defer: true, ...localServerConfig })
);
const localServerConfig = strapi.config.get('plugin.upload.providerOptions.localeServer', {});
strapi.server.routes([
{
method: 'GET',
path: '/uploads/(.*)',
handler: [range, koaStatic(staticDir, { defer: true, ...localServerConfig })],
},
]);
},
},
};

View File

@ -0,0 +1,119 @@
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/settings',
handler: 'upload.getSettings',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.settings.read'],
},
},
],
},
},
{
method: 'PUT',
path: '/settings',
handler: 'upload.updateSettings',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.settings.read'],
},
},
],
},
},
{
method: 'POST',
path: '/',
handler: 'upload.upload',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'GET',
path: '/files/count',
handler: 'upload.count',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.read'],
},
},
],
},
},
{
method: 'GET',
path: '/files',
handler: 'upload.find',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.read'],
},
},
],
},
},
{
method: 'GET',
path: '/files/:id',
handler: 'upload.findOne',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.read'],
},
},
],
},
},
{
method: 'GET',
path: '/search/:id',
handler: 'upload.search',
config: {
policies: ['admin::isAuthenticatedAdmin'],
},
},
{
method: 'DELETE',
path: '/files/:id',
handler: 'upload.destroy',
config: {
policies: [
'admin::isAuthenticatedAdmin',
{
name: 'admin::hasPermissions',
options: {
actions: ['plugin::upload.assets.update'],
},
},
],
},
},
],
};

View File

@ -0,0 +1,32 @@
'use strict';
module.exports = {
type: 'content-api',
routes: [
{
method: 'POST',
path: '/',
handler: 'upload.upload',
},
{
method: 'GET',
path: '/files/count',
handler: 'upload.count',
},
{
method: 'GET',
path: '/files',
handler: 'upload.find',
},
{
method: 'GET',
path: '/files/:id',
handler: 'upload.findOne',
},
{
method: 'DELETE',
path: '/files/:id',
handler: 'upload.destroy',
},
],
};

View File

@ -1,98 +1,6 @@
'use strict';
module.exports = [
{
method: 'GET',
path: '/settings',
handler: 'upload.getSettings',
config: {
policies: [],
},
},
{
method: 'PUT',
path: '/settings',
handler: 'upload.updateSettings',
config: {
policies: [],
},
},
{
method: 'POST',
path: '/',
handler: 'upload.upload',
config: {
policies: [],
description: 'upload a file',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
{
method: 'GET',
path: '/files/count',
handler: 'upload.count',
config: {
policies: [],
description: 'Retrieve the total number of uploaded files',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
{
method: 'GET',
path: '/files',
handler: 'upload.find',
config: {
policies: [],
description: 'Retrieve all file documents',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
{
method: 'GET',
path: '/files/:id',
handler: 'upload.findOne',
config: {
policies: [],
description: 'Retrieve a single file depending on its id',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
{
method: 'GET',
path: '/search/:id',
handler: 'upload.search',
config: {
policies: [],
description: 'Search for an uploaded file',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
{
method: 'DELETE',
path: '/files/:id',
handler: 'upload.destroy',
config: {
policies: [],
description: 'Delete an uploaded file',
tag: {
plugin: 'upload',
name: 'File',
},
},
},
];
module.exports = {
admin: require('./admin'),
'content-api': require('./content-api'),
};

View File

@ -53,7 +53,7 @@ const combineFilters = params => {
module.exports = ({ strapi }) => ({
emitEvent(event, data) {
const modelDef = strapi.getModel('file', 'upload');
const modelDef = strapi.getModel('plugin::upload.file');
strapi.eventHub.emit(event, { media: sanitizeEntity(data, { model: modelDef }) });
},

Some files were not shown because too many files have changed in this diff Show More