implement new findAvailable route

This commit is contained in:
Pierre Noël 2022-10-04 17:10:18 +02:00
parent 1200e43618
commit 241393eaa3
3 changed files with 42 additions and 72 deletions

View File

@ -26,57 +26,52 @@ module.exports = {
// idsToOmit: used to exclude relations that the front already added but that were not saved yet // idsToOmit: used to exclude relations that the front already added but that were not saved yet
// idsToInclude: used to include relations that the front removed but not saved yes // idsToInclude: used to include relations that the front removed but not saved yes
const { component, entityId, idsToOmit, idsToInclude, _q, ...query } = ctx.request.query; const { entityId, idsToOmit, idsToInclude, _q, ...query } = ctx.request.query;
const sourceModelUid = component || model; const modelSchema = strapi.getModel(model);
if (!modelSchema) {
const sourceModel = strapi.getModel(sourceModelUid);
if (!sourceModel) {
return ctx.badRequest("The model doesn't exist"); return ctx.badRequest("The model doesn't exist");
} }
const permissionChecker = getService('permission-checker').create({ const attribute = modelSchema.attributes[targetField];
userAbility,
model,
});
if (permissionChecker.cannot.read()) {
return ctx.forbidden();
}
const attribute = sourceModel.attributes[targetField];
if (!attribute || attribute.type !== 'relation') { if (!attribute || attribute.type !== 'relation') {
return ctx.badRequest("This relational field doesn't exist"); return ctx.badRequest("This relational field doesn't exist");
} }
// TODO: find a way to check field permission for component const isComponent = modelSchema.modelType === 'component';
if (!component && permissionChecker.cannot.read(null, targetField)) {
return ctx.forbidden();
}
if (entityId) { // RBAC checks when it's a content-type
const entityManager = getService('entity-manager'); // TODO: do RBAC check for components too
if (!isComponent) {
const permissionChecker = getService('permission-checker').create({
userAbility,
model,
});
const entity = await entityManager.findOneWithCreatorRoles(entityId, model); if (permissionChecker.cannot.read(null, targetField)) {
if (!entity) {
return ctx.notFound();
}
if (!component && permissionChecker.cannot.read(entity, targetField)) {
return ctx.forbidden(); return ctx.forbidden();
} }
// TODO: find a way to check field permission for component
if (component && permissionChecker.cannot.read(entity)) { if (entityId) {
return ctx.forbidden(); const entityManager = getService('entity-manager');
const entity = await entityManager.findOneWithCreatorRoles(entityId, model);
if (!entity) {
return ctx.notFound();
}
if (permissionChecker.cannot.read(entity, targetField)) {
return ctx.forbidden();
}
} }
} }
const targetedModel = strapi.getModel(attribute.target); const targetedModel = strapi.getModel(attribute.target);
const modelConfig = component const modelConfig = isComponent
? await getService('components').findConfiguration(sourceModel) ? await getService('components').findConfiguration(modelSchema)
: await getService('content-types').findConfiguration(sourceModel); : await getService('content-types').findConfiguration(modelSchema);
const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id'; const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id';
const fieldsToSelect = ['id', mainField]; const fieldsToSelect = ['id', mainField];
@ -101,7 +96,7 @@ module.exports = {
} }
if (entityId) { if (entityId) {
const subQuery = strapi.db.queryBuilder(sourceModel.uid); const subQuery = strapi.db.queryBuilder(modelSchema.uid);
const alias = subQuery.getAlias(); const alias = subQuery.getAlias();
@ -153,10 +148,6 @@ module.exports = {
model, model,
}); });
if (permissionChecker.cannot.read()) {
return ctx.forbidden();
}
if (permissionChecker.cannot.read(null, targetField)) { if (permissionChecker.cannot.read(null, targetField)) {
return ctx.forbidden(); return ctx.forbidden();
} }

View File

