feat : add import from buzz guide
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import "server-only";
|
||||
import { and, asc, eq, isNull } from "drizzle-orm";
|
||||
import { connection } from "next/server";
|
||||
import { getDb } from "@/db/client";
|
||||
import { guides, teamCompositions, teamConditions, teamMembers } from "@/db/schema";
|
||||
import type { TeamGuide } from "@/lib/team-guides";
|
||||
|
||||
export async function getTeamGuides(): Promise<TeamGuide[]> {
|
||||
await connection();
|
||||
if (!process.env.DATABASE_URL) return [];
|
||||
try {
|
||||
const rows = await getDb().select({
|
||||
guideId: guides.id,
|
||||
guideCharacterId: guides.characterKey,
|
||||
guideName: guides.name,
|
||||
teamId: teamCompositions.id,
|
||||
teamTitle: teamCompositions.title,
|
||||
combo: teamCompositions.combo,
|
||||
conditionName: teamConditions.name,
|
||||
position: teamMembers.position,
|
||||
characterId: teamMembers.characterKey,
|
||||
weaponId: teamMembers.weaponKey,
|
||||
artifactOneId: teamMembers.artifactKey,
|
||||
artifactTwoId: teamMembers.secondArtifactKey,
|
||||
constellation: teamMembers.constellation,
|
||||
refinement: teamMembers.refinement,
|
||||
}).from(guides)
|
||||
.innerJoin(teamCompositions, eq(teamCompositions.guideId, guides.id))
|
||||
.innerJoin(teamMembers, eq(teamMembers.teamId, teamCompositions.id))
|
||||
.leftJoin(teamConditions, eq(teamConditions.id, teamCompositions.conditionId))
|
||||
.where(and(eq(guides.isPublic, true), isNull(guides.trashedAt)))
|
||||
.orderBy(asc(guides.sortOrder), asc(guides.name), asc(teamConditions.sortOrder), asc(teamCompositions.sortOrder), asc(teamMembers.position));
|
||||
|
||||
const result: TeamGuide[] = [];
|
||||
for (const row of rows) {
|
||||
let guide = result.at(-1);
|
||||
if (!guide || guide.id !== row.guideId) {
|
||||
guide = { id: row.guideId, characterId: row.guideCharacterId, name: row.guideName, teams: [] };
|
||||
result.push(guide);
|
||||
}
|
||||
let team = guide.teams.at(-1);
|
||||
if (!team || team.id !== row.teamId) {
|
||||
team = { id: row.teamId, title: row.teamTitle, conditionName: row.conditionName, combo: row.combo, members: [] };
|
||||
guide.teams.push(team);
|
||||
}
|
||||
team.members.push({
|
||||
position: row.position,
|
||||
characterId: row.characterId,
|
||||
weaponId: row.weaponId,
|
||||
artifactOneId: row.artifactOneId,
|
||||
artifactTwoId: row.artifactTwoId,
|
||||
constellation: row.constellation,
|
||||
refinement: row.refinement,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Team guides unavailable", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { generateTeam } from "./translator/generate";
|
||||
import type { Catalog, CatalogItem } from "./translator/types";
|
||||
import { importGuideTeam, type TeamGuideTeam } from "./team-guides";
|
||||
|
||||
const item = (id: string, key: string, name: string, weaponType?: string): CatalogItem => ({ id, key, name, imageUrl: "", rarity: 5, weaponType, supported: true, baseStats: [] });
|
||||
const catalog: Catalog = {
|
||||
version: "test",
|
||||
characters: [item("char-1", "mavuika", "Mavuika", "claymore"), item("char-2", "xilonen", "Xilonen", "sword")],
|
||||
weapons: [item("weapon-1", "favoniusgreatsword", "Favonius Greatsword", "claymore"), item("weapon-2", "favoniussword", "Favonius Sword", "sword")],
|
||||
artifacts: [item("set-1", "obsidiancodex", "Obsidian Codex"), item("set-2", "scrolloftheheroofcindercity", "Scroll")],
|
||||
};
|
||||
|
||||
const guideTeam = (combo = "Mavuika E > Xilonen E N2"): TeamGuideTeam => ({
|
||||
id: "team-1",
|
||||
title: "Guide team",
|
||||
conditionName: "Main Sheet",
|
||||
combo,
|
||||
members: [
|
||||
{ position: 0, characterId: "char-1", weaponId: "weapon-1", artifactOneId: "set-1", artifactTwoId: null, constellation: 2, refinement: 1 },
|
||||
{ position: 1, characterId: "char-2", weaponId: "weapon-2", artifactOneId: "set-1", artifactTwoId: "set-2", constellation: 0, refinement: 5 },
|
||||
],
|
||||
});
|
||||
|
||||
describe("team guide import", () => {
|
||||
it("maps guide loadouts and defaults into a generated team", () => {
|
||||
const result = importGuideTeam(guideTeam(), catalog);
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.team!.builds[0]).toMatchObject({ characterKey: "mavuika", weaponKey: "favoniusgreatsword", constellation: 2, refinement: 1, artifactMode: "4pc", artifactOneKey: "obsidiancodex", level: 90, talents: [9, 9, 9], sands: "atk%", goblet: "atk%", circlet: "cr" });
|
||||
expect(result.team!.builds[1]).toMatchObject({ artifactMode: "2+2", artifactOneKey: "obsidiancodex", artifactTwoKey: "scrolloftheheroofcindercity", refinement: 5 });
|
||||
expect(result.team!.rotation).toMatchObject({ enabled: true, text: "Mavuika E > Xilonen E N2", repeat: false });
|
||||
expect(generateTeam(result.team!)).toContain('xilonen add set="scrolloftheheroofcindercity" count=2;');
|
||||
});
|
||||
|
||||
it("preserves unsupported combo text with rotation disabled", () => {
|
||||
const result = importGuideTeam(guideTeam("Mavuika 3[C ED] Q"), catalog);
|
||||
expect(result.team!.rotation).toEqual({ enabled: false, text: "Mavuika 3[C ED] Q", repeat: false });
|
||||
expect(generateTeam(result.team!)).not.toContain("# Rotation");
|
||||
});
|
||||
|
||||
it.each(["", " - "])("omits an empty guide combo %#", (combo) => {
|
||||
expect(importGuideTeam(guideTeam(combo), catalog).team!.rotation).toBeUndefined();
|
||||
});
|
||||
|
||||
it("reports missing catalog entries before importing", () => {
|
||||
const source = guideTeam();
|
||||
source.members[0].weaponId = "missing";
|
||||
expect(importGuideTeam(source, catalog).error).toBe("Weapon missing is missing from the catalog.");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createBuild } from "./translator/build";
|
||||
import { translateRotation } from "./translator/rotation";
|
||||
import type { Catalog, Team } from "./translator/types";
|
||||
|
||||
export interface TeamGuideMember {
|
||||
position: number;
|
||||
characterId: string;
|
||||
weaponId: string;
|
||||
artifactOneId: string;
|
||||
artifactTwoId: string | null;
|
||||
constellation: number;
|
||||
refinement: number;
|
||||
}
|
||||
|
||||
export interface TeamGuideTeam {
|
||||
id: string;
|
||||
title: string;
|
||||
conditionName: string | null;
|
||||
combo: string;
|
||||
members: TeamGuideMember[];
|
||||
}
|
||||
|
||||
export interface TeamGuide {
|
||||
id: string;
|
||||
characterId: string;
|
||||
name: string;
|
||||
teams: TeamGuideTeam[];
|
||||
}
|
||||
|
||||
export type GuideImportResult = { team: Team; error?: never } | { team?: never; error: string };
|
||||
|
||||
export function importGuideTeam(source: TeamGuideTeam, catalog: Catalog): GuideImportResult {
|
||||
const builds = [] as Team["builds"];
|
||||
for (const member of source.members) {
|
||||
const character = catalog.characters.find((item) => item.id === member.characterId);
|
||||
const weapon = catalog.weapons.find((item) => item.id === member.weaponId);
|
||||
const artifactOne = catalog.artifacts.find((item) => item.id === member.artifactOneId);
|
||||
const artifactTwo = member.artifactTwoId ? catalog.artifacts.find((item) => item.id === member.artifactTwoId) : undefined;
|
||||
if (!character) return { error: `Character ${member.characterId} is missing from the catalog.` };
|
||||
if (!weapon) return { error: `Weapon ${member.weaponId} is missing from the catalog.` };
|
||||
if (!artifactOne) return { error: `Artifact ${member.artifactOneId} is missing from the catalog.` };
|
||||
if (member.artifactTwoId && !artifactTwo) return { error: `Artifact ${member.artifactTwoId} is missing from the catalog.` };
|
||||
if (character.weaponType && weapon.weaponType !== character.weaponType) return { error: `${weapon.name} is not compatible with ${character.name}.` };
|
||||
|
||||
const build = createBuild(catalog, character, `guide-build-${source.id}-${member.position}`);
|
||||
builds.push({
|
||||
...build,
|
||||
weaponId: weapon.id,
|
||||
weaponKey: weapon.key,
|
||||
weaponName: weapon.name,
|
||||
refinement: member.refinement,
|
||||
constellation: member.constellation,
|
||||
artifactMode: artifactTwo ? "2+2" : "4pc",
|
||||
artifactOneId: artifactOne.id,
|
||||
artifactOneKey: artifactOne.key,
|
||||
artifactOneName: artifactOne.name,
|
||||
artifactTwoId: artifactTwo?.id ?? "",
|
||||
artifactTwoKey: artifactTwo?.key ?? "",
|
||||
artifactTwoName: artifactTwo?.name ?? "",
|
||||
});
|
||||
}
|
||||
if (!builds.length) return { error: "This guide team has no characters." };
|
||||
|
||||
const team: Team = { id: `guide-team-${source.id}`, name: source.title, builds };
|
||||
const combo = source.combo.trim();
|
||||
if (combo && combo !== "-") {
|
||||
team.rotation = { enabled: true, text: combo, repeat: false };
|
||||
if (translateRotation(team).error) team.rotation.enabled = false;
|
||||
}
|
||||
return { team };
|
||||
}
|
||||
Reference in New Issue
Block a user