#1692 add CRUD issue APIs

- Fix go-gogs-client#10
- Related to #809
This commit is contained in:
Unknwon 2016-03-13 23:20:22 -04:00
parent dd6faf7f9b
commit 9bd9ad4205
23 changed files with 406 additions and 140 deletions

View file

@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true) ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
##### Current version: 0.9.8 ##### Current version: 0.9.9
| Web | UI | Preview | | Web | UI | Preview |
|:-------------:|:-------:|:-------:| |:-------------:|:-------:|:-------:|

View file

@ -88,7 +88,7 @@ func checkVersion() {
{"gopkg.in/ini.v1", ini.Version, "1.8.4"}, {"gopkg.in/ini.v1", ini.Version, "1.8.4"},
{"gopkg.in/macaron.v1", macaron.Version, "1.1.2"}, {"gopkg.in/macaron.v1", macaron.Version, "1.1.2"},
{"github.com/gogits/git-module", git.Version, "0.2.9"}, {"github.com/gogits/git-module", git.Version, "0.2.9"},
{"github.com/gogits/go-gogs-client", gogs.Version, "0.7.3"}, {"github.com/gogits/go-gogs-client", gogs.Version, "0.7.4"},
} }
for _, c := range checkers { for _, c := range checkers {
if !version.Compare(c.Version(), c.Expected, ">=") { if !version.Compare(c.Version(), c.Expected, ">=") {

4
glide.lock generated
View file

@ -35,7 +35,7 @@ imports:
- name: github.com/go-xorm/core - name: github.com/go-xorm/core
version: 502158401cde814951eae62f064d9e5ff39e13ce version: 502158401cde814951eae62f064d9e5ff39e13ce
- name: github.com/go-xorm/xorm - name: github.com/go-xorm/xorm
version: bb8c5c04d7d65453c3f0176a307276857dccc3b0 version: 99686972253975273816d1ebb4cdb0a53bf39100
- name: github.com/gogits/chardet - name: github.com/gogits/chardet
version: 2404f777256163ea3eadb273dada5dcb037993c0 version: 2404f777256163ea3eadb273dada5dcb037993c0
- name: github.com/gogits/cron - name: github.com/gogits/cron
@ -43,7 +43,7 @@ imports:
- name: github.com/gogits/git-module - name: github.com/gogits/git-module
version: 76e8cce6c7ef3ba1cf75752261c721ebf14cd129 version: 76e8cce6c7ef3ba1cf75752261c721ebf14cd129
- name: github.com/gogits/go-gogs-client - name: github.com/gogits/go-gogs-client
version: d584b1e0fb4d8ad0e8cf2fae2368838f2526b408 version: 788ec59749df076b98e208909b44fdef02779deb
- name: github.com/issue9/identicon - name: github.com/issue9/identicon
version: f8c0d2ce04db79c663b1da33d3a9f62be753ee88 version: f8c0d2ce04db79c663b1da33d3a9f62be753ee88
- name: github.com/klauspost/compress - name: github.com/klauspost/compress

View file

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.9.8.0312" const APP_VER = "0.9.9.0313"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

View file

@ -88,6 +88,23 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err) log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err)
} }
i.Labels, err = GetLabelsByIssueID(i.ID)
if err != nil {
log.Error(3, "GetLabelsByIssueID[%d]: %v", i.ID, err)
}
case "poster_id":
i.Poster, err = GetUserByID(i.PosterID)
if err != nil {
if IsErrUserNotExist(err) {
i.PosterID = -1
i.Poster = NewFakeUser()
} else {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
}
return
}
case "milestone_id": case "milestone_id":
if i.MilestoneID == 0 { if i.MilestoneID == 0 {
return return
@ -97,6 +114,7 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
if err != nil { if err != nil {
log.Error(3, "GetMilestoneById[%d]: %v", i.ID, err) log.Error(3, "GetMilestoneById[%d]: %v", i.ID, err)
} }
case "assignee_id": case "assignee_id":
if i.AssigneeID == 0 { if i.AssigneeID == 0 {
return return
@ -106,6 +124,7 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
if err != nil { if err != nil {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err) log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
} }
case "deadline_unix": case "deadline_unix":
i.Deadline = time.Unix(i.DeadlineUnix, 0).Local() i.Deadline = time.Unix(i.DeadlineUnix, 0).Local()
case "created_unix": case "created_unix":
@ -120,21 +139,19 @@ func (i *Issue) HashTag() string {
return "issue-" + com.ToStr(i.ID) return "issue-" + com.ToStr(i.ID)
} }
// State returns string representation of issue status.
func (i *Issue) State() string {
if i.IsClosed {
return "closed"
}
return "open"
}
// IsPoster returns true if given user by ID is the poster. // IsPoster returns true if given user by ID is the poster.
func (i *Issue) IsPoster(uid int64) bool { func (i *Issue) IsPoster(uid int64) bool {
return i.PosterID == uid return i.PosterID == uid
} }
func (i *Issue) GetPoster() (err error) {
i.Poster, err = GetUserByID(i.PosterID)
if IsErrUserNotExist(err) {
i.PosterID = -1
i.Poster = NewFakeUser()
return nil
}
return err
}
func (i *Issue) hasLabel(e Engine, labelID int64) bool { func (i *Issue) hasLabel(e Engine, labelID int64) bool {
return hasIssueLabel(e, i.ID, labelID) return hasIssueLabel(e, i.ID, labelID)
} }
@ -175,11 +192,6 @@ func (i *Issue) getLabels(e Engine) (err error) {
return nil return nil
} }
// GetLabels retrieves all labels of issue and assign to corresponding field.
func (i *Issue) GetLabels() error {
return i.getLabels(x)
}
func (i *Issue) removeLabel(e *xorm.Session, label *Label) error { func (i *Issue) removeLabel(e *xorm.Session, label *Label) error {
return deleteIssueLabel(e, i, label) return deleteIssueLabel(e, i, label)
} }
@ -303,6 +315,19 @@ func (i *Issue) GetPullRequest() (err error) {
// It's caller's responsibility to create action. // It's caller's responsibility to create action.
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) { func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
issue.Name = strings.TrimSpace(issue.Name)
issue.Index = repo.NextIssueIndex()
if issue.AssigneeID > 0 {
// Silently drop invalid assignee
valid, err := hasAccess(e, &User{Id: issue.AssigneeID}, repo, ACCESS_MODE_WRITE)
if err != nil {
return fmt.Errorf("hasAccess: %v", err)
} else if !valid {
issue.AssigneeID = 0
}
}
if _, err = e.Insert(issue); err != nil { if _, err = e.Insert(issue); err != nil {
return err return err
} }
@ -325,6 +350,10 @@ func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64,
} }
for _, label := range labels { for _, label := range labels {
if label.RepoID != repo.ID {
continue
}
if err = issue.addLabel(e, label); err != nil { if err = issue.addLabel(e, label); err != nil {
return fmt.Errorf("addLabel: %v", err) return fmt.Errorf("addLabel: %v", err)
} }
@ -377,6 +406,10 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
return fmt.Errorf("newIssue: %v", err) return fmt.Errorf("newIssue: %v", err)
} }
if err = sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
// Notify watchers. // Notify watchers.
act := &Action{ act := &Action{
ActUserID: issue.Poster.Id, ActUserID: issue.Poster.Id,
@ -389,11 +422,11 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
RepoName: repo.Name, RepoName: repo.Name,
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
} }
if err = notifyWatchers(sess, act); err != nil { if err = NotifyWatchers(act); err != nil {
return err log.Error(4, "notifyWatchers: %v", err)
} }
return sess.Commit() return nil
} }
// GetIssueByRef returns an Issue specified by a GFM reference. // GetIssueByRef returns an Issue specified by a GFM reference.
@ -467,6 +500,10 @@ type IssuesOptions struct {
// Issues returns a list of issues by given conditions. // Issues returns a list of issues by given conditions.
func Issues(opts *IssuesOptions) ([]*Issue, error) { func Issues(opts *IssuesOptions) ([]*Issue, error) {
if opts.Page <= 0 {
opts.Page = 1
}
sess := x.Limit(setting.IssuePagingNum, (opts.Page-1)*setting.IssuePagingNum) sess := x.Limit(setting.IssuePagingNum, (opts.Page-1)*setting.IssuePagingNum)
if opts.RepoID > 0 { if opts.RepoID > 0 {
@ -997,6 +1034,9 @@ func (m *Milestone) BeforeUpdate() {
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
switch colName { switch colName {
case "num_closed_issues":
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
case "deadline_unix": case "deadline_unix":
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() m.Deadline = time.Unix(m.DeadlineUnix, 0).Local()
if m.Deadline.Year() == 9999 { if m.Deadline.Year() == 9999 {
@ -1007,14 +1047,18 @@ func (m *Milestone) AfterSet(colName string, _ xorm.Cell) {
if time.Now().Local().After(m.Deadline) { if time.Now().Local().After(m.Deadline) {
m.IsOverDue = true m.IsOverDue = true
} }
case "closed_date_unix": case "closed_date_unix":
m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local()
} }
} }
// CalOpenIssues calculates the open issues of milestone. // State returns string representation of milestone status.
func (m *Milestone) CalOpenIssues() { func (m *Milestone) State() string {
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues if m.IsClosed {
return "closed"
}
return "open"
} }
// NewMilestone creates new milestone of repository. // NewMilestone creates new milestone of repository.

View file

@ -5,10 +5,15 @@
package context package context
import ( import (
"fmt"
"strings"
"github.com/Unknwon/paginater"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
) )
type APIContext struct { type APIContext struct {
@ -35,6 +40,27 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
}) })
} }
func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
page := paginater.New(total, pageSize, ctx.QueryInt("page"), 0)
links := make([]string, 0, 4)
if page.HasNext() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.Next()))
}
if !page.IsLast() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.TotalPages()))
}
if !page.IsFirst() {
links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", setting.AppUrl, ctx.Req.URL.Path[1:]))
}
if page.HasPrevious() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", setting.AppUrl, ctx.Req.URL.Path[1:], page.Previous()))
}
if len(links) > 0 {
ctx.Header().Set("Link", strings.Join(links, ","))
}
}
func APIContexter() macaron.Handler { func APIContexter() macaron.Handler {
return func(c *Context) { return func(c *Context) {
ctx := &APIContext{ ctx := &APIContext{

View file

@ -40,5 +40,5 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
return return
} }
ctx.JSON(201, convert.ToApiOrganization(org)) ctx.JSON(201, convert.ToOrganization(org))
} }

View file

@ -69,7 +69,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
mailer.SendRegisterNotifyMail(ctx.Context.Context, u) mailer.SendRegisterNotifyMail(ctx.Context.Context, u)
} }
ctx.JSON(201, convert.ToApiUser(u)) ctx.JSON(201, convert.ToUser(u))
} }
// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#edit-an-existing-user // https://github.com/gogits/go-gogs-client/wiki/Administration-Users#edit-an-existing-user
@ -118,7 +118,7 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) {
} }
log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name) log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name)
ctx.JSON(200, convert.ToApiUser(u)) ctx.JSON(200, convert.ToUser(u))
} }
// https://github.com/gogits/go-gogs-client/wiki/Administration-Users#delete-a-user // https://github.com/gogits/go-gogs-client/wiki/Administration-Users#delete-a-user

