477 lines
14 KiB
JavaScript
477 lines
14 KiB
JavaScript
// 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
|
||
}
|
||
|
||
// 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.json’dan seed + her deal’a 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, categoryId, 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,
|
||
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 },
|
||
})
|
||
|
||
// ---------- 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: 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.json’dan 30 DEAL ÜRET ----------
|
||
const dealsFilePath = path.join(__dirname, "deals.json")
|
||
await seedDealsFromJson({
|
||
userId: user.id,
|
||
sellerId: amazon.id,
|
||
categoryId: 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()
|
||
})
|