Files
OpenList/server/static/static.go

229 lines
7.2 KiB
Go
Raw Normal View History

2022-08-28 23:13:03 +08:00
package static
import (
"encoding/json"
"errors"
2022-08-28 23:13:03 +08:00
"fmt"
"io"
2022-08-28 23:13:03 +08:00
"io/fs"
"net/http"
"os"
2022-08-28 23:13:03 +08:00
"strings"
"github.com/OpenListTeam/OpenList/v4/drivers/base"
"github.com/OpenListTeam/OpenList/v4/internal/conf"
"github.com/OpenListTeam/OpenList/v4/internal/setting"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
"github.com/OpenListTeam/OpenList/v4/public"
2022-08-28 23:13:03 +08:00
"github.com/gin-gonic/gin"
)
type ManifestIcon struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
}
type Manifest struct {
Display string `json:"display"`
Scope string `json:"scope"`
StartURL string `json:"start_url"`
Name string `json:"name"`
Icons []ManifestIcon `json:"icons"`
}
var static fs.FS
func initStatic() {
utils.Log.Debug("Initializing static file system...")
if conf.Conf.DistDir == "" {
dist, err := fs.Sub(public.Public, "dist")
if err != nil {
utils.Log.Fatalf("failed to read dist dir: %v", err)
}
static = dist
utils.Log.Debug("Using embedded dist directory")
return
}
static = os.DirFS(conf.Conf.DistDir)
utils.Log.Infof("Using custom dist directory: %s", conf.Conf.DistDir)
}
func replaceStrings(content string, replacements map[string]string) string {
for old, new := range replacements {
content = strings.Replace(content, old, new, 1)
}
return content
}
func initIndex(siteConfig SiteConfig) {
utils.Log.Debug("Initializing index.html...")
// dist_dir is empty and cdn is not empty, and web_version is empty or beta or dev or rolling
if conf.Conf.DistDir == "" && conf.Conf.Cdn != "" && (conf.WebVersion == "" || conf.WebVersion == "beta" || conf.WebVersion == "dev" || conf.WebVersion == "rolling") {
utils.Log.Infof("Fetching index.html from CDN: %s/index.html...", siteConfig.Cdn)
resp, err := base.RestyClient.R().
SetHeader("Accept", "text/html").
Get(fmt.Sprintf("%s/index.html", siteConfig.Cdn))
if err != nil {
utils.Log.Fatalf("failed to fetch index.html from CDN: %v", err)
}
if resp.StatusCode() != http.StatusOK {
utils.Log.Fatalf("failed to fetch index.html from CDN, status code: %d", resp.StatusCode())
}
conf.RawIndexHtml = string(resp.Body())
utils.Log.Info("Successfully fetched index.html from CDN")
} else {
utils.Log.Debug("Reading index.html from static files system...")
indexFile, err := static.Open("index.html")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
utils.Log.Fatalf("index.html not exist, you may forget to put dist of frontend to public/dist")
}
utils.Log.Fatalf("failed to read index.html: %v", err)
}
defer func() {
_ = indexFile.Close()
}()
index, err := io.ReadAll(indexFile)
if err != nil {
utils.Log.Fatalf("failed to read dist/index.html")
}
conf.RawIndexHtml = string(index)
utils.Log.Debug("Successfully read index.html from static files system")
}
utils.Log.Debug("Replacing placeholders in index.html...")
// Construct the correct manifest path based on basePath
manifestPath := "/manifest.json"
if siteConfig.BasePath != "/" {
manifestPath = siteConfig.BasePath + "/manifest.json"
}
replaceMap := map[string]string{
"cdn: undefined": fmt.Sprintf("cdn: '%s'", siteConfig.Cdn),
"base_path: undefined": fmt.Sprintf("base_path: '%s'", siteConfig.BasePath),
`href="/manifest.json"`: fmt.Sprintf(`href="%s"`, manifestPath),
}
conf.RawIndexHtml = replaceStrings(conf.RawIndexHtml, replaceMap)
2022-08-28 23:13:03 +08:00
UpdateIndex()
}
func UpdateIndex() {
utils.Log.Debug("Updating index.html with settings...")
2022-09-06 14:39:21 +08:00
favicon := setting.GetStr(conf.Favicon)
logo := strings.Split(setting.GetStr(conf.Logo), "\n")[0]
2022-09-06 14:39:21 +08:00
title := setting.GetStr(conf.SiteTitle)
customizeHead := setting.GetStr(conf.CustomizeHead)
customizeBody := setting.GetStr(conf.CustomizeBody)
2022-09-08 22:21:52 +08:00
mainColor := setting.GetStr(conf.MainColor)
utils.Log.Debug("Applying replacements for default pages...")
2022-08-28 23:13:03 +08:00
replaceMap1 := map[string]string{
"https://res.oplist.org/logo/logo.svg": favicon,
"https://res.oplist.org/logo/logo.png": logo,
"Loading...": title,
"main_color: undefined": fmt.Sprintf("main_color: '%s'", mainColor),
2022-08-28 23:13:03 +08:00
}
conf.ManageHtml = replaceStrings(conf.RawIndexHtml, replaceMap1)
utils.Log.Debug("Applying replacements for manage pages...")
2022-08-28 23:13:03 +08:00
replaceMap2 := map[string]string{
"<!-- customize head -->": customizeHead,
"<!-- customize body -->": customizeBody,
}
conf.IndexHtml = replaceStrings(conf.ManageHtml, replaceMap2)
utils.Log.Debug("Index.html update completed")
2022-08-28 23:13:03 +08:00
}
func ManifestJSON(c *gin.Context) {
// Get site configuration to ensure consistent base path handling
siteConfig := getSiteConfig()
// Get site title from settings
siteTitle := setting.GetStr(conf.SiteTitle)
// Get logo from settings, use the first line (light theme logo)
logoSetting := setting.GetStr(conf.Logo)
logoUrl := strings.Split(logoSetting, "\n")[0]
// Use base path from site config for consistency
basePath := siteConfig.BasePath
// Determine scope and start_url
// PWA scope and start_url should always point to our application's base path
// regardless of whether static resources come from CDN or local server
scope := basePath
startURL := basePath
manifest := Manifest{
Display: "standalone",
Scope: scope,
StartURL: startURL,
Name: siteTitle,
Icons: []ManifestIcon{
{
Src: logoUrl,
Sizes: "512x512",
Type: "image/png",
},
},
}
c.Header("Content-Type", "application/json")
c.Header("Cache-Control", "public, max-age=3600") // cache for 1 hour
if err := json.NewEncoder(c.Writer).Encode(manifest); err != nil {
utils.Log.Errorf("Failed to encode manifest.json: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate manifest"})
return
}
}
func Static(r *gin.RouterGroup, noRoute func(handlers ...gin.HandlerFunc)) {
utils.Log.Debug("Setting up static routes...")
siteConfig := getSiteConfig()
initStatic()
initIndex(siteConfig)
folders := []string{"assets", "images", "streamer", "static"}
if conf.Conf.Cdn == "" {
utils.Log.Debug("Setting up static file serving...")
r.Use(func(c *gin.Context) {
for _, folder := range folders {
if strings.HasPrefix(c.Request.RequestURI, fmt.Sprintf("/%s/", folder)) {
c.Header("Cache-Control", "public, max-age=15552000")
}
}
})
for _, folder := range folders {
sub, err := fs.Sub(static, folder)
if err != nil {
utils.Log.Fatalf("can't find folder: %s", folder)
2022-12-15 17:48:29 +08:00
}
utils.Log.Debugf("Setting up route for folder: %s", folder)
r.StaticFS(fmt.Sprintf("/%s/", folder), http.FS(sub))
2022-12-15 17:48:29 +08:00
}
} else {
// Ensure static file redirected to CDN
for _, folder := range folders {
r.GET(fmt.Sprintf("/%s/*filepath", folder), func(c *gin.Context) {
filepath := c.Param("filepath")
c.Redirect(http.StatusFound, fmt.Sprintf("%s/%s%s", siteConfig.Cdn, folder, filepath))
})
2022-08-28 23:13:03 +08:00
}
}
utils.Log.Debug("Setting up catch-all route...")
noRoute(func(c *gin.Context) {
if c.Request.Method != "GET" && c.Request.Method != "POST" {
c.Status(405)
return
}
2022-08-28 23:13:03 +08:00
c.Header("Content-Type", "text/html")
c.Status(200)
if strings.HasPrefix(c.Request.URL.Path, "/@manage") {
_, _ = c.Writer.WriteString(conf.ManageHtml)
} else {
_, _ = c.Writer.WriteString(conf.IndexHtml)
}
c.Writer.Flush()
c.Writer.WriteHeaderNow()
})
}