Files
AetherForge/server/internal/api/spread_export.go
AetherForge a32860b0d9
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
feat: alive UI wave, galaxy presence, spread and fleet enhancements
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
2026-06-04 22:36:17 -07:00

107 lines
2.4 KiB
Go

package api
import (
"archive/zip"
"bytes"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
)
var slugSanitize = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
// sanitizeExportSlug lowercases and strips unsafe characters for filenames and campaign segments.
func sanitizeExportSlug(s string) string {
s = strings.TrimSpace(s)
s = strings.ToLower(s)
s = slugSanitize.ReplaceAllString(s, "-")
s = strings.Trim(s, "-.")
if s == "" {
return "site"
}
if len(s) > 48 {
s = s[:48]
}
return s
}
// zipTemplateReplacements walks templateDir, applies repl to file contents, and writes a ZIP archive.
// remap rewrites archive entry paths (e.g. plugin-template → my-site).
func zipTemplateReplacements(templateDir string, repl map[string]string, remap func(rel string) string) ([]byte, error) {
templateDir, err := filepath.Abs(templateDir)
if err != nil {
return nil, err
}
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
err = filepath.Walk(templateDir, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil || info.IsDir() {
return walkErr
}
rel, err := filepath.Rel(templateDir, path)
if err != nil {
return err
}
rel = filepath.ToSlash(rel)
if remap != nil {
rel = remap(rel)
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
content := string(data)
for k, v := range repl {
content = strings.ReplaceAll(content, k, v)
}
w, err := zw.Create(rel)
if err != nil {
return err
}
_, err = io.WriteString(w, content)
return err
})
if err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func writeZipAttachment(w http.ResponseWriter, filename string, data []byte) {
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
w.Write(data)
}
func slugDisplayName(slug string) string {
parts := strings.Split(slug, "-")
for i, p := range parts {
if p == "" {
continue
}
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
return strings.Join(parts, " ")
}
func buildQuerySuffix(buildID, campaign string) (querySuffix, getQuerySuffix string) {
var qparts []string
if buildID != "" {
qparts = append(qparts, "pin="+buildID)
}
if campaign != "" {
qparts = append(qparts, "c="+campaign)
}
if len(qparts) == 0 {
return "", ""
}
joined := strings.Join(qparts, "&")
return "?" + joined, "&" + joined
}