Files
go-cart-actor/cmd/backoffice/fileserver.go
matst80 e1302a8ffa
All checks were successful
Build and Publish / Metadata (push) Successful in 10s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 1m30s
Build and Publish / BuildAndDeployArm64 (push) Successful in 4m22s
update
2025-10-15 09:22:00 +02:00

192 lines
4.9 KiB
Go

package main
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"git.tornberg.me/go-cart-actor/pkg/cart"
)
type FileServer struct {
// Define fields here
dataDir string
}
func NewFileServer(dataDir string) *FileServer {
return &FileServer{
dataDir: dataDir,
}
}
func listCartFiles(dir string) ([]CartFileInfo, error) {
entries, err := os.ReadDir(dir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return []CartFileInfo{}, nil
}
return nil, err
}
out := make([]CartFileInfo, 0)
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
var id uint64
var parseErr error
if strings.HasPrefix(name, "{") && strings.HasSuffix(name, "}.events.log") {
idStr := strings.TrimSuffix(strings.TrimPrefix(name, "{"), "}.events.log")
id, parseErr = strconv.ParseUint(idStr, 10, 64)
} else if strings.HasSuffix(name, ".events.log") {
base := strings.TrimSuffix(name, ".events.log")
id, parseErr = strconv.ParseUint(base, 10, 64)
} else {
continue
}
if parseErr != nil {
continue
}
p := filepath.Join(dir, name)
info, err := e.Info()
if err != nil {
continue
}
out = append(out, CartFileInfo{
ID: id,
CartId: cart.CartId(id),
Path: p,
Size: info.Size(),
Modified: info.ModTime(),
})
}
return out, nil
}
func readRawLogLines(path string) ([]string, error) {
fh, err := os.Open(path)
if err != nil {
return nil, err
}
defer fh.Close()
lines := make([]string, 0, 64)
s := bufio.NewScanner(fh)
// increase buffer to handle larger JSON lines
buf := make([]byte, 0, 1024*1024)
s.Buffer(buf, 1024*1024)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
lines = append(lines, line)
}
if err := s.Err(); err != nil {
return nil, err
}
return lines, nil
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func (fs *FileServer) CartsHandler(w http.ResponseWriter, r *http.Request) {
list, err := listCartFiles(fs.dataDir)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
// sort by modified desc
sort.Slice(list, func(i, j int) bool { return list[i].Modified.After(list[j].Modified) })
carts := make([]map[string]any, 0, len(list))
for _, it := range list {
carts = append(carts, map[string]any{
"id": it.ID,
"cartId": cart.CartId(it.ID).String(),
"filename": filepath.Base(it.Path),
"path": it.Path,
"size": it.Size,
"modified": it.Modified,
})
}
writeJSON(w, http.StatusOK, map[string]any{
"count": len(carts),
"carts": carts,
})
}
func (fs *FileServer) CartHandler(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
if idStr == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing id"})
return
}
// normalize idStr: support "{123}.events.log", "{123}", "123.events.log", "123"
if strings.HasPrefix(idStr, "{") && strings.HasSuffix(idStr, "}.events.log") {
idStr = strings.TrimSuffix(strings.TrimPrefix(idStr, "{"), "}.events.log")
} else if strings.HasPrefix(idStr, "{") && strings.HasSuffix(idStr, "}") {
idStr = strings.TrimSuffix(strings.TrimPrefix(idStr, "{"), "}")
} else if strings.HasSuffix(idStr, ".events.log") {
idStr = strings.TrimSuffix(idStr, ".events.log")
}
id, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid id"})
return
}
// reconstruct state from event log if present
grain := cart.NewCartGrain(id, time.Now())
if globalDisk != nil {
_ = globalDisk.LoadEvents(id, grain)
}
path := filepath.Join(fs.dataDir, fmt.Sprintf("%d.events.log", id))
info, err := os.Stat(path)
if err != nil && errors.Is(err, os.ErrNotExist) {
// try brace-wrapped filename as fallback
alt := filepath.Join(fs.dataDir, fmt.Sprintf("{%d}.events.log", id))
if fi, err2 := os.Stat(alt); err2 == nil {
path = alt
info = fi
} else {
if errors.Is(err2, os.ErrNotExist) {
writeJSON(w, http.StatusNotFound, map[string]string{"error": "cart not found"})
return
}
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err2.Error()})
return
}
} else if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
lines, err := readRawLogLines(path)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]any{
"id": id,
"cartId": cart.CartId(id).String(),
"state": grain,
"rawLog": lines,
"meta": map[string]any{
"size": info.Size(),
"modified": info.ModTime(),
"path": path,
},
})
}