feat : update
This commit is contained in:
+50
-4
@@ -161,16 +161,36 @@ async function advanceGuideVersion(tx: Parameters<Parameters<ReturnType<typeof g
|
||||
return guide;
|
||||
}
|
||||
|
||||
async function syncConditionIconReferences(
|
||||
tx: Parameters<Parameters<ReturnType<typeof getDb>["transaction"]>[0]>[0],
|
||||
previous: Array<string | null | undefined>,
|
||||
next: Array<string | null | undefined>,
|
||||
) {
|
||||
const nextIds = [...new Set(next.filter((id): id is string => Boolean(id)))];
|
||||
if (nextIds.length) {
|
||||
const ready = await tx.select({ id: media.id }).from(media).where(and(inArray(media.id, nextIds), eq(media.status, "ready")));
|
||||
if (ready.length !== nextIds.length) throw new Error("ไม่พบภาพ Condition ที่อัปโหลดแล้ว");
|
||||
}
|
||||
const changes = new Map<string, number>();
|
||||
for (const id of previous) if (id) changes.set(id, (changes.get(id) ?? 0) - 1);
|
||||
for (const id of next) if (id) changes.set(id, (changes.get(id) ?? 0) + 1);
|
||||
for (const [id, change] of changes) {
|
||||
if (change) await tx.update(media).set({ currentReferenceCount: sql`greatest(${media.currentReferenceCount} + ${change}, 0)` }).where(eq(media.id, id));
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveWeapons(guideId: string, input: unknown, context: GuideMutationContext) {
|
||||
const data = weaponSectionSchema.parse(input);
|
||||
const [guide] = await getDb().select().from(guides).where(eq(guides.id, guideId)).limit(1);
|
||||
if (!guide) throw new Error("ไม่พบ Guide");
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion, false, "weapon", invalidationDetails(context));
|
||||
const previous = await tx.select({ iconMediaId: weaponConditions.iconMediaId }).from(weaponConditions).where(eq(weaponConditions.guideId, guideId));
|
||||
await syncConditionIconReferences(tx, previous.map((group) => group.iconMediaId), data.groups.map((group) => group.iconMediaId));
|
||||
await tx.update(guideSections).set({ note: data.note }).where(and(eq(guideSections.guideId, guideId), eq(guideSections.slug, "weapon")));
|
||||
await tx.delete(weaponConditions).where(eq(weaponConditions.guideId, guideId));
|
||||
for (const [sortOrder, group] of data.groups.entries()) {
|
||||
const [created] = await tx.insert(weaponConditions).values({ guideId, name: group.name, note: group.note, sortOrder }).returning();
|
||||
const [created] = await tx.insert(weaponConditions).values({ guideId, name: group.name, note: group.note, iconMediaId: group.iconMediaId, sortOrder }).returning();
|
||||
if (group.rows.length) await tx.insert(weaponRecommendations).values(group.rows.map((row, rowOrder) => ({ ...row, refinement: row.refinement, overallPercent: row.overallPercent?.toString() ?? null, personalPercent: row.personalPercent?.toString() ?? null, conditionId: created.id, sortOrder: rowOrder })));
|
||||
}
|
||||
await auditGuide(tx, context, "guide.weapon.saved", updated, "weapon");
|
||||
@@ -259,9 +279,20 @@ export async function saveArtifacts(guideId: string, input: unknown, context: Gu
|
||||
if (!guide) throw new Error("ไม่พบ Guide");
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion, false, "artifact", invalidationDetails(context));
|
||||
const previousConditions = await tx.select({ iconMediaId: artifactConditions.iconMediaId }).from(artifactConditions).where(eq(artifactConditions.guideId, guideId));
|
||||
const [previousProfile] = await tx.select({ recommendedConditions: artifactProfiles.recommendedConditions, targetConditions: artifactProfiles.targetConditions }).from(artifactProfiles).where(eq(artifactProfiles.guideId, guideId)).limit(1);
|
||||
await syncConditionIconReferences(tx, [
|
||||
...previousConditions.map((group) => group.iconMediaId),
|
||||
...(previousProfile?.recommendedConditions ?? []).map((group) => group.iconMediaId),
|
||||
...(previousProfile?.targetConditions ?? []).map((group) => group.iconMediaId),
|
||||
], [
|
||||
...data.groups.map((group) => group.iconMediaId),
|
||||
...(data.recommendedGroups ?? []).map((group) => group.iconMediaId),
|
||||
...data.targetGroups.map((group) => group.iconMediaId),
|
||||
]);
|
||||
await tx.delete(artifactConditions).where(eq(artifactConditions.guideId, guideId));
|
||||
for (const [conditionOrder, condition] of data.groups.entries()) {
|
||||
const [createdCondition] = await tx.insert(artifactConditions).values({ guideId, name: condition.name, note: condition.note, sortOrder: conditionOrder }).returning();
|
||||
const [createdCondition] = await tx.insert(artifactConditions).values({ guideId, name: condition.name, note: condition.note, iconMediaId: condition.iconMediaId, sortOrder: conditionOrder }).returning();
|
||||
for (const [sortOrder, group] of condition.tiers.entries()) {
|
||||
const [created] = await tx.insert(artifactGroups).values({ conditionId: createdCondition.id, name: group.name, sortOrder }).returning();
|
||||
if (group.rows.length) await tx.insert(artifactRecommendations).values(group.rows.map((row, rowOrder) => ({ ...row, percent: row.percent.toString(), groupId: created.id, sortOrder: rowOrder })));
|
||||
@@ -277,10 +308,12 @@ export async function saveConstellations(guideId: string, input: unknown, contex
|
||||
const data = constellationSectionSchema.parse(input);
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion, false, "constellations", invalidationDetails(context));
|
||||
const previous = await tx.select({ iconMediaId: constellationConditions.iconMediaId }).from(constellationConditions).where(eq(constellationConditions.guideId, guideId));
|
||||
await syncConditionIconReferences(tx, previous.map((group) => group.iconMediaId), data.groups.map((group) => group.iconMediaId));
|
||||
await tx.update(guideSections).set({ note: data.note }).where(and(eq(guideSections.guideId, guideId), eq(guideSections.slug, "constellations")));
|
||||
await tx.delete(constellationConditions).where(eq(constellationConditions.guideId, guideId));
|
||||
for (const [sortOrder, group] of data.groups.entries()) {
|
||||
const [created] = await tx.insert(constellationConditions).values({ guideId, name: group.name, note: group.note, sortOrder }).returning();
|
||||
const [created] = await tx.insert(constellationConditions).values({ guideId, name: group.name, note: group.note, iconMediaId: group.iconMediaId, sortOrder }).returning();
|
||||
if (group.rows.length) await tx.insert(constellationRecommendations).values(group.rows.map((row) => ({ ...row, cumulativePercent: row.cumulativePercent.toString(), conditionId: created.id })));
|
||||
}
|
||||
await auditGuide(tx, context, "guide.constellations.saved", updated, "constellations");
|
||||
@@ -313,9 +346,11 @@ export async function saveTeams(guideId: string, input: unknown, context: GuideM
|
||||
}
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion, false, "team", invalidationDetails(context));
|
||||
const previous = await tx.select({ iconMediaId: teamConditions.iconMediaId }).from(teamConditions).where(eq(teamConditions.guideId, guideId));
|
||||
await syncConditionIconReferences(tx, previous.map((group) => group.iconMediaId), data.groups.map((group) => group.iconMediaId));
|
||||
await tx.delete(teamConditions).where(eq(teamConditions.guideId, guideId));
|
||||
for (const [groupOrder, group] of data.groups.entries()) {
|
||||
const [condition] = await tx.insert(teamConditions).values({ guideId, name: group.name, note: group.note, sortOrder: groupOrder }).returning();
|
||||
const [condition] = await tx.insert(teamConditions).values({ guideId, name: group.name, note: group.note, iconMediaId: group.iconMediaId, sortOrder: groupOrder }).returning();
|
||||
for (const [sortOrder, team] of group.teams.entries()) {
|
||||
const [created] = await tx.insert(teamCompositions).values({ guideId, conditionId: condition.id, title: team.title, rotationSeconds: team.rotationSeconds.toString(), combo: team.combo, sortOrder }).returning();
|
||||
await tx.insert(teamMembers).values(team.members.map((member, position) => ({ ...member, teamId: created.id, position, damage: member.damage.toString() })));
|
||||
@@ -583,7 +618,18 @@ export async function permanentlyDeleteGuide(
|
||||
const [guide] = await tx.select().from(guides).where(eq(guides.id, guideId)).limit(1);
|
||||
if (!guide?.trashedAt) throw new Error("Guide ต้องอยู่ในถังขยะก่อนลบถาวร");
|
||||
const bleeding = await tx.select({ mediaId: guidePosterBleeding.mediaId }).from(guidePosterBleeding).where(eq(guidePosterBleeding.guideId, guideId));
|
||||
const weapons = await tx.select({ iconMediaId: weaponConditions.iconMediaId }).from(weaponConditions).where(eq(weaponConditions.guideId, guideId));
|
||||
const artifacts = await tx.select({ iconMediaId: artifactConditions.iconMediaId }).from(artifactConditions).where(eq(artifactConditions.guideId, guideId));
|
||||
const constellations = await tx.select({ iconMediaId: constellationConditions.iconMediaId }).from(constellationConditions).where(eq(constellationConditions.guideId, guideId));
|
||||
const teams = await tx.select({ iconMediaId: teamConditions.iconMediaId }).from(teamConditions).where(eq(teamConditions.guideId, guideId));
|
||||
const profiles = await tx.select({ recommendedConditions: artifactProfiles.recommendedConditions, targetConditions: artifactProfiles.targetConditions }).from(artifactProfiles).where(eq(artifactProfiles.guideId, guideId));
|
||||
const conditionIconIds = [
|
||||
...weapons, ...artifacts, ...constellations, ...teams,
|
||||
...(profiles[0]?.recommendedConditions ?? []),
|
||||
...(profiles[0]?.targetConditions ?? []),
|
||||
].map((condition) => condition.iconMediaId);
|
||||
await tx.delete(guides).where(eq(guides.id, guideId));
|
||||
await syncConditionIconReferences(tx, conditionIconIds, []);
|
||||
await tx.update(media).set({ currentReferenceCount: sql`greatest(${media.currentReferenceCount} - 1, 0)` }).where(eq(media.id, guide.coverMediaId));
|
||||
if (guide.posterArtMediaId) {
|
||||
await tx.update(media).set({ currentReferenceCount: sql`greatest(${media.currentReferenceCount} - 1, 0)` }).where(eq(media.id, guide.posterArtMediaId));
|
||||
|
||||
@@ -25,9 +25,9 @@ vi.mock("@/db", () => ({
|
||||
}));
|
||||
|
||||
import {
|
||||
artifactConditions, constellationConditions, extraSectionRows,
|
||||
artifactConditions, artifactProfiles, constellationConditions, extraSectionRows,
|
||||
guides, guideSections, teamConditions, weaponConditions,
|
||||
catalogMaterials, guidePosterBleeding,
|
||||
catalogMaterials, guidePosterBleeding, media,
|
||||
} from "@/db/schema";
|
||||
import { getAdminGuideContent } from "./queries";
|
||||
|
||||
@@ -78,4 +78,27 @@ describe("admin section reads", () => {
|
||||
}
|
||||
expect(result?.extras).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("resolves condition icons from one batched media read", async () => {
|
||||
const iconMediaId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
mocks.rows.set(weaponConditions, [{ id: "weapon-condition", iconMediaId }]);
|
||||
mocks.rows.set(artifactConditions, [{ id: "artifact-condition", iconMediaId }]);
|
||||
mocks.rows.set(constellationConditions, [{ id: "constellation-condition", iconMediaId }]);
|
||||
mocks.rows.set(teamConditions, [{ id: "team-condition", iconMediaId }]);
|
||||
mocks.rows.set(artifactProfiles, [{
|
||||
sands: [], goblet: [], circlet: [], substats: [], targets: [],
|
||||
recommendedConditions: [{ name: "Recommended", note: null, iconMediaId, sands: [], goblet: [], circlet: [], substats: [] }],
|
||||
targetConditions: [{ name: "Target", note: null, iconMediaId, rows: [] }],
|
||||
}]);
|
||||
mocks.rows.set(media, [{ id: iconMediaId, objectKey: "condition-icon.png" }]);
|
||||
|
||||
const result = await getAdminGuideContent("test");
|
||||
expect(result?.weaponGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(result?.artifactGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(result?.constellationGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(result?.teamGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(result?.artifactProfile?.recommendedConditions[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(result?.artifactProfile?.targetConditions[0].iconUrl).toBe("/media/condition-icon.png");
|
||||
expect(mocks.reads.filter((table) => table === media)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
+13
-4
@@ -425,7 +425,15 @@ async function getGuideContent(slug: string, publicOnly: boolean, selectedSectio
|
||||
: Promise.resolve([]),
|
||||
() => 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 iconIds = [...new Set([
|
||||
...weaponConditionsRows, ...artifactConditionRows, ...constellationGroups, ...teamConditionRows,
|
||||
...(profile[0]?.recommendedConditions ?? []), ...(profile[0]?.targetConditions ?? []),
|
||||
].map((condition) => condition.iconMediaId).filter((id): id is string => Boolean(id)))];
|
||||
const iconAssetsPromise = iconIds.length ? getDb().select({ id: media.id, objectKey: media.objectKey }).from(media).where(and(inArray(media.id, iconIds), eq(media.status, "ready"))) : 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 iconAssets = await iconAssetsPromise;
|
||||
const iconUrls = new Map(iconAssets.map((asset) => [asset.id, publicMediaUrl(asset.objectKey)]));
|
||||
const iconUrl = (id: string | null | undefined) => id ? iconUrls.get(id) ?? null : null;
|
||||
const groupedAliases = aliasesByItem(aliases);
|
||||
const materialByKey = new Map(materials.map((item) => [item.key, item]));
|
||||
|
||||
@@ -485,11 +493,12 @@ async function getGuideContent(slug: string, publicOnly: boolean, selectedSectio
|
||||
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 = 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,
|
||||
weaponGroups: weaponConditionsRows.map((condition) => ({ ...condition, iconUrl: iconUrl(condition.iconMediaId), 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, iconUrl: iconUrl(condition.iconMediaId), 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, iconMediaId: null, sands: profile[0].sands, goblet: profile[0].goblet, circlet: profile[0].circlet, substats: profile[0].substats }]).map((condition) => ({ ...condition, iconMediaId: condition.iconMediaId ?? null, iconUrl: iconUrl(condition.iconMediaId) })), targetConditions: (profile[0].targetConditions.length ? profile[0].targetConditions : [{ name: null, note: null, iconMediaId: null, rows: profile[0].targets }]).map((condition) => ({ ...condition, iconMediaId: condition.iconMediaId ?? null, iconUrl: iconUrl(condition.iconMediaId) })) } : null,
|
||||
constellationGroups: constellationGroups.map((group) => ({
|
||||
...group,
|
||||
iconUrl: iconUrl(group.iconMediaId),
|
||||
rows: constellations.filter((recommendation) => recommendation.conditionId === group.id).map((recommendation) => {
|
||||
const catalog = row.character.constellations.find((item) => item.level === recommendation.level);
|
||||
return {
|
||||
@@ -502,7 +511,7 @@ async function getGuideContent(slug: string, publicOnly: boolean, selectedSectio
|
||||
}),
|
||||
})),
|
||||
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 }],
|
||||
teamGroups: teamConditionRows.length ? teamConditionRows.map((condition) => ({ ...condition, iconUrl: iconUrl(condition.iconMediaId), teams: hydratedTeams.filter((team) => team.conditionId === condition.id) })) : [{ id: "team-condition-default", guideId: row.guide.id, name: null, note: null, iconMediaId: null, iconUrl: null, sortOrder: 0, teams: hydratedTeams }],
|
||||
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) => {
|
||||
|
||||
@@ -78,6 +78,16 @@ describe("structured guide validation", () => {
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: null, groups: [{ name: "x".repeat(121), note: null, rows: [row] }] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts optional condition icons and rejects invalid media IDs", () => {
|
||||
const iconMediaId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
const weapon = { expectedVersion: 1, note: null, groups: [{ name: "DPS", note: null, iconMediaId, rows: [] }] };
|
||||
expect(weaponSectionSchema.parse(weapon).groups[0].iconMediaId).toBe(iconMediaId);
|
||||
expect(weaponSectionSchema.parse({ ...weapon, groups: [{ name: null, note: null, rows: [] }] }).groups[0].iconMediaId).toBeNull();
|
||||
expect(weaponSectionSchema.safeParse({ ...weapon, groups: [{ ...weapon.groups[0], iconMediaId: "bad" }] }).success).toBe(false);
|
||||
expect(constellationSectionSchema.parse({ expectedVersion: 1, note: null, groups: [{ name: "C1", note: null, iconMediaId, rows: [] }] }).groups[0].iconMediaId).toBe(iconMediaId);
|
||||
expect(teamSectionSchema.parse({ expectedVersion: 1, groups: [{ name: "Team", note: null, iconMediaId, teams: [] }] }).groups[0].iconMediaId).toBe(iconMediaId);
|
||||
});
|
||||
|
||||
it("limits weapon and constellation header notes", () => {
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: "Header note", groups: [] }).success).toBe(true);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: "x".repeat(2_001), groups: [] }).success).toBe(false);
|
||||
|
||||
@@ -10,6 +10,7 @@ export type GuideSectionKind = (typeof guideSectionKinds)[number];
|
||||
|
||||
const percent = z.coerce.number().finite().min(-9999).max(9999);
|
||||
const conditionName = z.string().trim().max(120).nullable().optional().transform((value) => value || null);
|
||||
const conditionIconMediaId = z.string().uuid().nullable().optional().transform((value) => value ?? null);
|
||||
const catalogKey = z.string().trim().min(1).max(64);
|
||||
export const weaponRefinementSchema = z.union([
|
||||
z.number().int().min(1).max(5).transform(String),
|
||||
@@ -56,6 +57,7 @@ export const weaponSectionSchema = z.strictObject({
|
||||
groups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
rows: z.array(z.strictObject({
|
||||
weaponKey: catalogKey,
|
||||
refinement: weaponRefinementSchema,
|
||||
@@ -93,6 +95,7 @@ export const artifactSectionSchema = z.strictObject({
|
||||
groups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
tiers: z.tuple([
|
||||
artifactGroupSchema("Late Game"),
|
||||
artifactGroupSchema("Early Game"),
|
||||
@@ -105,6 +108,7 @@ export const artifactSectionSchema = z.strictObject({
|
||||
recommendedGroups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
sands: z.array(z.enum(mainStatOptions.sands)),
|
||||
goblet: z.array(z.enum(mainStatOptions.goblet)),
|
||||
circlet: z.array(z.enum(mainStatOptions.circlet)),
|
||||
@@ -113,6 +117,7 @@ export const artifactSectionSchema = z.strictObject({
|
||||
targetGroups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
rows: z.array(z.strictObject({
|
||||
stat: z.enum(targetStatOptions),
|
||||
value: z.string().trim().min(1).max(80),
|
||||
@@ -142,6 +147,7 @@ export const constellationSectionSchema = z.strictObject({
|
||||
groups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
rows: constellationRowsSchema,
|
||||
})).max(20),
|
||||
});
|
||||
@@ -151,6 +157,7 @@ export const teamSectionSchema = z.strictObject({
|
||||
groups: z.array(z.strictObject({
|
||||
name: conditionName,
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
iconMediaId: conditionIconMediaId,
|
||||
teams: z.array(z.strictObject({
|
||||
title: z.string().trim().min(1).max(160),
|
||||
rotationSeconds: z.coerce.number().finite().positive().max(9999),
|
||||
|
||||
Reference in New Issue
Block a user