Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3dff19eca2 | |||
| 07b01c622a | |||
| 50de18c09c | |||
| dbee0cd8bc | |||
| f30f8ddd8d | |||
| bb9fd65dbb | |||
| 036fba8b53 | |||
| 3e8d5ad8b6 | |||
| 70ad2e3041 | |||
| 5376b939d5 |
@@ -1,5 +1,5 @@
|
|||||||
# Stage 1: Builder
|
# Stage 1: Builder
|
||||||
FROM registry.infra.mintel.me/mintel/nextjs:v1.8.20 AS base
|
FROM git.infra.mintel.me/mmintel/nextjs:latest AS base
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Arguments for build-time configuration
|
# Arguments for build-time configuration
|
||||||
@@ -52,7 +52,7 @@ ENV UV_THREADPOOL_SIZE=3
|
|||||||
RUN pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
# Stage 2: Runner
|
# Stage 2: Runner
|
||||||
FROM registry.infra.mintel.me/mintel/runtime:v1.8.20 AS runner
|
FROM git.infra.mintel.me/mmintel/runtime:latest AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Create nextjs user and group (standardized in runtime image but ensuring local ownership)
|
# Create nextjs user and group (standardized in runtime image but ensuring local ownership)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { notFound, redirect } from 'next/navigation';
|
import { notFound, redirect, permanentRedirect } from 'next/navigation';
|
||||||
import { Container, Badge, Heading } from '@/components/ui';
|
import { Container, Badge, Heading } from '@/components/ui';
|
||||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
@@ -62,6 +62,15 @@ export default async function StandardPage({ params }: PageProps) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle explicit CMS redirects (e.g. /en/terms -> /de/terms)
|
||||||
|
if (pageData.redirectUrl) {
|
||||||
|
if (pageData.redirectPermanent) {
|
||||||
|
permanentRedirect(pageData.redirectUrl);
|
||||||
|
} else {
|
||||||
|
redirect(pageData.redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect if accessed via a different locale's slug
|
// Redirect if accessed via a different locale's slug
|
||||||
const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);
|
const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);
|
||||||
const correctSlug = await mapFileSlugToTranslated(fileSlug, locale);
|
const correctSlug = await mapFileSlugToTranslated(fileSlug, locale);
|
||||||
|
|||||||
@@ -11,10 +11,21 @@ export async function getOgFonts() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[OG] Loading fonts: bold=${boldFontPath}, regular=${regularFontPath}`);
|
console.log(`[OG] Loading fonts: bold=${boldFontPath}, regular=${regularFontPath}`);
|
||||||
const boldFont = readFileSync(boldFontPath);
|
const boldFontBuffer = readFileSync(boldFontPath);
|
||||||
const regularFont = readFileSync(regularFontPath);
|
const regularFontBuffer = readFileSync(regularFontPath);
|
||||||
|
|
||||||
|
// Satori (Vercel OG) strictly requires an ArrayBuffer, not a Node Buffer view.
|
||||||
|
const boldFont = boldFontBuffer.buffer.slice(
|
||||||
|
boldFontBuffer.byteOffset,
|
||||||
|
boldFontBuffer.byteOffset + boldFontBuffer.byteLength,
|
||||||
|
);
|
||||||
|
const regularFont = regularFontBuffer.buffer.slice(
|
||||||
|
regularFontBuffer.byteOffset,
|
||||||
|
regularFontBuffer.byteOffset + regularFontBuffer.byteLength,
|
||||||
|
);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[OG] Fonts loaded successfully (${boldFont.length} and ${regularFont.length} bytes)`,
|
`[OG] Fonts loaded successfully (${boldFont.byteLength} and ${regularFont.byteLength} bytes)`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export interface PageFrontmatter {
|
|||||||
|
|
||||||
export interface PageData {
|
export interface PageData {
|
||||||
slug: string;
|
slug: string;
|
||||||
|
redirectUrl?: string;
|
||||||
|
redirectPermanent?: boolean;
|
||||||
frontmatter: PageFrontmatter;
|
frontmatter: PageFrontmatter;
|
||||||
content: any; // Lexical AST Document
|
content: any; // Lexical AST Document
|
||||||
}
|
}
|
||||||
@@ -96,6 +98,8 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageD
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
slug: doc.slug,
|
slug: doc.slug,
|
||||||
|
redirectUrl: doc.redirectUrl,
|
||||||
|
redirectPermanent: doc.redirectPermanent ?? true,
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: doc.title,
|
title: doc.title,
|
||||||
excerpt: doc.excerpt || '',
|
excerpt: doc.excerpt || '',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"pages": {
|
"pages": {
|
||||||
"impressum": "impressum",
|
"impressum": "impressum",
|
||||||
"datenschutz": "datenschutz",
|
"datenschutz": "datenschutz",
|
||||||
"agbs": "agbs",
|
"agbs": "terms",
|
||||||
"kontakt": "contact",
|
"kontakt": "contact",
|
||||||
"team": "team",
|
"team": "team",
|
||||||
"blog": "blog",
|
"blog": "blog",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"privacyPolicy": "Datenschutz",
|
"privacyPolicy": "Datenschutz",
|
||||||
"privacyPolicySlug": "datenschutz",
|
"privacyPolicySlug": "datenschutz",
|
||||||
"terms": "AGB",
|
"terms": "AGB",
|
||||||
"termsSlug": "agbs",
|
"termsSlug": "terms",
|
||||||
"products": "Produkte",
|
"products": "Produkte",
|
||||||
"lowVoltage": "Niederspannungskabel",
|
"lowVoltage": "Niederspannungskabel",
|
||||||
"mediumVoltage": "Mittelspannungskabel",
|
"mediumVoltage": "Mittelspannungskabel",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"pages": {
|
"pages": {
|
||||||
"legal-notice": "impressum",
|
"legal-notice": "impressum",
|
||||||
"privacy-policy": "datenschutz",
|
"privacy-policy": "datenschutz",
|
||||||
"terms": "agbs",
|
"terms": "terms",
|
||||||
"contact": "contact",
|
"contact": "contact",
|
||||||
"team": "team",
|
"team": "team",
|
||||||
"blog": "blog",
|
"blog": "blog",
|
||||||
@@ -396,4 +396,4 @@
|
|||||||
"cta": "Back to Safety"
|
"cta": "Back to Safety"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,18 @@ const nextConfig = {
|
|||||||
maxInactiveAge: 60 * 1000,
|
maxInactiveAge: 60 * 1000,
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
|
staleTimes: {
|
||||||
|
dynamic: 0,
|
||||||
|
static: 30,
|
||||||
|
},
|
||||||
optimizePackageImports: ['lucide-react', 'framer-motion', '@/components/ui'],
|
optimizePackageImports: ['lucide-react', 'framer-motion', '@/components/ui'],
|
||||||
cpus: 3,
|
cpus: 3,
|
||||||
workerThreads: false,
|
workerThreads: false,
|
||||||
|
serverActions: {
|
||||||
|
allowedOrigins: ["*.klz-cables.com", "*.branch.klz-cables.com", "localhost:3000", "klz.localhost"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
swcMinify: true,
|
|
||||||
productionBrowserSourceMaps: false,
|
productionBrowserSourceMaps: false,
|
||||||
logging: {
|
logging: {
|
||||||
fetches: {
|
fetches: {
|
||||||
@@ -437,6 +443,10 @@ const nextConfig = {
|
|||||||
source: '/de/kontakt',
|
source: '/de/kontakt',
|
||||||
destination: '/de/contact',
|
destination: '/de/contact',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/de/agbs',
|
||||||
|
destination: '/de/terms',
|
||||||
|
},
|
||||||
// Safety rewrites for English locale using German slugs (legacy or content errors)
|
// Safety rewrites for English locale using German slugs (legacy or content errors)
|
||||||
{
|
{
|
||||||
source: '/en/produkte',
|
source: '/en/produkte',
|
||||||
|
|||||||
@@ -87,7 +87,9 @@ export interface Config {
|
|||||||
products: ProductsSelect<false> | ProductsSelect<true>;
|
products: ProductsSelect<false> | ProductsSelect<true>;
|
||||||
pages: PagesSelect<false> | PagesSelect<true>;
|
pages: PagesSelect<false> | PagesSelect<true>;
|
||||||
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents':
|
||||||
|
| PayloadLockedDocumentsSelect<false>
|
||||||
|
| PayloadLockedDocumentsSelect<true>;
|
||||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
};
|
};
|
||||||
@@ -98,6 +100,9 @@ export interface Config {
|
|||||||
globals: {};
|
globals: {};
|
||||||
globalsSelect: {};
|
globalsSelect: {};
|
||||||
locale: 'de' | 'en';
|
locale: 'de' | 'en';
|
||||||
|
widgets: {
|
||||||
|
collections: CollectionsWidget;
|
||||||
|
};
|
||||||
user: User;
|
user: User;
|
||||||
jobs: {
|
jobs: {
|
||||||
tasks: unknown;
|
tasks: unknown;
|
||||||
@@ -328,6 +333,14 @@ export interface Page {
|
|||||||
layout?: ('default' | 'fullBleed') | null;
|
layout?: ('default' | 'fullBleed') | null;
|
||||||
excerpt?: string | null;
|
excerpt?: string | null;
|
||||||
featuredImage?: (number | null) | Media;
|
featuredImage?: (number | null) | Media;
|
||||||
|
/**
|
||||||
|
* If set, visiting this page will immediately redirect the user to this URL (e.g. /de/terms).
|
||||||
|
*/
|
||||||
|
redirectUrl?: string | null;
|
||||||
|
/**
|
||||||
|
* Check for a permanent (301) redirect. Uncheck for a temporary (302) redirect.
|
||||||
|
*/
|
||||||
|
redirectPermanent?: boolean | null;
|
||||||
content: {
|
content: {
|
||||||
root: {
|
root: {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -574,6 +587,8 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
layout?: T;
|
layout?: T;
|
||||||
excerpt?: T;
|
excerpt?: T;
|
||||||
featuredImage?: T;
|
featuredImage?: T;
|
||||||
|
redirectUrl?: T;
|
||||||
|
redirectPermanent?: T;
|
||||||
content?: T;
|
content?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
@@ -619,6 +634,16 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
|||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "collections_widget".
|
||||||
|
*/
|
||||||
|
export interface CollectionsWidget {
|
||||||
|
data?: {
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
|
width: 'full';
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "StatsBlock".
|
* via the `definition` "StatsBlock".
|
||||||
@@ -957,7 +982,6 @@ export interface Auth {
|
|||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
export interface GeneratedTypes extends Config {}
|
export interface GeneratedTypes extends Config {}
|
||||||
}
|
}
|
||||||
|
|||||||
3541
pnpm-lock.yaml
generated
3541
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@ fi
|
|||||||
|
|
||||||
DB_NAME="${PAYLOAD_DB_NAME:-payload}"
|
DB_NAME="${PAYLOAD_DB_NAME:-payload}"
|
||||||
DB_USER="${PAYLOAD_DB_USER:-payload}"
|
DB_USER="${PAYLOAD_DB_USER:-payload}"
|
||||||
DB_CONTAINER="klz-2026-klz-db-1"
|
|
||||||
BACKUP_DIR="./backups"
|
BACKUP_DIR="./backups"
|
||||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
BACKUP_FILE="${BACKUP_DIR}/payload_${TIMESTAMP}.sql.gz"
|
BACKUP_FILE="${BACKUP_DIR}/payload_${TIMESTAMP}.sql.gz"
|
||||||
@@ -21,20 +20,21 @@ BACKUP_FILE="${BACKUP_DIR}/payload_${TIMESTAMP}.sql.gz"
|
|||||||
# Ensure backup directory exists
|
# Ensure backup directory exists
|
||||||
mkdir -p "$BACKUP_DIR"
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
# Check if container is running
|
# Check if database container is running
|
||||||
if ! docker ps --format '{{.Names}}' | grep -q "$DB_CONTAINER"; then
|
if ! docker compose ps --services --filter "status=running" | grep -qx "klz-db"; then
|
||||||
echo "❌ Database container '$DB_CONTAINER' is not running."
|
echo "⚠️ Database container 'klz-db' is not running. Starting it..."
|
||||||
echo " Start it with: docker compose up -d klz-db"
|
docker compose up -d klz-db
|
||||||
exit 1
|
echo "⏳ Waiting for database to be ready..."
|
||||||
|
sleep 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📦 Backing up Payload database..."
|
echo "📦 Backing up Payload database..."
|
||||||
echo " Container: $DB_CONTAINER"
|
echo " Service: klz-db"
|
||||||
echo " Database: $DB_NAME"
|
echo " Database: $DB_NAME"
|
||||||
echo " Output: $BACKUP_FILE"
|
echo " Output: $BACKUP_FILE"
|
||||||
|
|
||||||
# Run pg_dump inside the container and compress
|
# Run pg_dump inside the container and compress
|
||||||
docker exec "$DB_CONTAINER" pg_dump -U "$DB_USER" -d "$DB_NAME" --clean --if-exists | gzip > "$BACKUP_FILE"
|
docker compose exec -T klz-db pg_dump -U "$DB_USER" -d "$DB_NAME" --clean --if-exists | gzip > "$BACKUP_FILE"
|
||||||
|
|
||||||
# Show result
|
# Show result
|
||||||
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
|||||||
@@ -72,6 +72,33 @@ export const Pages: CollectionConfig = {
|
|||||||
position: 'sidebar',
|
position: 'sidebar',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'collapsible',
|
||||||
|
label: 'Redirect Settings',
|
||||||
|
admin: {
|
||||||
|
position: 'sidebar',
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'redirectUrl',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'If set, visiting this page will immediately redirect the user to this URL (e.g. /de/terms).',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redirectPermanent',
|
||||||
|
type: 'checkbox',
|
||||||
|
defaultValue: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'Check for a permanent (301) redirect. Uncheck for a temporary (302) redirect.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'content',
|
name: 'content',
|
||||||
type: 'richText',
|
type: 'richText',
|
||||||
|
|||||||
Reference in New Issue
Block a user