{"version":3,"file":"unidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[entry.id] = newEntry.id;\n return acc;\n },\n {} as Record\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n // Insert those relations into the join table\n await trx.batchInsert(joinTable.name, newRelations, 1000);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","column","newRelations","newId","batchInsert"],"mappings":"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"}