Refactor relation-list route

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-10-27 16:01:46 +01:00
parent b37a92c60f
commit 7509a16152
6 changed files with 140 additions and 91 deletions

View File

@ -67,6 +67,24 @@
} }
}, },
{
"method": "GET",
"path": "/relations/:model/:targetField",
"handler": "relations.find",
"config": {
"policies": [
"admin::isAuthenticatedAdmin",
[
"plugins::content-manager.hasPermissions",
[
"plugins::content-manager.explorer.create",
"plugins::content-manager.explorer.update"
],
{ "hasAtLeastOne": true }
]
]
}
},
{ {
"method": "GET", "method": "GET",
@ -100,25 +118,7 @@
] ]
} }
}, },
{
"method": "GET",
"path": "/explorer/:model/relation-list/:targetField",
"handler": "ContentManager.findRelationList",
"config": {
"policies": [
"routing",
"admin::isAuthenticatedAdmin",
[
"plugins::content-manager.hasPermissions",
[
"plugins::content-manager.explorer.create",
"plugins::content-manager.explorer.update"
],
{ "hasAtLeastOne": true }
]
]
}
},
{ {
"method": "POST", "method": "POST",
"path": "/explorer/:model", "path": "/explorer/:model",

View File

@ -342,56 +342,4 @@ module.exports = {
ctx.body = pm.sanitize(unpublishedEntry, { action: ACTIONS.read }); ctx.body = pm.sanitize(unpublishedEntry, { action: ACTIONS.read });
}, },
async findRelationList(ctx) {
const { model, targetField } = ctx.params;
const { _component, ...query } = ctx.request.query;
const contentManagerServices = strapi.plugins['content-manager'].services;
if (!targetField) {
return ctx.badRequest();
}
const modelDef = _component ? strapi.db.getModel(_component) : strapi.db.getModel(model);
if (!modelDef) {
return ctx.notFound('model.notFound');
}
const attr = modelDef.attributes[targetField];
if (!attr) {
return ctx.badRequest('targetField.invalid');
}
const target = strapi.db.getModelByAssoc(attr);
if (!target) {
return ctx.notFound('target.notFound');
}
const contentManagerService = contentManagerServices.contentmanager;
let entities = [];
if (_.has(ctx.request.query, '_q')) {
entities = await contentManagerService.search(target.uid, query);
} else {
entities = await contentManagerService.fetchAll(target.uid, query);
}
if (!entities) {
return ctx.notFound();
}
const modelConfig = _component
? await contentManagerServices.components.getConfiguration(modelDef.uid)
: await contentManagerServices.contenttypes.getConfiguration(modelDef.uid);
const field = _.get(modelConfig, `metadatas.${targetField}.edit.mainField`, 'id');
const pickFields = [field, 'id', target.primaryKey, PUBLISHED_AT_ATTRIBUTE];
const sanitize = d => _.pick(d, pickFields);
ctx.body = _.isArray(entities) ? entities.map(sanitize) : sanitize(entities);
},
}; };

View File

