mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-09-10 04:02:07 +02:00
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>
This commit is contained in:
parent
aac07d010f
commit
03591f0f95
12 changed files with 206 additions and 44 deletions
|
@ -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").
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -48,6 +48,9 @@ type swaggerParameterBodies struct {
|
|||
// in:body
|
||||
CreateKeyOption api.CreateKeyOption
|
||||
|
||||
// in:body
|
||||
RenameUserOption api.RenameUserOption
|
||||
|
||||
// in:body
|
||||
CreateLabelOption api.CreateLabelOption
|
||||
// in:body
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,9 +55,8 @@ 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 {
|
||||
// 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"))
|
||||
|
@ -76,26 +73,7 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
|
|||
}
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := repo_model.UpdateRepositoryOwnerNames(user.ID, newName); err != nil {
|
||||
ctx.ServerError("UpdateRepository", err)
|
||||
return 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
41
services/user/rename.go
Normal file
41
services/user/rename.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue