This commit is contained in:
Jonas Franz 2018-06-17 15:00:55 +00:00 committed by GitHub
commit 64d0e4b5db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 246 additions and 49 deletions

7
Gopkg.lock generated
View File

@ -8,10 +8,11 @@
revision = "31f4b8e8c805438ac6d8914b38accb1d8aaf695e"
[[projects]]
branch = "master"
branch = "migration"
name = "code.gitea.io/sdk"
packages = ["gitea"]
revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d"
revision = "c01e6df2e1cdb53403f6542e5d2248ace831f3ec"
source = "github.com/JonasFranzDEV/go-sdk"
[[projects]]
name = "github.com/PuerkitoBio/goquery"
@ -873,6 +874,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "036b8c882671cf8d2c5e2fdbe53b1bdfbd39f7ebd7765bd50276c7c4ecf16687"
inputs-digest = "6e57d03c2ae6e7ee38ab336d3a8c2171b21f10f2b3f58ce7d19904cc725a2a06"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -11,7 +11,8 @@ ignored = ["google.golang.org/appengine*"]
name = "code.gitea.io/git"
[[constraint]]
branch = "master"
branch = "migration"
source = "github.com/JonasFranzDEV/go-sdk"
name = "code.gitea.io/sdk"
[[constraint]]

View File