@ -1,10 +1,10 @@
'use strict'; 'use strict';
const createContext = require('../../../../test/helpers/create-context'); const createContext = require('../../../../test/helpers/create-context');
const ContentManager = require('../ContentManager'); const relations = require('../relations');
describe('ContentManager', () => { describe('Relations', () => {
describe('findRelationList', () => { describe('find', () => {
test('Fails on model not found', async () => { test('Fails on model not found', async () => {
const notFound = jest.fn(); const notFound = jest.fn();
const ctx = createContext( const ctx = createContext(
@ -26,7 +26,7 @@ describe('ContentManager', () => {
}, },
}; };
await ContentManager.findRelationList(ctx); await relations.find(ctx);
expect(notFound).toHaveBeenCalledWith('model.notFound'); expect(notFound).toHaveBeenCalledWith('model.notFound');
}); });
@ -55,7 +55,7 @@ describe('ContentManager', () => {
}, },
}; };
await ContentManager.findRelationList(ctx); await relations.find(ctx);
expect(badRequest).toHaveBeenCalledWith('targetField.invalid'); expect(badRequest).toHaveBeenCalledWith('targetField.invalid');
}); });
@ -84,7 +84,7 @@ describe('ContentManager', () => {
}, },
}; };
await ContentManager.findRelationList(ctx); await relations.find(ctx);
expect(notFound).toHaveBeenCalledWith('target.notFound'); expect(notFound).toHaveBeenCalledWith('target.notFound');
}); });
@ -111,8 +111,8 @@ describe('ContentManager', () => {
plugins: { plugins: {
'content-manager': { 'content-manager': {
services: { services: {
contenttypes: { 'content-types': {
getConfiguration() { findConfiguration() {
return { return {
metadatas: { metadatas: {
target: { target: {
@ -145,7 +145,7 @@ describe('ContentManager', () => {
}, },
}; };
await ContentManager.findRelationList(ctx); await relations.find(ctx);
expect(ctx.body).toEqual([ expect(ctx.body).toEqual([
{ {

View File

@ -0,0 +1,57 @@
'use strict';
const { has, prop, pick } = require('lodash/fp');
const { PUBLISHED_AT_ATTRIBUTE } = require('strapi-utils').contentTypes.constants;
const { getService } = require('../utils');
module.exports = {
async find(ctx) {
const { model, targetField } = ctx.params;
const { _component, ...query } = ctx.request.query;
if (!targetField) {
return ctx.badRequest();
}
const modelDef = _component ? strapi.db.getModel(_component) : strapi.db.getModel(model);
if (!modelDef) {
return ctx.notFound('model.notFound');
}
const attr = modelDef.attributes[targetField];
if (!attr) {
return ctx.badRequest('targetField.invalid');
}
const target = strapi.db.getModelByAssoc(attr);
if (!target) {
return ctx.notFound('target.notFound');
}
const contentManagerService = getService('contentmanager');
let entities = [];
if (has('_q', ctx.request.query)) {
entities = await contentManagerService.search(target.uid, query);
} else {
entities = await contentManagerService.fetchAll(target.uid, query);
}
if (!entities) {
return ctx.notFound();
}
const modelConfig = _component
? await getService('components').findConfiguration(modelDef)
: await getService('content-types').findConfiguration(modelDef);
const field = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id';
const pickFields = [field, 'id', target.primaryKey, PUBLISHED_AT_ATTRIBUTE];
ctx.body = entities.map(pick(pickFields));
},
};

View File

@ -276,14 +276,50 @@ paths:
description: Suggestion if request value is not available description: Suggestion if request value is not available
# Relationships # Relationships
/content-manager/relationships/{modelUid}/{targetField}: /content-manager/relations/{model}/{targetField}:
get: get:
tags: tags:
- Relational fields - Relational fields
description: Fetch list of possible related content description: Fetch list of possible related content
parameters:
- $ref: '#/components/parameters/model'
- in: path
name: targetField
schema:
type: string
required: true
description: name of the field in the model that holds the relation
- in: query
name: _component
schema:
type: string
description: Component uid if the targetField is in a component
responses:
200:
description: Returns a list of sanitized entries based of the relational attribute info
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
oneOf:
- type: string
- type: integer
'[primaryKey]':
oneOf:
- type: string
- type: integer
'[mainField]':
type: string
description: value of the mainField of the entry
published_at:
type: date
# Collection type # Collection type
/content-manager/collection-type/{modelUid}: /content-manager/collection-type/{model}:
get: get:
tags: tags:
- Collection Types content management - Collection Types content management
@ -292,12 +328,12 @@ paths:
tags: tags:
- Collection Types content management - Collection Types content management
description: Create an entry description: Create an entry
/content-manager/collection-type/{modelUid}/actions/bulkDelete: /content-manager/collection-type/{model}/actions/bulkDelete:
post: post:
tags: tags:
- Collection Types content management - Collection Types content management
description: Bulk delete entries description: Bulk delete entries
/content-manager/collection-type/{modelUid}/{id}: /content-manager/collection-type/{model}/{id}:
get: get:
tags: tags:
- Collection Types content management - Collection Types content management
@ -310,19 +346,19 @@ paths:
tags: tags:
- Collection Types content management - Collection Types content management
description: Delete one entry description: Delete one entry
/content-manager/collection-type/{modelUid}/{id}/actions/publish: /content-manager/collection-type/{model}/{id}/actions/publish:
post: post:
tags: tags:
- Collection Types content management - Collection Types content management
description: Publish one entry description: Publish one entry
/content-manager/collection-type/{modelUid}/{id}/actions/unpublish: /content-manager/collection-type/{model}/{id}/actions/unpublish:
post: post:
tags: tags:
- Collection Types content management - Collection Types content management
description: Unpublish one entry description: Unpublish one entry
# Single type # Single type
/content-manager/single-type/{modelUid}: /content-manager/single-type/{model}:
get: get:
tags: tags:
- Single Types content management - Single Types content management
@ -332,11 +368,11 @@ paths:
delete: delete:
tags: tags:
- Single Types content management - Single Types content management
/content-manager/single-type/{modelUid}/actions/publish: /content-manager/single-type/{model}/actions/publish:
post: post:
tags: tags:
- Single Types content management - Single Types content management
/content-manager/single-type/{modelUid}/actions/unpublish: /content-manager/single-type/{model}/actions/unpublish:
post: post:
tags: tags:
- Single Types content management - Single Types content management
@ -521,6 +557,14 @@ components:
$ref: '#/components/schemas/kind' $ref: '#/components/schemas/kind'
description: The number of items to skip before starting to collect the result set description: The number of items to skip before starting to collect the result set
model:
in: path
name: model
schema:
type: string
required: true
description: Model uid
securitySchemes: securitySchemes:
bearerAuth: bearerAuth:
type: http type: http

View File

@ -141,7 +141,7 @@ describe('Relation-list route', () => {
test('Can get relation-list for products of a shop', async () => { test('Can get relation-list for products of a shop', async () => {
const res = await rq({ const res = await rq({
method: 'GET', method: 'GET',
url: '/content-manager/explorer/application::shop.shop/relation-list/products', url: '/content-manager/relations/application::shop.shop/products',
}); });
expect(res.body).toHaveLength(data.products.length); expect(res.body).toHaveLength(data.products.length);
@ -165,7 +165,7 @@ describe('Relation-list route', () => {
test('Can get relation-list for products of a shop', async () => { test('Can get relation-list for products of a shop', async () => {
const res = await rq({ const res = await rq({
method: 'GET', method: 'GET',
url: '/content-manager/explorer/application::shop.shop/relation-list/products', url: '/content-manager/relations/application::shop.shop/products',
}); });
expect(res.body).toHaveLength(data.products.length); expect(res.body).toHaveLength(data.products.length);