Files
breakpilot-compliance/ai-compliance-sdk/internal/training/store_audit.go
Sharang Parnerkar 9f96061631 refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC,
ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group
into same-package files, all under the 500-line hard cap. Zero behavior changes,
no renamed exported symbols. Also fixed pre-existing hazard_library split (missing
functions and duplicate UUID keys from a prior session).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:29:54 +02:00

129 lines
3.2 KiB
Go

package training
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
)
// LogAction creates an audit log entry
func (s *Store) LogAction(ctx context.Context, entry *AuditLogEntry) error {
entry.ID = uuid.New()
entry.CreatedAt = time.Now().UTC()
details, _ := json.Marshal(entry.Details)
_, err := s.pool.Exec(ctx, `
INSERT INTO training_audit_log (
id, tenant_id, user_id, action, entity_type,
entity_id, details, ip_address, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`,
entry.ID, entry.TenantID, entry.UserID, string(entry.Action), string(entry.EntityType),
entry.EntityID, details, entry.IPAddress, entry.CreatedAt,
)
return err
}
// ListAuditLog lists audit log entries for a tenant
func (s *Store) ListAuditLog(ctx context.Context, tenantID uuid.UUID, filters *AuditLogFilters) ([]AuditLogEntry, int, error) {
countQuery := "SELECT COUNT(*) FROM training_audit_log WHERE tenant_id = $1"
countArgs := []interface{}{tenantID}
countArgIdx := 2
query := `
SELECT
id, tenant_id, user_id, action, entity_type,
entity_id, details, ip_address, created_at
FROM training_audit_log WHERE tenant_id = $1`
args := []interface{}{tenantID}
argIdx := 2
if filters != nil {
if filters.UserID != nil {
query += fmt.Sprintf(" AND user_id = $%d", argIdx)
args = append(args, *filters.UserID)
argIdx++
countQuery += fmt.Sprintf(" AND user_id = $%d", countArgIdx)
countArgs = append(countArgs, *filters.UserID)
countArgIdx++
}
if filters.Action != "" {
query += fmt.Sprintf(" AND action = $%d", argIdx)
args = append(args, string(filters.Action))
argIdx++
countQuery += fmt.Sprintf(" AND action = $%d", countArgIdx)
countArgs = append(countArgs, string(filters.Action))
countArgIdx++
}
if filters.EntityType != "" {
query += fmt.Sprintf(" AND entity_type = $%d", argIdx)
args = append(args, string(filters.EntityType))
argIdx++
countQuery += fmt.Sprintf(" AND entity_type = $%d", countArgIdx)
countArgs = append(countArgs, string(filters.EntityType))
countArgIdx++
}
}
var total int
err := s.pool.QueryRow(ctx, countQuery, countArgs...).Scan(&total)
if err != nil {
return nil, 0, err
}
query += " ORDER BY created_at DESC"
if filters != nil && filters.Limit > 0 {
query += fmt.Sprintf(" LIMIT $%d", argIdx)
args = append(args, filters.Limit)
argIdx++
if filters.Offset > 0 {
query += fmt.Sprintf(" OFFSET $%d", argIdx)
args = append(args, filters.Offset)
argIdx++
}
}
rows, err := s.pool.Query(ctx, query, args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var entries []AuditLogEntry
for rows.Next() {
var entry AuditLogEntry
var action, entityType string
var details []byte
err := rows.Scan(
&entry.ID, &entry.TenantID, &entry.UserID, &action, &entityType,
&entry.EntityID, &details, &entry.IPAddress, &entry.CreatedAt,
)
if err != nil {
return nil, 0, err
}
entry.Action = AuditAction(action)
entry.EntityType = AuditEntityType(entityType)
json.Unmarshal(details, &entry.Details)
if entry.Details == nil {
entry.Details = map[string]interface{}{}
}
entries = append(entries, entry)
}
if entries == nil {
entries = []AuditLogEntry{}
}
return entries, total, nil
}