mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 01:47:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			222 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const { prop, isEmpty } = require('lodash/fp');
 | |
| const { hasDraftAndPublish } = require('@strapi/utils').contentTypes;
 | |
| const { isAnyToMany } = require('@strapi/utils').relations;
 | |
| const { PUBLISHED_AT_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants;
 | |
| 
 | |
| const { getService } = require('../utils');
 | |
| const { validateFindAvailable, validateFindExisting } = require('./validation/relations');
 | |
| 
 | |
| const addFiltersClause = (params, filtersClause) => {
 | |
|   params.filters = params.filters || {};
 | |
|   if (params.filters.$and) {
 | |
|     params.filters.$and.push(filtersClause);
 | |
|   } else {
 | |
|     params.filters.$and = [filtersClause];
 | |
|   }
 | |
| };
 | |
| 
 | |
| module.exports = {
 | |
|   async findAvailable(ctx) {
 | |
|     const { userAbility } = ctx.state;
 | |
|     const { model, targetField } = ctx.params;
 | |
| 
 | |
|     await validateFindAvailable(ctx.request.query);
 | |
| 
 | |
|     // 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
 | |
|     const { entityId, idsToOmit, idsToInclude, _q, ...query } = ctx.request.query;
 | |
| 
 | |
|     const modelSchema = strapi.getModel(model);
 | |
|     if (!modelSchema) {
 | |
|       return ctx.badRequest("The model doesn't exist");
 | |
|     }
 | |
| 
 | |
|     const attribute = modelSchema.attributes[targetField];
 | |
|     if (!attribute || attribute.type !== 'relation') {
 | |
|       return ctx.badRequest("This relational field doesn't exist");
 | |
|     }
 | |
| 
 | |
|     const isComponent = modelSchema.modelType === 'component';
 | |
| 
 | |
|     // RBAC checks when it's a content-type
 | |
|     // TODO: do RBAC check for components too
 | |
|     if (!isComponent) {
 | |
|       const permissionChecker = getService('permission-checker').create({
 | |
|         userAbility,
 | |
|         model,
 | |
|       });
 | |
| 
 | |
|       if (permissionChecker.cannot.read(null, targetField)) {
 | |
|         return ctx.forbidden();
 | |
|       }
 | |
| 
 | |
|       if (entityId) {
 | |
|         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();
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       // eslint-disable-next-line no-lonely-if
 | |
|       if (entityId) {
 | |
|         const entity = await strapi.entityService.findOne(model, entityId);
 | |
| 
 | |
|         if (!entity) {
 | |
|           return ctx.notFound();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const targetedModel = strapi.getModel(attribute.target);
 | |
| 
 | |
|     const modelConfig = isComponent
 | |
|       ? await getService('components').findConfiguration(modelSchema)
 | |
|       : await getService('content-types').findConfiguration(modelSchema);
 | |
|     const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id';
 | |
| 
 | |
|     const fieldsToSelect = ['id', mainField];
 | |
|     if (hasDraftAndPublish(targetedModel)) {
 | |
|       fieldsToSelect.push(PUBLISHED_AT_ATTRIBUTE);
 | |
|     }
 | |
| 
 | |
|     const queryParams = {
 | |
|       sort: mainField,
 | |
|       ...query,
 | |
|       fields: fieldsToSelect, // cannot select other fields as the user may not have the permissions
 | |
|       filters: {}, // cannot filter for RBAC reasons
 | |
|     };
 | |
| 
 | |
|     if (!isEmpty(idsToOmit)) {
 | |
|       addFiltersClause(queryParams, { id: { $notIn: idsToOmit } });
 | |
|     }
 | |
| 
 | |
|     // searching should be allowed only on mainField for permission reasons
 | |
|     if (_q) {
 | |
|       addFiltersClause(queryParams, { [mainField]: { $containsi: _q } });
 | |
|     }
 | |
| 
 | |
|     if (entityId) {
 | |
|       const subQuery = strapi.db.queryBuilder(modelSchema.uid);
 | |
| 
 | |
|       const alias = subQuery.getAlias();
 | |
| 
 | |
|       const where = {
 | |
|         id: entityId,
 | |
|         [`${alias}.id`]: { $notNull: true },
 | |
|       };
 | |
| 
 | |
|       if (!isEmpty(idsToInclude)) {
 | |
|         where[`${alias}.id`].$notIn = idsToInclude;
 | |
|       }
 | |
| 
 | |
|       const knexSubQuery = subQuery
 | |
|         .where(where)
 | |
|         .join({ alias, targetField })
 | |
|         .select(`${alias}.id`)
 | |
|         .getKnexQuery();
 | |
| 
 | |
|       addFiltersClause(queryParams, { id: { $notIn: knexSubQuery } });
 | |
|     }
 | |
| 
 | |
|     ctx.body = await strapi.entityService.findPage(targetedModel.uid, queryParams);
 | |
|   },
 | |
| 
 | |
|   async findExisting(ctx) {
 | |
|     const { userAbility } = ctx.state;
 | |
|     const { model, id, targetField } = ctx.params;
 | |
| 
 | |
|     await validateFindExisting(ctx.request.query);
 | |
| 
 | |
|     const modelSchema = strapi.getModel(model);
 | |
|     if (!modelSchema) {
 | |
|       return ctx.badRequest("The model doesn't exist");
 | |
|     }
 | |
| 
 | |
|     const attribute = modelSchema.attributes[targetField];
 | |
|     if (!attribute || attribute.type !== 'relation') {
 | |
|       return ctx.badRequest("This relational field doesn't exist");
 | |
|     }
 | |
| 
 | |
|     const isComponent = modelSchema.modelType === 'component';
 | |
| 
 | |
|     // RBAC checks when it's a content-type
 | |
|     // TODO: do RBAC check for components too
 | |
|     if (!isComponent) {
 | |
|       const entityManager = getService('entity-manager');
 | |
|       const permissionChecker = getService('permission-checker').create({
 | |
|         userAbility,
 | |
|         model,
 | |
|       });
 | |
| 
 | |
|       if (permissionChecker.cannot.read(null, targetField)) {
 | |
|         return ctx.forbidden();
 | |
|       }
 | |
| 
 | |
|       const entity = await entityManager.findOneWithCreatorRoles(id, model);
 | |
| 
 | |
|       if (!entity) {
 | |
|         return ctx.notFound();
 | |
|       }
 | |
| 
 | |
|       if (permissionChecker.cannot.read(entity, targetField)) {
 | |
|         return ctx.forbidden();
 | |
|       }
 | |
|     } else {
 | |
|       const entity = await strapi.entityService.findOne(model, id);
 | |
| 
 | |
|       if (!entity) {
 | |
|         return ctx.notFound();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const targetedModel = strapi.getModel(attribute.target);
 | |
| 
 | |
|     const modelConfig = isComponent
 | |
|       ? await getService('components').findConfiguration(modelSchema)
 | |
|       : await getService('content-types').findConfiguration(modelSchema);
 | |
| 
 | |
|     const mainField = prop(`metadatas.${targetField}.edit.mainField`, modelConfig) || 'id';
 | |
| 
 | |
|     const fieldsToSelect = ['id', mainField];
 | |
|     if (hasDraftAndPublish(targetedModel)) {
 | |
|       fieldsToSelect.push(PUBLISHED_AT_ATTRIBUTE);
 | |
|     }
 | |
| 
 | |
|     const queryParams = {
 | |
|       fields: fieldsToSelect,
 | |
|     };
 | |
| 
 | |
|     if (isAnyToMany(attribute)) {
 | |
|       const res = await strapi.entityService.loadPages(
 | |
|         model,
 | |
|         { id },
 | |
|         targetField,
 | |
|         {
 | |
|           ...queryParams,
 | |
|           ordering: 'desc',
 | |
|         },
 | |
|         {
 | |
|           page: ctx.request.query.page,
 | |
|           pageSize: ctx.request.query.pageSize,
 | |
|         }
 | |
|       );
 | |
| 
 | |
|       ctx.body = res;
 | |
|     } else {
 | |
|       const result = await strapi.entityService.load(model, { id }, targetField, queryParams);
 | |
|       ctx.body = {
 | |
|         data: result,
 | |
|       };
 | |
|     }
 | |
|   },
 | |
| };
 | 
