const { PrismaPg } = require("@prisma/adapter-pg"); const { PrismaClient } = require("@prisma/client"); const { Pool } = require("pg"); const connectionString = process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public"; const pool = new Pool({ connectionString }); const prisma = new PrismaClient({ adapter: new PrismaPg(pool) }); async function upsertMediaAsset(input) { const existing = await prisma.mediaAsset.findFirst({ where: { label: input.label, url: input.url, }, }); if (existing) { return prisma.mediaAsset.update({ where: { id: existing.id }, data: input, }); } return prisma.mediaAsset.create({ data: input, }); } async function syncProjectContent(projectId, sections, assets) { await prisma.portfolioSection.deleteMany({ where: { projectId }, }); await prisma.portfolioAsset.deleteMany({ where: { projectId }, }); const createdSections = []; for (const section of sections) { const createdSection = await prisma.portfolioSection.create({ data: { projectId, ...section, }, }); createdSections.push(createdSection); } const createdAssets = []; for (const asset of assets) { const createdAsset = await prisma.portfolioAsset.create({ data: { projectId, ...asset, }, }); createdAssets.push(createdAsset); } return { createdSections, createdAssets }; } async function syncProjectMediaUsages(projectId, mediaMap) { await prisma.mediaUsage.deleteMany({ where: { entityType: "portfolio-project", entityId: projectId, }, }); const usages = []; if (mediaMap.coverAssetId) { usages.push({ assetId: mediaMap.coverAssetId, usageType: "PORTFOLIO_COVER", entityType: "portfolio-project", entityId: projectId, fieldKey: "cover", }); } for (const sectionUsage of mediaMap.sectionUsages) { usages.push({ assetId: sectionUsage.assetId, usageType: "PORTFOLIO_SECTION", entityType: "portfolio-project", entityId: projectId, fieldKey: sectionUsage.fieldKey, }); } for (const assetUsage of mediaMap.assetUsages) { usages.push({ assetId: assetUsage.assetId, usageType: "PORTFOLIO_ASSET", entityType: "portfolio-project", entityId: projectId, fieldKey: assetUsage.fieldKey, }); } if (usages.length > 0) { await prisma.mediaUsage.createMany({ data: usages }); } } async function main() { await prisma.mediaUsage.deleteMany({ where: { entityType: "portfolio-project", }, }); await prisma.portfolioSection.deleteMany(); await prisma.portfolioAsset.deleteMany(); await prisma.portfolioProject.deleteMany(); await prisma.category.deleteMany(); await prisma.appConfig.upsert({ where: { key: "siteName" }, update: { value: "moh-sass" }, create: { key: "siteName", value: "moh-sass" }, }); await prisma.appConfig.upsert({ where: { key: "site_settings" }, update: { value: JSON.stringify({ titleTemplate: "{pageTitle} | moh-sass", locales: { ar: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, en: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, de: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, }, }), }, create: { key: "site_settings", value: JSON.stringify({ titleTemplate: "{pageTitle} | moh-sass", locales: { ar: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, en: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, de: { siteName: "moh-sass", titleTemplate: "{pageTitle} | {siteName}", siteDescription: "Multilingual Next.js base project", subhead: "", }, }, }), }, }); const brandCategory = await prisma.category.upsert({ where: { slug: "branding" }, update: { nameAr: "الهوية البصرية", nameEn: "Branding", nameDe: "Branding", descriptionAr: "مشاريع هوية بصرية وشعارات وأنظمة علامة.", descriptionEn: "Brand identity, logo, and design system work.", descriptionDe: "Branding, Logos und visuelle Systeme.", sortOrder: 1, isActive: true, }, create: { slug: "branding", nameAr: "الهوية البصرية", nameEn: "Branding", nameDe: "Branding", descriptionAr: "مشاريع هوية بصرية وشعارات وأنظمة علامة.", descriptionEn: "Brand identity, logo, and design system work.", descriptionDe: "Branding, Logos und visuelle Systeme.", sortOrder: 1, isActive: true, }, }); const webCategory = await prisma.category.upsert({ where: { slug: "web-experiences" }, update: { nameAr: "تجارب الويب", nameEn: "Web Experiences", nameDe: "Web Experiences", descriptionAr: "مواقع وصفحات هبوط وتجارب رقمية سريعة.", descriptionEn: "Websites, landing pages, and digital experiences.", descriptionDe: "Webseiten, Landingpages und digitale Erlebnisse.", sortOrder: 2, isActive: true, }, create: { slug: "web-experiences", nameAr: "تجارب الويب", nameEn: "Web Experiences", nameDe: "Web Experiences", descriptionAr: "مواقع وصفحات هبوط وتجارب رقمية سريعة.", descriptionEn: "Websites, landing pages, and digital experiences.", descriptionDe: "Webseiten, Landingpages und digitale Erlebnisse.", sortOrder: 2, isActive: true, }, }); const commerceCategory = await prisma.category.upsert({ where: { slug: "commerce" }, update: { nameAr: "التجارة الرقمية", nameEn: "Commerce", nameDe: "Commerce", descriptionAr: "متاجر وتجارب شراء رقمية مع تركيز على الوضوح والتحويل.", descriptionEn: "Commerce experiences with a focus on clarity and conversion.", descriptionDe: "Commerce-Projekte mit Fokus auf Klarheit und Conversion.", sortOrder: 3, isActive: true, }, create: { slug: "commerce", nameAr: "التجارة الرقمية", nameEn: "Commerce", nameDe: "Commerce", descriptionAr: "متاجر وتجارب شراء رقمية مع تركيز على الوضوح والتحويل.", descriptionEn: "Commerce experiences with a focus on clarity and conversion.", descriptionDe: "Commerce-Projekte mit Fokus auf Klarheit und Conversion.", sortOrder: 3, isActive: true, }, }); const gridCover = await upsertMediaAsset({ source: "UPLOAD", kind: "IMAGE", url: "/uploads/portfolio/demo-cover.svg", fileName: "demo-cover.svg", label: "Portfolio Grid Cover", altText: "Portfolio Grid Cover", mimeType: "image/svg+xml", size: 1024, }); const storyCover = await upsertMediaAsset({ source: "UPLOAD", kind: "IMAGE", url: "/uploads/portfolio/demo-cover.svg", fileName: "demo-cover.svg", label: "Portfolio Story Cover", altText: "Portfolio Story Cover", mimeType: "image/svg+xml", size: 1024, }); const caseStudyCover = await upsertMediaAsset({ source: "UPLOAD", kind: "IMAGE", url: "/uploads/portfolio/demo-cover.svg", fileName: "demo-cover.svg", label: "Portfolio Case Study Cover", altText: "Portfolio Case Study Cover", mimeType: "image/svg+xml", size: 1024, }); const projects = [ { slug: "grid-product-launch", categoryId: commerceCategory.id, viewMode: "GRID", titleAr: "إطلاق منتج رقمي", titleEn: "Grid Product Launch", titleDe: "Grid Product Launch", summaryAr: "مثال عرض شبكي لمشروع سريع مع أقسام قصيرة وأصول داعمة.", summaryEn: "Grid view example for a fast product launch page.", summaryDe: "Grid-Ansicht als Beispiel fuer einen schnellen Produktlaunch.", clientName: "Launch Studio", projectYear: 2026, serviceLabelAr: "تجربة إطلاق", serviceLabelEn: "Launch Experience", serviceLabelDe: "Launch Experience", previewUrl: "https://example.com/preview/grid-product-launch", coverImagePath: gridCover.url, isFeatured: true, isPublished: true, publishedAt: new Date("2026-01-12T09:00:00.000Z"), sortOrder: 1, coverAssetId: gridCover.id, sections: [ { type: "RICH_TEXT", titleAr: "الفكرة", titleEn: "Concept", titleDe: "Konzept", bodyAr: "واجهة سريعة لعرض المنتج والتركيز على الرسالة الأساسية.", bodyEn: "A fast modular presentation focused on the main launch message.", bodyDe: "Eine schnelle modulare Darstellung mit Fokus auf die Hauptbotschaft.", imagePath: null, linkUrl: null, sortOrder: 0, mediaAssetId: null, }, { type: "GALLERY", titleAr: "الصورة الرئيسية", titleEn: "Hero Visual", titleDe: "Hero Visual", bodyAr: "", bodyEn: "", bodyDe: "", imagePath: gridCover.url, linkUrl: null, sortOrder: 1, mediaAssetId: gridCover.id, }, ], assets: [ { kind: "IMAGE", filePath: gridCover.url, altAr: "غلاف مشروع Grid", altEn: "Grid project cover", altDe: "Grid Projekt Cover", sortOrder: 0, mediaAssetId: gridCover.id, }, ], }, { slug: "campaign-site", categoryId: webCategory.id, viewMode: "STORY", titleAr: "موقع حملة", titleEn: "Campaign Site", titleDe: "Campaign Site", summaryAr: "مثال عرض قصصي لمشروع ويب مع تسلسل سردي أوضح.", summaryEn: "Story view example for a launch campaign website.", summaryDe: "Story-Ansicht als Beispiel fuer eine Kampagnenseite.", clientName: "Launch Client", projectYear: 2024, serviceLabelAr: "موقع تسويقي", serviceLabelEn: "Marketing Website", serviceLabelDe: "Marketing Website", previewUrl: "https://example.com/preview/campaign-site", coverImagePath: storyCover.url, isFeatured: false, isPublished: true, publishedAt: new Date("2024-09-05T09:00:00.000Z"), sortOrder: 2, coverAssetId: storyCover.id, sections: [ { type: "RICH_TEXT", titleAr: "السياق", titleEn: "Context", titleDe: "Kontext", bodyAr: "الحملة احتاجت صفحة مرنة وسريعة تتبدل بين أكثر من مرحلة.", bodyEn: "The campaign needed a flexible page that could adapt across phases.", bodyDe: "Die Kampagne brauchte eine flexible Seite fuer mehrere Phasen.", imagePath: null, linkUrl: null, sortOrder: 0, mediaAssetId: null, }, { type: "GALLERY", titleAr: "العرض البصري", titleEn: "Visual Flow", titleDe: "Visueller Ablauf", bodyAr: "", bodyEn: "", bodyDe: "", imagePath: storyCover.url, linkUrl: null, sortOrder: 1, mediaAssetId: storyCover.id, }, { type: "LINK", titleAr: "المعاينة", titleEn: "Preview", titleDe: "Vorschau", bodyAr: "رابط العرض المباشر.", bodyEn: "Direct preview link.", bodyDe: "Direkter Vorschau-Link.", imagePath: null, linkUrl: "https://example.com/preview/campaign-site", sortOrder: 2, mediaAssetId: null, }, ], assets: [ { kind: "IMAGE", filePath: storyCover.url, altAr: "غلاف مشروع Story", altEn: "Story project cover", altDe: "Story Projekt Cover", sortOrder: 0, mediaAssetId: storyCover.id, }, ], }, { slug: "brand-redesign", categoryId: brandCategory.id, viewMode: "CASE_STUDY", titleAr: "إعادة تصميم الهوية", titleEn: "Brand Redesign", titleDe: "Brand Redesign", summaryAr: "مثال عرض دراسة حالة يركز على التحدي والحل والنتيجة.", summaryEn: "Case study example focused on challenge, solution, and outcome.", summaryDe: "Case-Study-Ansicht mit Fokus auf Herausforderung, Loesung und Ergebnis.", clientName: "Studio Client", projectYear: 2025, serviceLabelAr: "هوية بصرية", serviceLabelEn: "Brand Identity", serviceLabelDe: "Brand Identity", previewUrl: "https://example.com/preview/brand-redesign", coverImagePath: caseStudyCover.url, isFeatured: true, isPublished: true, publishedAt: new Date("2025-01-10T09:00:00.000Z"), sortOrder: 3, coverAssetId: caseStudyCover.id, sections: [ { type: "RICH_TEXT", titleAr: "التحدي", titleEn: "Challenge", titleDe: "Herausforderung", bodyAr: "كان المطلوب تحديث الهوية بدون خسارة التعرف البصري الحالي.", bodyEn: "The brief required a refreshed identity without losing recognition.", bodyDe: "Die Marke sollte modernisiert werden, ohne die Wiedererkennbarkeit zu verlieren.", imagePath: null, linkUrl: null, sortOrder: 0, mediaAssetId: null, }, { type: "RICH_TEXT", titleAr: "الحل", titleEn: "Solution", titleDe: "Loesung", bodyAr: "تم بناء نظام مرئي أوضح مع قواعد استخدام قابلة للتوسع.", bodyEn: "A clearer visual system with scalable usage rules was created.", bodyDe: "Es wurde ein klareres visuelles System mit skalierbaren Regeln aufgebaut.", imagePath: null, linkUrl: null, sortOrder: 1, mediaAssetId: null, }, { type: "GALLERY", titleAr: "التنفيذ البصري", titleEn: "Visual Execution", titleDe: "Visuelle Umsetzung", bodyAr: "", bodyEn: "", bodyDe: "", imagePath: caseStudyCover.url, linkUrl: null, sortOrder: 2, mediaAssetId: caseStudyCover.id, }, ], assets: [ { kind: "IMAGE", filePath: caseStudyCover.url, altAr: "غلاف مشروع Case Study", altEn: "Case study project cover", altDe: "Case Study Projekt Cover", sortOrder: 0, mediaAssetId: caseStudyCover.id, }, ], }, ]; for (const projectConfig of projects) { const project = await prisma.portfolioProject.upsert({ where: { slug: projectConfig.slug }, update: { categoryId: projectConfig.categoryId, viewMode: projectConfig.viewMode, titleAr: projectConfig.titleAr, titleEn: projectConfig.titleEn, titleDe: projectConfig.titleDe, summaryAr: projectConfig.summaryAr, summaryEn: projectConfig.summaryEn, summaryDe: projectConfig.summaryDe, clientName: projectConfig.clientName, projectYear: projectConfig.projectYear, serviceLabelAr: projectConfig.serviceLabelAr, serviceLabelEn: projectConfig.serviceLabelEn, serviceLabelDe: projectConfig.serviceLabelDe, previewUrl: projectConfig.previewUrl, coverImagePath: projectConfig.coverImagePath, isFeatured: projectConfig.isFeatured, isPublished: projectConfig.isPublished, publishedAt: projectConfig.publishedAt, sortOrder: projectConfig.sortOrder, }, create: { slug: projectConfig.slug, categoryId: projectConfig.categoryId, viewMode: projectConfig.viewMode, titleAr: projectConfig.titleAr, titleEn: projectConfig.titleEn, titleDe: projectConfig.titleDe, summaryAr: projectConfig.summaryAr, summaryEn: projectConfig.summaryEn, summaryDe: projectConfig.summaryDe, clientName: projectConfig.clientName, projectYear: projectConfig.projectYear, serviceLabelAr: projectConfig.serviceLabelAr, serviceLabelEn: projectConfig.serviceLabelEn, serviceLabelDe: projectConfig.serviceLabelDe, previewUrl: projectConfig.previewUrl, coverImagePath: projectConfig.coverImagePath, isFeatured: projectConfig.isFeatured, isPublished: projectConfig.isPublished, publishedAt: projectConfig.publishedAt, sortOrder: projectConfig.sortOrder, }, }); const created = await syncProjectContent(project.id, projectConfig.sections.map((section) => ({ type: section.type, titleAr: section.titleAr, titleEn: section.titleEn, titleDe: section.titleDe, bodyAr: section.bodyAr, bodyEn: section.bodyEn, bodyDe: section.bodyDe, imagePath: section.imagePath, linkUrl: section.linkUrl, sortOrder: section.sortOrder, })), projectConfig.assets.map((asset) => ({ kind: asset.kind, filePath: asset.filePath, altAr: asset.altAr, altEn: asset.altEn, altDe: asset.altDe, sortOrder: asset.sortOrder, }))); await syncProjectMediaUsages(project.id, { coverAssetId: projectConfig.coverAssetId, sectionUsages: created.createdSections .map((sectionRow, index) => ({ fieldKey: sectionRow.id, assetId: projectConfig.sections[index]?.mediaAssetId, })) .filter((entry) => entry.assetId), assetUsages: created.createdAssets .map((assetRow, index) => ({ fieldKey: assetRow.id, assetId: projectConfig.assets[index]?.mediaAssetId, })) .filter((entry) => entry.assetId), }); } } main() .catch((error) => { console.error("Seed failed:", error); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); await pool.end(); });