feat : update

This commit is contained in:
2026-10-05 20:59:44 +07:00 Unverified
parent 3c3b104779
commit 08b61b5d91
15 changed files with 4398 additions and 62 deletions
+50 -4
View File
@@ -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));