feat(server/ops-api): add operations console backend
Stand up an internal ops-api service with operator account, auth, and audit log subsystems backed by a dedicated migrations_ops migration set. Wire Makefile targets and the migrate Dockerfile stage so the new schema ships alongside the existing tenant + monitoring databases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
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, user_agent, request_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
e.OperatorID, operatorName, e.Action, targetType, targetID, metaBytes, ip, 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, 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.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
|
||||
}
|
||||
Reference in New Issue
Block a user