Files
pocket-id/backend/frontend/frontend_included.go
2025-05-24 22:55:46 +02:00

78 lines
2.1 KiB
Go

//go:build !exclude_frontend
package frontend
import (
"embed"
"fmt"
"io/fs"
"net/http"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
)
//go:embed all:dist/*
var frontendFS embed.FS
func RegisterFrontend(router *gin.Engine) error {
distFS, err := fs.Sub(frontendFS, "dist")
if err != nil {
return fmt.Errorf("failed to create sub FS: %w", err)
}
cacheMaxAge := time.Hour * 24
fileServer := NewFileServerWithCaching(http.FS(distFS), int(cacheMaxAge.Seconds()))
router.NoRoute(func(c *gin.Context) {
// Try to serve the requested file
path := strings.TrimPrefix(c.Request.URL.Path, "/")
if _, err := fs.Stat(distFS, path); os.IsNotExist(err) {
// File doesn't exist, serve index.html instead
c.Request.URL.Path = "/"
}
fileServer.ServeHTTP(c.Writer, c.Request)
})
return nil
}
// FileServerWithCaching wraps http.FileServer to add caching headers
type FileServerWithCaching struct {
root http.FileSystem
lastModified time.Time
cacheMaxAge int
lastModifiedHeaderValue string
cacheControlHeaderValue string
}
func NewFileServerWithCaching(root http.FileSystem, maxAge int) *FileServerWithCaching {
return &FileServerWithCaching{
root: root,
lastModified: time.Now(),
cacheMaxAge: maxAge,
lastModifiedHeaderValue: time.Now().UTC().Format(http.TimeFormat),
cacheControlHeaderValue: fmt.Sprintf("public, max-age=%d", maxAge),
}
}
func (f *FileServerWithCaching) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check if the client has a cached version
if ifModifiedSince := r.Header.Get("If-Modified-Since"); ifModifiedSince != "" {
ifModifiedSinceTime, err := time.Parse(http.TimeFormat, ifModifiedSince)
if err == nil && f.lastModified.Before(ifModifiedSinceTime.Add(1*time.Second)) {
// Client's cached version is up to date
w.WriteHeader(http.StatusNotModified)
return
}
}
w.Header().Set("Last-Modified", f.lastModifiedHeaderValue)
w.Header().Set("Cache-Control", f.cacheControlHeaderValue)
http.FileServer(f.root).ServeHTTP(w, r)
}