213 lines
6.2 KiB
JavaScript
213 lines
6.2 KiB
JavaScript
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,
|
|
}
|