mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	[API] Add notification endpoint (#9488)
* [API] Add notification endpoints
 * add func GetNotifications(opts FindNotificationOptions)
 * add func (n *Notification) APIFormat()
 * add func (nl NotificationList) APIFormat()
 * add func (n *Notification) APIURL()
 * add func (nl NotificationList) APIFormat()
 * add LoadAttributes functions (loadRepo, loadIssue, loadComment, loadUser)
 * add func (c *Comment) APIURL()
 * add func (issue *Issue) GetLastComment()
 * add endpoint GET /notifications
 * add endpoint PUT /notifications
 * add endpoint GET /repos/{owner}/{repo}/notifications
 * add endpoint PUT /repos/{owner}/{repo}/notifications
 * add endpoint GET /notifications/threads/{id}
 * add endpoint PATCH /notifications/threads/{id}
* Add TEST
* code format
* code format
			
			
This commit is contained in:
		
							
								
								
									
										106
									
								
								integrations/api_notification_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								integrations/api_notification_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					// 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 integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAPINotification(t *testing.T) {
 | 
				
			||||||
 | 
						defer prepareTestEnv(t)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
 | 
				
			||||||
 | 
						repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
 | 
				
			||||||
 | 
						thread5 := models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification)
 | 
				
			||||||
 | 
						assert.NoError(t, thread5.LoadAttributes())
 | 
				
			||||||
 | 
						session := loginUser(t, user2.Name)
 | 
				
			||||||
 | 
						token := getTokenForLoggedInUser(t, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// -- GET /notifications --
 | 
				
			||||||
 | 
						// test filter
 | 
				
			||||||
 | 
						since := "2000-01-01T00%3A50%3A01%2B00%3A00" //946687801
 | 
				
			||||||
 | 
						req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?since=%s&token=%s", since, token))
 | 
				
			||||||
 | 
						resp := session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						var apiNL []api.NotificationThread
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiNL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Len(t, apiNL, 1)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 5, apiNL[0].ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test filter
 | 
				
			||||||
 | 
						before := "2000-01-01T01%3A06%3A59%2B00%3A00" //946688819
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=%s&before=%s&token=%s", "true", before, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiNL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Len(t, apiNL, 3)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 4, apiNL[0].ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, true, apiNL[0].Unread)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, apiNL[0].Pinned)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 3, apiNL[1].ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, apiNL[1].Unread)
 | 
				
			||||||
 | 
						assert.EqualValues(t, true, apiNL[1].Pinned)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 2, apiNL[2].ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, apiNL[2].Unread)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, apiNL[2].Pinned)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// -- GET /repos/{owner}/{repo}/notifications --
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?token=%s", user2.Name, repo1.Name, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiNL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Len(t, apiNL, 1)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 4, apiNL[0].ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// -- GET /notifications/threads/{id} --
 | 
				
			||||||
 | 
						// get forbidden
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", 1, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusForbidden)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get own
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						var apiN api.NotificationThread
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.EqualValues(t, 5, apiN.ID)
 | 
				
			||||||
 | 
						assert.EqualValues(t, false, apiN.Pinned)
 | 
				
			||||||
 | 
						assert.EqualValues(t, true, apiN.Unread)
 | 
				
			||||||
 | 
						assert.EqualValues(t, "issue4", apiN.Subject.Title)
 | 
				
			||||||
 | 
						assert.EqualValues(t, "Issue", apiN.Subject.Type)
 | 
				
			||||||
 | 
						assert.EqualValues(t, thread5.Issue.APIURL(), apiN.Subject.URL)
 | 
				
			||||||
 | 
						assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// -- mark notifications as read --
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiNL)
 | 
				
			||||||
 | 
						assert.Len(t, apiNL, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastReadAt := "2000-01-01T00%3A50%3A01%2B00%3A00" //946687801 <- only Notification 4 is in this filter ...
 | 
				
			||||||
 | 
						req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/notifications?last_read_at=%s&token=%s", user2.Name, repo1.Name, lastReadAt, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusResetContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?token=%s", token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &apiNL)
 | 
				
			||||||
 | 
						assert.Len(t, apiNL, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// -- PATCH /notifications/threads/{id} --
 | 
				
			||||||
 | 
						req = NewRequest(t, "PATCH", fmt.Sprintf("/api/v1/notifications/threads/%d?token=%s", thread5.ID, token))
 | 
				
			||||||
 | 
						resp = session.MakeRequest(t, req, http.StatusResetContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, models.NotificationStatusUnread, thread5.Status)
 | 
				
			||||||
 | 
						thread5 = models.AssertExistsAndLoadBean(t, &models.Notification{ID: 5}).(*models.Notification)
 | 
				
			||||||
 | 
						assert.Equal(t, models.NotificationStatusRead, thread5.Status)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
  updated_by: 2
 | 
					  updated_by: 2
 | 
				
			||||||
  issue_id: 1
 | 
					  issue_id: 1
 | 
				
			||||||
  created_unix: 946684800
 | 
					  created_unix: 946684800
 | 
				
			||||||
  updated_unix: 946684800
 | 
					  updated_unix: 946684820
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-
 | 
					-
 | 
				
			||||||
  id: 2
 | 
					  id: 2
 | 
				
			||||||
@@ -17,8 +17,8 @@
 | 
				
			|||||||
  source: 1 # issue
 | 
					  source: 1 # issue
 | 
				
			||||||
  updated_by: 1
 | 
					  updated_by: 1
 | 
				
			||||||
  issue_id: 2
 | 
					  issue_id: 2
 | 
				
			||||||
  created_unix: 946684800
 | 
					  created_unix: 946685800
 | 
				
			||||||
  updated_unix: 946684800
 | 
					  updated_unix: 946685820
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-
 | 
					-
 | 
				
			||||||
  id: 3
 | 
					  id: 3
 | 
				
			||||||
@@ -27,9 +27,9 @@
 | 
				
			|||||||
  status: 3 # pinned
 | 
					  status: 3 # pinned
 | 
				
			||||||
  source: 1 # issue
 | 
					  source: 1 # issue
 | 
				
			||||||
  updated_by: 1
 | 
					  updated_by: 1
 | 
				
			||||||
  issue_id: 2
 | 
					  issue_id: 3
 | 
				
			||||||
  created_unix: 946684800
 | 
					  created_unix: 946686800
 | 
				
			||||||
  updated_unix: 946684800
 | 
					  updated_unix: 946686800
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-
 | 
					-
 | 
				
			||||||
  id: 4
 | 
					  id: 4
 | 
				
			||||||
@@ -38,6 +38,17 @@
 | 
				
			|||||||
  status: 1 # unread
 | 
					  status: 1 # unread
 | 
				
			||||||
  source: 1 # issue
 | 
					  source: 1 # issue
 | 
				
			||||||
  updated_by: 1
 | 
					  updated_by: 1
 | 
				
			||||||
  issue_id: 2
 | 
					  issue_id: 5
 | 
				
			||||||
  created_unix: 946684800
 | 
					  created_unix: 946687800
 | 
				
			||||||
  updated_unix: 946684800
 | 
					  updated_unix: 946687800
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-
 | 
				
			||||||
 | 
					  id: 5
 | 
				
			||||||
 | 
					  user_id: 2
 | 
				
			||||||
 | 
					  repo_id: 2
 | 
				
			||||||
 | 
					  status: 1 # unread
 | 
				
			||||||
 | 
					  source: 1 # issue
 | 
				
			||||||
 | 
					  updated_by: 5
 | 
				
			||||||
 | 
					  issue_id: 4
 | 
				
			||||||
 | 
					  created_unix: 946688800
 | 
				
			||||||
 | 
					  updated_unix: 946688820
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -843,6 +843,20 @@ func (issue *Issue) GetLastEventLabel() string {
 | 
				
			|||||||
	return "repo.issues.opened_by"
 | 
						return "repo.issues.opened_by"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLastComment return last comment for the current issue.
 | 
				
			||||||
 | 
					func (issue *Issue) GetLastComment() (*Comment, error) {
 | 
				
			||||||
 | 
						var c Comment
 | 
				
			||||||
 | 
						exist, err := x.Where("type = ?", CommentTypeComment).
 | 
				
			||||||
 | 
							And("issue_id = ?", issue.ID).Desc("id").Get(&c)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !exist {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &c, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username.
 | 
					// GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username.
 | 
				
			||||||
func (issue *Issue) GetLastEventLabelFake() string {
 | 
					func (issue *Issue) GetLastEventLabelFake() string {
 | 
				
			||||||
	if issue.IsClosed {
 | 
						if issue.IsClosed {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
@@ -235,6 +236,22 @@ func (c *Comment) HTMLURL() string {
 | 
				
			|||||||
	return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
 | 
						return fmt.Sprintf("%s#%s", c.Issue.HTMLURL(), c.HashTag())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APIURL formats a API-string to the issue-comment
 | 
				
			||||||
 | 
					func (c *Comment) APIURL() string {
 | 
				
			||||||
 | 
						err := c.LoadIssue()
 | 
				
			||||||
 | 
						if err != nil { // Silently dropping errors :unamused:
 | 
				
			||||||
 | 
							log.Error("LoadIssue(%d): %v", c.IssueID, err)
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = c.Issue.loadRepo(x)
 | 
				
			||||||
 | 
						if err != nil { // Silently dropping errors :unamused:
 | 
				
			||||||
 | 
							log.Error("loadRepo(%d): %v", c.Issue.RepoID, err)
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.Issue.Repo.APIURL() + "/" + path.Join("issues/comments", fmt.Sprint(c.ID))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IssueURL formats a URL-string to the issue
 | 
					// IssueURL formats a URL-string to the issue
 | 
				
			||||||
func (c *Comment) IssueURL() string {
 | 
					func (c *Comment) IssueURL() string {
 | 
				
			||||||
	err := c.LoadIssue()
 | 
						err := c.LoadIssue()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,14 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"code.gitea.io/gitea/modules/timeutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"xorm.io/builder"
 | 
				
			||||||
 | 
						"xorm.io/xorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type (
 | 
					type (
 | 
				
			||||||
@@ -47,17 +53,67 @@ type Notification struct {
 | 
				
			|||||||
	IssueID   int64  `xorm:"INDEX NOT NULL"`
 | 
						IssueID   int64  `xorm:"INDEX NOT NULL"`
 | 
				
			||||||
	CommitID  string `xorm:"INDEX"`
 | 
						CommitID  string `xorm:"INDEX"`
 | 
				
			||||||
	CommentID int64
 | 
						CommentID int64
 | 
				
			||||||
	Comment   *Comment `xorm:"-"`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	UpdatedBy int64 `xorm:"INDEX NOT NULL"`
 | 
						UpdatedBy int64 `xorm:"INDEX NOT NULL"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Issue      *Issue      `xorm:"-"`
 | 
						Issue      *Issue      `xorm:"-"`
 | 
				
			||||||
	Repository *Repository `xorm:"-"`
 | 
						Repository *Repository `xorm:"-"`
 | 
				
			||||||
 | 
						Comment    *Comment    `xorm:"-"`
 | 
				
			||||||
 | 
						User       *User       `xorm:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
 | 
						CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
 | 
				
			||||||
	UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
 | 
						UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
 | 
				
			||||||
 | 
					type FindNotificationOptions struct {
 | 
				
			||||||
 | 
						UserID            int64
 | 
				
			||||||
 | 
						RepoID            int64
 | 
				
			||||||
 | 
						IssueID           int64
 | 
				
			||||||
 | 
						Status            NotificationStatus
 | 
				
			||||||
 | 
						UpdatedAfterUnix  int64
 | 
				
			||||||
 | 
						UpdatedBeforeUnix int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToCond will convert each condition into a xorm-Cond
 | 
				
			||||||
 | 
					func (opts *FindNotificationOptions) ToCond() builder.Cond {
 | 
				
			||||||
 | 
						cond := builder.NewCond()
 | 
				
			||||||
 | 
						if opts.UserID != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"notification.user_id": opts.UserID})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.RepoID != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"notification.repo_id": opts.RepoID})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.IssueID != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.Status != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Eq{"notification.status": opts.Status})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.UpdatedAfterUnix != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Gte{"notification.updated_unix": opts.UpdatedAfterUnix})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.UpdatedBeforeUnix != 0 {
 | 
				
			||||||
 | 
							cond = cond.And(builder.Lte{"notification.updated_unix": opts.UpdatedBeforeUnix})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cond
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
 | 
				
			||||||
 | 
					func (opts *FindNotificationOptions) ToSession(e Engine) *xorm.Session {
 | 
				
			||||||
 | 
						return e.Where(opts.ToCond())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getNotifications(e Engine, options FindNotificationOptions) (nl NotificationList, err error) {
 | 
				
			||||||
 | 
						err = options.ToSession(e).OrderBy("notification.updated_unix DESC").Find(&nl)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetNotifications returns all notifications that fit to the given options.
 | 
				
			||||||
 | 
					func GetNotifications(opts FindNotificationOptions) (NotificationList, error) {
 | 
				
			||||||
 | 
						return getNotifications(x, opts)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateOrUpdateIssueNotifications creates an issue notification
 | 
					// CreateOrUpdateIssueNotifications creates an issue notification
 | 
				
			||||||
// for each watcher, or updates it if already exists
 | 
					// for each watcher, or updates it if already exists
 | 
				
			||||||
func CreateOrUpdateIssueNotifications(issueID, commentID int64, notificationAuthorID int64) error {
 | 
					func CreateOrUpdateIssueNotifications(issueID, commentID int64, notificationAuthorID int64) error {
 | 
				
			||||||
@@ -238,22 +294,124 @@ func notificationsForUser(e Engine, user *User, statuses []NotificationStatus, p
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APIFormat converts a Notification to api.NotificationThread
 | 
				
			||||||
 | 
					func (n *Notification) APIFormat() *api.NotificationThread {
 | 
				
			||||||
 | 
						result := &api.NotificationThread{
 | 
				
			||||||
 | 
							ID:        n.ID,
 | 
				
			||||||
 | 
							Unread:    !(n.Status == NotificationStatusRead || n.Status == NotificationStatusPinned),
 | 
				
			||||||
 | 
							Pinned:    n.Status == NotificationStatusPinned,
 | 
				
			||||||
 | 
							UpdatedAt: n.UpdatedUnix.AsTime(),
 | 
				
			||||||
 | 
							URL:       n.APIURL(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//since user only get notifications when he has access to use minimal access mode
 | 
				
			||||||
 | 
						if n.Repository != nil {
 | 
				
			||||||
 | 
							result.Repository = n.Repository.APIFormat(AccessModeRead)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//handle Subject
 | 
				
			||||||
 | 
						switch n.Source {
 | 
				
			||||||
 | 
						case NotificationSourceIssue:
 | 
				
			||||||
 | 
							result.Subject = &api.NotificationSubject{Type: "Issue"}
 | 
				
			||||||
 | 
							if n.Issue != nil {
 | 
				
			||||||
 | 
								result.Subject.Title = n.Issue.Title
 | 
				
			||||||
 | 
								result.Subject.URL = n.Issue.APIURL()
 | 
				
			||||||
 | 
								comment, err := n.Issue.GetLastComment()
 | 
				
			||||||
 | 
								if err == nil && comment != nil {
 | 
				
			||||||
 | 
									result.Subject.LatestCommentURL = comment.APIURL()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case NotificationSourcePullRequest:
 | 
				
			||||||
 | 
							result.Subject = &api.NotificationSubject{Type: "Pull"}
 | 
				
			||||||
 | 
							if n.Issue != nil {
 | 
				
			||||||
 | 
								result.Subject.Title = n.Issue.Title
 | 
				
			||||||
 | 
								result.Subject.URL = n.Issue.APIURL()
 | 
				
			||||||
 | 
								comment, err := n.Issue.GetLastComment()
 | 
				
			||||||
 | 
								if err == nil && comment != nil {
 | 
				
			||||||
 | 
									result.Subject.LatestCommentURL = comment.APIURL()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case NotificationSourceCommit:
 | 
				
			||||||
 | 
							result.Subject = &api.NotificationSubject{
 | 
				
			||||||
 | 
								Type:  "Commit",
 | 
				
			||||||
 | 
								Title: n.CommitID,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							//unused until now
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LoadAttributes load Repo Issue User and Comment if not loaded
 | 
				
			||||||
 | 
					func (n *Notification) LoadAttributes() (err error) {
 | 
				
			||||||
 | 
						return n.loadAttributes(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Notification) loadAttributes(e Engine) (err error) {
 | 
				
			||||||
 | 
						if err = n.loadRepo(e); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err = n.loadIssue(e); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err = n.loadUser(e); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err = n.loadComment(e); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Notification) loadRepo(e Engine) (err error) {
 | 
				
			||||||
 | 
						if n.Repository == nil {
 | 
				
			||||||
 | 
							n.Repository, err = getRepositoryByID(e, n.RepoID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("getRepositoryByID [%d]: %v", n.RepoID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Notification) loadIssue(e Engine) (err error) {
 | 
				
			||||||
 | 
						if n.Issue == nil {
 | 
				
			||||||
 | 
							n.Issue, err = getIssueByID(e, n.IssueID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("getIssueByID [%d]: %v", n.IssueID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return n.Issue.loadAttributes(e)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *Notification) loadUser(e Engine) (err error) {
 | 
				
			||||||
 | 
						if n.User == nil {
 | 
				
			||||||
 | 
							n.User, err = getUserByID(e, n.UserID)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("getUserByID [%d]: %v", n.UserID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetRepo returns the repo of the notification
 | 
					// GetRepo returns the repo of the notification
 | 
				
			||||||
func (n *Notification) GetRepo() (*Repository, error) {
 | 
					func (n *Notification) GetRepo() (*Repository, error) {
 | 
				
			||||||
	n.Repository = new(Repository)
 | 
						return n.Repository, n.loadRepo(x)
 | 
				
			||||||
	_, err := x.
 | 
					 | 
				
			||||||
		Where("id = ?", n.RepoID).
 | 
					 | 
				
			||||||
		Get(n.Repository)
 | 
					 | 
				
			||||||
	return n.Repository, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssue returns the issue of the notification
 | 
					// GetIssue returns the issue of the notification
 | 
				
			||||||
func (n *Notification) GetIssue() (*Issue, error) {
 | 
					func (n *Notification) GetIssue() (*Issue, error) {
 | 
				
			||||||
	n.Issue = new(Issue)
 | 
						return n.Issue, n.loadIssue(x)
 | 
				
			||||||
	_, err := x.
 | 
					 | 
				
			||||||
		Where("id = ?", n.IssueID).
 | 
					 | 
				
			||||||
		Get(n.Issue)
 | 
					 | 
				
			||||||
	return n.Issue, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HTMLURL formats a URL-string to the notification
 | 
					// HTMLURL formats a URL-string to the notification
 | 
				
			||||||
@@ -264,9 +422,34 @@ func (n *Notification) HTMLURL() string {
 | 
				
			|||||||
	return n.Issue.HTMLURL()
 | 
						return n.Issue.HTMLURL()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APIURL formats a URL-string to the notification
 | 
				
			||||||
 | 
					func (n *Notification) APIURL() string {
 | 
				
			||||||
 | 
						return setting.AppURL + path.Join("api/v1/notifications/threads", fmt.Sprintf("%d", n.ID))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NotificationList contains a list of notifications
 | 
					// NotificationList contains a list of notifications
 | 
				
			||||||
type NotificationList []*Notification
 | 
					type NotificationList []*Notification
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// APIFormat converts a NotificationList to api.NotificationThread list
 | 
				
			||||||
 | 
					func (nl NotificationList) APIFormat() []*api.NotificationThread {
 | 
				
			||||||
 | 
						var result = make([]*api.NotificationThread, 0, len(nl))
 | 
				
			||||||
 | 
						for _, n := range nl {
 | 
				
			||||||
 | 
							result = append(result, n.APIFormat())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LoadAttributes load Repo Issue User and Comment if not loaded
 | 
				
			||||||
 | 
					func (nl NotificationList) LoadAttributes() (err error) {
 | 
				
			||||||
 | 
						for i := 0; i < len(nl); i++ {
 | 
				
			||||||
 | 
							err = nl[i].LoadAttributes()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (nl NotificationList) getPendingRepoIDs() []int64 {
 | 
					func (nl NotificationList) getPendingRepoIDs() []int64 {
 | 
				
			||||||
	var ids = make(map[int64]struct{}, len(nl))
 | 
						var ids = make(map[int64]struct{}, len(nl))
 | 
				
			||||||
	for _, notification := range nl {
 | 
						for _, notification := range nl {
 | 
				
			||||||
@@ -486,7 +669,7 @@ func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// SetNotificationStatus change the notification status
 | 
					// SetNotificationStatus change the notification status
 | 
				
			||||||
func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
 | 
					func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
 | 
				
			||||||
	notification, err := getNotificationByID(notificationID)
 | 
						notification, err := getNotificationByID(x, notificationID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -501,9 +684,14 @@ func SetNotificationStatus(notificationID int64, user *User, status Notification
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getNotificationByID(notificationID int64) (*Notification, error) {
 | 
					// GetNotificationByID return notification by ID
 | 
				
			||||||
 | 
					func GetNotificationByID(notificationID int64) (*Notification, error) {
 | 
				
			||||||
 | 
						return getNotificationByID(x, notificationID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getNotificationByID(e Engine, notificationID int64) (*Notification, error) {
 | 
				
			||||||
	notification := new(Notification)
 | 
						notification := new(Notification)
 | 
				
			||||||
	ok, err := x.
 | 
						ok, err := e.
 | 
				
			||||||
		Where("id = ?", notificationID).
 | 
							Where("id = ?", notificationID).
 | 
				
			||||||
		Get(notification)
 | 
							Get(notification)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -512,7 +700,7 @@ func getNotificationByID(notificationID int64) (*Notification, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return nil, fmt.Errorf("Notification %d does not exists", notificationID)
 | 
							return nil, ErrNotExist{ID: notificationID}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return notification, nil
 | 
						return notification, nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,11 +31,13 @@ func TestNotificationsForUser(t *testing.T) {
 | 
				
			|||||||
	statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
 | 
						statuses := []NotificationStatus{NotificationStatusRead, NotificationStatusUnread}
 | 
				
			||||||
	notfs, err := NotificationsForUser(user, statuses, 1, 10)
 | 
						notfs, err := NotificationsForUser(user, statuses, 1, 10)
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	if assert.Len(t, notfs, 2) {
 | 
						if assert.Len(t, notfs, 3) {
 | 
				
			||||||
		assert.EqualValues(t, 2, notfs[0].ID)
 | 
							assert.EqualValues(t, 5, notfs[0].ID)
 | 
				
			||||||
		assert.EqualValues(t, user.ID, notfs[0].UserID)
 | 
							assert.EqualValues(t, user.ID, notfs[0].UserID)
 | 
				
			||||||
		assert.EqualValues(t, 4, notfs[1].ID)
 | 
							assert.EqualValues(t, 4, notfs[1].ID)
 | 
				
			||||||
		assert.EqualValues(t, user.ID, notfs[1].UserID)
 | 
							assert.EqualValues(t, user.ID, notfs[1].UserID)
 | 
				
			||||||
 | 
							assert.EqualValues(t, 2, notfs[2].ID)
 | 
				
			||||||
 | 
							assert.EqualValues(t, user.ID, notfs[2].UserID)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								modules/structs/notifications.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								modules/structs/notifications.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					// Copyright 2019 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 structs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotificationThread expose Notification on API
 | 
				
			||||||
 | 
					type NotificationThread struct {
 | 
				
			||||||
 | 
						ID         int64                `json:"id"`
 | 
				
			||||||
 | 
						Repository *Repository          `json:"repository"`
 | 
				
			||||||
 | 
						Subject    *NotificationSubject `json:"subject"`
 | 
				
			||||||
 | 
						Unread     bool                 `json:"unread"`
 | 
				
			||||||
 | 
						Pinned     bool                 `json:"pinned"`
 | 
				
			||||||
 | 
						UpdatedAt  time.Time            `json:"updated_at"`
 | 
				
			||||||
 | 
						URL        string               `json:"url"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotificationSubject contains the notification subject (Issue/Pull/Commit)
 | 
				
			||||||
 | 
					type NotificationSubject struct {
 | 
				
			||||||
 | 
						Title            string `json:"title"`
 | 
				
			||||||
 | 
						URL              string `json:"url"`
 | 
				
			||||||
 | 
						LatestCommentURL string `json:"latest_comment_url"`
 | 
				
			||||||
 | 
						Type             string `json:"type" binding:"In(Issue,Pull,Commit)"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -56,10 +56,10 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
 | 
				
			|||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
	//   "201":
 | 
						//   "201":
 | 
				
			||||||
	//     "$ref": "#/responses/User"
 | 
						//     "$ref": "#/responses/User"
 | 
				
			||||||
	//   "403":
 | 
					 | 
				
			||||||
	//     "$ref": "#/responses/forbidden"
 | 
					 | 
				
			||||||
	//   "400":
 | 
						//   "400":
 | 
				
			||||||
	//     "$ref": "#/responses/error"
 | 
						//     "$ref": "#/responses/error"
 | 
				
			||||||
 | 
						//   "403":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/forbidden"
 | 
				
			||||||
	//   "422":
 | 
						//   "422":
 | 
				
			||||||
	//     "$ref": "#/responses/validationError"
 | 
						//     "$ref": "#/responses/validationError"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,6 +70,7 @@ import (
 | 
				
			|||||||
	api "code.gitea.io/gitea/modules/structs"
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/admin"
 | 
						"code.gitea.io/gitea/routers/api/v1/admin"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/misc"
 | 
						"code.gitea.io/gitea/routers/api/v1/misc"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/api/v1/notify"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/org"
 | 
						"code.gitea.io/gitea/routers/api/v1/org"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/repo"
 | 
						"code.gitea.io/gitea/routers/api/v1/repo"
 | 
				
			||||||
	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
 | 
						_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
 | 
				
			||||||
@@ -512,6 +513,16 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
		m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
 | 
							m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
 | 
				
			||||||
		m.Post("/markdown/raw", misc.MarkdownRaw)
 | 
							m.Post("/markdown/raw", misc.MarkdownRaw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Notifications
 | 
				
			||||||
 | 
							m.Group("/notifications", func() {
 | 
				
			||||||
 | 
								m.Combo("").
 | 
				
			||||||
 | 
									Get(notify.ListNotifications).
 | 
				
			||||||
 | 
									Put(notify.ReadNotifications)
 | 
				
			||||||
 | 
								m.Combo("/threads/:id").
 | 
				
			||||||
 | 
									Get(notify.GetThread).
 | 
				
			||||||
 | 
									Patch(notify.ReadThread)
 | 
				
			||||||
 | 
							}, reqToken())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Users
 | 
							// Users
 | 
				
			||||||
		m.Group("/users", func() {
 | 
							m.Group("/users", func() {
 | 
				
			||||||
			m.Get("/search", user.Search)
 | 
								m.Get("/search", user.Search)
 | 
				
			||||||
@@ -610,6 +621,9 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
				m.Combo("").Get(reqAnyRepoReader(), repo.Get).
 | 
									m.Combo("").Get(reqAnyRepoReader(), repo.Get).
 | 
				
			||||||
					Delete(reqToken(), reqOwner(), repo.Delete).
 | 
										Delete(reqToken(), reqOwner(), repo.Delete).
 | 
				
			||||||
					Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRef(), repo.Edit)
 | 
										Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRef(), repo.Edit)
 | 
				
			||||||
 | 
									m.Combo("/notifications").
 | 
				
			||||||
 | 
										Get(reqToken(), notify.ListRepoNotifications).
 | 
				
			||||||
 | 
										Put(reqToken(), notify.ReadRepoNotifications)
 | 
				
			||||||
				m.Group("/hooks", func() {
 | 
									m.Group("/hooks", func() {
 | 
				
			||||||
					m.Combo("").Get(repo.ListHooks).
 | 
										m.Combo("").Get(repo.ListHooks).
 | 
				
			||||||
						Post(bind(api.CreateHookOption{}), repo.CreateHook)
 | 
											Post(bind(api.CreateHookOption{}), repo.CreateHook)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										151
									
								
								routers/api/v1/notify/repo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								routers/api/v1/notify/repo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListRepoNotifications list users's notification threads on a specific repo
 | 
				
			||||||
 | 
					func ListRepoNotifications(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: List users's notification threads on a specific repo
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: all
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: If true, show notifications marked as read. Default value is false
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// - name: since
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// - name: before
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "200":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/NotificationThreadList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						before, since, err := utils.GetQueryBeforeSince(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opts := models.FindNotificationOptions{
 | 
				
			||||||
 | 
							UserID:            ctx.User.ID,
 | 
				
			||||||
 | 
							RepoID:            ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
							UpdatedBeforeUnix: before,
 | 
				
			||||||
 | 
							UpdatedAfterUnix:  since,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						qAll := strings.Trim(ctx.Query("all"), " ")
 | 
				
			||||||
 | 
						if qAll != "true" {
 | 
				
			||||||
 | 
							opts.Status = models.NotificationStatusUnread
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nl, err := models.GetNotifications(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = nl.LoadAttributes()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, nl.APIFormat())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadRepoNotifications mark notification threads as read on a specific repo
 | 
				
			||||||
 | 
					func ReadRepoNotifications(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Mark notification threads as read on a specific repo
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: owner
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: owner of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: repo
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: name of the repo
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// - name: last_read_at
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "205":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastRead := int64(0)
 | 
				
			||||||
 | 
						qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
 | 
				
			||||||
 | 
						if len(qLastRead) > 0 {
 | 
				
			||||||
 | 
							tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !tmpLastRead.IsZero() {
 | 
				
			||||||
 | 
								lastRead = tmpLastRead.Unix()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opts := models.FindNotificationOptions{
 | 
				
			||||||
 | 
							UserID:            ctx.User.ID,
 | 
				
			||||||
 | 
							RepoID:            ctx.Repo.Repository.ID,
 | 
				
			||||||
 | 
							UpdatedBeforeUnix: lastRead,
 | 
				
			||||||
 | 
							Status:            models.NotificationStatusUnread,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nl, err := models.GetNotifications(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, n := range nl {
 | 
				
			||||||
 | 
							err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Status(http.StatusResetContent)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Status(http.StatusResetContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								routers/api/v1/notify/threads.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								routers/api/v1/notify/threads.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					// 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 (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetThread get notification by ID
 | 
				
			||||||
 | 
					func GetThread(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation GET /notifications/threads/{id} notification notifyGetThread
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Get notification thread by ID
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: id
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: id of notification thread
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "200":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/NotificationThread"
 | 
				
			||||||
 | 
						//   "403":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/forbidden"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := getThread(ctx)
 | 
				
			||||||
 | 
						if n == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := n.LoadAttributes(); err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, n.APIFormat())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadThread mark notification as read by ID
 | 
				
			||||||
 | 
					func ReadThread(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation PATCH /notifications/threads/{id} notification notifyReadThread
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Mark notification thread as read by ID
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: id
 | 
				
			||||||
 | 
						//   in: path
 | 
				
			||||||
 | 
						//   description: id of notification thread
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: true
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "205":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
						//   "403":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/forbidden"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						n := getThread(ctx)
 | 
				
			||||||
 | 
						if n == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ctx.Status(http.StatusResetContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getThread(ctx *context.APIContext) *models.Notification {
 | 
				
			||||||
 | 
						n, err := models.GetNotificationByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrNotExist(err) {
 | 
				
			||||||
 | 
								ctx.Error(http.StatusNotFound, "GetNotificationByID", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if n.UserID != ctx.User.ID && !ctx.User.IsAdmin {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusForbidden, "GetNotificationByID", fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID))
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return n
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								routers/api/v1/notify/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								routers/api/v1/notify/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					// 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"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/api/v1/utils"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListNotifications list users's notification threads
 | 
				
			||||||
 | 
					func ListNotifications(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation GET /notifications notification notifyGetList
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: List users's notification threads
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: all
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: If true, show notifications marked as read. Default value is false
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// - name: since
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// - name: before
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "200":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/NotificationThreadList"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						before, since, err := utils.GetQueryBeforeSince(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opts := models.FindNotificationOptions{
 | 
				
			||||||
 | 
							UserID:            ctx.User.ID,
 | 
				
			||||||
 | 
							UpdatedBeforeUnix: before,
 | 
				
			||||||
 | 
							UpdatedAfterUnix:  since,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						qAll := strings.Trim(ctx.Query("all"), " ")
 | 
				
			||||||
 | 
						if qAll != "true" {
 | 
				
			||||||
 | 
							opts.Status = models.NotificationStatusUnread
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nl, err := models.GetNotifications(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = nl.LoadAttributes()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(http.StatusOK, nl.APIFormat())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadNotifications mark notification threads as read
 | 
				
			||||||
 | 
					func ReadNotifications(ctx *context.APIContext) {
 | 
				
			||||||
 | 
						// swagger:operation PUT /notifications notification notifyReadList
 | 
				
			||||||
 | 
						// ---
 | 
				
			||||||
 | 
						// summary: Mark notification threads as read
 | 
				
			||||||
 | 
						// consumes:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// produces:
 | 
				
			||||||
 | 
						// - application/json
 | 
				
			||||||
 | 
						// parameters:
 | 
				
			||||||
 | 
						// - name: last_read_at
 | 
				
			||||||
 | 
						//   in: query
 | 
				
			||||||
 | 
						//   description: Describes the last point that notifications were checked. Anything updated since this time will not be updated.
 | 
				
			||||||
 | 
						//   type: string
 | 
				
			||||||
 | 
						//   format: date-time
 | 
				
			||||||
 | 
						//   required: false
 | 
				
			||||||
 | 
						// responses:
 | 
				
			||||||
 | 
						//   "205":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lastRead := int64(0)
 | 
				
			||||||
 | 
						qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
 | 
				
			||||||
 | 
						if len(qLastRead) > 0 {
 | 
				
			||||||
 | 
							tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !tmpLastRead.IsZero() {
 | 
				
			||||||
 | 
								lastRead = tmpLastRead.Unix()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opts := models.FindNotificationOptions{
 | 
				
			||||||
 | 
							UserID:            ctx.User.ID,
 | 
				
			||||||
 | 
							UpdatedBeforeUnix: lastRead,
 | 
				
			||||||
 | 
							Status:            models.NotificationStatusUnread,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						nl, err := models.GetNotifications(opts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.InternalServerError(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, n := range nl {
 | 
				
			||||||
 | 
							err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.InternalServerError(err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Status(http.StatusResetContent)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Status(http.StatusResetContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -136,6 +136,8 @@ func GetPullRequest(ctx *context.APIContext) {
 | 
				
			|||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/PullRequest"
 | 
						//     "$ref": "#/responses/PullRequest"
 | 
				
			||||||
 | 
						//   "404":
 | 
				
			||||||
 | 
						//     "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
						pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								routers/api/v1/swagger/notify.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								routers/api/v1/swagger/notify.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					// Copyright 2019 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 swagger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						api "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotificationThread
 | 
				
			||||||
 | 
					// swagger:response NotificationThread
 | 
				
			||||||
 | 
					type swaggerNotificationThread struct {
 | 
				
			||||||
 | 
						// in:body
 | 
				
			||||||
 | 
						Body api.NotificationThread `json:"body"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotificationThreadList
 | 
				
			||||||
 | 
					// swagger:response NotificationThreadList
 | 
				
			||||||
 | 
					type swaggerNotificationThreadList struct {
 | 
				
			||||||
 | 
						// in:body
 | 
				
			||||||
 | 
						Body []api.NotificationThread `json:"body"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -425,6 +425,143 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/notifications": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "List users's notification threads",
 | 
				
			||||||
 | 
					        "operationId": "notifyGetList",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "If true, show notifications marked as read. Default value is false",
 | 
				
			||||||
 | 
					            "name": "all",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
 | 
				
			||||||
 | 
					            "name": "since",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
 | 
				
			||||||
 | 
					            "name": "before",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/NotificationThreadList"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "put": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Mark notification threads as read",
 | 
				
			||||||
 | 
					        "operationId": "notifyReadList",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
 | 
				
			||||||
 | 
					            "name": "last_read_at",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "205": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "/notifications/threads/{id}": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Get notification thread by ID",
 | 
				
			||||||
 | 
					        "operationId": "notifyGetThread",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "id of notification thread",
 | 
				
			||||||
 | 
					            "name": "id",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/NotificationThread"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "403": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/forbidden"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "patch": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Mark notification thread as read by ID",
 | 
				
			||||||
 | 
					        "operationId": "notifyReadThread",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "id of notification thread",
 | 
				
			||||||
 | 
					            "name": "id",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "205": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "403": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/forbidden"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/org/{org}/repos": {
 | 
					    "/org/{org}/repos": {
 | 
				
			||||||
      "post": {
 | 
					      "post": {
 | 
				
			||||||
        "consumes": [
 | 
					        "consumes": [
 | 
				
			||||||
@@ -5231,6 +5368,103 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "/repos/{owner}/{repo}/notifications": {
 | 
				
			||||||
 | 
					      "get": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "List users's notification threads on a specific repo",
 | 
				
			||||||
 | 
					        "operationId": "notifyGetRepoList",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "If true, show notifications marked as read. Default value is false",
 | 
				
			||||||
 | 
					            "name": "all",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Only show notifications updated after the given time. This is a timestamp in RFC 3339 format",
 | 
				
			||||||
 | 
					            "name": "since",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Only show notifications updated before the given time. This is a timestamp in RFC 3339 format",
 | 
				
			||||||
 | 
					            "name": "before",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/NotificationThreadList"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "put": {
 | 
				
			||||||
 | 
					        "consumes": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "produces": [
 | 
				
			||||||
 | 
					          "application/json"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "notification"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "summary": "Mark notification threads as read on a specific repo",
 | 
				
			||||||
 | 
					        "operationId": "notifyReadRepoList",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "owner of the repo",
 | 
				
			||||||
 | 
					            "name": "owner",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "description": "name of the repo",
 | 
				
			||||||
 | 
					            "name": "repo",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "type": "string",
 | 
				
			||||||
 | 
					            "format": "date-time",
 | 
				
			||||||
 | 
					            "description": "Describes the last point that notifications were checked. Anything updated since this time will not be updated.",
 | 
				
			||||||
 | 
					            "name": "last_read_at",
 | 
				
			||||||
 | 
					            "in": "query"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "205": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "/repos/{owner}/{repo}/pulls": {
 | 
					    "/repos/{owner}/{repo}/pulls": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
@@ -5397,6 +5631,9 @@
 | 
				
			|||||||
        "responses": {
 | 
					        "responses": {
 | 
				
			||||||
          "200": {
 | 
					          "200": {
 | 
				
			||||||
            "$ref": "#/responses/PullRequest"
 | 
					            "$ref": "#/responses/PullRequest"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/notFound"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -10584,6 +10821,64 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "NotificationSubject": {
 | 
				
			||||||
 | 
					      "description": "NotificationSubject contains the notification subject (Issue/Pull/Commit)",
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "latest_comment_url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "LatestCommentURL"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Title"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "type": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Type"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "URL"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "NotificationThread": {
 | 
				
			||||||
 | 
					      "description": "NotificationThread expose Notification on API",
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "ID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "pinned": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "Pinned"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "repository": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/Repository"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "subject": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/NotificationSubject"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "unread": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "Unread"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "updated_at": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time",
 | 
				
			||||||
 | 
					          "x-go-name": "UpdatedAt"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "url": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "URL"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Organization": {
 | 
					    "Organization": {
 | 
				
			||||||
      "description": "Organization represents an organization",
 | 
					      "description": "Organization represents an organization",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
@@ -12012,6 +12307,21 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "NotificationThread": {
 | 
				
			||||||
 | 
					      "description": "NotificationThread",
 | 
				
			||||||
 | 
					      "schema": {
 | 
				
			||||||
 | 
					        "$ref": "#/definitions/NotificationThread"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "NotificationThreadList": {
 | 
				
			||||||
 | 
					      "description": "NotificationThreadList",
 | 
				
			||||||
 | 
					      "schema": {
 | 
				
			||||||
 | 
					        "type": "array",
 | 
				
			||||||
 | 
					        "items": {
 | 
				
			||||||
 | 
					          "$ref": "#/definitions/NotificationThread"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Organization": {
 | 
					    "Organization": {
 | 
				
			||||||
      "description": "Organization",
 | 
					      "description": "Organization",
 | 
				
			||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user