feat : improve import UI
CI / Verify (push) Successful in 1m15s
CI / Build immutable images and deploy (push) Successful in 1m50s

This commit is contained in:
2026-09-08 19:23:05 +07:00 Unverified
parent cd66d498ab
commit d85a48be25
5 changed files with 579 additions and 113 deletions
+74 -1
View File
@@ -1,6 +1,6 @@
import "server-only";
import { and, asc, eq, inArray, isNotNull, isNull } from "drizzle-orm";
import { and, asc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
import * as z from "zod";
import { getDb } from "@/db";
@@ -134,6 +134,79 @@ export async function listGuideCards(includeTrash = false) {
}));
}
export async function listAdminTeamImportGuides(excludeGuideId: string) {
const rows = await getDb()
.select({
id: guides.id,
name: guides.name,
characterKey: guides.characterKey,
isPublic: guides.isPublic,
coverObjectKey: media.objectKey,
})
.from(guides)
.innerJoin(media, eq(media.id, guides.coverMediaId))
.where(and(isNull(guides.trashedAt), ne(guides.id, excludeGuideId)))
.orderBy(asc(guides.sortOrder), asc(guides.name));
return rows.map(({ coverObjectKey, ...guide }) => ({
...guide,
coverUrl: publicMediaUrl(coverObjectKey),
}));
}
export async function getAdminTeamImportGuide(guideId: string) {
const [guide] = await getDb()
.select({ id: guides.id, name: guides.name })
.from(guides)
.where(and(eq(guides.id, guideId), isNull(guides.trashedAt)))
.limit(1);
if (!guide) return null;
const [conditions, teams] = await Promise.all([
getDb().select().from(teamConditions)
.where(eq(teamConditions.guideId, guideId))
.orderBy(asc(teamConditions.sortOrder)),
getDb().select().from(teamCompositions)
.where(eq(teamCompositions.guideId, guideId))
.orderBy(asc(teamCompositions.sortOrder)),
]);
const members = teams.length
? await getDb()
.select({ member: teamMembers, characterName: catalogCharacters.name })
.from(teamMembers)
.leftJoin(catalogCharacters, eq(catalogCharacters.key, teamMembers.characterKey))
.where(inArray(teamMembers.teamId, teams.map((team) => team.id)))
.orderBy(asc(teamMembers.position))
: [];
const hydratedTeams = teams.map((team) => ({
id: team.id,
title: team.title,
rotationSeconds: team.rotationSeconds,
combo: team.combo,
members: members.filter(({ member }) => member.teamId === team.id).map(({ member, characterName }) => ({
characterKey: member.characterKey,
characterName: characterName ?? member.characterKey,
weaponKey: member.weaponKey,
artifactKey: member.artifactKey,
secondArtifactKey: member.secondArtifactKey,
constellation: member.constellation,
refinement: member.refinement,
damage: member.damage,
})),
}));
return {
...guide,
groups: conditions.length
? conditions.map((condition) => ({
id: condition.id,
name: condition.name,
teams: hydratedTeams.filter((team) => teams.find((candidate) => candidate.id === team.id)?.conditionId === condition.id),
}))
: [{ id: "team-condition-default", name: null, teams: hydratedTeams }],
};
}
export async function listPublicGuideCards() {
const [rows, aliases] = await Promise.all([getDb()
.select({
+16
View File
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
applyTeamFields,
cloneTeamImports,
findInvalidTeamCatalogReference,
findUnsupportedTeamWeapon,
limitPresets,
@@ -144,6 +145,21 @@ describe("team bulk tools", () => {
});
});
it("clones imported teams with new ids and independent members", () => {
const source = team("source");
const transfer = {
title: source.title,
rotationSeconds: source.rotationSeconds,
combo: source.combo,
members: source.members,
};
const imported = cloneTeamImports([transfer], () => "imported-1");
expect(imported[0]).toEqual({ ...source, clientId: "imported-1" });
expect(imported[0].members).not.toBe(source.members);
expect(imported[0].members[0]).not.toBe(source.members[0]);
});
it("rejects malformed and unsupported team transfers", () => {
expect(() => parseTeamTransfer("not-json")).toThrow();
expect(() => parseTeamTransfer(JSON.stringify({
+11
View File
@@ -106,6 +106,17 @@ export function parseTeamTransfer(serialized: string): Omit<TeamToolValue, "clie
};
}
export function cloneTeamImports(
teams: readonly Omit<TeamToolValue, "clientId">[],
createId: () => string,
): TeamToolValue[] {
return teams.map((team) => ({
clientId: createId(),
...team,
members: team.members.map((member) => ({ ...member })),
}));
}
export function findInvalidTeamCatalogReference(
team: Omit<TeamToolValue, "clientId">,
characters: readonly TeamToolCatalogItem[],