Skip to content

Commit 7a9d2b1

Browse files
add admin api to modify user badges
1 parent 049b9f3 commit 7a9d2b1

File tree

6 files changed

+255
-5
lines changed

6 files changed

+255
-5
lines changed

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ var migrations = []Migration{
532532
NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
533533
// v275 -> v276
534534
NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun),
535+
// v276 -> v277
536+
NewMigration("Use Slug instead of ID for Badges", v1_21.UseSlugInsteadOfIDForBadges),
535537
}
536538

537539
// GetCurrentDBVersion returns the current db version

models/migrations/v1_21/v276.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_21 //nolint
5+
6+
import (
7+
"code.gitea.io/gitea/models/migrations/base"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
type BadgeUnique struct {
13+
Slug string `xorm:"UNIQUE"`
14+
}
15+
16+
func (BadgeUnique) TableName() string {
17+
return "badge"
18+
}
19+
20+
func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error {
21+
type Badge struct {
22+
Slug string
23+
}
24+
type UserBadge struct {
25+
BadgeSlug string `xorm:"INDEX"`
26+
}
27+
err := x.Sync(new(Badge), new(UserBadge))
28+
if err != nil {
29+
return err
30+
}
31+
sess := x.NewSession()
32+
defer sess.Close()
33+
if err := sess.Begin(); err != nil {
34+
return err
35+
}
36+
// add slug to each badge
37+
_, err = sess.Exec("UPDATE `badge` SET `slug` = `id`")
38+
if err != nil {
39+
return err
40+
}
41+
// add slug to each user badge
42+
_, err = sess.Exec("UPDATE `user_badge` SET `badge_slug` = (SELECT `slug` FROM `badge` WHERE `badge`.`id` = `user_badge`.`badge_id`)")
43+
if err != nil {
44+
return err
45+
}
46+
// drop badge_id columns from tables
47+
if err := base.DropTableColumns(sess, "user_badge", "badge_id"); err != nil {
48+
return err
49+
}
50+
if err := base.DropTableColumns(sess, "badge", "id"); err != nil {
51+
return err
52+
}
53+
err = sess.Sync(new(BadgeUnique))
54+
if err != nil {
55+
return err
56+
}
57+
return sess.Commit()
58+
}

models/user/badge.go

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ import (
1111

1212
// Badge represents a user badge
1313
type Badge struct {
14-
ID int64 `xorm:"pk autoincr"`
14+
Slug string `xorm:"UNIQUE"`
1515
Description string
1616
ImageURL string
1717
}
1818

1919
// UserBadge represents a user badge
2020
type UserBadge struct { //nolint:revive
21-
ID int64 `xorm:"pk autoincr"`
22-
BadgeID int64
23-
UserID int64 `xorm:"INDEX"`
21+
ID int64 `xorm:"pk autoincr"`
22+
BadgeSlug string
23+
UserID int64 `xorm:"INDEX"`
2424
}
2525

2626
func init() {
@@ -32,10 +32,81 @@ func init() {
3232
func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) {
3333
sess := db.GetEngine(ctx).
3434
Select("`badge`.*").
35-
Join("INNER", "user_badge", "`user_badge`.badge_id=badge.id").
35+
Join("INNER", "user_badge", "`user_badge`.badge_slug=badge.slug").
3636
Where("user_badge.user_id=?", u.ID)
3737

3838
badges := make([]*Badge, 0, 8)
3939
count, err := sess.FindAndCount(&badges)
4040
return badges, count, err
4141
}
42+
43+
// CreateBadge creates a new badge.
44+
func CreateBadge(ctx context.Context, badge *Badge) error {
45+
_, err := db.GetEngine(ctx).Insert(badge)
46+
return err
47+
}
48+
49+
// GetBadge returns a badge
50+
func GetBadge(ctx context.Context, slug string) (*Badge, error) {
51+
badge := new(Badge)
52+
has, err := db.GetEngine(ctx).Where("slug=?", slug).Get(badge)
53+
if !has {
54+
return nil, err
55+
}
56+
return badge, err
57+
}
58+
59+
// UpdateBadge updates a badge based on its slug.
60+
func UpdateBadge(ctx context.Context, badge *Badge) error {
61+
_, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Update(badge)
62+
return err
63+
}
64+
65+
// DeleteBadge deletes a badge.
66+
func DeleteBadge(ctx context.Context, badge *Badge) error {
67+
_, err := db.GetEngine(ctx).Delete(badge)
68+
return err
69+
}
70+
71+
// AddUserBadge adds a badge to a user.
72+
func AddUserBadge(ctx context.Context, u *User, badge *Badge) error {
73+
return AddUserBadges(ctx, u, []*Badge{badge})
74+
}
75+
76+
// AddUserBadges adds badges to a user.
77+
func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error {
78+
return db.WithTx(ctx, func(ctx context.Context) error {
79+
for _, badge := range badges {
80+
if err := db.Insert(ctx, &UserBadge{
81+
BadgeSlug: badge.Slug,
82+
UserID: u.ID,
83+
}); err != nil {
84+
return err
85+
}
86+
}
87+
return nil
88+
})
89+
}
90+
91+
// RemoveUserBadge removes a badge from a user.
92+
func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error {
93+
return RemoveUserBadges(ctx, u, []*Badge{badge})
94+
}
95+
96+
// RemoveUserBadges removes badges from a user.
97+
func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
98+
return db.WithTx(ctx, func(ctx context.Context) error {
99+
for _, badge := range badges {
100+
if _, err := db.GetEngine(ctx).Where("user_id=? AND badge_slug=?", u.ID, badge.Slug).Delete(&UserBadge{}); err != nil {
101+
return err
102+
}
103+
}
104+
return nil
105+
})
106+
}
107+
108+
// RemoveAllUserBadges removes all badges from a user.
109+
func RemoveAllUserBadges(ctx context.Context, u *User) error {
110+
_, err := db.GetEngine(ctx).Where("user_id=?", u.ID).Delete(&UserBadge{})
111+
return err
112+
}

modules/structs/user.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,25 @@ type UpdateUserAvatarOption struct {
108108
// image must be base64 encoded
109109
Image string `json:"image" binding:"Required"`
110110
}
111+
112+
// Badge represents a user badge
113+
// swagger:model
114+
type Badge struct {
115+
Slug string `json:"slug"`
116+
Description string `json:"description"`
117+
ImageURL string `json:"image_url"`
118+
}
119+
120+
// UserBadge represents a user badge
121+
// swagger:model
122+
type UserBadge struct {
123+
ID int64 `json:"id"`
124+
BadgeSlug int64 `json:"badge_slug"`
125+
UserID int64 `json:"user_id"`
126+
}
127+
128+
// UserBadgeOption options for link between users and badges
129+
// swagger:model
130+
type UserBadgeOption struct {
131+
BadgeSlugs []string `json:"badge_slugs" binding:"Required"`
132+
}

routers/api/v1/admin/user_badge.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package admin
5+
6+
import (
7+
"net/http"
8+
9+
user_model "code.gitea.io/gitea/models/user"
10+
"code.gitea.io/gitea/modules/context"
11+
api "code.gitea.io/gitea/modules/structs"
12+
"code.gitea.io/gitea/modules/web"
13+
)
14+
15+
// AddUserBadges add badges to a user
16+
func AddUserBadges(ctx *context.APIContext) {
17+
// swagger:operation POST /admin/users/{username}/badges admin adminAddUserBadges
18+
// ---
19+
// summary: Add a badge to a user
20+
// consumes:
21+
// - application/json
22+
// produces:
23+
// - application/json
24+
// parameters:
25+
// - name: username
26+
// in: path
27+
// description: username of user
28+
// type: string
29+
// required: true
30+
// - name: body
31+
// in: body
32+
// schema:
33+
// "$ref": "#/definitions/UserBadgeOption"
34+
// responses:
35+
// "204":
36+
// "$ref": "#/responses/empty"
37+
// "403":
38+
// "$ref": "#/responses/forbidden"
39+
40+
form := web.GetForm(ctx).(*api.UserBadgeOption)
41+
badges := prepareBadgesForReplaceOrAdd(ctx, *form)
42+
43+
if err := user_model.AddUserBadges(ctx, ctx.ContextUser, badges); err != nil {
44+
ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
45+
return
46+
}
47+
48+
ctx.Status(http.StatusNoContent)
49+
}
50+
51+
// DeleteUserBadges delete a badge from a user
52+
func DeleteUserBadges(ctx *context.APIContext) {
53+
// swagger:operation DELETE /admin/users/{username}/badges admin adminDeleteUserBadges
54+
// ---
55+
// summary: Remove a badge from a user
56+
// produces:
57+
// - application/json
58+
// parameters:
59+
// - name: username
60+
// in: path
61+
// description: username of user
62+
// type: string
63+
// required: true
64+
// - name: body
65+
// in: body
66+
// schema:
67+
// "$ref": "#/definitions/UserBadgeOption"
68+
// responses:
69+
// "204":
70+
// "$ref": "#/responses/empty"
71+
// "403":
72+
// "$ref": "#/responses/forbidden"
73+
// "422":
74+
// "$ref": "#/responses/validationError"
75+
76+
form := web.GetForm(ctx).(*api.UserBadgeOption)
77+
badges := prepareBadgesForReplaceOrAdd(ctx, *form)
78+
79+
if err := user_model.RemoveUserBadges(ctx, ctx.ContextUser, badges); err != nil {
80+
ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
81+
return
82+
}
83+
84+
ctx.Status(http.StatusNoContent)
85+
}
86+
87+
func prepareBadgesForReplaceOrAdd(ctx *context.APIContext, form api.UserBadgeOption) []*user_model.Badge {
88+
badges := make([]*user_model.Badge, len(form.BadgeSlugs))
89+
for i, badge := range form.BadgeSlugs {
90+
badges[i] = &user_model.Badge{
91+
Slug: badge,
92+
}
93+
}
94+
return badges
95+
}

routers/api/v1/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,8 @@ func Routes() *web.Route {
13941394
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
13951395
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
13961396
m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
1397+
m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges)
1398+
m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges)
13971399
}, context_service.UserAssignmentAPI())
13981400
})
13991401
m.Group("/emails", func() {

0 commit comments

Comments
 (0)