@ -30,12 +30,13 @@ type Issue struct {
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
Title string `xorm:"name"`
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
GhostName string
Title string `xorm:"name"`
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *User `xorm:"-"`
@ -48,7 +49,7 @@ type Issue struct {
DeadlineUnix util.TimeStamp `xorm:"INDEX"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
CreatedUnix util.TimeStamp `xorm:"INDEX"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
ClosedUnix util.TimeStamp `xorm:"INDEX"`
@ -67,6 +68,13 @@ var (
const issueTasksRegexpStr = `(^\s*[-*]\s\[[\sx]\]\s.)|(\n\s*[-*]\s\[[\sx]\]\s.)`
const issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[x]\]\s.)|(\n\s*[-*]\s\[[x]\]\s.)`
// BeforeInsert is invoked before XORM inserts this
func (issue *Issue) BeforeInsert() {
if issue.CreatedUnix == util.TimeStamp(0) {
issue.CreatedUnix = util.TimeStampNow()
}
}
func init() {
issueTasksPat = regexp.MustCompile(issueTasksRegexpStr)
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr)
@ -134,6 +142,10 @@ func (issue *Issue) loadPoster(e Engine) (err error) {
if !IsErrUserNotExist(err) {
return fmt.Errorf("getUserByID.(poster) [%d]: %v", issue.PosterID, err)
}
if issue.GhostName != "" {
issue.Poster.Name = issue.GhostName
issue.Poster.LowerName = strings.ToLower(issue.GhostName)
}
err = nil
return
}
@ -892,7 +904,9 @@ type NewIssueOptions struct {
func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
opts.Issue.Index = opts.Repo.NextIssueIndex()
if opts.Issue.Index == 0 {
opts.Issue.Index = opts.Repo.NextIssueIndex()
}
if opts.Issue.MilestoneID > 0 {
milestone, err := getMilestoneByRepoID(e, opts.Issue.RepoID, opts.Issue.MilestoneID)
@ -1010,15 +1024,11 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
return opts.Issue.loadAttributes(e)
}
// NewIssue creates new issue with labels for repository.
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
// NewSuppressedIssue creates an issue without sending notifications or webhooks
func NewSuppressedIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) error {
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if err = newIssue(sess, issue.Poster, NewIssueOptions{
if err := newIssue(sess, issue.Poster, NewIssueOptions{
Repo: repo,
Issue: issue,
LabelIDs: labelIDs,
@ -1030,10 +1040,17 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in
}
return fmt.Errorf("newIssue: %v", err)
}
if err = sess.Commit(); err != nil {
if err := sess.Commit(); err != nil {
return fmt.Errorf("Commit: %v", err)
}
return nil
}
// NewIssue creates new issue with labels for repository.
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []int64, uuids []string) (err error) {
if err = NewSuppressedIssue(repo, issue, labelIDs, assigneeIDs, uuids); err != nil {
return err
}
UpdateIssueIndexer(issue.ID)

View File

@ -83,8 +83,9 @@ const (
type Comment struct {
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
PosterID int64 `xorm:"INDEX"`
Poster *User `xorm:"-"`
GhostName string
IssueID int64 `xorm:"INDEX"`
Issue *Issue `xorm:"-"`
LabelID int64
@ -104,7 +105,7 @@ type Comment struct {
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
CreatedUnix util.TimeStamp `xorm:"INDEX"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
// Reference issue in commit message
@ -126,6 +127,13 @@ func (c *Comment) LoadIssue() (err error) {
return
}
// BeforeInsert is invoked before XORM inserts this
func (c *Comment) BeforeInsert() {
if c.CreatedUnix == util.TimeStamp(0) {
c.CreatedUnix = util.TimeStampNow()
}
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (c *Comment) AfterLoad(session *xorm.Session) {
var err error
@ -139,6 +147,10 @@ func (c *Comment) AfterLoad(session *xorm.Session) {
if IsErrUserNotExist(err) {
c.PosterID = -1
c.Poster = NewGhostUser()
if len(c.GhostName) > 0 {
c.Poster.Name = c.GhostName
c.Poster.LowerName = strings.ToLower(c.GhostName)
}
} else {
log.Error(3, "getUserByID[%d]: %v", c.ID, err)
}
@ -348,6 +360,10 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
Content: opts.Content,
OldTitle: opts.OldTitle,
NewTitle: opts.NewTitle,
CreatedUnix: opts.CreatedAt,
}
if opts.Doer.ID == -1 {
comment.GhostName = opts.Doer.Name
}
if _, err = e.Insert(comment); err != nil {
return nil, err
@ -568,6 +584,7 @@ type CreateCommentOptions struct {
LineNum int64
Content string
Attachments []string // UUIDs of attachments
CreatedAt util.TimeStamp
}
// CreateComment creates comment of issue or commit.
@ -594,7 +611,7 @@ func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
}
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string, createdAt util.TimeStamp) (*Comment, error) {
comment, err := CreateComment(&CreateCommentOptions{
Type: CommentTypeComment,
Doer: doer,
@ -602,6 +619,7 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
Issue: issue,
Content: content,
Attachments: attachments,
CreatedAt: createdAt,
})
if err != nil {
return nil, fmt.Errorf("CreateComment: %v", err)

View File

@ -4,7 +4,10 @@
package models
import "fmt"
import (
"fmt"
"strings"
)
// IssueList defines a list of issues
type IssueList []*Issue
@ -70,6 +73,11 @@ func (issues IssueList) loadPosters(e Engine) error {
for _, issue := range issues {
if issue.PosterID <= 0 {
if issue.GhostName != "" {
issue.Poster = NewGhostUser()
issue.Poster.Name = issue.GhostName
issue.Poster.LowerName = strings.ToLower(issue.GhostName)
}
continue
}
var ok bool

View File

@ -776,10 +776,21 @@ func (repo *Repository) getUsersWithAccessMode(e Engine, mode AccessMode) (_ []*
}
// NextIssueIndex returns the next issue index
// FIXME: should have a mutex to prevent producing same index for two issues that are created
// closely enough.
func (repo *Repository) NextIssueIndex() int64 {
return int64(repo.NumIssues+repo.NumPulls) + 1
func (repo *Repository) NextIssueIndex() (latestIndex int64) {
var err error
if latestIndex, err = repo.latestIndex(x); err != nil {
log.Warn("latestIndex: %v", err)
return int64(repo.NumIssues+repo.NumPulls) + 1
}
return latestIndex + 1
}
func (repo *Repository) latestIndex(e Engine) (int64, error) {
latestIdx := struct {
LatestIndex int64
}{}
_, err := e.Table("issue").Select("MAX(`index`) as latest_index").Where("repo_id = ?", repo.ID).Get(&latestIdx)
return latestIdx.LatestIndex, err
}
var (

View File

@ -1804,6 +1804,12 @@
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "suppresses notifications and webhooks if true. Requires repo admin permissions.",
"name": "suppress_notifications",
"in": "query"
},
{
"name": "body",
"in": "body",
@ -1815,6 +1821,9 @@
"responses": {
"201": {
"$ref": "#/responses/Issue"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
@ -2204,6 +2213,12 @@
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "suppresses notifications and webhooks if true. Requires repo admin permissions.",
"name": "suppress_notifications",
"in": "query"
},
{
"name": "body",
"in": "body",
@ -5674,6 +5689,17 @@
"body": {
"type": "string",
"x-go-name": "Body"
},
"created_at": {
"description": "Created will be used as creation date. This is used for migration. Requires admin permissions.",
"type": "string",
"format": "date-time",
"x-go-name": "Created"
},
"ghost_name": {
"description": "GhostName will be used if poster is not existing on Gitea. Requires admin permissions.",
"type": "string",
"x-go-name": "GhostName"
}
},
"x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
@ -5705,11 +5731,28 @@
"type": "boolean",
"x-go-name": "Closed"
},
"created_at": {
"description": "Created will be used as creation date. This is used for migration. Requires admin permissions.",
"type": "string",
"format": "date-time",
"x-go-name": "Created"
},
"due_date": {
"type": "string",
"format": "date-time",
"x-go-name": "Deadline"
},
"ghost_name": {
"description": "GhostName is used if user is not existing on the gitea instance. Requires admin permissions.",
"type": "string",
"x-go-name": "GhostName"
},
"index": {
"description": "Index is former index of the issue. If the index is already taken, an error will be returned. Requires admin permission. Use it only for migrations.",
"type": "integer",
"format": "int64",
"x-go-name": "Index"
},
"labels": {
"description": "list of label ids",
"type": "array",

View File

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/modules/indexer"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
)
@ -156,6 +155,11 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
// description: name of the repo
// type: string
// required: true
// - name: suppress_notifications
// in: query
// type: boolean
// description: suppresses notifications and webhooks if true. Requires repo admin permissions.
// required: false
// - name: body
// in: body
// schema:
@ -163,6 +167,8 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
// responses:
// "201":
// "$ref": "#/responses/Issue"
// "422":
// "$ref": "#/responses/validationError"
var deadlineUnix util.TimeStamp
if form.Deadline != nil {
@ -178,6 +184,33 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
DeadlineUnix: deadlineUnix,
}
if ctx.User.IsAdmin {
if form.Index != 0 {
if _, err := models.GetRawIssueByIndex(ctx.Repo.Repository.ID, form.Index); err == nil {
ctx.Error(422, "index is already in use", "index is already in use")
return
} else if err != nil && !models.IsErrIssueNotExist(err) {
ctx.Error(500, "GetRawIssueByIndex", err)
return
}
if form.Index <= 0 {
ctx.Error(422, "invalid index", "invalid index")
return
}
issue.Index = form.Index
}
issue.CreatedUnix = util.TimeStamp(form.Created.Unix())
issue.GhostName = form.GhostName
if len(issue.GhostName) > 0 {
issue.PosterID = -1
issue.Poster = &models.User{
ID: -1,
Name: form.GhostName,
LowerName: strings.ToLower(form.GhostName),
}
}
}
// Get all assignee IDs
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil {
@ -188,21 +221,30 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
}
return
}
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
if ctx.QueryBool("suppress_notifications") && ctx.User.IsAdminOfRepo(ctx.Repo.Repository) {
if err := models.NewSuppressedIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
}
ctx.Error(500, "NewIssue", err)
return
}
ctx.Error(500, "NewIssue", err)
return
}
if form.Closed {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil {
ctx.Error(500, "ChangeStatus", err)
} else {
if err := models.NewIssue(ctx.Repo.Repository, issue, form.Labels, assigneeIDs, nil); err != nil {
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
return
}
ctx.Error(500, "NewIssue", err)
return
}
if form.Closed {
if err := issue.ChangeStatus(ctx.User, ctx.Repo.Repository, true); err != nil {
ctx.Error(500, "ChangeStatus", err)
return
}
}
}
// Refetch from database to assign some automatic values

View File

@ -5,10 +5,12 @@
package repo
import (
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"
)
@ -144,6 +146,11 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
// description: index of the issue
// type: integer
// required: true
// - name: suppress_notifications
// in: query
// type: boolean
// description: suppresses notifications and webhooks if true. Requires repo admin permissions.
// required: false
// - name: body
// in: body
// schema:
@ -156,11 +163,37 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
ctx.Error(500, "GetIssueByIndex", err)
return
}
comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Body, nil)
if err != nil {
ctx.Error(500, "CreateIssueComment", err)
return
var comment *models.Comment
doer := ctx.User
createdUnix := util.TimeStamp(0)
if ctx.User.IsAdmin && len(form.GhostName) > 0 {
doer = &models.User{
ID: -1,
Name: form.GhostName,
LowerName: strings.ToLower(form.GhostName),
}
if !form.Created.IsZero() {
createdUnix = util.TimeStamp(form.Created.Unix())
}
}
if ctx.QueryBool("suppress_notifications") && ctx.User.IsAdminOfRepo(ctx.Repo.Repository) {
comment, err = models.CreateComment(&models.CreateCommentOptions{
Type: models.CommentTypeComment,
Doer: doer,
Repo: ctx.Repo.Repository,
Issue: issue,
Content: form.Body,
CreatedAt: createdUnix,
})
if err != nil {
ctx.Error(500, "CreateComment", err)
}
} else {
comment, err = models.CreateIssueComment(doer, ctx.Repo.Repository, issue, form.Body, nil, createdUnix)
if err != nil {
ctx.Error(500, "CreateIssueComment", err)
return
}
}
ctx.JSON(201, comment.APIFormat())

View File

@ -1059,7 +1059,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
return
}
comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments, util.TimeStamp(0))
if err != nil {
ctx.ServerError("CreateIssueComment", err)
return

View File

@ -103,6 +103,14 @@ type CreateIssueOption struct {
// list of label ids
Labels []int64 `json:"labels"`
Closed bool `json:"closed"`
// GhostName is used if user is not existing on the gitea instance. Requires admin permissions.
GhostName string `json:"ghost_name" binding:"AlphaDashDot;MaxSize(35)"`
// Index is former index of the issue. If the index is already taken, an error will be returned. Requires admin permission. Use it only for migrations.
Index int64 `json:"index"`
// Created will be used as creation date. This is used for migration. Requires admin permissions.
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
SuppressNotifications bool `json:"-"`
}
// CreateIssue create a new issue for a given repository
@ -112,7 +120,7 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
return nil, err
}
issue := new(Issue)
return issue, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
return issue, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues?surpress_notifications=%t", owner, repo, opt.SuppressNotifications),
jsonHeader, bytes.NewReader(body), issue)
}

View File

@ -41,6 +41,11 @@ func (c *Client) ListRepoIssueComments(owner, repo string) ([]*Comment, error) {
type CreateIssueCommentOption struct {
// required:true
Body string `json:"body" binding:"Required"`
// GhostName will be used if poster is not existing on Gitea. Requires admin permissions.
GhostName string `json:"ghost_name" binding:"AlphaDashDot;MaxSize(35)"`
// Created will be used as creation date. This is used for migration. Requires admin permissions.
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
}
// CreateIssueComment create comment on an issue.
@ -53,6 +58,16 @@ func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateI
return comment, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment)
}
// CreateSuppressedIssueComment create comment on an issue without sending notifications if suppressed is true. Requires admin permissions for the repo.
func (c *Client) CreateSuppressedIssueComment(owner, repo string, index int64, suppressed bool, opt CreateIssueCommentOption) (*Comment, error) {
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
comment := new(Comment)
return comment, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments?surpress_notifications=%t", owner, repo, index, suppressed), jsonHeader, bytes.NewReader(body), comment)
}
// EditIssueCommentOption options for editing a comment
type EditIssueCommentOption struct {
// required: true