feat: transaction activity audit and account activity audit

This commit is contained in:
juancwu 2026-05-03 23:50:39 +00:00
commit c96595d41e
19 changed files with 1259 additions and 20 deletions

View file

@ -9,6 +9,8 @@ type SpaceAuditLogRepository interface {
Create(log *model.SpaceAuditLog) error
ListBySpace(spaceID string, limit, offset int) ([]*model.SpaceAuditLogWithActor, error)
CountBySpace(spaceID string) (int, error)
ListAccountEvents(accountID string, limit, offset int) ([]*model.SpaceAuditLogWithActor, error)
CountAccountEvents(accountID string) (int, error)
}
type spaceAuditLogRepository struct {
@ -58,3 +60,31 @@ func (r *spaceAuditLogRepository) CountBySpace(spaceID string) (int, error) {
err := r.db.Get(&count, `SELECT COUNT(*) FROM space_audit_logs WHERE space_id = $1;`, spaceID)
return count, err
}
func (r *spaceAuditLogRepository) ListAccountEvents(accountID string, limit, offset int) ([]*model.SpaceAuditLogWithActor, error) {
query := `
SELECT
a.id, a.space_id, a.actor_id, a.action, a.target_user_id, a.target_email,
a.metadata, a.created_at,
actor.name AS actor_name, actor.email AS actor_email,
target.name AS target_user_name, target.email AS target_user_email
FROM space_audit_logs a
LEFT JOIN users actor ON actor.id = a.actor_id
LEFT JOIN users target ON target.id = a.target_user_id
WHERE a.action LIKE 'account.%'
AND a.metadata->>'account_id' = $1
ORDER BY a.created_at DESC
LIMIT $2 OFFSET $3;`
var logs []*model.SpaceAuditLogWithActor
err := r.db.Select(&logs, query, accountID, limit, offset)
return logs, err
}
func (r *spaceAuditLogRepository) CountAccountEvents(accountID string) (int, error) {
var count int
err := r.db.Get(&count,
`SELECT COUNT(*) FROM space_audit_logs
WHERE action LIKE 'account.%' AND metadata->>'account_id' = $1;`,
accountID)
return count, err
}

View file

@ -0,0 +1,90 @@
package repository
import (
"git.juancwu.dev/juancwu/budgit/internal/model"
"github.com/jmoiron/sqlx"
)
type TransactionAuditLogRepository interface {
Create(log *model.TransactionAuditLog) error
ListByTransaction(transactionID string, limit, offset int) ([]*model.TransactionAuditLogWithActor, error)
CountByTransaction(transactionID string) (int, error)
ListByAccount(accountID string, limit, offset int) ([]*model.TransactionAuditLogWithActor, error)
CountByAccount(accountID string) (int, error)
}
type transactionAuditLogRepository struct {
db *sqlx.DB
}
func NewTransactionAuditLogRepository(db *sqlx.DB) TransactionAuditLogRepository {
return &transactionAuditLogRepository{db: db}
}
func (r *transactionAuditLogRepository) Create(log *model.TransactionAuditLog) error {
query := `
INSERT INTO transaction_audit_logs
(id, transaction_id, actor_id, action, metadata, created_at)
VALUES ($1, $2, $3, $4, $5, $6);`
metadata := log.Metadata
if len(metadata) == 0 {
metadata = []byte("{}")
}
_, err := r.db.Exec(query,
log.ID, log.TransactionID, log.ActorID, log.Action, metadata, log.CreatedAt,
)
return err
}
func (r *transactionAuditLogRepository) ListByTransaction(transactionID string, limit, offset int) ([]*model.TransactionAuditLogWithActor, error) {
query := `
SELECT
a.id, a.transaction_id, a.actor_id, a.action, a.metadata, a.created_at,
actor.name AS actor_name, actor.email AS actor_email
FROM transaction_audit_logs a
LEFT JOIN users actor ON actor.id = a.actor_id
WHERE a.transaction_id = $1
ORDER BY a.created_at DESC
LIMIT $2 OFFSET $3;`
var logs []*model.TransactionAuditLogWithActor
err := r.db.Select(&logs, query, transactionID, limit, offset)
return logs, err
}
func (r *transactionAuditLogRepository) CountByTransaction(transactionID string) (int, error) {
var count int
err := r.db.Get(&count, `SELECT COUNT(*) FROM transaction_audit_logs WHERE transaction_id = $1;`, transactionID)
return count, err
}
// ListByAccount returns transaction audit entries whose transaction belongs to the given
// account. Uses the live transactions table when present and falls back to the metadata
// account_id (set on creation) so entries for deleted transactions are still surfaced.
func (r *transactionAuditLogRepository) ListByAccount(accountID string, limit, offset int) ([]*model.TransactionAuditLogWithActor, error) {
query := `
SELECT
a.id, a.transaction_id, a.actor_id, a.action, a.metadata, a.created_at,
actor.name AS actor_name, actor.email AS actor_email
FROM transaction_audit_logs a
LEFT JOIN users actor ON actor.id = a.actor_id
LEFT JOIN transactions t ON t.id = a.transaction_id
WHERE t.account_id = $1
OR (t.id IS NULL AND a.metadata->>'account_id' = $1)
ORDER BY a.created_at DESC
LIMIT $2 OFFSET $3;`
var logs []*model.TransactionAuditLogWithActor
err := r.db.Select(&logs, query, accountID, limit, offset)
return logs, err
}
func (r *transactionAuditLogRepository) CountByAccount(accountID string) (int, error) {
var count int
err := r.db.Get(&count,
`SELECT COUNT(*)
FROM transaction_audit_logs a
LEFT JOIN transactions t ON t.id = a.transaction_id
WHERE t.account_id = $1
OR (t.id IS NULL AND a.metadata->>'account_id' = $1);`,
accountID)
return count, err
}