feat: add guide section header notes
This commit is contained in:
@@ -92,6 +92,7 @@ export async function saveWeapons(guideId: string, input: unknown) {
|
||||
if (!guide) throw new Error("ไม่พบ Guide");
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion);
|
||||
await tx.update(guideSections).set({ note: data.note }).where(and(eq(guideSections.guideId, guideId), eq(guideSections.slug, "weapon")));
|
||||
await tx.delete(weaponRecommendations).where(eq(weaponRecommendations.guideId, guideId));
|
||||
if (data.rows.length) await tx.insert(weaponRecommendations).values(data.rows.map((row, sortOrder) => ({ ...row, overallPercent: row.overallPercent?.toString() ?? null, personalPercent: row.personalPercent?.toString() ?? null, guideId, sortOrder })));
|
||||
return updated;
|
||||
@@ -135,6 +136,7 @@ export async function saveConstellations(guideId: string, input: unknown) {
|
||||
const data = constellationSectionSchema.parse(input);
|
||||
return getDb().transaction(async (tx) => {
|
||||
const updated = await advanceGuideVersion(tx, guideId, data.expectedVersion);
|
||||
await tx.update(guideSections).set({ note: data.note }).where(and(eq(guideSections.guideId, guideId), eq(guideSections.slug, "constellations")));
|
||||
await tx.delete(constellationRecommendations).where(eq(constellationRecommendations.guideId, guideId));
|
||||
if (data.rows.length) await tx.insert(constellationRecommendations).values(data.rows.map((row) => ({ ...row, cumulativePercent: row.cumulativePercent.toString(), guideId })));
|
||||
return updated;
|
||||
|
||||
@@ -182,7 +182,7 @@ export async function getPublicTeamExport(teamId: string) {
|
||||
|
||||
export async function getPublicWeaponExport(guideId: string) {
|
||||
const [row] = await getDb()
|
||||
.select({ guide: guides })
|
||||
.select({ guide: guides, note: guideSections.note })
|
||||
.from(guides)
|
||||
.innerJoin(
|
||||
guideSections,
|
||||
@@ -208,6 +208,7 @@ export async function getPublicWeaponExport(guideId: string) {
|
||||
|
||||
return {
|
||||
guideName: row.guide.name,
|
||||
note: row.note,
|
||||
weapons: weapons.map(({ recommendation, weapon }) => ({
|
||||
...recommendation,
|
||||
weapon: { ...weapon, imageUrl: publicMediaUrl(weapon.imageKey) },
|
||||
|
||||
@@ -13,14 +13,21 @@ describe("structured guide validation", () => {
|
||||
});
|
||||
|
||||
it("requires at least one weapon percentage", () => {
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, rows: [{ weaponKey: "1", refinement: 5, overallPercent: null, personalPercent: null, note: null }] }).success).toBe(false);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, rows: [{ weaponKey: "1", refinement: 5, overallPercent: 104, personalPercent: null, note: "Best free-to-play option" }] }).success).toBe(true);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [{ weaponKey: "1", refinement: 5, overallPercent: null, personalPercent: null, note: null }] }).success).toBe(false);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [{ weaponKey: "1", refinement: 5, overallPercent: 104, personalPercent: null, note: "Best free-to-play option" }] }).success).toBe(true);
|
||||
});
|
||||
|
||||
it("limits an optional note on each weapon", () => {
|
||||
const row = { weaponKey: "1", refinement: 1, overallPercent: 100, personalPercent: null };
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, rows: [{ ...row, note: null }] }).success).toBe(true);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, rows: [{ ...row, note: "x".repeat(2_001) }] }).success).toBe(false);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [{ ...row, note: null }] }).success).toBe(true);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [{ ...row, note: "x".repeat(2_001) }] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("limits weapon and constellation header notes", () => {
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: "Header note", rows: [] }).success).toBe(true);
|
||||
expect(weaponSectionSchema.safeParse({ expectedVersion: 1, note: "x".repeat(2_001), rows: [] }).success).toBe(false);
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, note: "Header note", rows: [] }).success).toBe(true);
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, note: "x".repeat(2_001), rows: [] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("limits artifact main stats to two per slot", () => {
|
||||
@@ -70,8 +77,8 @@ describe("structured guide validation", () => {
|
||||
|
||||
it("persists whether a constellation percentage is marked as great", () => {
|
||||
const row = { level: 1, detail: "Test constellation", cumulativePercent: 120, isGreat: true };
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, rows: [row] }).success).toBe(true);
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, rows: [{ ...row, isGreat: "yes" }] }).success).toBe(false);
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [row] }).success).toBe(true);
|
||||
expect(constellationSectionSchema.safeParse({ expectedVersion: 1, note: null, rows: [{ ...row, isGreat: "yes" }] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("requires four unique team members and non-zero damage", () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ export const updateGuideSchema = z.strictObject({
|
||||
|
||||
export const weaponSectionSchema = z.strictObject({
|
||||
expectedVersion: z.number().int().positive(),
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
rows: z.array(z.strictObject({
|
||||
weaponKey: catalogKey,
|
||||
refinement: z.number().int().min(1).max(5),
|
||||
@@ -78,6 +79,7 @@ export const artifactSectionSchema = z.strictObject({
|
||||
|
||||
export const constellationSectionSchema = z.strictObject({
|
||||
expectedVersion: z.number().int().positive(),
|
||||
note: z.string().trim().max(2_000).nullable(),
|
||||
rows: z.array(z.strictObject({
|
||||
level: z.number().int().min(1).max(6),
|
||||
detail: z.string().trim().min(1).max(2_000),
|
||||
|
||||
Reference in New Issue
Block a user