feat: initial blog engine with multi-language, profile, and role system

- Gin + GORM + Tailwind CSS blog engine
- OS-aware config (Linux /etc/blog_go/, Windows ./win/etc/blog_go/)
- SQLite by default, MySQL support via config
- Auto-migrate database + seed admin user on first run
- Session-based auth (login/logout)
- i18n: Chinese/English with auto-detect + manual switch
- Avatar dropdown nav with click-to-toggle menu
- Profile page: avatar upload, edit info, change password
- Role system: admin (full access) and author (profile only)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 01:49:04 +08:00
co-authored by Claude Opus 4.8
commit c82d4482d4
20 changed files with 1592 additions and 0 deletions
+100
View File
@@ -0,0 +1,100 @@
package middleware
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"go_blog/i18n"
"go_blog/models"
)
// AuthRequired is middleware that protects routes. If the user is not logged in,
// they are redirected to /login.
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
userID := session.Get("user_id")
if userID == nil {
c.Redirect(http.StatusFound, "/login")
c.Abort()
return
}
c.Next()
}
}
// SetUserContext is global middleware that reads the session and sets
// template-friendly context values for all pages (language, auth state, etc.).
func SetUserContext(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
// --- Language detection ---
// Priority: query param > session > Accept-Language header > default EN
var lang i18n.Lang
queryLang := c.Query("lang")
switch queryLang {
case "zh":
lang = i18n.ZH
case "en":
lang = i18n.EN
case "":
// Try session
if saved, ok := session.Get("lang").(string); ok {
lang = i18n.Lang(saved)
}
if lang == "" {
// Try Accept-Language header
lang = i18n.DetectLang(c.GetHeader("Accept-Language"))
}
default:
// Unsupported language in query — fall back to English.
lang = i18n.EN
}
// Persist language in session.
session.Set("lang", string(lang))
session.Save()
// Make translations available in the Gin context.
c.Set("tr", i18n.T(lang))
c.Set("lang", string(lang))
// Set the opposite language code for the language switcher link.
switchLang := "zh"
if lang == i18n.ZH {
switchLang = "en"
}
c.Set("switch_lang", switchLang)
// --- Auth state ---
userID := session.Get("user_id")
isLoggedIn := false
var username string
var avatar string
var displayName string
var role string
if userID != nil {
isLoggedIn = true
var user models.User
if err := db.First(&user, userID).Error; err == nil {
username = user.Username
avatar = user.Avatar
displayName = user.DisplayName
role = user.Role
}
}
c.Set("is_logged_in", isLoggedIn)
c.Set("username", username)
c.Set("avatar", avatar)
c.Set("display_name", displayName)
c.Set("role", role)
c.Next()
}
}