323 lines
7.2 KiB
Go
323 lines
7.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"goblog/database"
|
|
"goblog/models"
|
|
"goblog/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// 获取可用主题列表
|
|
func GetThemes(c *gin.Context) {
|
|
themes := []string{}
|
|
|
|
// 读取 templates 目录
|
|
entries, err := os.ReadDir("templates")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "无法读取主题目录",
|
|
})
|
|
return
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
// 检查是否是有效主题(包含 base.html)
|
|
basePath := filepath.Join("templates", entry.Name(), "base.html")
|
|
if _, err := os.Stat(basePath); err == nil {
|
|
themes = append(themes, entry.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"themes": themes,
|
|
})
|
|
}
|
|
|
|
// 获取当前主题
|
|
func GetCurrentTheme(c *gin.Context) {
|
|
currentTheme := "default"
|
|
|
|
// 使用新的 Option 查询方法
|
|
theme, err := models.GetOptionValue(database.DB, "theme", 0)
|
|
if err == nil && theme != "" {
|
|
currentTheme = theme
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"theme": currentTheme,
|
|
})
|
|
}
|
|
|
|
// 切换主题
|
|
func SwitchTheme(c *gin.Context) {
|
|
type Request struct {
|
|
Theme string `json:"theme" binding:"required"`
|
|
}
|
|
|
|
var req Request
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请提供主题名称",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证主题是否存在
|
|
themePath := filepath.Join("templates", req.Theme)
|
|
if _, err := os.Stat(themePath); os.IsNotExist(err) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "主题不存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证主题是否有效
|
|
basePath := filepath.Join(themePath, "base.html")
|
|
if _, err := os.Stat(basePath); os.IsNotExist(err) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "主题文件不完整",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 更新数据库中的主题设置
|
|
err := models.SetOptionValue(database.DB, "theme", 0, req.Theme)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存主题设置失败"})
|
|
return
|
|
}
|
|
|
|
// 重新加载模板(立即生效)
|
|
renderer := utils.GetGlobalRenderer()
|
|
if renderer != nil {
|
|
renderer.Reload(req.Theme)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "主题切换成功",
|
|
"theme": req.Theme,
|
|
})
|
|
}
|
|
|
|
// 创建新主题
|
|
func CreateTheme(c *gin.Context) {
|
|
type Request struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Template string `json:"template"`
|
|
}
|
|
|
|
var req Request
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请提供主题名称",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证主题名称(只能包含字母、数字和下划线)
|
|
validName := true
|
|
for _, ch := range req.Name {
|
|
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
|
|
validName = false
|
|
break
|
|
}
|
|
}
|
|
if !validName || req.Name == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "主题名称只能包含字母、数字和下划线",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 检查主题是否已存在
|
|
themePath := filepath.Join("templates", req.Name)
|
|
if _, err := os.Stat(themePath); err == nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "主题已存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 创建主题目录
|
|
if err := os.MkdirAll(themePath, 0700); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "创建主题目录失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 创建新主题时必须选择已有主题作为模板
|
|
if req.Template == "" || req.Template == "blank" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "必须选择一个已有主题作为模板",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 验证所选模板是否存在并复制模板文件
|
|
templateSrc := filepath.Join("templates", req.Template)
|
|
if _, err := os.Stat(templateSrc); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "所选模板不存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 复制模板文件
|
|
files := []string{"base.html", "index.html", "post.html", "page.html"}
|
|
for _, file := range files {
|
|
srcFile := filepath.Join(templateSrc, file)
|
|
dstFile := filepath.Join(themePath, file)
|
|
if data, err := os.ReadFile(srcFile); err == nil {
|
|
os.WriteFile(dstFile, data, 0644)
|
|
}
|
|
}
|
|
|
|
// 复制模板的 CSS 文件
|
|
templateCSS := filepath.Join("static", "css", req.Template+".css")
|
|
if data, err := os.ReadFile(templateCSS); err == nil {
|
|
dstCSS := filepath.Join("static", "css", req.Name+".css")
|
|
os.MkdirAll(filepath.Dir(dstCSS), 0755)
|
|
os.WriteFile(dstCSS, data, 0644)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "主题创建成功",
|
|
"theme": req.Name,
|
|
})
|
|
}
|
|
|
|
// 获取主题文件列表
|
|
func GetThemeFiles(c *gin.Context) {
|
|
themeName := c.Param("theme")
|
|
|
|
themePath := filepath.Join("templates", themeName)
|
|
if _, err := os.Stat(themePath); os.IsNotExist(err) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "主题不存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
files := []string{}
|
|
|
|
// 读取模板文件
|
|
entries, err := os.ReadDir(themePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "读取文件列表失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
ext := filepath.Ext(entry.Name())
|
|
if ext == ".html" || ext == ".css" || ext == ".js" {
|
|
files = append(files, entry.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查是否有 CSS 文件
|
|
cssPath := filepath.Join("static", "css", themeName+".css")
|
|
if _, err := os.Stat(cssPath); err == nil {
|
|
files = append(files, themeName+".css")
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"files": files,
|
|
})
|
|
}
|
|
|
|
// 获取主题文件内容
|
|
func GetThemeFile(c *gin.Context) {
|
|
themeName := c.Param("theme")
|
|
filename := c.Param("file")
|
|
|
|
var filePath string
|
|
|
|
// 判断是模板文件还是 CSS 文件
|
|
if filename == themeName+".css" {
|
|
filePath = filepath.Join("static", "css", filename)
|
|
} else {
|
|
filePath = filepath.Join("templates", themeName, filename)
|
|
}
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "文件不存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "读取文件失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"content": string(content),
|
|
})
|
|
}
|
|
|
|
// 保存主题文件
|
|
func SaveThemeFile(c *gin.Context) {
|
|
themeName := c.Param("theme")
|
|
filename := c.Param("file")
|
|
|
|
type Request struct {
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
var req Request
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "请提供文件内容",
|
|
})
|
|
return
|
|
}
|
|
|
|
var filePath string
|
|
|
|
// 判断是模板文件还是 CSS 文件
|
|
if filename == themeName+".css" {
|
|
filePath = filepath.Join("static", "css", filename)
|
|
} else {
|
|
filePath = filepath.Join("templates", themeName, filename)
|
|
}
|
|
|
|
// 保存文件
|
|
if err := os.WriteFile(filePath, []byte(req.Content), 0600); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "保存文件失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 如果是模板文件且是当前使用的主题,重新加载
|
|
if filename != themeName+".css" {
|
|
currentTheme, err := models.GetOptionValue(database.DB, "theme", 0)
|
|
if err == nil && currentTheme == themeName {
|
|
renderer := utils.GetGlobalRenderer()
|
|
if renderer != nil {
|
|
renderer.Reload(themeName)
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "文件保存成功",
|
|
})
|
|
}
|