* 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 notificationstags/v1.12.0-dev
@@ -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) | |||||
} | } |
@@ -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. | ||||
@@ -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 | ||||
@@ -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 | ||||
@@ -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"` | |||||
} |
@@ -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) | ||||
@@ -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) | |||||
} | |||||
} |
@@ -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"` | |||||
} |
@@ -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": { | ||||