|
- package controller
-
- import (
- "net/http"
- "strconv"
-
- "github.com/QuantumNous/new-api/model"
- user_migration "github.com/QuantumNous/new-api/service/user_migration"
- "github.com/QuantumNous/new-api/setting/system_setting"
- "github.com/gin-gonic/gin"
- )
-
- func CreateUserMigrationBatch(c *gin.Context) {
- var req struct {
- Name string `json:"name" binding:"required"`
- SourceRegion string `json:"source_region" binding:"required"`
- TargetRegion string `json:"target_region" binding:"required"`
- }
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()})
- return
- }
-
- operatorID, _ := c.Get("id")
- batch := &model.UserMigrationBatch{
- Name: req.Name,
- SourceRegion: req.SourceRegion,
- TargetRegion: req.TargetRegion,
- OperatorId: operatorID.(int),
- }
- if err := model.CreateUserMigrationBatch(batch); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
- c.JSON(http.StatusOK, gin.H{"success": true, "data": batch})
- }
-
- func ListUserMigrationBatches(c *gin.Context) {
- var batches []model.UserMigrationBatch
- if err := model.DB.Order("id desc").Find(&batches).Error; err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
- c.JSON(http.StatusOK, gin.H{"success": true, "data": batches})
- }
-
- func GetUserMigrationBatch(c *gin.Context) {
- batchID, _ := strconv.Atoi(c.Param("id"))
-
- var batch model.UserMigrationBatch
- if err := model.DB.First(&batch, batchID).Error; err != nil {
- c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "batch not found"})
- return
- }
-
- page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
- pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
- if pageSize <= 0 {
- pageSize = 50
- }
- if pageSize > 200 {
- pageSize = 200
- }
-
- var items []model.UserMigrationItem
- var total int64
- model.DB.Model(&model.UserMigrationItem{}).Where("batch_id = ?", batchID).Count(&total)
- model.DB.Where("batch_id = ?", batchID).Order("id asc").Offset(page * pageSize).Limit(pageSize).Find(&items)
-
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "data": batch,
- "items": items,
- "total": total,
- })
- }
-
- func ScanUserMigrationBatch(c *gin.Context) {
- batchID, _ := strconv.Atoi(c.Param("id"))
- locked, err := model.TryLockBatchForScan(batchID)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
- if !locked {
- c.JSON(http.StatusConflict, gin.H{"success": false, "error": "batch is already being scanned or not in a scannable state"})
- return
- }
-
- svc := newMigrationService()
- go func() {
- if err := svc.ScanBatch(batchID); err != nil {
- model.DB.Model(&model.UserMigrationBatch{}).Where("id = ?", batchID).
- Update("status", model.UserMigrationBatchStatusFailed)
- }
- }()
-
- c.JSON(http.StatusAccepted, gin.H{"success": true, "message": "scan started"})
- }
-
- func ResolveUserMigrationItem(c *gin.Context) {
- itemID, _ := strconv.Atoi(c.Param("id"))
-
- var item model.UserMigrationItem
- if err := model.DB.Select("id", "batch_id").First(&item, itemID).Error; err != nil {
- c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "item not found"})
- return
- }
-
- var req struct {
- ResolutionStrategy string `json:"resolution_strategy" binding:"required"`
- TargetUserId int `json:"target_user_id"`
- }
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()})
- return
- }
-
- validStrategies := map[string]bool{
- model.UserMigrationStrategyCreateNew: true,
- model.UserMigrationStrategyMergeExisting: true,
- model.UserMigrationStrategySkip: true,
- }
- if !validStrategies[req.ResolutionStrategy] {
- c.JSON(http.StatusBadRequest, gin.H{
- "success": false,
- "error": "invalid resolution_strategy: must be create_new, merge_into_existing, or skip",
- })
- return
- }
- if req.ResolutionStrategy == model.UserMigrationStrategyMergeExisting && req.TargetUserId == 0 {
- c.JSON(http.StatusBadRequest, gin.H{
- "success": false,
- "error": "merge_into_existing requires target_user_id",
- })
- return
- }
-
- updates := map[string]any{
- "resolution_strategy": req.ResolutionStrategy,
- "status": model.UserMigrationItemStatusReady,
- }
- if req.TargetUserId != 0 {
- updates["target_user_id"] = req.TargetUserId
- }
- if err := model.DB.Model(&model.UserMigrationItem{}).Where("id = ?", itemID).Updates(updates).Error; err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
- if err := model.RefreshUserMigrationBatchStats(item.BatchId); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
-
- c.JSON(http.StatusOK, gin.H{"success": true})
- }
-
- func ExecuteUserMigrationBatch(c *gin.Context) {
- batchID, _ := strconv.Atoi(c.Param("id"))
- locked, err := model.TryLockBatchForExecution(batchID)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
- return
- }
- if !locked {
- c.JSON(http.StatusConflict, gin.H{"success": false, "error": "batch is already running or not in an executable state"})
- return
- }
-
- svc := newMigrationService()
- go func() {
- _ = svc.ExecuteBatch(batchID)
- }()
-
- c.JSON(http.StatusAccepted, gin.H{"success": true, "message": "execution started"})
- }
-
- func RetryUserMigrationBatch(c *gin.Context) {
- batchID, _ := strconv.Atoi(c.Param("id"))
-
- model.DB.Model(&model.UserMigrationItem{}).
- Where("batch_id = ? AND status = ?", batchID, model.UserMigrationItemStatusFailed).
- Updates(map[string]any{"status": model.UserMigrationItemStatusReady, "error_message": ""})
-
- locked, err := model.TryLockBatchForExecution(batchID)
- if err != nil || !locked {
- c.JSON(http.StatusConflict, gin.H{"success": false, "error": "batch cannot be retried now"})
- return
- }
-
- svc := newMigrationService()
- go func() {
- _ = svc.ExecuteBatch(batchID)
- }()
-
- c.JSON(http.StatusAccepted, gin.H{"success": true, "message": "retry started"})
- }
-
- func VerifyUserMigrationBatch(c *gin.Context) {
- batchID, _ := strconv.Atoi(c.Param("id"))
-
- var items []model.UserMigrationItem
- model.DB.Where("batch_id = ? AND status = ?", batchID, model.UserMigrationItemStatusMigrated).Find(&items)
-
- svc := newMigrationService()
- results := make([]map[string]any, 0, len(items))
- for _, item := range items {
- result, err := svc.VerifyItem(&item)
- entry := map[string]any{
- "source_user_id": item.SourceUserId,
- "target_user_id": item.TargetUserId,
- }
- if err != nil {
- entry["error"] = err.Error()
- } else {
- entry["result"] = result
- }
- results = append(results, entry)
- }
-
- c.JSON(http.StatusOK, gin.H{"success": true, "data": results})
- }
-
- func newMigrationService() *user_migration.Service {
- settings := system_setting.GetRegionSyncSettings()
- endpoint := settings.MasterEndpoint
- if settings.IsMaster && len(settings.SlaveEndpoints) > 0 {
- endpoint = settings.SlaveEndpoints[0]
- }
-
- client := user_migration.NewClient(endpoint, settings.SyncApiKey)
- return user_migration.NewService(client)
- }
|