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 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),
|
||||||
|
|||||||
Reference in New Issue
Block a user