View file

@ -62,16 +62,18 @@ func RepoAssignment() macaron.Handler {
return return
} }
mode, err := models.AccessLevel(ctx.User, repo) if ctx.IsSigned && ctx.User.IsAdmin {
if err != nil { ctx.Repo.AccessMode = models.ACCESS_MODE_OWNER
ctx.Error(500, "AccessLevel", err) } else {
return mode, err := models.AccessLevel(ctx.User, repo)
if err != nil {
ctx.Error(500, "AccessLevel", err)
return
}
ctx.Repo.AccessMode = mode
} }
ctx.Repo.AccessMode = mode if !ctx.Repo.HasAccess() {
// Check access.
if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
ctx.Status(404) ctx.Status(404)
return return
} }
@ -193,9 +195,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/:id").Get(repo.GetDeployKey). m.Combo("/:id").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey) Delete(repo.DeleteDeploykey)
}) })
m.Group("/issue", func() { m.Group("/issues", func() {
m.Combo("").Get().Post() m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Combo("/:index").Get().Patch() m.Combo("/:index").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
}) })
}, RepoAssignment()) }, RepoAssignment())
}, ReqToken()) }, ReqToken())

View file

@ -9,15 +9,19 @@ import (
"github.com/Unknwon/com" "github.com/Unknwon/com"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/git-module" "github.com/gogits/git-module"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
// ToApiUser converts user to its API format. func ToUser(u *models.User) *api.User {
func ToApiUser(u *models.User) *api.User { if u == nil {
return nil
}
return &api.User{ return &api.User{
ID: u.Id, ID: u.Id,
UserName: u.Name, UserName: u.Name,
@ -27,7 +31,7 @@ func ToApiUser(u *models.User) *api.User {
} }
} }
func ToApiEmail(email *models.EmailAddress) *api.Email { func ToEmail(email *models.EmailAddress) *api.Email {
return &api.Email{ return &api.Email{
Email: email.Email, Email: email.Email,
Verified: email.IsActivated, Verified: email.IsActivated,
@ -35,12 +39,11 @@ func ToApiEmail(email *models.EmailAddress) *api.Email {
} }
} }
// ToApiRepository converts repository to API format. func ToRepository(owner *models.User, repo *models.Repository, permission api.Permission) *api.Repository {
func ToApiRepository(owner *models.User, repo *models.Repository, permission api.Permission) *api.Repository {
cl := repo.CloneLink() cl := repo.CloneLink()
return &api.Repository{ return &api.Repository{
Id: repo.ID, ID: repo.ID,
Owner: *ToApiUser(owner), Owner: ToUser(owner),
FullName: owner.Name + "/" + repo.Name, FullName: owner.Name + "/" + repo.Name,
Private: repo.IsPrivate, Private: repo.IsPrivate,
Fork: repo.IsFork, Fork: repo.IsFork,
@ -51,30 +54,27 @@ func ToApiRepository(owner *models.User, repo *models.Repository, permission api
} }
} }
// ToApiBranch converts user to its API format. func ToBranch(b *models.Branch, c *git.Commit) *api.Branch {
func ToApiBranch(b *models.Branch,c *git.Commit) *api.Branch {
return &api.Branch{ return &api.Branch{
Name: b.Name, Name: b.Name,
Commit: ToApiCommit(c), Commit: ToCommit(c),
} }
} }
// ToApiCommit converts user to its API format. func ToCommit(c *git.Commit) *api.PayloadCommit {
func ToApiCommit(c *git.Commit) *api.PayloadCommit {
return &api.PayloadCommit{ return &api.PayloadCommit{
ID: c.ID.String(), ID: c.ID.String(),
Message: c.Message(), Message: c.Message(),
URL: "Not implemented", URL: "Not implemented",
Author: &api.PayloadAuthor{ Author: &api.PayloadAuthor{
Name: c.Committer.Name, Name: c.Committer.Name,
Email: c.Committer.Email, Email: c.Committer.Email,
/* UserName: c.Committer.UserName, */ /* UserName: c.Committer.UserName, */
}, },
} }
} }
// ToApiPublicKey converts public key to its API format. func ToPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey {
func ToApiPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey {
return &api.PublicKey{ return &api.PublicKey{
ID: key.ID, ID: key.ID,
Key: key.Content, Key: key.Content,
@ -84,8 +84,7 @@ func ToApiPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey {
} }
} }
// ToApiHook converts webhook to its API format. func ToHook(repoLink string, w *models.Webhook) *api.Hook {
func ToApiHook(repoLink string, w *models.Webhook) *api.Hook {
config := map[string]string{ config := map[string]string{
"url": w.URL, "url": w.URL,
"content_type": w.ContentType.Name(), "content_type": w.ContentType.Name(),
@ -110,8 +109,7 @@ func ToApiHook(repoLink string, w *models.Webhook) *api.Hook {
} }
} }
// ToApiDeployKey converts deploy key to its API format. func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
func ToApiDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
return &api.DeployKey{ return &api.DeployKey{
ID: key.ID, ID: key.ID,
Key: key.Content, Key: key.Content,
@ -122,7 +120,72 @@ func ToApiDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
} }
} }
func ToApiOrganization(org *models.User) *api.Organization { func ToLabel(label *models.Label) *api.Label {
return &api.Label{
Name: label.Name,
Color: label.Color,
}
}
func ToMilestone(milestone *models.Milestone) *api.Milestone {
if milestone == nil {
return nil
}
apiMilestone := &api.Milestone{
ID: milestone.ID,
State: milestone.State(),
Title: milestone.Name,
Description: milestone.Content,
OpenIssues: milestone.NumOpenIssues,
ClosedIssues: milestone.NumClosedIssues,
}
if milestone.IsClosed {
apiMilestone.Closed = &milestone.ClosedDate
}
if milestone.Deadline.Year() < 9999 {
apiMilestone.Deadline = &milestone.Deadline
}
return apiMilestone
}
func ToIssue(issue *models.Issue) *api.Issue {
apiLabels := make([]*api.Label, len(issue.Labels))
for i := range issue.Labels {
apiLabels[i] = ToLabel(issue.Labels[i])
}
apiIssue := &api.Issue{
ID: issue.ID,
Index: issue.Index,
State: issue.State(),
Title: issue.Name,
Body: issue.Content,
User: ToUser(issue.Poster),
Labels: apiLabels,
Assignee: ToUser(issue.Assignee),
Milestone: ToMilestone(issue.Milestone),
Comments: issue.NumComments,
Created: issue.Created,
Updated: issue.Updated,
}
if issue.IsPull {
if err := issue.GetPullRequest(); err != nil {
log.Error(4, "GetPullRequest", err)
} else {
apiIssue.PullRequest = &api.PullRequestMeta{
HasMerged: issue.PullRequest.HasMerged,
}
if issue.PullRequest.HasMerged {
apiIssue.PullRequest.Merged = &issue.PullRequest.Merged
}
}
}
return apiIssue
}
func ToOrganization(org *models.User) *api.Organization {
return &api.Organization{ return &api.Organization{
ID: org.Id, ID: org.Id,
AvatarUrl: org.AvatarLink(), AvatarUrl: org.AvatarLink(),

View file

@ -21,7 +21,7 @@ func listUserOrgs(ctx *context.APIContext, u *models.User, all bool) {
apiOrgs := make([]*api.Organization, len(u.Orgs)) apiOrgs := make([]*api.Organization, len(u.Orgs))
for i := range u.Orgs { for i := range u.Orgs {
apiOrgs[i] = convert.ToApiOrganization(u.Orgs[i]) apiOrgs[i] = convert.ToOrganization(u.Orgs[i])
} }
ctx.JSON(200, &apiOrgs) ctx.JSON(200, &apiOrgs)
} }
@ -46,7 +46,7 @@ func Get(ctx *context.APIContext) {
if ctx.Written() { if ctx.Written() {
return return
} }
ctx.JSON(200, convert.ToApiOrganization(org)) ctx.JSON(200, convert.ToOrganization(org))
} }
// https://github.com/gogits/go-gogs-client/wiki/Organizations#edit-an-organization // https://github.com/gogits/go-gogs-client/wiki/Organizations#edit-an-organization
@ -70,5 +70,5 @@ func Edit(ctx *context.APIContext, form api.EditOrgOption) {
return return
} }
ctx.JSON(200, convert.ToApiOrganization(org)) ctx.JSON(200, convert.ToOrganization(org))
} }

