feat(admin): add getDashboardData query

This commit is contained in:
2026-09-13 21:41:48 +08:00
parent 4f630afafb
commit a296ba4ce1
+49 -1
View File
@@ -1,9 +1,10 @@
import { and, asc, desc, eq, ne } from "drizzle-orm"; import { and, asc, desc, eq, isNull, ne, sql } from "drizzle-orm";
import type { DrizzleD1Database } from "drizzle-orm/d1"; import type { DrizzleD1Database } from "drizzle-orm/d1";
import { import {
blogKeywords, blogKeywords,
cases, cases,
contentItems, contentItems,
CONTENT_KINDS,
posts, posts,
siteSettings, siteSettings,
type BlogKeyword, type BlogKeyword,
@@ -71,6 +72,53 @@ export async function getKeywords(db: Db): Promise<BlogKeyword[]> {
.orderBy(asc(blogKeywords.createdAt), asc(blogKeywords.id)); .orderBy(asc(blogKeywords.createdAt), asc(blogKeywords.id));
} }
export type DashboardData = {
posts: { published: number; draft: number };
cases: { published: number; draft: number };
content: { kind: ContentKind; published: number; draft: number }[];
keywords: { pending: number; generated: number; skipped: number };
recentPosts: Post[];
postsWithoutCover: number;
};
/** 後台總覽用嘅統計(只讀 DB;AI secrets 由頁面層提供)。 */
export async function getDashboardData(db: Db): Promise<DashboardData> {
const [postRows, caseRows, itemRows, kwRows, recentPosts, noCover] = await Promise.all([
db.select({ status: posts.status, n: sql<number>`count(*)` }).from(posts).groupBy(posts.status),
db.select({ status: cases.status, n: sql<number>`count(*)` }).from(cases).groupBy(cases.status),
db
.select({ kind: contentItems.kind, status: contentItems.status, n: sql<number>`count(*)` })
.from(contentItems)
.groupBy(contentItems.kind, contentItems.status),
db
.select({ status: blogKeywords.status, n: sql<number>`count(*)` })
.from(blogKeywords)
.groupBy(blogKeywords.status),
db.select().from(posts).orderBy(desc(posts.updatedAt)).limit(5),
db.select({ n: sql<number>`count(*)` }).from(posts).where(isNull(posts.coverImage)),
]);
const pick = <T extends string>(rows: { status: T; n: number }[], status: T) =>
Number(rows.find((r) => r.status === status)?.n ?? 0);
return {
posts: { published: pick(postRows, "published"), draft: pick(postRows, "draft") },
cases: { published: pick(caseRows, "published"), draft: pick(caseRows, "draft") },
content: CONTENT_KINDS.map((kind) => ({
kind,
published: Number(itemRows.find((r) => r.kind === kind && r.status === "published")?.n ?? 0),
draft: Number(itemRows.find((r) => r.kind === kind && r.status === "draft")?.n ?? 0),
})),
keywords: {
pending: pick(kwRows, "pending"),
generated: pick(kwRows, "generated"),
skipped: pick(kwRows, "skipped"),
},
recentPosts,
postsWithoutCover: Number(noCover[0]?.n ?? 0),
};
}
export async function getHomeData(db: Db): Promise<HomeData> { export async function getHomeData(db: Db): Promise<HomeData> {
const [settings, services, features, steps, faqs, caseList] = await Promise.all([ const [settings, services, features, steps, faqs, caseList] = await Promise.all([
getSettings(db), getSettings(db),