feat(audit): append-only audit log for spaces
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Records who changed what, and is the mechanism statistics will read — one event table rather than two logs that drift apart. The table is deliberately generic (action + target + JSON metadata) so a new action needs no migration. Writes never throw: a kick must not fail because its log entry could not be written, since the kick already happened. Leaving is recorded as a different action from being removed. The same route serves both, and a log that conflates them misleads exactly when it matters. Actor is nullable with ON DELETE SET NULL: the event outlives the account, and a log that vanished with its actor would be worthless. Reads are gated on MANAGE_SPACE rather than a new permission bit, which would default to nobody until every role was re-edited. Paging uses the snowflake id, stable even for two events in the same millisecond, and an action this build does not know still renders a row.
This commit is contained in:
@@ -33,6 +33,27 @@ export const en = {
|
||||
'settings.voice.micTest.idle': 'Test your mic without joining a call.',
|
||||
'settings.voice.micTest.failed': 'Could not open the microphone. Check the device and its permission.',
|
||||
|
||||
// Audit log
|
||||
'audit.title': 'Audit Log',
|
||||
'audit.empty': 'Nothing recorded yet. Changes to the server show up here.',
|
||||
'audit.loadMore': 'Load more',
|
||||
'audit.unknownActor': 'Deleted account',
|
||||
'audit.action.space.update': '{actor} updated the server settings',
|
||||
'audit.action.space.transfer_ownership': '{actor} transferred ownership of the server',
|
||||
'audit.action.channel.create': '{actor} created the channel {name}',
|
||||
'audit.action.channel.update': '{actor} updated the channel {name}',
|
||||
'audit.action.channel.delete': '{actor} deleted the channel {name}',
|
||||
'audit.action.member.kick': '{actor} removed a member',
|
||||
'audit.action.member.leave': '{actor} left the server',
|
||||
'audit.action.member.ban': '{actor} banned a member',
|
||||
'audit.action.member.unban': '{actor} unbanned a member',
|
||||
'audit.action.role.create': '{actor} created the role {name}',
|
||||
'audit.action.role.update': '{actor} updated the role {name}',
|
||||
'audit.action.role.delete': '{actor} deleted a role',
|
||||
'audit.action.invite.create': '{actor} created an invite',
|
||||
'audit.action.message.delete': '{actor} deleted a message',
|
||||
'audit.action.unknown': '{actor} performed an action',
|
||||
|
||||
// GIF picker
|
||||
'gif.search': 'Search GIFs',
|
||||
'gif.tab.favorites': 'Favorites',
|
||||
|
||||
@@ -32,6 +32,27 @@ export const ptBR: Partial<Dictionary> = {
|
||||
'settings.voice.micTest.idle': 'Teste seu microfone sem entrar numa call.',
|
||||
'settings.voice.micTest.failed': 'Não foi possível abrir o microfone. Verifique o dispositivo e a permissão.',
|
||||
|
||||
// Registro de auditoria
|
||||
'audit.title': 'Registro de auditoria',
|
||||
'audit.empty': 'Nada registrado ainda. Mudanças no servidor aparecem aqui.',
|
||||
'audit.loadMore': 'Carregar mais',
|
||||
'audit.unknownActor': 'Conta excluída',
|
||||
'audit.action.space.update': '{actor} alterou as configurações do servidor',
|
||||
'audit.action.space.transfer_ownership': '{actor} transferiu a propriedade do servidor',
|
||||
'audit.action.channel.create': '{actor} criou o canal {name}',
|
||||
'audit.action.channel.update': '{actor} alterou o canal {name}',
|
||||
'audit.action.channel.delete': '{actor} excluiu o canal {name}',
|
||||
'audit.action.member.kick': '{actor} removeu um membro',
|
||||
'audit.action.member.leave': '{actor} saiu do servidor',
|
||||
'audit.action.member.ban': '{actor} baniu um membro',
|
||||
'audit.action.member.unban': '{actor} removeu o banimento de um membro',
|
||||
'audit.action.role.create': '{actor} criou o cargo {name}',
|
||||
'audit.action.role.update': '{actor} alterou o cargo {name}',
|
||||
'audit.action.role.delete': '{actor} excluiu um cargo',
|
||||
'audit.action.invite.create': '{actor} criou um convite',
|
||||
'audit.action.message.delete': '{actor} excluiu uma mensagem',
|
||||
'audit.action.unknown': '{actor} realizou uma ação',
|
||||
|
||||
// Seletor de GIF
|
||||
'gif.search': 'Buscar GIFs',
|
||||
'gif.tab.favorites': 'Favoritos',
|
||||
|
||||
Reference in New Issue
Block a user