Skip to content

Commit 6b055dd

Browse files
committed
Add release notification and fix repository transfer/commit notification
1 parent 40dec17 commit 6b055dd

File tree

12 files changed

+418
-87
lines changed

12 files changed

+418
-87
lines changed

models/activities/notification.go

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111

1212
"code.gitea.io/gitea/models/db"
1313
issues_model "code.gitea.io/gitea/models/issues"
14-
"code.gitea.io/gitea/models/organization"
1514
repo_model "code.gitea.io/gitea/models/repo"
1615
user_model "code.gitea.io/gitea/models/user"
1716
"code.gitea.io/gitea/modules/setting"
@@ -46,6 +45,8 @@ const (
4645
NotificationSourceCommit
4746
// NotificationSourceRepository is a notification for a repository
4847
NotificationSourceRepository
48+
// NotificationSourceRelease is a notification for a release
49+
NotificationSourceRelease
4950
)
5051

5152
// Notification represents a notification
@@ -60,13 +61,15 @@ type Notification struct {
6061
IssueID int64 `xorm:"NOT NULL"`
6162
CommitID string
6263
CommentID int64
64+
ReleaseID int64
6365

6466
UpdatedBy int64 `xorm:"NOT NULL"`
6567

6668
Issue *issues_model.Issue `xorm:"-"`
6769
Repository *repo_model.Repository `xorm:"-"`
6870
Comment *issues_model.Comment `xorm:"-"`
6971
User *user_model.User `xorm:"-"`
72+
Release *repo_model.Release `xorm:"-"`
7073

7174
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
7275
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
@@ -104,6 +107,10 @@ func (n *Notification) TableIndices() []*schemas.Index {
104107
commitIDIndex.AddColumn("commit_id")
105108
indices = append(indices, commitIDIndex)
106109

110+
releaseIDIndex := schemas.NewIndex("idx_notification_release_id", schemas.IndexType)
111+
releaseIDIndex.AddColumn("release_id")
112+
indices = append(indices, releaseIDIndex)
113+
107114
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
108115
updatedByIndex.AddColumn("updated_by")
109116
indices = append(indices, updatedByIndex)
@@ -116,36 +123,53 @@ func init() {
116123
}
117124

118125
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
119-
func CreateRepoTransferNotification(ctx context.Context, doer, newOwner *user_model.User, repo *repo_model.Repository) error {
120-
return db.WithTx(ctx, func(ctx context.Context) error {
121-
var notify []*Notification
122-
123-
if newOwner.IsOrganization() {
124-
users, err := organization.GetUsersWhoCanCreateOrgRepo(ctx, newOwner.ID)
125-
if err != nil || len(users) == 0 {
126-
return err
127-
}
128-
for i := range users {
129-
notify = append(notify, &Notification{
130-
UserID: i,
131-
RepoID: repo.ID,
132-
Status: NotificationStatusUnread,
133-
UpdatedBy: doer.ID,
134-
Source: NotificationSourceRepository,
135-
})
136-
}
137-
} else {
138-
notify = []*Notification{{
139-
UserID: newOwner.ID,
140-
RepoID: repo.ID,
141-
Status: NotificationStatusUnread,
142-
UpdatedBy: doer.ID,
143-
Source: NotificationSourceRepository,
144-
}}
145-
}
126+
func CreateRepoTransferNotification(ctx context.Context, doerID, repoID, receiverID int64) error {
127+
notify := &Notification{
128+
UserID: receiverID,
129+
RepoID: repoID,
130+
Status: NotificationStatusUnread,
131+
UpdatedBy: doerID,
132+
Source: NotificationSourceRepository,
133+
}
134+
return db.Insert(ctx, notify)
135+
}
146136

147-
return db.Insert(ctx, notify)
148-
})
137+
func CreateCommitNotifications(ctx context.Context, doerID, repoID int64, commitID string, receiverID int64) error {
138+
notification := &Notification{
139+
Source: NotificationSourceCommit,
140+
UserID: receiverID,
141+
RepoID: repoID,
142+
CommitID: commitID,
143+
Status: NotificationStatusUnread,
144+
UpdatedBy: doerID,
145+
}
146+
147+
return db.Insert(ctx, notification)
148+
}
149+
150+
func CreateOrUpdateReleaseNotifications(ctx context.Context, doerID, releaseID, receiverID int64) error {
151+
notification := new(Notification)
152+
if _, err := db.GetEngine(ctx).
153+
Where("user_id = ?", receiverID).
154+
And("release_id = ?", releaseID).
155+
Get(notification); err != nil {
156+
return err
157+
}
158+
if notification.ID > 0 {
159+
notification.Status = NotificationStatusUnread
160+
notification.UpdatedBy = doerID
161+
_, err := db.GetEngine(ctx).ID(notification.ID).Cols("status", "updated_by").Update(notification)
162+
return err
163+
}
164+
165+
notification = &Notification{
166+
Source: NotificationSourceRelease,
167+
UserID: receiverID,
168+
Status: NotificationStatusUnread,
169+
ReleaseID: releaseID,
170+
UpdatedBy: doerID,
171+
}
172+
return db.Insert(ctx, notification)
149173
}
150174

151175
func createIssueNotification(ctx context.Context, userID int64, issue *issues_model.Issue, commentID, updatedByID int64) error {
@@ -213,6 +237,9 @@ func (n *Notification) LoadAttributes(ctx context.Context) (err error) {
213237
if err = n.loadComment(ctx); err != nil {
214238
return err
215239
}
240+
if err = n.loadRelease(ctx); err != nil {
241+
return err
242+
}
216243
return err
217244
}
218245

@@ -253,6 +280,16 @@ func (n *Notification) loadComment(ctx context.Context) (err error) {
253280
return nil
254281
}
255282

283+
func (n *Notification) loadRelease(ctx context.Context) (err error) {
284+
if n.Release == nil && n.ReleaseID != 0 {
285+
n.Release, err = repo_model.GetReleaseByID(ctx, n.ReleaseID)
286+
if err != nil {
287+
return fmt.Errorf("GetReleaseByID [%d]: %w", n.ReleaseID, err)
288+
}
289+
}
290+
return nil
291+
}
292+
256293
func (n *Notification) loadUser(ctx context.Context) (err error) {
257294
if n.User == nil {
258295
n.User, err = user_model.GetUserByID(ctx, n.UserID)
@@ -285,6 +322,8 @@ func (n *Notification) HTMLURL(ctx context.Context) string {
285322
return n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
286323
case NotificationSourceRepository:
287324
return n.Repository.HTMLURL()
325+
case NotificationSourceRelease:
326+
return n.Release.HTMLURL()
288327
}
289328
return ""
290329
}
@@ -301,6 +340,8 @@ func (n *Notification) Link(ctx context.Context) string {
301340
return n.Repository.Link() + "/commit/" + url.PathEscape(n.CommitID)
302341
case NotificationSourceRepository:
303342
return n.Repository.Link()
343+
case NotificationSourceRelease:
344+
return n.Release.Link()
304345
}
305346
return ""
306347
}
@@ -373,6 +414,17 @@ func SetRepoReadBy(ctx context.Context, userID, repoID int64) error {
373414
return err
374415
}
375416

417+
// SetReleaseReadBy sets issue to be read by given user.
418+
func SetReleaseReadBy(ctx context.Context, releaseID, userID int64) error {
419+
_, err := db.GetEngine(ctx).Where(builder.Eq{
420+
"user_id": userID,
421+
"status": NotificationStatusUnread,
422+
"source": NotificationSourceRelease,
423+
"release_id": releaseID,
424+
}).Cols("status").Update(&Notification{Status: NotificationStatusRead})
425+
return err
426+
}
427+
376428
// SetNotificationStatus change the notification status
377429
func SetNotificationStatus(ctx context.Context, notificationID int64, user *user_model.User, status NotificationStatus) (*Notification, error) {
378430
notification, err := GetNotificationByID(ctx, notificationID)

models/activities/notification_list.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type FindNotificationOptions struct {
2525
UserID int64
2626
RepoID int64
2727
IssueID int64
28+
ReleaseID int64
2829
Status []NotificationStatus
2930
Source []NotificationSource
3031
UpdatedAfterUnix int64
@@ -43,6 +44,9 @@ func (opts FindNotificationOptions) ToConds() builder.Cond {
4344
if opts.IssueID != 0 {
4445
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
4546
}
47+
if opts.ReleaseID != 0 {
48+
cond = cond.And(builder.Eq{"notification.release_id": opts.ReleaseID})
49+
}
4650
if len(opts.Status) > 0 {
4751
if len(opts.Status) == 1 {
4852
cond = cond.And(builder.Eq{"notification.status": opts.Status[0]})
@@ -70,17 +74,9 @@ func (opts FindNotificationOptions) ToOrders() string {
7074
// for each watcher, or updates it if already exists
7175
// receiverID > 0 just send to receiver, else send to all watcher
7276
func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
73-
ctx, committer, err := db.TxContext(ctx)
74-
if err != nil {
75-
return err
76-
}
77-
defer committer.Close()
78-
79-
if err := createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID); err != nil {
80-
return err
81-
}
82-
83-
return committer.Commit()
77+
return db.WithTx(ctx, func(ctx context.Context) error {
78+
return createOrUpdateIssueNotifications(ctx, issueID, commentID, notificationAuthorID, receiverID)
79+
})
8480
}
8581

8682
func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
@@ -186,6 +182,9 @@ func (nl NotificationList) LoadAttributes(ctx context.Context) error {
186182
if _, err := nl.LoadComments(ctx); err != nil {
187183
return err
188184
}
185+
if _, err := nl.LoadReleases(ctx); err != nil {
186+
return err
187+
}
189188
return nil
190189
}
191190

@@ -458,6 +457,31 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
458457
return failures, nil
459458
}
460459

460+
func (nl NotificationList) LoadReleases(ctx context.Context) ([]int, error) {
461+
if len(nl) == 0 {
462+
return []int{}, nil
463+
}
464+
465+
releaseIDs := nl.getPendingCommentIDs()
466+
releases := make(map[int64]*repo_model.Release, len(releaseIDs))
467+
if err := db.GetEngine(ctx).In("id", releaseIDs).Find(&releases); err != nil {
468+
return nil, err
469+
}
470+
471+
failures := []int{}
472+
for i, notification := range nl {
473+
if notification.ReleaseID > 0 && notification.Release == nil && releases[notification.ReleaseID] != nil {
474+
notification.Release = releases[notification.ReleaseID]
475+
if notification.Release == nil {
476+
log.Error("Notification[%d]: ReleaseID[%d] failed to load", notification.ID, notification.ReleaseID)
477+
failures = append(failures, i)
478+
continue
479+
}
480+
}
481+
}
482+
return failures, nil
483+
}
484+
461485
// LoadIssuePullRequests loads all issues' pull requests if possible
462486
func (nl NotificationList) LoadIssuePullRequests(ctx context.Context) error {
463487
issues := make(map[int64]*issues_model.Issue, len(nl))

models/migrations/v1_25/v321.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_25 //nolint
5+
6+
import (
7+
"code.gitea.io/gitea/modules/timeutil"
8+
"xorm.io/xorm"
9+
"xorm.io/xorm/schemas"
10+
)
11+
12+
// NotificationV321 represents a notification
13+
type NotificationV321 struct {
14+
ID int64 `xorm:"pk autoincr"`
15+
UserID int64 `xorm:"NOT NULL"`
16+
RepoID int64 `xorm:"NOT NULL"`
17+
18+
Status uint8 `xorm:"SMALLINT NOT NULL"`
19+
Source uint8 `xorm:"SMALLINT NOT NULL"`
20+
21+
IssueID int64 `xorm:"NOT NULL"`
22+
CommitID string
23+
CommentID int64
24+
ReleaseID int64
25+
26+
UpdatedBy int64 `xorm:"NOT NULL"`
27+
28+
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
29+
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
30+
}
31+
32+
func (n *NotificationV321) TableName() string {
33+
return "notification"
34+
}
35+
36+
// TableIndices implements xorm's TableIndices interface
37+
func (n *NotificationV321) TableIndices() []*schemas.Index {
38+
indices := make([]*schemas.Index, 0, 8)
39+
usuuIndex := schemas.NewIndex("u_s_uu", schemas.IndexType)
40+
usuuIndex.AddColumn("user_id", "status", "updated_unix")
41+
indices = append(indices, usuuIndex)
42+
43+
// Add the individual indices that were previously defined in struct tags
44+
userIDIndex := schemas.NewIndex("idx_notification_user_id", schemas.IndexType)
45+
userIDIndex.AddColumn("user_id")
46+
indices = append(indices, userIDIndex)
47+
48+
repoIDIndex := schemas.NewIndex("idx_notification_repo_id", schemas.IndexType)
49+
repoIDIndex.AddColumn("repo_id")
50+
indices = append(indices, repoIDIndex)
51+
52+
statusIndex := schemas.NewIndex("idx_notification_status", schemas.IndexType)
53+
statusIndex.AddColumn("status")
54+
indices = append(indices, statusIndex)
55+
56+
sourceIndex := schemas.NewIndex("idx_notification_source", schemas.IndexType)
57+
sourceIndex.AddColumn("source")
58+
indices = append(indices, sourceIndex)
59+
60+
issueIDIndex := schemas.NewIndex("idx_notification_issue_id", schemas.IndexType)
61+
issueIDIndex.AddColumn("issue_id")
62+
indices = append(indices, issueIDIndex)
63+
64+
commitIDIndex := schemas.NewIndex("idx_notification_commit_id", schemas.IndexType)
65+
commitIDIndex.AddColumn("commit_id")
66+
indices = append(indices, commitIDIndex)
67+
68+
releaseIDIndex := schemas.NewIndex("idx_notification_release_id", schemas.IndexType)
69+
releaseIDIndex.AddColumn("release_id")
70+
indices = append(indices, releaseIDIndex)
71+
72+
updatedByIndex := schemas.NewIndex("idx_notification_updated_by", schemas.IndexType)
73+
updatedByIndex.AddColumn("updated_by")
74+
indices = append(indices, updatedByIndex)
75+
76+
return indices
77+
}
78+
79+
func AddReleaseNotification(x *xorm.Engine) error {
80+
return x.Sync(new(NotificationV321))
81+
}

models/repo/release.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ func init() {
9393
db.RegisterModel(new(Release))
9494
}
9595

96+
func (r *Release) LoadPublisher(ctx context.Context) error {
97+
if r.Publisher != nil {
98+
return nil
99+
}
100+
var err error
101+
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
102+
if err != nil {
103+
if user_model.IsErrUserNotExist(err) {
104+
r.Publisher = user_model.NewGhostUser()
105+
} else {
106+
return err
107+
}
108+
}
109+
return nil
110+
}
111+
96112
// LoadAttributes load repo and publisher attributes for a release
97113
func (r *Release) LoadAttributes(ctx context.Context) error {
98114
var err error
@@ -102,15 +118,8 @@ func (r *Release) LoadAttributes(ctx context.Context) error {
102118
return err
103119
}
104120
}
105-
if r.Publisher == nil {
106-
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
107-
if err != nil {
108-
if user_model.IsErrUserNotExist(err) {
109-
r.Publisher = user_model.NewGhostUser()
110-
} else {
111-
return err
112-
}
113-
}
121+
if err := r.LoadPublisher(ctx); err != nil {
122+
return err
114123
}
115124
return GetReleaseAttachments(ctx, r)
116125
}

0 commit comments

Comments
 (0)