feat : 6 astra improve it
This commit is contained in:
+59
-19
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
|
||||
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
|
||||
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne, sql } from "drizzle-orm";
|
||||
import * as z from "zod";
|
||||
|
||||
import { getDb } from "@/db";
|
||||
@@ -322,7 +322,37 @@ export async function getGuideByCharacterKey(characterKey: string) {
|
||||
};
|
||||
}
|
||||
|
||||
async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
/** Navigation probes return presence flags, never the content of other sections. */
|
||||
async function populatedSections(guideId: string) {
|
||||
const rows = await getDb().select({
|
||||
section: guideSections,
|
||||
populated: sql<boolean>`case ${guideSections.kind}
|
||||
when 'weapon' then exists (select 1 from ${weaponRecommendations} r join ${weaponConditions} c on c.id = r.condition_id where c.guide_id = ${guideId})
|
||||
when 'artifact' then exists (select 1 from ${artifactRecommendations} r join ${artifactGroups} g on g.id = r.group_id join ${artifactConditions} c on c.id = g.condition_id where c.guide_id = ${guideId})
|
||||
or exists (select 1 from ${artifactProfiles} p where p.guide_id = ${guideId} and (
|
||||
p.sands <> '[]'::jsonb or p.goblet <> '[]'::jsonb or p.circlet <> '[]'::jsonb or p.substats <> '[]'::jsonb or p.talents <> '[]'::jsonb or p.targets <> '[]'::jsonb
|
||||
or exists (select 1 from jsonb_array_elements(p.recommended_conditions) c where c->'sands' <> '[]'::jsonb or c->'goblet' <> '[]'::jsonb or c->'circlet' <> '[]'::jsonb or c->'substats' <> '[]'::jsonb)
|
||||
or exists (select 1 from jsonb_array_elements(p.target_conditions) c where c->'rows' <> '[]'::jsonb)))
|
||||
when 'constellations' then exists (select 1 from ${constellationRecommendations} r join ${constellationConditions} c on c.id = r.condition_id where c.guide_id = ${guideId})
|
||||
when 'team' then exists (select 1 from ${teamCompositions} t where t.guide_id = ${guideId})
|
||||
else exists (select 1 from ${extraSectionRows} r where r.section_id = ${guideSections.id}) end`,
|
||||
}).from(guideSections).where(and(eq(guideSections.guideId, guideId), eq(guideSections.enabled, true)))
|
||||
.orderBy(asc(guideSections.sortOrder));
|
||||
return rows.filter((row) => row.populated).map((row) => row.section);
|
||||
}
|
||||
|
||||
export async function getPublicGuideSummary(slug: string) {
|
||||
const [row] = await getDb().select({
|
||||
id: guides.id, slug: guides.slug, name: guides.name, overview: guides.overview,
|
||||
characterKey: guides.characterKey, coverObjectKey: media.objectKey,
|
||||
}).from(guides).innerJoin(media, eq(media.id, guides.coverMediaId))
|
||||
.where(and(eq(guides.slug, slug), eq(guides.isPublic, true), isNull(guides.trashedAt))).limit(1);
|
||||
if (!row) return null;
|
||||
const { coverObjectKey, ...summary } = row;
|
||||
return { ...summary, coverUrl: publicMediaUrl(coverObjectKey), sections: await populatedSections(row.id) };
|
||||
}
|
||||
|
||||
async function getGuideContent(slug: string, publicOnly: boolean, selectedSection?: string) {
|
||||
const [row] = await getDb()
|
||||
.select({ guide: guides, cover: media, character: catalogCharacters })
|
||||
.from(guides)
|
||||
@@ -335,42 +365,45 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
))
|
||||
.limit(1);
|
||||
if (!row) return null;
|
||||
const availableSections = publicOnly ? await populatedSections(row.guide.id) : undefined;
|
||||
if (publicOnly && selectedSection !== "overview" && !availableSections?.some((section) => section.slug === selectedSection)) return null;
|
||||
const wants = (section: string) => !publicOnly || selectedSection === section;
|
||||
const materialKeys = [...new Set(
|
||||
[...row.character.ascension, ...row.character.leveling].flatMap((group) => group.items.map((item) => item.key)),
|
||||
)];
|
||||
const [sections, weaponConditionsRows, artifactConditionRows, profile, constellationGroups, teamConditionRows, teams, aliases, materials, poster, bleeding] = await runWithConcurrency([
|
||||
() => getDb().select().from(guideSections).where(and(eq(guideSections.guideId, row.guide.id), eq(guideSections.enabled, true))).orderBy(asc(guideSections.sortOrder)),
|
||||
() => getDb().select().from(weaponConditions).where(eq(weaponConditions.guideId, row.guide.id)).orderBy(asc(weaponConditions.sortOrder)),
|
||||
() => getDb().select().from(artifactConditions).where(eq(artifactConditions.guideId, row.guide.id)).orderBy(asc(artifactConditions.sortOrder)),
|
||||
() => getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, row.guide.id)).limit(1),
|
||||
() => getDb().select().from(constellationConditions).where(eq(constellationConditions.guideId, row.guide.id)).orderBy(asc(constellationConditions.sortOrder)),
|
||||
() => getDb().select().from(teamConditions).where(eq(teamConditions.guideId, row.guide.id)).orderBy(asc(teamConditions.sortOrder)),
|
||||
() => getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)),
|
||||
() => wants("weapon") ? getDb().select().from(weaponConditions).where(eq(weaponConditions.guideId, row.guide.id)).orderBy(asc(weaponConditions.sortOrder)) : Promise.resolve([]),
|
||||
() => wants("artifact") ? getDb().select().from(artifactConditions).where(eq(artifactConditions.guideId, row.guide.id)).orderBy(asc(artifactConditions.sortOrder)) : Promise.resolve([]),
|
||||
() => wants("artifact") ? getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, row.guide.id)).limit(1) : Promise.resolve([]),
|
||||
() => wants("constellations") ? getDb().select().from(constellationConditions).where(eq(constellationConditions.guideId, row.guide.id)).orderBy(asc(constellationConditions.sortOrder)) : Promise.resolve([]),
|
||||
() => wants("team") ? getDb().select().from(teamConditions).where(eq(teamConditions.guideId, row.guide.id)).orderBy(asc(teamConditions.sortOrder)) : Promise.resolve([]),
|
||||
() => wants("team") ? getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)) : Promise.resolve([]),
|
||||
() => getDb().select({
|
||||
kind: glossaryAliases.kind,
|
||||
catalogKey: glossaryAliases.catalogKey,
|
||||
alias: glossaryAliases.alias,
|
||||
}).from(glossaryAliases),
|
||||
() => materialKeys.length
|
||||
() => wants("overview") && materialKeys.length
|
||||
? getDb().select().from(catalogMaterials).where(inArray(catalogMaterials.key, materialKeys))
|
||||
: Promise.resolve([]),
|
||||
() => row.guide.posterArtMediaId
|
||||
() => wants("overview") && row.guide.posterArtMediaId
|
||||
? getDb().select().from(media).where(eq(media.id, row.guide.posterArtMediaId)).limit(1)
|
||||
: Promise.resolve([]),
|
||||
() => getDb().select({ layer: guidePosterBleeding, asset: media }).from(guidePosterBleeding).leftJoin(media, eq(media.id, guidePosterBleeding.mediaId)).where(eq(guidePosterBleeding.guideId, row.guide.id)).orderBy(asc(guidePosterBleeding.sortOrder)),
|
||||
() => wants("overview") ? getDb().select({ layer: guidePosterBleeding, asset: media }).from(guidePosterBleeding).leftJoin(media, eq(media.id, guidePosterBleeding.mediaId)).where(eq(guidePosterBleeding.guideId, row.guide.id)).orderBy(asc(guidePosterBleeding.sortOrder)) : Promise.resolve([]),
|
||||
]);
|
||||
const groups = artifactConditionRows.length ? await getDb().select().from(artifactGroups).where(inArray(artifactGroups.conditionId, artifactConditionRows.map((condition) => condition.id))).orderBy(asc(artifactGroups.sortOrder)) : [];
|
||||
const groupedAliases = aliasesByItem(aliases);
|
||||
const materialByKey = new Map(materials.map((item) => [item.key, item]));
|
||||
|
||||
const elementIconUrls = Object.fromEntries(
|
||||
(await getDb()
|
||||
(wants("artifact") ? await getDb()
|
||||
.select({
|
||||
element: catalogCharacters.element,
|
||||
elementIconKey: catalogCharacters.elementIconKey,
|
||||
})
|
||||
.from(catalogCharacters)
|
||||
.where(isNotNull(catalogCharacters.elementIconKey)))
|
||||
.where(isNotNull(catalogCharacters.elementIconKey)) : [])
|
||||
.flatMap((character) =>
|
||||
character.element && character.elementIconKey
|
||||
? [[character.element, publicMediaUrl(character.elementIconKey)] as const]
|
||||
@@ -391,7 +424,7 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
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 extraIds = sections.filter((section) => section.kind === "extra" && wants(section.slug)).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)) : [];
|
||||
const extraCharacterKeys = [...new Set(extraRows.flatMap(({ row: extraRow }) => extraRow.catalogItems.filter((item) => item.kind === "character").map((item) => item.key)))];
|
||||
const extraWeaponKeys = [...new Set(extraRows.flatMap(({ row: extraRow }) => extraRow.catalogItems.filter((item) => item.kind === "weapon").map((item) => item.key)))];
|
||||
@@ -402,7 +435,11 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
extraArtifactKeys.length ? getDb().select().from(catalogArtifacts).where(inArray(catalogArtifacts.key, extraArtifactKeys)) : [],
|
||||
]);
|
||||
const extraCatalogs = { character: extraCharacters, weapon: extraWeapons, artifact: extraArtifacts };
|
||||
const hydratedTeams = 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); const secondArtifact = memberArtifacts.find((item) => item.key === member.secondArtifactKey); return { ...member, character: { ...catalogCharacterWithUrls(character), aliases: groupedAliases.character.get(character.key) ?? [] }, weapon: weapon ? { ...weapon, aliases: groupedAliases.weapon.get(weapon.key) ?? [], imageUrl: publicMediaUrl(weapon.imageKey) } : null, artifact: artifact ? { ...artifact, aliases: groupedAliases.artifact.get(artifact.key) ?? [], imageUrl: publicMediaUrl(artifact.imageKey) } : null, secondArtifact: secondArtifact ? { ...secondArtifact, aliases: groupedAliases.artifact.get(secondArtifact.key) ?? [], imageUrl: publicMediaUrl(secondArtifact.imageKey) } : null }; }) }));
|
||||
const memberWeaponByKey = new Map(memberWeapons.map((item) => [item.key, item]));
|
||||
const memberArtifactByKey = new Map(memberArtifacts.map((item) => [item.key, item]));
|
||||
const secondArtifactByKey = new Map(secondArtifacts.map((item) => [item.key, item]));
|
||||
const membersByTeam = Map.groupBy(members, ({ member }) => member.teamId);
|
||||
const hydratedTeams = teams.map((team) => ({ ...team, members: (membersByTeam.get(team.id) ?? []).map(({ member, character }) => { const weapon = memberWeaponByKey.get(member.weaponKey ?? ""); const artifact = memberArtifactByKey.get(member.artifactKey ?? ""); const secondArtifact = memberArtifactByKey.get(member.secondArtifactKey ?? ""); return { ...member, character: { ...catalogCharacterWithUrls(character), aliases: groupedAliases.character.get(character.key) ?? [] }, weapon: weapon ? { ...weapon, aliases: groupedAliases.weapon.get(weapon.key) ?? [], imageUrl: publicMediaUrl(weapon.imageKey) } : null, artifact: artifact ? { ...artifact, aliases: groupedAliases.artifact.get(artifact.key) ?? [], imageUrl: publicMediaUrl(artifact.imageKey) } : null, secondArtifact: secondArtifact ? { ...secondArtifact, aliases: groupedAliases.artifact.get(secondArtifact.key) ?? [], imageUrl: publicMediaUrl(secondArtifact.imageKey) } : null }; }) }));
|
||||
|
||||
return {
|
||||
...row.guide,
|
||||
@@ -413,9 +450,10 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
character: { ...catalogCharacterWithUrls(row.character, materialByKey), aliases: groupedAliases.character.get(row.character.key) ?? [] },
|
||||
elementIconUrls,
|
||||
sections,
|
||||
availableSections,
|
||||
weapons: weaponRows.map(({ recommendation, weapon }) => ({ ...recommendation, weapon: { ...weapon, aliases: groupedAliases.weapon.get(weapon.key) ?? [], imageUrl: publicMediaUrl(weapon.imageKey) } })),
|
||||
weaponGroups: weaponConditionsRows.map((condition) => ({ ...condition, rows: weaponRows.filter(({ recommendation }) => recommendation.conditionId === condition.id).map(({ recommendation, weapon }) => ({ ...recommendation, weapon: { ...weapon, aliases: groupedAliases.weapon.get(weapon.key) ?? [], imageUrl: publicMediaUrl(weapon.imageKey) } })) })),
|
||||
artifactGroups: artifactConditionRows.map((condition) => ({ ...condition, tiers: groups.filter((group) => group.conditionId === condition.id).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, aliases: groupedAliases.artifact.get(artifact.key) ?? [], imageUrl: publicMediaUrl(artifact.imageKey) }, second: second ? { ...second, aliases: groupedAliases.artifact.get(second.key) ?? [], imageUrl: publicMediaUrl(second.imageKey) } : null }; }) })) })),
|
||||
artifactGroups: artifactConditionRows.map((condition) => ({ ...condition, tiers: groups.filter((group) => group.conditionId === condition.id).map((group) => ({ ...group, rows: artifactRows.filter(({ recommendation }) => recommendation.groupId === group.id).map(({ recommendation, artifact }) => { const second = secondArtifactByKey.get(recommendation.secondArtifactKey ?? ""); return { ...recommendation, first: { ...artifact, aliases: groupedAliases.artifact.get(artifact.key) ?? [], imageUrl: publicMediaUrl(artifact.imageKey) }, second: second ? { ...second, aliases: groupedAliases.artifact.get(second.key) ?? [], imageUrl: publicMediaUrl(second.imageKey) } : null }; }) })) })),
|
||||
artifactProfile: profile[0] ? { ...profile[0], recommendedConditions: profile[0].recommendedConditions.length ? profile[0].recommendedConditions : [{ name: null, note: null, sands: profile[0].sands, goblet: profile[0].goblet, circlet: profile[0].circlet, substats: profile[0].substats }], targetConditions: profile[0].targetConditions.length ? profile[0].targetConditions : [{ name: null, note: null, rows: profile[0].targets }] } : null,
|
||||
constellationGroups: constellationGroups.map((group) => ({
|
||||
...group,
|
||||
@@ -431,7 +469,7 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
})),
|
||||
teams: hydratedTeams,
|
||||
teamGroups: teamConditionRows.length ? teamConditionRows.map((condition) => ({ ...condition, teams: hydratedTeams.filter((team) => team.conditionId === condition.id) })) : [{ id: "team-condition-default", guideId: row.guide.id, name: null, note: null, sortOrder: 0, teams: hydratedTeams }],
|
||||
extras: sections.filter((section) => section.kind === "extra").map((section) => ({ ...section, rows: extraRows.filter(({ row: extraRow }) => extraRow.sectionId === section.id).map(({ row: extraRow, asset }) => ({
|
||||
extras: sections.filter((section) => section.kind === "extra" && wants(section.slug)).map((section) => ({ ...section, rows: extraRows.filter(({ row: extraRow }) => extraRow.sectionId === section.id).map(({ row: extraRow, asset }) => ({
|
||||
...extraRow,
|
||||
catalogItems: extraRow.catalogItems.map((reference) => {
|
||||
const item = extraCatalogs[reference.kind].find((candidate) => candidate.key === reference.key);
|
||||
@@ -450,8 +488,8 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getPublicGuide(slug: string) {
|
||||
return getGuideContent(slug, true);
|
||||
export function getPublicGuide(slug: string, section = "overview") {
|
||||
return getGuideContent(slug, true, section);
|
||||
}
|
||||
|
||||
export function getAdminGuideContent(slug: string) {
|
||||
@@ -468,3 +506,5 @@ export async function getAdminGuideContentById(guideId: string) {
|
||||
}
|
||||
|
||||
export type PublicGuide = NonNullable<Awaited<ReturnType<typeof getPublicGuide>>>;
|
||||
|
||||
export type PublicGuideSummary = NonNullable<Awaited<ReturnType<typeof getPublicGuideSummary>>>;
|
||||
|
||||
Reference in New Issue
Block a user