Update per feedback

This commit is contained in:
techknowlogick 2025-01-15 14:21:40 -05:00
parent 9234ef5ecc
commit f601501fc1
7 changed files with 147 additions and 55 deletions

View File

@ -46,14 +46,25 @@ func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) {
return badges, count, err return badges, count, err
} }
// GetBadgeUsers returns the users that have a specific badge. // GetBadgeUsersOptions contains options for getting users with a specific badge
func GetBadgeUsers(ctx context.Context, b *Badge) ([]*User, int64, error) { type GetBadgeUsersOptions struct {
db.ListOptions
Badge *Badge
}
// GetBadgeUsers returns the users that have a specific badge with pagination support.
func GetBadgeUsers(ctx context.Context, opts *GetBadgeUsersOptions) ([]*User, int64, error) {
sess := db.GetEngine(ctx). sess := db.GetEngine(ctx).
Select("`user`.*"). Select("`user`.*").
Join("INNER", "user_badge", "`user_badge`.user_id=user.id"). Join("INNER", "user_badge", "`user_badge`.user_id=user.id").
Join("INNER", "badge", "`user_badge`.badge_id=badge.id"). Join("INNER", "badge", "`user_badge`.badge_id=badge.id").
Where("badge.slug=?", b.Slug) Where("badge.slug=?", opts.Badge.Slug)
users := make([]*User, 0, 8)
if opts.Page > 0 {
sess = db.SetSessionPagination(sess, opts)
}
users := make([]*User, 0, opts.PageSize)
count, err := sess.FindAndCount(&users) count, err := sess.FindAndCount(&users)
return users, count, err return users, count, err
} }
@ -82,12 +93,24 @@ func UpdateBadge(ctx context.Context, badge *Badge) error {
return err return err
} }
// DeleteBadge deletes a badge. // DeleteBadge deletes a badge and all associated user_badge entries.
func DeleteBadge(ctx context.Context, badge *Badge) error { func DeleteBadge(ctx context.Context, badge *Badge) error {
_, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge) return db.WithTx(ctx, func(ctx context.Context) error {
// First delete all user_badge entries for this badge
if _, err := db.GetEngine(ctx).
Where("badge_id = (SELECT id FROM badge WHERE slug = ?)", badge.Slug).
Delete(&UserBadge{}); err != nil {
return err return err
} }
// Then delete the badge itself
if _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge); err != nil {
return err
}
return nil
})
}
// AddUserBadge adds a badge to a user. // AddUserBadge adds a badge to a user.
func AddUserBadge(ctx context.Context, u *User, badge *Badge) error { func AddUserBadge(ctx context.Context, u *User, badge *Badge) error {
return AddUserBadges(ctx, u, []*Badge{badge}) return AddUserBadges(ctx, u, []*Badge{badge})
@ -123,19 +146,19 @@ func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error {
// RemoveUserBadges removes specific badges from a user. // RemoveUserBadges removes specific badges from a user.
func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error { func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
return db.WithTx(ctx, func(ctx context.Context) error { return db.WithTx(ctx, func(ctx context.Context) error {
for _, badge := range badges { slugs := make([]string, len(badges))
subQuery := builder. for i, badge := range badges {
Select("id"). slugs[i] = badge.Slug
From("badge"). }
Where(builder.Eq{"slug": badge.Slug})
if _, err := db.GetEngine(ctx). if _, err := db.GetEngine(ctx).
Table("user_badge").
Join("INNER", "badge", "`user_badge`.badge_id = badge.id").
Where("`user_badge`.user_id = ?", u.ID). Where("`user_badge`.user_id = ?", u.ID).
And(builder.In("badge_id", subQuery)). And(builder.In("badge.slug", slugs)).
Delete(&UserBadge{}); err != nil { Delete(&UserBadge{}); err != nil {
return err return err
} }
}
return nil return nil
}) })
} }
@ -155,8 +178,6 @@ type SearchBadgeOptions struct {
ID int64 ID int64
OrderBy db.SearchOrderBy OrderBy db.SearchOrderBy
Actor *User // The user doing the search Actor *User // The user doing the search
ExtraParamStrings map[string]string
} }
func (opts *SearchBadgeOptions) ToConds() builder.Cond { func (opts *SearchBadgeOptions) ToConds() builder.Cond {
@ -174,15 +195,6 @@ func (opts *SearchBadgeOptions) ToOrders() string {
return orderBy return orderBy
} }
func (opts *SearchBadgeOptions) ToJoins() []db.JoinFunc {
return []db.JoinFunc{
func(e db.Engine) error {
e.Join("INNER", "badge", "`user_badge`.badge_id=badge.id")
return nil
},
}
}
func SearchBadges(ctx context.Context, opts *SearchBadgeOptions) (badges []*Badge, _ int64, _ error) { func SearchBadges(ctx context.Context, opts *SearchBadgeOptions) (badges []*Badge, _ int64, _ error) {
sessCount := opts.toSearchQueryBase(ctx) sessCount := opts.toSearchQueryBase(ctx)
count, err := sessCount.Count(new(Badge)) count, err := sessCount.Count(new(Badge))
@ -233,3 +245,16 @@ func (opts *SearchBadgeOptions) toSearchQueryBase(ctx context.Context) *xorm.Ses
return e.Where(cond) return e.Where(cond)
} }
// GetBadgeByID returns a specific badge by ID
func GetBadgeByID(ctx context.Context, id int64) (*Badge, error) {
badge := new(Badge)
has, err := db.GetEngine(ctx).ID(id).Get(badge)
if err != nil {
return nil, err
}
if !has {
return nil, ErrBadgeNotExist{ID: id}
}
return badge, nil
}

View File

@ -131,6 +131,14 @@ func (err ErrBadgeAlreadyExist) Unwrap() error {
// ErrBadgeNotExist represents a "BadgeNotExist" kind of error. // ErrBadgeNotExist represents a "BadgeNotExist" kind of error.
type ErrBadgeNotExist struct { type ErrBadgeNotExist struct {
Slug string Slug string
ID int64
}
func (err ErrBadgeNotExist) Error() string {
if err.ID > 0 {
return fmt.Sprintf("badge does not exist [id: %d]", err.ID)
}
return fmt.Sprintf("badge does not exist [slug: %s]", err.Slug)
} }
// IsErrBadgeNotExist checks if an error is a ErrBadgeNotExist. // IsErrBadgeNotExist checks if an error is a ErrBadgeNotExist.
@ -139,10 +147,6 @@ func IsErrBadgeNotExist(err error) bool {
return ok return ok
} }
func (err ErrBadgeNotExist) Error() string {
return fmt.Sprintf("badge does not exist [slug: %s]", err.Slug)
}
// Unwrap unwraps this error as a ErrNotExist error // Unwrap unwraps this error as a ErrNotExist error
func (err ErrBadgeNotExist) Unwrap() error { func (err ErrBadgeNotExist) Unwrap() error {
return util.ErrNotExist return util.ErrNotExist

View File

@ -7,14 +7,13 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore" "code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
@ -23,11 +22,11 @@ import (
) )
const ( const (
tplBadges base.TplName = "admin/badge/list" tplBadges templates.TplName = "admin/badge/list"
tplBadgeNew base.TplName = "admin/badge/new" tplBadgeNew templates.TplName = "admin/badge/new"
tplBadgeView base.TplName = "admin/badge/view" tplBadgeView templates.TplName = "admin/badge/view"
tplBadgeEdit base.TplName = "admin/badge/edit" tplBadgeEdit templates.TplName = "admin/badge/edit"
tplBadgeUsers base.TplName = "admin/badge/users" tplBadgeUsers templates.TplName = "admin/badge/users"
) )
// BadgeSearchDefaultAdminSort is the default sort type for admin view // BadgeSearchDefaultAdminSort is the default sort type for admin view
@ -97,14 +96,14 @@ func NewBadgePost(ctx *context.Context) {
log.Trace("Badge created by admin (%s): %s", ctx.Doer.Name, b.Slug) log.Trace("Badge created by admin (%s): %s", ctx.Doer.Name, b.Slug)
ctx.Flash.Success(ctx.Tr("admin.badges.new_success", b.Slug)) ctx.Flash.Success(ctx.Tr("admin.badges.new_success", b.Slug))
ctx.Redirect(setting.AppSubURL + "/admin/badges/" + strconv.FormatInt(b.ID, 10)) ctx.Redirect(setting.AppSubURL + "/-/admin/badges/" + url.PathEscape(b.Slug))
} }
func prepareBadgeInfo(ctx *context.Context) *user_model.Badge { func prepareBadgeInfo(ctx *context.Context) *user_model.Badge {
b, err := user_model.GetBadge(ctx, ctx.PathParam(":badge_slug")) b, err := user_model.GetBadge(ctx, ctx.PathParam(":badge_slug"))
if err != nil { if err != nil {
if user_model.IsErrBadgeNotExist(err) { if user_model.IsErrBadgeNotExist(err) {
ctx.Redirect(setting.AppSubURL + "/admin/badges") ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
} else { } else {
ctx.ServerError("GetBadge", err) ctx.ServerError("GetBadge", err)
} }
@ -112,10 +111,16 @@ func prepareBadgeInfo(ctx *context.Context) *user_model.Badge {
} }
ctx.Data["Badge"] = b ctx.Data["Badge"] = b
users, count, err := user_model.GetBadgeUsers(ctx, b) opts := &user_model.GetBadgeUsersOptions{
ListOptions: db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum,
},
Badge: b,
}
users, count, err := user_model.GetBadgeUsers(ctx, opts)
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Redirect(setting.AppSubURL + "/admin/badges") ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
} else { } else {
ctx.ServerError("GetBadgeUsers", err) ctx.ServerError("GetBadgeUsers", err)
} }
@ -187,7 +192,7 @@ func EditBadgePost(ctx *context.Context) {
log.Trace("Badge updated by admin (%s): %s", ctx.Doer.Name, b.Slug) log.Trace("Badge updated by admin (%s): %s", ctx.Doer.Name, b.Slug)
ctx.Flash.Success(ctx.Tr("admin.badges.update_success")) ctx.Flash.Success(ctx.Tr("admin.badges.update_success"))
ctx.Redirect(setting.AppSubURL + "/admin/badges/" + url.PathEscape(ctx.PathParam(":badge_slug"))) ctx.Redirect(setting.AppSubURL + "/-/admin/badges/" + url.PathEscape(ctx.PathParam(":badge_slug")))
} }
// DeleteBadge response for deleting a badge // DeleteBadge response for deleting a badge
@ -206,20 +211,35 @@ func DeleteBadge(ctx *context.Context) {
log.Trace("Badge deleted by admin (%s): %s", ctx.Doer.Name, b.Slug) log.Trace("Badge deleted by admin (%s): %s", ctx.Doer.Name, b.Slug)
ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success")) ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success"))
ctx.Redirect(setting.AppSubURL + "/admin/badges") ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
} }
func BadgeUsers(ctx *context.Context) { func BadgeUsers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.PathParam(":badge_slug")) ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.PathParam(":badge_slug"))
ctx.Data["PageIsAdminBadges"] = true ctx.Data["PageIsAdminBadges"] = true
users, _, err := user_model.GetBadgeUsers(ctx, &user_model.Badge{Slug: ctx.PathParam(":badge_slug")}) page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
badge := &user_model.Badge{Slug: ctx.PathParam(":badge_slug")}
opts := &user_model.GetBadgeUsersOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.User.RepoPagingNum,
},
Badge: badge,
}
users, count, err := user_model.GetBadgeUsers(ctx, opts)
if err != nil { if err != nil {
ctx.ServerError("GetBadgeUsers", err) ctx.ServerError("GetBadgeUsers", err)
return return
} }
ctx.Data["Users"] = users ctx.Data["Users"] = users
ctx.Data["Total"] = count
ctx.Data["Page"] = context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
ctx.HTML(http.StatusOK, tplBadgeUsers) ctx.HTML(http.StatusOK, tplBadgeUsers)
} }
@ -267,8 +287,41 @@ func DeleteBadgeUser(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success")) ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success"))
} else { } else {
ctx.Flash.Error("DeleteUser: " + err.Error()) ctx.Flash.Error("DeleteUser: " + err.Error())
}
ctx.JSONRedirect(fmt.Sprintf("%s/-/admin/badges/%s/users", setting.AppSubURL, ctx.PathParam(":badge_slug")))
}
// ViewBadgeUsers render badge's users page
func ViewBadgeUsers(ctx *context.Context) {
badge, err := user_model.GetBadge(ctx, ctx.PathParam(":slug"))
if err != nil {
ctx.ServerError("GetBadge", err)
return return
} }
ctx.JSONRedirect(fmt.Sprintf("%s/admin/badges/%s/users", setting.AppSubURL, ctx.PathParam(":badge_slug"))) page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
opts := &user_model.GetBadgeUsersOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.User.RepoPagingNum,
},
Badge: badge,
}
users, count, err := user_model.GetBadgeUsers(ctx, opts)
if err != nil {
ctx.ServerError("GetBadgeUsers", err)
return
}
ctx.Data["Title"] = badge.Description
ctx.Data["Badge"] = badge
ctx.Data["Users"] = users
ctx.Data["Total"] = count
ctx.Data["Pages"] = context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
ctx.HTML(http.StatusOK, tplBadgeUsers)
} }

