138 lines
No EOL
3.6 KiB
Go
138 lines
No EOL
3.6 KiB
Go
package repositories
|
|
|
|
import (
|
|
"database/sql"
|
|
"time"
|
|
)
|
|
|
|
type AuditLogEntry struct {
|
|
ID string
|
|
UserID string
|
|
Username string
|
|
Role string
|
|
Action string
|
|
ResourceType string
|
|
ResourceName string
|
|
Provider string
|
|
SourceFile string
|
|
ContentHash string
|
|
Timestamp time.Time
|
|
Result string
|
|
ErrorCategory string
|
|
RollbackOccurred bool
|
|
ErrorMessage string
|
|
}
|
|
|
|
type AuditRepository struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func NewAuditRepository(db *sql.DB) *AuditRepository {
|
|
return &AuditRepository{db: db}
|
|
}
|
|
|
|
func (r *AuditRepository) Create(entry *AuditLogEntry) error {
|
|
_, err := r.db.Exec(
|
|
`INSERT INTO audit_log (id, user_id, username, role, action, resource_type, resource_name, provider, source_file, content_hash, timestamp, result, error_category, rollback_occurred, error_message)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
entry.ID, entry.UserID, entry.Username, entry.Role, entry.Action, entry.ResourceType, entry.ResourceName,
|
|
entry.Provider, entry.SourceFile, entry.ContentHash, entry.Timestamp, entry.Result, entry.ErrorCategory,
|
|
entry.RollbackOccurred, entry.ErrorMessage,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *AuditRepository) List(opts ListAuditOptions) ([]AuditLogEntry, int, error) {
|
|
query := `SELECT id, user_id, username, role, action, resource_type, resource_name, provider, source_file, content_hash, timestamp, result, error_category, rollback_occurred, error_message FROM audit_log WHERE 1=1 `
|
|
var args []interface{}
|
|
argIdx := 1
|
|
|
|
if opts.Username != "" {
|
|
// Look up user ID by username
|
|
var userID string
|
|
err := r.db.QueryRow(`SELECT id FROM users WHERE username = ?`, opts.Username).Scan(&userID)
|
|
if err == nil && userID != "" {
|
|
opts.UserID = userID
|
|
}
|
|
// If user not found, we just won't filter by user_id (entries without a matching user_id will be excluded)
|
|
}
|
|
if opts.UserID != "" {
|
|
query += " AND user_id=? "
|
|
args = append(args, opts.UserID)
|
|
argIdx++
|
|
}
|
|
if opts.Action != "" {
|
|
query += " AND action=? "
|
|
args = append(args, opts.Action)
|
|
argIdx++
|
|
}
|
|
if opts.Result != "" {
|
|
query += " AND result=? "
|
|
args = append(args, opts.Result)
|
|
argIdx++
|
|
}
|
|
if opts.ResourceType != "" {
|
|
query += " AND resource_type=? "
|
|
args = append(args, opts.ResourceType)
|
|
argIdx++
|
|
}
|
|
if opts.StartDate != "" {
|
|
query += " AND timestamp>=? "
|
|
args = append(args, opts.StartDate)
|
|
argIdx++
|
|
}
|
|
if opts.EndDate != "" {
|
|
query += " AND timestamp<=? "
|
|
args = append(args, opts.EndDate)
|
|
argIdx++
|
|
}
|
|
|
|
countQuery := query + " LIMIT 1 "
|
|
var count int
|
|
err := r.db.QueryRow(countQuery, args...).Scan(&count)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
query += " ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
|
args = append(args, opts.Limit, opts.Offset)
|
|
|
|
rows, err := r.db.Query(query, args...)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []AuditLogEntry
|
|
for rows.Next() {
|
|
var e AuditLogEntry
|
|
var rollbackOccurred int
|
|
err := rows.Scan(&e.ID, &e.UserID, &e.Username, &e.Role, &e.Action, &e.ResourceType, &e.ResourceName,
|
|
&e.Provider, &e.SourceFile, &e.ContentHash, &e.Timestamp, &e.Result, &e.ErrorCategory, &rollbackOccurred, &e.ErrorMessage)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
e.RollbackOccurred = rollbackOccurred != 0
|
|
entries = append(entries, e)
|
|
}
|
|
return entries, count, nil
|
|
}
|
|
|
|
type ListAuditOptions struct {
|
|
UserID string
|
|
Username string
|
|
Action string
|
|
Result string
|
|
ResourceType string
|
|
StartDate string
|
|
EndDate string
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
func defaultListOptions() ListAuditOptions {
|
|
return ListAuditOptions{
|
|
Limit: 50,
|
|
Offset: 0,
|
|
}
|
|
} |