diff --git a/gogs.go b/gogs.go index 23fed5d894..df1062e0eb 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/base" ) -const APP_VER = "0.3.3.0506 Alpha" +const APP_VER = "0.3.3.0507 Alpha" func init() { base.AppVer = APP_VER diff --git a/models/issue.go b/models/issue.go index 64fc45a61d..eb88190cc8 100644 --- a/models/issue.go +++ b/models/issue.go @@ -8,8 +8,6 @@ import ( "errors" "strings" "time" - - "github.com/gogits/gogs/modules/base" ) var ( @@ -21,7 +19,7 @@ type Issue struct { Id int64 Index int64 // Index in one repository. Name string - RepoId int64 `xorm:"index"` + RepoId int64 `xorm:"INDEX"` Repo *Repository `xorm:"-"` PosterId int64 Poster *User `xorm:"-"` @@ -35,44 +33,51 @@ type Issue struct { Priority int NumComments int Deadline time.Time - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` + Created time.Time `xorm:"CREATED"` + Updated time.Time `xorm:"UPDATED"` +} + +func (i *Issue) GetPoster() (err error) { + i.Poster, err = GetUserById(i.PosterId) + return err +} + +// IssseUser represents an issue-user relation. +type IssseUser struct { + Id int64 + Iid int64 // Issue ID. + Rid int64 // Repository ID. + Uid int64 // User ID. + IsRead bool + IsAssigned bool + IsMentioned bool + IsClosed bool } // CreateIssue creates new issue for repository. -func CreateIssue(userId, repoId, milestoneId, assigneeId int64, issueCount int, name, labels, content string, isPull bool) (issue *Issue, err error) { +func NewIssue(issue *Issue) (err error) { sess := orm.NewSession() defer sess.Close() - sess.Begin() - - issue = &Issue{ - Index: int64(issueCount) + 1, - Name: name, - RepoId: repoId, - PosterId: userId, - MilestoneId: milestoneId, - AssigneeId: assigneeId, - IsPull: isPull, - Labels: labels, - Content: content, + if err = sess.Begin(); err != nil { + return err } + if _, err = sess.Insert(issue); err != nil { sess.Rollback() - return nil, err + return err } rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?" - if _, err = sess.Exec(rawSql, repoId); err != nil { + if _, err = sess.Exec(rawSql, issue.RepoId); err != nil { sess.Rollback() - return nil, err + return err } - - return issue, sess.Commit() + return sess.Commit() } -// GetIssueById returns issue object by given id. -func GetIssueByIndex(repoId, index int64) (*Issue, error) { - issue := &Issue{RepoId: repoId, Index: index} +// GetIssueByIndex returns issue by given index in repository. +func GetIssueByIndex(rid, index int64) (*Issue, error) { + issue := &Issue{RepoId: rid, Index: index} has, err := orm.Get(issue) if err != nil { return nil, err @@ -83,30 +88,28 @@ func GetIssueByIndex(repoId, index int64) (*Issue, error) { } // GetIssues returns a list of issues by given conditions. -func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) { +func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labels, sortType string) ([]Issue, error) { sess := orm.Limit(20, (page-1)*20) - if repoId > 0 { - sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed) + if rid > 0 { + sess.Where("repo_id=?", rid).And("is_closed=?", isClosed) } else { sess.Where("is_closed=?", isClosed) } - if userId > 0 { - sess.And("assignee_id=?", userId) - } else if posterId > 0 { - sess.And("poster_id=?", posterId) - } else if isMention { - sess.And("mentions like '%$" + base.ToStr(userId) + "|%'") + if uid > 0 { + sess.And("assignee_id=?", uid) + } else if pid > 0 { + sess.And("poster_id=?", pid) } - if milestoneId > 0 { - sess.And("milestone_id=?", milestoneId) + if mid > 0 { + sess.And("milestone_id=?", mid) } if len(labels) > 0 { for _, label := range strings.Split(labels, ",") { - sess.And("mentions like '%$" + label + "|%'") + sess.And("labels like '%$" + label + "|%'") } } @@ -130,22 +133,133 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, return issues, err } +// PairsContains returns true when pairs list contains given issue. +func PairsContains(ius []*IssseUser, issueId int64) bool { + for i := range ius { + if ius[i].Iid == issueId { + return true + } + } + return false +} + +// GetIssueUserPairs returns all issue-user pairs by given repository and user. +func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssseUser, error) { + ius := make([]*IssseUser, 0, 10) + err := orm.Find(&ius, &IssseUser{Rid: rid, Uid: uid, IsClosed: isClosed}) + return ius, err +} + // GetUserIssueCount returns the number of issues that were created by given user in repository. -func GetUserIssueCount(userId, repoId int64) int64 { - count, _ := orm.Where("poster_id=?", userId).And("repo_id=?", repoId).Count(new(Issue)) +func GetUserIssueCount(uid, rid int64) int64 { + count, _ := orm.Where("poster_id=?", uid).And("repo_id=?", rid).Count(new(Issue)) return count } +// IssueStats represents issue statistic information. +type IssueStats struct { + OpenCount, ClosedCount int64 + AllCount int64 + AssignCount int64 + CreateCount int64 + MentionCount int64 +} + +// Filter modes. +const ( + FM_ASSIGN = iota + 1 + FM_CREATE + FM_MENTION +) + +// GetIssueStats returns issue statistic information by given condition. +func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats { + stats := &IssueStats{} + issue := new(Issue) + + sess := orm.Where("repo_id=?", rid) + tmpSess := sess + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue) + if isShowClosed { + stats.AllCount = stats.ClosedCount + } else { + stats.AllCount = stats.OpenCount + } + + if filterMode != FM_MENTION { + sess = orm.Where("repo_id=?", rid) + switch filterMode { + case FM_ASSIGN: + sess.And("assignee_id=?", uid) + case FM_CREATE: + sess.And("poster_id=?", uid) + default: + goto nofilter + } + *tmpSess = *sess + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue) + } else { + sess := orm.Where("rid=?", rid).And("uid=?", uid).And("is_mentioned=?", true) + tmpSess := sess + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssseUser)) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssseUser)) + } +nofilter: + stats.AssignCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue) + stats.CreateCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue) + stats.MentionCount, _ = orm.Where("rid=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssseUser)) + return stats +} + +// GetUserIssueStats returns issue statistic information for dashboard by given condition. +func GetUserIssueStats(uid int64, filterMode int) *IssueStats { + stats := &IssueStats{} + issue := new(Issue) + iu := new(IssseUser) + + sess := orm.Where("uid=?", uid) + tmpSess := sess + if filterMode == 0 { + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(iu) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(iu) + } + + switch filterMode { + case FM_ASSIGN: + sess.And("is_assigned=?", true) + *tmpSess = *sess + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(iu) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(iu) + case FM_CREATE: + sess.Where("poster_id=?", uid) + *tmpSess = *sess + stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue) + *tmpSess = *sess + stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue) + } + + stats.AssignCount, _ = orm.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue) + stats.CreateCount, _ = orm.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue) + return stats +} + // UpdateIssue updates information of issue. func UpdateIssue(issue *Issue) error { - _, err := orm.Id(issue.Id).AllCols().Update(issue) + _, err := orm.AllCols().Update(issue) return err } // Label represents a list of labels of repository for issues. type Label struct { Id int64 - RepoId int64 `xorm:"index"` + RepoId int64 `xorm:"INDEX"` Names string Colors string } @@ -154,12 +268,12 @@ type Label struct { type Milestone struct { Id int64 Name string - RepoId int64 `xorm:"index"` + RepoId int64 `xorm:"INDEX"` IsClosed bool Content string NumIssues int DueDate time.Time - Created time.Time `xorm:"created"` + Created time.Time `xorm:"CREATED"` } // Issue types. @@ -179,7 +293,7 @@ type Comment struct { CommitId int64 Line int64 Content string - Created time.Time `xorm:"created"` + Created time.Time `xorm:"CREATED"` } // CreateComment creates comment of issue or commit. diff --git a/models/models.go b/models/models.go index 6e4f7d1022..d7f3629927 100644 --- a/models/models.go +++ b/models/models.go @@ -34,7 +34,7 @@ var ( func init() { tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch), new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow), - new(Mirror), new(Release), new(LoginSource), new(Webhook)) + new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssseUser)) } func LoadModelsConfig() { diff --git a/models/publickey.go b/models/publickey.go index e594bbe91b..7ef25b9ceb 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -6,9 +6,9 @@ package models import ( "bufio" - "bytes" "errors" "fmt" + "io" "io/ioutil" "os" "path" @@ -26,7 +26,7 @@ import ( const ( // "### autogenerated by gitgos, DO NOT EDIT\n" - TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + _TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n" ) var ( @@ -64,7 +64,7 @@ func init() { } } -// PublicKey represents a SSH key of user. +// PublicKey represents a SSH key. type PublicKey struct { Id int64 OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` @@ -75,14 +75,29 @@ type PublicKey struct { Updated time.Time `xorm:"UPDATED"` } -// GenAuthorizedKey returns formatted public key string. -func GenAuthorizedKey(keyId int64, key string) string { - return fmt.Sprintf(TPL_PUBLICK_KEY+"\n", appPath, keyId, key) +// GetAuthorizedString generates and returns formatted public key string for authorized_keys file. +func (key *PublicKey) GetAuthorizedString() string { + return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.Id, key.Content) } -// AddPublicKey adds new public key to database and SSH key file. +// saveAuthorizedKeyFile writes SSH key content to authorized_keys file. +func saveAuthorizedKeyFile(key *PublicKey) error { + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + + fpath := filepath.Join(sshPath, "authorized_keys") + f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(key.GetAuthorizedString()) + return err +} + +// AddPublicKey adds new public key to database and authorized_keys file. func AddPublicKey(key *PublicKey) (err error) { - // Check if public key name has been used. has, err := orm.Get(key) if err != nil { return err @@ -91,7 +106,7 @@ func AddPublicKey(key *PublicKey) (err error) { } // Calculate fingerprint. - tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), + tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), "id_rsa.pub"), "\\", "/", -1) os.MkdirAll(path.Dir(tmpPath), os.ModePerm) if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil { @@ -108,8 +123,8 @@ func AddPublicKey(key *PublicKey) (err error) { // Save SSH key. if _, err = orm.Insert(key); err != nil { return err - } - if err = SaveAuthorizedKeyFile(key); err != nil { + } else if err = saveAuthorizedKeyFile(key); err != nil { + // Roll back. if _, err2 := orm.Delete(key); err2 != nil { return err2 } @@ -119,6 +134,13 @@ func AddPublicKey(key *PublicKey) (err error) { return nil } +// ListPublicKey returns a list of all public keys that user has. +func ListPublicKey(uid int64) ([]PublicKey, error) { + keys := make([]PublicKey, 0, 5) + err := orm.Find(&keys, &PublicKey{OwnerId: uid}) + return keys, err +} + // rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file. func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error { sshOpLocker.Lock() @@ -137,28 +159,38 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error { defer fw.Close() isFound := false - keyword := []byte(fmt.Sprintf("key-%d", key.Id)) - content := []byte(key.Content) + keyword := fmt.Sprintf("key-%d", key.Id) + buf := bufio.NewReader(fr) + for { + line, errRead := buf.ReadString('\n') + line = strings.TrimSpace(line) - snr := bufio.NewScanner(fr) - for snr.Scan() { - line := append(bytes.TrimSpace(snr.Bytes()), '\n') - if len(line) == 0 { - continue + if errRead != nil { + if errRead != io.EOF { + return errRead + } + + // Reached end of file, if nothing to read then break, + // otherwise handle the last line. + if len(line) == 0 { + break + } } // Found the line and copy rest of file. - if !isFound && bytes.Contains(line, keyword) && bytes.Contains(line, content) { + if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) { isFound = true continue } - // Still finding the line, copy the line that currently read. - if _, err = fw.Write(line); err != nil { + if _, err = fw.WriteString(line + "\n"); err != nil { return err } - } + if errRead == io.EOF { + break + } + } return nil } @@ -175,37 +207,14 @@ func DeletePublicKey(key *PublicKey) error { return err } - p := filepath.Join(sshPath, "authorized_keys") - tmpP := filepath.Join(sshPath, "authorized_keys.tmp") - log.Trace("publickey.DeletePublicKey(authorized_keys): %s", p) + fpath := filepath.Join(sshPath, "authorized_keys") + tmpPath := filepath.Join(sshPath, "authorized_keys.tmp") + log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath) - if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil { + if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil { return err - } else if err = os.Remove(p); err != nil { + } else if err = os.Remove(fpath); err != nil { return err } - return os.Rename(tmpP, p) -} - -// ListPublicKey returns a list of public keys that user has. -func ListPublicKey(userId int64) ([]PublicKey, error) { - keys := make([]PublicKey, 0) - err := orm.Find(&keys, &PublicKey{OwnerId: userId}) - return keys, err -} - -// SaveAuthorizedKeyFile writes SSH key content to SSH key file. -func SaveAuthorizedKeyFile(key *PublicKey) error { - sshOpLocker.Lock() - defer sshOpLocker.Unlock() - - p := filepath.Join(sshPath, "authorized_keys") - f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) - if err != nil { - return err - } - defer f.Close() - - _, err = f.WriteString(GenAuthorizedKey(key.Id, key.Content)) - return err + return os.Rename(tmpPath, fpath) } diff --git a/models/repo.go b/models/repo.go index d2b92be487..079e100a13 100644 --- a/models/repo.go +++ b/models/repo.go @@ -694,8 +694,8 @@ func GetRepositoryById(id int64) (*Repository, error) { } // GetRepositories returns the list of repositories of given user. -func GetRepositories(user *User, private bool) ([]Repository, error) { - repos := make([]Repository, 0, 10) +func GetRepositories(user *User, private bool) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) sess := orm.Desc("updated") if !private { sess.Where("is_private=?", false) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 2bd2f33a1b..2501e933c0 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -24,61 +24,84 @@ func Issues(ctx *middleware.Context) { ctx.Data["Title"] = "Issues" ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = true - ctx.Data["ViewType"] = "all" - milestoneId, _ := base.StrTo(ctx.Query("milestone")).Int() - page, _ := base.StrTo(ctx.Query("page")).Int() - - ctx.Data["IssueCreatedCount"] = 0 - - var posterId int64 = 0 - isCreatedBy := ctx.Query("type") == "created_by" - if isCreatedBy { - if !ctx.IsSigned { - ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) - ctx.Redirect("/user/login/", 302) - return - } - ctx.Data["ViewType"] = "created_by" + viewType := ctx.Query("type") + types := []string{"assigned", "created_by", "mentioned"} + if !com.IsSliceContainsStr(types, viewType) { + viewType = "all" } + isShowClosed := ctx.Query("state") == "closed" + + if viewType != "all" { + if !ctx.IsSigned { + ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) + ctx.Redirect("/user/login") + return + } + } + + var assigneeId, posterId int64 + var filterMode int + switch viewType { + case "assigned": + assigneeId = ctx.User.Id + filterMode = models.FM_ASSIGN + case "created_by": + posterId = ctx.User.Id + filterMode = models.FM_CREATE + case "mentioned": + filterMode = models.FM_MENTION + } + + mid, _ := base.StrTo(ctx.Query("milestone")).Int64() + page, _ := base.StrTo(ctx.Query("page")).Int() + // Get issues. - issues, err := models.GetIssues(0, ctx.Repo.Repository.Id, posterId, int64(milestoneId), page, - ctx.Query("state") == "closed", false, ctx.Query("labels"), ctx.Query("sortType")) + issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page, + isShowClosed, ctx.Query("labels"), ctx.Query("sortType")) if err != nil { - ctx.Handle(200, "issue.Issues: %v", err) + ctx.Handle(500, "issue.Issues(GetIssues): %v", err) return } - if ctx.IsSigned { - posterId = ctx.User.Id - } - var createdByCount int - - showIssues := make([]models.Issue, 0, len(issues)) - // Get posters. - for i := range issues { - u, err := models.GetUserById(issues[i].PosterId) + var pairs []*models.IssseUser + if filterMode == models.FM_MENTION { + // Get issue-user pairs. + pairs, err = models.GetIssueUserPairs(ctx.Repo.Repository.Id, ctx.User.Id, isShowClosed) if err != nil { - ctx.Handle(200, "issue.Issues(get poster): %v", err) + ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err) return } - if isCreatedBy && u.Id != posterId { - continue - } - if u.Id == posterId { - createdByCount++ - } - issues[i].Poster = u - showIssues = append(showIssues, issues[i]) } - ctx.Data["Issues"] = showIssues - ctx.Data["IssueCount"] = ctx.Repo.Repository.NumIssues - ctx.Data["OpenCount"] = ctx.Repo.Repository.NumOpenIssues - ctx.Data["ClosedCount"] = ctx.Repo.Repository.NumClosedIssues - ctx.Data["IssueCreatedCount"] = createdByCount - ctx.Data["IsShowClosed"] = ctx.Query("state") == "closed" + // Get posters. + for i := range issues { + if filterMode == models.FM_MENTION && !models.PairsContains(pairs, issues[i].Id) { + continue + } + + if err = issues[i].GetPoster(); err != nil { + ctx.Handle(500, "issue.Issues(GetPoster): %v", err) + return + } + } + + var uid int64 = -1 + if ctx.User != nil { + uid = ctx.User.Id + } + issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode) + ctx.Data["IssueStats"] = issueStats + ctx.Data["ViewType"] = viewType + ctx.Data["Issues"] = issues + ctx.Data["IsShowClosed"] = isShowClosed + if isShowClosed { + ctx.Data["State"] = "closed" + ctx.Data["ShowCount"] = issueStats.ClosedCount + } else { + ctx.Data["ShowCount"] = issueStats.OpenCount + } ctx.HTML(200, "issue/list") } @@ -99,15 +122,23 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C return } - issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId, - ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false) - if err != nil { - ctx.Handle(500, "issue.CreateIssue(CreateIssue)", err) + issue := &models.Issue{ + Index: int64(ctx.Repo.Repository.NumIssues) + 1, + Name: form.IssueName, + RepoId: ctx.Repo.Repository.Id, + PosterId: ctx.User.Id, + MilestoneId: form.MilestoneId, + AssigneeId: form.AssigneeId, + Labels: form.Labels, + Content: form.Content, + } + if err := models.NewIssue(issue); err != nil { + ctx.Handle(500, "issue.CreateIssue(NewIssue)", err) return } // Notify watchers. - if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email, + if err := models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email, OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil { ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err) @@ -144,13 +175,13 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C } func ViewIssue(ctx *middleware.Context, params martini.Params) { - index, err := base.StrTo(params["index"]).Int() - if err != nil { - ctx.Handle(404, "issue.ViewIssue", err) + idx, _ := base.StrTo(params["index"]).Int64() + if idx == 0 { + ctx.Handle(404, "issue.ViewIssue", nil) return } - issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) if err != nil { if err == models.ErrIssueNotExist { ctx.Handle(404, "issue.ViewIssue", err) @@ -160,10 +191,10 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { return } - // Get posters. + // Get poster. u, err := models.GetUserById(issue.PosterId) if err != nil { - ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) + ctx.Handle(500, "issue.ViewIssue(GetUserById): %v", err) return } issue.Poster = u @@ -172,7 +203,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { // Get comments. comments, err := models.GetIssueComments(issue.Id) if err != nil { - ctx.Handle(200, "issue.ViewIssue(get comments): %v", err) + ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) return } @@ -180,7 +211,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { for i := range comments { u, err := models.GetUserById(comments[i].PosterId) if err != nil { - ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) + ctx.Handle(500, "issue.ViewIssue(get poster of comment): %v", err) return } comments[i].Poster = u diff --git a/routers/user/home.go b/routers/user/home.go index 12099a5195..588cb8e3b0 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -7,6 +7,7 @@ package user import ( "fmt" + "github.com/Unknwon/com" "github.com/go-martini/martini" "github.com/gogits/gogs/models" @@ -105,85 +106,132 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) { func Issues(ctx *middleware.Context) { ctx.Data["Title"] = "Your Issues" - ctx.Data["ViewType"] = "all" - page, _ := base.StrTo(ctx.Query("page")).Int() - repoId, _ := base.StrTo(ctx.Query("repoid")).Int64() - - ctx.Data["RepoId"] = repoId - - var posterId int64 = 0 - if ctx.Query("type") == "created_by" { - posterId = ctx.User.Id - ctx.Data["ViewType"] = "created_by" + viewType := ctx.Query("type") + types := []string{"assigned", "created_by"} + if !com.IsSliceContainsStr(types, viewType) { + viewType = "all" } + isShowClosed := ctx.Query("state") == "closed" + + var assigneeId, posterId int64 + var filterMode int + switch viewType { + case "assigned": + assigneeId = ctx.User.Id + filterMode = models.FM_ASSIGN + case "created_by": + posterId = ctx.User.Id + filterMode = models.FM_CREATE + } + _, _ = assigneeId, posterId + + // page, _ := base.StrTo(ctx.Query("page")).Int() + // repoId, _ := base.StrTo(ctx.Query("repoid")).Int64() + + // ctx.Data["RepoId"] = repoId + + // var posterId int64 = 0 + // if ctx.Query("type") == "created_by" { + // posterId = ctx.User.Id + // ctx.Data["ViewType"] = "created_by" + // } + + rid, _ := base.StrTo(ctx.Query("repoid")).Int64() + issueStats := models.GetUserIssueStats(ctx.User.Id, filterMode) + // Get all repositories. repos, err := models.GetRepositories(ctx.User, true) if err != nil { - ctx.Handle(200, "user.Issues(get repositories)", err) + ctx.Handle(500, "user.Issues(get repositories)", err) return } - showRepos := make([]models.Repository, 0, len(repos)) - - isShowClosed := ctx.Query("state") == "closed" - var closedIssueCount, createdByCount, allIssueCount int + showRepos := make([]*models.Repository, 0, len(repos)) // Get all issues. - allIssues := make([]models.Issue, 0, 5*len(repos)) - for i, repo := range repos { - issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "") - if err != nil { - ctx.Handle(200, "user.Issues(get issues)", err) - return - } - - allIssueCount += repo.NumIssues - closedIssueCount += repo.NumClosedIssues - - // Set repository information to issues. - for j := range issues { - issues[j].Repo = &repos[i] - } - allIssues = append(allIssues, issues...) - - repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues - if repos[i].NumOpenIssues > 0 { - showRepos = append(showRepos, repos[i]) - } - } - - showIssues := make([]models.Issue, 0, len(allIssues)) - ctx.Data["IsShowClosed"] = isShowClosed - - // Get posters and filter issues. - for i := range allIssues { - u, err := models.GetUserById(allIssues[i].PosterId) - if err != nil { - ctx.Handle(200, "user.Issues(get poster): %v", err) - return - } - allIssues[i].Poster = u - if u.Id == ctx.User.Id { - createdByCount++ - } - - if repoId > 0 && repoId != allIssues[i].Repo.Id { + // allIssues := make([]models.Issue, 0, 5*len(repos)) + for _, repo := range repos { + if repo.NumIssues == 0 { continue } - if isShowClosed == allIssues[i].IsClosed { - showIssues = append(showIssues, allIssues[i]) + repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues + issueStats.AllCount += int64(repo.NumOpenIssues) + + // switch filterMode{ + // case models.FM_ASSIGN: + + // } + + if isShowClosed { + if repo.NumClosedIssues > 0 { + showRepos = append(showRepos, repo) + } + } else { + if repo.NumOpenIssues > 0 { + showRepos = append(showRepos, repo) + } } + + // issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, "", "") + // if err != nil { + // ctx.Handle(200, "user.Issues(get issues)", err) + // return + // } } + // allIssueCount += repo.NumIssues + // closedIssueCount += repo.NumClosedIssues + + // // Set repository information to issues. + // for j := range issues { + // issues[j].Repo = &repos[i] + // } + // allIssues = append(allIssues, issues...) + + // repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues + // if repos[i].NumOpenIssues > 0 { + // showRepos = append(showRepos, repos[i]) + // } + // } + + // showIssues := make([]models.Issue, 0, len(allIssues)) + // ctx.Data["IsShowClosed"] = isShowClosed + + // // Get posters and filter issues. + // for i := range allIssues { + // u, err := models.GetUserById(allIssues[i].PosterId) + // if err != nil { + // ctx.Handle(200, "user.Issues(get poster): %v", err) + // return + // } + // allIssues[i].Poster = u + // if u.Id == ctx.User.Id { + // createdByCount++ + // } + + // if repoId > 0 && repoId != allIssues[i].Repo.Id { + // continue + // } + + // if isShowClosed == allIssues[i].IsClosed { + // showIssues = append(showIssues, allIssues[i]) + // } + // } + + ctx.Data["RepoId"] = rid ctx.Data["Repos"] = showRepos - ctx.Data["Issues"] = showIssues - ctx.Data["AllIssueCount"] = allIssueCount - ctx.Data["ClosedIssueCount"] = closedIssueCount - ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount - ctx.Data["CreatedByCount"] = createdByCount + ctx.Data["ViewType"] = viewType + ctx.Data["IssueStats"] = issueStats + ctx.Data["IsShowClosed"] = isShowClosed + if isShowClosed { + ctx.Data["State"] = "closed" + ctx.Data["ShowCount"] = issueStats.ClosedCount + } else { + ctx.Data["ShowCount"] = issueStats.OpenCount + } ctx.HTML(200, "issue/user") } diff --git a/templates/admin/repos.tmpl b/templates/admin/repos.tmpl index 3c0f5e09f7..2c5e3d3b7d 100644 --- a/templates/admin/repos.tmpl +++ b/templates/admin/repos.tmpl @@ -17,6 +17,7 @@ Name Private Watches + Issues Forks Created @@ -29,6 +30,7 @@ {{.Name}} {{.NumWatches}} + {{.NumIssues}} {{.NumForks}} {{DateFormat .Created "M d, Y"}} diff --git a/templates/issue/list.tmpl b/templates/issue/list.tmpl index de25b0e37f..0b995f810c 100644 --- a/templates/issue/list.tmpl +++ b/templates/issue/list.tmpl @@ -6,21 +6,21 @@
- {{range .Issues}} + {{range .Issues}}{{if .Poster}}
#{{.Index}}
{{.Name}}
@@ -31,7 +31,7 @@ {{.NumComments}}

- {{end}} + {{end}}{{end}}
diff --git a/templates/issue/user.tmpl b/templates/issue/user.tmpl index 812d9d8e24..e1711d4476 100644 --- a/templates/issue/user.tmpl +++ b/templates/issue/user.tmpl @@ -16,9 +16,9 @@