Skip to content

Rework delete org and rename org UI #34762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2811,6 +2811,7 @@ team_permission_desc = Permission
team_unit_desc = Allow Access to Repository Sections
team_unit_disabled = (Disabled)

form.name_been_taken = The organisation name "%s" has already been taken.
form.name_reserved = The organization name "%s" is reserved.
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
form.create_org_not_allowed = You are not allowed to create an organization.
Expand All @@ -2832,15 +2833,28 @@ settings.visibility.private_shortname = Private

settings.update_settings = Update Settings
settings.update_setting_success = Organization settings have been updated.
settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name.
settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed.

settings.rename = Rename Organization
settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name.
settings.rename_success = Organization %[1]s have been renamed to %[2]s successfully.
settings.rename_no_change = Organization name is no change.
settings.rename_new_org_name = New Organization Name
settings.rename_failed = Rename Organization failed because of internal error
settings.rename_notices_1 = This operation <strong>CANNOT</strong> be undone.
settings.rename_notices_2 = The old name will redirect until it is claimed.

settings.update_avatar_success = The organization's avatar has been updated.
settings.delete = Delete Organization
settings.delete_account = Delete This Organization
settings.delete_prompt = The organization will be permanently removed. This <strong>CANNOT</strong> be undone!
settings.name_confirm = Enter the organization name as confirmation:
settings.delete_notices_1 = This operation <strong>CANNOT</strong> be undone.
settings.delete_notices_2 = This operation will permanently delete all the <strong>repositories</strong> of <strong>%s</strong> including code, issues, comments, wiki data and collaborator settings.
settings.delete_notices_3 = This operation will permanently delete all the <strong>packages</strong> of <strong>%s</strong>.
settings.delete_notices_4 = This operation will permanently delete all the <strong>projects</strong> of <strong>%s</strong>.
settings.confirm_delete_account = Confirm Deletion
settings.delete_org_title = Delete Organization
settings.delete_org_desc = This organization will be deleted permanently. Continue?
settings.delete_failed = Delete Organization failed because of internal error
settings.delete_successful = Organization <b>%s</b> has been deleted successfully.
settings.hooks_desc = Add webhooks which will be triggered for <strong>all repositories</strong> under this organization.

