From 44de66bf50d1ab9a5acc298063cd942768092a19 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 14 Jan 2020 16:37:19 +0100 Subject: [PATCH] [API] add endpoint to check notifications [Extend #9488] (#9595) * introduce GET /notifications/new * add TEST * use Sprintf instead of path.Join * Error more verbose * return number of notifications if unreaded exist * 200 http status for available notifications --- integrations/api_notification_test.go | 8 ++++++ models/issue.go | 3 +-- models/issue_comment.go | 3 +-- models/notification.go | 17 ++++++++++++- modules/structs/notifications.go | 5 ++++ routers/api/v1/api.go | 1 + routers/api/v1/notify/notifications.go | 33 ++++++++++++++++++++++++ routers/api/v1/swagger/notify.go | 7 ++++++ templates/swagger/v1_json.tmpl | 35 ++++++++++++++++++++++++++ 9 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 routers/api/v1/notify/notifications.go diff --git a/integrations/api_notification_test.go b/integrations/api_notification_test.go index 2c5477dfb..baab00f6d 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.StatusOK) + // -- 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) } diff --git a/models/issue.go b/models/issue.go index 25765292a..b6408365f 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 8f54d9656..699b8f048 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 diff --git a/models/notification.go b/models/notification.go index 8e9bca0dc..403c53243 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 } +// CountUnread count unread notifications for a user +func CountUnread(user *User) int64 { + return countUnread(x, user.ID) +} + +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("countUnread", err) + return 0 + } + return exist +} + // APIFormat converts a Notification to api.NotificationThread func (n *Notification) APIFormat() *api.NotificationThread { result := &api.NotificationThread{ @@ -388,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 diff --git a/modules/structs/notifications.go b/modules/structs/notifications.go index b1e8b7781..b6c9774a9 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/api.go b/routers/api/v1/api.go index 86c745017..4c9f9dd03 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 000000000..847fe3313 --- /dev/null +++ b/routers/api/v1/notify/notifications.go @@ -0,0 +1,33 @@ +// 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" + api "code.gitea.io/gitea/modules/structs" +) + +// 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: + // "200": + // "$ref": "#/responses/NotificationCount" + // "204": + // description: No unread notification + + count := models.CountUnread(ctx.User) + + if count > 0 { + ctx.JSON(http.StatusOK, 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 7d45da0e1..cd30d496e 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 a2baac136..8ff4597b2 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": { + "200": { + "$ref": "#/responses/NotificationCount" + }, + "204": { + "description": "No unread notification" + } + } + } + }, "/notifications/threads/{id}": { "get": { "consumes": [ @@ -10911,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", @@ -12397,6 +12426,12 @@ } } }, + "NotificationCount": { + "description": "Number of unread notifications", + "schema": { + "$ref": "#/definitions/NotificationCount" + } + }, "NotificationThread": { "description": "NotificationThread", "schema": {