mirror of
https://github.com/go-gitea/gitea
synced 2025-01-19 06:04:26 +00:00
Implemented User Badge Management Interface (#29798)
Co-authored-by: Diogo Vicente <diogo.m.s.vicente@tecnico.ulisboa.pt>
This commit is contained in:
parent
8b86c3140a
commit
f68e44daed
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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() {
|
||||
|
@ -52,7 +52,7 @@
|
||||
<tr>
|
||||
<td>{{.ID}}</td>
|
||||
<td>
|
||||
<a href="">{{.Slug}}</a>
|
||||
<a href="{{$.Link}}/{{.ID}}">{{.Slug}}</a>
|
||||
</td>
|
||||
<td class="gt-ellipsis tw-max-w-48">{{.Description}}</td>
|
||||
<td></td>
|
||||
@ -62,7 +62,7 @@
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="tw-flex tw-gap-2">
|
||||
<a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.users.details"}}">{{svg "octicon-star"}}</a>
|
||||
<a href="{{$.Link}}/{{.ID}}" data-tooltip-content="{{ctx.Locale.Tr "admin.badges.details"}}">{{svg "octicon-star"}}</a>
|
||||
<a href="{{$.Link}}/{{.ID}}/edit" data-tooltip-content="{{ctx.Locale.Tr "edit"}}">{{svg "octicon-pencil"}}</a>
|
||||
</div>
|
||||
</td>
|
||||
|
53
templates/admin/badge/users.tmpl
Normal file
53
templates/admin/badge/users.tmpl
Normal file
@ -0,0 +1,53 @@
|
||||
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin user")}}
|
||||
<div class="admin-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{.Title}}
|
||||
</h4>
|
||||
{{if .Users}}
|
||||
<div class="ui attached segment">
|
||||
<div class="flex-list">
|
||||
{{range .Users}}
|
||||
<div class="flex-item tw-items-center">
|
||||
<div class="flex-item-leading">
|
||||
<a href="{{.HomeLink}}">{{ctx.AvatarUtils.Avatar . 32}}</a>
|
||||
</div>
|
||||
<div class="flex-item-main">
|
||||
<div class="flex-item-title">
|
||||
{{template "shared/user/name" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<button class="ui red tiny button inline delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
|
||||
{{ctx.Locale.Tr "admin.badges.remove_user"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="ui bottom attached segment">
|
||||
<form class="ui form" id="search-badge-user-form" action="{{.Link}}" method="POST">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div id="search-user-box" class="ui search input tw-align-middle">
|
||||
<input class="prompt" name="user" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "admin.badges.add_user"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "admin.badges.remove_user"}}
|
||||
</div>
|
||||
<form class="ui form" method="post" id="remove-badge-user-form" action="{{.Link}}">
|
||||
<div class="content">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<p>{{ctx.Locale.Tr "admin.badges.delete_user_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{template "admin/layout_footer" .}}
|
@ -31,9 +31,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "explore.users"}}
|
||||
{{ctx.Locale.Tr "explore.users"}} ({{.UsersTotal}})
|
||||
<div class="ui right">
|
||||
{{.UsersTotal}}
|
||||
<a class="ui primary tiny button" href="{{.Link}}/users">{{ctx.Locale.Tr "admin.badges.manage_users"}}</a>
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
|
Loading…
x
Reference in New Issue
Block a user