Add notify watcher action
This commit is contained in:
		
							parent
							
								
									c5ff58272b
								
							
						
					
					
						commit
						d3b8e9daa1
					
				
					 8 changed files with 119 additions and 95 deletions
				
			
		|  | @ -32,7 +32,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o | |||
| - Create/delete/watch public repository. | ||||
| - User profile page. | ||||
| - Repository viewer. | ||||
| - Gravatar support. | ||||
| - Gravatar and cache support. | ||||
| - Mail service(register). | ||||
| - Administration panel. | ||||
| - Supports MySQL, PostgreSQL and SQLite3(binary release only). | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依 | |||
| - 创建/删除/关注公开仓库 | ||||
| - 用户个人信息页面 | ||||
| - 仓库浏览器 | ||||
| - Gravatar 支持 | ||||
| - Gravatar 以及缓存支持 | ||||
| - 邮件服务(注册) | ||||
| - 管理员面板 | ||||
| - 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本) | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ const ( | |||
| 	OP_STAR_REPO | ||||
| 	OP_FOLLOW_REPO | ||||
| 	OP_COMMIT_REPO | ||||
| 	OP_CREATE_ISSUE | ||||
| 	OP_PULL_REQUEST | ||||
| ) | ||||
| 
 | ||||
|  | @ -67,34 +68,10 @@ func CommitRepoAction(userId int64, userName string, | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Add feeds for user self and all watchers.
 | ||||
| 	watches, err := GetWatches(repoId) | ||||
| 	if err != nil { | ||||
| 		log.Error("action.CommitRepoAction(get watches): %d/%s", userId, repoName) | ||||
| 	if err = NotifyWatchers(userId, repoId, OP_COMMIT_REPO, userName, repoName, refName, string(bs)); err != nil { | ||||
| 		log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName) | ||||
| 		return err | ||||
| 	} | ||||
| 	watches = append(watches, Watch{UserId: userId}) | ||||
| 
 | ||||
| 	for i := range watches { | ||||
| 		if userId == watches[i].UserId && i > 0 { | ||||
| 			continue // Do not add twice in case author watches his/her repository.
 | ||||
| 		} | ||||
| 
 | ||||
| 		_, err = orm.InsertOne(&Action{ | ||||
| 			UserId:      watches[i].UserId, | ||||
| 			ActUserId:   userId, | ||||
| 			ActUserName: userName, | ||||
| 			OpType:      OP_COMMIT_REPO, | ||||
| 			Content:     string(bs), | ||||
| 			RepoId:      repoId, | ||||
| 			RepoName:    repoName, | ||||
| 			RefName:     refName, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			log.Error("action.CommitRepoAction(notify watches): %d/%s", userId, repoName) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Update repository last update time.
 | ||||
| 	repo, err := GetRepositoryByName(userId, repoName) | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ type Issue struct { | |||
| 	Name        string | ||||
| 	RepoId      int64 `xorm:"index"` | ||||
| 	PosterId    int64 | ||||
| 	Poster      *User `xorm:"-"` | ||||
| 	MilestoneId int64 | ||||
| 	AssigneeId  int64 | ||||
| 	IsPull      bool // Indicates whether is a pull request or not.
 | ||||
|  |  | |||
|  | @ -262,27 +262,27 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep | |||
| 	} | ||||
| 
 | ||||
| 	/* | ||||
| 	// hook/post-update
 | ||||
| 	pu, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-update"), os.O_CREATE|os.O_WRONLY, 0777) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer pu.Close() | ||||
| 	// TODO: Windows .bat
 | ||||
| 	if _, err = pu.WriteString(fmt.Sprintf("#!/usr/bin/env bash\n%s update\n", appPath)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 		// hook/post-update
 | ||||
| 		pu, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-update"), os.O_CREATE|os.O_WRONLY, 0777) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer pu.Close() | ||||
| 		// TODO: Windows .bat
 | ||||
| 		if _, err = pu.WriteString(fmt.Sprintf("#!/usr/bin/env bash\n%s update\n", appPath)); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 	// hook/post-update
 | ||||
| 	pu2, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-receive"), os.O_CREATE|os.O_WRONLY, 0777) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer pu2.Close() | ||||
| 	// TODO: Windows .bat
 | ||||
| 	if _, err = pu2.WriteString("#!/usr/bin/env bash\ngit update-server-info\n"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 		// hook/post-update
 | ||||
| 		pu2, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-receive"), os.O_CREATE|os.O_WRONLY, 0777) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		defer pu2.Close() | ||||
| 		// TODO: Windows .bat
 | ||||
| 		if _, err = pu2.WriteString("#!/usr/bin/env bash\ngit update-server-info\n"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	*/ | ||||
| 
 | ||||
| 	// Initialize repository according to user's choice.
 | ||||
|  | @ -506,6 +506,37 @@ func GetWatches(repoId int64) ([]Watch, error) { | |||
| 	return watches, err | ||||
| } | ||||
| 
 | ||||
| // NotifyWatchers creates batch of actions for every watcher.
 | ||||
| func NotifyWatchers(userId, repoId int64, opType int, userName, repoName, refName, content string) error { | ||||
| 	// Add feeds for user self and all watchers.
 | ||||
| 	watches, err := GetWatches(repoId) | ||||
| 	if err != nil { | ||||
| 		return errors.New("repo.NotifyWatchers(get watches): " + err.Error()) | ||||
| 	} | ||||
| 	watches = append(watches, Watch{UserId: userId}) | ||||
| 
 | ||||
| 	for i := range watches { | ||||
| 		if userId == watches[i].UserId && i > 0 { | ||||
| 			continue // Do not add twice in case author watches his/her repository.
 | ||||
| 		} | ||||
| 
 | ||||
| 		_, err = orm.InsertOne(&Action{ | ||||
| 			UserId:      watches[i].UserId, | ||||
| 			ActUserId:   userId, | ||||
| 			ActUserName: userName, | ||||
| 			OpType:      opType, | ||||
| 			Content:     content, | ||||
| 			RepoId:      repoId, | ||||
| 			RepoName:    repoName, | ||||
| 			RefName:     refName, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return errors.New("repo.NotifyWatchers(create action): " + err.Error()) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // IsWatching checks if user has watched given repository.
 | ||||
| func IsWatching(userId, repoId int64) bool { | ||||
| 	has, _ := orm.Get(&Watch{0, repoId, userId}) | ||||
|  |  | |||
|  | @ -486,15 +486,19 @@ func ActionIcon(opType int) string { | |||
| 		return "plus-circle" | ||||
| 	case 5: // Commit repository.
 | ||||
| 		return "arrow-circle-o-right" | ||||
| 	case 6: // Create issue.
 | ||||
| 		return "exclamation-circle" | ||||
| 	default: | ||||
| 		return "invalid type" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	TPL_CREATE_REPO    = `<a href="/user/%s">%s</a> created repository <a href="/%s/%s">%s</a>` | ||||
| 	TPL_COMMIT_REPO    = `<a href="/user/%s">%s</a> pushed to <a href="/%s/%s/tree/%s">%s</a> at <a href="/%s/%s">%s/%s</a>%s` | ||||
| 	TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>` | ||||
| 	TPL_CREATE_REPO    = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>` | ||||
| 	TPL_COMMIT_REPO    = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s` | ||||
| 	TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>` | ||||
| 	TPL_CREATE_Issue   = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a> | ||||
| <div><img src="%s?s=16" alt="user-avatar"/> %s</div>` | ||||
| ) | ||||
| 
 | ||||
| type PushCommits struct { | ||||
|  | @ -507,11 +511,12 @@ type PushCommits struct { | |||
| func ActionDesc(act Actioner, avatarLink string) string { | ||||
| 	actUserName := act.GetActUserName() | ||||
| 	repoName := act.GetRepoName() | ||||
| 	repoLink := actUserName + "/" + repoName | ||||
| 	branch := act.GetBranch() | ||||
| 	content := act.GetContent() | ||||
| 	switch act.GetOpType() { | ||||
| 	case 1: // Create repository.
 | ||||
| 		return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName) | ||||
| 		return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName) | ||||
| 	case 5: // Commit repository.
 | ||||
| 		var push *PushCommits | ||||
| 		if err := json.Unmarshal([]byte(content), &push); err != nil { | ||||
|  | @ -519,13 +524,17 @@ func ActionDesc(act Actioner, avatarLink string) string { | |||
| 		} | ||||
| 		buf := bytes.NewBuffer([]byte("\n")) | ||||
| 		for _, commit := range push.Commits { | ||||
| 			buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n") | ||||
| 			buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, repoLink, commit[0], commit[0][:7], commit[1]) + "\n") | ||||
| 		} | ||||
| 		if push.Len > 3 { | ||||
| 			buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len)) | ||||
| 		} | ||||
| 		return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName, | ||||
| 		return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink, | ||||
| 			buf.String()) | ||||
| 	case 6: // Create issue.
 | ||||
| 		infos := strings.SplitN(content, "|", 2) | ||||
| 		return fmt.Sprintf(TPL_CREATE_Issue, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0], | ||||
| 			avatarLink, infos[1]) | ||||
| 	default: | ||||
| 		return "invalid type" | ||||
| 	} | ||||
|  |  | |||
|  | @ -23,13 +23,33 @@ func Issues(ctx *middleware.Context, params martini.Params) { | |||
| 	milestoneId, _ := base.StrTo(params["milestone"]).Int() | ||||
| 	page, _ := base.StrTo(params["page"]).Int() | ||||
| 
 | ||||
| 	var err error | ||||
| 	ctx.Data["Issues"], err = models.GetIssues(0, ctx.Repo.Repository.Id, 0, | ||||
| 	// Get issues.
 | ||||
| 	issues, err := models.GetIssues(0, ctx.Repo.Repository.Id, 0, | ||||
| 		int64(milestoneId), page, params["state"] == "closed", false, params["labels"], params["sortType"]) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(200, "issue.Issues: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	var closedCount int | ||||
| 	// Get posters.
 | ||||
| 	for i := range issues { | ||||
| 		u, err := models.GetUserById(issues[i].PosterId) | ||||
| 		if err != nil { | ||||
| 			ctx.Handle(200, "issue.Issues(get poster): %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		if issues[i].IsClosed { | ||||
| 			closedCount++ | ||||
| 		} | ||||
| 		issues[i].Poster = u | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Data["Issues"] = issues | ||||
| 	ctx.Data["IssueCount"] = len(issues) | ||||
| 	ctx.Data["OpenCount"] = len(issues) - closedCount | ||||
| 	ctx.Data["ClosedCount"] = closedCount | ||||
| 	ctx.HTML(200, "issue/list") | ||||
| } | ||||
| 
 | ||||
|  | @ -54,12 +74,20 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat | |||
| 
 | ||||
| 	issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId, | ||||
| 		form.IssueName, form.Labels, form.Content, false) | ||||
| 	if err == nil { | ||||
| 		log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id) | ||||
| 		ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index)) | ||||
| 	if err != nil { | ||||
| 		ctx.Handle(200, "issue.CreateIssue", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Handle(200, "issue.CreateIssue", err) | ||||
| 
 | ||||
| 	// Notify watchers.
 | ||||
| 	if err = models.NotifyWatchers(ctx.User.Id, ctx.Repo.Repository.Id, models.OP_CREATE_ISSUE, | ||||
| 		ctx.User.Name, ctx.Repo.Repository.Name, "", fmt.Sprintf("%d|%s", issue.Index, issue.Name)); err != nil { | ||||
| 		ctx.Handle(200, "issue.CreateIssue", err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id) | ||||
| 	ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index)) | ||||
| } | ||||
| 
 | ||||
| func ViewIssue(ctx *middleware.Context, params martini.Params) { | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ | |||
|     <div id="issue"> | ||||
|         <div class="col-md-3 filter-list"> | ||||
|             <ul class="list-unstyled"> | ||||
|                 <li><a href="#" class="active">All Issues <strong class="pull-right">10</strong></a></li> | ||||
|                 <li><a href="#" class="active">All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li> | ||||
|                 <li><a href="#">My Issues</a></li> | ||||
|                 <li><a href="#">Mentioned</a></li> | ||||
|             </ul> | ||||
|  | @ -14,46 +14,24 @@ | |||
|         <div class="col-md-9"> | ||||
|             <div class="filter-option"> | ||||
|                 <div class="btn-group"> | ||||
|                     <a class="btn btn-default active issue-open" href="#">27 Open</a> | ||||
|                     <a class="btn btn-default issue-close" href="#">Close 128</a> | ||||
|                     <a class="btn btn-default active issue-open" href="#">{{.OpenCount}} Open</a> | ||||
|                     <a class="btn btn-default issue-close" href="#">{{.ClosedCount}} Closed</a> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="issues list-group"> | ||||
|                 {{range .Issues}} | ||||
|                 <div class="list-group-item issue-item" id="{{.Id}}"></div> | ||||
|                 <div class="list-group-item issue-item" id="{{.Id}}"> | ||||
|                     <span class="number pull-right">#{{.Index}}</span> | ||||
|                     <h5 class="title"><a href="/{{$.RepositoryLink}}/issues/{{.Index}}">{{.Name}}</a></h5> | ||||
|                     <p class="info"> | ||||
|                         <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/> | ||||
|                         <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span> | ||||
|                         <span class="time">{{TimeSince .Created}}</span> | ||||
|                         <span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span> | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 {{end}} | ||||
|             </div> | ||||
|             <div class="issues list-group"> | ||||
|                 <div class="list-group-item unread issue-item" id="issue-id"> | ||||
|                     <span class="number pull-right">#123</span> | ||||
|                     <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5> | ||||
|                     <p class="info"> | ||||
|                         <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/> | ||||
|                         <a href="#">Obama</a></span> | ||||
|                         <span class="time">3 days ago</span> | ||||
|                         <span class="comment"><i class="fa fa-comments"></i> 3</span> | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 <div class="list-group-item issue-item" id="issue-id2"> | ||||
|                     <span class="number pull-right">#123</span> | ||||
|                     <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5> | ||||
|                     <p class="info"> | ||||
|                         <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/> | ||||
|                         <a href="#">Obama</a></span> | ||||
|                         <span class="time">3 days ago</span> | ||||
|                         <span class="comment"><i class="fa fa-comments"></i> 3</span> | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 <div class="list-group-item issue-item" id="issue-id3"> | ||||
|                     <span class="number pull-right">#123</span> | ||||
|                     <h5 class="title"><a href="#">Bug: When running tests after generating a beego app, templates do not load.</a></h5> | ||||
|                     <p class="info"> | ||||
|                         <span class="author"><img class="avatar" src="http://tp2.sinaimg.cn/5068084885/50/40050297589/1" alt="" width="20"/> | ||||
|                         <a href="#">Obama</a></span> | ||||
|                         <span class="time">3 days ago</span> | ||||
|                         <span class="comment"><i class="fa fa-comments"></i> 3</span> | ||||
|                     </p> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue