mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 00:02:20 -05:00 
			
		
		
		
	WIP: Allow attachments for comments
This commit is contained in:
		
							parent
							
								
									6e9f1c52b1
								
							
						
					
					
						commit
						4617bef895
					
				@ -206,7 +206,7 @@ func runWeb(*cli.Context) {
 | 
				
			|||||||
		r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
 | 
							r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
 | 
				
			||||||
		r.Get("/:org/teams/:team/edit", org.EditTeam)
 | 
							r.Get("/:org/teams/:team/edit", org.EditTeam)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r.Get("/:org/team/:team",org.SingleTeam)
 | 
							r.Get("/:org/team/:team", org.SingleTeam)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r.Get("/:org/settings", org.Settings)
 | 
							r.Get("/:org/settings", org.Settings)
 | 
				
			||||||
		r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost)
 | 
							r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost)
 | 
				
			||||||
@ -238,6 +238,9 @@ func runWeb(*cli.Context) {
 | 
				
			|||||||
			r.Post("/:index/label", repo.UpdateIssueLabel)
 | 
								r.Post("/:index/label", repo.UpdateIssueLabel)
 | 
				
			||||||
			r.Post("/:index/milestone", repo.UpdateIssueMilestone)
 | 
								r.Post("/:index/milestone", repo.UpdateIssueMilestone)
 | 
				
			||||||
			r.Post("/:index/assignee", repo.UpdateAssignee)
 | 
								r.Post("/:index/assignee", repo.UpdateAssignee)
 | 
				
			||||||
 | 
								r.Post("/:index/attachment", repo.IssuePostAttachment)
 | 
				
			||||||
 | 
								r.Post("/:index/attachment/:id", repo.IssuePostAttachment)
 | 
				
			||||||
 | 
								r.Get("/:index/attachment/:id", repo.IssueGetAttachment)
 | 
				
			||||||
			r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
 | 
								r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
 | 
				
			||||||
			r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
 | 
								r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
 | 
				
			||||||
			r.Post("/labels/delete", repo.DeleteLabel)
 | 
								r.Post("/labels/delete", repo.DeleteLabel)
 | 
				
			||||||
 | 
				
			|||||||
@ -180,6 +180,11 @@ SESSION_ID_HASHKEY =
 | 
				
			|||||||
SERVICE = server
 | 
					SERVICE = server
 | 
				
			||||||
DISABLE_GRAVATAR = false
 | 
					DISABLE_GRAVATAR = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[attachment]
 | 
				
			||||||
 | 
					PATH = 
 | 
				
			||||||
 | 
					; One or more allowed types, e.g. image/jpeg|image/png
 | 
				
			||||||
 | 
					ALLOWED_TYPES = 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[log]
 | 
					[log]
 | 
				
			||||||
ROOT_PATH =
 | 
					ROOT_PATH =
 | 
				
			||||||
; Either "console", "file", "conn", "smtp" or "database", default is "console"
 | 
					; Either "console", "file", "conn", "smtp" or "database", default is "console"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										192
									
								
								models/issue.go
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								models/issue.go
									
									
									
									
									
								
							@ -7,19 +7,24 @@ package models
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-xorm/xorm"
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gogits/gogs/modules/base"
 | 
						"github.com/gogits/gogs/modules/base"
 | 
				
			||||||
 | 
						"github.com/gogits/gogs/modules/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	ErrIssueNotExist     = errors.New("Issue does not exist")
 | 
						ErrIssueNotExist       = errors.New("Issue does not exist")
 | 
				
			||||||
	ErrLabelNotExist     = errors.New("Label does not exist")
 | 
						ErrLabelNotExist       = errors.New("Label does not exist")
 | 
				
			||||||
	ErrMilestoneNotExist = errors.New("Milestone does not exist")
 | 
						ErrMilestoneNotExist   = errors.New("Milestone does not exist")
 | 
				
			||||||
	ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone")
 | 
						ErrWrongIssueCounter   = errors.New("Invalid number of issues for this milestone")
 | 
				
			||||||
 | 
						ErrAttachmentNotExist  = errors.New("Attachment does not exist")
 | 
				
			||||||
 | 
						ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Issue represents an issue or pull request of repository.
 | 
					// Issue represents an issue or pull request of repository.
 | 
				
			||||||
@ -91,6 +96,14 @@ func (i *Issue) GetAssignee() (err error) {
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *Issue) AfterDelete() {
 | 
				
			||||||
 | 
						_, err := DeleteAttachmentsByIssue(i.Id, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Info("Could not delete files for issue #%d: %s", i.Id, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateIssue creates new issue for repository.
 | 
					// CreateIssue creates new issue for repository.
 | 
				
			||||||
func NewIssue(issue *Issue) (err error) {
 | 
					func NewIssue(issue *Issue) (err error) {
 | 
				
			||||||
	sess := x.NewSession()
 | 
						sess := x.NewSession()
 | 
				
			||||||
@ -795,17 +808,19 @@ type Comment struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateComment creates comment of issue or commit.
 | 
					// CreateComment creates comment of issue or commit.
 | 
				
			||||||
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
 | 
					func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string, attachments []int64) (*Comment, error) {
 | 
				
			||||||
	sess := x.NewSession()
 | 
						sess := x.NewSession()
 | 
				
			||||||
	defer sess.Close()
 | 
						defer sess.Close()
 | 
				
			||||||
	if err := sess.Begin(); err != nil {
 | 
						if err := sess.Begin(); err != nil {
 | 
				
			||||||
		return err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
 | 
						comment := &Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
 | 
				
			||||||
		CommitId: commitId, Line: line, Content: content}); err != nil {
 | 
							CommitId: commitId, Line: line, Content: content}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := sess.Insert(comment); err != nil {
 | 
				
			||||||
		sess.Rollback()
 | 
							sess.Rollback()
 | 
				
			||||||
		return err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check comment type.
 | 
						// Check comment type.
 | 
				
			||||||
@ -814,22 +829,38 @@ func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, c
 | 
				
			|||||||
		rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
 | 
							rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
 | 
				
			||||||
		if _, err := sess.Exec(rawSql, issueId); err != nil {
 | 
							if _, err := sess.Exec(rawSql, issueId); err != nil {
 | 
				
			||||||
			sess.Rollback()
 | 
								sess.Rollback()
 | 
				
			||||||
			return err
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(attachments) > 0 {
 | 
				
			||||||
 | 
								rawSql = "UPDATE `attachment` SET comment_id = ? WHERE id IN (?)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								astrs := make([]string, 0, len(attachments))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for _, a := range attachments {
 | 
				
			||||||
 | 
									astrs = append(astrs, strconv.FormatInt(a, 10))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
 | 
				
			||||||
 | 
									sess.Rollback()
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case IT_REOPEN:
 | 
						case IT_REOPEN:
 | 
				
			||||||
		rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
 | 
							rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
 | 
				
			||||||
		if _, err := sess.Exec(rawSql, repoId); err != nil {
 | 
							if _, err := sess.Exec(rawSql, repoId); err != nil {
 | 
				
			||||||
			sess.Rollback()
 | 
								sess.Rollback()
 | 
				
			||||||
			return err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case IT_CLOSE:
 | 
						case IT_CLOSE:
 | 
				
			||||||
		rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
 | 
							rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
 | 
				
			||||||
		if _, err := sess.Exec(rawSql, repoId); err != nil {
 | 
							if _, err := sess.Exec(rawSql, repoId); err != nil {
 | 
				
			||||||
			sess.Rollback()
 | 
								sess.Rollback()
 | 
				
			||||||
			return err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return sess.Commit()
 | 
					
 | 
				
			||||||
 | 
						return comment, sess.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetIssueComments returns list of comment by given issue id.
 | 
					// GetIssueComments returns list of comment by given issue id.
 | 
				
			||||||
@ -838,3 +869,138 @@ func GetIssueComments(issueId int64) ([]Comment, error) {
 | 
				
			|||||||
	err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
 | 
						err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
 | 
				
			||||||
	return comments, err
 | 
						return comments, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Attachments returns the attachments for this comment.
 | 
				
			||||||
 | 
					func (c *Comment) Attachments() ([]*Attachment, error) {
 | 
				
			||||||
 | 
						return GetAttachmentsByComment(c.Id)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Comment) AfterDelete() {
 | 
				
			||||||
 | 
						_, err := DeleteAttachmentsByComment(c.Id, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Info("Could not delete files for comment %d on issue #%d: %s", c.Id, c.IssueId, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Attachment struct {
 | 
				
			||||||
 | 
						Id        int64
 | 
				
			||||||
 | 
						IssueId   int64
 | 
				
			||||||
 | 
						CommentId int64
 | 
				
			||||||
 | 
						Name      string
 | 
				
			||||||
 | 
						Path      string
 | 
				
			||||||
 | 
						Created   time.Time `xorm:"CREATED"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateAttachment creates a new attachment inside the database and
 | 
				
			||||||
 | 
					func CreateAttachment(issueId, commentId int64, name, path string) (*Attachment, error) {
 | 
				
			||||||
 | 
						sess := x.NewSession()
 | 
				
			||||||
 | 
						defer sess.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := sess.Begin(); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a := &Attachment{IssueId: issueId, CommentId: commentId, Name: name, Path: path}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := sess.Insert(a); err != nil {
 | 
				
			||||||
 | 
							sess.Rollback()
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return a, sess.Commit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Attachment returns the attachment by given ID.
 | 
				
			||||||
 | 
					func GetAttachmentById(id int64) (*Attachment, error) {
 | 
				
			||||||
 | 
						m := &Attachment{Id: id}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						has, err := x.Get(m)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !has {
 | 
				
			||||||
 | 
							return nil, ErrAttachmentNotExist
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return m, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAttachmentsByIssue returns a list of attachments for the given issue
 | 
				
			||||||
 | 
					func GetAttachmentsByIssue(issueId int64) ([]*Attachment, error) {
 | 
				
			||||||
 | 
						attachments := make([]*Attachment, 0, 10)
 | 
				
			||||||
 | 
						err := x.Where("issue_id = ?", issueId).Find(&attachments)
 | 
				
			||||||
 | 
						return attachments, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAttachmentsByComment returns a list of attachments for the given comment
 | 
				
			||||||
 | 
					func GetAttachmentsByComment(commentId int64) ([]*Attachment, error) {
 | 
				
			||||||
 | 
						attachments := make([]*Attachment, 0, 10)
 | 
				
			||||||
 | 
						err := x.Where("comment_id = ?", commentId).Find(&attachments)
 | 
				
			||||||
 | 
						return attachments, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteAttachment deletes the given attachment and optionally the associated file.
 | 
				
			||||||
 | 
					func DeleteAttachment(a *Attachment, remove bool) error {
 | 
				
			||||||
 | 
						_, err := DeleteAttachments([]*Attachment{a}, remove)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteAttachments deletes the given attachments and optionally the associated files.
 | 
				
			||||||
 | 
					func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
 | 
				
			||||||
 | 
						for i, a := range attachments {
 | 
				
			||||||
 | 
							if remove {
 | 
				
			||||||
 | 
								if err := os.Remove(a.Path); err != nil {
 | 
				
			||||||
 | 
									return i, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err := x.Delete(a.Id); err != nil {
 | 
				
			||||||
 | 
								return i, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return len(attachments), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
 | 
				
			||||||
 | 
					func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
 | 
				
			||||||
 | 
						attachments, err := GetAttachmentsByIssue(issueId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return DeleteAttachments(attachments, remove)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
 | 
				
			||||||
 | 
					func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
 | 
				
			||||||
 | 
						attachments, err := GetAttachmentsByComment(commentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return DeleteAttachments(attachments, remove)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AssignAttachment assigns the given attachment to the specified comment
 | 
				
			||||||
 | 
					func AssignAttachment(issueId, commentId, attachmentId int64) error {
 | 
				
			||||||
 | 
						a, err := GetAttachmentById(attachmentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.IssueId != issueId {
 | 
				
			||||||
 | 
							return ErrAttachmentNotLinked
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.CommentId = commentId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = x.Id(a.Id).Update(a)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,7 @@ func init() {
 | 
				
			|||||||
		new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
 | 
							new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
 | 
				
			||||||
		new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
 | 
							new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
 | 
				
			||||||
		new(Milestone), new(Label), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
 | 
							new(Milestone), new(Label), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
 | 
				
			||||||
		new(UpdateTask))
 | 
							new(UpdateTask), new(Attachment))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func LoadModelsConfig() {
 | 
					func LoadModelsConfig() {
 | 
				
			||||||
 | 
				
			|||||||
@ -71,6 +71,10 @@ var (
 | 
				
			|||||||
	LogModes    []string
 | 
						LogModes    []string
 | 
				
			||||||
	LogConfigs  []string
 | 
						LogConfigs  []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Attachment settings.
 | 
				
			||||||
 | 
						AttachmentPath         string
 | 
				
			||||||
 | 
						AttachmentAllowedTypes string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Cache settings.
 | 
						// Cache settings.
 | 
				
			||||||
	Cache        cache.Cache
 | 
						Cache        cache.Cache
 | 
				
			||||||
	CacheAdapter string
 | 
						CacheAdapter string
 | 
				
			||||||
@ -166,6 +170,13 @@ func NewConfigContext() {
 | 
				
			|||||||
	CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
 | 
						CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
 | 
				
			||||||
	ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
 | 
						ReverseProxyAuthUser = Cfg.MustValue("security", "REVERSE_PROXY_AUTHENTICATION_USER", "X-WEBAUTH-USER")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AttachmentPath = Cfg.MustValue("attachment", "PATH", "files/attachments")
 | 
				
			||||||
 | 
						AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "*/*")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Could not create directory %s: %s", AttachmentPath, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RunUser = Cfg.MustValue("", "RUN_USER")
 | 
						RunUser = Cfg.MustValue("", "RUN_USER")
 | 
				
			||||||
	curUser := os.Getenv("USER")
 | 
						curUser := os.Getenv("USER")
 | 
				
			||||||
	if len(curUser) == 0 {
 | 
						if len(curUser) == 0 {
 | 
				
			||||||
 | 
				
			|||||||
@ -520,6 +520,19 @@ function initIssue() {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }());
 | 
					    }());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (function() {
 | 
				
			||||||
 | 
					        var $attached = $("#attached");
 | 
				
			||||||
 | 
					        var $attachments = $("input[name=attachments]");
 | 
				
			||||||
 | 
					        var $addButton = $("#attachments-button");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var accepted = $addButton.attr("data-accept");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $addButton.on("click", function() {
 | 
				
			||||||
 | 
					            // TODO: (nuss-justin): open dialog, upload file, add id to list, add file to $attached list
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // issue edit mode
 | 
					    // issue edit mode
 | 
				
			||||||
    (function () {
 | 
					    (function () {
 | 
				
			||||||
        $("#issue-edit-btn").on("click", function () {
 | 
					        $("#issue-edit-btn").on("click", function () {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,9 @@ package repo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"mime"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@ -396,6 +399,8 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
 | 
				
			|||||||
		comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
 | 
							comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Title"] = issue.Name
 | 
						ctx.Data["Title"] = issue.Name
 | 
				
			||||||
	ctx.Data["Issue"] = issue
 | 
						ctx.Data["Issue"] = issue
 | 
				
			||||||
	ctx.Data["Comments"] = comments
 | 
						ctx.Data["Comments"] = comments
 | 
				
			||||||
@ -670,7 +675,7 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 | 
				
			|||||||
				cmtType = models.IT_REOPEN
 | 
									cmtType = models.IT_REOPEN
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil {
 | 
								if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil {
 | 
				
			||||||
				ctx.Handle(200, "issue.Comment(create status change comment)", err)
 | 
									ctx.Handle(200, "issue.Comment(create status change comment)", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -678,12 +683,14 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var comment *models.Comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var ms []string
 | 
						var ms []string
 | 
				
			||||||
	content := ctx.Query("content")
 | 
						content := ctx.Query("content")
 | 
				
			||||||
	if len(content) > 0 {
 | 
						if len(content) > 0 {
 | 
				
			||||||
		switch params["action"] {
 | 
							switch params["action"] {
 | 
				
			||||||
		case "new":
 | 
							case "new":
 | 
				
			||||||
			if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil {
 | 
								if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content, nil); err != nil {
 | 
				
			||||||
				ctx.Handle(500, "issue.Comment(create comment)", err)
 | 
									ctx.Handle(500, "issue.Comment(create comment)", err)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -709,6 +716,24 @@ func Comment(ctx *middleware.Context, params martini.Params) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						attachments := strings.Split(params["attachments"], ",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, a := range attachments {
 | 
				
			||||||
 | 
							aId, err := base.StrTo(a).Int64()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Handle(400, "issue.Comment(base.StrTo.Int64)", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = models.AssignAttachment(issue.Id, comment.Id, aId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Handle(400, "issue.Comment(models.AssignAttachment)", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Notify watchers.
 | 
						// Notify watchers.
 | 
				
			||||||
	act := &models.Action{
 | 
						act := &models.Action{
 | 
				
			||||||
		ActUserId:    ctx.User.Id,
 | 
							ActUserId:    ctx.User.Id,
 | 
				
			||||||
@ -985,3 +1010,118 @@ func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form au
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IssuePostAttachment(ctx *middleware.Context, params martini.Params) {
 | 
				
			||||||
 | 
						issueId, _ := base.StrTo(params["index"]).Int64()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if issueId == 0 {
 | 
				
			||||||
 | 
							ctx.Handle(400, "issue.IssuePostAttachment", nil)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commentId, err := base.StrTo(params["id"]).Int64()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil && len(params["id"]) > 0 {
 | 
				
			||||||
 | 
							ctx.JSON(400, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "invalid comment id",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						file, header, err := ctx.Req.FormFile("attachment")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(400, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "upload error",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check mime type, write to file, insert attachment to db
 | 
				
			||||||
 | 
						allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
 | 
				
			||||||
 | 
						allowed := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileType := mime.TypeByExtension(header.Filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, t := range allowedTypes {
 | 
				
			||||||
 | 
							t := strings.Trim(t, " ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if t == "*/*" || t == fileType {
 | 
				
			||||||
 | 
								allowed = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !allowed {
 | 
				
			||||||
 | 
							ctx.JSON(400, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "mime type not allowed",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(500, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "internal server error",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer out.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = io.Copy(out, file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(500, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "internal server error",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a, err := models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.JSON(500, map[string]interface{}{
 | 
				
			||||||
 | 
								"ok":    false,
 | 
				
			||||||
 | 
								"error": "internal server error",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(500, map[string]interface{}{
 | 
				
			||||||
 | 
							"ok": true,
 | 
				
			||||||
 | 
							"id": a.Id,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IssueGetAttachment(ctx *middleware.Context, params martini.Params) {
 | 
				
			||||||
 | 
						id, err := base.StrTo(params["id"]).Int64()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Handle(400, "issue.IssueGetAttachment(base.StrTo.Int64)", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						attachment, err := models.GetAttachmentById(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Handle(404, "issue.IssueGetAttachment(models.GetAttachmentById)", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.ServeFile(attachment.Path, attachment.Name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -62,6 +62,11 @@
 | 
				
			|||||||
                            <div class="panel-body markdown">
 | 
					                            <div class="panel-body markdown">
 | 
				
			||||||
                                {{str2html .Content}}
 | 
					                                {{str2html .Content}}
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="attachments">
 | 
				
			||||||
 | 
					                                {{range .Attachments}}
 | 
				
			||||||
 | 
					                                <a class="attachment" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a>
 | 
				
			||||||
 | 
					                                {{end}}
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    {{else if eq .Type 1}}
 | 
					                    {{else if eq .Type 1}}
 | 
				
			||||||
@ -103,8 +108,14 @@
 | 
				
			|||||||
                                    <div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div>
 | 
					                                    <div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div>
 | 
				
			||||||
 | 
					                                <div id="attached"></div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
                            <div class="text-right">
 | 
					                            <div class="text-right">
 | 
				
			||||||
                                <div class="form-group">
 | 
					                                <div class="form-group">
 | 
				
			||||||
 | 
					                                    <input type="hidden" name="attachments" value="" />
 | 
				
			||||||
 | 
					                                    <button data-accept="{{AllowedTypes}}" class="btn-default btn attachment-add" id="attachments-button">Add Attachments...</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    {{if .IsIssueOwner}}{{if .Issue.IsClosed}}
 | 
					                                    {{if .IsIssueOwner}}{{if .Issue.IsClosed}}
 | 
				
			||||||
                                    <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
 | 
					                                    <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
 | 
				
			||||||
                                    <input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}}{{end}}  
 | 
					                                    <input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}}{{end}}  
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user