forked from wrenn/wrenn
Reviewed-on: wrenn/wrenn#15 Co-authored-by: pptx704 <rafeed@omukk.dev> Co-committed-by: pptx704 <rafeed@omukk.dev>
114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
|
|
"git.omukk.dev/wrenn/wrenn/internal/db"
|
|
"git.omukk.dev/wrenn/wrenn/internal/id"
|
|
)
|
|
|
|
const auditMaxLimit = 200
|
|
|
|
// AuditEntry is a single audit log record returned by List.
|
|
type AuditEntry struct {
|
|
ID string
|
|
TeamID string
|
|
ActorType string
|
|
ActorID string // empty for system
|
|
ActorName string // empty for system
|
|
ResourceType string
|
|
ResourceID string // empty when not applicable
|
|
Action string
|
|
Scope string
|
|
Status string // 'success', 'info', 'warning', 'error'
|
|
Metadata map[string]any
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
// AuditListParams controls the ListAuditLogs query.
|
|
type AuditListParams struct {
|
|
TeamID pgtype.UUID
|
|
AdminScoped bool // true → include admin-scoped events; false → team-scoped only
|
|
ResourceTypes []string // empty = no filter; multiple values = OR match
|
|
Actions []string // empty = no filter; multiple values = OR match
|
|
Before time.Time // zero = no cursor (start from latest)
|
|
BeforeID pgtype.UUID // tie-breaker: id of the last item at the Before timestamp; zero = no tie-break
|
|
Limit int // clamped to auditMaxLimit by the handler
|
|
}
|
|
|
|
// AuditService provides the read side of the audit log.
|
|
type AuditService struct {
|
|
DB *db.Queries
|
|
}
|
|
|
|
// List returns a page of audit log entries for the given team.
|
|
func (s *AuditService) List(ctx context.Context, p AuditListParams) ([]AuditEntry, error) {
|
|
limit := p.Limit
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
if limit > auditMaxLimit {
|
|
limit = auditMaxLimit
|
|
}
|
|
|
|
scopes := []string{"team"}
|
|
if p.AdminScoped {
|
|
scopes = append(scopes, "admin")
|
|
}
|
|
|
|
var before pgtype.Timestamptz
|
|
if !p.Before.IsZero() {
|
|
before = pgtype.Timestamptz{Time: p.Before, Valid: true}
|
|
}
|
|
|
|
resourceTypes := p.ResourceTypes
|
|
if resourceTypes == nil {
|
|
resourceTypes = []string{}
|
|
}
|
|
actions := p.Actions
|
|
if actions == nil {
|
|
actions = []string{}
|
|
}
|
|
|
|
rows, err := s.DB.ListAuditLogs(ctx, db.ListAuditLogsParams{
|
|
TeamID: p.TeamID,
|
|
Column2: scopes,
|
|
Column3: resourceTypes,
|
|
Column4: actions,
|
|
Column5: before,
|
|
ID: p.BeforeID,
|
|
Limit: int32(limit),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list audit logs: %w", err)
|
|
}
|
|
|
|
entries := make([]AuditEntry, len(rows))
|
|
for i, row := range rows {
|
|
var meta map[string]any
|
|
if len(row.Metadata) > 0 {
|
|
_ = json.Unmarshal(row.Metadata, &meta)
|
|
}
|
|
entries[i] = AuditEntry{
|
|
ID: id.FormatAuditLogID(row.ID),
|
|
TeamID: id.FormatTeamID(row.TeamID),
|
|
ActorType: row.ActorType,
|
|
ActorID: row.ActorID.String,
|
|
ActorName: row.ActorName,
|
|
ResourceType: row.ResourceType,
|
|
ResourceID: row.ResourceID.String,
|
|
Action: row.Action,
|
|
Scope: row.Scope,
|
|
Status: row.Status,
|
|
Metadata: meta,
|
|
CreatedAt: row.CreatedAt.Time,
|
|
}
|
|
}
|
|
return entries, nil
|
|
}
|