286 lines
9.3 KiB
Go
286 lines
9.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.nakama.town/fmartingr/hako/internal/archival/archiver"
|
|
archivalRules "git.nakama.town/fmartingr/hako/internal/archival/rules"
|
|
archivalStore "git.nakama.town/fmartingr/hako/internal/archival/store"
|
|
"git.nakama.town/fmartingr/hako/internal/extractors"
|
|
"git.nakama.town/fmartingr/hako/internal/model"
|
|
"git.nakama.town/fmartingr/hako/internal/server/webcontext"
|
|
)
|
|
|
|
// SystemHandler handles system-related requests
|
|
type SystemHandler struct {
|
|
getVersion func(ctx context.Context) (string, error)
|
|
extractorMgr *extractors.Manager
|
|
archiverMgr *archiver.Manager
|
|
archiverConfigStore *archivalStore.ArchiverConfigStore
|
|
settingsStore *archivalStore.SettingsStore
|
|
}
|
|
|
|
// NewSystemHandler creates a new SystemHandler
|
|
func NewSystemHandler(getVersion func(ctx context.Context) (string, error), extractorMgr *extractors.Manager, archiverMgr *archiver.Manager, archiverConfigStore *archivalStore.ArchiverConfigStore, settingsStore *archivalStore.SettingsStore) *SystemHandler {
|
|
return &SystemHandler{
|
|
getVersion: getVersion,
|
|
extractorMgr: extractorMgr,
|
|
archiverMgr: archiverMgr,
|
|
archiverConfigStore: archiverConfigStore,
|
|
settingsStore: settingsStore,
|
|
}
|
|
}
|
|
|
|
// SystemResponse represents a system response
|
|
type SystemResponse struct {
|
|
Version string `json:"version,omitempty"`
|
|
Commit string `json:"commit,omitempty"`
|
|
Date string `json:"date,omitempty"`
|
|
}
|
|
|
|
// HandleSystem handles GET /system
|
|
func (h *SystemHandler) HandleSystem(w http.ResponseWriter, r *http.Request) {
|
|
// Create webcontext for this request
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
|
|
response := SystemResponse{
|
|
Version: model.BuildVersion,
|
|
Commit: model.BuildCommit,
|
|
Date: model.BuildDate,
|
|
}
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(response)
|
|
}
|
|
|
|
// HandleExtractors handles GET /system/extractors
|
|
func (h *SystemHandler) HandleExtractors(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Get status of all extractors
|
|
statuses := h.extractorMgr.GetStatus()
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(statuses)
|
|
}
|
|
|
|
// HandleArchivers handles GET /system/archivers
|
|
func (h *SystemHandler) HandleArchivers(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Get status of all archivers
|
|
statuses := h.archiverMgr.GetStatus(c.Context(), h.archiverConfigStore)
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(statuses)
|
|
}
|
|
|
|
// HandleGetArchiverConfig handles GET /system/archivers/{key}/config
|
|
func (h *SystemHandler) HandleGetArchiverConfig(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archiver key from path
|
|
key := r.PathValue("key")
|
|
if key == "" {
|
|
http.Error(w, "Archiver key is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get archiver to access default config
|
|
arch, ok := h.archiverMgr.GetArchiver(key)
|
|
if !ok {
|
|
http.Error(w, fmt.Sprintf("Archiver not found: %s", key), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Try to get stored config
|
|
storedConfig, err := h.archiverConfigStore.Get(c.Context(), key)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to get archiver config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var config any
|
|
if storedConfig != nil && storedConfig.ConfigJSON != "" {
|
|
// Parse stored config JSON
|
|
if err := json.Unmarshal([]byte(storedConfig.ConfigJSON), &config); err != nil {
|
|
// If parsing fails, fall back to default
|
|
config = arch.GetDefaultConfig()
|
|
}
|
|
} else {
|
|
// No stored config, use default
|
|
config = arch.GetDefaultConfig()
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(config)
|
|
}
|
|
|
|
// HandleSaveArchiverConfig handles PUT /system/archivers/{key}/config
|
|
func (h *SystemHandler) HandleSaveArchiverConfig(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archiver key from path
|
|
key := r.PathValue("key")
|
|
if key == "" {
|
|
http.Error(w, "Archiver key is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Verify archiver exists
|
|
arch, ok := h.archiverMgr.GetArchiver(key)
|
|
if !ok {
|
|
http.Error(w, fmt.Sprintf("Archiver not found: %s", key), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Parse request body
|
|
var config map[string]any
|
|
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
|
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate config by trying to apply it
|
|
if err := arch.ApplyConfig(config); err != nil {
|
|
http.Error(w, fmt.Sprintf("Invalid config: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Marshal config back to JSON
|
|
configJSON, err := json.Marshal(config)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to marshal config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Save to database
|
|
archiverConfig := &model.ArchiverConfig{
|
|
ArchiverKey: key,
|
|
ConfigJSON: string(configJSON),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
if err := h.archiverConfigStore.Upsert(c.Context(), archiverConfig); err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to save config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{"message": "Config saved successfully"})
|
|
}
|
|
|
|
// HandleDeleteArchiverConfig handles DELETE /system/archivers/{key}/config
|
|
func (h *SystemHandler) HandleDeleteArchiverConfig(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archiver key from path
|
|
key := r.PathValue("key")
|
|
if key == "" {
|
|
http.Error(w, "Archiver key is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Verify archiver exists
|
|
_, ok := h.archiverMgr.GetArchiver(key)
|
|
if !ok {
|
|
http.Error(w, fmt.Sprintf("Archiver not found: %s", key), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Delete from database
|
|
if err := h.archiverConfigStore.Delete(c.Context(), key); err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to delete config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{"message": "Config reset to default successfully"})
|
|
}
|
|
|
|
// HandleGetRulesConfig handles GET /system/rules/config
|
|
func (h *SystemHandler) HandleGetRulesConfig(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Get rules config from settings store
|
|
rulesConfig, err := h.settingsStore.GetRulesConfig(c.Context())
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to get rules config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Convert to JSON format for frontend
|
|
// We need to manually construct JSON since Rule is an interface
|
|
rulesJSON := make([]json.RawMessage, len(rulesConfig.Rules))
|
|
for i, rule := range rulesConfig.Rules {
|
|
ruleBytes, err := json.Marshal(rule)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to marshal rule %d: %v", i, err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
rulesJSON[i] = ruleBytes
|
|
}
|
|
|
|
response := map[string]interface{}{
|
|
"rules": rulesJSON,
|
|
"default_extractors": rulesConfig.DefaultArchivers, // JSON key kept for backward compatibility
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(response)
|
|
}
|
|
|
|
// HandleSaveRulesConfig handles PUT /system/rules/config
|
|
func (h *SystemHandler) HandleSaveRulesConfig(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Read request body
|
|
var requestBody map[string]interface{}
|
|
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
|
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Convert to JSON bytes for unmarshaling
|
|
requestBytes, err := json.Marshal(requestBody)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to marshal request: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Unmarshal and validate structure
|
|
rulesConfig, err := archivalRules.UnmarshalRuleConfig(requestBytes)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Invalid rules config structure: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate each rule
|
|
for i, rule := range rulesConfig.Rules {
|
|
if err := rule.IsValid(); err != nil {
|
|
http.Error(w, fmt.Sprintf("Rule %d is invalid: %v", i, err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Save to database
|
|
if err := h.settingsStore.SetRulesConfig(c.Context(), rulesConfig); err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to save rules config: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
c.ResponseWriter().WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{"message": "Rules config saved successfully"})
|
|
}
|