Skip to content

Commit 1ecc4bb

Browse files
committed
Merge branch 'Badge' of github.com:HenriquerPimentel/gitea into HenriquerPimentel-Badge
2 parents 4fe1066 + 9a80816 commit 1ecc4bb

File tree

20 files changed

+1157
-16
lines changed

20 files changed

+1157
-16
lines changed

models/fixtures/badge.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-
2+
id: 1
3+
slug: badge1
4+
description: just a test badge
5+
image_url: badge1.png

models/migrations/migrations.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,10 @@ func prepareMigrationTasks() []*migration {
391391
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
392392
newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
393393

394-
// Gitea 1.24.0 ends at database version 321
394+
// Gitea 1.24.0 ends at database version 321 (database version 322)
395395
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
396396
newMigration(322, "Extend comment tree_path length limit", v1_25.ExtendCommentTreePathLength),
397+
newMigration(323, "Add unique constraint for user badge", v1_25.AddUniqueIndexForUserBadge),
397398
}
398399
return preparedMigrations
399400
}

models/migrations/v1_25/v323.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_25
5+
6+
import (
7+
"fmt"
8+
9+
"xorm.io/xorm"
10+
"xorm.io/xorm/schemas"
11+
)
12+
13+
type UserBadge struct { //revive:disable-line:exported
14+
ID int64 `xorm:"pk autoincr"`
15+
BadgeID int64
16+
UserID int64
17+
}
18+
19+
// TableIndices implements xorm's TableIndices interface
20+
func (n *UserBadge) TableIndices() []*schemas.Index {
21+
indices := make([]*schemas.Index, 0, 1)
22+
ubUnique := schemas.NewIndex("unique_user_badge", schemas.UniqueType)
23+
ubUnique.AddColumn("user_id", "badge_id")
24+
indices = append(indices, ubUnique)
25+
return indices
26+
}
27+
28+
// AddUniqueIndexForUserBadge adds a compound unique indexes for user badge table
29+
// and it replaces an old index on user_id
30+
func AddUniqueIndexForUserBadge(x *xorm.Engine) error {
31+
// remove possible duplicated records in table user_badge
32+
type result struct {
33+
UserID int64
34+
BadgeID int64
35+
Cnt int
36+
}
37+
var results []result
38+
if err := x.Select("user_id, badge_id, count(*) as cnt").
39+
Table("user_badge").
40+
GroupBy("user_id, badge_id").
41+
Having("count(*) > 1").
42+
Find(&results); err != nil {
43+
return err
44+
}
45+
for _, r := range results {
46+
if x.Dialect().URI().DBType == schemas.MSSQL {
47+
if _, err := x.Exec(fmt.Sprintf("delete from user_badge where id in (SELECT top %d id FROM user_badge WHERE user_id = ? and badge_id = ?)", r.Cnt-1), r.UserID, r.BadgeID); err != nil {
48+
return err
49+
}
50+
} else {
51+
var ids []int64
52+
if err := x.SQL("SELECT id FROM user_badge WHERE user_id = ? and badge_id = ? limit ?", r.UserID, r.BadgeID, r.Cnt-1).Find(&ids); err != nil {
53+
return err
54+
}
55+
if _, err := x.Table("user_badge").In("id", ids).Delete(); err != nil {
56+
return err
57+
}
58+
}
59+
}
60+
61+
return x.Sync(new(UserBadge))
62+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_25
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/migrations/base"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
type UserBadgeBefore struct {
15+
ID int64 `xorm:"pk autoincr"`
16+
BadgeID int64
17+
UserID int64 `xorm:"INDEX"`
18+
}
19+
20+
func (UserBadgeBefore) TableName() string {
21+
return "user_badge"
22+
}
23+
24+
func Test_AddUniqueIndexForUserBadge(t *testing.T) {
25+
x, deferable := base.PrepareTestEnv(t, 0, new(UserBadgeBefore))
26+
defer deferable()
27+
if x == nil || t.Failed() {
28+
return
29+
}
30+
31+
testData := []*UserBadgeBefore{
32+
{UserID: 1, BadgeID: 1},
33+
{UserID: 1, BadgeID: 1}, // duplicate
34+
{UserID: 2, BadgeID: 1},
35+
{UserID: 1, BadgeID: 2},
36+
{UserID: 3, BadgeID: 3},
37+
{UserID: 3, BadgeID: 3}, // duplicate
38+
}
39+
40+
for _, data := range testData {
41+
_, err := x.Insert(data)
42+
assert.NoError(t, err)
43+
}
44+
45+
// check that we have duplicates
46+
count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
47+
assert.NoError(t, err)
48+
assert.Equal(t, int64(2), count)
49+
50+
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
51+
assert.NoError(t, err)
52+
assert.Equal(t, int64(2), count)
53+
54+
totalCount, err := x.Count(&UserBadgeBefore{})
55+
assert.NoError(t, err)
56+
assert.Equal(t, int64(6), totalCount)
57+
58+
// run the migration
59+
if err := AddUniqueIndexForUserBadge(x); err != nil {
60+
assert.NoError(t, err)
61+
return
62+
}
63+
64+
// verify the duplicates were removed
65+
count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{})
66+
assert.NoError(t, err)
67+
assert.Equal(t, int64(1), count)
68+
69+
count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{})
70+
assert.NoError(t, err)
71+
assert.Equal(t, int64(1), count)
72+
73+
// check total count
74+
totalCount, err = x.Count(&UserBadgeBefore{})
75+
assert.NoError(t, err)
76+
assert.Equal(t, int64(4), totalCount)
77+
78+
// fail to insert a duplicate
79+
_, err = x.Insert(&UserBadge{UserID: 1, BadgeID: 1})
80+
assert.Error(t, err)
81+
82+
// succeed adding a non-duplicate
83+
_, err = x.Insert(&UserBadge{UserID: 4, BadgeID: 1})
84+
assert.NoError(t, err)
85+
}

0 commit comments

Comments
 (0)