Skip to content

Commit f378b9b

Browse files
HenriquerPimentelDiogo Vicente
and
Diogo Vicente
committed
Implemented User Badge Management Interface (#29798)
Co-authored-by: Diogo Vicente <diogo.m.s.vicente@tecnico.ulisboa.pt>
1 parent ced2ad4 commit f378b9b

File tree

9 files changed

+156
-18
lines changed

9 files changed

+156
-18
lines changed

models/user/badge.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ func DeleteUserBadgeRecord(ctx context.Context, badge *Badge) error {
125125

126126
// AddUserBadge adds a badge to a user.
127127
func AddUserBadge(ctx context.Context, u *User, badge *Badge) error {
128+
isExist, err := IsBadgeUserExist(ctx, u.ID, badge.ID)
129+
if err != nil {
130+
return err
131+
} else if isExist {
132+
return ErrBadgeAlreadyExist{}
133+
}
134+
128135
return AddUserBadges(ctx, u, []*Badge{badge})
129136
}
130137

@@ -133,11 +140,11 @@ func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error {
133140
return db.WithTx(ctx, func(ctx context.Context) error {
134141
for _, badge := range badges {
135142
// hydrate badge and check if it exists
136-
has, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Get(badge)
143+
has, err := db.GetEngine(ctx).Where("id=?", badge.ID).Get(badge)
137144
if err != nil {
138145
return err
139146
} else if !has {
140-
return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug)
147+
return ErrBadgeNotExist{ID: badge.ID}
141148
}
142149
if err := db.Insert(ctx, &UserBadge{
143150
BadgeID: badge.ID,
@@ -159,10 +166,7 @@ func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error {
159166
func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
160167
return db.WithTx(ctx, func(ctx context.Context) error {
161168
for _, badge := range badges {
162-
if _, err := db.GetEngine(ctx).
163-
Join("INNER", "badge", "badge.id = `user_badge`.badge_id").
164-
Where("`user_badge`.user_id=? AND `badge`.slug=?", u.ID, badge.Slug).
165-
Delete(&UserBadge{}); err != nil {
169+
if _, err := db.GetEngine(ctx).Delete(&UserBadge{BadgeID: badge.ID, UserID: u.ID}); err != nil {
166170
return err
167171
}
168172
}
@@ -192,6 +196,12 @@ func IsBadgeExist(ctx context.Context, uid int64, slug string) (bool, error) {
192196
Get(&Badge{Slug: strings.ToLower(slug)})
193197
}
194198

199+
// IsBadgeUserExist checks if given badge id, uid exist,
200+
func IsBadgeUserExist(ctx context.Context, uid, bid int64) (bool, error) {
201+
return db.GetEngine(ctx).
202+
Get(&UserBadge{UserID: uid, BadgeID: bid})
203+
}
204+
195205
// SearchBadgeOptions represents the options when fdin badges
196206
type SearchBadgeOptions struct {
197207
db.ListOptions

models/user/error.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func IsErrBadgeNotExist(err error) bool {
141141
}
142142

143143
func (err ErrBadgeNotExist) Error() string {
144-
return fmt.Sprintf("badge does not exist [slug: %s | id: %i]", err.Slug, err.ID)
144+
return fmt.Sprintf("badge does not exist [slug: %s | id: %d]", err.Slug, err.ID)
145145
}
146146

147147
// Unwrap unwraps this error as a ErrNotExist error

options/locale/locale_en-US.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2988,6 +2988,14 @@ badges.edit_badge = Edit Badge
29882988
badges.update_badge = Update Badge
29892989
badges.delete_badge = Delete Badge
29902990
badges.delete_badge_desc = Are you sure you want to permanently delete this badge?
2991+
badges.users_with_badge = Users with Badge (%d)
2992+
badges.add_user = Add User
2993+
badges.remove_user = Remove User
2994+
badges.delete_user_desc = Are you sure you want to remove this badge from the user?
2995+
badges.not_found = Badge not found!
2996+
badges.user_add_success = User has been added to the badge.
2997+
badges.user_remove_success = User has been removed from the badge.
2998+
badges.manage_users = Manage Users
29912999

29923000

29933001
orgs.org_manage_panel = Organization Management

routers/web/admin/badges.go

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"net/url"
1010
"strconv"
11+
"strings"
1112

1213
"code.gitea.io/gitea/models/db"
1314
user_model "code.gitea.io/gitea/models/user"
@@ -22,10 +23,11 @@ import (
2223
)
2324

2425
const (
25-
tplBadges base.TplName = "admin/badge/list"
26-
tplBadgeNew base.TplName = "admin/badge/new"
27-
tplBadgeView base.TplName = "admin/badge/view"
28-
tplBadgeEdit base.TplName = "admin/badge/edit"
26+
tplBadges base.TplName = "admin/badge/list"
27+
tplBadgeNew base.TplName = "admin/badge/new"
28+
tplBadgeView base.TplName = "admin/badge/view"
29+
tplBadgeEdit base.TplName = "admin/badge/edit"
30+
tplBadgeUsers base.TplName = "admin/badge/users"
2931
)
3032

3133
// BadgeSearchDefaultAdminSort is the default sort type for admin view
@@ -213,3 +215,67 @@ func DeleteBadge(ctx *context.Context) {
213215
ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success"))
214216
ctx.Redirect(setting.AppSubURL + "/admin/badges")
215217
}
218+
219+
func BadgeUsers(ctx *context.Context) {
220+
ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.ParamsInt64(":badgeid"))
221+
ctx.Data["PageIsAdminBadges"] = true
222+
223+
users, _, err := user_model.GetBadgeUsers(ctx, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")})
224+
if err != nil {
225+
ctx.ServerError("GetBadgeUsers", err)
226+
return
227+
}
228+
229+
ctx.Data["Users"] = users
230+
231+
ctx.HTML(http.StatusOK, tplBadgeUsers)
232+
}
233+
234+
// BadgeUsersPost response for actions for user badges
235+
func BadgeUsersPost(ctx *context.Context) {
236+
name := strings.ToLower(ctx.FormString("user"))
237+
238+
u, err := user_model.GetUserByName(ctx, name)
239+
if err != nil {
240+
if user_model.IsErrUserNotExist(err) {
241+
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
242+
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
243+
} else {
244+
ctx.ServerError("GetUserByName", err)
245+
}
246+
return
247+
}
248+
249+
if err = user_model.AddUserBadge(ctx, u, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")}); err != nil {
250+
if user_model.IsErrBadgeNotExist(err) {
251+
ctx.Flash.Error(ctx.Tr("admin.badges.not_found"))
252+
} else {
253+
ctx.ServerError("AddUserBadge", err)
254+
}
255+
return
256+
}
257+
258+
ctx.Flash.Success(ctx.Tr("admin.badges.user_add_success"))
259+
ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
260+
}
261+
262+
// DeleteBadgeUser delete a badge from a user
263+
func DeleteBadgeUser(ctx *context.Context) {
264+
if user, err := user_model.GetUserByID(ctx, ctx.FormInt64("id")); err != nil {
265+
if user_model.IsErrUserNotExist(err) {
266+
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
267+
} else {
268+
ctx.ServerError("GetUserByName", err)
269+
return
270+
}
271+
} else {
272+
if err := user_model.RemoveUserBadge(ctx, user, &user_model.Badge{ID: ctx.ParamsInt64(":badgeid")}); err == nil {
273+
ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success"))
274+
} else {
275+
ctx.Flash.Error("DeleteUser: " + err.Error())
276+
return
277+
}
278+
}
279+
280+
ctx.JSONRedirect(setting.AppSubURL + "/admin/badges/" + ctx.Params(":badgeid") + "/users")
281+
}

routers/web/explore/badge.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions
5656
orderBy = "`badge`.slug ASC"
5757
default:
5858
// in case the sortType is not valid, we set it to recent update
59-
sortOrder = "alphabetically"
60-
ctx.Data["SortType"] = "alphabetically"
61-
orderBy = "`badge`.slug ASC"
59+
ctx.Data["SortType"] = "oldest"
60+
orderBy = "`badge`.id ASC"
6261
}
6362

6463
opts.Keyword = ctx.FormTrim("q")

routers/web/web.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,8 @@ func registerRoutes(m *web.Route) {
726726
m.Get("/{badgeid}", admin.ViewBadge)
727727
m.Combo("/{badgeid}/edit").Get(admin.EditBadge).Post(web.Bind(forms.AdminCreateBadgeForm{}), admin.EditBadgePost)
728728
m.Post("/{badgeid}/delete", admin.DeleteBadge)
729+
m.Combo("/{badgeid}/users").Get(admin.BadgeUsers).Post(admin.BadgeUsersPost)
730+
m.Post("/{badgeid}/users/delete", admin.DeleteBadgeUser)
729731
})
730732

731733
m.Group("/emails", func() {

templates/admin/badge/list.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<tr>
5353
<td>{{.ID}}</td>
5454
<td>
55-
<a href="">{{.Slug}}</a>
55+
<a href="{{$.Link}}/{{.ID}}">{{.Slug}}</a>
5656
</td>
5757
<td class="gt-ellipsis tw-max-w-48">{{.Description}}</td>
5858
<td></td>
@@ -62,7 +62,7 @@
6262
<td></td>
6363
<td>
6464
<div class="tw-flex tw-gap-2">
65-
<a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">{{svg "octicon-star"}}</a>
65+
<a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.badges.details"}}">{{svg "octicon-star"}}</a>
6666
<a href="{{$.Link}}/{{.ID}}/edit" data-tooltip-content="{{ctx.Locale.Tr "edit"}}">{{svg "octicon-pencil"}}</a>
6767
</div>
6868
</td>

templates/admin/badge/users.tmpl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin user")}}
2+
<div class="admin-setting-content">
3+
<h4 class="ui top attached header">
4+
{{.Title}}
5+
</h4>
6+
{{if .Users}}
7+
<div class="ui attached segment">
8+
<div class="flex-list">
9+
{{range .Users}}
10+
<div class="flex-item tw-items-center">
11+
<div class="flex-item-leading">
12+
<a href="{{.HomeLink}}">{{ctx.AvatarUtils.Avatar . 32}}</a>
13+
</div>
14+
<div class="flex-item-main">
15+
<div class="flex-item-title">
16+
{{template "shared/user/name" .}}
17+
</div>
18+
</div>
19+
<div class="flex-item-trailing">
20+
<button class="ui red tiny button inline delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
21+
{{ctx.Locale.Tr "admin.badges.remove_user"}}
22+
</button>
23+
</div>
24+
</div>
25+
{{end}}
26+
</div>
27+
</div>
28+
{{end}}
29+
<div class="ui bottom attached segment">
30+
<form class="ui form" id="search-badge-user-form" action="{{.Link}}" method="POST">
31+
{{.CsrfTokenHtml}}
32+
<div id="search-user-box" class="ui search input tw-align-middle">
33+
<input class="prompt" name="user" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" autofocus required>
34+
</div>
35+
<button class="ui primary button">{{ctx.Locale.Tr "admin.badges.add_user"}}</button>
36+
</form>
37+
</div>
38+
39+
<div class="ui g-modal-confirm delete modal">
40+
<div class="header">
41+
{{svg "octicon-trash"}}
42+
{{ctx.Locale.Tr "admin.badges.remove_user"}}
43+
</div>
44+
<form class="ui form" method="post" id="remove-badge-user-form" action="{{.Link}}">
45+
<div class="content">
46+
{{$.CsrfTokenHtml}}
47+
<p>{{ctx.Locale.Tr "admin.badges.delete_user_desc"}}</p>
48+
</div>
49+
{{template "base/modal_actions_confirm" .}}
50+
</form>
51+
</div>
52+
53+
{{template "admin/layout_footer" .}}

templates/admin/badge/view.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
</div>
3232
</div>
3333
<h4 class="ui top attached header">
34-
{{ctx.Locale.Tr "explore.users"}}
34+
{{ctx.Locale.Tr "explore.users"}} ({{.UsersTotal}})
3535
<div class="ui right">
36-
{{.UsersTotal}}
36+
<a class="ui primary tiny button" href="{{.Link}}/users">{{ctx.Locale.Tr "admin.badges.manage_users"}}</a>
3737
</div>
3838
</h4>
3939
<div class="ui attached segment">

0 commit comments

Comments
 (0)