View file

@ -25,7 +25,7 @@ func GetBranch(ctx *context.APIContext) {
return return
} }
ctx.JSON(200, convert.ToApiBranch(branch, c)) ctx.JSON(200, convert.ToBranch(branch, c))
} }
// https://github.com/gogits/go-gogs-client/wiki/Repositories#list-branches // https://github.com/gogits/go-gogs-client/wiki/Repositories#list-branches
@ -43,7 +43,7 @@ func ListBranches(ctx *context.APIContext) {
ctx.Error(500, "GetCommit", err) ctx.Error(500, "GetCommit", err)
return return
} }
apiBranches[i] = convert.ToApiBranch(branches[i], c) apiBranches[i] = convert.ToBranch(branches[i], c)
} }
ctx.JSON(200, &apiBranches) ctx.JSON(200, &apiBranches)

View file

@ -26,9 +26,8 @@ func ListHooks(ctx *context.APIContext) {
apiHooks := make([]*api.Hook, len(hooks)) apiHooks := make([]*api.Hook, len(hooks))
for i := range hooks { for i := range hooks {
apiHooks[i] = convert.ToApiHook(ctx.Repo.RepoLink, hooks[i]) apiHooks[i] = convert.ToHook(ctx.Repo.RepoLink, hooks[i])
} }
ctx.JSON(200, &apiHooks) ctx.JSON(200, &apiHooks)
} }
@ -94,7 +93,7 @@ func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
return return
} }
ctx.JSON(201, convert.ToApiHook(ctx.Repo.RepoLink, w)) ctx.JSON(201, convert.ToHook(ctx.Repo.RepoLink, w))
} }
// https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook // https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook
@ -161,5 +160,5 @@ func EditHook(ctx *context.APIContext, form api.EditHookOption) {
return return
} }
ctx.JSON(200, convert.ToApiHook(ctx.Repo.RepoLink, w)) ctx.JSON(200, convert.ToHook(ctx.Repo.RepoLink, w))
} }

