Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

204 строки
6.3 KiB

  1. package controller
  2. import (
  3. "errors"
  4. "net/http"
  5. "strconv"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/model"
  8. user_migration "github.com/QuantumNous/new-api/service/user_migration"
  9. "github.com/gin-gonic/gin"
  10. )
  11. func ListMigrationUsers(c *gin.Context) {
  12. page, _ := strconv.Atoi(c.DefaultQuery("page", "0"))
  13. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
  14. keyword := c.Query("keyword")
  15. if pageSize <= 0 || pageSize > 200 {
  16. pageSize = 50
  17. }
  18. users, total, err := model.QueryLocalUsersForMigration(page, pageSize, keyword)
  19. if err != nil {
  20. c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "database error"})
  21. return
  22. }
  23. snapshots := make([]user_migration.RemoteUserSnapshot, 0, len(users))
  24. for _, user := range users {
  25. bindings, _ := model.GetUserOAuthBindingsByUserId(user.Id)
  26. snapshots = append(snapshots, buildRemoteUserSnapshot(user, bindings))
  27. }
  28. c.JSON(http.StatusOK, gin.H{"success": true, "data": snapshots, "total": total})
  29. }
  30. func QueryMigrationUsers(c *gin.Context) {
  31. var req user_migration.QueryRemoteUsersRequest
  32. if err := c.ShouldBindJSON(&req); err != nil {
  33. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid request body"})
  34. return
  35. }
  36. if req.SelectionMode != user_migration.SelectionModeExplicitIDs {
  37. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid selection_mode"})
  38. return
  39. }
  40. if req.PageSize <= 0 || req.PageSize > 200 {
  41. req.PageSize = 50
  42. }
  43. indexed := make(map[int]struct{}, len(req.SourceUserIDs))
  44. orderedIDs := make([]int, 0, len(req.SourceUserIDs))
  45. for _, id := range req.SourceUserIDs {
  46. if id <= 0 {
  47. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid source_user_ids"})
  48. return
  49. }
  50. if _, ok := indexed[id]; ok {
  51. continue
  52. }
  53. indexed[id] = struct{}{}
  54. orderedIDs = append(orderedIDs, id)
  55. }
  56. summary := &user_migration.SelectionSummary{
  57. Requested: len(orderedIDs),
  58. Excluded: make([]user_migration.SelectionExcluded, 0),
  59. }
  60. snapshots := make([]user_migration.RemoteUserSnapshot, 0, len(orderedIDs))
  61. for _, userID := range orderedIDs {
  62. user, err := model.GetUserById(userID, true)
  63. if err != nil || user == nil {
  64. summary.Excluded = append(summary.Excluded, user_migration.SelectionExcluded{UserID: userID, Reason: "not_found"})
  65. continue
  66. }
  67. if user.Role == common.RoleRootUser {
  68. summary.Excluded = append(summary.Excluded, user_migration.SelectionExcluded{UserID: userID, Reason: "root_user_not_migratable"})
  69. continue
  70. }
  71. if user.Source == common.UserSourceSynced {
  72. summary.Excluded = append(summary.Excluded, user_migration.SelectionExcluded{UserID: userID, Reason: "already_synced"})
  73. continue
  74. }
  75. bindings, _ := model.GetUserOAuthBindingsByUserId(userID)
  76. snapshots = append(snapshots, buildRemoteUserSnapshot(user, bindings))
  77. }
  78. summary.Matched = len(snapshots)
  79. start := req.Page * req.PageSize
  80. if start > len(snapshots) {
  81. start = len(snapshots)
  82. }
  83. end := start + req.PageSize
  84. if end > len(snapshots) {
  85. end = len(snapshots)
  86. }
  87. c.JSON(http.StatusOK, gin.H{
  88. "success": true,
  89. "data": snapshots[start:end],
  90. "total": len(snapshots),
  91. "selection_summary": summary,
  92. })
  93. }
  94. func GetMigrationUser(c *gin.Context) {
  95. userId, err := strconv.Atoi(c.Param("id"))
  96. if err != nil {
  97. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid user id"})
  98. return
  99. }
  100. user, err := model.GetUserById(userId, true)
  101. if err != nil || user == nil {
  102. c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "user not found"})
  103. return
  104. }
  105. if user.Role == common.RoleRootUser {
  106. c.JSON(http.StatusNotFound, gin.H{"success": false, "error": "user not found"})
  107. return
  108. }
  109. bindings, _ := model.GetUserOAuthBindingsByUserId(userId)
  110. c.JSON(http.StatusOK, gin.H{"success": true, "data": buildRemoteUserSnapshot(user, bindings)})
  111. }
  112. func ConvertMigrationUserToSynced(c *gin.Context) {
  113. userId, err := strconv.Atoi(c.Param("id"))
  114. if err != nil {
  115. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid user id"})
  116. return
  117. }
  118. var req user_migration.ConvertRemoteUserRequest
  119. if err := c.ShouldBindJSON(&req); err != nil || req.RemoteUserId == 0 {
  120. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid request body"})
  121. return
  122. }
  123. if err := model.ConvertUserToSynced(userId, req.RemoteUserId, req.SyncedQuota); err != nil {
  124. if errors.Is(err, model.ErrConvertRootUserToSynced) ||
  125. errors.Is(err, model.ErrSyncedUserRemoteUserIDImmutable) {
  126. c.JSON(http.StatusConflict, gin.H{"success": false, "error": err.Error()})
  127. return
  128. }
  129. c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
  130. return
  131. }
  132. c.JSON(http.StatusOK, gin.H{"success": true})
  133. }
  134. func CheckSyncedCopy(c *gin.Context) {
  135. cnUserId, err := strconv.Atoi(c.Query("cn_user_id"))
  136. if err != nil {
  137. c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "invalid cn_user_id"})
  138. return
  139. }
  140. hasCopy, err := model.HasOVSyncedCopyByRemoteId(cnUserId)
  141. if err != nil {
  142. c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
  143. return
  144. }
  145. c.JSON(http.StatusOK, gin.H{"success": true, "has_copy": hasCopy})
  146. }
  147. func buildRemoteUserSnapshot(user *model.User, bindings []*model.UserOAuthBinding) user_migration.RemoteUserSnapshot {
  148. snapshot := user_migration.RemoteUserSnapshot{
  149. Id: user.Id,
  150. Username: user.Username,
  151. Password: user.Password,
  152. Email: user.Email,
  153. DisplayName: user.DisplayName,
  154. Status: user.Status,
  155. Role: user.Role,
  156. Group: user.Group,
  157. Quota: user.Quota,
  158. AffCode: user.AffCode,
  159. CreatedAt: user.CreatedAt,
  160. Setting: user.Setting,
  161. GitHubId: user.GitHubId,
  162. DiscordId: user.DiscordId,
  163. OidcId: user.OidcId,
  164. WeChatId: user.WeChatId,
  165. TelegramId: user.TelegramId,
  166. LinuxDOId: user.LinuxDOId,
  167. Source: user.Source,
  168. RemoteUserId: user.RemoteUserId,
  169. SyncedQuota: user.SyncedQuota,
  170. }
  171. for _, binding := range bindings {
  172. provider, err := model.GetCustomOAuthProviderById(binding.ProviderId)
  173. if err != nil {
  174. continue
  175. }
  176. snapshot.OAuthBindings = append(snapshot.OAuthBindings, user_migration.RemoteOAuthBinding{
  177. ProviderSlug: provider.Slug,
  178. ProviderUserId: binding.ProviderUserId,
  179. })
  180. }
  181. return snapshot
  182. }