budgit/internal/service/account_activity.go

131 lines
3.8 KiB
Go

package service
import (
"fmt"
"sort"
"git.juancwu.dev/juancwu/budgit/internal/model"
)
type AccountActivityService struct {
spaceAudit *SpaceAuditLogService
txAudit *TransactionAuditLogService
}
func NewAccountActivityService(spaceAudit *SpaceAuditLogService, txAudit *TransactionAuditLogService) *AccountActivityService {
return &AccountActivityService{
spaceAudit: spaceAudit,
txAudit: txAudit,
}
}
// List returns a merged feed of account-scoped events (account.created/renamed/deleted)
// and transaction events (created/edited/deleted) for transactions in this account.
//
// Rather than a SQL UNION across two heterogeneous tables, we fetch up to (offset+limit)
// from each side and merge in Go. Audit volume per account is low, so the simplicity
// outweighs the slight overfetch.
func (s *AccountActivityService) List(accountID string, limit, offset int) ([]model.ActivityRow, error) {
if limit <= 0 {
limit = 25
}
if offset < 0 {
offset = 0
}
fetchN := offset + limit
spaceLogs, err := s.spaceAudit.repo.ListAccountEvents(accountID, fetchN, 0)
if err != nil {
return nil, fmt.Errorf("failed to list account events: %w", err)
}
txLogs, err := s.txAudit.repo.ListByAccount(accountID, fetchN, 0)
if err != nil {
return nil, fmt.Errorf("failed to list transaction events: %w", err)
}
rows := make([]model.ActivityRow, 0, len(spaceLogs)+len(txLogs))
for _, l := range spaceLogs {
rows = append(rows, model.ActivityRow{SpaceLog: l})
}
for _, l := range txLogs {
rows = append(rows, model.ActivityRow{TxLog: l})
}
sort.Slice(rows, func(i, j int) bool {
return rows[i].Timestamp().After(rows[j].Timestamp())
})
if offset >= len(rows) {
return nil, nil
}
end := offset + limit
if end > len(rows) {
end = len(rows)
}
return rows[offset:end], nil
}
func (s *AccountActivityService) Count(accountID string) (int, error) {
spaceCount, err := s.spaceAudit.repo.CountAccountEvents(accountID)
if err != nil {
return 0, fmt.Errorf("failed to count account events: %w", err)
}
txCount, err := s.txAudit.repo.CountByAccount(accountID)
if err != nil {
return 0, fmt.Errorf("failed to count transaction events: %w", err)
}
return spaceCount + txCount, nil
}
// ListSpace returns a merged feed of every audit entry scoped to the space — its own
// space_audit_logs (rename, members, account events) plus every transaction event for
// transactions whose account belongs to this space. Same in-memory merge as List.
func (s *AccountActivityService) ListSpace(spaceID string, limit, offset int) ([]model.ActivityRow, error) {
if limit <= 0 {
limit = 25
}
if offset < 0 {
offset = 0
}
fetchN := offset + limit
spaceLogs, err := s.spaceAudit.repo.ListBySpace(spaceID, fetchN, 0)
if err != nil {
return nil, fmt.Errorf("failed to list space events: %w", err)
}
txLogs, err := s.txAudit.repo.ListBySpace(spaceID, fetchN, 0)
if err != nil {
return nil, fmt.Errorf("failed to list transaction events: %w", err)
}
rows := make([]model.ActivityRow, 0, len(spaceLogs)+len(txLogs))
for _, l := range spaceLogs {
rows = append(rows, model.ActivityRow{SpaceLog: l})
}
for _, l := range txLogs {
rows = append(rows, model.ActivityRow{TxLog: l})
}
sort.Slice(rows, func(i, j int) bool {
return rows[i].Timestamp().After(rows[j].Timestamp())
})
if offset >= len(rows) {
return nil, nil
}
end := offset + limit
if end > len(rows) {
end = len(rows)
}
return rows[offset:end], nil
}
func (s *AccountActivityService) CountSpace(spaceID string) (int, error) {
spaceCount, err := s.spaceAudit.repo.CountBySpace(spaceID)
if err != nil {
return 0, fmt.Errorf("failed to count space events: %w", err)
}
txCount, err := s.txAudit.repo.CountBySpace(spaceID)
if err != nil {
return 0, fmt.Errorf("failed to count transaction events: %w", err)
}
return spaceCount + txCount, nil
}