Files
go_blog/models/config_cache.go
T
kevinandClaude Fable 5 6139b3ef2c feat: add article comment system with moderation
- Comment model with nested replies (ParentID), dual authorship
  (logged-in UserID / anonymous GuestToken cookie), email hash for
  Gravatar, private flag, and moderation status
- CommentConfig singleton (enabled / allow guest / guest-require-approval
  / use Gravatar) cached like the other platform config
- Markdown comments with built-in emoji picker, preview, and markdown
  help; rendered client-side via marked + DOMPurify, with server-side
  HTML/dangerous-scheme stripping as a first XSS defense
- Private comments visible only to admin and the author; pending
  comments visible only to admin and the author
- Admin moderation list (pending/approved/rejected/all tabs) with
  approve/reject/delete and a pending-count badge
- Comment settings page with the four toggles
- One-time session flash notice (auto-dismissed after 4s) so the
  "comment posted" banner no longer persists across refreshes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 17:21:54 +08:00

121 lines
3.3 KiB
Go

package models
import (
"sync"
"gorm.io/gorm"
)
// configCache holds process-level caches of platform configuration so that
// per-request rendering and upload validation do not hit the database. The
// cache is populated once at startup and refreshed whenever an admin saves a
// settings page (see RefreshConfigCache / the admin handlers).
var configCache = struct {
mu sync.RWMutex
site *SiteSetting
upload *UploadConfig
comment *CommentConfig
types []UploadFileType
baseURLs []DownloadBaseURL
}{
site: &SiteSetting{},
upload: &UploadConfig{Enabled: true, DefaultMaxSize: DefaultUploadMaxSize, StorageDir: "attachments"},
}
// LoadConfigCache reads all platform configuration from the database into the
// process cache. Called once at startup after InitDB.
func LoadConfigCache(db *gorm.DB) {
configCache.mu.Lock()
defer configCache.mu.Unlock()
var s SiteSetting
if err := db.First(&s, 1).Error; err == nil {
configCache.site = &s
} else {
configCache.site = &SiteSetting{ID: 1}
}
var u UploadConfig
if err := db.First(&u, 1).Error; err == nil {
configCache.upload = &u
} else {
configCache.upload = &UploadConfig{ID: 1, Enabled: true, DefaultMaxSize: DefaultUploadMaxSize, StorageDir: "attachments"}
}
var cc CommentConfig
if err := db.First(&cc, 1).Error; err == nil {
configCache.comment = &cc
} else {
configCache.comment = defaultCommentConfig()
}
var t []UploadFileType
if err := db.Order("sort asc, id asc").Find(&t).Error; err == nil {
configCache.types = t
}
var b []DownloadBaseURL
if err := db.Order("is_default desc, priority asc, id asc").Find(&b).Error; err == nil {
configCache.baseURLs = b
}
}
// RefreshConfigCache reloads all cached platform configuration. Admin handlers
// call this after writing changes so the next request sees them.
func RefreshConfigCache(db *gorm.DB) {
LoadConfigCache(db)
}
// GetSiteSetting returns a pointer to the cached site settings (read-only copy
// semantics: callers must not mutate).
func GetSiteSetting() *SiteSetting {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
return configCache.site
}
// GetUploadConfig returns the cached upload policy.
func GetUploadConfig() *UploadConfig {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
return configCache.upload
}
// GetCommentConfig returns the cached comment policy.
func GetCommentConfig() *CommentConfig {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
return configCache.comment
}
// GetUploadFileTypes returns the cached list of permitted file types.
func GetUploadFileTypes() []UploadFileType {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
return configCache.types
}
// GetDownloadBaseURLs returns the cached list of download base URLs.
func GetDownloadBaseURLs() []DownloadBaseURL {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
return configCache.baseURLs
}
// DefaultDownloadBaseURL returns the base URL used to build attachment download
// links: the enabled row marked IsDefault, else the highest-priority enabled
// row. Returns an empty string if none is configured.
func DefaultDownloadBaseURL() string {
configCache.mu.RLock()
defer configCache.mu.RUnlock()
// Rows are ordered is_default desc, priority asc, so the first enabled row
// is the right pick.
for _, b := range configCache.baseURLs {
if b.Enabled {
return b.BaseURL
}
}
return ""
}