chore: import clean Agent Desk snapshot (2/7)
This commit is contained in:
7
internal/migration/000001_init_schema.go
Normal file
7
internal/migration/000001_init_schema.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package migration
|
||||
|
||||
func init() {
|
||||
register(1, "init schema migration", func() error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
256
internal/migration/000002_init_auth_data.go
Normal file
256
internal/migration/000002_init_auth_data.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/constants"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/repositories"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(2, "init auth builtin data", func() error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
permissions, err := ensurePermissions(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roles, err := ensureRoles(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ensureRolePermissions(ctx.Tx, roles, permissions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureBootstrapAdmin(ctx.Tx, roles[constants.RoleCodeSuperAdmin])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func ensurePermissions(tx *gorm.DB) (map[string]*models.Permission, error) {
|
||||
permissions := make(map[string]*models.Permission, len(constants.Permissions))
|
||||
now := time.Now()
|
||||
|
||||
for _, spec := range constants.Permissions {
|
||||
permission := repositories.PermissionRepository.FindOne(tx, sqls.NewCnd().Eq("code", spec.Code))
|
||||
if permission == nil {
|
||||
permission = &models.Permission{
|
||||
Name: spec.Name,
|
||||
Code: spec.Code,
|
||||
Type: spec.Type,
|
||||
GroupName: spec.GroupName,
|
||||
Method: spec.Method,
|
||||
APIPath: spec.APIPath,
|
||||
SortNo: spec.SortNo,
|
||||
Status: enums.StatusOk,
|
||||
IsBuiltin: true,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: constants.SystemAuditUserID,
|
||||
CreateUserName: constants.SystemAuditUserName,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: constants.SystemAuditUserID,
|
||||
UpdateUserName: constants.SystemAuditUserName,
|
||||
},
|
||||
}
|
||||
if err := repositories.PermissionRepository.Create(tx, permission); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// slog.Info("initialized builtin permission", "code", spec.Code)
|
||||
} else {
|
||||
if err := repositories.PermissionRepository.Updates(tx, permission.ID, map[string]any{
|
||||
"name": spec.Name,
|
||||
"type": spec.Type,
|
||||
"group_name": spec.GroupName,
|
||||
"method": spec.Method,
|
||||
"api_path": spec.APIPath,
|
||||
"sort_no": spec.SortNo,
|
||||
"status": enums.StatusOk,
|
||||
"is_builtin": true,
|
||||
"update_user_id": constants.SystemAuditUserID,
|
||||
"update_user_name": constants.SystemAuditUserName,
|
||||
"updated_at": now,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
permission = repositories.PermissionRepository.Get(tx, permission.ID)
|
||||
}
|
||||
permissions[spec.Code] = permission
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func ensureRoles(tx *gorm.DB) (map[string]*models.Role, error) {
|
||||
roles := make(map[string]*models.Role, len(constants.Roles))
|
||||
now := time.Now()
|
||||
|
||||
for _, spec := range constants.Roles {
|
||||
role := repositories.RoleRepository.GetByCode(tx, spec.Code)
|
||||
if role == nil {
|
||||
role = &models.Role{
|
||||
Name: spec.Name,
|
||||
Code: spec.Code,
|
||||
Status: enums.StatusOk,
|
||||
IsSystem: true,
|
||||
SortNo: spec.SortNo,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: constants.SystemAuditUserID,
|
||||
CreateUserName: constants.SystemAuditUserName,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: constants.SystemAuditUserID,
|
||||
UpdateUserName: constants.SystemAuditUserName,
|
||||
},
|
||||
}
|
||||
if err := repositories.RoleRepository.Create(tx, role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// slog.Info("initialized builtin role", "code", spec.Code)
|
||||
} else {
|
||||
if err := repositories.RoleRepository.Updates(tx, role.ID, map[string]any{
|
||||
"name": spec.Name,
|
||||
"sort_no": spec.SortNo,
|
||||
"status": enums.StatusOk,
|
||||
"is_system": true,
|
||||
"update_user_id": constants.SystemAuditUserID,
|
||||
"update_user_name": constants.SystemAuditUserName,
|
||||
"updated_at": now,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role = repositories.RoleRepository.Get(tx, role.ID)
|
||||
}
|
||||
roles[spec.Code] = role
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func ensureRolePermissions(tx *gorm.DB, roles map[string]*models.Role, permissions map[string]*models.Permission) error {
|
||||
now := time.Now()
|
||||
|
||||
for roleCode, rolePermissions := range constants.RolePermissions {
|
||||
role := roles[roleCode]
|
||||
if role == nil {
|
||||
return errors.New("builtin role not found: " + roleCode)
|
||||
}
|
||||
|
||||
for _, permissionSpec := range rolePermissions {
|
||||
permission := permissions[permissionSpec.Code]
|
||||
if permission == nil {
|
||||
return errors.New("builtin permission not found: " + permissionSpec.Code)
|
||||
}
|
||||
|
||||
exists := repositories.RolePermissionRepository.FindOne(tx, sqls.NewCnd().
|
||||
Eq("role_id", role.ID).
|
||||
Eq("permission_id", permission.ID))
|
||||
if exists != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := repositories.RolePermissionRepository.Create(tx, &models.RolePermission{
|
||||
RoleID: role.ID,
|
||||
PermissionID: permission.ID,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: constants.SystemAuditUserID,
|
||||
CreateUserName: constants.SystemAuditUserName,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: constants.SystemAuditUserID,
|
||||
UpdateUserName: constants.SystemAuditUserName,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureBootstrapAdmin(tx *gorm.DB, superAdminRole *models.Role) error {
|
||||
if superAdminRole == nil {
|
||||
return errors.New("super admin role not found")
|
||||
}
|
||||
|
||||
username := constants.BootstrapAdminUsername
|
||||
nickname := constants.BootstrapAdminNickname
|
||||
password := constants.BootstrapAdminPassword
|
||||
|
||||
if strings.TrimSpace(password) == "" {
|
||||
password = "ChangeMe123!"
|
||||
slog.Warn("bootstrap admin password is empty, using default password", "username", username)
|
||||
}
|
||||
|
||||
user := repositories.UserRepository.FindOne(tx, sqls.NewCnd().Eq("username", username))
|
||||
now := time.Now()
|
||||
if user == nil {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user = &models.User{
|
||||
Username: username,
|
||||
Nickname: nickname,
|
||||
Password: string(hashedPassword),
|
||||
UserType: enums.UserTypeEmployee,
|
||||
Status: enums.StatusOk,
|
||||
Remark: "bootstrap super admin",
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: constants.SystemAuditUserID,
|
||||
CreateUserName: constants.SystemAuditUserName,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: constants.SystemAuditUserID,
|
||||
UpdateUserName: constants.SystemAuditUserName,
|
||||
},
|
||||
}
|
||||
if err := repositories.UserRepository.Create(tx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
slog.Warn("initialized bootstrap admin user", "username", username)
|
||||
} else {
|
||||
if err := repositories.UserRepository.Updates(tx, user.ID, map[string]any{
|
||||
"nickname": nickname,
|
||||
"status": enums.StatusOk,
|
||||
"update_user_id": constants.SystemAuditUserID,
|
||||
"update_user_name": constants.SystemAuditUserName,
|
||||
"updated_at": now,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
user = repositories.UserRepository.Get(tx, user.ID)
|
||||
}
|
||||
|
||||
exists := repositories.UserRoleRepository.FindOne(tx, sqls.NewCnd().
|
||||
Eq("user_id", user.ID).
|
||||
Eq("role_id", superAdminRole.ID))
|
||||
if exists != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return repositories.UserRoleRepository.Create(tx, &models.UserRole{
|
||||
UserID: user.ID,
|
||||
RoleID: superAdminRole.ID,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: constants.SystemAuditUserID,
|
||||
CreateUserName: constants.SystemAuditUserName,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: constants.SystemAuditUserID,
|
||||
UpdateUserName: constants.SystemAuditUserName,
|
||||
},
|
||||
})
|
||||
}
|
||||
21
internal/migration/000004_sync_ai_agent_permissions.go
Normal file
21
internal/migration/000004_sync_ai_agent_permissions.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package migration
|
||||
|
||||
import "github.com/mlogclub/simple/sqls"
|
||||
|
||||
func init() {
|
||||
register(4, "sync ai agent permissions", func() error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
permissions, err := ensurePermissions(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roles, err := ensureRoles(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureRolePermissions(ctx.Tx, roles, permissions)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"agent-desk/internal/models"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(6, "backfill conversation customer name", func() error {
|
||||
db := sqls.DB()
|
||||
if !db.Migrator().HasColumn(&models.Conversation{}, "customer_name") {
|
||||
return nil
|
||||
}
|
||||
return db.Exec(`
|
||||
UPDATE t_conversation
|
||||
SET customer_name = (
|
||||
SELECT name FROM t_customer WHERE t_customer.id = t_conversation.customer_id
|
||||
)
|
||||
WHERE customer_id > 0
|
||||
AND (customer_name = '' OR customer_name IS NULL)
|
||||
`).Error
|
||||
})
|
||||
}
|
||||
21
internal/migration/000007_sync_notification_permissions.go
Normal file
21
internal/migration/000007_sync_notification_permissions.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package migration
|
||||
|
||||
import "github.com/mlogclub/simple/sqls"
|
||||
|
||||
func init() {
|
||||
register(7, "sync notification permissions", func() error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
permissions, err := ensurePermissions(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roles, err := ensureRoles(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureRolePermissions(ctx.Tx, roles, permissions)
|
||||
})
|
||||
})
|
||||
}
|
||||
21
internal/migration/000009_sync_wxwork_outbox_permissions.go
Normal file
21
internal/migration/000009_sync_wxwork_outbox_permissions.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package migration
|
||||
|
||||
import "github.com/mlogclub/simple/sqls"
|
||||
|
||||
func init() {
|
||||
register(9, "sync wxwork outbox permissions", func() error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
permissions, err := ensurePermissions(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
roles, err := ensureRoles(ctx.Tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureRolePermissions(ctx.Tx, roles, permissions)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/constants"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"time"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(10, "repair bootstrap admin user type", func() error {
|
||||
db := sqls.DB()
|
||||
superAdmins := db.Table("t_user_role AS ur").Select("ur.user_id").
|
||||
Joins("JOIN t_role AS r ON r.id = ur.role_id").
|
||||
Where("r.code = ? AND r.status = ?", constants.RoleCodeSuperAdmin, enums.StatusOk)
|
||||
|
||||
// Migration 2 has already run on existing installations. Repair only the
|
||||
// active bootstrap account that already has the super admin role.
|
||||
return db.Model(&models.User{}).
|
||||
Where("username = ?", constants.BootstrapAdminUsername).
|
||||
Where("user_type = ?", enums.UserTypeUser).
|
||||
Where("status = ?", enums.StatusOk).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("id IN (?)", superAdmins).
|
||||
Updates(map[string]any{
|
||||
"user_type": enums.UserTypeEmployee,
|
||||
"updated_at": time.Now(),
|
||||
"update_user_id": constants.SystemAuditUserID,
|
||||
"update_user_name": constants.SystemAuditUserName,
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/constants"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func setupBootstrapAdminTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "auth.db")), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{TablePrefix: "t_", SingularTable: true},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.AutoMigrate(&models.User{}, &models.Role{}, &models.UserRole{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = conn.Close() })
|
||||
return db
|
||||
}
|
||||
|
||||
func TestBootstrapAdminCreatedAsEmployee(t *testing.T) {
|
||||
db := setupBootstrapAdminTestDB(t)
|
||||
role := models.Role{Code: constants.RoleCodeSuperAdmin, Status: enums.StatusOk}
|
||||
if err := db.Create(&role).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ensureBootstrapAdmin(db, &role); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var user models.User
|
||||
if err := db.Where("username = ?", constants.BootstrapAdminUsername).First(&user).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if user.UserType != enums.UserTypeEmployee {
|
||||
t.Fatalf("bootstrap admin type = %q, want employee", user.UserType)
|
||||
}
|
||||
var count int64
|
||||
if err := db.Model(&models.UserRole{}).Where("user_id = ? AND role_id = ?", user.ID, role.ID).Count(&count).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("super admin role assignments = %d, want 1", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepairBootstrapAdminUserType(t *testing.T) {
|
||||
repair, ok := migrationFuncs[10]
|
||||
if !ok {
|
||||
t.Fatal("bootstrap admin repair migration is not registered")
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
username string
|
||||
userType enums.UserType
|
||||
status enums.Status
|
||||
roleCode string
|
||||
roleStatus enums.Status
|
||||
deleted bool
|
||||
want enums.UserType
|
||||
}{
|
||||
{"legacy admin", "admin", enums.UserTypeUser, enums.StatusOk, constants.RoleCodeSuperAdmin, enums.StatusOk, false, enums.UserTypeEmployee},
|
||||
{"already employee", "admin", enums.UserTypeEmployee, enums.StatusOk, constants.RoleCodeSuperAdmin, enums.StatusOk, false, enums.UserTypeEmployee},
|
||||
{"ordinary user", "customer", enums.UserTypeUser, enums.StatusOk, "", enums.StatusOk, false, enums.UserTypeUser},
|
||||
{"same name without role", "admin", enums.UserTypeUser, enums.StatusOk, "", enums.StatusOk, false, enums.UserTypeUser},
|
||||
{"other super admin", "other", enums.UserTypeUser, enums.StatusOk, constants.RoleCodeSuperAdmin, enums.StatusOk, false, enums.UserTypeUser},
|
||||
{"disabled admin", "admin", enums.UserTypeUser, enums.StatusDisabled, constants.RoleCodeSuperAdmin, enums.StatusOk, false, enums.UserTypeUser},
|
||||
{"disabled role", "admin", enums.UserTypeUser, enums.StatusOk, constants.RoleCodeSuperAdmin, enums.StatusDisabled, false, enums.UserTypeUser},
|
||||
{"deleted admin", "admin", enums.UserTypeUser, enums.StatusOk, constants.RoleCodeSuperAdmin, enums.StatusOk, true, enums.UserTypeUser},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
db := setupBootstrapAdminTestDB(t)
|
||||
sqls.SetDB(db)
|
||||
t.Cleanup(func() { sqls.SetDB(nil) })
|
||||
user := models.User{Username: tc.username, Password: "unchanged-test-value", UserType: tc.userType, Status: tc.status}
|
||||
if tc.deleted {
|
||||
now := time.Now()
|
||||
user.DeletedAt = &now
|
||||
}
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tc.roleCode != "" {
|
||||
role := models.Role{Code: tc.roleCode, Status: tc.roleStatus}
|
||||
if err := db.Create(&role).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Create(&models.UserRole{UserID: user.ID, RoleID: role.ID}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := repair.Fn(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got models.User
|
||||
if err := db.First(&got, user.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.UserType != tc.want {
|
||||
t.Fatalf("type = %q, want %q", got.UserType, tc.want)
|
||||
}
|
||||
if got.Password != user.Password || got.Status != user.Status {
|
||||
t.Fatal("repair changed password or account status")
|
||||
}
|
||||
updatedAt := got.UpdatedAt
|
||||
if err := repair.Fn(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.First(&got, user.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.UpdatedAt.Equal(updatedAt) {
|
||||
t.Fatal("second repair changed updated_at")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
56
internal/migration/000011_remove_chinese_platforms.go
Normal file
56
internal/migration/000011_remove_chinese_platforms.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(11, "remove chinese platforms tables and data", func() error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
tx := ctx.Tx
|
||||
|
||||
// 1. Drop orphaned tables
|
||||
tables := []string{
|
||||
"t_wx_work_kf_conversation",
|
||||
"t_wx_work_kf_message_ref",
|
||||
"t_wx_work_kf_sync_state",
|
||||
}
|
||||
for _, table := range tables {
|
||||
if err := tx.Exec("DROP TABLE IF EXISTS " + table).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Remove channel rows of removed types
|
||||
removedChannelTypes := []string{"wxwork_kf", "wechat_mp", "lark", "wechat"}
|
||||
if err := tx.Exec("DELETE FROM t_channel WHERE channel_type IN (?)", removedChannelTypes).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Remove outbox entries for removed channels
|
||||
if err := tx.Exec("DELETE FROM t_channel_message_outbox WHERE channel_type IN (?)", removedChannelTypes).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Remove customer identities of removed external sources
|
||||
removedExternalSources := []string{"wxwork_kf", "lark", "wechat"}
|
||||
if err := tx.Exec("DELETE FROM t_customer_identity WHERE external_source IN (?)", removedExternalSources).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Remove obsolete permissions and role permissions
|
||||
removedPermissionCodes := []string{"wxworkOutbox.view"}
|
||||
var permissionIDs []int64
|
||||
if err := tx.Table("t_permission").Where("code IN (?)", removedPermissionCodes).Pluck("id", &permissionIDs).Error; err == nil && len(permissionIDs) > 0 {
|
||||
if err := tx.Exec("DELETE FROM t_role_permission WHERE permission_id IN (?)", permissionIDs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("DELETE FROM t_permission WHERE id IN (?)", permissionIDs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
3
internal/migration/README.md
Normal file
3
internal/migration/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# data migration
|
||||
|
||||
data migration only, DLL are prohibited.
|
||||
101
internal/migration/migration.go
Normal file
101
internal/migration/migration.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/services"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
var migrationFuncs = make(map[int64]MigrationFunc)
|
||||
var versions = make([]int64, 0)
|
||||
var migrations = make(map[int64]models.Migration, 0)
|
||||
var mu sync.Mutex
|
||||
|
||||
type MigrationFunc struct {
|
||||
Version int64
|
||||
Remark string
|
||||
Fn func() error
|
||||
}
|
||||
|
||||
func Migrate() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if list := services.MigrationService.Find(sqls.NewCnd().Asc("version")); len(list) > 0 {
|
||||
for _, element := range list {
|
||||
migrations[element.Version] = element
|
||||
}
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
if err := runMigration(version); err != nil {
|
||||
slog.Error("migrate failed", "version", version, "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func register(version int64, remark string, fn func() error) {
|
||||
if len(versions) == 0 || version > versions[len(versions)-1] {
|
||||
versions = append(versions, version)
|
||||
migrationFuncs[version] = MigrationFunc{
|
||||
Version: version,
|
||||
Remark: remark,
|
||||
Fn: fn,
|
||||
}
|
||||
} else {
|
||||
slog.Error("register migration failed, version is less than latest version", slog.Any("version", version))
|
||||
panic(errors.New("register migration failed, version is less than latest version. version: " + cast.ToString(version)))
|
||||
}
|
||||
}
|
||||
|
||||
func runMigration(version int64) error {
|
||||
migration, found := migrations[version]
|
||||
if found && migration.Success {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, ok := migrationFuncs[version]
|
||||
if !ok {
|
||||
return errors.New("migration function not found")
|
||||
}
|
||||
|
||||
err := f.Fn()
|
||||
|
||||
if !found {
|
||||
migration = models.Migration{
|
||||
Version: f.Version,
|
||||
Remark: f.Remark,
|
||||
Success: false,
|
||||
RetryCount: 0,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
migration.Success = true
|
||||
} else {
|
||||
migration.Success = false
|
||||
migration.ErrorInfo = err.Error()
|
||||
}
|
||||
migration.RetryCount++
|
||||
migration.UpdatedAt = time.Now()
|
||||
if found {
|
||||
if e := services.MigrationService.Update(&migration); e != nil {
|
||||
slog.Error("update migration failed", "version", version, "error", err)
|
||||
}
|
||||
} else {
|
||||
if e := services.MigrationService.Create(&migration); e != nil {
|
||||
slog.Error("create migration failed", "version", version, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user