HotTRDealsBackend/services/tag.service.js
2026-02-04 06:39:10 +00:00

192 lines
5.5 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 } } },
})
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,
}