You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
2.8 KiB
Go

package postgres
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"knowfoolery/backend/services/admin-service/internal/domain/audit"
sharedpostgres "knowfoolery/backend/shared/infra/database/postgres"
)
// AuditRepository implements audit.Repository using pgx.
type AuditRepository struct {
db *sharedpostgres.Client
}
func NewAuditRepository(db *sharedpostgres.Client) *AuditRepository {
return &AuditRepository{db: db}
}
func (r *AuditRepository) EnsureSchema(ctx context.Context) error {
q := `
CREATE TABLE IF NOT EXISTS admin_audit_logs (
id TEXT PRIMARY KEY,
at TIMESTAMPTZ NOT NULL,
actor_id TEXT NOT NULL,
actor_email TEXT NOT NULL,
action TEXT NOT NULL,
resource TEXT NOT NULL,
details JSONB NULL
);
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_at ON admin_audit_logs(at DESC);
`
_, err := r.db.Pool.Exec(ctx, q)
return err
}
func (r *AuditRepository) Append(ctx context.Context, e audit.Entry) error {
var detailsJSON []byte
if e.Details != nil {
b, err := json.Marshal(e.Details)
if err != nil {
return fmt.Errorf("marshal details: %w", err)
}
detailsJSON = b
}
_, err := r.db.Pool.Exec(ctx,
`INSERT INTO admin_audit_logs(id, at, actor_id, actor_email, action, resource, details)
VALUES($1,$2,$3,$4,$5,$6,$7)
ON CONFLICT (id) DO NOTHING`,
e.ID, e.At, e.ActorID, e.ActorEmail, e.Action, e.Resource, detailsJSON,
)
return err
}
func (r *AuditRepository) List(ctx context.Context, limit, offset int) ([]audit.Entry, error) {
if limit <= 0 {
limit = 50
}
if limit > 200 {
limit = 200
}
if offset < 0 {
offset = 0
}
rows, err := r.db.Pool.Query(ctx,
`SELECT id, at, actor_id, actor_email, action, resource, details
FROM admin_audit_logs
ORDER BY at DESC
LIMIT $1 OFFSET $2`, limit, offset,
)
if err != nil {
return nil, err
}
defer rows.Close()
var out []audit.Entry
for rows.Next() {
var e audit.Entry
var detailsBytes []byte
if err := rows.Scan(&e.ID, &e.At, &e.ActorID, &e.ActorEmail, &e.Action, &e.Resource, &detailsBytes); err != nil {
return nil, err
}
if len(detailsBytes) > 0 {
var v any
if err := json.Unmarshal(detailsBytes, &v); err == nil {
e.Details = v
}
}
out = append(out, e)
}
return out, rows.Err()
}
func (r *AuditRepository) Count(ctx context.Context) (int64, error) {
var n int64
err := r.db.Pool.QueryRow(ctx, `SELECT COUNT(*) FROM admin_audit_logs`).Scan(&n)
return n, err
}
func (r *AuditRepository) PruneBefore(ctx context.Context, before time.Time) (int64, error) {
ct, err := r.db.Pool.Exec(ctx, `DELETE FROM admin_audit_logs WHERE at < $1`, before)
if err != nil {
return 0, err
}
return ct.RowsAffected(), nil
}
var _ audit.Repository = (*AuditRepository)(nil)
// Compile-time check for pgx import.
var _ = pgx.ErrNoRows