62 changed files with 4922 additions and 2713 deletions
@ -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) |
||||
} |
@ -0,0 +1,17 @@
|
||||
// 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 migrations |
||||
|
||||
import "xorm.io/xorm" |
||||
|
||||
func addIsRestricted(x *xorm.Engine) error { |
||||
// User see models/user.go
|
||||
type User struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
IsRestricted bool `xorm:"NOT NULL DEFAULT false"` |
||||
} |
||||
|
||||
return x.Sync2(new(User)) |
||||
} |
@ -1,86 +0,0 @@
|
||||
// Copyright 2016 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 models |
||||
|
||||
import ( |
||||
"container/list" |
||||
"testing" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/modules/git" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestCommitToPushCommit(t *testing.T) { |
||||
now := time.Now() |
||||
sig := &git.Signature{ |
||||
Email: "example@example.com", |
||||
Name: "John Doe", |
||||
When: now, |
||||
} |
||||
const hexString = "0123456789abcdef0123456789abcdef01234567" |
||||
sha1, err := git.NewIDFromString(hexString) |
||||
assert.NoError(t, err) |
||||
pushCommit := CommitToPushCommit(&git.Commit{ |
||||
ID: sha1, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Commit Message", |
||||
}) |
||||
assert.Equal(t, hexString, pushCommit.Sha1) |
||||
assert.Equal(t, "Commit Message", pushCommit.Message) |
||||
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail) |
||||
assert.Equal(t, "John Doe", pushCommit.AuthorName) |
||||
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail) |
||||
assert.Equal(t, "John Doe", pushCommit.CommitterName) |
||||
assert.Equal(t, now, pushCommit.Timestamp) |
||||
} |
||||
|
||||
func TestListToPushCommits(t *testing.T) { |
||||
now := time.Now() |
||||
sig := &git.Signature{ |
||||
Email: "example@example.com", |
||||
Name: "John Doe", |
||||
When: now, |
||||
} |
||||
|
||||
const hexString1 = "0123456789abcdef0123456789abcdef01234567" |
||||
hash1, err := git.NewIDFromString(hexString1) |
||||
assert.NoError(t, err) |
||||
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98" |
||||
hash2, err := git.NewIDFromString(hexString2) |
||||
assert.NoError(t, err) |
||||
|
||||
l := list.New() |
||||
l.PushBack(&git.Commit{ |
||||
ID: hash1, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Message1", |
||||
}) |
||||
l.PushBack(&git.Commit{ |
||||
ID: hash2, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Message2", |
||||
}) |
||||
|
||||
pushCommits := ListToPushCommits(l) |
||||
assert.Equal(t, 2, pushCommits.Len) |
||||
if assert.Len(t, pushCommits.Commits, 2) { |
||||
assert.Equal(t, "Message1", pushCommits.Commits[0].Message) |
||||
assert.Equal(t, hexString1, pushCommits.Commits[0].Sha1) |
||||
assert.Equal(t, "example@example.com", pushCommits.Commits[0].AuthorEmail) |
||||
assert.Equal(t, now, pushCommits.Commits[0].Timestamp) |
||||
|
||||
assert.Equal(t, "Message2", pushCommits.Commits[1].Message) |
||||
assert.Equal(t, hexString2, pushCommits.Commits[1].Sha1) |
||||
assert.Equal(t, "example@example.com", pushCommits.Commits[1].AuthorEmail) |
||||
assert.Equal(t, now, pushCommits.Commits[1].Timestamp) |
||||
} |
||||
} |
||||
|
||||
// TODO TestPushUpdate
|
@ -0,0 +1,16 @@
|
||||
// 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 convert |
||||
|
||||
import ( |
||||
"path/filepath" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
) |
||||
|
||||
func TestMain(m *testing.M) { |
||||
models.MainTest(m, filepath.Join("..", "..")) |
||||
} |
@ -0,0 +1,141 @@
|
||||
// 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 convert |
||||
|
||||
import ( |
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/modules/git" |
||||
"code.gitea.io/gitea/modules/log" |
||||
api "code.gitea.io/gitea/modules/structs" |
||||
) |
||||
|
||||
// ToAPIPullRequest assumes following fields have been assigned with valid values:
|
||||
// Required - Issue
|
||||
// Optional - Merger
|
||||
func ToAPIPullRequest(pr *models.PullRequest) *api.PullRequest { |
||||
var ( |
||||
baseBranch *git.Branch |
||||
headBranch *git.Branch |
||||
baseCommit *git.Commit |
||||
headCommit *git.Commit |
||||
err error |
||||
) |
||||
if err = pr.Issue.LoadRepo(); err != nil { |
||||
log.Error("loadRepo[%d]: %v", pr.ID, err) |
||||
return nil |
||||
} |
||||
apiIssue := pr.Issue.APIFormat() |
||||
if pr.BaseRepo == nil { |
||||
pr.BaseRepo, err = models.GetRepositoryByID(pr.BaseRepoID) |
||||
if err != nil { |
||||
log.Error("GetRepositoryById[%d]: %v", pr.ID, err) |
||||
return nil |
||||
} |
||||
} |
||||
if pr.HeadRepo == nil { |
||||
pr.HeadRepo, err = models.GetRepositoryByID(pr.HeadRepoID) |
||||
if err != nil { |
||||
log.Error("GetRepositoryById[%d]: %v", pr.ID, err) |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
if err = pr.Issue.LoadRepo(); err != nil { |
||||
log.Error("pr.Issue.loadRepo[%d]: %v", pr.ID, err) |
||||
return nil |
||||
} |
||||
|
||||
apiPullRequest := &api.PullRequest{ |
||||
ID: pr.ID, |
||||
URL: pr.Issue.HTMLURL(), |
||||
Index: pr.Index, |
||||
Poster: apiIssue.Poster, |
||||
Title: apiIssue.Title, |
||||
Body: apiIssue.Body, |
||||
Labels: apiIssue.Labels, |
||||
Milestone: apiIssue.Milestone, |
||||
Assignee: apiIssue.Assignee, |
||||
Assignees: apiIssue.Assignees, |
||||
State: apiIssue.State, |
||||
Comments: apiIssue.Comments, |
||||
HTMLURL: pr.Issue.HTMLURL(), |
||||
DiffURL: pr.Issue.DiffURL(), |
||||
PatchURL: pr.Issue.PatchURL(), |
||||
HasMerged: pr.HasMerged, |
||||
MergeBase: pr.MergeBase, |
||||
Deadline: apiIssue.Deadline, |
||||
Created: pr.Issue.CreatedUnix.AsTimePtr(), |
||||
Updated: pr.Issue.UpdatedUnix.AsTimePtr(), |
||||
} |
||||
baseBranch, err = pr.BaseRepo.GetBranch(pr.BaseBranch) |
||||
if err != nil { |
||||
if git.IsErrBranchNotExist(err) { |
||||
apiPullRequest.Base = nil |
||||
} else { |
||||
log.Error("GetBranch[%s]: %v", pr.BaseBranch, err) |
||||
return nil |
||||
} |
||||
} else { |
||||
apiBaseBranchInfo := &api.PRBranchInfo{ |
||||
Name: pr.BaseBranch, |
||||
Ref: pr.BaseBranch, |
||||
RepoID: pr.BaseRepoID, |
||||
Repository: pr.BaseRepo.APIFormat(models.AccessModeNone), |
||||
} |
||||
baseCommit, err = baseBranch.GetCommit() |
||||
if err != nil { |
||||
if git.IsErrNotExist(err) { |
||||
apiBaseBranchInfo.Sha = "" |
||||
} else { |
||||
log.Error("GetCommit[%s]: %v", baseBranch.Name, err) |
||||
return nil |
||||
} |
||||
} else { |
||||
apiBaseBranchInfo.Sha = baseCommit.ID.String() |
||||
} |
||||
apiPullRequest.Base = apiBaseBranchInfo |
||||
} |
||||
|
||||
headBranch, err = pr.HeadRepo.GetBranch(pr.HeadBranch) |
||||
if err != nil { |
||||
if git.IsErrBranchNotExist(err) { |
||||
apiPullRequest.Head = nil |
||||
} else { |
||||
log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) |
||||
return nil |
||||
} |
||||
} else { |
||||
apiHeadBranchInfo := &api.PRBranchInfo{ |
||||
Name: pr.HeadBranch, |
||||
Ref: pr.HeadBranch, |
||||
RepoID: pr.HeadRepoID, |
||||
Repository: pr.HeadRepo.APIFormat(models.AccessModeNone), |
||||
} |
||||
headCommit, err = headBranch.GetCommit() |
||||
if err != nil { |
||||
if git.IsErrNotExist(err) { |
||||
apiHeadBranchInfo.Sha = "" |
||||
} else { |
||||
log.Error("GetCommit[%s]: %v", headBranch.Name, err) |
||||
return nil |
||||
} |
||||
} else { |
||||
apiHeadBranchInfo.Sha = headCommit.ID.String() |
||||
} |
||||
apiPullRequest.Head = apiHeadBranchInfo |
||||
} |
||||
|
||||
if pr.Status != models.PullRequestStatusChecking { |
||||
mergeable := !(pr.Status == models.PullRequestStatusConflict || pr.Status == models.PullRequestStatusError) && !pr.IsWorkInProgress() |
||||
apiPullRequest.Mergeable = mergeable |
||||
} |
||||
if pr.HasMerged { |
||||
apiPullRequest.Merged = pr.MergedUnix.AsTimePtr() |
||||
apiPullRequest.MergedCommitID = &pr.MergedCommitID |
||||
apiPullRequest.MergedBy = pr.Merger.APIFormat() |
||||
} |
||||
|
||||
return apiPullRequest |
||||
} |
@ -0,0 +1,23 @@
|
||||
// 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 convert |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestPullRequest_APIFormat(t *testing.T) { |
||||
assert.NoError(t, models.PrepareTestDatabase()) |
||||
pr := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) |
||||
assert.NoError(t, pr.LoadAttributes()) |
||||
assert.NoError(t, pr.LoadIssue()) |
||||
apiPullRequest := ToAPIPullRequest(pr) |
||||
assert.NotNil(t, apiPullRequest) |
||||
assert.Nil(t, apiPullRequest.Head) |
||||
} |
@ -0,0 +1,168 @@
|
||||
// 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 repository |
||||
|
||||
import ( |
||||
"container/list" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/modules/base" |
||||
"code.gitea.io/gitea/modules/git" |
||||
"code.gitea.io/gitea/modules/log" |
||||
api "code.gitea.io/gitea/modules/structs" |
||||
) |
||||
|
||||
// PushCommit represents a commit in a push operation.
|
||||
type PushCommit struct { |
||||
Sha1 string |
||||
Message string |
||||
AuthorEmail string |
||||
AuthorName string |
||||
CommitterEmail string |
||||
CommitterName string |
||||
Timestamp time.Time |
||||
} |
||||
|
||||
// PushCommits represents list of commits in a push operation.
|
||||
type PushCommits struct { |
||||
Len int |
||||
Commits []*PushCommit |
||||
CompareURL string |
||||
|
||||
avatars map[string]string |
||||
emailUsers map[string]*models.User |
||||
} |
||||
|
||||
// NewPushCommits creates a new PushCommits object.
|
||||
func NewPushCommits() *PushCommits { |
||||
return &PushCommits{ |
||||
avatars: make(map[string]string), |
||||
emailUsers: make(map[string]*models.User), |
||||
} |
||||
} |
||||
|
||||
// ToAPIPayloadCommits converts a PushCommits object to
|
||||
// api.PayloadCommit format.
|
||||
func (pc *PushCommits) ToAPIPayloadCommits(repoPath, repoLink string) ([]*api.PayloadCommit, error) { |
||||
commits := make([]*api.PayloadCommit, len(pc.Commits)) |
||||
|
||||
if pc.emailUsers == nil { |
||||
pc.emailUsers = make(map[string]*models.User) |
||||
} |
||||
var err error |
||||
for i, commit := range pc.Commits { |
||||
authorUsername := "" |
||||
author, ok := pc.emailUsers[commit.AuthorEmail] |
||||
if !ok { |
||||
author, err = models.GetUserByEmail(commit.AuthorEmail) |
||||
if err == nil { |
||||
authorUsername = author.Name |
||||
pc.emailUsers[commit.AuthorEmail] = author |
||||
} |
||||
} else { |
||||
authorUsername = author.Name |
||||
} |
||||
|
||||
committerUsername := "" |
||||
committer, ok := pc.emailUsers[commit.CommitterEmail] |
||||
if !ok { |
||||
committer, err = models.GetUserByEmail(commit.CommitterEmail) |
||||
if err == nil { |
||||
// TODO: check errors other than email not found.
|
||||
committerUsername = committer.Name |
||||
pc.emailUsers[commit.CommitterEmail] = committer |
||||
} |
||||
} else { |
||||
committerUsername = committer.Name |
||||
} |
||||
|
||||
fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err) |
||||
} |
||||
|
||||
commits[i] = &api.PayloadCommit{ |
||||
ID: commit.Sha1, |
||||
Message: commit.Message, |
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1), |
||||
Author: &api.PayloadUser{ |
||||
Name: commit.AuthorName, |
||||
Email: commit.AuthorEmail, |
||||
UserName: authorUsername, |
||||
}, |
||||
Committer: &api.PayloadUser{ |
||||
Name: commit.CommitterName, |
||||
Email: commit.CommitterEmail, |
||||
UserName: committerUsername, |
||||
}, |
||||
Added: fileStatus.Added, |
||||
Removed: fileStatus.Removed, |
||||
Modified: fileStatus.Modified, |
||||
Timestamp: commit.Timestamp, |
||||
} |
||||
} |
||||
return commits, nil |
||||
} |
||||
|
||||
// AvatarLink tries to match user in database with e-mail
|
||||
// in order to show custom avatar, and falls back to general avatar link.
|
||||
func (pc *PushCommits) AvatarLink(email string) string { |
||||
if pc.avatars == nil { |
||||
pc.avatars = make(map[string]string) |
||||
} |
||||
avatar, ok := pc.avatars[email] |
||||
if ok { |
||||
return avatar |
||||
} |
||||
|
||||
u, ok := pc.emailUsers[email] |
||||
if !ok { |
||||
var err error |
||||
u, err = models.GetUserByEmail(email) |
||||
if err != nil { |
||||
pc.avatars[email] = base.AvatarLink(email) |
||||
if !models.IsErrUserNotExist(err) { |
||||
log.Error("GetUserByEmail: %v", err) |
||||
return "" |
||||
} |
||||
} else { |
||||
pc.emailUsers[email] = u |
||||
} |
||||
} |
||||
if u != nil { |
||||
pc.avatars[email] = u.RelAvatarLink() |
||||
} |
||||
|
||||
return pc.avatars[email] |
||||
} |
||||
|
||||
// CommitToPushCommit transforms a git.Commit to PushCommit type.
|
||||
func CommitToPushCommit(commit *git.Commit) *PushCommit { |
||||
return &PushCommit{ |
||||
Sha1: commit.ID.String(), |
||||
Message: commit.Message(), |
||||
AuthorEmail: commit.Author.Email, |
||||
AuthorName: commit.Author.Name, |
||||
CommitterEmail: commit.Committer.Email, |
||||
CommitterName: commit.Committer.Name, |
||||
Timestamp: commit.Author.When, |
||||
} |
||||
} |
||||
|
||||
// ListToPushCommits transforms a list.List to PushCommits type.
|
||||
func ListToPushCommits(l *list.List) *PushCommits { |
||||
var commits []*PushCommit |
||||
var actEmail string |
||||
for e := l.Front(); e != nil; e = e.Next() { |
||||
commit := e.Value.(*git.Commit) |
||||
if actEmail == "" { |
||||
actEmail = commit.Committer.Email |
||||
} |
||||
commits = append(commits, CommitToPushCommit(commit)) |
||||
} |
||||
return &PushCommits{l.Len(), commits, "", make(map[string]string), make(map[string]*models.User)} |
||||
} |
@ -0,0 +1,190 @@
|
||||
// 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 repository |
||||
|
||||
import ( |
||||
"container/list" |
||||
"testing" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
"code.gitea.io/gitea/modules/git" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { |
||||
assert.NoError(t, models.PrepareTestDatabase()) |
||||
|
||||
pushCommits := NewPushCommits() |
||||
pushCommits.Commits = []*PushCommit{ |
||||
{ |
||||
Sha1: "69554a6", |
||||
CommitterEmail: "user2@example.com", |
||||
CommitterName: "User2", |
||||
AuthorEmail: "user2@example.com", |
||||
AuthorName: "User2", |
||||
Message: "not signed commit", |
||||
}, |
||||
{ |
||||
Sha1: "27566bd", |
||||
CommitterEmail: "user2@example.com", |
||||
CommitterName: "User2", |
||||
AuthorEmail: "user2@example.com", |
||||
AuthorName: "User2", |
||||
Message: "good signed commit (with not yet validated email)", |
||||
}, |
||||
{ |
||||
Sha1: "5099b81", |
||||
CommitterEmail: "user2@example.com", |
||||
CommitterName: "User2", |
||||
AuthorEmail: "user2@example.com", |
||||
AuthorName: "User2", |
||||
Message: "good signed commit", |
||||
}, |
||||
} |
||||
pushCommits.Len = len(pushCommits.Commits) |
||||
|
||||
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) |
||||
payloadCommits, err := pushCommits.ToAPIPayloadCommits(repo.RepoPath(), "/user2/repo16") |
||||
assert.NoError(t, err) |
||||
assert.EqualValues(t, 3, len(payloadCommits)) |
||||
|
||||
assert.Equal(t, "69554a6", payloadCommits[0].ID) |
||||
assert.Equal(t, "not signed commit", payloadCommits[0].Message) |
||||
assert.Equal(t, "/user2/repo16/commit/69554a6", payloadCommits[0].URL) |
||||
assert.Equal(t, "User2", payloadCommits[0].Committer.Name) |
||||
assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) |
||||
assert.Equal(t, "User2", payloadCommits[0].Author.Name) |
||||
assert.Equal(t, "user2", payloadCommits[0].Author.UserName) |
||||
assert.EqualValues(t, []string{}, payloadCommits[0].Added) |
||||
assert.EqualValues(t, []string{}, payloadCommits[0].Removed) |
||||
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified) |
||||
|
||||
assert.Equal(t, "27566bd", payloadCommits[1].ID) |
||||
assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message) |
||||
assert.Equal(t, "/user2/repo16/commit/27566bd", payloadCommits[1].URL) |
||||
assert.Equal(t, "User2", payloadCommits[1].Committer.Name) |
||||
assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) |
||||
assert.Equal(t, "User2", payloadCommits[1].Author.Name) |
||||
assert.Equal(t, "user2", payloadCommits[1].Author.UserName) |
||||
assert.EqualValues(t, []string{}, payloadCommits[1].Added) |
||||
assert.EqualValues(t, []string{}, payloadCommits[1].Removed) |
||||
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified) |
||||
|
||||
assert.Equal(t, "5099b81", payloadCommits[2].ID) |
||||
assert.Equal(t, "good signed commit", payloadCommits[2].Message) |
||||
assert.Equal(t, "/user2/repo16/commit/5099b81", payloadCommits[2].URL) |
||||
assert.Equal(t, "User2", payloadCommits[2].Committer.Name) |
||||
assert.Equal(t, "user2", payloadCommits[2].Committer.UserName) |
||||
assert.Equal(t, "User2", payloadCommits[2].Author.Name) |
||||
assert.Equal(t, "user2", payloadCommits[2].Author.UserName) |
||||
assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added) |
||||
assert.EqualValues(t, []string{}, payloadCommits[2].Removed) |
||||
assert.EqualValues(t, []string{}, payloadCommits[2].Modified) |
||||
} |
||||
|
||||
func TestPushCommits_AvatarLink(t *testing.T) { |
||||
assert.NoError(t, models.PrepareTestDatabase()) |
||||
|
||||
pushCommits := NewPushCommits() |
||||
pushCommits.Commits = []*PushCommit{ |
||||
{ |
||||
Sha1: "abcdef1", |
||||
CommitterEmail: "user2@example.com", |
||||
CommitterName: "User Two", |
||||
AuthorEmail: "user4@example.com", |
||||
AuthorName: "User Four", |
||||
Message: "message1", |
||||
}, |
||||
{ |
||||
Sha1: "abcdef2", |
||||
CommitterEmail: "user2@example.com", |
||||
CommitterName: "User Two", |
||||
AuthorEmail: "user2@example.com", |
||||
AuthorName: "User Two", |
||||
Message: "message2", |
||||
}, |
||||
} |
||||
pushCommits.Len = len(pushCommits.Commits) |
||||
|
||||
assert.Equal(t, |
||||
"/user/avatar/user2/-1", |
||||
pushCommits.AvatarLink("user2@example.com")) |
||||
|
||||
assert.Equal(t, |
||||
"https://secure.gravatar.com/avatar/19ade630b94e1e0535b3df7387434154?d=identicon", |
||||
pushCommits.AvatarLink("nonexistent@example.com")) |
||||
} |
||||
|
||||
func TestCommitToPushCommit(t *testing.T) { |
||||
now := time.Now() |
||||
sig := &git.Signature{ |
||||
Email: "example@example.com", |
||||
Name: "John Doe", |
||||
When: now, |
||||
} |
||||
const hexString = "0123456789abcdef0123456789abcdef01234567" |
||||
sha1, err := git.NewIDFromString(hexString) |
||||
assert.NoError(t, err) |
||||
pushCommit := CommitToPushCommit(&git.Commit{ |
||||
ID: sha1, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Commit Message", |
||||
}) |
||||
assert.Equal(t, hexString, pushCommit.Sha1) |
||||
assert.Equal(t, "Commit Message", pushCommit.Message) |
||||
assert.Equal(t, "example@example.com", pushCommit.AuthorEmail) |
||||
assert.Equal(t, "John Doe", pushCommit.AuthorName) |
||||
assert.Equal(t, "example@example.com", pushCommit.CommitterEmail) |
||||
assert.Equal(t, "John Doe", pushCommit.CommitterName) |
||||
assert.Equal(t, now, pushCommit.Timestamp) |
||||
} |
||||
|
||||
func TestListToPushCommits(t *testing.T) { |
||||
now := time.Now() |
||||
sig := &git.Signature{ |
||||
Email: "example@example.com", |
||||
Name: "John Doe", |
||||
When: now, |
||||
} |
||||
|
||||
const hexString1 = "0123456789abcdef0123456789abcdef01234567" |
||||
hash1, err := git.NewIDFromString(hexString1) |
||||
assert.NoError(t, err) |
||||
const hexString2 = "fedcba9876543210fedcba9876543210fedcba98" |
||||
hash2, err := git.NewIDFromString(hexString2) |
||||
assert.NoError(t, err) |
||||
|
||||
l := list.New() |
||||
l.PushBack(&git.Commit{ |
||||
ID: hash1, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Message1", |
||||
}) |
||||
l.PushBack(&git.Commit{ |
||||
ID: hash2, |
||||
Author: sig, |
||||
Committer: sig, |
||||
CommitMessage: "Message2", |
||||
}) |
||||
|
||||
pushCommits := ListToPushCommits(l) |
||||
assert.Equal(t, 2, pushCommits.Len) |
||||
if assert.Len(t, pushCommits.Commits, 2) { |
||||
assert.Equal(t, "Message1", pushCommits.Commits[0].Message) |
||||
assert.Equal(t, hexString1, pushCommits.Commits[0].Sha1) |
||||
assert.Equal(t, "example@example.com", pushCommits.Commits[0].AuthorEmail) |
||||
assert.Equal(t, now, pushCommits.Commits[0].Timestamp) |
||||
|
||||
assert.Equal(t, "Message2", pushCommits.Commits[1].Message) |
||||
assert.Equal(t, hexString2, pushCommits.Commits[1].Sha1) |
||||
assert.Equal(t, "example@example.com", pushCommits.Commits[1].AuthorEmail) |
||||
assert.Equal(t, now, pushCommits.Commits[1].Timestamp) |
||||
} |
||||
} |
||||
|
||||
// TODO TestPushUpdate
|
@ -0,0 +1,16 @@
|
||||
// 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 repository |
||||
|
||||
import ( |
||||
"path/filepath" |
||||
"testing" |
||||
|
||||
"code.gitea.io/gitea/models" |
||||
) |
||||
|
||||
func TestMain(m *testing.M) { |
||||
models.MainTest(m, filepath.Join("..", "..")) |
||||
} |
@ -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)"` |
||||
} |
@ -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) |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
@ -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"` |
||||
} |
Loading…
Reference in new issue