From db2dca8b03740ea88995bfdbb8c05a81ad056a71 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 3 Jan 2020 11:51:39 +0100 Subject: [PATCH 1/6] introduce GET /notifications/new --- models/notification.go | 15 +++++++++++++ routers/api/v1/api.go | 1 + routers/api/v1/notify/notifications.go | 30 ++++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 17 +++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 routers/api/v1/notify/notifications.go diff --git a/models/notification.go b/models/notification.go index 8e9bca0dc699d..952fec610a07f 100644 --- a/models/notification.go +++ b/models/notification.go @@ -8,6 +8,7 @@ import ( "fmt" "path" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" @@ -294,6 +295,20 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p return } +// UnreadAvailable check if unread notifications exist +func UnreadAvailable(user *User) bool { + return unreadAvailable(x, user.ID) +} + +func unreadAvailable(e Engine, userID int64) bool { + exist, err := e.Where("user_id = ?", userID).And("status = ?", NotificationStatusUnread).Get(new(Notification)) + if err != nil { + log.Error("newsAvailable", err) + return false + } + return exist +} + // APIFormat converts a Notification to api.NotificationThread func (n *Notification) APIFormat() *api.NotificationThread { result := &api.NotificationThread{ diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 86c745017302a..4c9f9dd03e2e7 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -518,6 +518,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo(""). Get(notify.ListNotifications). Put(notify.ReadNotifications) + m.Get("/new", notify.NewAvailable) m.Combo("/threads/:id"). Get(notify.GetThread). Patch(notify.ReadThread) diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go new file mode 100644 index 0000000000000..e2e7faf81b330 --- /dev/null +++ b/routers/api/v1/notify/notifications.go @@ -0,0 +1,30 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package notify + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" +) + +// NewAvailable check if unread notifications exist +func NewAvailable(ctx *context.APIContext) { + // swagger:operation GET /notifications/new notification notifyNewAvailable + // --- + // summary: Check if unread notifications exist + // responses: + // "204": + // description: No unread notification + // "302": + // description: Unread notification found + + if models.UnreadAvailable(ctx.User) { + ctx.Status(http.StatusFound) + } else { + ctx.Status(http.StatusNoContent) + } +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index a2baac13645a9..22b3573a15199 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -494,6 +494,23 @@ } } }, + "/notifications/new": { + "get": { + "tags": [ + "notification" + ], + "summary": "Check if unread notifications exist", + "operationId": "notifyNewAvailable", + "responses": { + "204": { + "description": "No unread notification" + }, + "302": { + "description": "Unread notification found" + } + } + } + }, "/notifications/threads/{id}": { "get": { "consumes": [ From 0f2b5710d0f18bdc34de5acc07f4edaa3d0a01ee Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 3 Jan 2020 11:51:54 +0100 Subject: [PATCH 2/6] add TEST --- integrations/api_notification_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/integrations/api_notification_test.go b/integrations/api_notification_test.go index 2c5477dfb0d2e..bbe5503fc6f5d 100644 --- a/integrations/api_notification_test.go +++ b/integrations/api_notification_test.go @@ -81,6 +81,10 @@ func TestAPINotification(t *testing.T) { assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL) assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) + // -- check notifications -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/new?token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusFound) + // -- mark notifications as read -- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token)) resp = session.MakeRequest(t, req, http.StatusOK) @@ -103,4 +107,8 @@ func TestAPINotification(t *testing.T) { assert.Equal(t, models.NotificationStatusUnread, thread5.Status) thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) assert.Equal(t, models.NotificationStatusRead, thread5.Status) + + // -- check notifications -- + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/new?token=%s", token)) + resp = session.MakeRequest(t, req, http.StatusNoContent) } From 98bb9e2bac7334ae2d09c387155cc05284f5568d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 12 Jan 2020 15:47:03 +0100 Subject: [PATCH 3/6] use Sprintf instead of path.Join --- models/issue.go | 3 +-- models/issue_comment.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/models/issue.go b/models/issue.go index 25765292ae3b1..b6408365f7111 100644 --- a/models/issue.go +++ b/models/issue.go @@ -7,7 +7,6 @@ package models import ( "fmt" - "path" "regexp" "sort" "strconv" @@ -324,7 +323,7 @@ func (issue *Issue) GetIsRead(userID int64) error { // APIURL returns the absolute APIURL to this issue. func (issue *Issue) APIURL() string { - return issue.Repo.APIURL() + "/" + path.Join("issues", fmt.Sprint(issue.Index)) + return fmt.Sprintf("%s/issues/%d", issue.Repo.APIURL(), issue.Index) } // HTMLURL returns the absolute URL to this issue. diff --git a/models/issue_comment.go b/models/issue_comment.go index 8f54d9656a7c8..699b8f0487161 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -8,7 +8,6 @@ package models import ( "fmt" - "path" "strings" "code.gitea.io/gitea/modules/git" @@ -249,7 +248,7 @@ func (c *Comment) APIURL() string { return "" } - return c.Issue.Repo.APIURL() + "/" + path.Join("issues/comments", fmt.Sprint(c.ID)) + return fmt.Sprintf("%s/issues/comments/%d", c.Issue.Repo.APIURL(), c.ID) } // IssueURL formats a URL-string to the issue From 049d90787cfcbb83610a69ab0d7e5a9f6d650b45 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 12 Jan 2020 15:48:49 +0100 Subject: [PATCH 4/6] Error more verbose --- models/notification.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/notification.go b/models/notification.go index 952fec610a07f..2c3678e24a4f5 100644 --- a/models/notification.go +++ b/models/notification.go @@ -403,7 +403,7 @@ func (n *Notification) loadComment(e Engine) (err error) { if n.Comment == nil && n.CommentID > 0 { n.Comment, err = GetCommentByID(n.CommentID) if err != nil { - return fmt.Errorf("GetCommentByID [%d]: %v", n.CommentID, err) + return fmt.Errorf("GetCommentByID [%d] for issue ID [%d]: %v", n.CommentID, n.IssueID, err) } } return nil From 42ef28e97b5b798eb46a28bdc154a64beda7c027 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 12 Jan 2020 16:21:53 +0100 Subject: [PATCH 5/6] return number of notifications if unreaded exist --- models/notification.go | 14 +++++++------- modules/structs/notifications.go | 5 +++++ routers/api/v1/notify/notifications.go | 9 ++++++--- routers/api/v1/swagger/notify.go | 7 +++++++ templates/swagger/v1_json.tmpl | 20 +++++++++++++++++++- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/models/notification.go b/models/notification.go index 2c3678e24a4f5..403c53243d287 100644 --- a/models/notification.go +++ b/models/notification.go @@ -295,16 +295,16 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p return } -// UnreadAvailable check if unread notifications exist -func UnreadAvailable(user *User) bool { - return unreadAvailable(x, user.ID) +// CountUnread count unread notifications for a user +func CountUnread(user *User) int64 { + return countUnread(x, user.ID) } -func unreadAvailable(e Engine, userID int64) bool { - exist, err := e.Where("user_id = ?", userID).And("status = ?", NotificationStatusUnread).Get(new(Notification)) +func countUnread(e Engine, userID int64) int64 { + exist, err := e.Where("user_id = ?", userID).And("status = ?", NotificationStatusUnread).Count(new(Notification)) if err != nil { - log.Error("newsAvailable", err) - return false + log.Error("countUnread", err) + return 0 } return exist } diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index b1e8b7781c3a6..b6c9774a97ad8 100644 --- a/modules/structs/notifications.go +++ b/modules/structs/notifications.go @@ -26,3 +26,8 @@ type NotificationSubject struct { LatestCommentURL string `json:"latest_comment_url"` Type string `json:"type" binding:"In(Issue,Pull,Commit)"` } + +// NotificationCount number of unread notifications +type NotificationCount struct { + New int64 `json:"new"` +} diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go index e2e7faf81b330..2f3c67943c4ad 100644 --- a/routers/api/v1/notify/notifications.go +++ b/routers/api/v1/notify/notifications.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" + api "code.gitea.io/gitea/modules/structs" ) // NewAvailable check if unread notifications exist @@ -20,10 +21,12 @@ func NewAvailable(ctx *context.APIContext) { // "204": // description: No unread notification // "302": - // description: Unread notification found + // "$ref": "#/responses/NotificationCount" - if models.UnreadAvailable(ctx.User) { - ctx.Status(http.StatusFound) + count := models.CountUnread(ctx.User) + + if count > 0 { + ctx.JSON(http.StatusFound, api.NotificationCount{New: count}) } else { ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/swagger/notify.go b/routers/api/v1/swagger/notify.go index 7d45da0e1258c..cd30d496e0dc1 100644 --- a/routers/api/v1/swagger/notify.go +++ b/routers/api/v1/swagger/notify.go @@ -21,3 +21,10 @@ type swaggerNotificationThreadList struct { // in:body Body []api.NotificationThread `json:"body"` } + +// Number of unread notifications +// swagger:response NotificationCount +type swaggerNotificationCount struct { + // in:body + Body api.NotificationCount `json:"body"` +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 22b3573a15199..5b908edde7671 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -506,7 +506,7 @@ "description": "No unread notification" }, "302": { - "description": "Unread notification found" + "$ref": "#/responses/NotificationCount" } } } @@ -10928,6 +10928,18 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "NotificationCount": { + "description": "NotificationCount number of unread notifications", + "type": "object", + "properties": { + "new": { + "type": "integer", + "format": "int64", + "x-go-name": "New" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "NotificationSubject": { "description": "NotificationSubject contains the notification subject (Issue/Pull/Commit)", "type": "object", @@ -12414,6 +12426,12 @@ } } }, + "NotificationCount": { + "description": "Number of unread notifications", + "schema": { + "$ref": "#/definitions/NotificationCount" + } + }, "NotificationThread": { "description": "NotificationThread", "schema": { From c5cb836cad76fd353be1931c8337e663301dc718 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 14 Jan 2020 12:59:41 +0100 Subject: [PATCH 6/6] 200 http status for available notifications --- integrations/api_notification_test.go | 2 +- routers/api/v1/notify/notifications.go | 6 +++--- templates/swagger/v1_json.tmpl | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/integrations/api_notification_test.go b/integrations/api_notification_test.go index bbe5503fc6f5d..baab00f6d29bb 100644 --- a/integrations/api_notification_test.go +++ b/integrations/api_notification_test.go @@ -83,7 +83,7 @@ func TestAPINotification(t *testing.T) { // -- check notifications -- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/new?token=%s", token)) - resp = session.MakeRequest(t, req, http.StatusFound) + resp = session.MakeRequest(t, req, http.StatusOK) // -- mark notifications as read -- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token)) diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go index 2f3c67943c4ad..847fe3313e0d5 100644 --- a/routers/api/v1/notify/notifications.go +++ b/routers/api/v1/notify/notifications.go @@ -18,15 +18,15 @@ func NewAvailable(ctx *context.APIContext) { // --- // summary: Check if unread notifications exist // responses: + // "200": + // "$ref": "#/responses/NotificationCount" // "204": // description: No unread notification - // "302": - // "$ref": "#/responses/NotificationCount" count := models.CountUnread(ctx.User) if count > 0 { - ctx.JSON(http.StatusFound, api.NotificationCount{New: count}) + ctx.JSON(http.StatusOK, api.NotificationCount{New: count}) } else { ctx.Status(http.StatusNoContent) } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 5b908edde7671..8ff4597b2e9ab 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -502,11 +502,11 @@ "summary": "Check if unread notifications exist", "operationId": "notifyNewAvailable", "responses": { + "200": { + "$ref": "#/responses/NotificationCount" + }, "204": { "description": "No unread notification" - }, - "302": { - "$ref": "#/responses/NotificationCount" } } }