feat: append-only audit trail for all mutations (P0)
- Database: kanzlai.audit_log table with RLS, append-only policies (no UPDATE/DELETE), indexes for entity, user, and time queries - Backend: AuditService.Log() with context-based tenant/user/IP/UA extraction, wired into all 7 services (case, deadline, appointment, document, note, party, tenant) - API: GET /api/audit-log with entity_type, entity_id, user_id, from/to date, and pagination filters - Frontend: Protokoll tab on case detail page with chronological audit entries, diff preview, and pagination Required by § 50 BRAO and DSGVO Art. 5(2).
This commit is contained in:
@@ -13,11 +13,12 @@ import (
|
||||
)
|
||||
|
||||
type TenantService struct {
|
||||
db *sqlx.DB
|
||||
db *sqlx.DB
|
||||
audit *AuditService
|
||||
}
|
||||
|
||||
func NewTenantService(db *sqlx.DB) *TenantService {
|
||||
return &TenantService{db: db}
|
||||
func NewTenantService(db *sqlx.DB, audit *AuditService) *TenantService {
|
||||
return &TenantService{db: db, audit: audit}
|
||||
}
|
||||
|
||||
// Create creates a new tenant and assigns the creator as owner.
|
||||
@@ -49,6 +50,7 @@ func (s *TenantService) Create(ctx context.Context, userID uuid.UUID, name, slug
|
||||
return nil, fmt.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "create", "tenant", &tenant.ID, nil, tenant)
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
@@ -171,6 +173,7 @@ func (s *TenantService) InviteByEmail(ctx context.Context, tenantID uuid.UUID, e
|
||||
return nil, fmt.Errorf("invite user: %w", err)
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "create", "membership", &tenantID, nil, ut)
|
||||
return &ut, nil
|
||||
}
|
||||
|
||||
@@ -186,6 +189,7 @@ func (s *TenantService) UpdateSettings(ctx context.Context, tenantID uuid.UUID,
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update settings: %w", err)
|
||||
}
|
||||
s.audit.Log(ctx, "update", "settings", &tenantID, nil, settings)
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
@@ -223,5 +227,6 @@ func (s *TenantService) RemoveMember(ctx context.Context, tenantID, userID uuid.
|
||||
return fmt.Errorf("remove member: %w", err)
|
||||
}
|
||||
|
||||
s.audit.Log(ctx, "delete", "membership", &tenantID, map[string]any{"user_id": userID, "role": role}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user