282 lines
14 KiB
TypeScript
282 lines
14 KiB
TypeScript
import "server-only";
|
|
|
|
import { and, asc, eq, inArray, isNotNull, isNull } from "drizzle-orm";
|
|
|
|
import { getDb } from "@/db";
|
|
import {
|
|
artifactGroups,
|
|
artifactProfiles,
|
|
artifactRecommendations,
|
|
catalogArtifacts,
|
|
catalogCharacters,
|
|
catalogWeapons,
|
|
constellationRecommendations,
|
|
extraSectionRows,
|
|
guides,
|
|
guideSections,
|
|
media,
|
|
teamCompositions,
|
|
teamMembers,
|
|
weaponRecommendations,
|
|
} from "@/db/schema";
|
|
import { publicMediaUrl } from "@/lib/media/storage";
|
|
|
|
function catalogCharacterWithUrls(item: typeof catalogCharacters.$inferSelect) {
|
|
return {
|
|
...item,
|
|
imageUrl: publicMediaUrl(item.imageKey),
|
|
splashImageUrl: item.coopImageKey ? publicMediaUrl(item.coopImageKey) : null,
|
|
nameCardImageUrl: item.nameCardImageKey ? publicMediaUrl(item.nameCardImageKey) : null,
|
|
elementIconUrl: item.elementIconKey ? publicMediaUrl(item.elementIconKey) : null,
|
|
constellations: item.constellations.map((constellation) => ({
|
|
...constellation,
|
|
iconUrl: constellation.iconKey ? publicMediaUrl(constellation.iconKey) : null,
|
|
})),
|
|
talents: item.talents.map((talent) => ({
|
|
...talent,
|
|
iconUrl: talent.iconKey ? publicMediaUrl(talent.iconKey) : null,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export async function getActiveCatalog() {
|
|
const db = getDb();
|
|
const [characters, weapons, artifacts] = await Promise.all([
|
|
db.select().from(catalogCharacters).orderBy(asc(catalogCharacters.name)),
|
|
db.select().from(catalogWeapons).orderBy(asc(catalogWeapons.name)),
|
|
db.select().from(catalogArtifacts).orderBy(asc(catalogArtifacts.name)),
|
|
]);
|
|
if (!characters.length) return null;
|
|
return {
|
|
characters: characters.map(catalogCharacterWithUrls),
|
|
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, 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, 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, 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(inArray(catalogArtifacts.key, secondKeys)) : [];
|
|
const members = teams.length ? await getDb().select({ member: teamMembers, character: catalogCharacters }).from(teamMembers).innerJoin(catalogCharacters, eq(catalogCharacters.key, teamMembers.characterKey)).where(inArray(teamMembers.teamId, teams.map((team) => team.id))).orderBy(asc(teamMembers.position)) : [];
|
|
const memberWeaponKeys = members.map(({ member }) => member.weaponKey).filter((key): key is string => Boolean(key));
|
|
const memberArtifactKeys = members.map(({ member }) => member.artifactKey).filter((key): key is string => Boolean(key));
|
|
const [memberWeapons, memberArtifacts] = await Promise.all([
|
|
memberWeaponKeys.length ? getDb().select().from(catalogWeapons).where(inArray(catalogWeapons.key, memberWeaponKeys)) : [],
|
|
memberArtifactKeys.length ? getDb().select().from(catalogArtifacts).where(inArray(catalogArtifacts.key, memberArtifactKeys)) : [],
|
|
]);
|
|
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: catalogCharacterWithUrls(row.character),
|
|
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: constellations.map((recommendation) => {
|
|
const catalog = row.character.constellations.find((item) => item.level === recommendation.level);
|
|
return {
|
|
...recommendation,
|
|
name: catalog?.name ?? `C${recommendation.level}`,
|
|
detail: catalog?.description ?? recommendation.detail,
|
|
iconUrl: catalog?.iconKey ? publicMediaUrl(catalog.iconKey) : null,
|
|
};
|
|
}),
|
|
teams: teams.map((team) => ({ ...team, members: members.filter(({ member }) => member.teamId === team.id).map(({ member, character }) => { const weapon = memberWeapons.find((item) => item.key === member.weaponKey); const artifact = memberArtifacts.find((item) => item.key === member.artifactKey); return { ...member, character: catalogCharacterWithUrls(character), weapon: weapon ? { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } : null, artifact: artifact ? { ...artifact, imageUrl: publicMediaUrl(artifact.imageKey) } : null }; }) })),
|
|
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 getPublicTeamExport(teamId: string) {
|
|
const [row] = await getDb()
|
|
.select({ team: teamCompositions, guide: guides })
|
|
.from(teamCompositions)
|
|
.innerJoin(guides, eq(guides.id, teamCompositions.guideId))
|
|
.innerJoin(
|
|
guideSections,
|
|
and(
|
|
eq(guideSections.guideId, guides.id),
|
|
eq(guideSections.slug, "team"),
|
|
eq(guideSections.enabled, true),
|
|
),
|
|
)
|
|
.where(and(eq(teamCompositions.id, teamId), isNull(guides.trashedAt)))
|
|
.limit(1);
|
|
if (!row) return null;
|
|
|
|
const members = await getDb()
|
|
.select({ member: teamMembers, character: catalogCharacters })
|
|
.from(teamMembers)
|
|
.innerJoin(
|
|
catalogCharacters,
|
|
eq(catalogCharacters.key, teamMembers.characterKey),
|
|
)
|
|
.where(eq(teamMembers.teamId, teamId))
|
|
.orderBy(asc(teamMembers.position));
|
|
|
|
const weaponKeys = members.map(({ member }) => member.weaponKey).filter((key): key is string => Boolean(key));
|
|
const artifactKeys = members.map(({ member }) => member.artifactKey).filter((key): key is string => Boolean(key));
|
|
const [weapons, artifacts] = await Promise.all([
|
|
weaponKeys.length ? getDb().select().from(catalogWeapons).where(inArray(catalogWeapons.key, weaponKeys)) : [],
|
|
artifactKeys.length ? getDb().select().from(catalogArtifacts).where(inArray(catalogArtifacts.key, artifactKeys)) : [],
|
|
]);
|
|
|
|
return {
|
|
...row.team,
|
|
guideId: row.guide.id,
|
|
guideName: row.guide.name,
|
|
members: members.map(({ member, character }) => {
|
|
const weapon = weapons.find((item) => item.key === member.weaponKey);
|
|
const artifact = artifacts.find((item) => item.key === member.artifactKey);
|
|
return {
|
|
...member,
|
|
character: catalogCharacterWithUrls(character),
|
|
weapon: weapon ? { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } : null,
|
|
artifact: artifact ? { ...artifact, imageUrl: publicMediaUrl(artifact.imageKey) } : null,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
|
|
export async function getPublicWeaponExport(guideId: string) {
|
|
const [row] = await getDb()
|
|
.select({ guide: guides, note: guideSections.note })
|
|
.from(guides)
|
|
.innerJoin(
|
|
guideSections,
|
|
and(
|
|
eq(guideSections.guideId, guides.id),
|
|
eq(guideSections.slug, "weapon"),
|
|
eq(guideSections.enabled, true),
|
|
),
|
|
)
|
|
.where(and(eq(guides.id, guideId), isNull(guides.trashedAt)))
|
|
.limit(1);
|
|
if (!row) return null;
|
|
|
|
const weapons = await getDb()
|
|
.select({ recommendation: weaponRecommendations, weapon: catalogWeapons })
|
|
.from(weaponRecommendations)
|
|
.innerJoin(
|
|
catalogWeapons,
|
|
eq(catalogWeapons.key, weaponRecommendations.weaponKey),
|
|
)
|
|
.where(eq(weaponRecommendations.guideId, guideId))
|
|
.orderBy(asc(weaponRecommendations.sortOrder));
|
|
|
|
return {
|
|
guideName: row.guide.name,
|
|
note: row.note,
|
|
weapons: weapons.map(({ recommendation, weapon }) => ({
|
|
...recommendation,
|
|
weapon: { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) },
|
|
})),
|
|
};
|
|
}
|
|
|
|
export async function getPublicArtifactExport(guideId: string) {
|
|
const [row] = await getDb()
|
|
.select({ guide: guides, character: catalogCharacters })
|
|
.from(guides)
|
|
.innerJoin(
|
|
guideSections,
|
|
and(
|
|
eq(guideSections.guideId, guides.id),
|
|
eq(guideSections.slug, "artifact"),
|
|
eq(guideSections.enabled, true),
|
|
),
|
|
)
|
|
.innerJoin(
|
|
catalogCharacters,
|
|
eq(catalogCharacters.key, guides.characterKey),
|
|
)
|
|
.where(and(eq(guides.id, guideId), isNull(guides.trashedAt)))
|
|
.limit(1);
|
|
if (!row) return null;
|
|
|
|
const [groups, profile] = await Promise.all([
|
|
getDb().select().from(artifactGroups).where(eq(artifactGroups.guideId, guideId)).orderBy(asc(artifactGroups.sortOrder)),
|
|
getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, guideId)).limit(1),
|
|
]);
|
|
const recommendations = groups.length
|
|
? await getDb()
|
|
.select({ recommendation: artifactRecommendations, artifact: catalogArtifacts })
|
|
.from(artifactRecommendations)
|
|
.innerJoin(
|
|
catalogArtifacts,
|
|
eq(catalogArtifacts.key, artifactRecommendations.firstArtifactKey),
|
|
)
|
|
.where(inArray(artifactRecommendations.groupId, groups.map((group) => group.id)))
|
|
.orderBy(asc(artifactRecommendations.sortOrder))
|
|
: [];
|
|
const secondKeys = recommendations
|
|
.map(({ recommendation }) => recommendation.secondArtifactKey)
|
|
.filter((key): key is string => Boolean(key));
|
|
const secondArtifacts = secondKeys.length
|
|
? await getDb().select().from(catalogArtifacts).where(inArray(catalogArtifacts.key, secondKeys))
|
|
: [];
|
|
|
|
return {
|
|
guideName: row.guide.name,
|
|
character: catalogCharacterWithUrls(row.character),
|
|
groups: groups.map((group) => ({
|
|
...group,
|
|
rows: recommendations
|
|
.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,
|
|
};
|
|
}),
|
|
})),
|
|
profile: profile[0] ?? null,
|
|
};
|
|
}
|
|
|
|
export type PublicGuide = NonNullable<Awaited<ReturnType<typeof getPublicGuide>>>;
|