"use server"; import { MediaUsageType } from "@prisma/client"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { isRedirectError } from "next/dist/client/components/redirect-error"; import { SITE_SETTINGS_DEFAULT_OG_IMAGE_FIELD_KEY, SITE_SETTINGS_ENTITY_ID, SITE_SETTINGS_ENTITY_TYPE, SITE_SETTINGS_FAVICON_FIELD_KEY, SITE_SETTINGS_LOGO_DARK_FIELD_KEY, SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY, getSiteSettings, updateSiteSettings, } from "@/lib/app-config"; import { getAdminAppPath, toInternalAdminPath } from "@/lib/admin-routing"; import { PAGE_TITLE_TOKEN, normalizeSiteDefaultLocale, normalizeSitePrimaryColor, type SiteSettings, } from "@/lib/site-settings"; import { clearAdminSessionCookie, isAdminAuthenticated } from "@/lib/admin-auth"; import { replaceEntityMediaUsages } from "@/lib/media"; import { resolveMediaSelection } from "@/lib/media-service"; import { routing } from "@/i18n/routing"; import { getLocalizedPath } from "@/lib/locale"; import { removeManagedMediaFile } from "@/lib/media-storage"; import { mediaFieldInputSchema } from "@/lib/media-validation"; import { prisma } from "@/lib/prisma"; async function ensureAdmin() { if (!(await isAdminAuthenticated())) { await clearAdminSessionCookie(); redirect(getAdminAppPath("/")); } } function withMessage(pathname: string, type: "success" | "error", message: string) { const params = new URLSearchParams(); params.set(type, message); return `${pathname}?${params.toString()}`; } function parseJsonObject(rawValue: FormDataEntryValue | null, key: string) { if (typeof rawValue !== "string" || rawValue.trim() === "") { return undefined; } try { const parsed = JSON.parse(rawValue); if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") { throw new Error(`${key} must be an object.`); } return parsed; } catch { throw new Error(`Invalid ${key} payload.`); } } async function cleanupCreatedMedia(assetIds: string[], uploadedPaths: string[]) { if (assetIds.length > 0) { await prisma.mediaAsset.deleteMany({ where: { id: { in: Array.from(new Set(assetIds)), }, }, }); } for (const filePath of Array.from(new Set(uploadedPaths.filter(Boolean)))) { await removeManagedMediaFile(filePath); } } async function revalidateSiteSettingsPages(defaultLocale: SiteSettings["defaultLocale"]) { revalidatePath("/", "layout"); revalidatePath(toInternalAdminPath("/")); revalidatePath(toInternalAdminPath("/site-settings")); revalidatePath("/coming-soon"); const publicPaths = ["/", "/about", "/portfolio", "/contact", "/success", "/coming-soon"]; for (const locale of routing.locales) { revalidatePath(getLocalizedPath(locale, "/", defaultLocale), "layout"); for (const path of publicPaths) { revalidatePath(getLocalizedPath(locale, path, defaultLocale)); } } } export async function saveSiteSettingsAction(formData: FormData) { return saveSiteBrandSettingsAction(formData); } export async function saveSiteBrandSettingsAction(formData: FormData) { await ensureAdmin(); const createdMediaAssetIds: string[] = []; const uploadedPaths: string[] = []; try { const currentSettings = await getSiteSettings(); const siteLogoLightMedia = parseJsonObject(formData.get("siteLogoLightMedia"), "siteLogoLightMedia"); const siteLogoDarkMedia = parseJsonObject(formData.get("siteLogoDarkMedia"), "siteLogoDarkMedia"); const faviconMedia = parseJsonObject(formData.get("faviconMedia"), "faviconMedia"); const defaultOgImageMedia = parseJsonObject( formData.get("defaultOgImageMedia"), "defaultOgImageMedia", ); const parsedSettings: SiteSettings = { ...currentSettings, brand: { primaryColor: normalizeSitePrimaryColor(formData.get("primaryColor")), }, }; const siteLogoLightSelection = siteLogoLightMedia ? await resolveMediaSelection({ media: mediaFieldInputSchema.parse(siteLogoLightMedia), uploadFile: formData.get("siteLogoLightFile"), folder: "site-settings", fallbackLabel: `${parsedSettings.locales.de.siteName || parsedSettings.locales.en.siteName} logo light`, required: false, }) : { assetId: null, url: "", createdAssetId: null, uploadedUrl: null, }; const siteLogoDarkSelection = siteLogoDarkMedia ? await resolveMediaSelection({ media: mediaFieldInputSchema.parse(siteLogoDarkMedia), uploadFile: formData.get("siteLogoDarkFile"), folder: "site-settings", fallbackLabel: `${parsedSettings.locales.de.siteName || parsedSettings.locales.en.siteName} logo dark`, required: false, }) : { assetId: null, url: "", createdAssetId: null, uploadedUrl: null, }; const faviconSelection = faviconMedia ? await resolveMediaSelection({ media: mediaFieldInputSchema.parse(faviconMedia), uploadFile: formData.get("faviconFile"), folder: "site-settings", fallbackLabel: `${parsedSettings.locales.de.siteName || parsedSettings.locales.en.siteName} favicon`, required: false, }) : { assetId: null, url: "", createdAssetId: null, uploadedUrl: null, }; const defaultOgImageSelection = defaultOgImageMedia ? await resolveMediaSelection({ media: mediaFieldInputSchema.parse(defaultOgImageMedia), uploadFile: formData.get("defaultOgImageFile"), folder: "site-settings", fallbackLabel: `${parsedSettings.locales.de.siteName || parsedSettings.locales.en.siteName} og-image`, required: false, }) : { assetId: null, url: "", createdAssetId: null, uploadedUrl: null, }; if (siteLogoLightSelection.createdAssetId) { createdMediaAssetIds.push(siteLogoLightSelection.createdAssetId); } if (siteLogoLightSelection.uploadedUrl) { uploadedPaths.push(siteLogoLightSelection.uploadedUrl); } if (siteLogoDarkSelection.createdAssetId) { createdMediaAssetIds.push(siteLogoDarkSelection.createdAssetId); } if (siteLogoDarkSelection.uploadedUrl) { uploadedPaths.push(siteLogoDarkSelection.uploadedUrl); } if (faviconSelection.createdAssetId) { createdMediaAssetIds.push(faviconSelection.createdAssetId); } if (faviconSelection.uploadedUrl) { uploadedPaths.push(faviconSelection.uploadedUrl); } if (defaultOgImageSelection.createdAssetId) { createdMediaAssetIds.push(defaultOgImageSelection.createdAssetId); } if (defaultOgImageSelection.uploadedUrl) { uploadedPaths.push(defaultOgImageSelection.uploadedUrl); } await updateSiteSettings(parsedSettings); await replaceEntityMediaUsages({ entityType: SITE_SETTINGS_ENTITY_TYPE, entityId: SITE_SETTINGS_ENTITY_ID, usages: [ ...(siteLogoLightSelection.assetId ? [ { assetId: siteLogoLightSelection.assetId, usageType: MediaUsageType.GENERIC, fieldKey: SITE_SETTINGS_LOGO_LIGHT_FIELD_KEY, }, ] : []), ...(siteLogoDarkSelection.assetId ? [ { assetId: siteLogoDarkSelection.assetId, usageType: MediaUsageType.GENERIC, fieldKey: SITE_SETTINGS_LOGO_DARK_FIELD_KEY, }, ] : []), ...(faviconSelection.assetId ? [ { assetId: faviconSelection.assetId, usageType: MediaUsageType.GENERIC, fieldKey: SITE_SETTINGS_FAVICON_FIELD_KEY, }, ] : []), ...(defaultOgImageSelection.assetId ? [ { assetId: defaultOgImageSelection.assetId, usageType: MediaUsageType.GENERIC, fieldKey: SITE_SETTINGS_DEFAULT_OG_IMAGE_FIELD_KEY, }, ] : []), ], }); await revalidateSiteSettingsPages(parsedSettings.defaultLocale); redirect(withMessage(getAdminAppPath("/site-settings/brand"), "success", "Einstellungen gespeichert.")); } catch (error) { if (isRedirectError(error)) { throw error; } await cleanupCreatedMedia(createdMediaAssetIds, uploadedPaths); const message = error instanceof Error ? error.message : "Einstellungen konnten nicht gespeichert werden."; redirect(withMessage(getAdminAppPath("/site-settings/brand"), "error", message)); } } export async function saveSiteLocalizationSettingsAction(formData: FormData) { await ensureAdmin(); try { const requestedDefaultLocale = String(formData.get("defaultLocale") ?? ""); const currentSettings = await getSiteSettings(); const parsedSettings: SiteSettings = { ...currentSettings, defaultLocale: normalizeSiteDefaultLocale(requestedDefaultLocale), locales: { ar: { siteName: String(formData.get("siteNameAr") ?? "").trim(), titleTemplate: String(formData.get("titleTemplateAr") ?? "").trim(), siteDescription: String(formData.get("siteDescriptionAr") ?? "").trim(), subhead: String(formData.get("subheadAr") ?? "").trim(), }, en: { siteName: String(formData.get("siteNameEn") ?? "").trim(), titleTemplate: String(formData.get("titleTemplateEn") ?? "").trim(), siteDescription: String(formData.get("siteDescriptionEn") ?? "").trim(), subhead: String(formData.get("subheadEn") ?? "").trim(), }, de: { siteName: String(formData.get("siteNameDe") ?? "").trim(), titleTemplate: String(formData.get("titleTemplateDe") ?? "").trim(), siteDescription: String(formData.get("siteDescriptionDe") ?? "").trim(), subhead: String(formData.get("subheadDe") ?? "").trim(), }, }, }; for (const locale of routing.locales) { if (!parsedSettings.locales[locale].siteName) { throw new Error(`Site Name fuer ${locale} ist erforderlich.`); } if ( !parsedSettings.locales[locale].titleTemplate || !parsedSettings.locales[locale].titleTemplate.includes(PAGE_TITLE_TOKEN) ) { throw new Error(`Title Template fuer ${locale} muss {pageTitle} enthalten.`); } } await updateSiteSettings(parsedSettings); await revalidateSiteSettingsPages(parsedSettings.defaultLocale); redirect(withMessage(getAdminAppPath("/site-settings/localization"), "success", "Einstellungen gespeichert.")); } catch (error) { if (isRedirectError(error)) { throw error; } const message = error instanceof Error ? error.message : "Einstellungen konnten nicht gespeichert werden."; redirect(withMessage(getAdminAppPath("/site-settings/localization"), "error", message)); } }