wip 21
This commit is contained in:
128
api/v1/admin.go
128
api/v1/admin.go
@@ -941,7 +941,16 @@ func (s *Server) GetLogs(ctx context.Context, request GetLogsRequestObject) (Get
|
||||
return GetLogs401JSONResponse{Code: 401, Message: "Unauthorized"}, nil
|
||||
}
|
||||
|
||||
// Get filter parameter (mirroring legacy)
|
||||
page := int64(1)
|
||||
if request.Params.Page != nil && *request.Params.Page > 0 {
|
||||
page = *request.Params.Page
|
||||
}
|
||||
|
||||
limit := int64(100)
|
||||
if request.Params.Limit != nil && *request.Params.Limit > 0 {
|
||||
limit = *request.Params.Limit
|
||||
}
|
||||
|
||||
filter := ""
|
||||
if request.Params.Filter != nil {
|
||||
filter = strings.TrimSpace(*request.Params.Filter)
|
||||
@@ -967,7 +976,6 @@ func (s *Server) GetLogs(ctx context.Context, request GetLogsRequestObject) (Get
|
||||
}
|
||||
}
|
||||
|
||||
// Open Log File (mirroring legacy)
|
||||
logPath := filepath.Join(s.cfg.ConfigPath, "logs/antholume.log")
|
||||
logFile, err := os.Open(logPath)
|
||||
if err != nil {
|
||||
@@ -975,58 +983,90 @@ func (s *Server) GetLogs(ctx context.Context, request GetLogsRequestObject) (Get
|
||||
}
|
||||
defer logFile.Close()
|
||||
|
||||
// Log Lines (mirroring legacy)
|
||||
var logLines []string
|
||||
offset := (page - 1) * limit
|
||||
logLines := make([]string, 0, limit)
|
||||
matchedCount := int64(0)
|
||||
|
||||
scanner := bufio.NewScanner(logFile)
|
||||
for scanner.Scan() {
|
||||
rawLog := scanner.Text()
|
||||
|
||||
// Attempt JSON Pretty (mirroring legacy)
|
||||
var jsonMap map[string]any
|
||||
err := json.Unmarshal([]byte(rawLog), &jsonMap)
|
||||
if err != nil {
|
||||
logLines = append(logLines, rawLog)
|
||||
formattedLog, matched := formatLogLine(scanner.Text(), basicFilter, jqFilter)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse JSON (mirroring legacy)
|
||||
rawData, err := json.MarshalIndent(jsonMap, "", " ")
|
||||
if err != nil {
|
||||
logLines = append(logLines, rawLog)
|
||||
continue
|
||||
if matchedCount >= offset && int64(len(logLines)) < limit {
|
||||
logLines = append(logLines, formattedLog)
|
||||
}
|
||||
matchedCount++
|
||||
}
|
||||
|
||||
// Basic Filter (mirroring legacy)
|
||||
if basicFilter != "" && strings.Contains(string(rawData), basicFilter) {
|
||||
logLines = append(logLines, string(rawData))
|
||||
continue
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return GetLogs500JSONResponse{Code: 500, Message: "Unable to read AnthoLume log file"}, nil
|
||||
}
|
||||
|
||||
// No JQ Filter (mirroring legacy)
|
||||
if jqFilter == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Error or nil (mirroring legacy)
|
||||
result, _ := jqFilter.Run(jsonMap).Next()
|
||||
if _, ok := result.(error); ok {
|
||||
logLines = append(logLines, string(rawData))
|
||||
continue
|
||||
} else if result == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt filtered json (mirroring legacy)
|
||||
filteredData, err := json.MarshalIndent(result, "", " ")
|
||||
if err == nil {
|
||||
rawData = filteredData
|
||||
}
|
||||
|
||||
logLines = append(logLines, string(rawData))
|
||||
var nextPage *int64
|
||||
var previousPage *int64
|
||||
if page > 1 {
|
||||
previousPage = ptrOf(page - 1)
|
||||
}
|
||||
if offset+int64(len(logLines)) < matchedCount {
|
||||
nextPage = ptrOf(page + 1)
|
||||
}
|
||||
|
||||
return GetLogs200JSONResponse{
|
||||
Logs: &logLines,
|
||||
Filter: &filter,
|
||||
Logs: &logLines,
|
||||
Filter: &filter,
|
||||
Page: &page,
|
||||
Limit: &limit,
|
||||
NextPage: nextPage,
|
||||
PreviousPage: previousPage,
|
||||
Total: &matchedCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func formatLogLine(rawLog string, basicFilter string, jqFilter *gojq.Code) (string, bool) {
|
||||
var jsonMap map[string]any
|
||||
if err := json.Unmarshal([]byte(rawLog), &jsonMap); err != nil {
|
||||
if basicFilter == "" && jqFilter == nil {
|
||||
return rawLog, true
|
||||
}
|
||||
if basicFilter != "" && strings.Contains(rawLog, basicFilter) {
|
||||
return rawLog, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
rawData, err := json.MarshalIndent(jsonMap, "", " ")
|
||||
if err != nil {
|
||||
if basicFilter == "" && jqFilter == nil {
|
||||
return rawLog, true
|
||||
}
|
||||
if basicFilter != "" && strings.Contains(rawLog, basicFilter) {
|
||||
return rawLog, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
formattedLog := string(rawData)
|
||||
if basicFilter != "" {
|
||||
return formattedLog, strings.Contains(formattedLog, basicFilter)
|
||||
}
|
||||
if jqFilter == nil {
|
||||
return formattedLog, true
|
||||
}
|
||||
|
||||
result, _ := jqFilter.Run(jsonMap).Next()
|
||||
if _, ok := result.(error); ok {
|
||||
return formattedLog, true
|
||||
}
|
||||
if result == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
filteredData, err := json.MarshalIndent(result, "", " ")
|
||||
if err == nil {
|
||||
formattedLog = string(filteredData)
|
||||
}
|
||||
|
||||
return formattedLog, true
|
||||
}
|
||||
|
||||
152
api/v1/admin_test.go
Normal file
152
api/v1/admin_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
argon2 "github.com/alexedwards/argon2id"
|
||||
"github.com/stretchr/testify/require"
|
||||
"reichard.io/antholume/config"
|
||||
"reichard.io/antholume/database"
|
||||
)
|
||||
|
||||
func createAdminTestUser(t *testing.T, db *database.DBManager, username, password string) {
|
||||
t.Helper()
|
||||
|
||||
md5Hash := fmt.Sprintf("%x", md5.Sum([]byte(password)))
|
||||
hashedPassword, err := argon2.CreateHash(md5Hash, argon2.DefaultParams)
|
||||
require.NoError(t, err)
|
||||
|
||||
authHash := "test-auth-hash"
|
||||
_, err = db.Queries.CreateUser(context.Background(), database.CreateUserParams{
|
||||
ID: username,
|
||||
Pass: &hashedPassword,
|
||||
AuthHash: &authHash,
|
||||
Admin: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func loginAdminTestUser(t *testing.T, srv *Server, username, password string) *http.Cookie {
|
||||
t.Helper()
|
||||
|
||||
body, err := json.Marshal(LoginRequest{Username: username, Password: password})
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
|
||||
w := httptest.NewRecorder()
|
||||
srv.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
cookies := w.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
|
||||
return cookies[0]
|
||||
}
|
||||
|
||||
func TestGetLogsPagination(t *testing.T) {
|
||||
configPath := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(configPath, "logs"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(configPath, "logs", "antholume.log"), []byte(
|
||||
"{\"level\":\"info\",\"msg\":\"one\"}\n"+
|
||||
"plain two\n"+
|
||||
"{\"level\":\"error\",\"msg\":\"three\"}\n"+
|
||||
"plain four\n",
|
||||
), 0o644))
|
||||
|
||||
cfg := &config.Config{
|
||||
ListenPort: "8080",
|
||||
DBType: "memory",
|
||||
DBName: "test",
|
||||
ConfigPath: configPath,
|
||||
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
||||
CookieEncKey: "0123456789abcdef",
|
||||
CookieSecure: false,
|
||||
CookieHTTPOnly: true,
|
||||
Version: "test",
|
||||
DemoMode: false,
|
||||
RegistrationEnabled: true,
|
||||
}
|
||||
|
||||
db := database.NewMgr(cfg)
|
||||
srv := NewServer(db, cfg, nil)
|
||||
createAdminTestUser(t, db, "admin", "password")
|
||||
cookie := loginAdminTestUser(t, srv, "admin", "password")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/logs?page=2&limit=2", nil)
|
||||
req.AddCookie(cookie)
|
||||
w := httptest.NewRecorder()
|
||||
srv.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp LogsResponse
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
require.NotNil(t, resp.Logs)
|
||||
require.Len(t, *resp.Logs, 2)
|
||||
require.NotNil(t, resp.Page)
|
||||
require.Equal(t, int64(2), *resp.Page)
|
||||
require.NotNil(t, resp.Limit)
|
||||
require.Equal(t, int64(2), *resp.Limit)
|
||||
require.NotNil(t, resp.Total)
|
||||
require.Equal(t, int64(4), *resp.Total)
|
||||
require.Nil(t, resp.NextPage)
|
||||
require.NotNil(t, resp.PreviousPage)
|
||||
require.Equal(t, int64(1), *resp.PreviousPage)
|
||||
require.Contains(t, (*resp.Logs)[0], "three")
|
||||
require.Contains(t, (*resp.Logs)[1], "plain four")
|
||||
}
|
||||
|
||||
func TestGetLogsPaginationWithBasicFilter(t *testing.T) {
|
||||
configPath := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(configPath, "logs"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(configPath, "logs", "antholume.log"), []byte(
|
||||
"{\"level\":\"info\",\"msg\":\"match-1\"}\n"+
|
||||
"{\"level\":\"info\",\"msg\":\"skip\"}\n"+
|
||||
"plain match-2\n"+
|
||||
"{\"level\":\"info\",\"msg\":\"match-3\"}\n",
|
||||
), 0o644))
|
||||
|
||||
cfg := &config.Config{
|
||||
ListenPort: "8080",
|
||||
DBType: "memory",
|
||||
DBName: "test",
|
||||
ConfigPath: configPath,
|
||||
CookieAuthKey: "test-auth-key-32-bytes-long-enough",
|
||||
CookieEncKey: "0123456789abcdef",
|
||||
CookieSecure: false,
|
||||
CookieHTTPOnly: true,
|
||||
Version: "test",
|
||||
DemoMode: false,
|
||||
RegistrationEnabled: true,
|
||||
}
|
||||
|
||||
db := database.NewMgr(cfg)
|
||||
srv := NewServer(db, cfg, nil)
|
||||
createAdminTestUser(t, db, "admin", "password")
|
||||
cookie := loginAdminTestUser(t, srv, "admin", "password")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/logs?filter=%22match%22&page=1&limit=2", nil)
|
||||
req.AddCookie(cookie)
|
||||
w := httptest.NewRecorder()
|
||||
srv.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp LogsResponse
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
require.NotNil(t, resp.Logs)
|
||||
require.Len(t, *resp.Logs, 2)
|
||||
require.NotNil(t, resp.Total)
|
||||
require.Equal(t, int64(3), *resp.Total)
|
||||
require.NotNil(t, resp.NextPage)
|
||||
require.Equal(t, int64(2), *resp.NextPage)
|
||||
}
|
||||
@@ -314,8 +314,13 @@ type LoginResponse struct {
|
||||
|
||||
// LogsResponse defines model for LogsResponse.
|
||||
type LogsResponse struct {
|
||||
Filter *string `json:"filter,omitempty"`
|
||||
Logs *[]LogEntry `json:"logs,omitempty"`
|
||||
Filter *string `json:"filter,omitempty"`
|
||||
Limit *int64 `json:"limit,omitempty"`
|
||||
Logs *[]LogEntry `json:"logs,omitempty"`
|
||||
NextPage *int64 `json:"next_page,omitempty"`
|
||||
Page *int64 `json:"page,omitempty"`
|
||||
PreviousPage *int64 `json:"previous_page,omitempty"`
|
||||
Total *int64 `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
// MessageResponse defines model for MessageResponse.
|
||||
@@ -465,6 +470,8 @@ type PostImportFormdataBody struct {
|
||||
// GetLogsParams defines parameters for GetLogs.
|
||||
type GetLogsParams struct {
|
||||
Filter *string `form:"filter,omitempty" json:"filter,omitempty"`
|
||||
Page *int64 `form:"page,omitempty" json:"page,omitempty"`
|
||||
Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateUserFormdataBody defines parameters for UpdateUser.
|
||||
@@ -862,6 +869,22 @@ func (siw *ServerInterfaceWrapper) GetLogs(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "page" -------------
|
||||
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "page", r.URL.Query(), ¶ms.Page, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "limit" -------------
|
||||
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "limit", r.URL.Query(), ¶ms.Limit, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetLogs(w, r, params)
|
||||
}))
|
||||
|
||||
@@ -594,6 +594,21 @@ components:
|
||||
$ref: '#/components/schemas/LogEntry'
|
||||
filter:
|
||||
type: string
|
||||
page:
|
||||
type: integer
|
||||
format: int64
|
||||
limit:
|
||||
type: integer
|
||||
format: int64
|
||||
next_page:
|
||||
type: integer
|
||||
format: int64
|
||||
previous_page:
|
||||
type: integer
|
||||
format: int64
|
||||
total:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
InfoResponse:
|
||||
type: object
|
||||
@@ -1764,6 +1779,18 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 1
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 1
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
|
||||
Reference in New Issue
Block a user