Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
78 lines
1.9 KiB
Go
78 lines
1.9 KiB
Go
package adminapi
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// AuditReader lists recorded changes.
|
|
type AuditReader interface {
|
|
AuditEvents(ctx context.Context, f store.AuditFilter) ([]store.AuditEvent, error)
|
|
}
|
|
|
|
// SetAudit enables GET /api/v1/audit.
|
|
func (a *API) SetAudit(r AuditReader) { a.audit = r }
|
|
|
|
// audit answers GET /api/v1/audit?since=24h&actor=&contains=&limit=.
|
|
func (a *API) auditEvents(w http.ResponseWriter, r *http.Request) {
|
|
if a.audit == nil {
|
|
writeError(w, http.StatusNotImplemented, "this control plane keeps no audit log: it has no store")
|
|
return
|
|
}
|
|
q := r.URL.Query()
|
|
since := 7 * 24 * time.Hour
|
|
if s := q.Get("since"); s != "" {
|
|
d, err := parseSince(s)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
since = d
|
|
}
|
|
limit, _ := strconv.Atoi(q.Get("limit"))
|
|
ev, err := a.audit.AuditEvents(r.Context(), store.AuditFilter{
|
|
Since: time.Now().Add(-since), Actor: q.Get("actor"), Contains: q.Get("contains"), Limit: limit,
|
|
})
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if ev == nil {
|
|
ev = []store.AuditEvent{}
|
|
}
|
|
writeJSON(w, http.StatusOK, ev)
|
|
}
|
|
|
|
// parseSince reads 90m, 24h, 7d.
|
|
func parseSince(s string) (time.Duration, error) {
|
|
if n, ok := cutSuffix(s, "d"); ok {
|
|
days, err := strconv.Atoi(n)
|
|
if err != nil || days < 0 {
|
|
return 0, errBadSince(s)
|
|
}
|
|
return time.Duration(days) * 24 * time.Hour, nil
|
|
}
|
|
d, err := time.ParseDuration(s)
|
|
if err != nil || d < 0 {
|
|
return 0, errBadSince(s)
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
type errBadSince string
|
|
|
|
func (e errBadSince) Error() string {
|
|
return "since " + string(e) + " is not a duration like 90m, 24h or 7d"
|
|
}
|
|
|
|
func cutSuffix(s, suf string) (string, bool) {
|
|
if len(s) > len(suf) && s[len(s)-len(suf):] == suf {
|
|
return s[:len(s)-len(suf)], true
|
|
}
|
|
return "", false
|
|
}
|