aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortechknowlogick <techknowlogick@gitea.io>2023-03-14 03:45:21 -0400
committerGitHub <noreply@github.com>2023-03-14 03:45:21 -0400
commit03591f0f95823a0b1dcca969d2a3ed505c7e6d73 (patch)
tree0117b3cf1f27b4f0f7a0346fcf724fa3af0e3f8f
parentaac07d010f261c00fb3bd9644c71dc108c668c11 (diff)
downloadgitea-03591f0f95823a0b1dcca969d2a3ed505c7e6d73.tar.gz
gitea-03591f0f95823a0b1dcca969d2a3ed505c7e6d73.zip
add user rename endpoint to admin api (#22789)
this is a simple endpoint that adds the ability to rename users to the admin API. Note: this is not in a mergeable state. It would be better if this was handled by a PATCH/POST to the /api/v1/admin/users/{username} endpoint and the username is modified. --------- Co-authored-by: Jason Song <i@wolfogre.com>
-rw-r--r--models/issues/pull.go4
-rw-r--r--models/user/user.go4
-rw-r--r--modules/structs/user.go9
-rw-r--r--routers/api/v1/admin/user.go58
-rw-r--r--routers/api/v1/api.go1
-rw-r--r--routers/api/v1/swagger/options.go3
-rw-r--r--routers/web/org/setting.go2
-rw-r--r--routers/web/user/setting/profile.go52
-rw-r--r--services/agit/agit.go4
-rw-r--r--services/user/rename.go41
-rw-r--r--services/user/user.go16
-rw-r--r--templates/swagger/v1_json.tmpl56
12 files changed, 206 insertions, 44 deletions
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 6a1dc31556..a15ebec0b5 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -660,10 +660,10 @@ func GetPullRequestByIssueID(ctx context.Context, issueID int64) (*PullRequest,
// GetAllUnmergedAgitPullRequestByPoster get all unmerged agit flow pull request
// By poster id.
-func GetAllUnmergedAgitPullRequestByPoster(uid int64) ([]*PullRequest, error) {
+func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*PullRequest, error) {
pulls := make([]*PullRequest, 0, 10)
- err := db.GetEngine(db.DefaultContext).
+ err := db.GetEngine(ctx).
Where("has_merged=? AND flow = ? AND issue.is_closed=? AND issue.poster_id=?",
false, PullRequestFlowAGit, false, uid).
Join("INNER", "issue", "issue.id=pull_request.issue_id").
diff --git a/models/user/user.go b/models/user/user.go
index 454779b9ea..82c2d3b6cd 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -742,13 +742,13 @@ func VerifyUserActiveCode(code string) (user *User) {
}
// ChangeUserName changes all corresponding setting from old user name to new one.
-func ChangeUserName(u *User, newUserName string) (err error) {
+func ChangeUserName(ctx context.Context, u *User, newUserName string) (err error) {
oldUserName := u.Name
if err = IsUsableUsername(newUserName); err != nil {
return err
}
- ctx, committer, err := db.TxContext(db.DefaultContext)
+ ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
diff --git a/modules/structs/user.go b/modules/structs/user.go
index c5e96f3356..f68b92ac06 100644
--- a/modules/structs/user.go
+++ b/modules/structs/user.go
@@ -93,3 +93,12 @@ type UserSettingsOptions struct {
HideEmail *bool `json:"hide_email"`
HideActivity *bool `json:"hide_activity"`
}
+
+// RenameUserOption options when renaming a user
+type RenameUserOption struct {
+ // New username for this user. This name cannot be in use yet by any other user.
+ //
+ // required: true
+ // unique: true
+ NewName string `json:"new_username" binding:"Required"`
+}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 4192d8654d..369d13943a 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -461,3 +461,61 @@ func GetAllUsers(ctx *context.APIContext) {
ctx.SetTotalCountHeader(maxResults)
ctx.JSON(http.StatusOK, &results)
}
+
+// RenameUser api for renaming a user
+func RenameUser(ctx *context.APIContext) {
+ // swagger:operation POST /admin/users/{username}/rename admin adminRenameUser
+ // ---
+ // summary: Rename a user
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: username
+ // in: path
+ // description: existing username of user
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // required: true
+ // schema:
+ // "$ref": "#/definitions/RenameUserOption"
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ if ctx.ContextUser.IsOrganization() {
+ ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
+ return
+ }
+
+ newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
+
+ if strings.EqualFold(newName, ctx.ContextUser.Name) {
+ // Noop as username is not changed
+ ctx.Status(http.StatusNoContent)
+ return
+ }
+
+ // Check if user name has been changed
+ if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
+ switch {
+ case user_model.IsErrUserAlreadyExist(err):
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
+ case db.IsErrNameReserved(err):
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_reserved", newName))
+ case db.IsErrNamePatternNotAllowed(err):
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_pattern_not_allowed", newName))
+ case db.IsErrNameCharsNotAllowed(err):
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("user.form.name_chars_not_allowed", newName))
+ default:
+ ctx.ServerError("ChangeUserName", err)
+ }
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 735939a551..7001dc72ac 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1257,6 +1257,7 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Get("/orgs", org.ListUserOrgs)
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
+ m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
}, context_service.UserAssignmentAPI())
})
m.Group("/unadopted", func() {
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index 979b184075..0c8d3d353f 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -49,6 +49,9 @@ type swaggerParameterBodies struct {
CreateKeyOption api.CreateKeyOption
// in:body
+ RenameUserOption api.RenameUserOption
+
+ // in:body
CreateLabelOption api.CreateLabelOption
// in:body
EditLabelOption api.EditLabelOption
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index b57ebfbcda..654e9000fa 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -79,7 +79,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Data["OrgName"] = true
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
return
- } else if err = user_model.ChangeUserName(org.AsUser(), form.Name); err != nil {
+ } else if err = user_model.ChangeUserName(ctx, org.AsUser(), form.Name); err != nil {
switch {
case db.IsErrNameReserved(err):
ctx.Data["OrgName"] = true
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index f0f053a514..f500be7632 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -27,9 +27,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
- "code.gitea.io/gitea/services/agit"
"code.gitea.io/gitea/services/forms"
- container_service "code.gitea.io/gitea/services/packages/container"
user_service "code.gitea.io/gitea/services/user"
)
@@ -57,45 +55,25 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
return fmt.Errorf(ctx.Tr("form.username_change_not_local_user"))
}
- // Check if user name has been changed
- if user.LowerName != strings.ToLower(newName) {
- if err := user_model.ChangeUserName(user, newName); err != nil {
- switch {
- case user_model.IsErrUserAlreadyExist(err):
- ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
- case user_model.IsErrEmailAlreadyUsed(err):
- ctx.Flash.Error(ctx.Tr("form.email_been_used"))
- case db.IsErrNameReserved(err):
- ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
- case db.IsErrNamePatternNotAllowed(err):
- ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName))
- case db.IsErrNameCharsNotAllowed(err):
- ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName))
- default:
- ctx.ServerError("ChangeUserName", err)
- }
- return err
- }
- } else {
- if err := repo_model.UpdateRepositoryOwnerNames(user.ID, newName); err != nil {
- ctx.ServerError("UpdateRepository", err)
- return err
+ // rename user
+ if err := user_service.RenameUser(ctx, user, newName); err != nil {
+ switch {
+ case user_model.IsErrUserAlreadyExist(err):
+ ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
+ case user_model.IsErrEmailAlreadyUsed(err):
+ ctx.Flash.Error(ctx.Tr("form.email_been_used"))
+ case db.IsErrNameReserved(err):
+ ctx.Flash.Error(ctx.Tr("user.form.name_reserved", newName))
+ case db.IsErrNamePatternNotAllowed(err):
+ ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName))
+ case db.IsErrNameCharsNotAllowed(err):
+ ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName))
+ default:
+ ctx.ServerError("ChangeUserName", err)
}
- }
-
- // update all agit flow pull request header
- err := agit.UserNameChanged(user, newName)
- if err != nil {
- ctx.ServerError("agit.UserNameChanged", err)
- return err
- }
-
- if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
- ctx.ServerError("UpdateRepositoryNames", err)
return err
}
- log.Trace("User name changed: %s -> %s", user.Name, newName)
return nil
}
diff --git a/services/agit/agit.go b/services/agit/agit.go
index b61cb6f3f5..32fc3cba4a 100644
--- a/services/agit/agit.go
+++ b/services/agit/agit.go
@@ -226,8 +226,8 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
}
// UserNameChanged handle user name change for agit flow pull
-func UserNameChanged(user *user_model.User, newName string) error {
- pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(user.ID)
+func UserNameChanged(ctx context.Context, user *user_model.User, newName string) error {
+ pulls, err := issues_model.GetAllUnmergedAgitPullRequestByPoster(ctx, user.ID)
if err != nil {
return err
}
diff --git a/services/user/rename.go b/services/user/rename.go
new file mode 100644
index 0000000000..af195d7d76
--- /dev/null
+++ b/services/user/rename.go
@@ -0,0 +1,41 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package user
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/agit"
+ container_service "code.gitea.io/gitea/services/packages/container"
+)
+
+func renameUser(ctx context.Context, u *user_model.User, newUserName string) error {
+ if u.IsOrganization() {
+ return fmt.Errorf("cannot rename organization")
+ }
+
+ if err := user_model.ChangeUserName(ctx, u, newUserName); err != nil {
+ return err
+ }
+
+ if err := agit.UserNameChanged(ctx, u, newUserName); err != nil {
+ return err
+ }
+ if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil {
+ return err
+ }
+
+ u.Name = newUserName
+ u.LowerName = strings.ToLower(newUserName)
+ if err := user_model.UpdateUser(ctx, u, false); err != nil {
+ return err
+ }
+
+ log.Trace("User name changed: %s -> %s", u.Name, newUserName)
+ return nil
+}
diff --git a/services/user/user.go b/services/user/user.go
index f0b8fe1c31..d52a2f404b 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -27,6 +27,22 @@ import (
"code.gitea.io/gitea/services/packages"
)
+// RenameUser renames a user
+func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+ if err := renameUser(ctx, u, newUserName); err != nil {
+ return err
+ }
+ if err := committer.Commit(); err != nil {
+ return err
+ }
+ return err
+}
+
// DeleteUser completely and permanently deletes everything of a user,
// but issues/comments/pulls will be kept and shown as someone has been deleted,
// unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS.
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index c304a7a497..7dc7f563c2 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -679,6 +679,46 @@
}
}
},
+ "/admin/users/{username}/rename": {
+ "post": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "admin"
+ ],
+ "summary": "Rename a user",
+ "operationId": "adminRenameUser",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "existing username of user",
+ "name": "username",
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/RenameUserOption"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "$ref": "#/responses/empty"
+ },
+ "403": {
+ "$ref": "#/responses/forbidden"
+ },
+ "422": {
+ "$ref": "#/responses/validationError"
+ }
+ }
+ }
+ },
"/admin/users/{username}/repos": {
"post": {
"consumes": [
@@ -19105,6 +19145,22 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "RenameUserOption": {
+ "description": "RenameUserOption options when renaming a user",
+ "type": "object",
+ "required": [
+ "new_username"
+ ],
+ "properties": {
+ "new_username": {
+ "description": "New username for this user. This name cannot be in use yet by any other user.",
+ "type": "string",
+ "uniqueItems": true,
+ "x-go-name": "NewName"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"RepoCollaboratorPermission": {
"description": "RepoCollaboratorPermission to get repository permission for a collaborator",
"type": "object",