const prisma = require("../db/client") const { slugify } = require("../utils/slugify") function normalizeTags(tags = []) { const arr = Array.isArray(tags) ? tags : [] const map = new Map() arr.forEach((raw) => { let name = "" if (typeof raw === "string") { name = raw } else if (raw && typeof raw === "object") { if (typeof raw.name === "string") name = raw.name else if (typeof raw.title === "string") name = raw.title else if (typeof raw.slug === "string") name = raw.slug else if (typeof raw.label === "string") name = raw.label else if (typeof raw.text === "string") name = raw.text } const trimmed = String(name || "").trim() if (!trimmed) return const slug = slugify(trimmed) if (!slug) return if (!map.has(slug)) { map.set(slug, { slug, name: trimmed }) } }) return Array.from(map.values()).slice(0, 5) } async function attachTagsToDeal(dealId, tags = []) { const id = Number(dealId) if (!Number.isInteger(id) || id <= 0) throw new Error("INVALID_DEAL_ID") const normalized = normalizeTags(tags) if (!normalized.length) return { tags: [], created: 0 } return prisma.$transaction(async (tx) => { const upserted = await Promise.all( normalized.map((t) => tx.tag.upsert({ where: { slug: t.slug }, update: {}, create: { slug: t.slug, name: t.name }, }) ) ) const tagIds = upserted.map((t) => t.id) const existing = await tx.dealTag.findMany({ where: { dealId: id, tagId: { in: tagIds } }, select: { tagId: true }, }) const existingIds = new Set(existing.map((e) => e.tagId)) const toCreate = upserted.filter((t) => !existingIds.has(t.id)) if (toCreate.length) { await tx.dealTag.createMany({ data: toCreate.map((t) => ({ dealId: id, tagId: t.id })), skipDuplicates: true, }) await tx.tag.updateMany({ where: { id: { in: toCreate.map((t) => t.id) } }, data: { usageCount: { increment: 1 } }, }) } const tagsForDeal = await tx.dealTag.findMany({ where: { dealId: id }, select: { tag: { select: { id: true, slug: true, name: true } } }, }) return { tags: tagsForDeal.map((entry) => entry.tag), created: toCreate.length, } }) } async function removeTagsFromDeal(dealId, tags = []) { const id = Number(dealId) if (!Number.isInteger(id) || id <= 0) throw new Error("INVALID_DEAL_ID") const normalized = normalizeTags(tags) if (!normalized.length) return { tags: [], removed: 0 } const slugs = normalized.map((t) => t.slug) return prisma.$transaction(async (tx) => { const tagRows = await tx.tag.findMany({ where: { slug: { in: slugs } }, select: { id: true }, }) if (!tagRows.length) return { tags: [], removed: 0 } const tagIds = tagRows.map((t) => t.id) const existing = await tx.dealTag.findMany({ where: { dealId: id, tagId: { in: tagIds } }, select: { tagId: true }, }) const existingIds = new Set(existing.map((e) => e.tagId)) const toRemove = tagIds.filter((tagId) => existingIds.has(tagId)) if (toRemove.length) { await tx.dealTag.deleteMany({ where: { dealId: id, tagId: { in: toRemove } } }) await tx.tag.updateMany({ where: { id: { in: toRemove }, usageCount: { gt: 0 } }, data: { usageCount: { decrement: 1 } }, }) } const tagsForDeal = await tx.dealTag.findMany({ where: { dealId: id }, select: { tag: { select: { id: true, slug: true, name: true } } }, }) const remainingSlugs = new Set( tagsForDeal.map((entry) => entry?.tag?.slug).filter(Boolean) ) if (slugs.length) { const aiReview = await tx.dealAiReview.findUnique({ where: { dealId: id }, select: { tags: true }, }) if (aiReview && Array.isArray(aiReview.tags) && aiReview.tags.length) { const filtered = aiReview.tags.filter( (tag) => !slugs.includes(String(tag)) || remainingSlugs.has(String(tag)) ) if (filtered.length !== aiReview.tags.length) { await tx.dealAiReview.update({ where: { dealId: id }, data: { tags: filtered }, }) } } } return { tags: tagsForDeal.map((entry) => entry.tag), removed: toRemove.length, } }) } async function replaceTagsForDeal(dealId, tags = []) { const id = Number(dealId) if (!Number.isInteger(id) || id <= 0) throw new Error("INVALID_DEAL_ID") const normalized = normalizeTags(tags) return prisma.$transaction(async (tx) => { const existing = await tx.dealTag.findMany({ where: { dealId: id }, select: { tagId: true }, }) const existingIds = new Set(existing.map((e) => e.tagId)) let desiredIds = [] if (normalized.length) { const upserted = await Promise.all( normalized.map((t) => tx.tag.upsert({ where: { slug: t.slug }, update: {}, create: { slug: t.slug, name: t.name }, }) ) ) desiredIds = upserted.map((t) => t.id) } const desiredSet = new Set(desiredIds) const toRemove = Array.from(existingIds).filter((tagId) => !desiredSet.has(tagId)) const toAdd = desiredIds.filter((tagId) => !existingIds.has(tagId)) if (toRemove.length) { await tx.dealTag.deleteMany({ where: { dealId: id, tagId: { in: toRemove } } }) await tx.tag.updateMany({ where: { id: { in: toRemove }, usageCount: { gt: 0 } }, data: { usageCount: { decrement: 1 } }, }) } if (toAdd.length) { await tx.dealTag.createMany({ data: toAdd.map((tagId) => ({ dealId: id, tagId })), skipDuplicates: true, }) await tx.tag.updateMany({ where: { id: { in: toAdd } }, data: { usageCount: { increment: 1 } }, }) } const tagsForDeal = await tx.dealTag.findMany({ where: { dealId: id }, select: { tag: { select: { id: true, slug: true, name: true } } }, }) return { tags: tagsForDeal.map((entry) => entry.tag), added: toAdd.length, removed: toRemove.length, } }) } module.exports = { attachTagsToDeal, normalizeTags, removeTagsFromDeal, replaceTagsForDeal, }