settings.labels_desc = Add labels which can be used on issues for <strong>all repositories</strong> under this organization.
Expand Down
105 changes: 53 additions & 52 deletions routers/web/org/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
user_setting "code.gitea.io/gitea/routers/web/user/setting"
Expand All @@ -31,8 +32,6 @@ import (
const (
// tplSettingsOptions template path for render settings
tplSettingsOptions templates.TplName = "org/settings/options"
// tplSettingsDelete template path for render delete repository
tplSettingsDelete templates.TplName = "org/settings/delete"
// tplSettingsHooks template path for render hook settings
tplSettingsHooks templates.TplName = "org/settings/hooks"
// tplSettingsLabels template path for render labels settings
Expand Down Expand Up @@ -71,26 +70,6 @@ func SettingsPost(ctx *context.Context) {

org := ctx.Org.Organization

if org.Name != form.Name {
if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil {
if user_model.IsErrUserAlreadyExist(err) {
ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
} else if db.IsErrNameReserved(err) {
ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
} else if db.IsErrNamePatternNotAllowed(err) {
ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
} else {
ctx.ServerError("RenameUser", err)
}
return
}

ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name)
}

if form.Email != "" {
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
ctx.Data["Err_Email"] = true
Expand Down Expand Up @@ -163,42 +142,27 @@ func SettingsDeleteAvatar(ctx *context.Context) {
ctx.JSONRedirect(ctx.Org.OrgLink + "/settings")
}

// SettingsDelete response for deleting an organization
func SettingsDelete(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsDelete"] = true

if ctx.Req.Method == http.MethodPost {
if ctx.Org.Organization.Name != ctx.FormString("org_name") {
ctx.Data["Err_OrgName"] = true
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil)
return
}

if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
if repo_model.IsErrUserOwnRepos(err) {
ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
} else if packages_model.IsErrUserOwnPackages(err) {
ctx.Flash.Error(ctx.Tr("form.org_still_own_packages"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
} else {
ctx.ServerError("DeleteOrganization", err)
}
} else {
log.Trace("Organization deleted: %s", ctx.Org.Organization.Name)
ctx.Redirect(setting.AppSubURL + "/")
}
// SettingsDeleteOrgPost response for deleting an organization
func SettingsDeleteOrgPost(ctx *context.Context) {
if ctx.Org.Organization.Name != ctx.FormString("org_name") {
ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
return
}

if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err)
if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false /* no purge */); err != nil {
if repo_model.IsErrUserOwnRepos(err) {
ctx.JSONError(ctx.Tr("form.org_still_own_repo"))
} else if packages_model.IsErrUserOwnPackages(err) {
ctx.JSONError(ctx.Tr("form.org_still_own_packages"))
} else {
log.Error("DeleteOrganization: %v", err)
ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.delete_failed"))))
}
return
}

ctx.HTML(http.StatusOK, tplSettingsDelete)
ctx.Flash.Success(ctx.Tr("org.settings.delete_successful", ctx.Org.Organization.Name))
ctx.JSONRedirect(setting.AppSubURL + "/")
}

// Webhooks render webhook list page
Expand Down Expand Up @@ -250,3 +214,40 @@ func Labels(ctx *context.Context) {

ctx.HTML(http.StatusOK, tplSettingsLabels)
}

// SettingsRenamePost response for renaming organization
func SettingsRenamePost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RenameOrgForm)
if ctx.HasError() {
ctx.JSONError(ctx.GetErrMsg())
return
}

oldOrgName, newOrgName := ctx.Org.Organization.Name, form.NewOrgName

if form.OrgName != oldOrgName {
ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
return
}
if newOrgName == oldOrgName {
ctx.JSONError(ctx.Tr("org.settings.rename_no_change"))
return
}

if err := user_service.RenameUser(ctx, ctx.Org.Organization.AsUser(), newOrgName); err != nil {
if user_model.IsErrUserAlreadyExist(err) {
ctx.JSONError(ctx.Tr("org.form.name_been_taken", newOrgName))
} else if db.IsErrNameReserved(err) {
ctx.JSONError(ctx.Tr("org.form.name_reserved", newOrgName))
} else if db.IsErrNamePatternNotAllowed(err) {
ctx.JSONError(ctx.Tr("org.form.name_pattern_not_allowed", newOrgName))
} else {
log.Error("RenameOrganization: %v", err)
ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed"))))
}
return
}

ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, newOrgName))
ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(newOrgName) + "/settings")
}
3 changes: 2 additions & 1 deletion routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,8 @@ func registerWebRoutes(m *web.Router) {
addSettingsVariablesRoutes()
}, actions.MustEnableActions)

m.Methods("GET,POST", "/delete", org.SettingsDelete)
m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRenamePost)
m.Post("/delete", org.SettingsDeleteOrgPost)

m.Group("/packages", func() {
m.Get("", org.Packages)
Expand Down
6 changes: 5 additions & 1 deletion services/forms/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding

// UpdateOrgSettingForm form for updating organization settings
type UpdateOrgSettingForm struct {
Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
FullName string `binding:"MaxSize(100)"`
Email string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(255)"`
Expand All @@ -53,6 +52,11 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}

type RenameOrgForm struct {
OrgName string `binding:"Required"`
NewOrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
}

// ___________
// \__ ___/___ _____ _____
// | |_/ __ \\__ \ / \
Expand Down
35 changes: 0 additions & 35 deletions templates/org/settings/delete.tmpl

This file was deleted.

3 changes: 0 additions & 3 deletions templates/org/settings/navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,5 @@
</div>
</details>
{{end}}
<a class="{{if .PageIsSettingsDelete}}active {{end}}item" href="{{.OrgLink}}/settings/delete">
{{ctx.Locale.Tr "org.settings.delete"}}
</a>
</div>
</div>
Loading