feat: migrate templates to embedded filesystem

- Move templates from web/templates/ to webtemplates/templates/
- Replace file-based template loading with Go embed
- Remove external template directory dependency from Dockerfile
- Add webtemplates package with embedded template functionality
- Include comprehensive tests for embedded templates
- Update server initialization to use new embedded template system

This change makes the application self-contained by embedding templates
directly in the binary, eliminating the need for external template files
at runtime.
This commit is contained in:
Benjamin
2025-09-14 21:18:16 +02:00
parent bde1049343
commit c1595ffe3e
12 changed files with 88 additions and 36 deletions

View File

@@ -51,7 +51,6 @@ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Set working directory and copy application files
WORKDIR /app
COPY --from=builder /app/ackify-ce /app/ackify-ce
COPY --from=builder /app/web /app/web
# Use non-root user (already set in distroless image)
# USER 65532:65532

View File

@@ -6,11 +6,11 @@ import (
"log"
"os"
"database/sql"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/lib/pq"
"database/sql"
)
func main() {
@@ -101,4 +101,4 @@ func printUsage() {
fmt.Println(" go run cmd/migrate/main.go up")
fmt.Println(" go run cmd/migrate/main.go down 2")
fmt.Println(" go run cmd/migrate/main.go version")
}
}

View File

@@ -31,4 +31,3 @@ func InitDB(ctx context.Context, config Config) (*sql.DB, error) {
return db, nil
}

View File

@@ -1,30 +0,0 @@
package templates
import (
"fmt"
"html/template"
"path/filepath"
)
// InitTemplates initializes the HTML templates from files
func InitTemplates() (*template.Template, error) {
// Get the templates directory path relative to the binary
templatesDir := "web/templates"
// Parse the base template first
tmpl, err := template.New("base").ParseFiles(filepath.Join(templatesDir, "base.html.tpl"))
if err != nil {
return nil, fmt.Errorf("failed to parse base template: %w", err)
}
// Parse the additional templates
additionalTemplates := []string{"index.html.tpl", "sign.html.tpl", "signatures.html.tpl", "embed.html.tpl"}
for _, templateFile := range additionalTemplates {
_, err = tmpl.ParseFiles(filepath.Join(templatesDir, templateFile))
if err != nil {
return nil, fmt.Errorf("failed to parse template %s: %w", templateFile, err)
}
}
return tmpl, nil
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/btouchard/ackify-ce/internal/infrastructure/config"
"github.com/btouchard/ackify-ce/internal/infrastructure/database"
"github.com/btouchard/ackify-ce/internal/presentation/handlers"
"github.com/btouchard/ackify-ce/internal/presentation/templates"
"github.com/btouchard/ackify-ce/pkg/crypto"
"github.com/btouchard/ackify-ce/webtemplates"
)
// Server represents the Ackify CE web server
@@ -123,7 +123,7 @@ func initInfrastructure(ctx context.Context) (*config.Config, *sql.DB, *template
}
// Initialize templates
tmpl, err := templates.InitTemplates()
tmpl, err := webtemplates.InitTemplates()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to initialize templates: %w", err)
}

30
webtemplates/embed.go Normal file
View File

@@ -0,0 +1,30 @@
package webtemplates
import (
"embed"
"fmt"
"html/template"
)
//go:embed templates/*.tpl
var TemplatesFS embed.FS
// InitTemplates initializes the HTML templates from embedded files
func InitTemplates() (*template.Template, error) {
// Parse the base template first
tmpl, err := template.New("base").ParseFS(TemplatesFS, "templates/base.html.tpl")
if err != nil {
return nil, fmt.Errorf("failed to parse base template: %w", err)
}
// Parse the additional templates
additionalTemplates := []string{"templates/index.html.tpl", "templates/sign.html.tpl", "templates/signatures.html.tpl", "templates/embed.html.tpl"}
for _, templateFile := range additionalTemplates {
_, err = tmpl.ParseFS(TemplatesFS, templateFile)
if err != nil {
return nil, fmt.Errorf("failed to parse template %s: %w", templateFile, err)
}
}
return tmpl, nil
}

View File

@@ -0,0 +1,54 @@
package webtemplates
import (
"testing"
)
func TestTemplatesFS(t *testing.T) {
// Test that the embedded filesystem contains the expected files
expectedFiles := []string{
"templates/base.html.tpl",
"templates/index.html.tpl",
"templates/sign.html.tpl",
"templates/signatures.html.tpl",
"templates/embed.html.tpl",
}
for _, file := range expectedFiles {
data, err := TemplatesFS.ReadFile(file)
if err != nil {
t.Errorf("Failed to read embedded file %s: %v", file, err)
}
if len(data) == 0 {
t.Errorf("Embedded file %s is empty", file)
}
}
}
func TestInitTemplates(t *testing.T) {
// Test that InitTemplates works correctly
tmpl, err := InitTemplates()
if err != nil {
t.Fatalf("InitTemplates failed: %v", err)
}
if tmpl == nil {
t.Fatal("InitTemplates returned nil template")
}
// Test that all expected templates are parsed
expectedTemplateNames := []string{
"base",
"base.html.tpl",
"index.html.tpl",
"sign.html.tpl",
"signatures.html.tpl",
"embed.html.tpl",
}
for _, name := range expectedTemplateNames {
if tmpl.Lookup(name) == nil {
t.Errorf("Template %s not found in parsed templates", name)
}
}
}