Refactor portfolio admin and views
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
CREATE TYPE "PortfolioProjectViewMode" AS ENUM ('GRID', 'STORY', 'CASE_STUDY');
|
||||
|
||||
ALTER TABLE "PortfolioProject"
|
||||
ADD COLUMN "viewMode" "PortfolioProjectViewMode" NOT NULL DEFAULT 'GRID';
|
||||
+15
-8
@@ -27,6 +27,12 @@ enum PortfolioAssetKind {
|
||||
DOCUMENT
|
||||
}
|
||||
|
||||
enum PortfolioProjectViewMode {
|
||||
GRID
|
||||
STORY
|
||||
CASE_STUDY
|
||||
}
|
||||
|
||||
enum MediaSource {
|
||||
UPLOAD
|
||||
EXTERNAL
|
||||
@@ -61,9 +67,10 @@ model Category {
|
||||
}
|
||||
|
||||
model PortfolioProject {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
categoryId String
|
||||
slug String @unique
|
||||
slug String @unique
|
||||
viewMode PortfolioProjectViewMode @default(GRID)
|
||||
titleAr String
|
||||
titleEn String
|
||||
titleDe String
|
||||
@@ -77,13 +84,13 @@ model PortfolioProject {
|
||||
serviceLabelDe String
|
||||
previewUrl String?
|
||||
coverImagePath String?
|
||||
isFeatured Boolean @default(false)
|
||||
isPublished Boolean @default(false)
|
||||
isFeatured Boolean @default(false)
|
||||
isPublished Boolean @default(false)
|
||||
publishedAt DateTime?
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
||||
sections PortfolioSection[]
|
||||
assets PortfolioAsset[]
|
||||
|
||||
|
||||
+449
-128
@@ -9,7 +9,121 @@ const connectionString =
|
||||
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" },
|
||||
@@ -121,156 +235,363 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
const project = await prisma.portfolioProject.upsert({
|
||||
where: { slug: "brand-redesign" },
|
||||
const commerceCategory = await prisma.category.upsert({
|
||||
where: { slug: "commerce" },
|
||||
update: {
|
||||
categoryId: brandCategory.id,
|
||||
titleAr: "إعادة تصميم الهوية",
|
||||
titleEn: "Brand Redesign",
|
||||
titleDe: "Brand Redesign",
|
||||
summaryAr: "إعادة بناء لهوية رقمية مع نظام مرئي أوضح ومسارات استخدام أسرع.",
|
||||
summaryEn: "A digital brand refresh with a clearer visual system and faster journeys.",
|
||||
summaryDe: "Ein digitales Redesign mit klarerem visuellen System und schnelleren Journeys.",
|
||||
clientName: "Studio Client",
|
||||
projectYear: 2025,
|
||||
serviceLabelAr: "هوية بصرية",
|
||||
serviceLabelEn: "Brand Identity",
|
||||
serviceLabelDe: "Brand Identity",
|
||||
previewUrl: "https://example.com/preview/brand-redesign",
|
||||
coverImagePath: "/uploads/portfolio/demo-cover.svg",
|
||||
isFeatured: true,
|
||||
isPublished: true,
|
||||
publishedAt: new Date("2025-01-10T09:00:00.000Z"),
|
||||
sortOrder: 1,
|
||||
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: {
|
||||
categoryId: brandCategory.id,
|
||||
slug: "brand-redesign",
|
||||
titleAr: "إعادة تصميم الهوية",
|
||||
titleEn: "Brand Redesign",
|
||||
titleDe: "Brand Redesign",
|
||||
summaryAr: "إعادة بناء لهوية رقمية مع نظام مرئي أوضح ومسارات استخدام أسرع.",
|
||||
summaryEn: "A digital brand refresh with a clearer visual system and faster journeys.",
|
||||
summaryDe: "Ein digitales Redesign mit klarerem visuellen System und schnelleren Journeys.",
|
||||
clientName: "Studio Client",
|
||||
projectYear: 2025,
|
||||
serviceLabelAr: "هوية بصرية",
|
||||
serviceLabelEn: "Brand Identity",
|
||||
serviceLabelDe: "Brand Identity",
|
||||
previewUrl: "https://example.com/preview/brand-redesign",
|
||||
coverImagePath: "/uploads/portfolio/demo-cover.svg",
|
||||
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("2025-01-10T09:00:00.000Z"),
|
||||
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,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.portfolioSection.deleteMany({
|
||||
where: { projectId: project.id },
|
||||
});
|
||||
|
||||
await prisma.portfolioAsset.deleteMany({
|
||||
where: { projectId: project.id },
|
||||
});
|
||||
|
||||
await prisma.portfolioSection.createMany({
|
||||
data: [
|
||||
{
|
||||
projectId: project.id,
|
||||
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.",
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
projectId: project.id,
|
||||
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.",
|
||||
sortOrder: 2,
|
||||
},
|
||||
{
|
||||
projectId: project.id,
|
||||
type: "LINK",
|
||||
titleAr: "المعاينة",
|
||||
titleEn: "Preview",
|
||||
titleDe: "Vorschau",
|
||||
bodyAr: "رابط مباشر لعرض المشروع.",
|
||||
bodyEn: "Direct link for reviewing the work.",
|
||||
bodyDe: "Direkter Link zur Projektansicht.",
|
||||
linkUrl: "https://example.com/preview/brand-redesign",
|
||||
sortOrder: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.portfolioAsset.createMany({
|
||||
data: [
|
||||
{
|
||||
projectId: project.id,
|
||||
kind: "IMAGE",
|
||||
filePath: "/uploads/portfolio/demo-cover.svg",
|
||||
altAr: "غلاف مشروع إعادة تصميم الهوية",
|
||||
altEn: "Brand redesign cover artwork",
|
||||
altDe: "Titelgrafik fuer Brand Redesign",
|
||||
sortOrder: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await prisma.portfolioProject.upsert({
|
||||
where: { slug: "campaign-site" },
|
||||
update: {
|
||||
categoryId: webCategory.id,
|
||||
titleAr: "موقع حملة",
|
||||
titleEn: "Campaign Site",
|
||||
titleDe: "Campaign Site",
|
||||
summaryAr: "صفحة إطلاق مرنة لحملة رقمية مع تركيز على السرعة والتحويل.",
|
||||
summaryEn: "A launch site built for speed, iteration, and conversion.",
|
||||
summaryDe: "Eine Kampagnenseite mit Fokus auf Tempo, Iteration und Conversion.",
|
||||
clientName: "Launch Client",
|
||||
projectYear: 2024,
|
||||
serviceLabelAr: "موقع تسويقي",
|
||||
serviceLabelEn: "Marketing Website",
|
||||
serviceLabelDe: "Marketing Website",
|
||||
previewUrl: "https://example.com/preview/campaign-site",
|
||||
coverImagePath: "/uploads/portfolio/demo-cover.svg",
|
||||
isFeatured: false,
|
||||
isPublished: true,
|
||||
publishedAt: new Date("2024-09-05T09:00:00.000Z"),
|
||||
sortOrder: 2,
|
||||
},
|
||||
create: {
|
||||
categoryId: webCategory.id,
|
||||
{
|
||||
slug: "campaign-site",
|
||||
categoryId: webCategory.id,
|
||||
viewMode: "STORY",
|
||||
titleAr: "موقع حملة",
|
||||
titleEn: "Campaign Site",
|
||||
titleDe: "Campaign Site",
|
||||
summaryAr: "صفحة إطلاق مرنة لحملة رقمية مع تركيز على السرعة والتحويل.",
|
||||
summaryEn: "A launch site built for speed, iteration, and conversion.",
|
||||
summaryDe: "Eine Kampagnenseite mit Fokus auf Tempo, Iteration und Conversion.",
|
||||
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: "/uploads/portfolio/demo-cover.svg",
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user