201 lines
7.8 KiB
Go
201 lines
7.8 KiB
Go
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
package migrations
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/apache/answer/internal/base/data"
|
|
"github.com/apache/answer/internal/entity"
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
const minDBVersion = 0
|
|
|
|
// Migration describes on migration from lower version to high version
|
|
type Migration interface {
|
|
Version() string
|
|
Description() string
|
|
Migrate(ctx context.Context, x *xorm.Engine) error
|
|
ShouldCleanCache() bool
|
|
}
|
|
|
|
type migration struct {
|
|
version string
|
|
description string
|
|
migrate func(ctx context.Context, x *xorm.Engine) error
|
|
shouldCleanCache bool
|
|
}
|
|
|
|
// Version returns the migration's version
|
|
func (m *migration) Version() string {
|
|
return m.version
|
|
}
|
|
|
|
// Description returns the migration's description
|
|
func (m *migration) Description() string {
|
|
return m.description
|
|
}
|
|
|
|
// Migrate executes the migration
|
|
func (m *migration) Migrate(ctx context.Context, x *xorm.Engine) error {
|
|
return m.migrate(ctx, x)
|
|
}
|
|
|
|
// ShouldCleanCache should clean the cache
|
|
func (m *migration) ShouldCleanCache() bool {
|
|
return m.shouldCleanCache
|
|
}
|
|
|
|
// NewMigration creates a new migration
|
|
func NewMigration(version, desc string, fn func(ctx context.Context, x *xorm.Engine) error, shouldCleanCache bool) Migration {
|
|
return &migration{version: version, description: desc, migrate: fn, shouldCleanCache: shouldCleanCache}
|
|
}
|
|
|
|
// Use noopMigration when there is a migration that has been no-oped
|
|
var noopMigration = func(_ context.Context, _ *xorm.Engine) error { return nil }
|
|
|
|
var migrations = []Migration{
|
|
// 0->1
|
|
NewMigration("v0.0.1", "this is first version, no operation", noopMigration, false),
|
|
NewMigration("v0.3.0", "add user language", addUserLanguage, false),
|
|
NewMigration("v0.4.1", "add recommend and reserved tag fields", addTagRecommendedAndReserved, false),
|
|
NewMigration("v0.5.0", "add activity timeline", addActivityTimeline, false),
|
|
NewMigration("v0.6.0", "add user role", addRoleFeatures, false),
|
|
NewMigration("v1.0.0", "add theme and private mode", addThemeAndPrivateMode, true),
|
|
NewMigration("v1.0.2", "add new answer notification", addNewAnswerNotification, true),
|
|
NewMigration("v1.0.5", "add plugin", addPlugin, false),
|
|
NewMigration("v1.0.7", "add user pin hide features", addRolePinAndHideFeatures, true),
|
|
NewMigration("v1.0.8", "update accept answer rank", updateAcceptAnswerRank, true),
|
|
NewMigration("v1.0.9", "add login limitations", addLoginLimitations, true),
|
|
NewMigration("v1.1.0-beta.1", "update user pin hide features", updateRolePinAndHideFeatures, true),
|
|
NewMigration("v1.1.0-beta.2", "update question post time", updateQuestionPostTime, true),
|
|
NewMigration("v1.1.0", "add gravatar base url", updateCount, true),
|
|
NewMigration("v1.1.1", "update the length of revision content", updateTheLengthOfRevisionContent, false),
|
|
NewMigration("v1.1.2", "add notification config", addNoticeConfig, true),
|
|
NewMigration("v1.1.3", "set default user notification config", setDefaultUserNotificationConfig, false),
|
|
NewMigration("v1.2.0", "add recover answer permission", addRecoverPermission, true),
|
|
NewMigration("v1.2.1", "add password login control", addPasswordLoginControl, true),
|
|
NewMigration("v1.2.5", "add notification plugin and theme config", addNotificationPluginAndThemeConfig, true),
|
|
NewMigration("v1.3.0", "add review", addReview, false),
|
|
NewMigration("v1.3.6", "add hot score to question table", addQuestionHotScore, true),
|
|
NewMigration("v1.4.0", "add badge/badge_group/badge_award table", addBadges, true),
|
|
NewMigration("v1.4.1", "add question link", addQuestionLink, true),
|
|
NewMigration("v1.4.2", "add the number of question links", addQuestionLinkedCount, true),
|
|
NewMigration("v1.4.5", "add file record", addFileRecord, true),
|
|
NewMigration("v1.5.1", "add plugin kv storage", addPluginKVStorage, true),
|
|
NewMigration("v1.6.0", "move user config to interface", moveUserConfigToInterface, true),
|
|
NewMigration("v1.7.0", "add optional tags", addOptionalTags, true),
|
|
NewMigration("v1.7.2", "expand avatar column length", expandAvatarColumnLength, false),
|
|
NewMigration("v1.8.0", "change admin menu", updateAdminMenuSettings, true),
|
|
NewMigration("v1.8.1", "ai feat", aiFeat, true),
|
|
NewMigration("v2.0.1", "change avatar type to text", updateAvatarType, false),
|
|
NewMigration("v2.0.2", "add reasoning content to ai conversation record", addAIConversationReasoningContent, false),
|
|
NewMigration("v2.0.3", "add require email verification login setting", addRequireEmailVerification, true),
|
|
}
|
|
|
|
func GetMigrations() []Migration {
|
|
return migrations
|
|
}
|
|
|
|
// GetCurrentDBVersion returns the current db version
|
|
func GetCurrentDBVersion(engine *xorm.Engine) (int64, error) {
|
|
if err := engine.Sync(new(entity.Version)); err != nil {
|
|
return -1, fmt.Errorf("sync version failed: %v", err)
|
|
}
|
|
|
|
currentVersion := &entity.Version{ID: 1}
|
|
has, err := engine.Get(currentVersion)
|
|
if err != nil {
|
|
return -1, fmt.Errorf("get first version failed: %v", err)
|
|
}
|
|
if !has {
|
|
_, err := engine.InsertOne(&entity.Version{ID: 1, VersionNumber: 0})
|
|
if err != nil {
|
|
return -1, fmt.Errorf("insert first version failed: %v", err)
|
|
}
|
|
return 0, nil
|
|
}
|
|
return currentVersion.VersionNumber, nil
|
|
}
|
|
|
|
// ExpectedVersion returns the expected db version
|
|
func ExpectedVersion() int64 {
|
|
return int64(minDBVersion + len(migrations))
|
|
}
|
|
|
|
// Migrate database to current version
|
|
func Migrate(debug bool, dbConf *data.Database, cacheConf *data.CacheConf, upgradeToSpecificVersion string) error {
|
|
cache, cacheCleanup, err := data.NewCache(cacheConf)
|
|
if err != nil {
|
|
fmt.Println("new cache failed:", err.Error())
|
|
}
|
|
engine, err := data.NewDB(debug, dbConf)
|
|
if err != nil {
|
|
fmt.Println("new database failed: ", err.Error())
|
|
return err
|
|
}
|
|
defer func() {
|
|
_ = engine.Close()
|
|
}()
|
|
|
|
currentDBVersion, err := GetCurrentDBVersion(engine)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expectedVersion := ExpectedVersion()
|
|
if len(upgradeToSpecificVersion) > 0 {
|
|
fmt.Printf("[migrate] user set upgrade to version: %s\n", upgradeToSpecificVersion)
|
|
for i, m := range migrations {
|
|
if m.Version() == upgradeToSpecificVersion {
|
|
currentDBVersion = int64(i)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for currentDBVersion < expectedVersion {
|
|
fmt.Printf("[migrate] current db version is %d, try to migrate version %d, latest version is %d\n",
|
|
currentDBVersion, currentDBVersion+1, expectedVersion)
|
|
migrationFunc := migrations[currentDBVersion]
|
|
fmt.Printf("[migrate] try to migrate Answer version %s, description: %s\n", migrationFunc.Version(), migrationFunc.Description())
|
|
if err := migrationFunc.Migrate(context.Background(), engine); err != nil {
|
|
fmt.Printf("[migrate] migrate to db version %d failed: %s\n", currentDBVersion+1, err.Error())
|
|
return err
|
|
}
|
|
if migrationFunc.ShouldCleanCache() {
|
|
if err := cache.Flush(context.Background()); err != nil {
|
|
fmt.Printf("[migrate] flush cache failed: %s\n", err.Error())
|
|
}
|
|
}
|
|
fmt.Printf("[migrate] migrate to db version %d success\n", currentDBVersion+1)
|
|
if _, err := engine.Update(&entity.Version{ID: 1, VersionNumber: currentDBVersion + 1}); err != nil {
|
|
fmt.Printf("[migrate] migrate to db version %d, update failed: %s", currentDBVersion+1, err.Error())
|
|
return err
|
|
}
|
|
currentDBVersion++
|
|
}
|
|
if cache != nil {
|
|
cacheCleanup()
|
|
}
|
|
return nil
|
|
}
|