Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 91b085b

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 7e734b6 commit 91b085b

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
@@ -2982,6 +2982,14 @@ badges.edit_badge = Edit Badge
29822982
badges.update_badge = Update Badge
29832983
badges.delete_badge = Delete Badge
29842984
badges.delete_badge_desc = Are you sure you want to permanently delete this badge?
2985+
badges.users_with_badge = Users with Badge (%d)
2986+
badges.add_user = Add User
2987+
badges.remove_user = Remove User
2988+
badges.delete_user_desc = Are you sure you want to remove this badge from the user?
2989+
badges.not_found = Badge not found!
2990+
badges.user_add_success = User has been added to the badge.
2991+
badges.user_remove_success = User has been removed from the badge.
2992+
badges.manage_users = Manage Users
29852993

29862994

29872995
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
@@ -725,6 +725,8 @@ func registerRoutes(m *web.Route) {
725725
m.Get("/{badgeid}", admin.ViewBadge)
726726
m.Combo("/{badgeid}/edit").Get(admin.EditBadge).Post(web.Bind(forms.AdminCreateBadgeForm{}), admin.EditBadgePost)
727727
m.Post("/{badgeid}/delete", admin.DeleteBadge)
728+
m.Combo("/{badgeid}/users").Get(admin.BadgeUsers).Post(admin.BadgeUsersPost)
729+
m.Post("/{badgeid}/users/delete", admin.DeleteBadgeUser)
728730
})
729731

730732
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="repo-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)

AltStyle によって変換されたページ (->オリジナル) /