From 92cd81e24cb499dddec5b6447731d8b914ec90c0 Mon Sep 17 00:00:00 2001 From: tavo Date: Mon, 30 Jun 2025 18:03:49 -0600 Subject: [PATCH] checkpoint --- config/config.go | 40 +++++++++++++++++ go.mod | 7 ++- go.sum | 6 +++ main.go | 14 ++++++ scripts/minify.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++ views/views.go | 76 +++++++++++++++++++++++++++++++ 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 config/config.go create mode 100644 scripts/minify.go create mode 100644 views/views.go diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..a067f63 --- /dev/null +++ b/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "strings" +) + +var basePartials = []string{ + "views/baseof.html", + "views/_partials/head.html", + "views/_partials/header.html", + "views/_partials/footer.html", +} + +var ViewMap = map[string][]string{ + "index-page": append(basePartials, "views/index.html", "views/index-page.html"), + "login-page": append(basePartials, "views/login.html", "views/login-page.html"), + "dashboard-page": append(basePartials, "views/dashboard.html", "views/dashboard-page.html", "views/control-madre.html"), + "suppliers-page": append(basePartials, "views/suppliers.html", "views/suppliers-page.html"), + "fse-page": append(basePartials, "views/fse.html", "views/fse-page.html"), + "panel-page": append(basePartials, "views/panel.html", "views/panel-page.html", "views/users.html", "views/user.html"), + + "login": {"views/login.html"}, + "dashboard": {"views/dashboard.html", "views/control-madre.html"}, + "control-madre": {"views/control-madre.html"}, + "user": {"views/user.html"}, + "users": {"views/users.html", "views/user.html"}, + "success-button": {"views/success-button.html"}, + "fail-button": {"views/fail-button.html"}, +} + +var FuncMap = map[string]any{ + "uppercase": func(s string) string { return strings.ToUpper(s) }, + "firstWord": func(s string) string { + words := strings.Fields(s) + if len(words) > 0 { + return words[0] + } + return "" + }, +} diff --git a/go.mod b/go.mod index 9847650..ddc50b2 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,9 @@ module git.tavo.one/tavo/axiom go 1.22.1 -require github.com/mattn/go-sqlite3 v1.14.28 // indirect +require ( + github.com/mattn/go-sqlite3 v1.14.28 + github.com/tdewolff/minify/v2 v2.23.8 +) + +require github.com/tdewolff/parse/v2 v2.8.1 // indirect diff --git a/go.sum b/go.sum index 42e5bac..28312e3 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,8 @@ github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/tdewolff/minify/v2 v2.23.8 h1:tvjHzRer46kwOfpdCBCWsDblCw3QtnLJRd61pTVkyZ8= +github.com/tdewolff/minify/v2 v2.23.8/go.mod h1:VW3ISUd3gDOZuQ/jwZr4sCzsuX+Qvsx87FDMjk6Rvno= +github.com/tdewolff/parse/v2 v2.8.1 h1:J5GSHru6o3jF1uLlEKVXkDxxcVx6yzOlIVIotK4w2po= +github.com/tdewolff/parse/v2 v2.8.1/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= +github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= +github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= diff --git a/main.go b/main.go index c1d6a99..b8b07e0 100644 --- a/main.go +++ b/main.go @@ -6,12 +6,21 @@ import ( "os" "os/signal" "syscall" + "embed" "git.tavo.one/tavo/axiom/database" "git.tavo.one/tavo/axiom/handlers" "git.tavo.one/tavo/axiom/storage" + "git.tavo.one/tavo/axiom/config" + "git.tavo.one/tavo/axiom/views" ) +// //go:embed static/* +// var publicFS embed.FS + +//go:embed views/* +var viewFS embed.FS + func init() { needed := []string{ "PRODUCTION", @@ -29,6 +38,11 @@ func init() { } func main() { + err := views.Init(viewFS, config.ViewMap, config.FuncMap) + if err != nil { + log.Fatalf("failed to initialize templates: %v", err) + } + db, err := database.Init(os.Getenv("DB_CONNDVR"), os.Getenv("DB_CONNSTR")) if err != nil { log.Fatalf("failed to initialize database: %v", err) diff --git a/scripts/minify.go b/scripts/minify.go new file mode 100644 index 0000000..ee772d9 --- /dev/null +++ b/scripts/minify.go @@ -0,0 +1,112 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io/fs" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/tdewolff/minify/v2" + "github.com/tdewolff/minify/v2/css" + "github.com/tdewolff/minify/v2/html" + "github.com/tdewolff/minify/v2/js" + "github.com/tdewolff/minify/v2/json" + "github.com/tdewolff/minify/v2/svg" + "github.com/tdewolff/minify/v2/xml" +) + +func main() { + srcDir := flag.String("src", "", "Source directory to minify") + dstDir := flag.String("dst", "", "Destination directory for minified files") + flag.Parse() + + if *srcDir == "" || *dstDir == "" { + fmt.Println("Usage: minifyfolder -src path/to/source -dst path/to/dest") + os.Exit(1) + } + + m := minify.New() + m.AddFunc("text/html", html.Minify) + m.AddFunc("text/css", css.Minify) + m.AddFunc("application/javascript", js.Minify) + m.AddFunc("application/json", json.Minify) + m.AddFunc("image/svg+xml", svg.Minify) + m.AddFunc("text/xml", xml.Minify) + + err := filepath.WalkDir(*srcDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil // skip directories + } + + ext := strings.ToLower(filepath.Ext(path)) + mime := mimeTypeFromExt(ext) + if mime == "" { + // unsupported file type, skip + return nil + } + + relPath, err := filepath.Rel(*srcDir, path) + if err != nil { + return err + } + + dstPath := filepath.Join(*dstDir, relPath) + err = os.MkdirAll(filepath.Dir(dstPath), 0755) + if err != nil { + return err + } + + err = minifyFile(m, mime, path, dstPath) + if err != nil { + return fmt.Errorf("failed to minify %s: %w", path, err) + } + + fmt.Printf("Minified %s -> %s\n", path, dstPath) + return nil + }) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error during minify: %v\n", err) + os.Exit(1) + } +} + +func mimeTypeFromExt(ext string) string { + switch ext { + case ".html", ".htm": + return "text/html" + case ".css": + return "text/css" + case ".js": + return "application/javascript" + case ".json": + return "application/json" + case ".svg": + return "image/svg+xml" + case ".xml": + return "text/xml" + default: + return "" + } +} + +func minifyFile(m *minify.M, mime, srcPath, dstPath string) error { + data, err := ioutil.ReadFile(srcPath) + if err != nil { + return err + } + + minified, err := m.Bytes(mime, data) + if err != nil { + return err + } + + return ioutil.WriteFile(dstPath, minified, 0644) +} diff --git a/views/views.go b/views/views.go new file mode 100644 index 0000000..5f474e6 --- /dev/null +++ b/views/views.go @@ -0,0 +1,76 @@ +package views + +import ( + "bytes" + "embed" + "fmt" + "html/template" + "io" + "net/http" + "path/filepath" + "strings" +) + +var ( + TemplateCache map[string]*template.Template +) + +func Init(viewFS embed.FS, viewMap map[string][]string, funcMap map[string]any) error { + var err error + TemplateCache, err = initTemplates(viewFS, viewMap, funcMap) + if err != nil { + return fmt.Errorf("failed to initialize templates: %v", err) + } + return nil +} + +func initTemplates(viewFS embed.FS, viewMap map[string][]string, funcMap map[string]any) (map[string]*template.Template, error) { + cache := make(map[string]*template.Template) + tmplFuncMap := template.FuncMap(funcMap) + + for name, paths := range viewMap { + if len(paths) == 0 { + continue + } + + rootName := strings.TrimSuffix(filepath.Base(paths[0]), filepath.Ext(paths[0])) + tmpl := template.New(rootName).Funcs(tmplFuncMap) + + var combined []byte + for _, path := range paths { + content, err := viewFS.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("reading %s: %w", path, err) + } + combined = append(combined, content...) + combined = append(combined, '\n') + } + + parsed, err := tmpl.Parse(string(combined)) + if err != nil { + return nil, fmt.Errorf("parsing template %s: %w", name, err) + } + + cache[name] = parsed + } + + return cache, nil +} + +func RenderHTML(w http.ResponseWriter, name string, data any) error { + tmpl, ok := TemplateCache[name] + if !ok { + http.Error(w, "template not found: "+name, http.StatusNotFound) + return fmt.Errorf("template %q not found", name) + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, tmpl.Name(), data); err != nil { + return fmt.Errorf("failed to execute template %q: %w", name, err) + } + + _, err := io.Copy(w, &buf) + return err +}