// Package service provides audit service business logic. package service import ( "context" "fmt" "time" "git.dcentral.systems/toolz/goplt/internal/ent" "git.dcentral.systems/toolz/goplt/internal/ent/auditlog" "git.dcentral.systems/toolz/goplt/pkg/logger" "github.com/google/uuid" "go.uber.org/zap" ) // AuditLogEntry represents an audit log entry. type AuditLogEntry struct { UserID string Action string Resource string ResourceID string IPAddress string UserAgent string Metadata map[string]string Timestamp int64 } // AuditLogFilters contains filters for querying audit logs. type AuditLogFilters struct { UserID *string Action *string Resource *string ResourceID *string StartTime *int64 EndTime *int64 Limit int Offset int } // AuditService provides audit logging functionality. type AuditService struct { client *ent.Client logger logger.Logger } // NewAuditService creates a new audit service. func NewAuditService(client *ent.Client, log logger.Logger) *AuditService { return &AuditService{ client: client, logger: log, } } // Record records an audit log entry. func (s *AuditService) Record(ctx context.Context, entry *AuditLogEntry) error { // Convert metadata map to JSON metadataJSON := make(map[string]interface{}) for k, v := range entry.Metadata { metadataJSON[k] = v } // Create audit log entry timestamp := time.Unix(entry.Timestamp, 0) if entry.Timestamp == 0 { timestamp = time.Now() } create := s.client.AuditLog.Create(). SetID(uuid.New().String()). SetUserID(entry.UserID). SetAction(entry.Action). SetMetadata(metadataJSON). SetTimestamp(timestamp) if entry.Resource != "" { create = create.SetResource(entry.Resource) } if entry.ResourceID != "" { create = create.SetResourceID(entry.ResourceID) } if entry.IPAddress != "" { create = create.SetIPAddress(entry.IPAddress) } if entry.UserAgent != "" { create = create.SetUserAgent(entry.UserAgent) } auditLog, err := create.Save(ctx) if err != nil { s.logger.Error("Failed to record audit log", zap.Error(err), zap.String("user_id", entry.UserID), zap.String("action", entry.Action), ) return fmt.Errorf("failed to record audit log: %w", err) } s.logger.Debug("Audit log recorded", zap.String("id", auditLog.ID), zap.String("user_id", entry.UserID), zap.String("action", entry.Action), ) return nil } // Query queries audit logs based on filters. func (s *AuditService) Query(ctx context.Context, filters *AuditLogFilters) ([]*AuditLogEntry, error) { query := s.client.AuditLog.Query() // Apply filters if filters.UserID != nil { query = query.Where(auditlog.UserID(*filters.UserID)) } if filters.Action != nil { query = query.Where(auditlog.Action(*filters.Action)) } if filters.Resource != nil { query = query.Where(auditlog.Resource(*filters.Resource)) } if filters.ResourceID != nil { query = query.Where(auditlog.ResourceID(*filters.ResourceID)) } if filters.StartTime != nil { query = query.Where(auditlog.TimestampGTE(time.Unix(*filters.StartTime, 0))) } if filters.EndTime != nil { query = query.Where(auditlog.TimestampLTE(time.Unix(*filters.EndTime, 0))) } // Apply pagination if filters.Limit > 0 { query = query.Limit(filters.Limit) } if filters.Offset > 0 { query = query.Offset(filters.Offset) } // Order by timestamp descending query = query.Order(ent.Desc(auditlog.FieldTimestamp)) // Execute query auditLogs, err := query.All(ctx) if err != nil { s.logger.Error("Failed to query audit logs", zap.Error(err), ) return nil, fmt.Errorf("failed to query audit logs: %w", err) } // Convert to service entries entries := make([]*AuditLogEntry, 0, len(auditLogs)) for _, log := range auditLogs { // Convert metadata from map[string]interface{} to map[string]string metadata := make(map[string]string) if log.Metadata != nil { for k, v := range log.Metadata { if str, ok := v.(string); ok { metadata[k] = str } else { metadata[k] = fmt.Sprintf("%v", v) } } } entry := &AuditLogEntry{ UserID: log.UserID, Action: log.Action, Resource: log.Resource, ResourceID: log.ResourceID, IPAddress: log.IPAddress, UserAgent: log.UserAgent, Metadata: metadata, Timestamp: log.Timestamp.Unix(), } entries = append(entries, entry) } return entries, nil }