import { NextRequest, NextResponse } from "next/server"; import { prisma, projectService } from "@/lib/prisma"; import { requireSessionAuth } from "@/lib/auth"; import type { Prisma } from "@prisma/client"; type ImportSiteSettings = { defaultLocale?: unknown; locales?: unknown; theme?: unknown; }; type ImportContentPageTranslation = { locale?: unknown; title?: unknown; slug?: unknown; content?: unknown; metaDescription?: unknown; keywords?: unknown; }; type ImportContentPage = { key?: unknown; status?: unknown; translations?: unknown; }; type ImportProject = { id?: unknown; slug?: unknown; defaultLocale?: unknown; title?: unknown; description?: unknown; content?: unknown; tags?: unknown; category?: unknown; featured?: unknown; github?: unknown; live?: unknown; published?: unknown; imageUrl?: unknown; difficulty?: unknown; timeToComplete?: unknown; technologies?: unknown; challenges?: unknown; lessonsLearned?: unknown; futureImprovements?: unknown; demoVideo?: unknown; screenshots?: unknown; colorScheme?: unknown; accessibility?: unknown; performance?: unknown; analytics?: unknown; }; type ImportProjectTranslation = { projectId?: unknown; locale?: unknown; title?: unknown; description?: unknown; content?: unknown; metaDescription?: unknown; keywords?: unknown; ogImage?: unknown; schema?: unknown; }; type ImportPayload = { projects?: unknown; siteSettings?: unknown; contentPages?: unknown; projectTranslations?: unknown; }; function asString(v: unknown): string | null { return typeof v === "string" ? v : null; } function asStringArray(v: unknown): string[] | null { if (!Array.isArray(v)) return null; const allStrings = v.filter((x) => typeof x === "string") as string[]; return allStrings.length === v.length ? allStrings : null; } export async function POST(request: NextRequest) { try { const isAdminRequest = request.headers.get("x-admin-request") === "true"; if (!isAdminRequest) { return NextResponse.json({ error: "Admin access required" }, { status: 403 }); } const authError = requireSessionAuth(request); if (authError) return authError; const body = (await request.json()) as ImportPayload; // Validate import data structure if (!Array.isArray(body.projects)) { return NextResponse.json( { error: "Invalid import data format" }, { status: 400 }, ); } const results = { imported: 0, skipped: 0, errors: [] as string[], }; // Import SiteSettings (optional) if (body.siteSettings && typeof body.siteSettings === "object") { try { const ss = body.siteSettings as ImportSiteSettings; const defaultLocale = asString(ss.defaultLocale); const locales = asStringArray(ss.locales); const theme = ss.theme as Prisma.InputJsonValue | undefined; await prisma.siteSettings.upsert({ where: { id: 1 }, create: { id: 1, ...(defaultLocale ? { defaultLocale } : {}), ...(locales ? { locales } : {}), ...(theme ? { theme } : {}), }, update: { ...(defaultLocale ? { defaultLocale } : {}), ...(locales ? { locales } : {}), ...(theme ? { theme } : {}), }, }); } catch { // non-blocking } } // Import CMS content pages (optional) if (Array.isArray(body.contentPages)) { for (const page of body.contentPages) { try { const key = asString((page as ImportContentPage)?.key); if (!key) continue; const statusRaw = asString((page as ImportContentPage)?.status); const status = statusRaw === "DRAFT" || statusRaw === "PUBLISHED" ? statusRaw : "PUBLISHED"; const upserted = await prisma.contentPage.upsert({ where: { key }, create: { key, status }, update: { status }, }); const translations = (page as ImportContentPage)?.translations; if (Array.isArray(translations)) { for (const tr of translations as ImportContentPageTranslation[]) { const locale = asString(tr?.locale); if (!locale || typeof tr?.content === "undefined" || tr?.content === null) continue; await prisma.contentPageTranslation.upsert({ where: { pageId_locale: { pageId: upserted.id, locale } }, create: { pageId: upserted.id, locale, title: asString(tr.title), slug: asString(tr.slug), content: tr.content as Prisma.InputJsonValue, metaDescription: asString(tr.metaDescription), keywords: asString(tr.keywords), }, update: { title: asString(tr.title), slug: asString(tr.slug), content: tr.content as Prisma.InputJsonValue, metaDescription: asString(tr.metaDescription), keywords: asString(tr.keywords), }, }); } } } catch (error) { const key = asString((page as ImportContentPage)?.key) ?? "unknown"; results.errors.push( `Failed to import content page "${key}": ${error instanceof Error ? error.message : "Unknown error"}`, ); } } } // Preload existing titles once (avoid O(n^2) DB reads during import) const existingProjectsResult = await projectService.getAllProjects({ limit: 10000 }); const existingProjects = existingProjectsResult.projects || existingProjectsResult; const existingTitles = new Set(existingProjects.map(p => p.title)); const existingSlugs = new Set( existingProjects .map((p) => (p as unknown as { slug?: string }).slug) .filter((s): s is string => typeof s === "string" && s.length > 0), ); // Process each project for (const projectData of body.projects as ImportProject[]) { try { // Check if project already exists (by title) const title = asString(projectData.title); if (!title) continue; const exists = existingTitles.has(title); if (exists) { results.skipped++; results.errors.push(`Project "${title}" already exists`); continue; } // Create new project const created = await projectService.createProject({ slug: asString(projectData.slug) ?? undefined, defaultLocale: asString(projectData.defaultLocale) ?? "en", title, description: asString(projectData.description) ?? "", content: projectData.content as Prisma.InputJsonValue | undefined, tags: (asStringArray(projectData.tags) ?? []) as string[], category: asString(projectData.category) ?? "General", featured: projectData.featured === true, github: asString(projectData.github) ?? undefined, live: asString(projectData.live) ?? undefined, published: projectData.published !== false, // Default to true imageUrl: asString(projectData.imageUrl) ?? undefined, difficulty: asString(projectData.difficulty) ?? "Intermediate", timeToComplete: asString(projectData.timeToComplete) ?? undefined, technologies: (asStringArray(projectData.technologies) ?? []) as string[], challenges: (asStringArray(projectData.challenges) ?? []) as string[], lessonsLearned: (asStringArray(projectData.lessonsLearned) ?? []) as string[], futureImprovements: (asStringArray(projectData.futureImprovements) ?? []) as string[], demoVideo: asString(projectData.demoVideo) ?? undefined, screenshots: (asStringArray(projectData.screenshots) ?? []) as string[], colorScheme: asString(projectData.colorScheme) ?? "Dark", accessibility: projectData.accessibility !== false, // Default to true performance: (projectData.performance as Record | null) || { lighthouse: 0, bundleSize: "0KB", loadTime: "0s", }, analytics: (projectData.analytics as Record | null) || { views: 0, likes: 0, shares: 0, }, }); // Import translations (optional, from export v2) if (Array.isArray(body.projectTranslations)) { for (const tr of body.projectTranslations as ImportProjectTranslation[]) { const projectId = typeof tr?.projectId === "number" ? tr.projectId : null; const locale = asString(tr?.locale); if (!projectId || !locale) continue; // Map translation to created project by original slug/title when possible. // We match by slug if available in exported project list; otherwise by title. const exportedProject = (body.projects as ImportProject[]).find( (p) => typeof p.id === "number" && p.id === projectId, ); const exportedSlug = asString(exportedProject?.slug); const matches = (exportedSlug && (created as unknown as { slug?: string }).slug === exportedSlug) || (!!asString(exportedProject?.title) && (created as unknown as { title?: string }).title === asString(exportedProject?.title)); if (!matches) continue; const trTitle = asString(tr.title); const trDescription = asString(tr.description); if (!trTitle || !trDescription) continue; await prisma.projectTranslation.upsert({ where: { projectId_locale: { projectId: (created as unknown as { id: number }).id, locale, }, }, create: { projectId: (created as unknown as { id: number }).id, locale, title: trTitle, description: trDescription, content: (tr.content as Prisma.InputJsonValue) ?? null, metaDescription: asString(tr.metaDescription), keywords: asString(tr.keywords), ogImage: asString(tr.ogImage), schema: (tr.schema as Prisma.InputJsonValue) ?? null, }, update: { title: trTitle, description: trDescription, content: (tr.content as Prisma.InputJsonValue) ?? null, metaDescription: asString(tr.metaDescription), keywords: asString(tr.keywords), ogImage: asString(tr.ogImage), schema: (tr.schema as Prisma.InputJsonValue) ?? null, }, }); } } results.imported++; existingTitles.add(title); const slug = asString(projectData.slug); if (slug) existingSlugs.add(slug); } catch (error) { results.skipped++; const title = asString(projectData.title) ?? "unknown"; results.errors.push( `Failed to import "${title}": ${error instanceof Error ? error.message : "Unknown error"}`, ); } } return NextResponse.json({ success: true, message: `Import completed: ${results.imported} imported, ${results.skipped} skipped`, results }); } catch (error) { console.error("Import error:", error); return NextResponse.json( { error: "Failed to import projects" }, { status: 500 }, ); } }