'use strict'; const knex = require('./knex'); const slugify = require('./utils/slugify'); async function matchReleaseTags(releases) { const rawTags = releases .map(release => release.tags).flat() .filter(Boolean); const casedTags = [...new Set( rawTags .concat(rawTags.map(tag => tag.toLowerCase())) .concat(rawTags.map(tag => tag.toUpperCase())), )]; const tagEntries = await knex('tags') .select('tags.id', 'tags.name', 'tags.alias_for') .whereIn('tags.name', casedTags); const tagIdsBySlug = tagEntries .reduce((acc, tag) => ({ ...acc, [slugify(tag.name)]: tag.alias_for || tag.id, }), {}); return tagIdsBySlug; } async function getSiteTags(releases) { const siteIds = releases.map(release => release.site.id); const siteTags = await knex('sites_tags').whereIn('site_id', siteIds); const siteTagIdsBySiteId = siteTags.reduce((acc, siteTag) => { if (!acc[siteTag.site_id]) { acc[siteTag.site_id] = []; } acc[siteTag.site_id].push(siteTag.tag_id); return acc; }, {}); return siteTagIdsBySiteId; } function buildReleaseTagAssociations(releases, tagIdsBySlug, siteTagIdsBySiteId) { const tagAssociations = releases .map((release) => { const siteTagIds = siteTagIdsBySiteId[release.site.id]; const releaseTags = release.tags || []; const releaseTagIds = releaseTags.every(tag => typeof tag === 'number') ? releaseTags // obsolete scraper returned pre-matched tags : releaseTags.map(tag => tagIdsBySlug[slugify(tag)]); const tags = [...new Set( // filter duplicates and empties releaseTagIds .concat(siteTagIds) .filter(Boolean), )] .map(tagId => ({ release_id: release.id, tag_id: tagId, })); return tags; }) .flat(); return tagAssociations; } async function filterUniqueAssociations(tagAssociations) { const duplicateAssociations = await knex('releases_tags') .whereIn(['release_id', 'tag_id'], tagAssociations.map(association => [association.release_id, association.tag_id])); const duplicateAssociationsByReleaseIdAndTagId = duplicateAssociations.reduce((acc, association) => { if (!acc[association.release_id]) { acc[association.release_id] = {}; } acc[association.release_id][association.tag_id] = true; return acc; }, {}); const uniqueAssociations = tagAssociations .filter(association => !duplicateAssociationsByReleaseIdAndTagId[association.release_id]?.[association.tag_id]); return uniqueAssociations; } async function associateReleaseTags(releases) { const tagIdsBySlug = await matchReleaseTags(releases); const siteTagIdsBySiteId = await getSiteTags(releases); const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, siteTagIdsBySiteId); const uniqueAssociations = await filterUniqueAssociations(tagAssociations); await knex('releases_tags').insert(uniqueAssociations); } module.exports = { associateReleaseTags, };