mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"io/fs"
|
|
"log/slog"
|
|
"mime"
|
|
"net/http"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
"github.com/memohai/memoh/internal/embedded"
|
|
)
|
|
|
|
type EmbeddedWebHandler struct {
|
|
log *slog.Logger
|
|
webFS fs.FS
|
|
}
|
|
|
|
var embeddedStaticRoutes = map[string]struct {
|
|
assetPath string
|
|
contentType string
|
|
}{
|
|
"/logo.png": {assetPath: "logo.png", contentType: "image/png"},
|
|
"/channels/telegram.webp": {assetPath: "channels/telegram.webp", contentType: "image/webp"},
|
|
"/channels/feishu.png": {assetPath: "channels/feishu.png", contentType: "image/png"},
|
|
}
|
|
|
|
func NewEmbeddedWebHandler(log *slog.Logger) (*EmbeddedWebHandler, error) {
|
|
webFS, err := embedded.WebFS()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &EmbeddedWebHandler{log: log, webFS: webFS}, nil
|
|
}
|
|
|
|
func (h *EmbeddedWebHandler) Register(e *echo.Echo) {
|
|
e.GET("/assets/*", h.serveAsset)
|
|
for route, meta := range embeddedStaticRoutes {
|
|
e.GET(route, h.serveStatic(meta.assetPath, meta.contentType))
|
|
}
|
|
e.GET("/", h.serveIndex)
|
|
e.GET("/*", func(c echo.Context) error {
|
|
reqPath := c.Request().URL.Path
|
|
if isBackendPath(reqPath) || strings.Contains(path.Base(reqPath), ".") {
|
|
return echo.ErrNotFound
|
|
}
|
|
return h.serveIndex(c)
|
|
})
|
|
}
|
|
|
|
func (h *EmbeddedWebHandler) serveIndex(c echo.Context) error {
|
|
return h.serveKnownGzip(c, "index.html", "text/html; charset=utf-8")
|
|
}
|
|
|
|
func (h *EmbeddedWebHandler) serveStatic(targetPath, contentType string) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
return h.serveKnownGzip(c, targetPath, contentType)
|
|
}
|
|
}
|
|
|
|
func (h *EmbeddedWebHandler) serveAsset(c echo.Context) error {
|
|
assetPath := strings.TrimPrefix(c.Param("*"), "/")
|
|
if assetPath == "" {
|
|
return echo.ErrNotFound
|
|
}
|
|
|
|
fullPath := path.Join("assets", assetPath)
|
|
contentType := mime.TypeByExtension(filepath.Ext(assetPath))
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
gzipPath := fullPath + ".gz"
|
|
content, err := fs.ReadFile(h.webFS, gzipPath)
|
|
if err != nil {
|
|
return echo.ErrNotFound
|
|
}
|
|
header := c.Response().Header()
|
|
header.Set(echo.HeaderContentEncoding, "gzip")
|
|
header.Set(echo.HeaderVary, "Accept-Encoding")
|
|
return c.Blob(http.StatusOK, contentType, content)
|
|
}
|
|
|
|
func (h *EmbeddedWebHandler) serveKnownGzip(c echo.Context, targetPath, contentType string) error {
|
|
gzipPath := targetPath + ".gz"
|
|
content, err := fs.ReadFile(h.webFS, gzipPath)
|
|
if err != nil {
|
|
if targetPath == "index.html" {
|
|
h.log.Error("read embedded index.html.gz failed", slog.Any("error", err))
|
|
}
|
|
return echo.ErrNotFound
|
|
}
|
|
header := c.Response().Header()
|
|
header.Set(echo.HeaderContentEncoding, "gzip")
|
|
header.Set(echo.HeaderVary, "Accept-Encoding")
|
|
return c.Blob(http.StatusOK, contentType, content)
|
|
}
|
|
|
|
func isBackendPath(p string) bool {
|
|
return p == "/ping" ||
|
|
p == "/health" ||
|
|
strings.HasPrefix(p, "/api") ||
|
|
strings.HasPrefix(p, "/auth") ||
|
|
strings.HasPrefix(p, "/channels") ||
|
|
strings.HasPrefix(p, "/containers") ||
|
|
strings.HasPrefix(p, "/inbox")
|
|
}
|