Files
geo/server/internal/ops/repository/audit.go
T

129 lines
3.3 KiB
Go
Raw Normal View History

package repository
import (
"context"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/geo-platform/tenant-api/internal/ops/domain"
)
type AuditRepository struct {
pool *pgxpool.Pool
}
func NewAuditRepository(pool *pgxpool.Pool) *AuditRepository {
return &AuditRepository{pool: pool}
}
func (r *AuditRepository) Append(ctx context.Context, e domain.AuditEvent) error {
var metaBytes []byte
if e.Metadata != nil {
b, err := json.Marshal(e.Metadata)
if err != nil {
return fmt.Errorf("marshal audit metadata: %w", err)
}
metaBytes = b
}
var (
operatorName = nullableString(e.OperatorName)
targetType = nullableString(e.TargetType)
targetID = nullableString(e.TargetID)
ip = nullableString(e.IP)
ipRegion = nullableString(e.IPRegion)
ua = nullableString(e.UserAgent)
reqID = nullableString(e.RequestID)
)
_, err := r.pool.Exec(ctx, `
INSERT INTO ops.audit_logs
(operator_id, operator_name, action, target_type, target_id, metadata, ip, ip_region, user_agent, request_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
e.OperatorID, operatorName, e.Action, targetType, targetID, metaBytes, ip, ipRegion, ua, reqID)
return err
}
func (r *AuditRepository) List(ctx context.Context, f domain.AuditLogFilter) ([]domain.AuditLog, int64, error) {
args := []any{}
where := "1=1"
if f.OperatorID != nil {
args = append(args, *f.OperatorID)
where += fmt.Sprintf(" AND operator_id = $%d", len(args))
}
if f.Action != "" {
args = append(args, f.Action)
where += fmt.Sprintf(" AND action = $%d", len(args))
}
if f.StartAt != nil {
args = append(args, *f.StartAt)
where += fmt.Sprintf(" AND created_at >= $%d", len(args))
}
if f.EndAt != nil {
args = append(args, *f.EndAt)
where += fmt.Sprintf(" AND created_at < $%d", len(args))
}
var total int64
if err := r.pool.QueryRow(ctx,
"SELECT COUNT(*) FROM ops.audit_logs WHERE "+where, args...).Scan(&total); err != nil {
return nil, 0, err
}
limit := f.Limit
if limit <= 0 || limit > 500 {
limit = 50
}
offset := f.Offset
if offset < 0 {
offset = 0
}
args = append(args, limit, offset)
limitArg := len(args) - 1
offsetArg := len(args)
rows, err := r.pool.Query(ctx, fmt.Sprintf(`
SELECT id, operator_id, operator_name, action, target_type, target_id,
metadata, ip, ip_region, user_agent, request_id, created_at
FROM ops.audit_logs
WHERE %s
ORDER BY created_at DESC, id DESC
LIMIT $%d OFFSET $%d`, where, limitArg, offsetArg), args...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
out := make([]domain.AuditLog, 0, limit)
for rows.Next() {
var (
log domain.AuditLog
metaRaw []byte
)
if err := rows.Scan(
&log.ID, &log.OperatorID, &log.OperatorName, &log.Action,
&log.TargetType, &log.TargetID, &metaRaw, &log.IP, &log.IPRegion,
&log.UserAgent, &log.RequestID, &log.CreatedAt,
); err != nil {
return nil, 0, err
}
if len(metaRaw) > 0 {
if err := json.Unmarshal(metaRaw, &log.Metadata); err != nil {
return nil, 0, fmt.Errorf("decode audit metadata: %w", err)
}
}
out = append(out, log)
}
return out, total, rows.Err()
}
func nullableString(s string) *string {
if s == "" {
return nil
}
return &s
}