fix(housekeeping): coordinate all rank mutation paths
CI / check (pull_request) Successful in 1m44s
CI / deploy (pull_request) Skipped
CI / e2e (pull_request) Skipped

This commit is contained in:
Simo committed 2026-09-05 09:48:30 +02:00
1 parent 3d53321575
commit 866f38818b
14 files changed
+1070 -317

No files matched your search

@@ -34,6 +34,11 @@ import {
executeModerationAction,
reloadWordFilter,
} from "@/lib/services/moderation";
import {
RankAssignmentFailure,
type RankAssignmentTransaction,
rankAssignmentCoordinator,
} from "@/lib/services/rank-assignment";
import { rcon } from "@/lib/services/rcon";
import { siteSettings } from "@/lib/services/site-settings";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -438,17 +443,6 @@ async function assertBulkTargetHierarchy(
}
}
async function assertConfiguredRankExists(rank: number): Promise<void> {
const [rows] = (await db.execute(
sql`SELECT id FROM permission_ranks WHERE id = ${rank} LIMIT 1`,
)) as unknown as [unknown[], unknown];
if (!Array.isArray(rows) || rows.length === 0) {
throw new PeopleMutationFailure("VALIDATION", "errors.validation.invalid", {
rank: ["errors.validation.invalid"],
});
}
}
function targetSnapshot(target: TargetUser) {
return {
id: target.id,
@@ -642,22 +636,39 @@ async function executeUserMutation(
"errors.housekeeping.forbidden",
);
}
await assertConfiguredRankExists(rank);
}
const userPatch = Object.fromEntries(
["username", "mail", "rank", "motto", "credits", "pixels"].flatMap(
(key) => (fields[key] === undefined ? [] : [[key, fields[key]]]),
["username", "mail", "motto", "credits", "pixels"].flatMap((key) =>
fields[key] === undefined ? [] : [[key, fields[key]]],
),
);
const safeFields = Object.fromEntries(
Object.entries(fields).filter(([key]) => key !== "mail"),
);
if (fields.mail !== undefined) safeFields.mailChanged = true;
const after: Record<string, unknown> = { ...before, ...safeFields };
const rankSyncAudit = {
userId: context.capability.actor.id,
action: "system.external-sync",
target: "rcon.set-rank",
targetId: userId,
correlationId: context.correlationId,
domain: "system" as const,
after: {
kind: "set-rank",
operation: "rcon.set-rank",
userId,
rank: nextRank,
},
};
if (nextRank !== undefined) after.rankRecoveryId = context.correlationId;
const snapshot: PeopleMutationSnapshot = {
before,
after: { ...before, ...safeFields },
after,
};
await db.transaction(async (tx) => {
const persist = async (tx: RankAssignmentTransaction) => {
if (nextRank !== undefined)
await logAudit({ ...rankSyncAudit, outcome: "intent" }, tx);
if (Object.keys(userPatch).length > 0) {
await tx.update(User).set(userPatch).where(eq(User.id, userId));
}
@@ -683,8 +694,49 @@ async function executeUserMutation(
),
tx,
);
});
return finalizeExternalWithAudit(
};
if (nextRank !== undefined) {
try {
await rankAssignmentCoordinator.commit({
userId,
rank: positiveInteger(nextRank),
authorize(currentRank) {
before.rank = currentRank;
if (
!context.capability.isSuperAdmin &&
currentRank >= context.capability.actor.rank
) {
throw new PeopleMutationFailure(
"FORBIDDEN",
"errors.housekeeping.forbidden",
);
}
},
mutate: persist,
});
} catch (error) {
if (error instanceof RankAssignmentFailure) {
if (error.code === "RANK_NOT_FOUND") {
throw new PeopleMutationFailure(
"VALIDATION",
"errors.validation.invalid",
{
rank: ["errors.validation.invalid"],
},
);
}
throw new PeopleMutationFailure(
"NOT_FOUND",
"errors.housekeeping.notFound",
);
}
throw error;
}
} else {
await db.transaction(persist);
}
let rankAuditUnavailable = false;
const completed = await finalizeExternalWithAudit(
context,
operation,
"User",
@@ -692,6 +744,33 @@ async function executeUserMutation(
snapshot,
async () => {
invalidateLoginCache(target.username);
if (nextRank !== undefined) {
const delivery = await rankAssignmentCoordinator.synchronize(userId);
after.rankSynchronization = delivery.status;
if (delivery.status !== "superseded")
after.synchronizedRank = delivery.rank;
try {
await logAudit({
...rankSyncAudit,
after: {
...rankSyncAudit.after,
...(delivery.status === "superseded"
? { superseded: true }
: { rank: delivery.rank }),
},
outcome: delivery.status === "pending" ? "partial" : "success",
});
} catch {
rankAuditUnavailable = true;
after.rankCompletionAudit = "pending";
}
if (delivery.status === "pending") {
throw new PeopleMutationFailure(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
);
}
}
void notify({
action: "user_edit",
actor: context.capability.actor.username,
@@ -700,6 +779,14 @@ async function executeUserMutation(
});
},
);
if (rankAuditUnavailable) {
return withPartialCompletion(completed, {
status: "partial",
external: completed.completion?.external ?? "completed",
audit: "unavailable",
});
}
return completed;
}
if (operation === "user.ban") {