From f68e44daed891a989623f9bde1f5801991f4701a Mon Sep 17 00:00:00 2001 From: Henrique Pimentel Date: Mon, 27 May 2024 12:05:22 +0100 Subject: [PATCH] Implemented User Badge Management Interface (#29798) Co-authored-by: Diogo Vicente --- models/user/badge.go | 22 +++++++--- models/user/error.go | 2 +- options/locale/locale_en-US.ini | 8 ++++ routers/web/admin/badges.go | 74 ++++++++++++++++++++++++++++++-- routers/web/explore/badge.go | 5 +-- routers/web/web.go | 2 + templates/admin/badge/list.tmpl | 4 +- templates/admin/badge/users.tmpl | 53 +++++++++++++++++++++++ templates/admin/badge/view.tmpl | 4 +- 9 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 templates/admin/badge/users.tmpl diff --git a/models/user/badge.go b/models/user/badge.go index aace0655ce..e143d3de8d 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -125,6 +125,13 @@ func DeleteUserBadgeRecord(ctx context.Context, badge *Badge) error { // AddUserBadge adds a badge to a user. func AddUserBadge(ctx context.Context, u *User, badge *Badge) error { + isExist, err := IsBadgeUserExist(ctx, u.ID, badge.ID) + if err != nil { + return err + } else if isExist { + return ErrBadgeAlreadyExist{} + } + return AddUserBadges(ctx, u, []*Badge{badge}) } @@ -133,11 +140,11 @@ func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error { return db.WithTx(ctx, func(ctx context.Context) error { for _, badge := range badges { // hydrate badge and check if it exists - has, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Get(badge) + has, err := db.GetEngine(ctx).Where("id=?", badge.ID).Get(badge) if err != nil { return err } else if !has { - return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug) + return ErrBadgeNotExist{ID: badge.ID} } if err := db.Insert(ctx, &UserBadge{ BadgeID: badge.ID, @@ -159,10 +166,7 @@ func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error { func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error { return db.WithTx(ctx, func(ctx context.Context) error { for _, badge := range badges { - if _, err := db.GetEngine(ctx). - Join("INNER", "badge", "badge.id = `user_badge`.badge_id"). - Where("`user_badge`.user_id=? AND `badge`.slug=?", u.ID, badge.Slug). - Delete(&UserBadge{}); err != nil { + if _, err := db.GetEngine(ctx).Delete(&UserBadge{BadgeID: badge.ID, UserID: u.ID}); err != nil { return err } } @@ -192,6 +196,12 @@ func IsBadgeExist(ctx context.Context, uid int64, slug string) (bool, error) { Get(&Badge{Slug: strings.ToLower(slug)}) } +// IsBadgeUserExist checks if given badge id, uid exist, +func IsBadgeUserExist(ctx context.Context, uid, bid int64) (bool, error) { + return db.GetEngine(ctx). + Get(&UserBadge{UserID: uid, BadgeID: bid}) +} + // SearchBadgeOptions represents the options when fdin badges type SearchBadgeOptions struct { db.ListOptions diff --git a/models/user/error.go b/models/user/error.go index f8e8855f1a..1c65ea7f16 100644 --- a/models/user/error.go +++ b/models/user/error.go @@ -141,7 +141,7 @@ func IsErrBadgeNotExist(err error) bool { } func (err ErrBadgeNotExist) Error() string { - return fmt.Sprintf("badge does not exist [slug: %s | id: %i]", err.Slug, err.ID) + return fmt.Sprintf("badge does not exist [slug: %s | id: %d]", err.Slug, err.ID) } // Unwrap unwraps this error as a ErrNotExist error diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 17f3545167..f8b171a1e6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2988,6 +2988,14 @@ badges.edit_badge = Edit Badge badges.update_badge = Update Badge badges.delete_badge = Delete Badge badges.delete_badge_desc = Are you sure you want to permanently delete this badge? +badges.users_with_badge = Users with Badge (%d) +badges.add_user = Add User +badges.remove_user = Remove User +badges.delete_user_desc = Are you sure you want to remove this badge from the user? +badges.not_found = Badge not found! +badges.user_add_success = User has been added to the badge. +badges.user_remove_success = User has been removed from the badge. +badges.manage_users = Manage Users orgs.org_manage_panel = Organization Management diff --git a/routers/web/admin/badges.go b/routers/web/admin/badges.go index e471160559..c96c43761f 100644 --- a/routers/web/admin/badges.go +++ b/routers/web/admin/badges.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -22,10 +23,11 @@ import ( ) const ( - tplBadges base.TplName = "admin/badge/list" - tplBadgeNew base.TplName = "admin/badge/new" - tplBadgeView base.TplName = "admin/badge/view" - tplBadgeEdit base.TplName = "admin/badge/edit" + tplBadges base.TplName = "admin/badge/list" + tplBadgeNew base.TplName = "admin/badge/new" + tplBadgeView base.TplName = "admin/badge/view" + tplBadgeEdit base.TplName = "admin/badge/edit" + tplBadgeUsers base.TplName = "admin/badge/users" ) // BadgeSearchDefaultAdminSort is the default sort type for admin view @@ -213,3 +215,67 @@ func DeleteBadge(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success")) ctx.Redirect(setting.AppSubURL + "/admin/badges") } + +func BadgeUsers(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.ParamsInt64(":badgeid")) + ctx.Data["PageIsAdminBadges"] = true + + users, _, err := user_model.GetBadgeUsers(ctx, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")}) + if err != nil { + ctx.ServerError("GetBadgeUsers", err) + return + } + + ctx.Data["Users"] = users + + ctx.HTML(http.StatusOK, tplBadgeUsers) +} + +// BadgeUsersPost response for actions for user badges +func BadgeUsersPost(ctx *context.Context) { + name := strings.ToLower(ctx.FormString("user")) + + u, err := user_model.GetUserByName(ctx, name) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Flash.Error(ctx.Tr("form.user_not_exist")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) + } else { + ctx.ServerError("GetUserByName", err) + } + return + } + + if err = user_model.AddUserBadge(ctx, u, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")}); err != nil { + if user_model.IsErrBadgeNotExist(err) { + ctx.Flash.Error(ctx.Tr("admin.badges.not_found")) + } else { + ctx.ServerError("AddUserBadge", err) + } + return + } + + ctx.Flash.Success(ctx.Tr("admin.badges.user_add_success")) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) +} + +// DeleteBadgeUser delete a badge from a user +func DeleteBadgeUser(ctx *context.Context) { + if user, err := user_model.GetUserByID(ctx, ctx.FormInt64("id")); err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Flash.Error(ctx.Tr("form.user_not_exist")) + } else { + ctx.ServerError("GetUserByName", err) + return + } + } else { + if err := user_model.RemoveUserBadge(ctx, user, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")}); err == nil { + ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success")) + } else { + ctx.Flash.Error("DeleteUser: " + err.Error()) + return + } + } + + ctx.JSONRedirect(setting.AppSubURL + "/admin/badges/" + ctx.Params(":badgeid") + "/users") +} diff --git a/routers/web/explore/badge.go b/routers/web/explore/badge.go index cbc3fb1ac4..fd16f38015 100644 --- a/routers/web/explore/badge.go +++ b/routers/web/explore/badge.go @@ -56,9 +56,8 @@ func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions orderBy = "`badge`.slug ASC" default: // in case the sortType is not valid, we set it to recent update - sortOrder = "alphabetically" - ctx.Data["SortType"] = "alphabetically" - orderBy = "`badge`.slug ASC" + ctx.Data["SortType"] = "oldest" + orderBy = "`badge`.id ASC" } opts.Keyword = ctx.FormTrim("q") diff --git a/routers/web/web.go b/routers/web/web.go index 03aa75ef45..2f0d344bad 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -726,6 +726,8 @@ func registerRoutes(m *web.Router) { m.Get("/{badgeid}", admin.ViewBadge) m.Combo("/{badgeid}/edit").Get(admin.EditBadge).Post(web.Bind(forms.AdminCreateBadgeForm{}), admin.EditBadgePost) m.Post("/{badgeid}/delete", admin.DeleteBadge) + m.Combo("/{badgeid}/users").Get(admin.BadgeUsers).Post(admin.BadgeUsersPost) + m.Post("/{badgeid}/users/delete", admin.DeleteBadgeUser) }) m.Group("/emails", func() { diff --git a/templates/admin/badge/list.tmpl b/templates/admin/badge/list.tmpl index ebe3271d9a..c0480f3f57 100644 --- a/templates/admin/badge/list.tmpl +++ b/templates/admin/badge/list.tmpl @@ -52,7 +52,7 @@ {{.ID}} - {{.Slug}} + {{.Slug}} {{.Description}} @@ -62,7 +62,7 @@ diff --git a/templates/admin/badge/users.tmpl b/templates/admin/badge/users.tmpl new file mode 100644 index 0000000000..32bfa4c842 --- /dev/null +++ b/templates/admin/badge/users.tmpl @@ -0,0 +1,53 @@ +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin user")}} +
+

+ {{.Title}} +

+ {{if .Users}} +
+
+ {{range .Users}} +
+ +
+
+ {{template "shared/user/name" .}} +
+
+
+ +
+
+ {{end}} +
+
+ {{end}} +
+
+ {{.CsrfTokenHtml}} + + +
+
+ + + +{{template "admin/layout_footer" .}} diff --git a/templates/admin/badge/view.tmpl b/templates/admin/badge/view.tmpl index ec6469afbe..1f4a3e11c4 100644 --- a/templates/admin/badge/view.tmpl +++ b/templates/admin/badge/view.tmpl @@ -31,9 +31,9 @@

- {{ctx.Locale.Tr "explore.users"}} + {{ctx.Locale.Tr "explore.users"}} ({{.UsersTotal}})