View file

@ -0,0 +1,165 @@
// Copyright 2016 The Gogs 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 repo
import (
"fmt"
"strings"
api "github.com/gogits/go-gogs-client"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/context"
"github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/routers/api/v1/convert"
"github.com/gogits/gogs/routers/repo"
)
func ListIssues(ctx *context.APIContext) {
issues, err := models.Issues(&models.IssuesOptions{
RepoID: ctx.Repo.Repository.ID,
Page: ctx.QueryInt("page"),
})
if err != nil {
ctx.Error(500, "Issues", err)
return
}
apiIssues := make([]*api.Issue, len(issues))
for i := range issues {
apiIssues[i] = convert.ToIssue(issues[i])
}
ctx.SetLinkHeader(ctx.Repo.Repository.NumIssues, setting.IssuePagingNum)
ctx.JSON(200, &apiIssues)
}
func GetIssue(ctx *context.APIContext) {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
ctx.Status(404)
} else {
ctx.Error(500, "GetIssueByIndex", err)
}
return
}
ctx.JSON(200, convert.ToIssue(issue))
}
func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
issue := &models.Issue{
RepoID: ctx.Repo.Repository.ID,
Name: form.Title,
PosterID: ctx.User.Id,
Poster: ctx.User,
Content: form.Body,
}
if ctx.Repo.IsWriter() {
if len(form.Assignee) > 0 {
assignee, err := models.GetUserByName(form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
issue.AssigneeID = assignee.Id
}
issue.MilestoneID = form.Milestone
} else {
form.Labels = nil
}
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, nil); err != nil {
ctx.Error(500, "NewIssue", err)
return
} else if err := repo.MailWatchersAndMentions(ctx.Context, issue); err != nil {
ctx.Error(500, "MailWatchersAndMentions", err)
return
}
// Refetch from database to assign some automatic values
var err error
issue, err = models.GetIssueByID(issue.ID)
if err != nil {
ctx.Error(500, "GetIssueByID", err)
return
}
ctx.JSON(201, convert.ToIssue(issue))
}
func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
ctx.Status(404)
} else {
ctx.Error(500, "GetIssueByIndex", err)
}
return
}
if !issue.IsPoster(ctx.User.Id) && !ctx.Repo.IsWriter() {
ctx.Status(403)
return
}
if len(form.Title) > 0 {
issue.Name = form.Title
}
if form.Body != nil {
issue.Content = *form.Body
}
if ctx.Repo.IsWriter() && form.Assignee != nil &&
(issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
if len(*form.Assignee) == 0 {
issue.AssigneeID = 0
} else {
assignee, err := models.GetUserByName(*form.Assignee)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", *form.Assignee))
} else {
ctx.Error(500, "GetUserByName", err)
}
return
}
issue.AssigneeID = assignee.Id
}
if err = models.UpdateIssueUserByAssignee(issue); err != nil {
ctx.Error(500, "UpdateIssueUserByAssignee", err)
return
}
}
if ctx.Repo.IsWriter() && form.Milestone != nil &&
issue.MilestoneID != *form.Milestone {
oldMid := issue.MilestoneID
issue.MilestoneID = *form.Milestone
if err = models.ChangeMilestoneAssign(oldMid, issue); err != nil {
ctx.Error(500, "ChangeMilestoneAssign", err)
return
}
}
if err = models.UpdateIssue(issue); err != nil {
ctx.Error(500, "UpdateIssue", err)
return
}
// Refetch from database to assign some automatic values
issue, err = models.GetIssueByID(issue.ID)
if err != nil {
ctx.Error(500, "GetIssueByID", err)
return
}
ctx.JSON(201, convert.ToIssue(issue))
}

