fix(admin): Protokoll formuliert Aktionen am eigenen Konto natürlich

„hat die eigenen Daten exportiert“ statt „die Daten von sich selbst“.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-05 08:28:27 +02:00
1 parent 0504d284e2
commit ed0b4666bc
3 files changed
+21 -9

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ const CATEGORIES = Object.keys(AUDIT_CATEGORY_LABEL) as AuditCategory[];
function EntryRow({ entry }: { entry: Entry }) {
const [isOpen, setIsOpen] = useState(false);
const isSelf = entry.targetType === "USER" && entry.targetId === entry.moderatorId;
const view = formatAuditEntry({ ...entry, targetLabel: isSelf ? "sich selbst" : entry.targetLabel });
const view = formatAuditEntry({ ...entry, isSelf });
const Icon = entry.isAutomatic ? Bot : CATEGORY_ICON[view.category];
const removal = isRemoval(entry.action);
const actor = entry.isAutomatic ? "Automatik" : (entry.moderatorName ?? "Unbekannt");
+8
View File
@@ -43,6 +43,14 @@ describe("formatAuditEntry", () => {
expect(text(fmt("GRANT_ROLE", "MODERATOR", "Lea").parts)).toBe("hat **Lea** die Rolle **Moderator** gegeben");
});
it("phrases actions on the moderator's own account naturally", () => {
const self = (action: string, reason: string | null = null) =>
text(formatAuditEntry({ action, reason, targetLabel: "Daniel", rankName: "Pfoten-Legende", isSelf: true }).parts);
expect(self("DATA_EXPORT")).toBe("hat die eigenen Daten exportiert");
expect(self("GRANT_REFERRAL_RANK", "rank-1")).toBe("hat sich selbst den Rang **Pfoten-Legende** verliehen");
expect(self("UNBAN_USER")).toBe("hat die eigene Sperre aufgehoben");
});
it("never shows a bare code for unknown actions", () => {
const v = fmt("SOMETHING_NEW");
expect(v.category).toBe("OTHER");
+12 -8
View File
@@ -67,6 +67,8 @@ export interface AuditInput {
targetLabel: string | null;
/** Vom Server aufgelöster Rangname bei Empfehlungs-Aktionen. */
rankName?: string | null;
/** Halter-Aktion am eigenen Konto → „sich selbst“ bzw. „die eigenen Daten“ statt zweimal derselbe Name. */
isSelf?: boolean;
}
export interface AuditView {
@@ -107,13 +109,15 @@ function splitPrefix(reason: string | null): [string | null, string | null] {
const reasonLine = (text: string | null | undefined) => (text && text.trim() ? [`Grund: ${text.trim()}`] : []);
const formatValue = (v: unknown) => (v === true ? "an" : v === false ? "aus" : String(v));
export function formatAuditEntry({ action, reason, targetLabel, rankName }: AuditInput): AuditView {
export function formatAuditEntry({ action, reason, targetLabel, rankName, isSelf = false }: AuditInput): AuditView {
const category = CATEGORY_BY_ACTION.get(action) ?? "OTHER";
const s = (label: string | null | undefined, fallback: string): AuditPart => (label ? { strong: label } : fallback);
const view = (parts: AuditPart[], details: string[] = []): AuditView => ({ category, parts, details });
const post = targetLabel ? ["den Beitrag von ", { strong: targetLabel }] : ["einen Beitrag"];
const owner = s(targetLabel, "einen Halter");
const owner: AuditPart = isSelf ? "sich selbst" : s(targetLabel, "einen Halter");
/** „die Sperre von X“ bzw. am eigenen Konto „die eigene Sperre“. */
const ofOwner = (noun: string, ownNoun: string): AuditPart[] => (isSelf ? [ownNoun] : [`${noun} von `, owner]);
const pet = s(targetLabel, "ein Tier");
const rank = s(rankName, "einen Rang");
@@ -142,17 +146,17 @@ export function formatAuditEntry({ action, reason, targetLabel, rankName }: Audi
case "BAN_USER":
return view(["hat ", owner, " gesperrt"], reasonLine(reason));
case "UNBAN_USER":
return view(["hat die Sperre von ", owner, " aufgehoben"]);
return view(["hat ", ...ofOwner("die Sperre", "die eigene Sperre"), " aufgehoben"]);
case "SHADOWBAN_USER":
return view(["hat ", owner, " unsichtbar geschaltet (Shadowban)"], reasonLine(reason));
case "UNSHADOWBAN_USER":
return view(["hat den Shadowban von ", owner, " aufgehoben"]);
return view(["hat ", ...ofOwner("den Shadowban", "den eigenen Shadowban"), " aufgehoben"]);
case "WARN_USER":
return view(["hat ", owner, " verwarnt"], reasonLine(reason));
case "DELETE_ACCOUNT":
return view(["hat das Konto von ", owner, " gelöscht"], reasonLine(reason));
return view(["hat ", ...ofOwner("das Konto", "das eigene Konto"), " gelöscht"], reasonLine(reason));
case "DATA_EXPORT":
return view(["hat die Daten von ", owner, " exportiert"]);
return view(["hat ", ...ofOwner("die Daten", "die eigenen Daten"), " exportiert"]);
// Tiere
case "VERIFY_PET":
@@ -241,9 +245,9 @@ export function formatAuditEntry({ action, reason, targetLabel, rankName }: Audi
return view(["hat die Einstellungen des ", { strong: "Empfehlungsprogramms" }, " geändert"], details);
}
case "APPROVE_REFERRAL":
return view(["hat die Einladung von ", owner, " freigegeben"]);
return view(["hat ", ...ofOwner("die Einladung", "die eigene Einladung"), " freigegeben"]);
case "REVOKE_REFERRAL":
return view(["hat die Einladung von ", owner, " aberkannt"], reasonLine(reason));
return view(["hat ", ...ofOwner("die Einladung", "die eigene Einladung"), " aberkannt"], reasonLine(reason));
case "GRANT_REFERRAL_RANK":
return view(["hat ", owner, " den Rang ", rank, " verliehen"], splitPrefix(reason)[1] ? [`Notiz: ${splitPrefix(reason)[1]}`] : []);
case "RESTORE_REFERRAL_RANK":