@ -84,19 +84,7 @@ module.exports = {
path: '/relations/:model/:targetField', path: '/relations/:model/:targetField',
handler: 'relations.findAvailable', handler: 'relations.findAvailable',
config: { config: {
policies: [ policies: ['admin::isAuthenticatedAdmin'],
'admin::isAuthenticatedAdmin',
{
name: 'plugin::content-manager.hasPermissions',
config: {
actions: [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.update',
],
hasAtLeastOne: true,
},
},
],
}, },
}, },
{ {

View File

@ -180,25 +180,25 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
['products_mw', false], ['products_mw', false],
['compo_products_ow', true], ['compo_products_ow', true],
['compo_products_mw', true], ['compo_products_mw', true],
])('Relation not in a component (%s)', (fieldName, isComponent) => { ])('Relation (%s) (is in component: %s)', (fieldName, isComponent) => {
let entityId; let entityId;
let entityIdEmptyShop; let entityIdEmptyShop;
let modelUID;
beforeAll(() => { beforeAll(() => {
entityId = isComponent ? data.shops[0].myCompo.id : data.shops[0].id; entityId = isComponent ? data.shops[0].myCompo.id : data.shops[0].id;
entityIdEmptyShop = isComponent ? data.shops[1].myCompo.id : data.shops[1].id; entityIdEmptyShop = isComponent ? data.shops[1].myCompo.id : data.shops[1].id;
modelUID = isComponent ? 'default.compo' : 'api::shop.shop';
}); });
test('Can retrieve available relation(s) for an entity that have some relations', async () => { test('Can retrieve available relation(s) for an entity that have some relations', async () => {
let res = await rq({ let res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
entityId, entityId,
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
expect(res.status).toBe(200); expect(res.status).toBe(200);
expect(res.body.results).toMatchObject([ expect(res.body.results).toMatchObject([
@ -212,11 +212,10 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
// can omitIds // can omitIds
res = await rq({ res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
entityId, entityId,
idsToOmit: [data.products[1].id], idsToOmit: [data.products[1].id],
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
@ -226,10 +225,9 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
test("Can retrieve available relation(s) for an entity that don't have relations yet", async () => { test("Can retrieve available relation(s) for an entity that don't have relations yet", async () => {
let res = await rq({ let res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
entityId: entityIdEmptyShop, entityId: entityIdEmptyShop,
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
@ -251,11 +249,10 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
// can omitIds // can omitIds
res = await rq({ res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
entityId, entityId,
idsToOmit: [data.products[1].id], idsToOmit: [data.products[1].id],
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
@ -265,10 +262,7 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
test('Can retrieve available relation(s) without entity', async () => { test('Can retrieve available relation(s) without entity', async () => {
let res = await rq({ let res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: {
...(isComponent ? { component: 'default.compo' } : {}),
},
}); });
expect(res.status).toBe(200); expect(res.status).toBe(200);
@ -289,10 +283,9 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
// can omitIds // can omitIds
res = await rq({ res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
idsToOmit: [data.products[0].id], idsToOmit: [data.products[0].id],
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
@ -311,11 +304,10 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
test("search ''", async () => { test("search ''", async () => {
const res = await rq({ const res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
_q: '', _q: '',
...(withEntity ? { entityId } : {}), ...(withEntity ? { entityId } : {}),
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });
@ -331,11 +323,10 @@ describe.each([[false], [true]])('Relations, with d&p: %p', (withDraftAndPublish
test("search 'Can'", async () => { test("search 'Can'", async () => {
const res = await rq({ const res = await rq({
method: 'GET', method: 'GET',
url: `/content-manager/relations/api::shop.shop/${fieldName}`, url: `/content-manager/relations/${modelUID}/${fieldName}`,
qs: { qs: {
_q: 'Can', _q: 'Can',
...(withEntity ? { entityId } : {}), ...(withEntity ? { entityId } : {}),
...(isComponent ? { component: 'default.compo' } : {}),
}, },
}); });