Skip to content

Commit 489e916

Browse files
authored
Extend Notifications API and return pinned notifications by default (go-gitea#12164) (go-gitea#12232)
Backport go-gitea#12164 This PR extends the notifications API to allow specific notification statuses to be searched for and to allow setting of notifications to statuses other than read. By default unread and pinned statuses will be returned when querying for notifications - however pinned statuses will not be marked as read. Close go-gitea#12152 Signed-off-by: Andrew Thornton [email protected]
1 parent 5e62137 commit 489e916

File tree

7 files changed

+206
-24
lines changed

7 files changed

+206
-24
lines changed

integrations/api_notification_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func TestAPINotification(t *testing.T) {
5555
assert.EqualValues(t, false, apiNL[2].Pinned)
5656

5757
// -- GET /repos/{owner}/{repo}/notifications --
58-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
58+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?status-types=unread&token=%s", user2.Name, repo1.Name, token))
5959
resp = session.MakeRequest(t, req, http.StatusOK)
6060
DecodeJSON(t, resp, &apiNL)
6161

@@ -92,7 +92,7 @@ func TestAPINotification(t *testing.T) {
9292
assert.True(t, new.New > 0)
9393

9494
// -- mark notifications as read --
95-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
95+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
9696
resp = session.MakeRequest(t, req, http.StatusOK)
9797
DecodeJSON(t, resp, &apiNL)
9898
assert.Len(t, apiNL, 2)
@@ -101,7 +101,7 @@ func TestAPINotification(t *testing.T) {
101101
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
102102
resp = session.MakeRequest(t, req, http.StatusResetContent)
103103

104-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
104+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
105105
resp = session.MakeRequest(t, req, http.StatusOK)
106106
DecodeJSON(t, resp, &apiNL)
107107
assert.Len(t, apiNL, 1)

integrations/eventsource_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestEventSourceManagerRun(t *testing.T) {
5959
var apiNL []api.NotificationThread
6060

6161
// -- mark notifications as read --
62-
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
62+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?status-types=unread&token=%s", token))
6363
resp := session.MakeRequest(t, req, http.StatusOK)
6464

6565
DecodeJSON(t, resp, &apiNL)
@@ -69,7 +69,7 @@ func TestEventSourceManagerRun(t *testing.T) {
6969
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
7070
resp = session.MakeRequest(t, req, http.StatusResetContent)
7171

72-
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
72+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s&status-types=unread", token))
7373
resp = session.MakeRequest(t, req, http.StatusOK)
7474
DecodeJSON(t, resp, &apiNL)
7575
assert.Len(t, apiNL, 1)

models/notification.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type FindNotificationOptions struct {
7272
UserID int64
7373
RepoID int64
7474
IssueID int64
75-
Status NotificationStatus
75+
Status []NotificationStatus
7676
UpdatedAfterUnix int64
7777
UpdatedBeforeUnix int64
7878
}
@@ -89,8 +89,8 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
8989
if opts.IssueID != 0 {
9090
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
9191
}
92-
if opts.Status != 0 {
93-
cond = cond.And(builder.Eq{"notification.status": opts.Status})
92+
if len(opts.Status) > 0 {
93+
cond = cond.And(builder.In("notification.status", opts.Status))
9494
}
9595
if opts.UpdatedAfterUnix != 0 {
9696
cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})

routers/api/v1/notify/repo.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,37 @@ import (
1111

1212
"code.gitea.io/gitea/models"
1313
"code.gitea.io/gitea/modules/context"
14+
"code.gitea.io/gitea/modules/log"
1415
"code.gitea.io/gitea/routers/api/v1/utils"
1516
)
1617

18+
func statusStringToNotificationStatus(status string) models.NotificationStatus {
19+
switch strings.ToLower(strings.TrimSpace(status)) {
20+
case "unread":
21+
return models.NotificationStatusUnread
22+
case "read":
23+
return models.NotificationStatusRead
24+
case "pinned":
25+
return models.NotificationStatusPinned
26+
default:
27+
return 0
28+
}
29+
}
30+
31+
func statusStringsToNotificationStatuses(statuses []string, defaultStatuses []string) []models.NotificationStatus {
32+
if len(statuses) == 0 {
33+
statuses = defaultStatuses
34+
}
35+
results := make([]models.NotificationStatus, 0, len(statuses))
36+
for _, status := range statuses {
37+
notificationStatus := statusStringToNotificationStatus(status)
38+
if notificationStatus > 0 {
39+
results = append(results, notificationStatus)
40+
}
41+
}
42+
return results
43+
}
44+
1745
// ListRepoNotifications list users's notification threads on a specific repo
1846
func ListRepoNotifications(ctx *context.APIContext) {
1947
// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
@@ -39,6 +67,14 @@ func ListRepoNotifications(ctx *context.APIContext) {
3967
// description: If true, show notifications marked as read. Default value is false
4068
// type: string
4169
// required: false
70+
// - name: status-types
71+
// in: query
72+
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned"
73+
// type: array
74+
// collectionFormat: multi
75+
// items:
76+
// type: string
77+
// required: false
4278
// - name: since
4379
// in: query
4480
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
@@ -75,9 +111,10 @@ func ListRepoNotifications(ctx *context.APIContext) {
75111
UpdatedBeforeUnix: before,
76112
UpdatedAfterUnix: since,
77113
}
78-
qAll := strings.Trim(ctx.Query("all"), " ")
79-
if qAll != "true" {
80-
opts.Status = models.NotificationStatusUnread
114+
115+
if !ctx.QueryBool("all") {
116+
statuses := ctx.QueryStrings("status-types")
117+
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
81118
}
82119
nl, err := models.GetNotifications(opts)
83120
if err != nil {
@@ -97,7 +134,7 @@ func ListRepoNotifications(ctx *context.APIContext) {
97134
func ReadRepoNotifications(ctx *context.APIContext) {
98135
// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
99136
// ---
100-
// summary: Mark notification threads as read on a specific repo
137+
// summary: Mark notification threads as read, pinned or unread on a specific repo
101138
// consumes:
102139
// - application/json
103140
// produces:
@@ -113,6 +150,24 @@ func ReadRepoNotifications(ctx *context.APIContext) {
113150
// description: name of the repo
114151
// type: string
115152
// required: true
153+
// - name: all
154+
// in: query
155+
// description: If true, mark all notifications on this repo. Default value is false
156+
// type: string
157+
// required: false
158+
// - name: status-types
159+
// in: query
160+
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
161+
// type: array
162+
// collectionFormat: multi
163+
// items:
164+
// type: string
165+
// required: false
166+
// - name: to-status
167+
// in: query
168+
// description: Status to mark notifications as. Defaults to read.
169+
// type: string
170+
// required: false
116171
// - name: last_read_at
117172
// in: query
118173
// description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
@@ -135,20 +190,31 @@ func ReadRepoNotifications(ctx *context.APIContext) {
135190
lastRead = tmpLastRead.Unix()
136191
}
137192
}
193+
138194
opts := models.FindNotificationOptions{
139195
UserID: ctx.User.ID,
140196
RepoID: ctx.Repo.Repository.ID,
141197
UpdatedBeforeUnix: lastRead,
142-
Status: models.NotificationStatusUnread,
198+
}
199+
200+
if !ctx.QueryBool("all") {
201+
statuses := ctx.QueryStrings("status-types")
202+
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
203+
log.Error("%v", opts.Status)
143204
}
144205
nl, err := models.GetNotifications(opts)
145206
if err != nil {
146207
ctx.InternalServerError(err)
147208
return
148209
}
149210

211+
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
212+
if targetStatus == 0 {
213+
targetStatus = models.NotificationStatusRead
214+
}
215+
150216
for _, n := range nl {
151-
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
217+
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
152218
if err != nil {
153219
ctx.InternalServerError(err)
154220
return

routers/api/v1/notify/threads.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ func ReadThread(ctx *context.APIContext) {
6262
// description: id of notification thread
6363
// type: string
6464
// required: true
65+
// - name: to-status
66+
// in: query
67+
// description: Status to mark notifications as
68+
// type: string
69+
// default: read
70+
// required: false
6571
// responses:
6672
// "205":
6773
// "$ref": "#/responses/empty"
@@ -75,7 +81,12 @@ func ReadThread(ctx *context.APIContext) {
7581
return
7682
}
7783

78-
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
84+
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
85+
if targetStatus == 0 {
86+
targetStatus = models.NotificationStatusRead
87+
}
88+
89+
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
7990
if err != nil {
8091
ctx.InternalServerError(err)
8192
return

routers/api/v1/notify/user.go

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ func ListNotifications(ctx *context.APIContext) {
2929
// description: If true, show notifications marked as read. Default value is false
3030
// type: string
3131
// required: false
32+
// - name: status-types
33+
// in: query
34+
// description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned."
35+
// type: array
36+
// collectionFormat: multi
37+
// items:
38+
// type: string
39+
// required: false
3240
// - name: since
3341
// in: query
3442
// description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
@@ -64,9 +72,9 @@ func ListNotifications(ctx *context.APIContext) {
6472
UpdatedBeforeUnix: before,
6573
UpdatedAfterUnix: since,
6674
}
67-
qAll := strings.Trim(ctx.Query("all"), " ")
68-
if qAll != "true" {
69-
opts.Status = models.NotificationStatusUnread
75+
if !ctx.QueryBool("all") {
76+
statuses := ctx.QueryStrings("status-types")
77+
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"})
7078
}
7179
nl, err := models.GetNotifications(opts)
7280
if err != nil {
@@ -82,11 +90,11 @@ func ListNotifications(ctx *context.APIContext) {
8290
ctx.JSON(http.StatusOK, nl.APIFormat())
8391
}
8492

85-
// ReadNotifications mark notification threads as read
93+
// ReadNotifications mark notification threads as read, unread, or pinned
8694
func ReadNotifications(ctx *context.APIContext) {
8795
// swagger:operation PUT /notifications notification notifyReadList
8896
// ---
89-
// summary: Mark notification threads as read
97+
// summary: Mark notification threads as read, pinned or unread
9098
// consumes:
9199
// - application/json
92100
// produces:
@@ -98,6 +106,24 @@ func ReadNotifications(ctx *context.APIContext) {
98106
// type: string
99107
// format: date-time
100108
// required: false
109+
// - name: all
110+
// in: query
111+
// description: If true, mark all notifications on this repo. Default value is false
112+
// type: string
113+
// required: false
114+
// - name: status-types
115+
// in: query
116+
// description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread."
117+
// type: array
118+
// collectionFormat: multi
119+
// items:
120+
// type: string
121+
// required: false
122+
// - name: to-status
123+
// in: query
124+
// description: Status to mark notifications as, Defaults to read.
125+
// type: string
126+
// required: false
101127
// responses:
102128
// "205":
103129
// "$ref": "#/responses/empty"
@@ -117,16 +143,24 @@ func ReadNotifications(ctx *context.APIContext) {
117143
opts := models.FindNotificationOptions{
118144
UserID: ctx.User.ID,
119145
UpdatedBeforeUnix: lastRead,
120-
Status: models.NotificationStatusUnread,
146+
}
147+
if !ctx.QueryBool("all") {
148+
statuses := ctx.QueryStrings("status-types")
149+
opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"})
121150
}
122151
nl, err := models.GetNotifications(opts)
123152
if err != nil {
124153
ctx.InternalServerError(err)
125154
return
126155
}
127156

157+
targetStatus := statusStringToNotificationStatus(ctx.Query("to-status"))
158+
if targetStatus == 0 {
159+
targetStatus = models.NotificationStatusRead
160+
}
161+
128162
for _, n := range nl {
129-
err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
163+
err := models.SetNotificationStatus(n.ID, ctx.User, targetStatus)
130164
if err != nil {
131165
ctx.InternalServerError(err)
132166
return

0 commit comments

Comments
 (0)