BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
+122
-99
@@ -4,10 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ledgerBaseURL = getEnv("LEDGER_URL", "http://localhost:3250")
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
@@ -15,6 +14,8 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
var ledgerBaseURL = getEnv("LEDGER_URL", "http://172.17.0.1:3250")
|
||||
|
||||
// LedgerClient handles communication with aamos-ledger
|
||||
type LedgerClient struct {
|
||||
BaseURL string
|
||||
@@ -37,19 +38,39 @@ func NewLedgerFinanceHandler() *LedgerFinanceHandler {
|
||||
return &LedgerFinanceHandler{Client: NewLedgerClient()}
|
||||
}
|
||||
|
||||
// GetBalanceSheet returns trial balance from ledger
|
||||
func (h *LedgerFinanceHandler) GetBalanceSheet(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/reports/balance")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
period := r.URL.Query().Get("period")
|
||||
if period == "" {
|
||||
period = time.Now().Format("2006-01")
|
||||
}
|
||||
|
||||
resp, err := h.Client.Get("/api/ledger/trial-balance?period=" + period)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
// Fallback to mock data
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"assets": []map[string]interface{}{
|
||||
{"account": "1930 - Checkkonto", "amount": 245000},
|
||||
{"account": "1940 - Sparkonto", "amount": 500000},
|
||||
{"account": "1510 - Kundfordringar", "amount": 125000},
|
||||
},
|
||||
"liabilities": []map[string]interface{}{
|
||||
{"account": "2440 - Leverantörsskulder", "amount": 85000},
|
||||
{"account": "2013 - Aktiekapital", "amount": 100000},
|
||||
},
|
||||
"equity": []map[string]interface{}{
|
||||
{"account": "2091 - Balanserad vinst", "amount": 485000},
|
||||
},
|
||||
"total_assets": 870000,
|
||||
"total_liabilities": 185000,
|
||||
"total_equity": 685000,
|
||||
"period": period,
|
||||
})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
@@ -60,91 +81,60 @@ func (h *LedgerFinanceHandler) GetBalanceSheet(w http.ResponseWriter, r *http.Re
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
// GetIncomeStatement returns income statement (not yet implemented in ledger)
|
||||
func (h *LedgerFinanceHandler) GetIncomeStatement(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/reports/income")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
return
|
||||
}
|
||||
|
||||
// Return mock data until ledger implements this
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"revenue": 245000,
|
||||
"expenses": 180000,
|
||||
"net_income": 65000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
// GetMomsReport returns VAT report (not yet implemented in ledger)
|
||||
func (h *LedgerFinanceHandler) GetMomsReport(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/tax/moms")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"moms_in": 61250,
|
||||
"moms_ut": 35000,
|
||||
"moms_att_betala": 26250,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccounts returns chart of accounts from ledger
|
||||
func (h *LedgerFinanceHandler) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/accounts")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
resp, err := h.Client.Get("/api/v1/accounts")
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
// Fallback to mock data
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"accounts": []map[string]interface{}{
|
||||
{"id": "1930", "name": "Checkkonto", "type": "asset", "balance": 245000},
|
||||
{"id": "1940", "name": "Sparkonto", "type": "asset", "balance": 500000},
|
||||
{"id": "1510", "name": "Kundfordringar", "type": "asset", "balance": 125000},
|
||||
{"id": "2440", "name": "Leverantörsskulder", "type": "liability", "balance": 85000},
|
||||
{"id": "2013", "name": "Aktiekapital", "type": "equity", "balance": 100000},
|
||||
{"id": "2091", "name": "Balanserad vinst", "type": "equity", "balance": 485000},
|
||||
{"id": "3001", "name": "Försäljning tjänster", "type": "revenue", "balance": 450000},
|
||||
{"id": "6100", "name": "Löner", "type": "expense", "balance": 180000},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (h *LedgerFinanceHandler) GetCustomers(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/customers")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
var accounts []map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&accounts); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"accounts": accounts})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,25 +142,58 @@ func (h *LedgerFinanceHandler) GetCustomers(w http.ResponseWriter, r *http.Reque
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
// GetInvoices returns invoices (mock until implemented)
|
||||
func (h *LedgerFinanceHandler) GetInvoices(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := h.Client.Get("/api/ledger/invoices")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusServiceUnavailable, "ledger unavailable")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
writeError(w, resp.StatusCode, "ledger error")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "decode error")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(result)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"invoices": []map[string]interface{}{
|
||||
{"id": "INV-001", "customer": "Test AB", "amount": 25000, "status": "paid", "due_date": "2026-07-30"},
|
||||
{"id": "INV-002", "customer": "Acme Corp", "amount": 45000, "status": "pending", "due_date": "2026-08-15"},
|
||||
},
|
||||
"total": 2,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCashflow returns cashflow (mock until implemented)
|
||||
func (h *LedgerFinanceHandler) GetCashflow(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"inflow": 320000,
|
||||
"outflow": 180000,
|
||||
"net": 140000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
// GetBudget returns budget (mock until implemented)
|
||||
func (h *LedgerFinanceHandler) GetBudget(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"budget": 500000,
|
||||
"actual": 245000,
|
||||
"remaining": 255000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
// CreateExpense creates an expense (mock until implemented)
|
||||
func (h *LedgerFinanceHandler) CreateExpense(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": "EXP-001",
|
||||
"status": "created",
|
||||
})
|
||||
}
|
||||
|
||||
// ListExpenses lists expenses (mock until implemented)
|
||||
func (h *LedgerFinanceHandler) ListExpenses(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"expenses": []map[string]interface{}{
|
||||
{"id": "EXP-001", "category": "Boende", "amount": 8500, "date": "2026-07-01"},
|
||||
{"id": "EXP-002", "category": "Mat", "amount": 3200, "date": "2026-07-05"},
|
||||
},
|
||||
"total": 2,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MockLedgerHandler provides mock data when real ledger is unavailable
|
||||
type MockLedgerHandler struct{}
|
||||
|
||||
func NewMockLedgerHandler() *MockLedgerHandler {
|
||||
return &MockLedgerHandler{}
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetBalanceSheet(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"assets": []map[string]interface{}{
|
||||
{"account": "1930 - Checkkonto", "amount": 245000},
|
||||
{"account": "1940 - Sparkonto", "amount": 500000},
|
||||
{"account": "1510 - Kundfordringar", "amount": 125000},
|
||||
},
|
||||
"liabilities": []map[string]interface{}{
|
||||
{"account": "2440 - Leverantörsskulder", "amount": 85000},
|
||||
{"account": "2013 - Aktiekapital", "amount": 100000},
|
||||
},
|
||||
"equity": []map[string]interface{}{
|
||||
{"account": "2091 - Balanserad vinst", "amount": 485000},
|
||||
},
|
||||
"total_assets": 870000,
|
||||
"total_liabilities": 185000,
|
||||
"total_equity": 685000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetIncomeStatement(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"revenue": []map[string]interface{}{
|
||||
{"account": "3001 - Försäljning tjänster", "amount": 450000},
|
||||
{"account": "3002 - Försäljning produkter", "amount": 125000},
|
||||
},
|
||||
"expenses": []map[string]interface{}{
|
||||
{"account": "6100 - Löner", "amount": 180000},
|
||||
{"account": "6200 - Hyra", "amount": 45000},
|
||||
{"account": "6300 - Marknadsföring", "amount": 35000},
|
||||
{"account": "6400 - IT-kostnader", "amount": 25000},
|
||||
},
|
||||
"total_revenue": 575000,
|
||||
"total_expenses": 285000,
|
||||
"net_income": 290000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetMomsReport(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"moms_in": 143750,
|
||||
"moms_ut": 71250,
|
||||
"moms_att_betala": 72500,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"accounts": []map[string]interface{}{
|
||||
{"id": "1930", "name": "Checkkonto", "type": "asset", "balance": 245000},
|
||||
{"id": "1940", "name": "Sparkonto", "type": "asset", "balance": 500000},
|
||||
{"id": "1510", "name": "Kundfordringar", "type": "asset", "balance": 125000},
|
||||
{"id": "2440", "name": "Leverantörsskulder", "type": "liability", "balance": 85000},
|
||||
{"id": "2013", "name": "Aktiekapital", "type": "equity", "balance": 100000},
|
||||
{"id": "2091", "name": "Balanserad vinst", "type": "equity", "balance": 485000},
|
||||
{"id": "3001", "name": "Försäljning tjänster", "type": "revenue", "balance": 450000},
|
||||
{"id": "6100", "name": "Löner", "type": "expense", "balance": 180000},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetInvoices(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"invoices": []map[string]interface{}{
|
||||
{"id": "INV-001", "customer": "Test AB", "amount": 25000, "status": "paid", "due_date": "2026-07-30"},
|
||||
{"id": "INV-002", "customer": "Acme Corp", "amount": 45000, "status": "pending", "due_date": "2026-08-15"},
|
||||
{"id": "INV-003", "customer": "Stark Industries", "amount": 125000, "status": "overdue", "due_date": "2026-06-30"},
|
||||
},
|
||||
"total": 3,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetCashflow(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"inflow": 320000,
|
||||
"outflow": 180000,
|
||||
"net": 140000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) GetBudget(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"budget": 500000,
|
||||
"actual": 245000,
|
||||
"remaining": 255000,
|
||||
"period": time.Now().Format("2006-01"),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) CreateExpense(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": "EXP-001",
|
||||
"status": "created",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MockLedgerHandler) ListExpenses(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"expenses": []map[string]interface{}{
|
||||
{"id": "EXP-001", "category": "Boende", "amount": 8500, "date": "2026-07-01"},
|
||||
{"id": "EXP-002", "category": "Mat", "amount": 3200, "date": "2026-07-05"},
|
||||
{"id": "EXP-003", "category": "Resa", "amount": 4500, "date": "2026-07-10"},
|
||||
},
|
||||
"total": 3,
|
||||
})
|
||||
}
|
||||
@@ -28,7 +28,7 @@ type Contract struct {
|
||||
StartDate *time.Time `json:"start_date"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
RenewalDate *time.Time `json:"renewal_date"`
|
||||
DocumentURL string `json:"document_url"`
|
||||
DocumentURL *string `json:"document_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ type Product struct {
|
||||
func (h *SalesHandler) ListDeals(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
if status == "" {
|
||||
status = "open"
|
||||
status = "active"
|
||||
}
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SettingsHandler manages system configuration
|
||||
type SettingsHandler struct {
|
||||
mu sync.RWMutex
|
||||
settings map[string]interface{}
|
||||
path string
|
||||
}
|
||||
|
||||
func NewSettingsHandler() *SettingsHandler {
|
||||
h := &SettingsHandler{
|
||||
settings: make(map[string]interface{}),
|
||||
path: "/app/config/system.json",
|
||||
}
|
||||
h.load()
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) load() {
|
||||
data, err := os.ReadFile(h.path)
|
||||
if err != nil {
|
||||
// Use defaults
|
||||
h.settings = h.defaultSettings()
|
||||
return
|
||||
}
|
||||
json.Unmarshal(data, &h.settings)
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) save() error {
|
||||
data, err := json.MarshalIndent(h.settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(h.path, data, 0644)
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) defaultSettings() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"appearance": map[string]interface{}{
|
||||
"theme": "light",
|
||||
"density": "comfortable",
|
||||
"sidebar_width": 240,
|
||||
"animations": true,
|
||||
},
|
||||
"dashboard": map[string]interface{}{
|
||||
"greeting_enabled": true,
|
||||
"kpi_refresh": 300,
|
||||
"activity_max": 10,
|
||||
},
|
||||
"notifications": map[string]interface{}{
|
||||
"in_app": true,
|
||||
"email": false,
|
||||
"slack": false,
|
||||
},
|
||||
"advanced": map[string]interface{}{
|
||||
"api_rate_limit": 1000,
|
||||
"cache_ttl": 300,
|
||||
"log_level": "info",
|
||||
"export_max_rows": 10000,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetSettings returns all settings
|
||||
func (h *SettingsHandler) GetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(h.settings)
|
||||
}
|
||||
|
||||
// UpdateSettings updates settings
|
||||
func (h *SettingsHandler) UpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
var updates map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Merge updates
|
||||
for key, value := range updates {
|
||||
h.settings[key] = value
|
||||
}
|
||||
|
||||
if err := h.save(); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to save settings")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(h.settings)
|
||||
}
|
||||
|
||||
// GetModuleConfig returns module configuration
|
||||
func (h *SettingsHandler) GetModuleConfig(w http.ResponseWriter, r *http.Request) {
|
||||
module := r.URL.Query().Get("module")
|
||||
if module == "" {
|
||||
writeError(w, http.StatusBadRequest, "module required")
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
modules, ok := h.settings["modules"].(map[string]interface{})
|
||||
if !ok {
|
||||
writeError(w, http.StatusNotFound, "modules not configured")
|
||||
return
|
||||
}
|
||||
|
||||
config, ok := modules[module]
|
||||
if !ok {
|
||||
writeError(w, http.StatusNotFound, "module not found")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(config)
|
||||
}
|
||||
|
||||
// ToggleModule enables/disables a module
|
||||
func (h *SettingsHandler) ToggleModule(w http.ResponseWriter, r *http.Request) {
|
||||
module := r.URL.Query().Get("module")
|
||||
if module == "" {
|
||||
writeError(w, http.StatusBadRequest, "module required")
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
modules, ok := h.settings["modules"].(map[string]interface{})
|
||||
if !ok {
|
||||
modules = make(map[string]interface{})
|
||||
h.settings["modules"] = modules
|
||||
}
|
||||
|
||||
config, ok := modules[module].(map[string]interface{})
|
||||
if !ok {
|
||||
config = map[string]interface{}{"enabled": false}
|
||||
}
|
||||
|
||||
// Toggle enabled state
|
||||
if enabled, ok := config["enabled"].(bool); ok {
|
||||
config["enabled"] = !enabled
|
||||
} else {
|
||||
config["enabled"] = true
|
||||
}
|
||||
modules[module] = config
|
||||
|
||||
if err := h.save(); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to save")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(config)
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.GetSettings(w, r)
|
||||
case http.MethodPut:
|
||||
h.UpdateSettings(w, r)
|
||||
default:
|
||||
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ type TicketComment struct {
|
||||
func (h *SupportHandler) ListTickets(w http.ResponseWriter, r *http.Request) {
|
||||
status := r.URL.Query().Get("status")
|
||||
if status == "" {
|
||||
status = "open"
|
||||
status = "active"
|
||||
}
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
|
||||
Reference in New Issue
Block a user