|  | @@ -17,6 +17,7 @@ import (
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  var (
 | 
	
		
			
				|  |  |  	ErrIssueNotExist     = errors.New("Issue does not exist")
 | 
	
		
			
				|  |  | +	ErrLabelNotExist     = errors.New("Label does not exist")
 | 
	
		
			
				|  |  |  	ErrMilestoneNotExist = errors.New("Milestone does not exist")
 | 
	
		
			
				|  |  |  )
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -28,14 +29,15 @@ type Issue struct {
 | 
	
		
			
				|  |  |  	Name            string
 | 
	
		
			
				|  |  |  	Repo            *Repository `xorm:"-"`
 | 
	
		
			
				|  |  |  	PosterId        int64
 | 
	
		
			
				|  |  | -	Poster          *User `xorm:"-"`
 | 
	
		
			
				|  |  | +	Poster          *User    `xorm:"-"`
 | 
	
		
			
				|  |  | +	LabelIds        string   `xorm:"TEXT"`
 | 
	
		
			
				|  |  | +	Labels          []*Label `xorm:"-"`
 | 
	
		
			
				|  |  |  	MilestoneId     int64
 | 
	
		
			
				|  |  |  	AssigneeId      int64
 | 
	
		
			
				|  |  |  	Assignee        *User `xorm:"-"`
 | 
	
		
			
				|  |  |  	IsRead          bool  `xorm:"-"`
 | 
	
		
			
				|  |  |  	IsPull          bool  // Indicates whether is a pull request or not.
 | 
	
		
			
				|  |  |  	IsClosed        bool
 | 
	
		
			
				|  |  | -	Labels          string `xorm:"TEXT"`
 | 
	
		
			
				|  |  |  	Content         string `xorm:"TEXT"`
 | 
	
		
			
				|  |  |  	RenderedContent string `xorm:"-"`
 | 
	
		
			
				|  |  |  	Priority        int
 | 
	
	
		
			
				|  | @@ -54,11 +56,37 @@ func (i *Issue) GetPoster() (err error) {
 | 
	
		
			
				|  |  |  	return err
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +func (i *Issue) GetLabels() error {
 | 
	
		
			
				|  |  | +	if len(i.LabelIds) < 3 {
 | 
	
		
			
				|  |  | +		return nil
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
 | 
	
		
			
				|  |  | +	i.Labels = make([]*Label, 0, len(strIds))
 | 
	
		
			
				|  |  | +	for _, strId := range strIds {
 | 
	
		
			
				|  |  | +		id, _ := base.StrTo(strId).Int64()
 | 
	
		
			
				|  |  | +		if id > 0 {
 | 
	
		
			
				|  |  | +			l, err := GetLabelById(id)
 | 
	
		
			
				|  |  | +			if err != nil {
 | 
	
		
			
				|  |  | +				if err == ErrLabelNotExist {
 | 
	
		
			
				|  |  | +					continue
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				return err
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			i.Labels = append(i.Labels, l)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  func (i *Issue) GetAssignee() (err error) {
 | 
	
		
			
				|  |  |  	if i.AssigneeId == 0 {
 | 
	
		
			
				|  |  |  		return nil
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  	i.Assignee, err = GetUserById(i.AssigneeId)
 | 
	
		
			
				|  |  | +	if err == ErrUserNotExist {
 | 
	
		
			
				|  |  | +		return nil
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  	return err
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -108,7 +136,7 @@ func GetIssueById(id int64) (*Issue, error) {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // GetIssues returns a list of issues by given conditions.
 | 
	
		
			
				|  |  | -func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortType string) ([]Issue, error) {
 | 
	
		
			
				|  |  | +func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
 | 
	
		
			
				|  |  |  	sess := orm.Limit(20, (page-1)*20)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if rid > 0 {
 | 
	
	
		
			
				|  | @@ -127,9 +155,9 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy
 | 
	
		
			
				|  |  |  		sess.And("milestone_id=?", mid)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if len(labels) > 0 {
 | 
	
		
			
				|  |  | -		for _, label := range strings.Split(labels, ",") {
 | 
	
		
			
				|  |  | -			sess.And("labels like '%$" + label + "|%'")
 | 
	
		
			
				|  |  | +	if len(labelIds) > 0 {
 | 
	
		
			
				|  |  | +		for _, label := range strings.Split(labelIds, ",") {
 | 
	
		
			
				|  |  | +			sess.And("label_ids like '%$" + label + "|%'")
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -155,6 +183,13 @@ func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortTy
 | 
	
		
			
				|  |  |  	return issues, err
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// GetIssuesByLabel returns a list of issues by given label and repository.
 | 
	
		
			
				|  |  | +func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
 | 
	
		
			
				|  |  | +	issues := make([]*Issue, 0, 10)
 | 
	
		
			
				|  |  | +	err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
 | 
	
		
			
				|  |  | +	return issues, err
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // GetIssueCountByPoster returns number of issues of repository by poster.
 | 
	
		
			
				|  |  |  func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
 | 
	
		
			
				|  |  |  	count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
 | 
	
	
		
			
				|  | @@ -175,7 +210,6 @@ type IssueUser struct {
 | 
	
		
			
				|  |  |  	IssueId     int64
 | 
	
		
			
				|  |  |  	RepoId      int64
 | 
	
		
			
				|  |  |  	MilestoneId int64
 | 
	
		
			
				|  |  | -	Labels      string `xorm:"TEXT"`
 | 
	
		
			
				|  |  |  	IsRead      bool
 | 
	
		
			
				|  |  |  	IsAssigned  bool
 | 
	
		
			
				|  |  |  	IsMentioned bool
 | 
	
	
		
			
				|  | @@ -400,6 +434,98 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
 | 
	
		
			
				|  |  |  	return nil
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// .____          ___.          .__
 | 
	
		
			
				|  |  | +// |    |   _____ \_ |__   ____ |  |
 | 
	
		
			
				|  |  | +// |    |   \__  \ | __ \_/ __ \|  |
 | 
	
		
			
				|  |  | +// |    |___ / __ \| \_\ \  ___/|  |__
 | 
	
		
			
				|  |  | +// |_______ (____  /___  /\___  >____/
 | 
	
		
			
				|  |  | +//         \/    \/    \/     \/
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Label represents a label of repository for issues.
 | 
	
		
			
				|  |  | +type Label struct {
 | 
	
		
			
				|  |  | +	Id              int64
 | 
	
		
			
				|  |  | +	RepoId          int64 `xorm:"INDEX"`
 | 
	
		
			
				|  |  | +	Name            string
 | 
	
		
			
				|  |  | +	Color           string `xorm:"VARCHAR(7)"`
 | 
	
		
			
				|  |  | +	NumIssues       int
 | 
	
		
			
				|  |  | +	NumClosedIssues int
 | 
	
		
			
				|  |  | +	NumOpenIssues   int  `xorm:"-"`
 | 
	
		
			
				|  |  | +	IsChecked       bool `xorm:"-"`
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// CalOpenIssues calculates the open issues of label.
 | 
	
		
			
				|  |  | +func (m *Label) CalOpenIssues() {
 | 
	
		
			
				|  |  | +	m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// NewLabel creates new label of repository.
 | 
	
		
			
				|  |  | +func NewLabel(l *Label) error {
 | 
	
		
			
				|  |  | +	_, err := orm.Insert(l)
 | 
	
		
			
				|  |  | +	return err
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// GetLabelById returns a label by given ID.
 | 
	
		
			
				|  |  | +func GetLabelById(id int64) (*Label, error) {
 | 
	
		
			
				|  |  | +	l := &Label{Id: id}
 | 
	
		
			
				|  |  | +	has, err := orm.Get(l)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  | +	} else if !has {
 | 
	
		
			
				|  |  | +		return nil, ErrLabelNotExist
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return l, nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// GetLabels returns a list of labels of given repository ID.
 | 
	
		
			
				|  |  | +func GetLabels(repoId int64) ([]*Label, error) {
 | 
	
		
			
				|  |  | +	labels := make([]*Label, 0, 10)
 | 
	
		
			
				|  |  | +	err := orm.Where("repo_id=?", repoId).Find(&labels)
 | 
	
		
			
				|  |  | +	return labels, err
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// UpdateLabel updates label information.
 | 
	
		
			
				|  |  | +func UpdateLabel(l *Label) error {
 | 
	
		
			
				|  |  | +	_, err := orm.Id(l.Id).Update(l)
 | 
	
		
			
				|  |  | +	return err
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// DeleteLabel delete a label of given repository.
 | 
	
		
			
				|  |  | +func DeleteLabel(repoId int64, strId string) error {
 | 
	
		
			
				|  |  | +	id, _ := base.StrTo(strId).Int64()
 | 
	
		
			
				|  |  | +	l, err := GetLabelById(id)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		if err == ErrLabelNotExist {
 | 
	
		
			
				|  |  | +			return nil
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		return err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	issues, err := GetIssuesByLabel(repoId, strId)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	sess := orm.NewSession()
 | 
	
		
			
				|  |  | +	defer sess.Close()
 | 
	
		
			
				|  |  | +	if err = sess.Begin(); err != nil {
 | 
	
		
			
				|  |  | +		return err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	for _, issue := range issues {
 | 
	
		
			
				|  |  | +		issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
 | 
	
		
			
				|  |  | +		if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
 | 
	
		
			
				|  |  | +			sess.Rollback()
 | 
	
		
			
				|  |  | +			return err
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if _, err = sess.Delete(l); err != nil {
 | 
	
		
			
				|  |  | +		sess.Rollback()
 | 
	
		
			
				|  |  | +		return err
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return sess.Commit()
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  //    _____  .__.__                   __
 | 
	
		
			
				|  |  |  //   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 | 
	
		
			
				|  |  |  //  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
 | 
	
	
		
			
				|  | @@ -611,42 +737,6 @@ func DeleteMilestone(m *Milestone) (err error) {
 | 
	
		
			
				|  |  |  	return sess.Commit()
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -// .____          ___.          .__
 | 
	
		
			
				|  |  | -// |    |   _____ \_ |__   ____ |  |
 | 
	
		
			
				|  |  | -// |    |   \__  \ | __ \_/ __ \|  |
 | 
	
		
			
				|  |  | -// |    |___ / __ \| \_\ \  ___/|  |__
 | 
	
		
			
				|  |  | -// |_______ (____  /___  /\___  >____/
 | 
	
		
			
				|  |  | -//         \/    \/    \/     \/
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// Label represents a label of repository for issues.
 | 
	
		
			
				|  |  | -type Label struct {
 | 
	
		
			
				|  |  | -	Id              int64
 | 
	
		
			
				|  |  | -	RepoId          int64 `xorm:"INDEX"`
 | 
	
		
			
				|  |  | -	Name            string
 | 
	
		
			
				|  |  | -	Color           string `xorm:"VARCHAR(7)"`
 | 
	
		
			
				|  |  | -	NumIssues       int
 | 
	
		
			
				|  |  | -	NumClosedIssues int
 | 
	
		
			
				|  |  | -	NumOpenIssues   int `xorm:"-"`
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// CalOpenIssues calculates the open issues of label.
 | 
	
		
			
				|  |  | -func (m *Label) CalOpenIssues() {
 | 
	
		
			
				|  |  | -	m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// NewLabel creates new label of repository.
 | 
	
		
			
				|  |  | -func NewLabel(l *Label) error {
 | 
	
		
			
				|  |  | -	_, err := orm.Insert(l)
 | 
	
		
			
				|  |  | -	return err
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// GetLabels returns a list of labels of given repository ID.
 | 
	
		
			
				|  |  | -func GetLabels(repoId int64) ([]*Label, error) {
 | 
	
		
			
				|  |  | -	labels := make([]*Label, 0, 10)
 | 
	
		
			
				|  |  | -	err := orm.Where("repo_id=?", repoId).Find(&labels)
 | 
	
		
			
				|  |  | -	return labels, err
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  // _________                                       __
 | 
	
		
			
				|  |  |  // \_   ___ \  ____   _____   _____   ____   _____/  |_
 | 
	
		
			
				|  |  |  // /    \  \/ /  _ \ /     \ /     \_/ __ \ /    \   __\
 |