update
This commit is contained in:
@@ -13,7 +13,7 @@ import { getAdminSession } from "@/lib/auth/server";
|
|||||||
import {
|
import {
|
||||||
getActiveCatalog,
|
getActiveCatalog,
|
||||||
getAdminGuideContent,
|
getAdminGuideContent,
|
||||||
getGuideByCharacterKey,
|
getGuideEditorHeader,
|
||||||
} from "@/lib/guides/queries";
|
} from "@/lib/guides/queries";
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
@@ -33,7 +33,7 @@ export default async function StructuredEditorPage({
|
|||||||
await connection();
|
await connection();
|
||||||
if (!(await getAdminSession())) redirect("/admin/login");
|
if (!(await getAdminSession())) redirect("/admin/login");
|
||||||
const { character, page } = await params;
|
const { character, page } = await params;
|
||||||
const guide = await getGuideByCharacterKey(character);
|
const guide = await getGuideEditorHeader(character);
|
||||||
if (!guide) notFound();
|
if (!guide) notFound();
|
||||||
const extraSections = guide.sections.filter((section) => section.kind === "extra");
|
const extraSections = guide.sections.filter((section) => section.kind === "extra");
|
||||||
if (page === "extra") {
|
if (page === "extra") {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default async function GuideOverviewPage({ params }: { params: Promise<{
|
|||||||
<GuideOverviewForm guide={guide} />
|
<GuideOverviewForm guide={guide} />
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
{sections.map(({ slug, title, description }) => (
|
{sections.map(({ slug, title, description }) => (
|
||||||
<Link key={slug} href={`/admin/${guide.characterKey}/${slug}`} prefetch className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
<Link key={slug} href={`/admin/${guide.characterKey}/${slug}`} prefetch={false} className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||||
<Card className="h-full transition-colors hover:bg-muted/50">
|
<Card className="h-full transition-colors hover:bg-muted/50">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
{navigationIconPath(slug) ? <Image src={navigationIconPath(slug)!} alt="" width={40} height={40} className="size-10 object-contain" /> : null}
|
{navigationIconPath(slug) ? <Image src={navigationIconPath(slug)!} alt="" width={40} height={40} className="size-10 object-contain" /> : null}
|
||||||
|
|||||||
+9
-6
@@ -4,17 +4,20 @@ import { connection } from "next/server";
|
|||||||
import { StructuredDashboard } from "@/components/admin/structured-dashboard";
|
import { StructuredDashboard } from "@/components/admin/structured-dashboard";
|
||||||
import { AdminHeader } from "@/components/admin/admin-header";
|
import { AdminHeader } from "@/components/admin/admin-header";
|
||||||
import { getAdminSession } from "@/lib/auth/server";
|
import { getAdminSession } from "@/lib/auth/server";
|
||||||
import { getLatestLunarisVersion } from "@/lib/catalog/sync";
|
import {
|
||||||
import { getActiveCatalog, listGuideCards } from "@/lib/guides/queries";
|
getCurrentCatalogVersion,
|
||||||
|
getLatestLunarisVersion,
|
||||||
|
} from "@/lib/catalog/sync";
|
||||||
|
import { listGuideCards } from "@/lib/guides/queries";
|
||||||
|
|
||||||
export default async function AdminPage() {
|
export default async function AdminPage() {
|
||||||
await connection();
|
await connection();
|
||||||
if (!(await getAdminSession())) redirect("/admin/login");
|
if (!(await getAdminSession())) redirect("/admin/login");
|
||||||
const [guides, trash, catalog, latestCatalogVersion] = await Promise.all([
|
const [guides, trash, catalogVersion, latestCatalogVersion] = await Promise.all([
|
||||||
listGuideCards(),
|
listGuideCards(),
|
||||||
listGuideCards(true),
|
listGuideCards(true),
|
||||||
getActiveCatalog(),
|
getCurrentCatalogVersion(),
|
||||||
getLatestLunarisVersion().catch(() => null),
|
getLatestLunarisVersion(AbortSignal.timeout(3_000)).catch(() => null),
|
||||||
]);
|
]);
|
||||||
return (
|
return (
|
||||||
<div className="min-h-svh">
|
<div className="min-h-svh">
|
||||||
@@ -25,7 +28,7 @@ export default async function AdminPage() {
|
|||||||
<h1 className="font-heading text-3xl font-semibold">Guide Dashboard</h1>
|
<h1 className="font-heading text-3xl font-semibold">Guide Dashboard</h1>
|
||||||
<p className="mt-2 text-muted-foreground">จัดการ Guide แบบภาพ-first และบันทึกขึ้นเว็บไซต์ทันที</p>
|
<p className="mt-2 text-muted-foreground">จัดการ Guide แบบภาพ-first และบันทึกขึ้นเว็บไซต์ทันที</p>
|
||||||
</header>
|
</header>
|
||||||
<StructuredDashboard guides={guides} trash={trash} catalogVersion={catalog?.version ?? null} latestCatalogVersion={latestCatalogVersion} />
|
<StructuredDashboard guides={guides} trash={trash} catalogVersion={catalogVersion} latestCatalogVersion={latestCatalogVersion} />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function GuideSectionNav({
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/${characterKey}`}
|
href={`/admin/${characterKey}`}
|
||||||
prefetch
|
prefetch={false}
|
||||||
onClick={confirmNavigation}
|
onClick={confirmNavigation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
@@ -56,7 +56,7 @@ export function GuideSectionNav({
|
|||||||
<Link
|
<Link
|
||||||
key={section.slug}
|
key={section.slug}
|
||||||
href={`/admin/${characterKey}/${section.slug}`}
|
href={`/admin/${characterKey}/${section.slug}`}
|
||||||
prefetch
|
prefetch={false}
|
||||||
onClick={confirmNavigation}
|
onClick={confirmNavigation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
@@ -72,7 +72,7 @@ export function GuideSectionNav({
|
|||||||
<Link
|
<Link
|
||||||
key={extra.id}
|
key={extra.id}
|
||||||
href={`/admin/${characterKey}/${extra.slug}`}
|
href={`/admin/${characterKey}/${extra.slug}`}
|
||||||
prefetch
|
prefetch={false}
|
||||||
onClick={confirmNavigation}
|
onClick={confirmNavigation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
"flex min-h-8 shrink-0 items-center gap-2 rounded-md px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export function StructuredDashboard({
|
|||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/${guide.characterKey}`}
|
href={`/admin/${guide.characterKey}`}
|
||||||
prefetch
|
prefetch={false}
|
||||||
className="absolute inset-0 z-10 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="absolute inset-0 z-10 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
<span className="sr-only">แก้ไข Guide {guide.name}</span>
|
<span className="sr-only">แก้ไข Guide {guide.name}</span>
|
||||||
@@ -205,6 +205,7 @@ export function StructuredDashboard({
|
|||||||
src={guide.coverUrl}
|
src={guide.coverUrl}
|
||||||
alt={`Guide ${guide.name}`}
|
alt={`Guide ${guide.name}`}
|
||||||
fill
|
fill
|
||||||
|
sizes="(min-width: 1280px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export function StructuredDirectory({ guides }: { guides: GuideCard[] }) {
|
|||||||
src={guide.coverUrl}
|
src={guide.coverUrl}
|
||||||
alt={`ไกด์ ${guide.name}`}
|
alt={`ไกด์ ${guide.name}`}
|
||||||
fill
|
fill
|
||||||
|
sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export function StructuredGuideShell({
|
|||||||
? "bg-muted text-foreground"
|
? "bg-muted text-foreground"
|
||||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
prefetch
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src={navigationIconPath("overview")!}
|
src={navigationIconPath("overview")!}
|
||||||
@@ -167,7 +167,7 @@ export function StructuredGuideShell({
|
|||||||
? "bg-muted text-foreground"
|
? "bg-muted text-foreground"
|
||||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
)}
|
)}
|
||||||
prefetch
|
prefetch={false}
|
||||||
>
|
>
|
||||||
{navigationIconPath(item.slug) ? (
|
{navigationIconPath(item.slug) ? (
|
||||||
<Image
|
<Image
|
||||||
@@ -306,7 +306,7 @@ function GuideNavigation({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
href={`/${guide.slug}/${item.slug}`}
|
href={`/${guide.slug}/${item.slug}`}
|
||||||
className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
prefetch
|
prefetch={false}
|
||||||
>
|
>
|
||||||
<Card size="sm" className="transition-colors hover:bg-muted/50">
|
<Card size="sm" className="transition-colors hover:bg-muted/50">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
Vendored
-1
@@ -57,7 +57,6 @@ export async function getCachedPublicGuide(slug: string) {
|
|||||||
"use cache: remote";
|
"use cache: remote";
|
||||||
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
|
cacheLife({ stale: 15, revalidate: 60, expire: 60 * 60 });
|
||||||
const result = await getPublicGuide(slug);
|
const result = await getPublicGuide(slug);
|
||||||
cacheTag(DIRECTORY_CACHE_TAG);
|
|
||||||
cacheTag(GLOSSARY_CACHE_TAG);
|
cacheTag(GLOSSARY_CACHE_TAG);
|
||||||
if (result) cacheTag(pageCacheTag(result.id));
|
if (result) cacheTag(pageCacheTag(result.id));
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+4
-2
@@ -157,8 +157,10 @@ function rarity(value: string | undefined): number | null {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLatestLunarisVersion(): Promise<string> {
|
export async function getLatestLunarisVersion(
|
||||||
return versionSchema.parse(await getJson(VERSION_URL)).version;
|
signal?: AbortSignal,
|
||||||
|
): Promise<string> {
|
||||||
|
return versionSchema.parse(await getJson(VERSION_URL, signal)).version;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mapConcurrent<T, R>(
|
async function mapConcurrent<T, R>(
|
||||||
|
|||||||
+12
-10
@@ -55,9 +55,11 @@ export class GuideVersionConflictError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function emitGuideEvents(tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0], guideId: string, version: number) {
|
async function emitGuideEvents(tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0], guideId: string, version: number, directoryChanged = false) {
|
||||||
await tx.insert(outboxEvents).values([
|
await tx.insert(outboxEvents).values([
|
||||||
{ topic: "directory", aggregateId: guideId, eventType: "directory.updated", payload: { id: guideId, version } },
|
...(directoryChanged
|
||||||
|
? [{ topic: "directory", aggregateId: guideId, eventType: "directory.updated", payload: { id: guideId, version } }]
|
||||||
|
: []),
|
||||||
{ topic: `page:${guideId}`, aggregateId: guideId, eventType: "page.updated", payload: { id: guideId, version } },
|
{ topic: `page:${guideId}`, aggregateId: guideId, eventType: "page.updated", payload: { id: guideId, version } },
|
||||||
{ topic: "admin", aggregateId: guideId, eventType: "admin.updated", payload: { id: guideId, version } },
|
{ topic: "admin", aggregateId: guideId, eventType: "admin.updated", payload: { id: guideId, version } },
|
||||||
]);
|
]);
|
||||||
@@ -89,19 +91,19 @@ export async function createGuide(input: unknown) {
|
|||||||
]);
|
]);
|
||||||
await tx.insert(artifactProfiles).values({ guideId: guide.id });
|
await tx.insert(artifactProfiles).values({ guideId: guide.id });
|
||||||
await tx.update(media).set({ currentReferenceCount: sql`${media.currentReferenceCount} + 1` }).where(eq(media.id, parsed.coverMediaId));
|
await tx.update(media).set({ currentReferenceCount: sql`${media.currentReferenceCount} + 1` }).where(eq(media.id, parsed.coverMediaId));
|
||||||
await emitGuideEvents(tx, guide.id, guide.version);
|
await emitGuideEvents(tx, guide.id, guide.version, true);
|
||||||
return guide;
|
return guide;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function advanceGuideVersion(tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0], guideId: string, expectedVersion: number) {
|
async function advanceGuideVersion(tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0], guideId: string, expectedVersion: number, directoryChanged = false) {
|
||||||
const [guide] = await tx.update(guides).set({ version: expectedVersion + 1 }).where(and(eq(guides.id, guideId), eq(guides.version, expectedVersion), sql`${guides.trashedAt} is null`)).returning();
|
const [guide] = await tx.update(guides).set({ version: expectedVersion + 1 }).where(and(eq(guides.id, guideId), eq(guides.version, expectedVersion), sql`${guides.trashedAt} is null`)).returning();
|
||||||
if (!guide) {
|
if (!guide) {
|
||||||
const [current] = await tx.select({ version: guides.version }).from(guides).where(eq(guides.id, guideId)).limit(1);
|
const [current] = await tx.select({ version: guides.version }).from(guides).where(eq(guides.id, guideId)).limit(1);
|
||||||
if (current) throw new GuideVersionConflictError(current.version);
|
if (current) throw new GuideVersionConflictError(current.version);
|
||||||
throw new Error("ไม่พบ Guide");
|
throw new Error("ไม่พบ Guide");
|
||||||
}
|
}
|
||||||
await emitGuideEvents(tx, guide.id, guide.version);
|
await emitGuideEvents(tx, guide.id, guide.version, directoryChanged);
|
||||||
return guide;
|
return guide;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +148,7 @@ export async function updateGuideBasics(guideId: string, input: unknown) {
|
|||||||
: [];
|
: [];
|
||||||
if (bleedingMedia.length !== bleedingMediaIds.length) throw new Error("ไม่พบภาพ Bleeding ที่อัปโหลดแล้ว");
|
if (bleedingMedia.length !== bleedingMediaIds.length) throw new Error("ไม่พบภาพ Bleeding ที่อัปโหลดแล้ว");
|
||||||
}
|
}
|
||||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion);
|
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion, true);
|
||||||
await tx.update(guides).set({
|
await tx.update(guides).set({
|
||||||
overview: data.overview,
|
overview: data.overview,
|
||||||
coverMediaId: data.coverMediaId,
|
coverMediaId: data.coverMediaId,
|
||||||
@@ -414,7 +416,7 @@ export async function trashGuide(guideId: string) {
|
|||||||
return getDb().transaction(async (tx) => {
|
return getDb().transaction(async (tx) => {
|
||||||
const [guide] = await tx.update(guides).set({ trashedAt: new Date(), version: sql`${guides.version} + 1` }).where(eq(guides.id, guideId)).returning();
|
const [guide] = await tx.update(guides).set({ trashedAt: new Date(), version: sql`${guides.version} + 1` }).where(eq(guides.id, guideId)).returning();
|
||||||
if (!guide) throw new Error("ไม่พบ Guide");
|
if (!guide) throw new Error("ไม่พบ Guide");
|
||||||
await emitGuideEvents(tx, guide.id, guide.version);
|
await emitGuideEvents(tx, guide.id, guide.version, true);
|
||||||
return guide;
|
return guide;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -423,7 +425,7 @@ export async function restoreGuide(guideId: string) {
|
|||||||
return getDb().transaction(async (tx) => {
|
return getDb().transaction(async (tx) => {
|
||||||
const [guide] = await tx.update(guides).set({ trashedAt: null, version: sql`${guides.version} + 1` }).where(eq(guides.id, guideId)).returning();
|
const [guide] = await tx.update(guides).set({ trashedAt: null, version: sql`${guides.version} + 1` }).where(eq(guides.id, guideId)).returning();
|
||||||
if (!guide) throw new Error("ไม่พบ Guide");
|
if (!guide) throw new Error("ไม่พบ Guide");
|
||||||
await emitGuideEvents(tx, guide.id, guide.version);
|
await emitGuideEvents(tx, guide.id, guide.version, true);
|
||||||
return guide;
|
return guide;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -436,7 +438,7 @@ export async function setGuideVisibility(guideId: string, isPublic: boolean) {
|
|||||||
.where(and(eq(guides.id, guideId), sql`${guides.trashedAt} is null`))
|
.where(and(eq(guides.id, guideId), sql`${guides.trashedAt} is null`))
|
||||||
.returning();
|
.returning();
|
||||||
if (!guide) throw new Error("ไม่พบ Guide");
|
if (!guide) throw new Error("ไม่พบ Guide");
|
||||||
await emitGuideEvents(tx, guide.id, guide.version);
|
await emitGuideEvents(tx, guide.id, guide.version, true);
|
||||||
return guide;
|
return guide;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -454,7 +456,7 @@ export async function permanentlyDeleteGuide(guideId: string) {
|
|||||||
for (const layer of bleeding) {
|
for (const layer of bleeding) {
|
||||||
await tx.update(media).set({ currentReferenceCount: sql`greatest(${media.currentReferenceCount} - 1, 0)` }).where(eq(media.id, layer.mediaId));
|
await tx.update(media).set({ currentReferenceCount: sql`greatest(${media.currentReferenceCount} - 1, 0)` }).where(eq(media.id, layer.mediaId));
|
||||||
}
|
}
|
||||||
await emitGuideEvents(tx, guide.id, guide.version + 1);
|
await emitGuideEvents(tx, guide.id, guide.version + 1, true);
|
||||||
return { version: guide.version + 1 };
|
return { version: guide.version + 1 };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-4
@@ -113,23 +113,72 @@ export async function getActiveCatalog() {
|
|||||||
|
|
||||||
export async function listGuideCards(includeTrash = false) {
|
export async function listGuideCards(includeTrash = false) {
|
||||||
const rows = await getDb()
|
const rows = await getDb()
|
||||||
.select({ guide: guides, cover: media })
|
.select({
|
||||||
|
id: guides.id,
|
||||||
|
characterKey: guides.characterKey,
|
||||||
|
name: guides.name,
|
||||||
|
overview: guides.overview,
|
||||||
|
version: guides.version,
|
||||||
|
updatedAt: guides.updatedAt,
|
||||||
|
trashedAt: guides.trashedAt,
|
||||||
|
isPublic: guides.isPublic,
|
||||||
|
coverObjectKey: media.objectKey,
|
||||||
|
})
|
||||||
.from(guides)
|
.from(guides)
|
||||||
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
||||||
.where(includeTrash ? isNotNull(guides.trashedAt) : isNull(guides.trashedAt))
|
.where(includeTrash ? isNotNull(guides.trashedAt) : isNull(guides.trashedAt))
|
||||||
.orderBy(asc(guides.sortOrder), asc(guides.name));
|
.orderBy(asc(guides.sortOrder), asc(guides.name));
|
||||||
return rows.map(({ guide, cover }) => ({ ...guide, coverUrl: publicMediaUrl(cover.objectKey) }));
|
return rows.map(({ coverObjectKey, ...guide }) => ({
|
||||||
|
...guide,
|
||||||
|
coverUrl: publicMediaUrl(coverObjectKey),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listPublicGuideCards() {
|
export async function listPublicGuideCards() {
|
||||||
const [rows, aliases] = await Promise.all([getDb()
|
const [rows, aliases] = await Promise.all([getDb()
|
||||||
.select({ guide: guides, cover: media })
|
.select({
|
||||||
|
id: guides.id,
|
||||||
|
characterKey: guides.characterKey,
|
||||||
|
slug: guides.slug,
|
||||||
|
name: guides.name,
|
||||||
|
overview: guides.overview,
|
||||||
|
version: guides.version,
|
||||||
|
coverObjectKey: media.objectKey,
|
||||||
|
})
|
||||||
.from(guides)
|
.from(guides)
|
||||||
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
||||||
.where(and(eq(guides.isPublic, true), isNull(guides.trashedAt)))
|
.where(and(eq(guides.isPublic, true), isNull(guides.trashedAt)))
|
||||||
.orderBy(asc(guides.sortOrder), asc(guides.name)), getDb().select().from(glossaryAliases)]);
|
.orderBy(asc(guides.sortOrder), asc(guides.name)), getDb().select().from(glossaryAliases)]);
|
||||||
const grouped = aliasesByItem(aliases);
|
const grouped = aliasesByItem(aliases);
|
||||||
return rows.map(({ guide, cover }) => ({ ...guide, aliases: grouped.character.get(guide.characterKey) ?? [], coverUrl: publicMediaUrl(cover.objectKey) }));
|
return rows.map(({ coverObjectKey, ...guide }) => ({
|
||||||
|
...guide,
|
||||||
|
aliases: grouped.character.get(guide.characterKey) ?? [],
|
||||||
|
coverUrl: publicMediaUrl(coverObjectKey),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGuideEditorHeader(characterKey: string) {
|
||||||
|
const [row] = await getDb()
|
||||||
|
.select({
|
||||||
|
id: guides.id,
|
||||||
|
characterKey: guides.characterKey,
|
||||||
|
slug: guides.slug,
|
||||||
|
name: guides.name,
|
||||||
|
version: guides.version,
|
||||||
|
coverObjectKey: media.objectKey,
|
||||||
|
})
|
||||||
|
.from(guides)
|
||||||
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
||||||
|
.where(and(eq(guides.characterKey, characterKey), isNull(guides.trashedAt)))
|
||||||
|
.limit(1);
|
||||||
|
if (!row) return null;
|
||||||
|
const sections = await getDb()
|
||||||
|
.select()
|
||||||
|
.from(guideSections)
|
||||||
|
.where(eq(guideSections.guideId, row.id))
|
||||||
|
.orderBy(asc(guideSections.sortOrder));
|
||||||
|
const { coverObjectKey, ...guide } = row;
|
||||||
|
return { ...guide, coverUrl: publicMediaUrl(coverObjectKey), sections };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getGuideByCharacterKey(characterKey: string) {
|
export async function getGuideByCharacterKey(characterKey: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user