feat: payload cms
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m13s
Build & Deploy / 🏗️ Build (push) Failing after 5m53s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s

This commit is contained in:
2026-02-24 02:28:48 +01:00
parent 41cfe19cbf
commit a5d77fc69b
89 changed files with 25282 additions and 1903 deletions

View File

@@ -1,7 +1,5 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { config } from '@/lib/config';
export function extractExcerpt(content: string): string {
@@ -42,7 +40,7 @@ export interface PostFrontmatter {
export interface PostMdx {
slug: string;
frontmatter: PostFrontmatter;
content: string;
content: any; // Mapped to Lexical SerializedEditorState
}
export function isPostVisible(post: { frontmatter: { date: string; public?: boolean } }) {
@@ -57,87 +55,81 @@ export function isPostVisible(post: { frontmatter: { date: string; public?: bool
}
export async function getPostBySlug(slug: string, locale: string): Promise<PostMdx | null> {
// Map translated slug to file slug
const fileSlug = await mapSlugToFileSlug(slug, locale);
const postsDir = path.join(process.cwd(), 'data', 'blog', locale);
const filePath = path.join(postsDir, `${fileSlug}.mdx`);
const payload = await getPayload({ config: configPromise });
if (!fs.existsSync(filePath)) {
return null;
}
const { docs } = await payload.find({
collection: 'posts',
where: {
slug: { equals: slug },
locale: { equals: locale },
},
draft: process.env.NODE_ENV === 'development',
limit: 1,
});
const fileContent = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(fileContent);
if (!docs || docs.length === 0) return null;
const postInfo = {
slug: fileSlug,
const doc = docs[0];
return {
slug: doc.slug,
frontmatter: {
...data,
excerpt: data.excerpt || extractExcerpt(content),
title: doc.title,
date: doc.date,
excerpt: doc.excerpt || '',
category: doc.category || '',
locale: doc.locale,
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.sizes?.card?.url || doc.featuredImage.url
: null,
public: doc._status === 'published',
} as PostFrontmatter,
content,
content: doc.content as any, // Native Lexical Editor State
};
if (!isPostVisible(postInfo)) {
return null;
}
return postInfo;
}
export async function getAllPosts(locale: string): Promise<PostMdx[]> {
const postsDir = path.join(process.cwd(), 'data', 'blog', locale);
if (!fs.existsSync(postsDir)) return [];
const payload = await getPayload({ config: configPromise });
// Query only published posts (access checks applied automatically by Payload!)
const { docs } = await payload.find({
collection: 'posts',
where: {
locale: {
equals: locale,
},
},
sort: '-date',
draft: process.env.NODE_ENV === 'development', // Includes Drafts if running locally
limit: 100,
});
const files = fs.readdirSync(postsDir);
const posts = files
.filter((file) => file.endsWith('.mdx'))
.map((file) => {
const filePath = path.join(postsDir, file);
const fileContent = fs.readFileSync(filePath, 'utf8');
const { data, content } = matter(fileContent);
return {
slug: file.replace(/\.mdx$/, ''),
frontmatter: {
...data,
excerpt: data.excerpt || extractExcerpt(content),
} as PostFrontmatter,
content,
};
})
.filter(isPostVisible)
.sort(
(a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(),
);
return posts;
return docs.map((doc) => {
return {
slug: doc.slug,
frontmatter: {
title: doc.title,
date: doc.date,
excerpt: doc.excerpt || '',
category: doc.category || '',
locale: doc.locale,
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.sizes?.card?.url || doc.featuredImage.url
: null,
} as PostFrontmatter,
// Pass the Lexical content object rather than raw markdown string
content: doc.content as any,
};
});
}
export async function getAllPostsMetadata(locale: string): Promise<Partial<PostMdx>[]> {
const postsDir = path.join(process.cwd(), 'data', 'blog', locale);
if (!fs.existsSync(postsDir)) return [];
const files = fs.readdirSync(postsDir);
return files
.filter((file) => file.endsWith('.mdx'))
.map((file) => {
const filePath = path.join(postsDir, file);
const fileContent = fs.readFileSync(filePath, 'utf8');
const { data } = matter(fileContent);
return {
slug: file.replace(/\.mdx$/, ''),
frontmatter: {
...data,
excerpt: data.excerpt || extractExcerpt(fileContent.replace(/^---[\s\S]*?---/, '')),
} as PostFrontmatter,
};
})
.filter(isPostVisible)
.sort(
(a, b) =>
new Date(b.frontmatter.date as string).getTime() -
new Date(a.frontmatter.date as string).getTime(),
);
const posts = await getAllPosts(locale);
return posts.map((p) => ({
slug: p.slug,
frontmatter: p.frontmatter,
}));
}
export async function getAdjacentPosts(