From 7a9d2b194baaeeff93416359b646f66a952dfdef Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sat, 9 Sep 2023 03:36:56 +0000 Subject: [PATCH 01/22] add admin api to modify user badges --- models/migrations/migrations.go | 2 + models/migrations/v1_21/v276.go | 58 ++++++++++++++++++ models/user/badge.go | 81 +++++++++++++++++++++++-- modules/structs/user.go | 22 +++++++ routers/api/v1/admin/user_badge.go | 95 ++++++++++++++++++++++++++++++ routers/api/v1/api.go | 2 + 6 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 models/migrations/v1_21/v276.go create mode 100644 routers/api/v1/admin/user_badge.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index f0a8b05d5337d..b3715fb34e8f0 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -532,6 +532,8 @@ var migrations = []Migration{ NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable), // v275 -> v276 NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun), + // v276 -> v277 + NewMigration("Use Slug instead of ID for Badges", v1_21.UseSlugInsteadOfIDForBadges), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go new file mode 100644 index 0000000000000..c67fed3e422cd --- /dev/null +++ b/models/migrations/v1_21/v276.go @@ -0,0 +1,58 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "code.gitea.io/gitea/models/migrations/base" + + "xorm.io/xorm" +) + +type BadgeUnique struct { + Slug string `xorm:"UNIQUE"` +} + +func (BadgeUnique) TableName() string { + return "badge" +} + +func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { + type Badge struct { + Slug string + } + type UserBadge struct { + BadgeSlug string `xorm:"INDEX"` + } + err := x.Sync(new(Badge), new(UserBadge)) + if err != nil { + return err + } + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + // add slug to each badge + _, err = sess.Exec("UPDATE `badge` SET `slug` = `id`") + if err != nil { + return err + } + // add slug to each user badge + _, err = sess.Exec("UPDATE `user_badge` SET `badge_slug` = (SELECT `slug` FROM `badge` WHERE `badge`.`id` = `user_badge`.`badge_id`)") + if err != nil { + return err + } + // drop badge_id columns from tables + if err := base.DropTableColumns(sess, "user_badge", "badge_id"); err != nil { + return err + } + if err := base.DropTableColumns(sess, "badge", "id"); err != nil { + return err + } + err = sess.Sync(new(BadgeUnique)) + if err != nil { + return err + } + return sess.Commit() +} diff --git a/models/user/badge.go b/models/user/badge.go index ee52b44cf51de..596ee3a44d28f 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -11,16 +11,16 @@ import ( // Badge represents a user badge type Badge struct { - ID int64 `xorm:"pk autoincr"` + Slug string `xorm:"UNIQUE"` Description string ImageURL string } // UserBadge represents a user badge type UserBadge struct { //nolint:revive - ID int64 `xorm:"pk autoincr"` - BadgeID int64 - UserID int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + BadgeSlug string + UserID int64 `xorm:"INDEX"` } func init() { @@ -32,10 +32,81 @@ func init() { func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) { sess := db.GetEngine(ctx). Select("`badge`.*"). - Join("INNER", "user_badge", "`user_badge`.badge_id=badge.id"). + Join("INNER", "user_badge", "`user_badge`.badge_slug=badge.slug"). Where("user_badge.user_id=?", u.ID) badges := make([]*Badge, 0, 8) count, err := sess.FindAndCount(&badges) return badges, count, err } + +// CreateBadge creates a new badge. +func CreateBadge(ctx context.Context, badge *Badge) error { + _, err := db.GetEngine(ctx).Insert(badge) + return err +} + +// GetBadge returns a badge +func GetBadge(ctx context.Context, slug string) (*Badge, error) { + badge := new(Badge) + has, err := db.GetEngine(ctx).Where("slug=?", slug).Get(badge) + if !has { + return nil, err + } + return badge, err +} + +// UpdateBadge updates a badge based on its slug. +func UpdateBadge(ctx context.Context, badge *Badge) error { + _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Update(badge) + return err +} + +// DeleteBadge deletes a badge. +func DeleteBadge(ctx context.Context, badge *Badge) error { + _, err := db.GetEngine(ctx).Delete(badge) + return err +} + +// AddUserBadge adds a badge to a user. +func AddUserBadge(ctx context.Context, u *User, badge *Badge) error { + return AddUserBadges(ctx, u, []*Badge{badge}) +} + +// AddUserBadges adds badges to a user. +func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error { + return db.WithTx(ctx, func(ctx context.Context) error { + for _, badge := range badges { + if err := db.Insert(ctx, &UserBadge{ + BadgeSlug: badge.Slug, + UserID: u.ID, + }); err != nil { + return err + } + } + return nil + }) +} + +// RemoveUserBadge removes a badge from a user. +func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error { + return RemoveUserBadges(ctx, u, []*Badge{badge}) +} + +// RemoveUserBadges removes badges from a user. +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).Where("user_id=? AND badge_slug=?", u.ID, badge.Slug).Delete(&UserBadge{}); err != nil { + return err + } + } + return nil + }) +} + +// RemoveAllUserBadges removes all badges from a user. +func RemoveAllUserBadges(ctx context.Context, u *User) error { + _, err := db.GetEngine(ctx).Where("user_id=?", u.ID).Delete(&UserBadge{}) + return err +} diff --git a/modules/structs/user.go b/modules/structs/user.go index 0df67894b0397..019be8aff7d1e 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -108,3 +108,25 @@ type UpdateUserAvatarOption struct { // image must be base64 encoded Image string `json:"image" binding:"Required"` } + +// Badge represents a user badge +// swagger:model +type Badge struct { + Slug string `json:"slug"` + Description string `json:"description"` + ImageURL string `json:"image_url"` +} + +// UserBadge represents a user badge +// swagger:model +type UserBadge struct { + ID int64 `json:"id"` + BadgeSlug int64 `json:"badge_slug"` + UserID int64 `json:"user_id"` +} + +// UserBadgeOption options for link between users and badges +// swagger:model +type UserBadgeOption struct { + BadgeSlugs []string `json:"badge_slugs" binding:"Required"` +} diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go new file mode 100644 index 0000000000000..f835e6770625d --- /dev/null +++ b/routers/api/v1/admin/user_badge.go @@ -0,0 +1,95 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "net/http" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" +) + +// AddUserBadges add badges to a user +func AddUserBadges(ctx *context.APIContext) { + // swagger:operation POST /admin/users/{username}/badges admin adminAddUserBadges + // --- + // summary: Add a badge to a user + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: username + // in: path + // description: username of user + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UserBadgeOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + + form := web.GetForm(ctx).(*api.UserBadgeOption) + badges := prepareBadgesForReplaceOrAdd(ctx, *form) + + if err := user_model.AddUserBadges(ctx, ctx.ContextUser, badges); err != nil { + ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err) + return + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteUserBadges delete a badge from a user +func DeleteUserBadges(ctx *context.APIContext) { + // swagger:operation DELETE /admin/users/{username}/badges admin adminDeleteUserBadges + // --- + // summary: Remove a badge from a user + // produces: + // - application/json + // parameters: + // - name: username + // in: path + // description: username of user + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/UserBadgeOption" + // responses: + // "204": + // "$ref": "#/responses/empty" + // "403": + // "$ref": "#/responses/forbidden" + // "422": + // "$ref": "#/responses/validationError" + + form := web.GetForm(ctx).(*api.UserBadgeOption) + badges := prepareBadgesForReplaceOrAdd(ctx, *form) + + if err := user_model.RemoveUserBadges(ctx, ctx.ContextUser, badges); err != nil { + ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err) + return + } + + ctx.Status(http.StatusNoContent) +} + +func prepareBadgesForReplaceOrAdd(ctx *context.APIContext, form api.UserBadgeOption) []*user_model.Badge { + badges := make([]*user_model.Badge, len(form.BadgeSlugs)) + for i, badge := range form.BadgeSlugs { + badges[i] = &user_model.Badge{ + Slug: badge, + } + } + return badges +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 757b406799613..278a6c34bcc18 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1394,6 +1394,8 @@ func Routes() *web.Route { m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser) + m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges) + m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges) }, context_service.UserAssignmentAPI()) }) m.Group("/emails", func() { From 0cc7302e102466648aeacc2da8c3962efd103fc4 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sat, 9 Sep 2023 03:42:37 +0000 Subject: [PATCH 02/22] make generate-swagger --- templates/swagger/v1_json.tmpl | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 03beca3f73de5..0b370d1f3b136 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -669,6 +669,82 @@ } } }, + "/admin/users/{username}/badges": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Add a badge to a user", + "operationId": "adminAddUserBadges", + "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "username", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UserBadgeOption" + } + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Remove a badge from a user", + "operationId": "adminDeleteUserBadges", + "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "username", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/UserBadgeOption" + } + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/admin/users/{username}/keys": { "post": { "consumes": [ From 41fb7d537dd185ceb0125150badb7ca8acad7a60 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sat, 9 Sep 2023 04:07:35 +0000 Subject: [PATCH 03/22] fix swagger --- modules/structs/user.go | 3 ++- routers/api/v1/swagger/options.go | 3 +++ templates/swagger/v1_json.tmpl | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/structs/user.go b/modules/structs/user.go index 019be8aff7d1e..4f94130889324 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package structs @@ -126,7 +127,7 @@ type UserBadge struct { } // UserBadgeOption options for link between users and badges -// swagger:model type UserBadgeOption struct { + // example: ["badge1","badge2"] BadgeSlugs []string `json:"badge_slugs" binding:"Required"` } diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 6f7859df62ed4..471e7d9c4e3f6 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -190,4 +190,7 @@ type swaggerParameterBodies struct { // in:body CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption + + // in:body + UserBadgeOption api.UserBadgeOption } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0b370d1f3b136..4963c512a5527 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -22320,6 +22320,24 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "UserBadgeOption": { + "description": "UserBadgeOption options for link between users and badges", + "type": "object", + "properties": { + "badge_slugs": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "BadgeSlugs", + "example": [ + "badge1", + "badge2" + ] + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "UserHeatmapData": { "description": "UserHeatmapData represents the data needed to create a heatmap", "type": "object", @@ -23514,7 +23532,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreateOrUpdateSecretOption" + "$ref": "#/definitions/UserBadgeOption" } }, "redirect": { From b8593e99a128d82b68528acf29eedd7410614d34 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Wed, 13 Sep 2023 15:52:00 +0000 Subject: [PATCH 04/22] add pk to table --- models/migrations/v1_21/v276.go | 13 ++++++------- models/user/badge.go | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index c67fed3e422cd..5fb229fe2dbdc 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -10,7 +10,9 @@ import ( ) type BadgeUnique struct { - Slug string `xorm:"UNIQUE"` + Slug string `xorm:"pk UNIQUE"` + Description string + ImageURL string } func (BadgeUnique) TableName() string { @@ -38,8 +40,9 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { if err != nil { return err } - // add slug to each user badge + // update user_badge keys to use slug instead of id _, err = sess.Exec("UPDATE `user_badge` SET `badge_slug` = (SELECT `slug` FROM `badge` WHERE `badge`.`id` = `user_badge`.`badge_id`)") + if err != nil { return err } @@ -47,11 +50,7 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { if err := base.DropTableColumns(sess, "user_badge", "badge_id"); err != nil { return err } - if err := base.DropTableColumns(sess, "badge", "id"); err != nil { - return err - } - err = sess.Sync(new(BadgeUnique)) - if err != nil { + if err := base.RecreateTables(new(BadgeUnique))(sess.Engine()); err != nil { return err } return sess.Commit() diff --git a/models/user/badge.go b/models/user/badge.go index 596ee3a44d28f..867de8d5a18ec 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -11,7 +11,7 @@ import ( // Badge represents a user badge type Badge struct { - Slug string `xorm:"UNIQUE"` + Slug string `xorm:"pk UNIQUE"` Description string ImageURL string } From 07d9045c19588dade642fd29708272e423c1498d Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Wed, 13 Sep 2023 16:24:10 +0000 Subject: [PATCH 05/22] update migration session --- models/migrations/v1_21/v276.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index 5fb229fe2dbdc..fb978509cd090 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -50,8 +50,6 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { if err := base.DropTableColumns(sess, "user_badge", "badge_id"); err != nil { return err } - if err := base.RecreateTables(new(BadgeUnique))(sess.Engine()); err != nil { - return err - } - return sess.Commit() + sess.Commit() + return base.RecreateTables(new(BadgeUnique))(x) } From fc957154fe63cc29bba2b26d61271016638df11f Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 15:38:54 +0000 Subject: [PATCH 06/22] keep badge ID --- models/migrations/v1_21/v276.go | 29 +++++++++-------------------- models/user/badge.go | 25 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index fb978509cd090..2c8d8953e9c4d 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -4,15 +4,11 @@ package v1_21 //nolint import ( - "code.gitea.io/gitea/models/migrations/base" - "xorm.io/xorm" ) type BadgeUnique struct { - Slug string `xorm:"pk UNIQUE"` - Description string - ImageURL string + Slug string `xorm:"UNIQUE"` } func (BadgeUnique) TableName() string { @@ -23,33 +19,26 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { type Badge struct { Slug string } - type UserBadge struct { - BadgeSlug string `xorm:"INDEX"` - } - err := x.Sync(new(Badge), new(UserBadge)) - if err != nil { - return err - } + sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - // add slug to each badge - _, err = sess.Exec("UPDATE `badge` SET `slug` = `id`") + err := sess.Sync(new(Badge)) if err != nil { return err } - // update user_badge keys to use slug instead of id - _, err = sess.Exec("UPDATE `user_badge` SET `badge_slug` = (SELECT `slug` FROM `badge` WHERE `badge`.`id` = `user_badge`.`badge_id`)") + _, err = sess.Exec("UPDATE `badge` SET `slug` = `id`") if err != nil { return err } - // drop badge_id columns from tables - if err := base.DropTableColumns(sess, "user_badge", "badge_id"); err != nil { + + err := sess.Sync(new(BadgeUnique)) + if err != nil { return err } - sess.Commit() - return base.RecreateTables(new(BadgeUnique))(x) + + return sess.Commit() } diff --git a/models/user/badge.go b/models/user/badge.go index 867de8d5a18ec..708d329125388 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -11,16 +11,17 @@ import ( // Badge represents a user badge type Badge struct { - Slug string `xorm:"pk UNIQUE"` + ID int64 `xorm:"pk autoincr"` + Slug string `xorm:"UNIQUE"` Description string ImageURL string } // UserBadge represents a user badge type UserBadge struct { //nolint:revive - ID int64 `xorm:"pk autoincr"` - BadgeSlug string - UserID int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + BadgeID int64 + UserID int64 `xorm:"INDEX"` } func init() { @@ -64,7 +65,7 @@ func UpdateBadge(ctx context.Context, badge *Badge) error { // DeleteBadge deletes a badge. func DeleteBadge(ctx context.Context, badge *Badge) error { - _, err := db.GetEngine(ctx).Delete(badge) + _, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge) return err } @@ -77,9 +78,14 @@ func AddUserBadge(ctx context.Context, u *User, badge *Badge) error { 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=?", slug).Get(badge) + if !has || err != nil { + return err + } if err := db.Insert(ctx, &UserBadge{ - BadgeSlug: badge.Slug, - UserID: u.ID, + BadgeID: badge.ID, + UserID: u.ID, }); err != nil { return err } @@ -97,7 +103,10 @@ 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).Where("user_id=? AND badge_slug=?", u.ID, badge.Slug).Delete(&UserBadge{}); err != nil { + 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 { return err } } From 88eb680555b7d5b8019a000b7db46df2cf8fab56 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 15:56:29 +0000 Subject: [PATCH 07/22] fix variable --- models/user/badge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/badge.go b/models/user/badge.go index 708d329125388..064a58090e715 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -79,7 +79,7 @@ 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=?", slug).Get(badge) + has, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Get(badge) if !has || err != nil { return err } From a9a415d5dc25bb4bc4ecf1bc386bb0e5dbc6ee56 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 15:58:01 +0000 Subject: [PATCH 08/22] no new var --- models/migrations/v1_21/v276.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index 2c8d8953e9c4d..d9a916db574ad 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -35,7 +35,7 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { return err } - err := sess.Sync(new(BadgeUnique)) + err = sess.Sync(new(BadgeUnique)) if err != nil { return err } From eb4b86b893104966399e9cfa0f4b54950eda14bd Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 16:40:37 +0000 Subject: [PATCH 09/22] add badge_id back into swagger model --- modules/structs/user.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/structs/user.go b/modules/structs/user.go index 4f94130889324..21ecc1479e2b5 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -113,6 +113,7 @@ type UpdateUserAvatarOption struct { // Badge represents a user badge // swagger:model type Badge struct { + ID int64 `json:"id"` Slug string `json:"slug"` Description string `json:"description"` ImageURL string `json:"image_url"` @@ -121,9 +122,9 @@ type Badge struct { // UserBadge represents a user badge // swagger:model type UserBadge struct { - ID int64 `json:"id"` - BadgeSlug int64 `json:"badge_slug"` - UserID int64 `json:"user_id"` + ID int64 `json:"id"` + BadgeID int64 `json:"badge_id"` + UserID int64 `json:"user_id"` } // UserBadgeOption options for link between users and badges From 200f9835cc0b213236142108b6169816aaaaecfc Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 16:41:23 +0000 Subject: [PATCH 10/22] fix sql --- models/user/badge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/user/badge.go b/models/user/badge.go index 064a58090e715..5a71d80eeaaf4 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -33,7 +33,7 @@ func init() { func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) { sess := db.GetEngine(ctx). Select("`badge`.*"). - Join("INNER", "user_badge", "`user_badge`.badge_slug=badge.slug"). + Join("INNER", "user_badge", "`user_badge`.badge_id=badge.id"). Where("user_badge.user_id=?", u.ID) badges := make([]*Badge, 0, 8) From cb0b1ff979a7da82576c140a788eee7feacd88e4 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 17:09:10 +0000 Subject: [PATCH 11/22] GET endpoint --- modules/structs/user.go | 7 +++ routers/api/v1/admin/user_badge.go | 30 ++++++++++++ routers/api/v1/swagger/options.go | 3 ++ templates/swagger/v1_json.tmpl | 79 +++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/modules/structs/user.go b/modules/structs/user.go index 21ecc1479e2b5..c43558be5d2bf 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -132,3 +132,10 @@ type UserBadgeOption struct { // example: ["badge1","badge2"] BadgeSlugs []string `json:"badge_slugs" binding:"Required"` } + +// BadgeList +// swagger:response BadgeList +type BadgeList struct { + // in:body + Body []Badge `json:"body"` +} diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go index f835e6770625d..1febf81970539 100644 --- a/routers/api/v1/admin/user_badge.go +++ b/routers/api/v1/admin/user_badge.go @@ -12,6 +12,36 @@ import ( "code.gitea.io/gitea/modules/web" ) +// ListUserBadges lists all badges belonging to a user +func ListUserBadges(ctx *context.APIContext) { + // swagger:operation GET /users/{username}/badges admin adminListUserBadges + // --- + // summary: List a user's badges + // produces: + // - application/json + // parameters: + // - name: username + // in: path + // description: username of user + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/BadgeList" + // "404": + // "$ref": "#/responses/notFound" + + badges, maxResults, err := user_model.GetUserBadges(ctx, ctx.ContextUser) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserBadges", err) + return + } + + ctx.SetTotalCountHeader(maxResults) + ctx.JSON(http.StatusOK, &badges) + +} + // AddUserBadges add badges to a user func AddUserBadges(ctx *context.APIContext) { // swagger:operation POST /admin/users/{username}/badges admin adminAddUserBadges diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 471e7d9c4e3f6..e03862d7b9aa6 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -193,4 +193,7 @@ type swaggerParameterBodies struct { // in:body UserBadgeOption api.UserBadgeOption + + // in:body + UserBadgeList api.BadgeList } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 6895ca55534ff..5ee998474aba0 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -15966,6 +15966,35 @@ } } }, + "/users/{username}/badges": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List a user's badges", + "operationId": "adminListUserBadges", + "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/BadgeList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/users/{username}/followers": { "get": { "produces": [ @@ -16779,6 +16808,45 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "Badge": { + "description": "Badge represents a user badge", + "type": "object", + "properties": { + "description": { + "type": "string", + "x-go-name": "Description" + }, + "id": { + "type": "integer", + "format": "int64", + "x-go-name": "ID" + }, + "image_url": { + "type": "string", + "x-go-name": "ImageURL" + }, + "slug": { + "type": "string", + "x-go-name": "Slug" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "BadgeList": { + "description": "BadgeList", + "type": "object", + "properties": { + "body": { + "description": "in:body", + "type": "array", + "items": { + "$ref": "#/definitions/Badge" + }, + "x-go-name": "Body" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "Branch": { "description": "Branch represents a repository branch", "type": "object", @@ -23077,6 +23145,15 @@ } } }, + "BadgeList": { + "description": "BadgeList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Badge" + } + } + }, "Branch": { "description": "Branch", "schema": { @@ -23982,7 +24059,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/UserBadgeOption" + "$ref": "#/definitions/BadgeList" } }, "redirect": { From 9417f3fc1f2dab4e2c5d8c1cdaa1ba2b76063d49 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 14 Sep 2023 16:47:40 -0400 Subject: [PATCH 12/22] Update user_badge.go Fmt? --- routers/api/v1/admin/user_badge.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go index 1febf81970539..009dd60c2114f 100644 --- a/routers/api/v1/admin/user_badge.go +++ b/routers/api/v1/admin/user_badge.go @@ -39,7 +39,6 @@ func ListUserBadges(ctx *context.APIContext) { ctx.SetTotalCountHeader(maxResults) ctx.JSON(http.StatusOK, &badges) - } // AddUserBadges add badges to a user From 3239ebb8e1e6cbdef47a35f51d31cca59ba09296 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 15 Sep 2023 10:09:39 -0400 Subject: [PATCH 13/22] Update models/user/badge.go Co-authored-by: Lunny Xiao --- models/user/badge.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/user/badge.go b/models/user/badge.go index 5a71d80eeaaf4..1196a9cc92af9 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -80,8 +80,10 @@ func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error { for _, badge := range badges { // hydrate badge and check if it exists has, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Get(badge) - if !has || err != nil { + if err != nil { return err + } else if !has { + return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug) } if err := db.Insert(ctx, &UserBadge{ BadgeID: badge.ID, From 2dce75b17b9abd177b40ef68019aaca3dcb07de4 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 15 Sep 2023 14:29:34 +0000 Subject: [PATCH 14/22] fix missing import --- models/migrations/v1_21/v276.go | 9 +++++---- models/user/badge.go | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go index d9a916db574ad..4a76eee070a27 100644 --- a/models/migrations/v1_21/v276.go +++ b/models/migrations/v1_21/v276.go @@ -20,15 +20,16 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { Slug string } + err := x.Sync(new(Badge)) + if err != nil { + return err + } + sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { return err } - err := sess.Sync(new(Badge)) - if err != nil { - return err - } _, err = sess.Exec("UPDATE `badge` SET `slug` = `id`") if err != nil { diff --git a/models/user/badge.go b/models/user/badge.go index 1196a9cc92af9..3ff3530a369a5 100644 --- a/models/user/badge.go +++ b/models/user/badge.go @@ -5,6 +5,7 @@ package user import ( "context" + "fmt" "code.gitea.io/gitea/models/db" ) @@ -83,7 +84,7 @@ func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error { if err != nil { return err } else if !has { - return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug) + return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug) } if err := db.Insert(ctx, &UserBadge{ BadgeID: badge.ID, From 03462cf718a7ea2fa9d88febfb55f3bdd95417f1 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 15 Sep 2023 14:38:01 +0000 Subject: [PATCH 15/22] add list route --- routers/api/v1/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 007f185e830f3..f50f779ea81f7 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1495,6 +1495,7 @@ func Routes() *web.Route { m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser) + m.Get("/badges", admin.ListUserBadges) m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges) m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges) }, context_service.UserAssignmentAPI()) From bf8ae057fe6e66dcd32d295bcc68454ba53b727d Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 05:30:42 +0000 Subject: [PATCH 16/22] move to 1.22 --- models/migrations/{v1_21 => v1_22}/v276.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename models/migrations/{v1_21 => v1_22}/v276.go (100%) diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_22/v276.go similarity index 100% rename from models/migrations/v1_21/v276.go rename to models/migrations/v1_22/v276.go From 80e69d95242ed700430f9105da0db9739633b6ee Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 05:44:40 +0000 Subject: [PATCH 17/22] add tests --- models/migrations/v1_22/{v276.go => v287.go} | 2 +- models/migrations/v1_22/v287_test.go | 40 ++++++++++++++++++++ routers/api/v1/admin/user_badge.go | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) rename models/migrations/v1_22/{v276.go => v287.go} (96%) create mode 100644 models/migrations/v1_22/v287_test.go diff --git a/models/migrations/v1_22/v276.go b/models/migrations/v1_22/v287.go similarity index 96% rename from models/migrations/v1_22/v276.go rename to models/migrations/v1_22/v287.go index 4a76eee070a27..4e4cdc4f61bf6 100644 --- a/models/migrations/v1_22/v276.go +++ b/models/migrations/v1_22/v287.go @@ -1,7 +1,7 @@ // Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package v1_21 //nolint +package v1_22 //nolint import ( "xorm.io/xorm" diff --git a/models/migrations/v1_22/v287_test.go b/models/migrations/v1_22/v287_test.go new file mode 100644 index 0000000000000..d918041a6a870 --- /dev/null +++ b/models/migrations/v1_22/v287_test.go @@ -0,0 +1,40 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func Test_UpdateBadgeColName(t *testing.T) { + + type Badge struct { + ID int64 `xorm:"pk autoincr"` + ID int64 `xorm:"pk autoincr"` + Slug string `xorm:"UNIQUE"` + Description string + ImageURL string + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(BadgeUnique), new(Badge)) + defer deferable() + if x == nil || t.Failed() { + return + } + + // TODO: insert example badges + + if err := UseSlugInsteadOfIDForBadges(x); err != nil { + assert.NoError(t, err) + return + } + + // TODO: check if badges have been updated + +} diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go index 009dd60c2114f..e92324cfc931e 100644 --- a/routers/api/v1/admin/user_badge.go +++ b/routers/api/v1/admin/user_badge.go @@ -7,9 +7,9 @@ import ( "net/http" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/context" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/context" ) // ListUserBadges lists all badges belonging to a user From 4caef422b83cf68aa534b45896eb0000264dff0a Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 05:47:10 +0000 Subject: [PATCH 18/22] fix struct --- models/migrations/v1_22/v287_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/models/migrations/v1_22/v287_test.go b/models/migrations/v1_22/v287_test.go index d918041a6a870..8f3ab6d01a091 100644 --- a/models/migrations/v1_22/v287_test.go +++ b/models/migrations/v1_22/v287_test.go @@ -12,9 +12,7 @@ import ( ) func Test_UpdateBadgeColName(t *testing.T) { - type Badge struct { - ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"` Slug string `xorm:"UNIQUE"` Description string @@ -36,5 +34,4 @@ func Test_UpdateBadgeColName(t *testing.T) { } // TODO: check if badges have been updated - } From 53dfaa87317dbba41117fd6e6969a5bcb9dd0c16 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 06:01:48 +0000 Subject: [PATCH 19/22] add tests --- models/migrations/v1_22/v287.go | 1 + models/migrations/v1_22/v287_test.go | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go index 4e4cdc4f61bf6..5936afeea5eb6 100644 --- a/models/migrations/v1_22/v287.go +++ b/models/migrations/v1_22/v287.go @@ -8,6 +8,7 @@ import ( ) type BadgeUnique struct { + ID int64 `xorm:"pk autoincr"` Slug string `xorm:"UNIQUE"` } diff --git a/models/migrations/v1_22/v287_test.go b/models/migrations/v1_22/v287_test.go index 8f3ab6d01a091..19c7ae3b91167 100644 --- a/models/migrations/v1_22/v287_test.go +++ b/models/migrations/v1_22/v287_test.go @@ -4,6 +4,7 @@ package v1_22 //nolint import ( + "fmt" "testing" "code.gitea.io/gitea/models/migrations/base" @@ -13,8 +14,7 @@ import ( func Test_UpdateBadgeColName(t *testing.T) { type Badge struct { - ID int64 `xorm:"pk autoincr"` - Slug string `xorm:"UNIQUE"` + ID int64 `xorm:"pk autoincr"` Description string ImageURL string } @@ -26,12 +26,32 @@ func Test_UpdateBadgeColName(t *testing.T) { return } - // TODO: insert example badges + oldBadges := []Badge{ + {ID: 1, Description: "Test Badge 1", ImageURL: "https://example.com/badge1.png"}, + {ID: 2, Description: "Test Badge 2", ImageURL: "https://example.com/badge2.png"}, + {ID: 3, Description: "Test Badge 3", ImageURL: "https://example.com/badge3.png"}, + } + + for _, badge := range oldBadges { + _, err := x.Insert(&badge) + assert.NoError(t, err) + } if err := UseSlugInsteadOfIDForBadges(x); err != nil { assert.NoError(t, err) return } + got := []BadgeUnique{} + if err := x.Table("badge").Asc("id").Find(&got); !assert.NoError(t, err) { + return + } + + for i, e := range oldBadges { + got := got[i] + assert.Equal(t, e.ID, got.ID) + assert.Equal(t, fmt.Sprintf("%d", e.ID), got.Slug) + } + // TODO: check if badges have been updated } From d749ffab2088191dcbb497a6e8796a648447447c Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 18:14:47 -0500 Subject: [PATCH 20/22] Update v287.go Co-authored-by: Lunny Xiao --- models/migrations/v1_22/v287.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go index 5936afeea5eb6..c8b1593286945 100644 --- a/models/migrations/v1_22/v287.go +++ b/models/migrations/v1_22/v287.go @@ -32,7 +32,7 @@ func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error { return err } - _, err = sess.Exec("UPDATE `badge` SET `slug` = `id`") + _, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL") if err != nil { return err } From 3d0547a74c6b6b04e8c4b5426f6e85212b51c4c4 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 29 Feb 2024 18:14:58 -0500 Subject: [PATCH 21/22] Update user_badge.go Co-authored-by: Lunny Xiao --- routers/api/v1/admin/user_badge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/admin/user_badge.go b/routers/api/v1/admin/user_badge.go index e92324cfc931e..bacd1f809bfac 100644 --- a/routers/api/v1/admin/user_badge.go +++ b/routers/api/v1/admin/user_badge.go @@ -14,7 +14,7 @@ import ( // ListUserBadges lists all badges belonging to a user func ListUserBadges(ctx *context.APIContext) { - // swagger:operation GET /users/{username}/badges admin adminListUserBadges + // swagger:operation GET /admin/users/{username}/badges admin adminListUserBadges // --- // summary: List a user's badges // produces: From 56fd7e7c44366d47c4adcd7742e13af84fde1a7f Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 1 Mar 2024 07:52:19 +0000 Subject: [PATCH 22/22] make generate-swagger --- templates/swagger/v1_json.tmpl | 56 ++++++++++++++++------------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 8f19932847718..d4c5d9a7ee672 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -690,6 +690,33 @@ } }, "/admin/users/{username}/badges": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List a user's badges", + "operationId": "adminListUserBadges", + "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/BadgeList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + }, "post": { "consumes": [ "application/json" @@ -16257,35 +16284,6 @@ } } }, - "/users/{username}/badges": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "admin" - ], - "summary": "List a user's badges", - "operationId": "adminListUserBadges", - "parameters": [ - { - "type": "string", - "description": "username of user", - "name": "username", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "$ref": "#/responses/BadgeList" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - } - }, "/users/{username}/followers": { "get": { "produces": [