summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorBertrand Yuan <bert.yuan@outlook.com>2025-12-16 00:25:04 +0800
committerGitHub <noreply@github.com>2025-12-16 00:25:04 +0800
commit39c83fbb69ef06d2d56790d75abc254ba7e34394 (patch)
treedd006593448c3500bdcb414af3b4656f7a7683d4 /src/lib
parent48b07bc308a35734a6a7a305c8fdccbfa47de7d8 (diff)
parent785371bb3eccca455e5ce5fccbe9b6e3752a03f6 (diff)
Merge pull request #1 from bertyuan/feat-introduce-payloadv1.0
Feat: introduce payload
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/metadata-image.ts7
-rw-r--r--src/lib/payload-posts.ts192
-rw-r--r--src/lib/source.ts40
3 files changed, 192 insertions, 47 deletions
diff --git a/src/lib/metadata-image.ts b/src/lib/metadata-image.ts
deleted file mode 100644
index f2b91b6..0000000
--- a/src/lib/metadata-image.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { source } from '@/lib/source';
-import { createMetadataImage } from 'fumadocs-core/server';
-
-export const metadataImage = createMetadataImage({
- source,
- imageRoute: 'og',
-});
diff --git a/src/lib/payload-posts.ts b/src/lib/payload-posts.ts
new file mode 100644
index 0000000..7cf1a56
--- /dev/null
+++ b/src/lib/payload-posts.ts
@@ -0,0 +1,192 @@
+import { getPayload } from 'payload'
+import config from '@payload-config'
+import type { Media } from '@/../payload-types'
+
+// Payload 文章类型
+export interface PayloadPost {
+ id: number
+ title: string
+ slug: string
+ description?: string
+ content: unknown // Lexical 富文本内容
+ featuredImage?: Media | number
+ author?: string
+ tags?: { tag: string; id?: string }[]
+ status: 'draft' | 'published'
+ publishedAt?: string
+ createdAt: string
+ updatedAt: string
+}
+
+// 转换后的文章类型(用于前端展示)
+export interface BlogPost {
+ id: number
+ title: string
+ slug: string
+ url: string
+ description: string
+ content: unknown
+ image?: string
+ author: string
+ tags: string[]
+ date: Date
+ createdAt: Date
+ updatedAt: Date
+}
+
+// 获取 Payload 实例
+async function getPayloadClient() {
+ return getPayload({ config })
+}
+
+// 将 Payload 文章转换为博客文章格式
+function transformPost(post: PayloadPost): BlogPost {
+ // 处理封面图片 URL
+ let imageUrl: string | undefined
+ if (post.featuredImage && typeof post.featuredImage === 'object') {
+ imageUrl = post.featuredImage.url || undefined
+ }
+
+ // 处理标签数组
+ const tags = post.tags?.map((t) => t.tag).filter(Boolean) || []
+
+ // 处理日期
+ const date = post.publishedAt ? new Date(post.publishedAt) : new Date(post.createdAt)
+
+ return {
+ id: post.id,
+ title: post.title,
+ slug: post.slug,
+ url: `/posts/${post.slug}`,
+ description: post.description || '',
+ content: post.content,
+ image: imageUrl,
+ author: post.author || 'Admin',
+ tags,
+ date,
+ createdAt: new Date(post.createdAt),
+ updatedAt: new Date(post.updatedAt),
+ }
+}
+
+// 获取所有已发布的文章(按发布时间排序)
+export async function getPublishedPosts(options?: {
+ limit?: number
+ page?: number
+}): Promise<{ posts: BlogPost[]; totalDocs: number; totalPages: number }> {
+ const payload = await getPayloadClient()
+
+ const result = await payload.find({
+ collection: 'posts',
+ where: {
+ status: { equals: 'published' },
+ },
+ sort: '-publishedAt',
+ limit: options?.limit || 10,
+ page: options?.page || 1,
+ depth: 1, // 获取关联的 media 数据
+ })
+
+ return {
+ posts: result.docs.map((doc) => transformPost(doc as unknown as PayloadPost)),
+ totalDocs: result.totalDocs,
+ totalPages: result.totalPages,
+ }
+}
+
+// 根据 slug 获取单篇文章
+export async function getPostBySlug(slug: string): Promise<BlogPost | null> {
+ const payload = await getPayloadClient()
+
+ const result = await payload.find({
+ collection: 'posts',
+ where: {
+ slug: { equals: slug },
+ status: { equals: 'published' },
+ },
+ limit: 1,
+ depth: 1,
+ })
+
+ if (result.docs.length === 0) {
+ return null
+ }
+
+ return transformPost(result.docs[0] as unknown as PayloadPost)
+}
+
+// 获取所有已发布文章的 slug(用于静态生成)
+export async function getAllPostSlugs(): Promise<string[]> {
+ const payload = await getPayloadClient()
+
+ const result = await payload.find({
+ collection: 'posts',
+ where: {
+ status: { equals: 'published' },
+ },
+ limit: 1000,
+ depth: 0,
+ })
+
+ return result.docs.map((doc) => (doc as unknown as PayloadPost).slug)
+}
+
+// 根据标签获取文章
+export async function getPostsByTag(
+ tag: string,
+ options?: { limit?: number; page?: number }
+): Promise<{ posts: BlogPost[]; totalDocs: number; totalPages: number }> {
+ const payload = await getPayloadClient()
+
+ const result = await payload.find({
+ collection: 'posts',
+ where: {
+ and: [
+ { status: { equals: 'published' } },
+ { 'tags.tag': { equals: tag } },
+ ],
+ },
+ sort: '-publishedAt',
+ limit: options?.limit || 10,
+ page: options?.page || 1,
+ depth: 1,
+ })
+
+ return {
+ posts: result.docs.map((doc) => transformPost(doc as unknown as PayloadPost)),
+ totalDocs: result.totalDocs,
+ totalPages: result.totalPages,
+ }
+}
+
+// 获取所有标签
+export async function getAllTags(): Promise<{ tag: string; count: number }[]> {
+ const payload = await getPayloadClient()
+
+ const result = await payload.find({
+ collection: 'posts',
+ where: {
+ status: { equals: 'published' },
+ },
+ limit: 1000,
+ depth: 0,
+ })
+
+ // 统计标签
+ const tagCounts = new Map<string, number>()
+
+ for (const doc of result.docs) {
+ const post = doc as unknown as PayloadPost
+ if (post.tags) {
+ for (const { tag } of post.tags) {
+ if (tag) {
+ tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1)
+ }
+ }
+ }
+ }
+
+ return Array.from(tagCounts.entries())
+ .map(([tag, count]) => ({ tag, count }))
+ .sort((a, b) => b.count - a.count)
+}
diff --git a/src/lib/source.ts b/src/lib/source.ts
deleted file mode 100644
index 34bc7ac..0000000
--- a/src/lib/source.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { loader } from 'fumadocs-core/source';
-import type { InferMetaType, InferPageType } from 'fumadocs-core/source';
-import { createMDXSource } from 'fumadocs-mdx';
-import { blog } from '.source';
-
-export const source = loader({
- baseUrl: '/posts',
- source: createMDXSource(blog),
-});
-export const { getPage: getPost, getPages: getPosts, pageTree } = source;
-
-export type Post = ReturnType<typeof getPost>;
-
-const posts = getPosts();
-
-export const getSortedByDatePosts = () =>
- posts.toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime());
-
-export const getTags = () => {
- const tagSet = new Set<string>();
-
- for (const post of posts) {
- if (post.data.tags) {
- for (const tag of post.data.tags) {
- tagSet.add(tag);
- }
- }
- }
-
- return Array.from(tagSet).toSorted();
-};
-
-export const getPostsByTag = (tag: string) => {
- return posts
- .filter((post) => post.data.tags?.includes(tag))
- .toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime());
-};
-
-export type Page = InferPageType<typeof source>;
-export type Meta = InferMetaType<typeof source>;