Files
triggerssmith/api/block/handle.go
2026-01-03 15:41:21 +02:00

112 lines
3.3 KiB
Go

// Package block provides functionality to load HTML blocks with associated content, JavaScript, and CSS from the filesystem.
// API Endpoint:
//
// /api/block/{blockPath}
//
// Example:
//
// /api/block/header would load the block located at {BlockDir}/header/
package api_block
import (
"encoding/json"
"log/slog"
"net/http"
"os"
"path/filepath"
"git.oblat.lv/alex/triggerssmith/internal/config"
"git.oblat.lv/alex/triggerssmith/internal/server"
"github.com/go-chi/chi/v5"
)
type Block struct {
Content string `json:"content"`
JS string `json:"js"`
CSS string `json:"css"`
}
type blockHandler struct {
cfg *config.Config
}
func MustRoute(config *config.Config) func(chi.Router) {
if config == nil {
panic("config is nil")
}
h := &blockHandler{
cfg: config,
}
return func(r chi.Router) {
r.Get("/*", h.handleBlock)
}
}
// @Summary Get block
// @Tags block
// @Produce json
// @Param blockPath path string true "Block Path" example(menu)
// @Success 200 {object} Block
// @Failure 403 {object} server.ProblemDetails
// @Failure 404 {object} server.ProblemDetails
// @Failure 500 {object} server.ProblemDetails
// @Router /api/block/{blockPath} [get]
func (h *blockHandler) handleBlock(w http.ResponseWriter, r *http.Request) {
if !h.cfg.Server.BlockConfig.Enabled {
server.WriteProblem(w, http.StatusForbidden, "/errors/block/block-serving-disabled", "Block serving is disabled", "Block serving is disabled", r)
return
}
blockPath := r.URL.Path[len("/api/block/"):]
block, err := LoadBlock(blockPath, h.cfg)
if err != nil {
slog.Error("failed to load block", slog.String("path", blockPath), slog.String("err", err.Error()))
server.WriteProblem(w, http.StatusInternalServerError, "/errors/internal-server-error", "Internal Server Error", "failed to load block", r)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(block.ToJSON()))
}
// LoadBlock loads a block from the filesystem given its path and configuration.
// It reads the content, JavaScript, and CSS files associated with the block.
// If err is not nil, it indicates a failure in reading the block files.
func LoadBlock(path string, cfg *config.Config) (*Block, error) {
slog.Debug("loading block", slog.String("path", path))
path = filepath.Join(cfg.Server.BlockConfig.BlockDir, path)
var block Block
var err error
contentPath := filepath.Join(path, "content.md")
jsPath := filepath.Join(path, "script.js")
cssPath := filepath.Join(path, "style.css")
if b, err := os.ReadFile(contentPath); err == nil {
block.Content = string(b)
} else {
slog.Warn("failed to read block content", slog.String("path", contentPath), slog.String("err", err.Error()))
}
if b, err := os.ReadFile(jsPath); err == nil {
block.JS = string(b)
} else {
slog.Warn("failed to read block JS", slog.String("path", contentPath), slog.String("err", err.Error()))
}
if b, err := os.ReadFile(cssPath); err == nil {
block.CSS = string(b)
} else {
slog.Warn("failed to read block CSS", slog.String("path", contentPath), slog.String("err", err.Error()))
}
return &block, err
}
func (b *Block) ToJSON() string {
jsonData, err := json.Marshal(b)
if err != nil {
slog.Error("failed to marshal block to JSON", slog.String("err", err.Error()))
return "{}"
}
return string(jsonData)
}