From e273f7f76d4d96d31ee04ae5cc38c8f81644f479 Mon Sep 17 00:00:00 2001 From: Marc-Roig Date: Tue, 20 Dec 2022 10:17:38 +0100 Subject: [PATCH] look for duplicated inverseBy relationships --- packages/core/database/lib/index.js | 84 ++++++++++++++++++- .../core/database/lib/metadata/relations.js | 5 +- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/core/database/lib/index.js b/packages/core/database/lib/index.js index 3e003fcbf7..68859df4c8 100644 --- a/packages/core/database/lib/index.js +++ b/packages/core/database/lib/index.js @@ -11,6 +11,8 @@ const errors = require('./errors'); // TODO: move back into strapi const { transformContentTypes } = require('./utils/content-types'); +const { getJoinTableName } = require('./metadata/relations'); +const types = require('./types'); class Database { constructor(config) { @@ -72,9 +74,89 @@ class Database { } } +// TODO: Do the same for repeated mappedBy relations +const getLinks = ({ db }) => { + const relationsToUpdate = {}; + + db.metadata.forEach((contentType) => { + const attributes = contentType.attributes; + + // For each relation type, add the joinTable name to tablesToUpdate + Object.values(attributes).forEach((attribute) => { + if (!types.isRelation(attribute.type)) return; + + if (attribute.inversedBy) { + const invRelation = db.metadata.get(attribute.target).attributes[attribute.inversedBy]; + + // Both relations use inversedBy + if (invRelation?.inversedBy) { + relationsToUpdate[attribute.joinTable.name] = { + relation: attribute, + invRelation, + }; + } + } + }); + }); + + return Object.values(relationsToUpdate); +}; + +// const isLinkTableEmpty = async (db, linkTableName) => { +// const result = await db.getConnection().count('* as count').from(linkTableName); +// return result.count === 0; +// }; + // TODO: move into strapi Database.transformContentTypes = transformContentTypes; -Database.init = async (config) => new Database(config); +Database.init = async (config) => { + const db = new Database(config); + + // TODO: Create validations folder for this. + const links = getLinks({ db }); + + const errorList = []; + + for (const { relation, invRelation } of links) { + // Generate the join table name based on the relation target + // table and attribute name. + const joinTableName = getJoinTableName( + db.metadata.get(relation.target).tableName, + relation.inversedBy + ); + + const contentType = db.metadata.get(invRelation.target); + const invContentType = db.metadata.get(relation.target); + + // If both sides use inversedBy + // TODO: Same for mappedBy + if (relation.inversedBy && invRelation.inversedBy) { + // If the generated join table name is the same as the one assigned in relation.joinTable, + // relation is on the inversed side of the bidirectional relation. + // and the other is on the owner side. + if (joinTableName === relation.joinTable.name) { + errorList.push( + `Error on attribute "${invRelation.inversedBy}" in model "${invContentType.tableName}"(${invContentType.uid}):` + + ` One of the sides of the relationship must be the owning side. You should use mappedBy` + + ` instead of inversedBy in the relation "${invRelation.inversedBy}".` + ); + } else { + errorList.push( + `Error on attribute "${relation.inversedBy}" in model "${contentType.tableName}"(${contentType.uid}):` + + ` One of the sides of the relationship must be the owning side. You should use mappedBy` + + ` instead of inversedBy in the relation "${relation.inversedBy}".` + ); + } + } + } + + if (errorList.length > 0) { + errorList.forEach((error) => strapi.log.error(error)); + throw new Error('There are errors in some of your models. Please check the logs above.'); + } + + return db; +}; module.exports = { Database, diff --git a/packages/core/database/lib/metadata/relations.js b/packages/core/database/lib/metadata/relations.js index 5cdb2374b6..c3c051785f 100644 --- a/packages/core/database/lib/metadata/relations.js +++ b/packages/core/database/lib/metadata/relations.js @@ -16,6 +16,8 @@ const isAnyToMany = (attribute) => ['oneToMany', 'manyToMany'].includes(attribut const isBidirectional = (attribute) => hasInversedBy(attribute) || hasMappedBy(attribute); const isOwner = (attribute) => !isBidirectional(attribute) || hasInversedBy(attribute); const shouldUseJoinTable = (attribute) => attribute.useJoinTable !== false; +const getJoinTableName = (tableName, attributeName) => + _.snakeCase(`${tableName}_${attributeName}_links`); /** * Creates a oneToOne relation metadata @@ -397,7 +399,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => { throw new Error(`Unknown target ${attribute.target}`); } - const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_links`); + const joinTableName = getJoinTableName(meta.tableName, attributeName); const joinColumnName = _.snakeCase(`${meta.singularName}_id`); let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`); @@ -560,4 +562,5 @@ module.exports = { isAnyToMany, hasOrderColumn, hasInverseOrderColumn, + getJoinTableName, };