The other half of the error log. That one is what broke; this is what was done, and nobody could ask it: `ListActivity` takes one project id, and an administrator is not necessarily a member of the project they need to look into. Paged with a keyset cursor rather than an offset. The log is written to constantly and read rarely, so a page fetched a minute after the one before it would be shifted by everything written in between — one row shown twice and the row behind it skipped, which is the single failure an audit log must not have. Both halves of the cursor matter: the log is written in whole seconds, so a timestamp alone cannot separate two rows written in the same one. Migration 0009 makes `activity.user_id` SET NULL for the same reason 0008 did it to `created_by`. A log whose rows vanish with the account they describe answers "what has happened here" with everything except the part somebody had a reason to remove. `project_id` keeps cascading on purpose: the trash is the undo, and past it the deletion was deliberate. The event names moved to `web/src/lib/events.js`, because two surfaces read the same log now and two copies of nineteen strings is two places for them to drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
45 lines
1.7 KiB
JavaScript
45 lines
1.7 KiB
JavaScript
/**
|
|
* What each recorded activity event is called.
|
|
*
|
|
* The keys are the server's and they are stable; this is the only place they
|
|
* become Danish. It lives in its own module because two surfaces read the same log
|
|
* now — a project's own history panel, and the administrator's instance-wide audit
|
|
* page — and two copies of nineteen strings is two places for them to drift apart.
|
|
*
|
|
* An unknown key falls through to the key itself rather than being hidden: a build
|
|
* that records something this table has not learned yet should still show that it
|
|
* happened.
|
|
*/
|
|
export const EVENTS = {
|
|
'project.created': 'oprettede projektet',
|
|
'project.updated': 'ændrede projektet',
|
|
'project.deleted': 'slettede projektet',
|
|
'project.imported': 'importerede projektet',
|
|
'section.created': 'oprettede en sektion',
|
|
'section.updated': 'ændrede en sektion',
|
|
'section.deleted': 'slettede en sektion',
|
|
'member.invited': 'inviterede',
|
|
'member.added': 'tilføjede',
|
|
'member.removed': 'fjernede',
|
|
'member.role_changed': 'ændrede rollen for',
|
|
'task.created': 'oprettede en opgave',
|
|
'task.updated': 'ændrede en opgave',
|
|
'task.completed': 'lukkede en opgave',
|
|
'task.reopened': 'genåbnede en opgave',
|
|
'task.moved': 'flyttede en opgave',
|
|
'task.deleted': 'slettede en opgave',
|
|
'task.split': 'delte en opgave op',
|
|
'comment.created': 'skrev en kommentar'
|
|
};
|
|
|
|
/** The name of an event, or the raw key when it is one this build does not know. */
|
|
export function eventName(event) {
|
|
return EVENTS[event] ?? event;
|
|
}
|
|
|
|
/** The bit of context an entry carries, when it has one worth reading. */
|
|
export function eventDetail(entry) {
|
|
const p = entry.payload ?? {};
|
|
return p.name ?? p.email ?? p.role ?? '';
|
|
}
|