feat(admin): add getDashboardData query
This commit is contained in:
+49
-1
@@ -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 {
|
||||
blogKeywords,
|
||||
cases,
|
||||
contentItems,
|
||||
CONTENT_KINDS,
|
||||
posts,
|
||||
siteSettings,
|
||||
type BlogKeyword,
|
||||
@@ -71,6 +72,53 @@ export async function getKeywords(db: Db): Promise<BlogKeyword[]> {
|
||||
.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> {
|
||||
const [settings, services, features, steps, faqs, caseList] = await Promise.all([
|
||||
getSettings(db),
|
||||
|
||||
Reference in New Issue
Block a user