[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
This commit is contained in:
6543 2020-01-14 16:37:19 +01:00 committed by Antoine GIRARD
parent ce274d652f
commit 44de66bf50
9 changed files with 107 additions and 5 deletions

View file

@ -81,6 +81,10 @@ func TestAPINotification(t *testing.T) {
assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL) assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL)
assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) 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 -- // -- mark notifications as read --
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token)) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
@ -103,4 +107,8 @@ func TestAPINotification(t *testing.T) {
assert.Equal(t, models.NotificationStatusUnread, thread5.Status) assert.Equal(t, models.NotificationStatusUnread, thread5.Status)
thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification) thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification)
assert.Equal(t, models.NotificationStatusRead, thread5.Status) 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)
} }

View file

@ -7,7 +7,6 @@ package models
import ( import (
"fmt" "fmt"
"path"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
@ -324,7 +323,7 @@ func (issue *Issue) GetIsRead(userID int64) error {
// APIURL returns the absolute APIURL to this issue. // APIURL returns the absolute APIURL to this issue.
func (issue *Issue) APIURL() string { 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. // HTMLURL returns the absolute URL to this issue.

View file

@ -8,7 +8,6 @@ package models
import ( import (
"fmt" "fmt"
"path"
"strings" "strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -249,7 +248,7 @@ func (c *Comment) APIURL() string {
return "" 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 // IssueURL formats a URL-string to the issue

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"path" "path"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
@ -294,6 +295,20 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p
return 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 // APIFormat converts a Notification to api.NotificationThread
func (n *Notification) APIFormat() *api.NotificationThread { func (n *Notification) APIFormat() *api.NotificationThread {
result := &api.NotificationThread{ result := &api.NotificationThread{
@ -388,7 +403,7 @@ func (n *Notification) loadComment(e Engine) (err error) {
if n.Comment == nil && n.CommentID > 0 { if n.Comment == nil && n.CommentID > 0 {
n.Comment, err = GetCommentByID(n.CommentID) n.Comment, err = GetCommentByID(n.CommentID)
if err != nil { 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 return nil

View file

@ -26,3 +26,8 @@ type NotificationSubject struct {
LatestCommentURL string `json:"latest_comment_url"` LatestCommentURL string `json:"latest_comment_url"`
Type string `json:"type" binding:"In(Issue,Pull,Commit)"` Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
} }
// NotificationCount number of unread notifications
type NotificationCount struct {
New int64 `json:"new"`
}

View file

@ -518,6 +518,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo(""). m.Combo("").
Get(notify.ListNotifications). Get(notify.ListNotifications).
Put(notify.ReadNotifications) Put(notify.ReadNotifications)
m.Get("/new", notify.NewAvailable)
m.Combo("/threads/:id"). m.Combo("/threads/:id").
Get(notify.GetThread). Get(notify.GetThread).
Patch(notify.ReadThread) Patch(notify.ReadThread)

View file

@ -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)
}
}

View file

@ -21,3 +21,10 @@ type swaggerNotificationThreadList struct {
// in:body // in:body
Body []api.NotificationThread `json:"body"` Body []api.NotificationThread `json:"body"`
} }
// Number of unread notifications
// swagger:response NotificationCount
type swaggerNotificationCount struct {
// in:body
Body api.NotificationCount `json:"body"`
}

View file

@ -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}": { "/notifications/threads/{id}": {
"get": { "get": {
"consumes": [ "consumes": [
@ -10911,6 +10928,18 @@
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "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": { "NotificationSubject": {
"description": "NotificationSubject contains the notification subject (Issue/Pull/Commit)", "description": "NotificationSubject contains the notification subject (Issue/Pull/Commit)",
"type": "object", "type": "object",
@ -12397,6 +12426,12 @@
} }
} }
}, },
"NotificationCount": {
"description": "Number of unread notifications",
"schema": {
"$ref": "#/definitions/NotificationCount"
}
},
"NotificationThread": { "NotificationThread": {
"description": "NotificationThread", "description": "NotificationThread",
"schema": { "schema": {