From 7f521c934832b9ffa0a985527f262ef80dcbe42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20No=C3=ABl?= Date: Fri, 5 Aug 2022 16:05:52 +0200 Subject: [PATCH] refactor relation findNew route --- .../server/controllers/relations.js | 97 +++++++++++-------- .../controllers/validation/relations.js | 27 ++++++ .../content-manager/server/routes/admin.js | 4 +- .../core/database/lib/query/query-builder.js | 47 +++++---- 4 files changed, 115 insertions(+), 60 deletions(-) create mode 100644 packages/core/content-manager/server/controllers/validation/relations.js diff --git a/packages/core/content-manager/server/controllers/relations.js b/packages/core/content-manager/server/controllers/relations.js index 35dda67a86..9c8bf522b8 100644 --- a/packages/core/content-manager/server/controllers/relations.js +++ b/packages/core/content-manager/server/controllers/relations.js @@ -1,64 +1,85 @@ 'use strict'; -const { prop, pick } = require('lodash/fp'); +const { prop, isEmpty } = require('lodash/fp'); const { PUBLISHED_AT_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants; const { getService } = require('../utils'); +const { validateFindNew } = require('./validation/relations'); module.exports = { - async find(ctx) { + async findNew(ctx) { const { model, targetField } = ctx.params; - const { _component, ...query } = ctx.request.query; - const { idsToOmit } = ctx.request.body; - if (!targetField) { - return ctx.badRequest(); - } + await validateFindNew(ctx.request.query); - const modelDef = _component ? strapi.getModel(_component) : strapi.getModel(model); + const { component, entityId, idsToOmit, page = 1, pageSize = 10, q } = ctx.request.query; + const sourceModel = component || model; + + const modelDef = strapi.getModel(sourceModel); if (!modelDef) { - return ctx.notFound('model.notFound'); + return ctx.badRequest("The model doesn't exist"); } const attribute = modelDef.attributes[targetField]; if (!attribute || attribute.type !== 'relation') { - return ctx.badRequest('targetField.invalid'); + return ctx.badRequest("This relational field doesn't exist"); } - const target = strapi.getModel(attribute.target); + const targetedModel = strapi.getModel(attribute.target); - if (!target) { - return ctx.notFound('target.notFound'); - } + const offset = Math.max(page - 1, 0) * pageSize; + const limit = Number(pageSize); - if (idsToOmit && Array.isArray(idsToOmit)) { - query.filters = { - $and: [ - { - id: { - $notIn: idsToOmit, - }, - }, - ].concat(query.filters || []), - }; - } - - const entityManager = getService('entity-manager'); - - const entities = await entityManager.find(query, target.uid, []); - - if (!entities) { - return ctx.notFound(); - } - - const modelConfig = _component + const modelConfig = component ? await getService('components').findConfiguration(modelDef) : await getService('content-types').findConfiguration(modelDef); + const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id'; - const field = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id'; - const pickFields = [field, 'id', target.primaryKey, PUBLISHED_AT_ATTRIBUTE]; + const query = strapi.db.queryBuilder(targetedModel.uid); - ctx.body = entities.map(pick(pickFields)); + if (q) { + query.search(q); + } + + if (!isEmpty(idsToOmit)) { + query.where({ id: { $notIn: idsToOmit } }); + } + + if (entityId) { + const joinTable = strapi.db.metadata.get(sourceModel).attributes[targetField].joinTable; + const sourceColumn = component ? joinTable.joinColumn.name : joinTable.inverseJoinColumn.name; + const targetColumn = component ? joinTable.inverseJoinColumn.name : joinTable.joinColumn.name; + + // Select ids of targeted entities already having a relation with _entityId + const knexSubQuery = strapi.db + .queryBuilder(joinTable.name) + .select([targetColumn]) + .where({ [sourceColumn]: entityId }) + .getKnexQuery(); + + query.where({ id: { $notIn: knexSubQuery } }); + } + + const { count } = await query + .clone() + .count() + .first() + .execute(); + const entities = await query + .select([mainField, 'id', PUBLISHED_AT_ATTRIBUTE]) + .orderBy(mainField) + .offset(offset) + .limit(limit) + .execute(); + + ctx.body = { + results: entities, + pagination: { + page: Number(page), + pageSize: Number(pageSize), + total: count, + }, + }; }, }; diff --git a/packages/core/content-manager/server/controllers/validation/relations.js b/packages/core/content-manager/server/controllers/validation/relations.js new file mode 100644 index 0000000000..10926fadce --- /dev/null +++ b/packages/core/content-manager/server/controllers/validation/relations.js @@ -0,0 +1,27 @@ +'use strict'; + +const { yup, validateYupSchema } = require('@strapi/utils'); + +const validateFindNewSchema = yup + .object() + .shape({ + component: yup.string(), + entityId: yup.strapiID(), + q: yup.string(), + omitIds: yup.array().of(yup.strapiID()), + page: yup + .number() + .integer() + .min(1), + pageSize: yup + .number() + .integer() + .min(1) + .max(100), + }) + .noUnknown() + .required(); + +module.exports = { + validateFindNew: validateYupSchema(validateFindNewSchema, { strict: false }), +}; diff --git a/packages/core/content-manager/server/routes/admin.js b/packages/core/content-manager/server/routes/admin.js index 26f1b3aa0e..5d891761b4 100644 --- a/packages/core/content-manager/server/routes/admin.js +++ b/packages/core/content-manager/server/routes/admin.js @@ -80,9 +80,9 @@ module.exports = { }, }, { - method: 'POST', + method: 'GET', path: '/relations/:model/:targetField', - handler: 'relations.find', + handler: 'relations.findNew', config: { policies: [ 'admin::isAuthenticatedAdmin', diff --git a/packages/core/database/lib/query/query-builder.js b/packages/core/database/lib/query/query-builder.js index c7bb9e13fd..40e2be9f64 100644 --- a/packages/core/database/lib/query/query-builder.js +++ b/packages/core/database/lib/query/query-builder.js @@ -4,36 +4,43 @@ const _ = require('lodash/fp'); const helpers = require('./helpers'); -const createQueryBuilder = (uid, db) => { +const createQueryBuilder = (uid, db, initialState = {}) => { const meta = db.metadata.get(uid); const { tableName } = meta; - const state = { - type: 'select', - select: [], - count: null, - max: null, - first: false, - data: null, - where: [], - joins: [], - populate: null, - limit: null, - offset: null, - transaction: null, - forUpdate: false, - orderBy: [], - groupBy: [], - }; + const state = _.defaults( + { + type: 'select', + select: [], + count: null, + max: null, + first: false, + data: null, + where: [], + joins: [], + populate: null, + limit: null, + offset: null, + transaction: null, + forUpdate: false, + orderBy: [], + groupBy: [], + aliasCounter: 0, + }, + initialState + ); - let counter = 0; - const getAlias = () => `t${counter++}`; + const getAlias = () => `t${state.aliasCounter++}`; return { alias: getAlias(), getAlias, state, + clone() { + return createQueryBuilder(uid, db, state); + }, + select(args) { state.type = 'select'; state.select = _.uniq(_.castArray(args));