Changeset 0.17.1 (#76)
This commit is contained in:
@@ -3,10 +3,11 @@ package handlers
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/database"
|
||||
)
|
||||
@@ -33,10 +34,17 @@ func AuditLog(c *gin.Context, action, resourceType, resourceID string, metadata
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
||||
`, actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
if database.IsSQLite() {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (id, actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, uuid.New().String(), actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
} else {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
||||
`, actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
}
|
||||
}
|
||||
|
||||
// AuditLogAnon inserts an audit entry without a gin context (e.g. system actions).
|
||||
@@ -50,10 +58,18 @@ func AuditLogAnon(action, resourceType, resourceID string, metadata map[string]i
|
||||
metaJSON = string(b)
|
||||
}
|
||||
}
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (action, resource_type, resource_id, metadata)
|
||||
VALUES ($1, $2, $3, $4::jsonb)
|
||||
`, action, resourceType, resourceID, metaJSON)
|
||||
|
||||
if database.IsSQLite() {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (id, action, resource_type, resource_id, metadata)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, uuid.New().String(), action, resourceType, resourceID, metaJSON)
|
||||
} else {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (action, resource_type, resource_id, metadata)
|
||||
VALUES ($1, $2, $3, $4::jsonb)
|
||||
`, action, resourceType, resourceID, metaJSON)
|
||||
}
|
||||
}
|
||||
|
||||
// AuditLogWithActor inserts an audit entry with an explicit actor ID (e.g. pre-auth flows).
|
||||
@@ -69,10 +85,18 @@ func AuditLogWithActor(actorID string, c *gin.Context, action, resourceType, res
|
||||
metaJSON = string(b)
|
||||
}
|
||||
}
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
||||
`, actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
|
||||
if database.IsSQLite() {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (id, actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, uuid.New().String(), actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
} else {
|
||||
_, _ = database.DB.Exec(`
|
||||
INSERT INTO audit_log (actor_id, action, resource_type, resource_id, metadata, ip_address, user_agent)
|
||||
VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
|
||||
`, actorID, action, resourceType, resourceID, metaJSON, ip, ua)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Admin Audit Viewer ──────────────────────
|
||||
@@ -94,49 +118,52 @@ type auditEntry struct {
|
||||
func (h *AdminHandler) ListAuditLog(c *gin.Context) {
|
||||
page, perPage, offset := parsePagination(c)
|
||||
|
||||
// Build filter clauses
|
||||
// Build filter clauses with ? placeholders, convert for Postgres later
|
||||
where := "WHERE 1=1"
|
||||
args := []interface{}{}
|
||||
argN := 1
|
||||
|
||||
if action := c.Query("action"); action != "" {
|
||||
where += " AND al.action = $" + strconv.Itoa(argN)
|
||||
where += " AND al.action = ?"
|
||||
args = append(args, action)
|
||||
argN++
|
||||
}
|
||||
if actorID := c.Query("actor_id"); actorID != "" {
|
||||
where += " AND al.actor_id = $" + strconv.Itoa(argN)
|
||||
where += " AND al.actor_id = ?"
|
||||
args = append(args, actorID)
|
||||
argN++
|
||||
}
|
||||
if rt := c.Query("resource_type"); rt != "" {
|
||||
where += " AND al.resource_type = $" + strconv.Itoa(argN)
|
||||
where += " AND al.resource_type = ?"
|
||||
args = append(args, rt)
|
||||
argN++
|
||||
}
|
||||
|
||||
// Count
|
||||
var total int
|
||||
countArgs := make([]interface{}, len(args))
|
||||
copy(countArgs, args)
|
||||
err := database.DB.QueryRow(`SELECT COUNT(*) FROM audit_log al `+where, countArgs...).Scan(&total)
|
||||
countQ := convertPlaceholders(`SELECT COUNT(*) FROM audit_log al ` + where)
|
||||
err := database.DB.QueryRow(countQ, countArgs...).Scan(&total)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "count failed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Query
|
||||
query := `
|
||||
// Query — metadata column needs ::text on Postgres only
|
||||
metadataCol := "COALESCE(al.metadata::text, '{}')"
|
||||
if database.IsSQLite() {
|
||||
metadataCol = "COALESCE(al.metadata, '{}')"
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
SELECT al.id, al.actor_id, COALESCE(u.username, '') as actor_name,
|
||||
al.action, al.resource_type, al.resource_id,
|
||||
COALESCE(al.metadata::text, '{}'), al.ip_address, al.created_at
|
||||
%s, al.ip_address, al.created_at
|
||||
FROM audit_log al
|
||||
LEFT JOIN users u ON al.actor_id = u.id
|
||||
` + where + `
|
||||
%s
|
||||
ORDER BY al.created_at DESC
|
||||
LIMIT $` + strconv.Itoa(argN) + ` OFFSET $` + strconv.Itoa(argN+1)
|
||||
LIMIT ? OFFSET ?`, metadataCol, where)
|
||||
args = append(args, perPage, offset)
|
||||
|
||||
query = convertPlaceholders(query)
|
||||
rows, err := database.DB.Query(query, args...)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
|
||||
|
||||
Reference in New Issue
Block a user