feat(extra) : add catalog items and image export
This commit is contained in:
+25
-1
@@ -148,6 +148,15 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
]);
|
||||
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)) : [];
|
||||
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)))];
|
||||
const extraArtifactKeys = [...new Set(extraRows.flatMap(({ row: extraRow }) => extraRow.catalogItems.filter((item) => item.kind === "artifact").map((item) => item.key)))];
|
||||
const [extraCharacters, extraWeapons, extraArtifacts] = await Promise.all([
|
||||
extraCharacterKeys.length ? getDb().select().from(catalogCharacters).where(inArray(catalogCharacters.key, extraCharacterKeys)) : [],
|
||||
extraWeaponKeys.length ? getDb().select().from(catalogWeapons).where(inArray(catalogWeapons.key, extraWeaponKeys)) : [],
|
||||
extraArtifactKeys.length ? getDb().select().from(catalogArtifacts).where(inArray(catalogArtifacts.key, extraArtifactKeys)) : [],
|
||||
]);
|
||||
const extraCatalogs = { character: extraCharacters, weapon: extraWeapons, artifact: extraArtifacts };
|
||||
|
||||
return {
|
||||
...row.guide,
|
||||
@@ -172,7 +181,22 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
||||
}),
|
||||
})),
|
||||
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); const secondArtifact = memberArtifacts.find((item) => item.key === member.secondArtifactKey); return { ...member, character: catalogCharacterWithUrls(character), weapon: weapon ? { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) } : null, artifact: artifact ? { ...artifact, imageUrl: publicMediaUrl(artifact.imageKey) } : null, secondArtifact: secondArtifact ? { ...secondArtifact, imageUrl: publicMediaUrl(secondArtifact.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, imageWidth: asset?.width ?? null, imageHeight: asset?.height ?? 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,
|
||||
catalogItems: extraRow.catalogItems.map((reference) => {
|
||||
const item = extraCatalogs[reference.kind].find((candidate) => candidate.key === reference.key);
|
||||
return {
|
||||
...reference,
|
||||
name: item?.name ?? reference.key,
|
||||
imageUrl: item ? publicMediaUrl(item.imageKey) : null,
|
||||
rarity: item?.rarity ?? null,
|
||||
available: Boolean(item),
|
||||
};
|
||||
}),
|
||||
imageUrl: asset ? publicMediaUrl(asset.objectKey) : null,
|
||||
imageWidth: asset?.width ?? null,
|
||||
imageHeight: asset?.height ?? null,
|
||||
})) })),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,33 @@ describe("structured guide validation", () => {
|
||||
expect(result.data.sections[0].rows[0].body).toBe("<p><strong>Safe</strong></p>");
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps ordered mixed catalog items in Extra rows and rejects duplicates", () => {
|
||||
const row = {
|
||||
mediaId: null,
|
||||
heading: "Build options",
|
||||
body: "",
|
||||
percent: null,
|
||||
backgroundColor: null,
|
||||
highlights: [],
|
||||
catalogItems: [
|
||||
{ kind: "character", key: "10000089" },
|
||||
{ kind: "weapon", key: "WEAPON_CLAYMORE" },
|
||||
{ kind: "artifact", key: "ARTIFACT_SET" },
|
||||
],
|
||||
} as const;
|
||||
const result = extraSectionSchema.safeParse({
|
||||
expectedVersion: 1,
|
||||
sections: [{ title: "Extra", rows: [row] }],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) expect(result.data.sections[0].rows[0].catalogItems).toEqual(row.catalogItems);
|
||||
expect(extraSectionSchema.safeParse({
|
||||
expectedVersion: 1,
|
||||
sections: [{ title: "Extra", rows: [{ ...row, catalogItems: [row.catalogItems[0], row.catalogItems[0]] }] }],
|
||||
}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Team Calc", () => {
|
||||
|
||||
@@ -140,6 +140,13 @@ const extraRowSchema = z.strictObject({
|
||||
body: z.string().trim().max(10_000),
|
||||
percent: percent.nullable(),
|
||||
backgroundColor: hexColor.nullable().default(null),
|
||||
catalogItems: z.array(z.strictObject({
|
||||
kind: z.enum(["character", "weapon", "artifact"]),
|
||||
key: z.string().trim().min(1).max(64),
|
||||
})).max(30).refine(
|
||||
(items) => new Set(items.map((item) => `${item.kind}:${item.key}`)).size === items.length,
|
||||
"รายการตัวละคร อาวุธ และอาร์ติแฟกต์ต้องไม่ซ้ำกัน",
|
||||
).default([]),
|
||||
highlights: z.array(z.strictObject({
|
||||
text: z.string().trim().min(1).max(80),
|
||||
color: hexColor,
|
||||
|
||||
Reference in New Issue
Block a user