View File

@ -8,12 +8,12 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
) )
func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions, tplName base.TplName) { func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions, tplName templates.TplName) {
// Sitemap index for sitemap paths // Sitemap index for sitemap paths
opts.Page = int(ctx.PathParamInt64("idx")) opts.Page = int(ctx.PathParamInt64("idx"))
if opts.Page <= 1 { if opts.Page <= 1 {
@ -68,10 +68,7 @@ func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions
ctx.Data["Badges"] = badges ctx.Data["Badges"] = badges
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
pager.SetDefaultParams(ctx) pager.AddParamFromRequest(ctx.Req)
for paramKey, paramVal := range opts.ExtraParamStrings {
pager.AddParamString(paramKey, paramVal)
}
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplName) ctx.HTML(http.StatusOK, tplName)

View File

@ -44,3 +44,15 @@ func DeleteBadge(ctx context.Context, b *user_model.Badge) error {
return nil return nil
} }
// GetBadgeUsers returns the users that have a specific badge
func GetBadgeUsers(ctx context.Context, badge *user_model.Badge, page, pageSize int) ([]*user_model.User, int64, error) {
opts := &user_model.GetBadgeUsersOptions{
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
},
Badge: badge,
}
return user_model.GetBadgeUsers(ctx, opts)
}

View File

@ -3,7 +3,7 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.badges.badges_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}}) {{ctx.Locale.Tr "admin.badges.badges_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/badges/new">{{ctx.Locale.Tr "admin.badges.new_badge"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/-/admin/badges/new">{{ctx.Locale.Tr "admin.badges.new_badge"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">

View File

@ -26,6 +26,7 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{template "base/paginate" .}}
<div class="ui bottom attached segment"> <div class="ui bottom attached segment">
<form class="ui form" id="search-badge-user-form" action="{{.Link}}" method="post"> <form class="ui form" id="search-badge-user-form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}