Add four DB-backed configuration tables for site-wide settings: site_settings (logo, top-left title, header/footer text with zh/en variants), upload_configs (master switch, default size, storage dir), upload_file_types (per-extension whitelist with per-type size limits), and download_baseurls (multiple download sources with priority/default). - Models, AutoMigrate, and first-run seed (18 common file types) - Process-level config cache warmed at startup, refreshed on admin save - SetUserContext injects cached site settings into templates - base.html renders logo (local upload or external URL), title, header banner, and footer with i18n fallback - Upload validator drives avatar uploads (form + AJAX) through the configured switch/type/size policy - Admin pages for site/upload/download settings with i18n keys Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
1.8 KiB
Go
73 lines
1.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"go_blog/models"
|
|
)
|
|
|
|
// ErrUploadsDisabled is returned when the global upload switch is off.
|
|
var ErrUploadsDisabled = errors.New("uploads are disabled")
|
|
|
|
// FileValidationError describes why an uploaded file was rejected.
|
|
type FileValidationError struct {
|
|
Reason string
|
|
}
|
|
|
|
func (e *FileValidationError) Error() string { return e.Reason }
|
|
|
|
// FileCheck is the outcome of validating an uploaded file header.
|
|
type FileCheck struct {
|
|
OK bool
|
|
Type *models.UploadFileType // matched type, nil if not found
|
|
MaxSize int64 // effective byte limit applied
|
|
}
|
|
|
|
// ValidateUpload checks a file header against the cached platform upload
|
|
// policy: master switch, extension whitelist, and per-type size limit. The
|
|
// reported MaxSize is the effective limit (per-type override, else default).
|
|
func ValidateUpload(header *multipart.FileHeader) FileCheck {
|
|
cfg := models.GetUploadConfig()
|
|
|
|
if !cfg.Enabled {
|
|
return FileCheck{OK: false}
|
|
}
|
|
|
|
ext := strings.ToLower(filepath.Ext(header.Filename))
|
|
def := cfg.DefaultMaxSize
|
|
for i := range models.GetUploadFileTypes() {
|
|
t := &models.GetUploadFileTypes()[i]
|
|
if !t.Enabled {
|
|
continue
|
|
}
|
|
if strings.EqualFold(t.Extension, ext) {
|
|
max := t.EffectiveMaxSize(def)
|
|
if header.Size > max {
|
|
return FileCheck{OK: false, Type: t, MaxSize: max}
|
|
}
|
|
return FileCheck{OK: true, Type: t, MaxSize: max}
|
|
}
|
|
}
|
|
|
|
// Extension not in the whitelist.
|
|
return FileCheck{OK: false, MaxSize: def}
|
|
}
|
|
|
|
// formatSize renders a byte count as a human-readable string.
|
|
func formatSize(b int64) string {
|
|
const unit = 1024
|
|
if b < unit {
|
|
return fmt.Sprintf("%d B", b)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := b / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp])
|
|
}
|