View file

@ -34,7 +34,7 @@ func ListDeployKeys(ctx *context.APIContext) {
ctx.Error(500, "GetContent", err) ctx.Error(500, "GetContent", err)
return return
} }
apiKeys[i] = convert.ToApiDeployKey(apiLink, keys[i]) apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
} }
ctx.JSON(200, &apiKeys) ctx.JSON(200, &apiKeys)
@ -58,7 +58,7 @@ func GetDeployKey(ctx *context.APIContext) {
} }
apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name)
ctx.JSON(200, convert.ToApiDeployKey(apiLink, key)) ctx.JSON(200, convert.ToDeployKey(apiLink, key))
} }
func HandleCheckKeyStringError(ctx *context.APIContext, err error) { func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
@ -96,7 +96,7 @@ func CreateDeployKey(ctx *context.APIContext, form api.CreateKeyOption) {
key.Content = content key.Content = content
apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name)
ctx.JSON(201, convert.ToApiDeployKey(apiLink, key)) ctx.JSON(201, convert.ToDeployKey(apiLink, key))
} }
// https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key // https://github.com/gogits/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key

View file

@ -69,7 +69,7 @@ func Search(ctx *context.APIContext) {
return return
} }
results[i] = &api.Repository{ results[i] = &api.Repository{
Id: repos[i].ID, ID: repos[i].ID,
FullName: path.Join(repos[i].Owner.Name, repos[i].Name), FullName: path.Join(repos[i].Owner.Name, repos[i].Name),
} }
} }
@ -97,12 +97,12 @@ func ListMyRepos(ctx *context.APIContext) {
repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos)) repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
for i := range ownRepos { for i := range ownRepos {
repos[i] = convert.ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true}) repos[i] = convert.ToRepository(ctx.User, ownRepos[i], api.Permission{true, true, true})
} }
i := numOwnRepos i := numOwnRepos
for repo, access := range accessibleRepos { for repo, access := range accessibleRepos {
repos[i] = convert.ToApiRepository(repo.Owner, repo, api.Permission{ repos[i] = convert.ToRepository(repo.Owner, repo, api.Permission{
Admin: access >= models.ACCESS_MODE_ADMIN, Admin: access >= models.ACCESS_MODE_ADMIN,
Push: access >= models.ACCESS_MODE_WRITE, Push: access >= models.ACCESS_MODE_WRITE,
Pull: true, Pull: true,
@ -139,7 +139,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
return return
} }
ctx.JSON(201, convert.ToApiRepository(owner, repo, api.Permission{true, true, true})) ctx.JSON(201, convert.ToRepository(owner, repo, api.Permission{true, true, true}))
} }
// https://github.com/gogits/go-gogs-client/wiki/Repositories#create // https://github.com/gogits/go-gogs-client/wiki/Repositories#create
@ -239,7 +239,7 @@ func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
} }
log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName) log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
ctx.JSON(201, convert.ToApiRepository(ctxUser, repo, api.Permission{true, true, true})) ctx.JSON(201, convert.ToRepository(ctxUser, repo, api.Permission{true, true, true}))
} }
func parseOwnerAndRepo(ctx *context.APIContext) (*models.User, *models.Repository) { func parseOwnerAndRepo(ctx *context.APIContext) (*models.User, *models.Repository) {
@ -273,7 +273,7 @@ func Get(ctx *context.APIContext) {
return return
} }
ctx.JSON(200, convert.ToApiRepository(owner, repo, api.Permission{true, true, true})) ctx.JSON(200, convert.ToRepository(owner, repo, api.Permission{true, true, true}))
} }
// https://github.com/gogits/go-gogs-client/wiki/Repositories#delete // https://github.com/gogits/go-gogs-client/wiki/Repositories#delete

