Initial commit with server startup infrastructure
Core infrastructure: - Uber fx dependency injection - Chi router with middleware stack - SQLite database with embedded migrations - Embedded templates and static assets - Structured logging with slog Features implemented: - Authentication (login, logout, session management, argon2id hashing) - App management (create, edit, delete, list) - Deployment pipeline (clone, build, deploy, health check) - Webhook processing for Gitea - Notifications (ntfy, Slack) - Environment variables, labels, volumes per app - SSH key generation for deploy keys Server startup: - Server.Run() starts HTTP server on configured port - Server.Shutdown() for graceful shutdown - SetupRoutes() wires all handlers with chi router
This commit is contained in:
98
templates/templates.go
Normal file
98
templates/templates.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Package templates provides HTML template handling.
|
||||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//go:embed *.html
|
||||
var templatesRaw embed.FS
|
||||
|
||||
// Template cache variables are global to enable efficient template reuse
|
||||
// across requests without re-parsing on each call.
|
||||
var (
|
||||
//nolint:gochecknoglobals // singleton pattern for template cache
|
||||
baseTemplate *template.Template
|
||||
//nolint:gochecknoglobals // singleton pattern for template cache
|
||||
pageTemplates map[string]*template.Template
|
||||
//nolint:gochecknoglobals // protects template cache access
|
||||
templatesMutex sync.RWMutex
|
||||
)
|
||||
|
||||
// initTemplates parses base template and creates cloned templates for each page.
|
||||
func initTemplates() {
|
||||
templatesMutex.Lock()
|
||||
defer templatesMutex.Unlock()
|
||||
|
||||
if pageTemplates != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse base template with shared components
|
||||
baseTemplate = template.Must(template.ParseFS(templatesRaw, "base.html"))
|
||||
|
||||
// Pages that extend base
|
||||
pages := []string{
|
||||
"setup.html",
|
||||
"login.html",
|
||||
"dashboard.html",
|
||||
"app_new.html",
|
||||
"app_detail.html",
|
||||
"app_edit.html",
|
||||
"deployments.html",
|
||||
}
|
||||
|
||||
pageTemplates = make(map[string]*template.Template)
|
||||
|
||||
for _, page := range pages {
|
||||
// Clone base template and parse page-specific template into it
|
||||
clone := template.Must(baseTemplate.Clone())
|
||||
pageTemplates[page] = template.Must(clone.ParseFS(templatesRaw, page))
|
||||
}
|
||||
}
|
||||
|
||||
// GetParsed returns a template executor that routes to the correct page template.
|
||||
func GetParsed() *TemplateExecutor {
|
||||
initTemplates()
|
||||
|
||||
return &TemplateExecutor{}
|
||||
}
|
||||
|
||||
// TemplateExecutor executes templates using the correct cloned template set.
|
||||
type TemplateExecutor struct{}
|
||||
|
||||
// ExecuteTemplate executes the named template with the given data.
|
||||
func (t *TemplateExecutor) ExecuteTemplate(
|
||||
writer io.Writer,
|
||||
name string,
|
||||
data any,
|
||||
) error {
|
||||
templatesMutex.RLock()
|
||||
|
||||
tmpl, ok := pageTemplates[name]
|
||||
|
||||
templatesMutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
// Fallback for non-page templates
|
||||
err := baseTemplate.ExecuteTemplate(writer, name, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute base template %s: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute the "base" template from the cloned set
|
||||
// (which has page-specific overrides)
|
||||
err := tmpl.ExecuteTemplate(writer, "base", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("execute page template %s: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user