110 lines
7.3 KiB
TypeScript
110 lines
7.3 KiB
TypeScript
import "server-only";
|
|
|
|
import { and, asc, desc, eq, inArray, isNotNull, isNull } from "drizzle-orm";
|
|
|
|
import { getDb } from "@/db";
|
|
import {
|
|
artifactGroups,
|
|
artifactProfiles,
|
|
artifactRecommendations,
|
|
catalogArtifacts,
|
|
catalogCharacters,
|
|
catalogSyncJobs,
|
|
catalogVersions,
|
|
catalogWeapons,
|
|
constellationRecommendations,
|
|
extraSectionRows,
|
|
guides,
|
|
guideSections,
|
|
media,
|
|
teamCompositions,
|
|
teamMembers,
|
|
weaponRecommendations,
|
|
} from "@/db/schema";
|
|
import { publicMediaUrl } from "@/lib/media/storage";
|
|
|
|
export async function getActiveCatalog() {
|
|
const db = getDb();
|
|
const [version] = await db.select().from(catalogVersions).where(eq(catalogVersions.active, true)).limit(1);
|
|
if (!version) return null;
|
|
const [characters, weapons, artifacts] = await Promise.all([
|
|
db.select().from(catalogCharacters).where(eq(catalogCharacters.version, version.version)).orderBy(asc(catalogCharacters.name)),
|
|
db.select().from(catalogWeapons).where(eq(catalogWeapons.version, version.version)).orderBy(asc(catalogWeapons.name)),
|
|
db.select().from(catalogArtifacts).where(eq(catalogArtifacts.version, version.version)).orderBy(asc(catalogArtifacts.name)),
|
|
]);
|
|
return {
|
|
version: version.version,
|
|
characters: characters.map((item) => ({ ...item, imageUrl: publicMediaUrl(item.imageKey) })),
|
|
weapons: weapons.map((item) => ({ ...item, imageUrl: publicMediaUrl(item.imageKey) })),
|
|
artifacts: artifacts.map((item) => ({ ...item, imageUrl: publicMediaUrl(item.imageKey) })),
|
|
};
|
|
}
|
|
|
|
export async function listGuideCards(includeTrash = false) {
|
|
const rows = await getDb()
|
|
.select({ guide: guides, cover: media })
|
|
.from(guides)
|
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
|
.where(includeTrash ? isNotNull(guides.trashedAt) : isNull(guides.trashedAt))
|
|
.orderBy(asc(guides.sortOrder), asc(guides.name));
|
|
return rows.map(({ guide, cover }) => ({ ...guide, coverUrl: publicMediaUrl(cover.objectKey) }));
|
|
}
|
|
|
|
export async function getGuideByCharacterKey(characterKey: string) {
|
|
const [row] = await getDb()
|
|
.select({ guide: guides, cover: media })
|
|
.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.guide.id)).orderBy(asc(guideSections.sortOrder));
|
|
return { ...row.guide, coverUrl: publicMediaUrl(row.cover.objectKey), sections };
|
|
}
|
|
|
|
export async function getPublicGuide(slug: string) {
|
|
const [row] = await getDb()
|
|
.select({ guide: guides, cover: media, character: catalogCharacters })
|
|
.from(guides)
|
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
|
.innerJoin(catalogCharacters, and(eq(catalogCharacters.version, guides.catalogVersion), eq(catalogCharacters.key, guides.characterKey)))
|
|
.where(and(eq(guides.slug, slug), isNull(guides.trashedAt)))
|
|
.limit(1);
|
|
if (!row) return null;
|
|
const [sections, weaponRows, groups, profile, constellations, teams] = await Promise.all([
|
|
getDb().select().from(guideSections).where(and(eq(guideSections.guideId, row.guide.id), eq(guideSections.enabled, true))).orderBy(asc(guideSections.sortOrder)),
|
|
getDb().select({ recommendation: weaponRecommendations, weapon: catalogWeapons }).from(weaponRecommendations).innerJoin(catalogWeapons, and(eq(catalogWeapons.version, weaponRecommendations.catalogVersion), eq(catalogWeapons.key, weaponRecommendations.weaponKey))).where(eq(weaponRecommendations.guideId, row.guide.id)).orderBy(asc(weaponRecommendations.sortOrder)),
|
|
getDb().select().from(artifactGroups).where(eq(artifactGroups.guideId, row.guide.id)).orderBy(asc(artifactGroups.sortOrder)),
|
|
getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, row.guide.id)).limit(1),
|
|
getDb().select().from(constellationRecommendations).where(eq(constellationRecommendations.guideId, row.guide.id)).orderBy(asc(constellationRecommendations.level)),
|
|
getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)),
|
|
]);
|
|
|
|
const artifactRows = groups.length ? await getDb().select({ recommendation: artifactRecommendations, artifact: catalogArtifacts }).from(artifactRecommendations).innerJoin(catalogArtifacts, and(eq(catalogArtifacts.version, artifactRecommendations.catalogVersion), eq(catalogArtifacts.key, artifactRecommendations.firstArtifactKey))).where(inArray(artifactRecommendations.groupId, groups.map((group) => group.id))).orderBy(asc(artifactRecommendations.sortOrder)) : [];
|
|
const secondKeys = artifactRows.map(({ recommendation }) => recommendation.secondArtifactKey).filter((key): key is string => Boolean(key));
|
|
const secondArtifacts = secondKeys.length ? await getDb().select().from(catalogArtifacts).where(and(eq(catalogArtifacts.version, row.guide.catalogVersion), inArray(catalogArtifacts.key, secondKeys))) : [];
|
|
const members = teams.length ? await getDb().select({ member: teamMembers, character: catalogCharacters }).from(teamMembers).innerJoin(catalogCharacters, and(eq(catalogCharacters.version, teamMembers.catalogVersion), eq(catalogCharacters.key, teamMembers.characterKey))).where(inArray(teamMembers.teamId, teams.map((team) => team.id))).orderBy(asc(teamMembers.position)) : [];
|
|
const extraIds = sections.filter((section) => section.kind === "extra").map((section) => section.id);
|
|
const extraRows = extraIds.length ? await getDb().select({ row: extraSectionRows, asset: media }).from(extraSectionRows).leftJoin(media, eq(media.id, extraSectionRows.mediaId)).where(inArray(extraSectionRows.sectionId, extraIds)).orderBy(asc(extraSectionRows.sortOrder)) : [];
|
|
|
|
return {
|
|
...row.guide,
|
|
coverUrl: publicMediaUrl(row.cover.objectKey),
|
|
character: { ...row.character, imageUrl: publicMediaUrl(row.character.imageKey) },
|
|
sections,
|
|
weapons: weaponRows.map(({ recommendation, weapon }) => ({ ...recommendation, weapon: { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } })),
|
|
artifactGroups: groups.map((group) => ({ ...group, rows: artifactRows.filter(({ recommendation }) => recommendation.groupId === group.id).map(({ recommendation, artifact }) => { const second = secondArtifacts.find((item) => item.key === recommendation.secondArtifactKey); return { ...recommendation, first: { ...artifact, imageUrl: publicMediaUrl(artifact.imageKey) }, second: second ? { ...second, imageUrl: publicMediaUrl(second.imageKey) } : null }; }) })),
|
|
artifactProfile: profile[0] ?? null,
|
|
constellations,
|
|
teams: teams.map((team) => ({ ...team, members: members.filter(({ member }) => member.teamId === team.id).map(({ member, character }) => ({ ...member, character: { ...character, imageUrl: publicMediaUrl(character.imageKey) } })) })),
|
|
extras: sections.filter((section) => section.kind === "extra").map((section) => ({ ...section, rows: extraRows.filter(({ row: extraRow }) => extraRow.sectionId === section.id).map(({ row: extraRow, asset }) => ({ ...extraRow, imageUrl: asset ? publicMediaUrl(asset.objectKey) : null })) })),
|
|
};
|
|
}
|
|
|
|
export async function getLatestCatalogSyncJob() {
|
|
const [job] = await getDb().select().from(catalogSyncJobs).orderBy(desc(catalogSyncJobs.createdAt)).limit(1);
|
|
return job ?? null;
|
|
}
|
|
|
|
export type PublicGuide = NonNullable<Awaited<ReturnType<typeof getPublicGuide>>>;
|