HotTRDealsBackend/prisma/seed.js
2026-02-09 21:47:55 +00:00

491 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// prisma/seed.js
const { PrismaClient, DealStatus, SaleType, AffiliateType } = require("@prisma/client")
const fs = require("fs")
const path = require("path")
const prisma = new PrismaClient()
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
function pickRandomCategoryId(categoryIds = [], fallbackCategoryId = 0) {
if (Array.isArray(categoryIds) && categoryIds.length) {
return categoryIds[randInt(0, categoryIds.length - 1)]
}
return fallbackCategoryId
}
// Stabil gerçek foto linkleri (redirect yok, hotlink derdi az)
function realImage(seed, w = 1200, h = 900) {
return `https://picsum.photos/seed/${encodeURIComponent(seed)}/${w}/${h}`
}
// Son N gün içinde random tarih
function randomDateWithinLastDays(days = 5) {
const now = Date.now()
const maxBack = days * 24 * 60 * 60 * 1000
const offset = randInt(0, maxBack)
return new Date(now - offset)
}
function normalizeSlug(s) {
return String(s ?? "").trim().toLowerCase()
}
function toNumberOrNull(v) {
if (v === null || v === undefined || v === "") return null
const n = Number(v)
return Number.isFinite(n) ? n : null
}
function toDateOrNull(v) {
if (v === null || v === undefined || v === "") return null
const d = new Date(v)
return Number.isNaN(d.getTime()) ? null : d
}
async function upsertTagBySlug(slug, name) {
const s = normalizeSlug(slug)
return prisma.tag.upsert({
where: { slug: s },
update: { name },
create: { slug: s, name },
})
}
async function attachTagsToDeal(dealId, tagSlugs) {
const unique = [...new Set((tagSlugs ?? []).map(normalizeSlug).filter(Boolean))]
if (!unique.length) return
const tags = await prisma.$transaction(
unique.map((slug) =>
prisma.tag.upsert({
where: { slug },
update: {},
create: { slug, name: slug },
})
)
)
await prisma.dealTag.createMany({
data: tags.map((t) => ({ dealId, tagId: t.id })),
skipDuplicates: true,
})
}
function loadCategoriesJson(filePath) {
const raw = fs.readFileSync(filePath, "utf-8")
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) throw new Error("categories.json array olmalı")
const cats = arr.map((c) => ({
id: Number(c.id),
name: String(c.name ?? "").trim(),
slug: normalizeSlug(c.slug),
description: c.description,
parentId: c.parentId === null || c.parentId === undefined ? null : Number(c.parentId),
}))
for (const c of cats) {
if (!Number.isInteger(c.id)) throw new Error(`Category id invalid: ${c.id}`)
if (!c.name) throw new Error(`Category name boş olamaz (id=${c.id})`)
if (!c.slug) throw new Error(`Category slug boş olamaz (id=${c.id})`)
}
const has0 = cats.some((c) => c.id === 0)
if (!has0) {
cats.unshift({ id: 0, name: "Undefined", slug: "undefined", parentId: null })
}
const idSet = new Set()
const slugSet = new Set()
for (const c of cats) {
if (idSet.has(c.id)) throw new Error(`categories.json duplicate id: ${c.id}`)
idSet.add(c.id)
if (slugSet.has(c.slug)) throw new Error(`categories.json duplicate slug: ${c.slug}`)
slugSet.add(c.slug)
}
return cats
}
async function seedCategoriesFromJson(categoriesFilePath) {
const categories = loadCategoriesJson(categoriesFilePath)
await prisma.$transaction(
categories.map((c) =>
prisma.category.upsert({
where: { id: c.id },
update: {
name: c.name,
slug: c.slug,
description: c.description,
},
create: {
id: c.id,
name: c.name,
slug: c.slug,
description: c.description,
parentId: null,
},
})
),
{ timeout: 60_000 }
)
await prisma.$transaction(
categories.map((c) =>
prisma.category.update({
where: { id: c.id },
data: { parentId: c.parentId },
})
),
{ timeout: 60_000 }
)
await prisma.$executeRawUnsafe(`
SELECT setval(
pg_get_serial_sequence('"Category"', 'id'),
COALESCE((SELECT MAX(id) FROM "Category"), 0) + 1,
false
);
`)
return { count: categories.length }
}
function loadSellersJson(filePath) {
const raw = fs.readFileSync(filePath, "utf-8")
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) throw new Error("sellers.json array olmalı")
const sellers = arr.map((s) => ({
name: String(s.name ?? "").trim(),
url: String(s.url ?? "").trim(),
sellerLogo: String(s.sellerLogo ?? "").trim(),
isActive: s.isActive === undefined ? true : Boolean(s.isActive),
createdAt: toDateOrNull(s.createdAt),
createdById: toNumberOrNull(s.createdById),
}))
for (const s of sellers) {
if (!s.name) throw new Error("Seller name boÅŸ olamaz")
}
return sellers
}
async function seedSellersFromJson(filePath, fallbackCreatedById) {
const sellers = loadSellersJson(filePath)
let count = 0
for (const s of sellers) {
const createdById = s.createdById ?? fallbackCreatedById
if (!createdById) throw new Error(`Seller createdById eksik: ${s.name}`)
const createData = {
name: s.name,
url: s.url,
sellerLogo: s.sellerLogo,
isActive: s.isActive,
createdById,
}
if (s.createdAt) createData.createdAt = s.createdAt
await prisma.seller.upsert({
where: { name: s.name },
update: { url: s.url, sellerLogo: s.sellerLogo, isActive: s.isActive },
create: createData,
})
count++
}
return { count }
}
function loadDealsJson(filePath) {
const raw = fs.readFileSync(filePath, "utf-8")
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) throw new Error("deals.json array olmalı")
const items = arr.map((d, idx) => {
const title = String(d.title ?? "").trim()
const url = String(d.url ?? "").trim()
const q = String(d.q ?? "").trim()
const price = toNumberOrNull(d.price)
const originalPrice = toNumberOrNull(d.originalPrice)
const shippingPrice = toNumberOrNull(d.shippingPrice)
if (!title) throw new Error(`deals.json: title boş (index=${idx})`)
if (!url) throw new Error(`deals.json: url boş (index=${idx})`)
if (price === null) throw new Error(`deals.json: price invalid (index=${idx})`)
// Mantık: originalPrice varsa price'dan küçük olamaz
if (originalPrice !== null && originalPrice < price) {
throw new Error(`deals.json: originalPrice < price (index=${idx})`)
}
return {
title,
url,
q,
price,
originalPrice,
shippingPrice,
}
})
// url unique olsun (seed idempotent)
const urlSet = new Set()
for (const it of items) {
if (urlSet.has(it.url)) throw new Error(`deals.json duplicate url: ${it.url}`)
urlSet.add(it.url)
}
return items
}
// deals.jsondan seed + her deala 3 foto + score 0-200 + tarih dağılımı:
// - %70: son 5 gün
// - %30: 9-11 gün önce
async function seedDealsFromJson({ userId, sellerId, categoryIds = [], defaultCategoryId = 0, dealsFilePath }) {
const baseItems = loadDealsJson(dealsFilePath)
// 30 adet olacak şekilde çoğalt (title/url benzersizleşsin)
const items = []
for (let i = 0; i < 1000; i++) {
const base = baseItems[i % baseItems.length]
const n = i + 1
// price'ı hafif oynat (base price üzerinden)
const price = Number((base.price * (0.9 + randInt(0, 30) / 100)).toFixed(2))
// originalPrice varsa, yeni price'a göre ölçekle (mantık korunur)
let originalPrice = null
if (base.originalPrice !== null && base.originalPrice !== undefined) {
const ratio = base.originalPrice / base.price // >= 1 olmalı
originalPrice = Number((price * ratio).toFixed(2))
if (originalPrice < price) originalPrice = Number((price * 1.05).toFixed(2))
}
// shippingPrice varsa bazen aynen, bazen 0/ufak varyasyon (ama null değilse)
let shippingPrice = null
if (base.shippingPrice !== null && base.shippingPrice !== undefined) {
// 70% aynı, 30% küçük oynat
if (Math.random() < 0.7) {
shippingPrice = Number(base.shippingPrice)
} else {
const candidates = [0, 19.9, 29.9, 39.9, 49.9, 59.9]
shippingPrice = candidates[randInt(0, candidates.length - 1)]
}
}
items.push({
title: `${base.title} #${n}`,
price,
originalPrice,
shippingPrice,
url: `${base.url}${base.url.includes("?") ? "&" : "?"}seed=${n}`,
q: base.q || "product",
})
}
for (let i = 0; i < items.length; i++) {
const it = items[i]
const older = Math.random() < 0.3
const createdAt = older
? new Date(Date.now() - randInt(9, 11) * 24 * 60 * 60 * 1000 - randInt(0, 12) * 60 * 60 * 1000)
: randomDateWithinLastDays(5)
const dealData = {
title: it.title,
description: "Seed test deal açıklaması (otomatik üretim).",
url: it.url,
price: it.price,
originalPrice: it.originalPrice ?? null,
shippingPrice: it.shippingPrice ?? null,
status: DealStatus.ACTIVE,
saletype: SaleType.ONLINE,
affiliateType: AffiliateType.NON_AFFILIATE,
commentCount: randInt(0, 25),
userId,
sellerId,
categoryId: pickRandomCategoryId(categoryIds, defaultCategoryId),
score: randInt(0, 200),
createdAt,
}
const existing = await prisma.deal.findFirst({
where: { url: it.url },
select: { id: true },
})
const deal = existing
? await prisma.deal.update({ where: { id: existing.id }, data: dealData })
: await prisma.deal.create({ data: dealData })
await prisma.dealImage.deleteMany({ where: { dealId: deal.id } })
await prisma.dealImage.createMany({
data: [
{ dealId: deal.id, imageUrl: realImage(`${it.q}-${i}-1`), order: 0 },
{ dealId: deal.id, imageUrl: realImage(`${it.q}-${i}-2`), order: 1 },
{ dealId: deal.id, imageUrl: realImage(`${it.q}-${i}-3`), order: 2 },
],
})
}
}
async function main() {
const hashedPassword = "$2b$10$PVfLq2NmcGmKbhE5VK3yNeVj46O/1w2p/2BNu4h1CYacqSgkCcoCW"
// ---------- USERS ----------
const admin = await prisma.user.upsert({
where: { email: "test" },
update: {},
create: {
username: "test",
email: "test",
passwordHash: hashedPassword,
role: "ADMIN",
},
})
const user = await prisma.user.upsert({
where: { email: "test2" },
update: {},
create: {
username: "test2",
email: "test2",
passwordHash: hashedPassword,
role: "USER",
},
})
// ---------- SELLERS (FROM JSON) ----------
const sellersFilePath = path.join(__dirname, "sellers.json")
await seedSellersFromJson(sellersFilePath, admin.id)
const amazon = await prisma.seller.findUnique({ where: { name: "Amazon" } })
if (!amazon) throw new Error("Amazon seller bulunamadı (sellers.json)")
// ---------- SELLER DOMAINS ----------
const domains = ["amazon.com", "amazon.com.tr"]
for (const domain of domains) {
await prisma.sellerDomain.upsert({
where: { domain },
update: { sellerId: amazon.id },
create: {
domain,
sellerId: amazon.id,
createdById: admin.id,
},
})
}
// ---------- CATEGORIES (FROM JSON) ----------
const categoriesFilePath = path.join(__dirname, "categories.json")
const { count } = await seedCategoriesFromJson(categoriesFilePath)
const catSSD = await prisma.category.findUnique({
where: { slug: "pc-ssd" },
select: { id: true },
})
const availableCategoryIds = (
await prisma.category.findMany({
where: { isActive: true, id: { gt: 0 } },
select: { id: true },
})
).map((cat) => cat.id)
// ---------- TAGS ----------
await upsertTagBySlug("ssd", "SSD")
await upsertTagBySlug("nvme", "NVMe")
await upsertTagBySlug("1tb", "1TB")
// ---------- DEAL (tek örnek) ----------
const dealUrl = "https://www.amazon.com.tr/dp/test"
const existing = await prisma.deal.findFirst({
where: { url: dealUrl },
select: { id: true },
})
const dealData = {
title: "Samsung SSD 1TB",
description: "Test deal açıklaması",
url: dealUrl,
price: 1299.99,
originalPrice: 1499.99, // örnek
shippingPrice: 0, // örnek
status: DealStatus.ACTIVE,
saletype: SaleType.ONLINE,
affiliateType: AffiliateType.NON_AFFILIATE,
commentCount: 1,
userId: user.id,
sellerId: amazon.id,
categoryId: pickRandomCategoryId(availableCategoryIds, catSSD?.id ?? 0),
// score: randInt(0, 200), // modelinde varsa aç
}
const deal = existing
? await prisma.deal.update({ where: { id: existing.id }, data: dealData })
: await prisma.deal.create({ data: dealData })
// ---------- DEAL TAGS ----------
await attachTagsToDeal(deal.id, ["ssd", "nvme", "1tb"])
// ---------- DEAL IMAGES (tek örnek) ----------
await prisma.dealImage.deleteMany({ where: { dealId: deal.id } })
await prisma.dealImage.createMany({
data: [
{ dealId: deal.id, imageUrl: realImage("nvme-ssd-single-1"), order: 0 },
{ dealId: deal.id, imageUrl: realImage("nvme-ssd-single-2"), order: 1 },
{ dealId: deal.id, imageUrl: realImage("nvme-ssd-single-3"), order: 2 },
],
})
// ✅ ---------- deals.jsondan 30 DEAL ÜRET ----------
const dealsFilePath = path.join(__dirname, "deals.json")
await seedDealsFromJson({
userId: user.id,
sellerId: amazon.id,
categoryIds: availableCategoryIds,
defaultCategoryId: catSSD?.id ?? 0,
dealsFilePath,
})
// ---------- VOTE ----------
await prisma.dealVote.upsert({
where: { dealId_userId: { dealId: deal.id, userId: admin.id } },
update: { voteType: 1, lastVotedAt: new Date() },
create: { dealId: deal.id, userId: admin.id, voteType: 1 },
})
// ---------- COMMENT ----------
const hasComment = await prisma.comment.findFirst({
where: { dealId: deal.id, userId: admin.id, text: "Gerçekten iyi fırsat" },
select: { id: true },
})
if (!hasComment) {
await prisma.comment.create({
data: { text: "Gerçekten iyi fırsat", userId: admin.id, dealId: deal.id },
})
}
console.log(`✅ Seed tamamlandı (categories.json yüklendi: ${count} kategori)`)
console.log("✅ deals.json baz alınarak 30 adet test deal + 3'er görsel + score(0-200) + tarih dağılımı eklendi/güncellendi")
}
main()
.catch((err) => {
console.error(err)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})