{"version":3,"file":"clean-component-join-table.mjs","sources":["../../../../src/services/document-service/utils/clean-component-join-table.ts"],"sourcesContent":["import type { Database } from '@strapi/database';\nimport type { Schema } from '@strapi/types';\nimport { findComponentParent, getParentSchemasForComponent } from '../components';\n\n/**\n * Cleans ghost relations with publication state mismatches from a join table\n * Uses schema-based draft/publish checking like prevention fix\n */\nexport const cleanComponentJoinTable = async (\n  db: Database,\n  joinTableName: string,\n  relation: any,\n  sourceModel: any\n): Promise<number> => {\n  try {\n    // Get the target model metadata\n    const targetModel = db.metadata.get(relation.target);\n    if (!targetModel) {\n      db.logger.debug(`Target model ${relation.target} not found, skipping ${joinTableName}`);\n      return 0;\n    }\n\n    // Check if source supports draft/publish, if it doesnt it should contain duplicate states\n    const sourceContentType = strapi.contentTypes[sourceModel.uid];\n    // It could be a model, which does not have the draftAndPublish option\n    const sourceSupportsDraftPublish = sourceContentType?.options?.draftAndPublish;\n\n    if (sourceContentType && !sourceSupportsDraftPublish) {\n      return 0;\n    }\n\n    // Check if target supports draft/publish using schema-based approach (like prevention fix)\n    const targetContentType =\n      strapi.contentTypes[relation.target as keyof typeof strapi.contentTypes];\n    const targetSupportsDraftPublish = targetContentType?.options?.draftAndPublish || false;\n\n    if (!targetSupportsDraftPublish) {\n      return 0;\n    }\n\n    // Find entries with publication state mismatches\n    const ghostEntries = await findPublicationStateMismatches(\n      db,\n      joinTableName,\n      relation,\n      targetModel,\n      sourceModel\n    );\n\n    if (ghostEntries.length === 0) {\n      return 0;\n    }\n\n    // Remove ghost entries\n    await db.connection(joinTableName).whereIn('id', ghostEntries).del();\n    db.logger.debug(\n      `Removed ${ghostEntries.length} ghost relations with publication state mismatches from ${joinTableName}`\n    );\n\n    return ghostEntries.length;\n  } catch (error) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    db.logger.error(`Failed to clean join table \"${joinTableName}\": ${errorMessage}`);\n    return 0;\n  }\n};\n\nconst findContentTypeParentForComponentInstance = async (\n  componentSchema: Schema.Component,\n  componentId: number | string\n): Promise<{ uid: string; table: string; parentId: number | string } | null> => {\n  // Get the parent schemas that could contain this component\n  const parentSchemas = getParentSchemasForComponent(componentSchema);\n  if (parentSchemas.length === 0) {\n    // No potential parents\n    return null;\n  }\n\n  // Find the actual parent for THIS specific component instance\n  const parent = await findComponentParent(componentSchema, componentId, parentSchemas);\n  if (!parent) {\n    // No parent found for this component instance\n    return null;\n  }\n\n  if (strapi.components[parent.uid as keyof typeof strapi.components]) {\n    // If the parent is a component, we need to check its parents recursively\n    const parentComponentSchema = strapi.components[parent.uid as keyof typeof strapi.components];\n    return findContentTypeParentForComponentInstance(parentComponentSchema, parent.parentId);\n  }\n\n  if (strapi.contentTypes[parent.uid as keyof typeof strapi.contentTypes]) {\n    // Found a content type parent\n    return parent;\n  }\n\n  return null;\n};\n\n/**\n * Finds join table entries with publication state mismatches\n * Uses existing component parent detection from document service\n */\nconst findPublicationStateMismatches = async (\n  db: Database,\n  joinTableName: string,\n  relation: any,\n  targetModel: any,\n  sourceModel: any\n): Promise<number[]> => {\n  try {\n    // Get join column names using proper functions (addressing PR feedback)\n    const sourceColumn = relation.joinTable.joinColumn.name;\n    const targetColumn = relation.joinTable.inverseJoinColumn.name;\n\n    // Get all join entries with their target entities\n    const query = db\n      .connection(joinTableName)\n      .select(\n        `${joinTableName}.id as join_id`,\n        `${joinTableName}.${sourceColumn} as source_id`,\n        `${joinTableName}.${targetColumn} as target_id`,\n        `${targetModel.tableName}.published_at as target_published_at`\n      )\n      .leftJoin(\n        targetModel.tableName,\n        `${joinTableName}.${targetColumn}`,\n        `${targetModel.tableName}.id`\n      );\n\n    const joinEntries = await query;\n\n    // Group by source_id to find duplicates pointing to draft/published versions of same entity\n    const entriesBySource: { [key: string]: any[] } = {};\n    for (const entry of joinEntries) {\n      const sourceId = entry.source_id;\n      if (!entriesBySource[sourceId]) {\n        entriesBySource[sourceId] = [];\n      }\n      entriesBySource[sourceId].push(entry);\n    }\n\n    const ghostEntries: number[] = [];\n\n    // Check if this is a join table (ends with _lnk)\n    const isRelationJoinTable = joinTableName.endsWith('_lnk');\n    const isComponentModel =\n      !sourceModel.uid?.startsWith('api::') &&\n      !sourceModel.uid?.startsWith('plugin::') &&\n      sourceModel.uid?.includes('.');\n\n    // Check for draft/publish inconsistencies\n    for (const [sourceId, entries] of Object.entries(entriesBySource)) {\n      // Skip entries with single relations\n      if (entries.length <= 1) {\n        // eslint-disable-next-line no-continue\n        continue;\n      }\n\n      // For component join tables, check if THIS specific component instance's parent supports D&P\n      if (isRelationJoinTable && isComponentModel) {\n        try {\n          const componentSchema = strapi.components[sourceModel.uid] as Schema.Component;\n          if (!componentSchema) {\n            // eslint-disable-next-line no-continue\n            continue;\n          }\n\n          const parent = await findContentTypeParentForComponentInstance(componentSchema, sourceId);\n          if (!parent) {\n            continue;\n          }\n\n          // Check if THIS component instance's parent supports draft/publish\n          const parentContentType =\n            strapi.contentTypes[parent.uid as keyof typeof strapi.contentTypes];\n          if (!parentContentType?.options?.draftAndPublish) {\n            // This component instance's parent does NOT support D&P - skip cleanup\n            // eslint-disable-next-line no-continue\n            continue;\n          }\n\n          // If we reach here, this component instance's parent DOES support D&P\n          // Continue to process this component instance for ghost relations\n        } catch (error) {\n          // Skip this component instance on error\n          // eslint-disable-next-line no-continue\n          continue;\n        }\n      }\n\n      // Find ghost relations (same logic as original but with improved parent checking)\n      for (const entry of entries) {\n        if (entry.target_published_at === null) {\n          // This is a draft target - find its published version\n          const draftTarget = await db\n            .connection(targetModel.tableName)\n            .select('document_id')\n            .where('id', entry.target_id)\n            .first();\n\n          if (draftTarget) {\n            const publishedVersion = await db\n              .connection(targetModel.tableName)\n              .select('id', 'document_id')\n              .where('document_id', draftTarget.document_id)\n              .whereNotNull('published_at')\n              .first();\n\n            if (publishedVersion) {\n              // Check if we also have a relation to the published version\n              const publishedRelation = entries.find((e) => e.target_id === publishedVersion.id);\n              if (publishedRelation) {\n                ghostEntries.push(publishedRelation.join_id);\n              }\n            }\n          }\n        }\n      }\n    }\n\n    return ghostEntries;\n  } catch (error) {\n    return [];\n  }\n};\n"],"names":["cleanComponentJoinTable","db","joinTableName","relation","sourceModel","targetModel","metadata","get","target","logger","debug","sourceContentType","strapi","contentTypes","uid","sourceSupportsDraftPublish","options","draftAndPublish","targetContentType","targetSupportsDraftPublish","ghostEntries","findPublicationStateMismatches","length","connection","whereIn","del","error","errorMessage","Error","message","String","findContentTypeParentForComponentInstance","componentSchema","componentId","parentSchemas","getParentSchemasForComponent","parent","findComponentParent","components","parentComponentSchema","parentId","sourceColumn","joinTable","joinColumn","name","targetColumn","inverseJoinColumn","query","select","tableName","leftJoin","joinEntries","entriesBySource","entry","sourceId","source_id","push","isRelationJoinTable","endsWith","isComponentModel","startsWith","includes","entries","Object","parentContentType","target_published_at","draftTarget","where","target_id","first","publishedVersion","document_id","whereNotNull","publishedRelation","find","e","id","join_id"],"mappings":"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"}