View file

@ -22,7 +22,7 @@ func ListEmails(ctx *context.APIContext) {
} }
apiEmails := make([]*api.Email, len(emails)) apiEmails := make([]*api.Email, len(emails))
for i := range emails { for i := range emails {
apiEmails[i] = convert.ToApiEmail(emails[i]) apiEmails[i] = convert.ToEmail(emails[i])
} }
ctx.JSON(200, &apiEmails) ctx.JSON(200, &apiEmails)
} }
@ -54,7 +54,7 @@ func AddEmail(ctx *context.APIContext, form api.CreateEmailOption) {
apiEmails := make([]*api.Email, len(emails)) apiEmails := make([]*api.Email, len(emails))
for i := range emails { for i := range emails {
apiEmails[i] = convert.ToApiEmail(emails[i]) apiEmails[i] = convert.ToEmail(emails[i])
} }
ctx.JSON(201, &apiEmails) ctx.JSON(201, &apiEmails)
} }

View file

@ -15,7 +15,7 @@ import (
func responseApiUsers(ctx *context.APIContext, users []*models.User) { func responseApiUsers(ctx *context.APIContext, users []*models.User) {
apiUsers := make([]*api.User, len(users)) apiUsers := make([]*api.User, len(users))
for i := range users { for i := range users {
apiUsers[i] = convert.ToApiUser(users[i]) apiUsers[i] = convert.ToUser(users[i])
} }
ctx.JSON(200, &apiUsers) ctx.JSON(200, &apiUsers)
} }

View file

@ -46,7 +46,7 @@ func listPublicKeys(ctx *context.APIContext, uid int64) {
apiLink := composePublicKeysAPILink() apiLink := composePublicKeysAPILink()
apiKeys := make([]*api.PublicKey, len(keys)) apiKeys := make([]*api.PublicKey, len(keys))
for i := range keys { for i := range keys {
apiKeys[i] = convert.ToApiPublicKey(apiLink, keys[i]) apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
} }
ctx.JSON(200, &apiKeys) ctx.JSON(200, &apiKeys)
@ -79,7 +79,7 @@ func GetPublicKey(ctx *context.APIContext) {
} }
apiLink := composePublicKeysAPILink() apiLink := composePublicKeysAPILink()
ctx.JSON(200, convert.ToApiPublicKey(apiLink, key)) ctx.JSON(200, convert.ToPublicKey(apiLink, key))
} }
// CreateUserPublicKey creates new public key to given user by ID. // CreateUserPublicKey creates new public key to given user by ID.
@ -96,7 +96,7 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
return return
} }
apiLink := composePublicKeysAPILink() apiLink := composePublicKeysAPILink()
ctx.JSON(201, convert.ToApiPublicKey(apiLink, key)) ctx.JSON(201, convert.ToPublicKey(apiLink, key))
} }
// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key // https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key

