- nav_links 增加 position(header/footer,迁移 v11),后台“导航链接”拆为头部/底部两张卡片,页脚链接支持多语言、新窗口与点分隔,删除硬编码的关于我们等链接 - site_settings 增加 favicon(迁移 v12),新增 PUT/DELETE /api/site/favicon,支持 ICO/PNG/SVG 等且不裁剪,引用计数与 Logo 一致自动管理 - 文件服务识别 SVG 并允许内联,统一附加 CSP(default-src 'none'; sandbox)防止存储型 XSS;Logo 维持仅栅格 - 前端 SiteInfoForm 增加 Favicon 上传/清空/预览,App.vue 动态更新 link rel=icon,三语文案补齐 - 补充位置与 Favicon 的接口/引用计数/安全头测试,重新生成 Swagger 文档
186 lines
4.5 KiB
Go
186 lines
4.5 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"rill/internal/model"
|
|
)
|
|
|
|
// Migration 描述一次数据库结构变更,Version 必须唯一且递增。
|
|
type Migration struct {
|
|
Version int
|
|
Name string
|
|
Up func(*gorm.DB) error
|
|
}
|
|
|
|
// migrations 按 Version 升序登记所有迁移,新增迁移只能追加。
|
|
var migrations = []Migration{
|
|
{
|
|
Version: 1,
|
|
Name: "create_notes",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.Note{})
|
|
},
|
|
},
|
|
{
|
|
Version: 2,
|
|
Name: "create_user_groups",
|
|
Up: func(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&model.UserGroup{}); err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
seeds := []struct {
|
|
id uint
|
|
name string
|
|
desc string
|
|
}{
|
|
{id: model.GroupIDAdmin, name: "admin", desc: "Administrator group"},
|
|
{id: model.GroupIDUser, name: "user", desc: "Regular user group"},
|
|
}
|
|
for _, seed := range seeds {
|
|
if err := tx.Exec(
|
|
"INSERT INTO user_groups (id, name, description, is_system, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
seed.id, seed.name, seed.desc, true, now, now,
|
|
).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Version: 3,
|
|
Name: "create_users",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.User{}, &model.UserGroupMember{})
|
|
},
|
|
},
|
|
{
|
|
Version: 4,
|
|
Name: "seed_admin_user",
|
|
Up: seedAdminUser,
|
|
},
|
|
{
|
|
Version: 5,
|
|
Name: "translate_builtin_data",
|
|
Up: func(tx *gorm.DB) error {
|
|
statements := []string{
|
|
"UPDATE user_groups SET description = 'Administrator group' WHERE id = 0 AND description = '管理员组'",
|
|
"UPDATE user_groups SET description = 'Regular user group' WHERE id = 1 AND description = '普通用户组'",
|
|
"UPDATE users SET nickname = 'Administrator' WHERE username = 'admin' AND nickname = '管理员'",
|
|
}
|
|
for _, statement := range statements {
|
|
if err := tx.Exec(statement).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
Version: 6,
|
|
Name: "add_user_profile_fields",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.User{})
|
|
},
|
|
},
|
|
{
|
|
Version: 7,
|
|
Name: "create_site_settings",
|
|
Up: func(tx *gorm.DB) error {
|
|
if err := tx.AutoMigrate(&model.SiteSetting{}); err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
return tx.Exec(
|
|
"INSERT INTO site_settings (id, site_name, logo, footer, created_at, updated_at) VALUES (?, ?, '', '', ?, ?)",
|
|
model.SiteSettingID, "Rill", now, now,
|
|
).Error
|
|
},
|
|
},
|
|
{
|
|
Version: 8,
|
|
Name: "create_files",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.File{})
|
|
},
|
|
},
|
|
{
|
|
Version: 9,
|
|
Name: "create_file_operations",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.FileOperation{})
|
|
},
|
|
},
|
|
{
|
|
Version: 10,
|
|
Name: "create_nav_links",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.NavLink{}, &model.NavLinkTranslation{})
|
|
},
|
|
},
|
|
{
|
|
Version: 11,
|
|
Name: "add_nav_link_position",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.NavLink{})
|
|
},
|
|
},
|
|
{
|
|
Version: 12,
|
|
Name: "add_site_favicon",
|
|
Up: func(tx *gorm.DB) error {
|
|
return tx.AutoMigrate(&model.SiteSetting{})
|
|
},
|
|
},
|
|
}
|
|
|
|
// schemaMigration 记录已应用的迁移版本。
|
|
type schemaMigration struct {
|
|
Version int `gorm:"primaryKey"`
|
|
Name string
|
|
AppliedAt time.Time
|
|
}
|
|
|
|
func (schemaMigration) TableName() string { return "schema_migrations" }
|
|
|
|
// Migrate 依次执行未应用的迁移,每个迁移在独立事务中完成并记录版本,可重复调用。
|
|
func Migrate(ctx context.Context, db *gorm.DB) error {
|
|
tx := db.WithContext(ctx)
|
|
if err := tx.AutoMigrate(&schemaMigration{}); err != nil {
|
|
return fmt.Errorf("初始化迁移记录表失败: %w", err)
|
|
}
|
|
|
|
var applied []int
|
|
if err := tx.Model(&schemaMigration{}).Pluck("version", &applied).Error; err != nil {
|
|
return fmt.Errorf("读取迁移记录失败: %w", err)
|
|
}
|
|
done := make(map[int]bool, len(applied))
|
|
for _, version := range applied {
|
|
done[version] = true
|
|
}
|
|
|
|
for _, m := range migrations {
|
|
if done[m.Version] {
|
|
continue
|
|
}
|
|
err := tx.Transaction(func(tx *gorm.DB) error {
|
|
if err := m.Up(tx); err != nil {
|
|
return err
|
|
}
|
|
return tx.Create(&schemaMigration{Version: m.Version, Name: m.Name, AppliedAt: time.Now()}).Error
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("执行迁移 %d_%s 失败: %w", m.Version, m.Name, err)
|
|
}
|
|
slog.Info("数据库迁移已应用", "version", m.Version, "name", m.Name)
|
|
}
|
|
return nil
|
|
}
|