package main import ( "bufio" "encoding/json" "errors" "fmt" "net/http" "os" "path/filepath" "sort" "strconv" "strings" ) 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() m := cartFileRe.FindStringSubmatch(name) if m == nil { continue } idStr := m[1] id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { continue } p := filepath.Join(dir, name) info, err := e.Info() if err != nil { continue } out = append(out, CartFileInfo{ ID: 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) }) writeJSON(w, http.StatusOK, map[string]any{ "count": len(list), "carts": list, }) } 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 } // allow both decimal id and filename-like with suffix 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 } path := filepath.Join(fs.dataDir, fmt.Sprintf("%d.events.log", id)) info, err := os.Stat(path) if err != nil { if errors.Is(err, os.ErrNotExist) { writeJSON(w, http.StatusNotFound, map[string]string{"error": "cart not found"}) return } 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, "rawLog": lines, "meta": map[string]any{ "size": info.Size(), "modified": info.ModTime(), "path": path, }, }) }