Skip to content

Commit c59a057

Browse files
authored
Refactor rename user and rename organization (#24052)
This PR is a refactor at the beginning. And now it did 4 things. - [x] Move renaming organizaiton and user logics into services layer and merged as one function - [x] Support rename a user capitalization only. For example, rename the user from `Lunny` to `lunny`. We just need to change one table `user` and others should not be touched. - [x] Before this PR, some renaming were missed like `agit` - [x] Fix bug the API reutrned from `http.StatusNoContent` to `http.StatusOK`
1 parent 64f6a5d commit c59a057

File tree

12 files changed

+267
-188
lines changed

12 files changed

+267
-188
lines changed

models/repo/repo.go

+8
Original file line numberDiff line numberDiff line change
@@ -832,3 +832,11 @@ func FixNullArchivedRepository(ctx context.Context) (int64, error) {
832832
IsArchived: false,
833833
})
834834
}
835+
836+
// UpdateRepositoryOwnerName updates the owner name of all repositories owned by the user
837+
func UpdateRepositoryOwnerName(ctx context.Context, oldUserName, newUserName string) error {
838+
if _, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
839+
return fmt.Errorf("change repo owner name: %w", err)
840+
}
841+
return nil
842+
}

models/user/error.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ import (
99
"code.gitea.io/gitea/modules/util"
1010
)
1111

12-
// ____ ___
13-
// | | \______ ___________
14-
// | | / ___// __ \_ __ \
15-
// | | /\___ \\ ___/| | \/
16-
// |______//____ >\___ >__|
17-
// \/ \/
18-
1912
// ErrUserAlreadyExist represents a "user already exists" error.
2013
type ErrUserAlreadyExist struct {
2114
Name string
@@ -99,3 +92,34 @@ func (err ErrUserInactive) Error() string {
9992
func (err ErrUserInactive) Unwrap() error {
10093
return util.ErrPermissionDenied
10194
}
95+
96+
// ErrUserIsNotLocal represents a "ErrUserIsNotLocal" kind of error.
97+
type ErrUserIsNotLocal struct {
98+
UID int64
99+
Name string
100+
}
101+
102+
func (err ErrUserIsNotLocal) Error() string {
103+
return fmt.Sprintf("user is not local type [uid: %d, name: %s]", err.UID, err.Name)
104+
}
105+
106+
// IsErrUserIsNotLocal
107+
func IsErrUserIsNotLocal(err error) bool {
108+
_, ok := err.(ErrUserIsNotLocal)
109+
return ok
110+
}
111+
112+
type ErrUsernameNotChanged struct {
113+
UID int64
114+
Name string
115+
}
116+
117+
func (err ErrUsernameNotChanged) Error() string {
118+
return fmt.Sprintf("username hasn't been changed[uid: %d, name: %s]", err.UID, err.Name)
119+
}
120+
121+
// IsErrUsernameNotChanged
122+
func IsErrUsernameNotChanged(err error) bool {
123+
_, ok := err.(ErrUsernameNotChanged)
124+
return ok
125+
}

models/user/user.go

-45
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"encoding/hex"
1010
"fmt"
1111
"net/url"
12-
"os"
1312
"path/filepath"
1413
"strings"
1514
"time"
@@ -756,50 +755,6 @@ func VerifyUserActiveCode(code string) (user *User) {
756755
return nil
757756
}
758757

759-
// ChangeUserName changes all corresponding setting from old user name to new one.
760-
func ChangeUserName(ctx context.Context, u *User, newUserName string) (err error) {
761-
oldUserName := u.Name
762-
if err = IsUsableUsername(newUserName); err != nil {
763-
return err
764-
}
765-
766-
ctx, committer, err := db.TxContext(ctx)
767-
if err != nil {
768-
return err
769-
}
770-
defer committer.Close()
771-
772-
isExist, err := IsUserExist(ctx, 0, newUserName)
773-
if err != nil {
774-
return err
775-
} else if isExist {
776-
return ErrUserAlreadyExist{newUserName}
777-
}
778-
779-
if _, err = db.GetEngine(ctx).Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil {
780-
return fmt.Errorf("Change repo owner name: %w", err)
781-
}
782-
783-
// Do not fail if directory does not exist
784-
if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) {
785-
return fmt.Errorf("Rename user directory: %w", err)
786-
}
787-
788-
if err = NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil {
789-
return err
790-
}
791-
792-
if err = committer.Commit(); err != nil {
793-
if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) {
794-
log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2)
795-
return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2)
796-
}
797-
return err
798-
}
799-
800-
return nil
801-
}
802-
803758
// checkDupEmail checks whether there are the same email with the user
804759
func checkDupEmail(ctx context.Context, u *User) error {
805760
u.Email = strings.ToLower(u.Email)

options/locale/locale_en-US.ini

+1
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ lang_select_error = Select a language from the list.
520520

521521
username_been_taken = The username is already taken.
522522
username_change_not_local_user = Non-local users are not allowed to change their username.
523+
username_has_not_been_changed = Username has not been changed
523524
repo_name_been_taken = The repository name is already used.
524525
repository_force_private = Force Private is enabled: private repositories cannot be made public.
525526
repository_files_already_exist = Files already exist for this repository. Contact the system administrator.

routers/api/v1/admin/user.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -502,17 +502,15 @@ func RenameUser(ctx *context.APIContext) {
502502
return
503503
}
504504

505+
oldName := ctx.ContextUser.Name
505506
newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
506507

507-
if strings.EqualFold(newName, ctx.ContextUser.Name) {
508-
// Noop as username is not changed
509-
ctx.Status(http.StatusNoContent)
510-
return
511-
}
512-
513508
// Check if user name has been changed
514509
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
515510
switch {
511+
case user_model.IsErrUsernameNotChanged(err):
512+
// Noop as username is not changed
513+
ctx.Status(http.StatusNoContent)
516514
case user_model.IsErrUserAlreadyExist(err):
517515
ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken"))
518516
case db.IsErrNameReserved(err):
@@ -526,5 +524,7 @@ func RenameUser(ctx *context.APIContext) {
526524
}
527525
return
528526
}
529-
ctx.Status(http.StatusNoContent)
527+
528+
log.Trace("User name changed: %s -> %s", oldName, newName)
529+
ctx.Status(http.StatusOK)
530530
}

