229 lines
7.8 KiB
Go
229 lines
7.8 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// WebTemplateAPI exposes endpoints to manage web templates
|
|
type WebTemplateAPI struct {
|
|
templateManager interface {
|
|
GetTemplate(name string) (string, error)
|
|
SaveTemplate(name, content string) error
|
|
ListTemplates() ([]string, error)
|
|
DeleteTemplate(name string) error
|
|
ValidateTemplate(content string) error
|
|
CreateDefaultTemplate(name string) error
|
|
}
|
|
}
|
|
|
|
// NewWebTemplateAPI constructs the API wrapper
|
|
func NewWebTemplateAPI(tm interface {
|
|
GetTemplate(name string) (string, error)
|
|
SaveTemplate(name, content string) error
|
|
ListTemplates() ([]string, error)
|
|
DeleteTemplate(name string) error
|
|
ValidateTemplate(content string) error
|
|
CreateDefaultTemplate(name string) error
|
|
}) *WebTemplateAPI {
|
|
return &WebTemplateAPI{templateManager: tm}
|
|
}
|
|
|
|
// RegisterRoutes registers all endpoints (guarded by security middleware from caller)
|
|
func (wta *WebTemplateAPI) RegisterRoutes(mux *http.ServeMux, sm *SecurityManager) {
|
|
mux.HandleFunc("/api/webtemplates", sm.APIAuthMiddleware(sm.RoleMiddleware("admin")(wta.handleTemplates)))
|
|
mux.HandleFunc("/api/webtemplates/", sm.APIAuthMiddleware(sm.RoleMiddleware("admin")(wta.handleTemplate)))
|
|
mux.HandleFunc("/api/webtemplates/validate", sm.APIAuthMiddleware(sm.RoleMiddleware("admin")(wta.handleValidateTemplate)))
|
|
}
|
|
|
|
// handleTemplates supports list and create
|
|
func (wta *WebTemplateAPI) handleTemplates(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
wta.handleListTemplates(w, r)
|
|
case http.MethodPost:
|
|
wta.handleCreateTemplate(w, r)
|
|
default:
|
|
wta.sendJSONError(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// handleTemplate supports get/update/delete for a specific template name in the path
|
|
func (wta *WebTemplateAPI) handleTemplate(w http.ResponseWriter, r *http.Request) {
|
|
name := strings.TrimPrefix(r.URL.Path, "/api/webtemplates/")
|
|
if name == "" || name == "/" {
|
|
wta.sendJSONError(w, "Template name required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
// Normalize (strip leading slash, append .html if missing)
|
|
name = strings.TrimPrefix(strings.TrimSpace(name), "/")
|
|
if name != "" && !strings.HasSuffix(strings.ToLower(name), ".html") {
|
|
name += ".html"
|
|
}
|
|
if !wta.isValidTemplateName(name) {
|
|
wta.sendJSONError(w, "Invalid template name", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
wta.handleGetTemplate(w, r, name)
|
|
case http.MethodPut:
|
|
wta.handleUpdateTemplate(w, r, name)
|
|
case http.MethodDelete:
|
|
wta.handleDeleteTemplate(w, r, name)
|
|
default:
|
|
wta.sendJSONError(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// List
|
|
func (wta *WebTemplateAPI) handleListTemplates(w http.ResponseWriter, r *http.Request) {
|
|
names, err := wta.templateManager.ListTemplates()
|
|
if err != nil {
|
|
wta.sendJSONError(w, "Failed to list templates", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"templates": names,
|
|
"count": len(names),
|
|
})
|
|
}
|
|
|
|
// Get
|
|
func (wta *WebTemplateAPI) handleGetTemplate(w http.ResponseWriter, r *http.Request, name string) {
|
|
content, err := wta.templateManager.GetTemplate(name)
|
|
if err != nil {
|
|
wta.sendJSONError(w, "Template not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"name": name, "content": content})
|
|
}
|
|
|
|
// Create
|
|
func (wta *WebTemplateAPI) handleCreateTemplate(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Content string `json:"content"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
wta.sendJSONError(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
if req.Name != "" && !strings.HasSuffix(strings.ToLower(req.Name), ".html") {
|
|
req.Name += ".html"
|
|
}
|
|
if !wta.isValidTemplateName(req.Name) {
|
|
wta.sendJSONError(w, fmt.Sprintf("Invalid template name: %v", req.Name), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Content) == "" {
|
|
if err := wta.templateManager.CreateDefaultTemplate(req.Name); err != nil {
|
|
wta.sendJSONError(w, fmt.Sprintf("Failed to create default template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
if err := wta.templateManager.ValidateTemplate(req.Content); err != nil {
|
|
wta.sendJSONError(w, fmt.Sprintf("Invalid template: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := wta.templateManager.SaveTemplate(req.Name, req.Content); err != nil {
|
|
wta.sendJSONError(w, fmt.Sprintf("Failed to save template: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"success": "Template created successfully", "name": req.Name})
|
|
}
|
|
|
|
// Update
|
|
func (wta *WebTemplateAPI) handleUpdateTemplate(w http.ResponseWriter, r *http.Request, name string) {
|
|
var req struct{ Content string `json:"content"` }
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
wta.sendJSONError(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := wta.templateManager.ValidateTemplate(req.Content); err != nil {
|
|
wta.sendJSONError(w, fmt.Sprintf("Invalid template: %v", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := wta.templateManager.SaveTemplate(name, req.Content); err != nil {
|
|
wta.sendJSONError(w, "Failed to update template", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"success": "Template updated successfully", "name": name})
|
|
}
|
|
|
|
// Delete
|
|
func (wta *WebTemplateAPI) handleDeleteTemplate(w http.ResponseWriter, r *http.Request, name string) {
|
|
if err := wta.templateManager.DeleteTemplate(name); err != nil {
|
|
wta.sendJSONError(w, "Failed to delete template", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"success": "Template deleted successfully", "name": name})
|
|
}
|
|
|
|
// Validate (content only)
|
|
func (wta *WebTemplateAPI) handleValidateTemplate(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
wta.sendJSONError(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
var req struct{ Content string `json:"content"` }
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
wta.sendJSONError(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := wta.templateManager.ValidateTemplate(req.Content); err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"valid": false, "error": err.Error()})
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"valid": true})
|
|
}
|
|
|
|
// Validation helper for names
|
|
func (wta *WebTemplateAPI) isValidTemplateName(name string) bool {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return false
|
|
}
|
|
if !strings.HasSuffix(strings.ToLower(name), ".html") {
|
|
return false
|
|
}
|
|
// Disallow path separators and traversal
|
|
if strings.Contains(name, "/") || strings.Contains(name, "\\") || strings.Contains(name, "..") {
|
|
return false
|
|
}
|
|
// Must be a base filename
|
|
if name != filepath.Base(name) {
|
|
return false
|
|
}
|
|
if len(name) > 100 {
|
|
return false
|
|
}
|
|
// Only safe characters
|
|
for i := 0; i < len(name); i++ {
|
|
c := name[i]
|
|
if !(c == '-' || c == '_' || c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (wta *WebTemplateAPI) sendJSONError(w http.ResponseWriter, message string, statusCode int) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"error": message})
|
|
}
|