* 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.21.12.1
| @@ -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": { | ||||