routers/web/org/setting.go

+15-24
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/web"
2323
user_setting "code.gitea.io/gitea/routers/web/user/setting"
2424
"code.gitea.io/gitea/services/forms"
25-
"code.gitea.io/gitea/services/org"
26-
container_service "code.gitea.io/gitea/services/packages/container"
25+
org_service "code.gitea.io/gitea/services/org"
2726
repo_service "code.gitea.io/gitea/services/repository"
2827
user_service "code.gitea.io/gitea/services/user"
2928
)
@@ -67,31 +66,23 @@ func SettingsPost(ctx *context.Context) {
6766
nameChanged := org.Name != form.Name
6867

6968
// Check if organization name has been changed.
70-
if org.LowerName != strings.ToLower(form.Name) {
71-
isExist, err := user_model.IsUserExist(ctx, org.ID, form.Name)
72-
if err != nil {
73-
ctx.ServerError("IsUserExist", err)
74-
return
75-
} else if isExist {
69+
if nameChanged {
70+
err := org_service.RenameOrganization(ctx, org, form.Name)
71+
switch {
72+
case user_model.IsErrUserAlreadyExist(err):
7673
ctx.Data["OrgName"] = true
7774
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
7875
return
79-
} else if err = user_model.ChangeUserName(ctx, org.AsUser(), form.Name); err != nil {
80-
switch {
81-
case db.IsErrNameReserved(err):
82-
ctx.Data["OrgName"] = true
83-
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
84-
case db.IsErrNamePatternNotAllowed(err):
85-
ctx.Data["OrgName"] = true
86-
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
87-
default:
88-
ctx.ServerError("ChangeUserName", err)
89-
}
76+
case db.IsErrNameReserved(err):
77+
ctx.Data["OrgName"] = true
78+
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
9079
return
91-
}
92-
93-
if err := container_service.UpdateRepositoryNames(ctx, org.AsUser(), form.Name); err != nil {
94-
ctx.ServerError("UpdateRepositoryNames", err)
80+
case db.IsErrNamePatternNotAllowed(err):
81+
ctx.Data["OrgName"] = true
82+
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
83+
return
84+
case err != nil:
85+
ctx.ServerError("org_service.RenameOrganization", err)
9586
return
9687
}
9788

@@ -186,7 +177,7 @@ func SettingsDelete(ctx *context.Context) {
186177
return
187178
}
188179

189-
if err := org.DeleteOrganization(ctx.Org.Organization); err != nil {
180+
if err := org_service.DeleteOrganization(ctx.Org.Organization); err != nil {
190181
if models.IsErrUserOwnRepos(err) {
191182
ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
192183
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")

routers/web/user/setting/profile.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ func Profile(ctx *context.Context) {
4949

5050
// HandleUsernameChange handle username changes from user settings and admin interface
5151
func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName string) error {
52-
// Non-local users are not allowed to change their username.
53-
if !user.IsLocal() {
54-
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
55-
return fmt.Errorf(ctx.Tr("form.username_change_not_local_user"))
56-
}
57-
52+
oldName := user.Name
5853
// rename user
5954
if err := user_service.RenameUser(ctx, user, newName); err != nil {
6055
switch {
56+
// Noop as username is not changed
57+
case user_model.IsErrUsernameNotChanged(err):
58+
ctx.Flash.Error(ctx.Tr("form.username_has_not_been_changed"))
59+
// Non-local users are not allowed to change their username.
60+
case user_model.IsErrUserIsNotLocal(err):
61+
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
6162
case user_model.IsErrUserAlreadyExist(err):
6263
ctx.Flash.Error(ctx.Tr("form.username_been_taken"))
6364
case user_model.IsErrEmailAlreadyUsed(err):
@@ -73,7 +74,7 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s
7374
}
7475
return err
7576
}
76-
77+
log.Trace("User name changed: %s -> %s", oldName, newName)
7778
return nil
7879
}
7980

services/org/org.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44
package org
55

66
import (
7+
"context"
78
"fmt"
89

910
"code.gitea.io/gitea/models"
1011
"code.gitea.io/gitea/models/db"
11-
"code.gitea.io/gitea/models/organization"
12+
org_model "code.gitea.io/gitea/models/organization"
1213
packages_model "code.gitea.io/gitea/models/packages"
1314
repo_model "code.gitea.io/gitea/models/repo"
1415
user_model "code.gitea.io/gitea/models/user"
1516
"code.gitea.io/gitea/modules/storage"
1617
"code.gitea.io/gitea/modules/util"
18+
user_service "code.gitea.io/gitea/services/user"
1719
)
1820

1921
// DeleteOrganization completely and permanently deletes everything of organization.
20-
func DeleteOrganization(org *organization.Organization) error {
22+
func DeleteOrganization(org *org_model.Organization) error {
2123
ctx, commiter, err := db.TxContext(db.DefaultContext)
2224
if err != nil {
2325
return err
@@ -39,7 +41,7 @@ func DeleteOrganization(org *organization.Organization) error {
3941
return models.ErrUserOwnPackages{UID: org.ID}
4042
}
4143

42-
if err := organization.DeleteOrganization(ctx, org); err != nil {
44+
if err := org_model.DeleteOrganization(ctx, org); err != nil {
4345
return fmt.Errorf("DeleteOrganization: %w", err)
4446
}
4547

@@ -53,15 +55,20 @@ func DeleteOrganization(org *organization.Organization) error {
5355
path := user_model.UserPath(org.Name)
5456

5557
if err := util.RemoveAll(path); err != nil {
56-
return fmt.Errorf("Failed to RemoveAll %s: %w", path, err)
58+
return fmt.Errorf("failed to RemoveAll %s: %w", path, err)
5759
}
5860

5961
if len(org.Avatar) > 0 {
6062
avatarPath := org.CustomAvatarRelativePath()
6163
if err := storage.Avatars.Delete(avatarPath); err != nil {
62-
return fmt.Errorf("Failed to remove %s: %w", avatarPath, err)
64+
return fmt.Errorf("failed to remove %s: %w", avatarPath, err)
6365
}
6466
}
6567

6668
return nil
6769
}
70+
71+
// RenameOrganization renames an organization.
72+
func RenameOrganization(ctx context.Context, org *org_model.Organization, newName string) error {
73+
return user_service.RenameUser(ctx, org.AsUser(), newName)
74+
}

services/user/avatar.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package user
5+
6+
import (
7+
"fmt"
8+
"io"
9+
10+
"code.gitea.io/gitea/models/db"
11+
user_model "code.gitea.io/gitea/models/user"
12+
"code.gitea.io/gitea/modules/avatar"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/storage"
15+
)
16+
17+
// UploadAvatar saves custom avatar for user.
18+
func UploadAvatar(u *user_model.User, data []byte) error {
19+
avatarData, err := avatar.ProcessAvatarImage(data)
20+
if err != nil {
21+
return err
22+
}
23+
24+
ctx, committer, err := db.TxContext(db.DefaultContext)
25+
if err != nil {
26+
return err
27+
}
28+
defer committer.Close()
29+
30+
u.UseCustomAvatar = true
31+
u.Avatar = avatar.HashAvatar(u.ID, data)
32+
if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil {
33+
return fmt.Errorf("updateUser: %w", err)
34+
}
35+
36+
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
37+
_, err := w.Write(avatarData)
38+
return err
39+
}); err != nil {
40+
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
41+
}
42+
43+
return committer.Commit()
44+
}
45+
46+
// DeleteAvatar deletes the user's custom avatar.
47+
func DeleteAvatar(u *user_model.User) error {
48+
aPath := u.CustomAvatarRelativePath()
49+
log.Trace("DeleteAvatar[%d]: %s", u.ID, aPath)
50+
if len(u.Avatar) > 0 {
51+
if err := storage.Avatars.Delete(aPath); err != nil {
52+
return fmt.Errorf("Failed to remove %s: %w", aPath, err)
53+
}
54+
}
55+
56+
u.UseCustomAvatar = false
57+
u.Avatar = ""
58+
if _, err := db.GetEngine(db.DefaultContext).ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil {
59+
return fmt.Errorf("UpdateUser: %w", err)
60+
}
61+
return nil
62+
}

0 commit comments

Comments
 (0)