View file

@ -195,16 +195,6 @@ func Issues(ctx *context.Context) {
// Get posters. // Get posters.
for i := range issues { for i := range issues {
if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
if err = issues[i].GetLabels(); err != nil {
ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
if !ctx.IsSigned { if !ctx.IsSigned {
issues[i].IsRead = true issues[i].IsRead = true
continue continue
@ -405,7 +395,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
return labelIDs, milestoneID, assigneeID return labelIDs, milestoneID, assigneeID
} }
func notifyWatchersAndMentions(ctx *context.Context, issue *models.Issue) { func MailWatchersAndMentions(ctx *context.Context, issue *models.Issue) error {
// Update mentions // Update mentions
mentions := markdown.MentionPattern.FindAllString(issue.Content, -1) mentions := markdown.MentionPattern.FindAllString(issue.Content, -1)
if len(mentions) > 0 { if len(mentions) > 0 {
@ -414,8 +404,7 @@ func notifyWatchersAndMentions(ctx *context.Context, issue *models.Issue) {
} }
if err := models.UpdateMentions(mentions, issue.ID); err != nil { if err := models.UpdateMentions(mentions, issue.ID); err != nil {
ctx.Handle(500, "UpdateMentions", err) return fmt.Errorf("UpdateMentions: %v", err)
return
} }
} }
@ -425,8 +414,7 @@ func notifyWatchersAndMentions(ctx *context.Context, issue *models.Issue) {
if setting.Service.EnableNotifyMail { if setting.Service.EnableNotifyMail {
tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, repo, issue) tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, repo, issue)
if err != nil { if err != nil {
ctx.Handle(500, "SendIssueNotifyMail", err) return fmt.Errorf("SendIssueNotifyMail: %v", err)
return
} }
tos = append(tos, ctx.User.LowerName) tos = append(tos, ctx.User.LowerName)
@ -440,10 +428,11 @@ func notifyWatchersAndMentions(ctx *context.Context, issue *models.Issue) {
} }
if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
repo, issue, models.GetUserEmailsByNames(newTos)); err != nil { repo, issue, models.GetUserEmailsByNames(newTos)); err != nil {
ctx.Handle(500, "SendIssueMentionMail", err) return fmt.Errorf("SendIssueMentionMail: %v", err)
return
} }
} }
return nil
} }
func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
@ -471,9 +460,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
} }
issue := &models.Issue{ issue := &models.Issue{
RepoID: ctx.Repo.Repository.ID, RepoID: repo.ID,
Index: repo.NextIssueIndex(), Name: form.Title,
Name: strings.TrimSpace(form.Title),
PosterID: ctx.User.Id, PosterID: ctx.User.Id,
Poster: ctx.User, Poster: ctx.User,
MilestoneID: milestoneID, MilestoneID: milestoneID,
@ -483,10 +471,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil { if err := models.NewIssue(repo, issue, labelIDs, attachments); err != nil {
ctx.Handle(500, "NewIssue", err) ctx.Handle(500, "NewIssue", err)
return return
} } else if err := MailWatchersAndMentions(ctx, issue); err != nil {
ctx.Handle(500, "MailWatchersAndMentions", err)
notifyWatchersAndMentions(ctx, issue)
if ctx.Written() {
return return
} }
@ -586,10 +572,6 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsIssueList"] = true
} }
if err = issue.GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", err)
return
}
issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink, issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
ctx.Repo.Repository.ComposeMetas())) ctx.Repo.Repository.ComposeMetas()))
@ -610,10 +592,6 @@ func ViewIssue(ctx *context.Context) {
// Metas. // Metas.
// Check labels. // Check labels.
if err = issue.GetLabels(); err != nil {
ctx.Handle(500, "GetLabels", err)
return
}
labelIDMark := make(map[int64]bool) labelIDMark := make(map[int64]bool)
for i := range issue.Labels { for i := range issue.Labels {
labelIDMark[issue.Labels[i].ID] = true labelIDMark[issue.Labels[i].ID] = true
@ -698,7 +676,7 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["Participants"] = participants ctx.Data["Participants"] = participants
ctx.Data["NumParticipants"] = len(participants) ctx.Data["NumParticipants"] = len(participants)
ctx.Data["Issue"] = issue ctx.Data["Issue"] = issue
ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && (issue.IsPoster(ctx.User.Id) || ctx.User.IsAdmin)) ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.Id))
ctx.Data["SignInLink"] = setting.AppSubUrl + "/user/login" ctx.Data["SignInLink"] = setting.AppSubUrl + "/user/login"
ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireHighlightJS"] = true
@ -725,7 +703,7 @@ func UpdateIssueTitle(ctx *context.Context) {
return return
} }
if !ctx.IsSigned || (ctx.User.Id != issue.PosterID && !ctx.Repo.IsWriter() && !ctx.User.IsAdmin) { if !ctx.IsSigned || (!issue.IsPoster(ctx.User.Id) && !ctx.Repo.IsWriter()) {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -752,7 +730,7 @@ func UpdateIssueContent(ctx *context.Context) {
return return
} }
if !ctx.IsSigned || (ctx.User.Id != issue.PosterID && !ctx.Repo.IsWriter() && !ctx.User.IsAdmin) { if !ctx.IsSigned || (ctx.User.Id != issue.PosterID && !ctx.Repo.IsWriter()) {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -955,7 +933,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return return
} }
notifyWatchersAndMentions(ctx, &models.Issue{ MailWatchersAndMentions(ctx, &models.Issue{
ID: issue.ID, ID: issue.ID,
Index: issue.Index, Index: issue.Index,
Name: issue.Name, Name: issue.Name,
@ -1099,7 +1077,6 @@ func Milestones(ctx *context.Context) {
} }
for _, m := range miles { for _, m := range miles {
m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
m.CalOpenIssues()
} }
ctx.Data["Milestones"] = miles ctx.Data["Milestones"] = miles

View file

@ -156,10 +156,7 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
return nil return nil
} }
if err = issue.GetPoster(); err != nil { if err = issue.GetPullRequest(); err != nil {
ctx.Handle(500, "GetPoster", err)
return nil
} else if err = issue.GetPullRequest(); err != nil {
ctx.Handle(500, "GetPullRequest", err) ctx.Handle(500, "GetPullRequest", err)
return nil return nil
} else if err = issue.GetHeadRepo(); err != nil { } else if err = issue.GetHeadRepo(); err != nil {
@ -682,10 +679,8 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
} else if err := pullRequest.PushToBaseRepo(); err != nil { } else if err := pullRequest.PushToBaseRepo(); err != nil {
ctx.Handle(500, "PushToBaseRepo", err) ctx.Handle(500, "PushToBaseRepo", err)
return return
} } else if err := MailWatchersAndMentions(ctx, pullIssue); err != nil {
ctx.Handle(500, "MailWatchersAndMentions", err)
notifyWatchersAndMentions(ctx, pullIssue)
if ctx.Written() {
return return
} }

View file

@ -284,11 +284,6 @@ func Issues(ctx *context.Context) {
ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issues[i].ID, err)) ctx.Handle(500, "GetOwner", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return return
} }
if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "GetPoster", fmt.Errorf("[#%d]%v", issues[i].ID, err))
return
}
} }
ctx.Data["Issues"] = issues ctx.Data["Issues"] = issues

View file

@ -1 +1 @@
0.9.8.0312 0.9.9.0313