mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-09 11:21:19 +01:00
9302eba971
* DBContext is just a Context This PR removes some of the specialness from the DBContext and makes it context This allows us to simplify the GetEngine code to wrap around any context in future and means that we can change our loadRepo(e Engine) functions to simply take contexts. Signed-off-by: Andrew Thornton <art27@cantab.net> * fix unit tests Signed-off-by: Andrew Thornton <art27@cantab.net> * another place that needs to set the initial context Signed-off-by: Andrew Thornton <art27@cantab.net> * avoid race Signed-off-by: Andrew Thornton <art27@cantab.net> * change attachment error Signed-off-by: Andrew Thornton <art27@cantab.net>
413 lines
12 KiB
Go
413 lines
12 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// ActionType represents the type of an action.
|
|
type ActionType int
|
|
|
|
// Possible action types.
|
|
const (
|
|
ActionCreateRepo ActionType = iota + 1 // 1
|
|
ActionRenameRepo // 2
|
|
ActionStarRepo // 3
|
|
ActionWatchRepo // 4
|
|
ActionCommitRepo // 5
|
|
ActionCreateIssue // 6
|
|
ActionCreatePullRequest // 7
|
|
ActionTransferRepo // 8
|
|
ActionPushTag // 9
|
|
ActionCommentIssue // 10
|
|
ActionMergePullRequest // 11
|
|
ActionCloseIssue // 12
|
|
ActionReopenIssue // 13
|
|
ActionClosePullRequest // 14
|
|
ActionReopenPullRequest // 15
|
|
ActionDeleteTag // 16
|
|
ActionDeleteBranch // 17
|
|
ActionMirrorSyncPush // 18
|
|
ActionMirrorSyncCreate // 19
|
|
ActionMirrorSyncDelete // 20
|
|
ActionApprovePullRequest // 21
|
|
ActionRejectPullRequest // 22
|
|
ActionCommentPull // 23
|
|
ActionPublishRelease // 24
|
|
ActionPullReviewDismissed // 25
|
|
ActionPullRequestReadyForReview // 26
|
|
)
|
|
|
|
// Action represents user operation type and other information to
|
|
// repository. It implemented interface base.Actioner so that can be
|
|
// used in template render.
|
|
type Action struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
UserID int64 `xorm:"INDEX"` // Receiver user id.
|
|
OpType ActionType
|
|
ActUserID int64 `xorm:"INDEX"` // Action user id.
|
|
ActUser *User `xorm:"-"`
|
|
RepoID int64 `xorm:"INDEX"`
|
|
Repo *Repository `xorm:"-"`
|
|
CommentID int64 `xorm:"INDEX"`
|
|
Comment *Comment `xorm:"-"`
|
|
IsDeleted bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
|
RefName string
|
|
IsPrivate bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
|
Content string `xorm:"TEXT"`
|
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(Action))
|
|
}
|
|
|
|
// GetOpType gets the ActionType of this action.
|
|
func (a *Action) GetOpType() ActionType {
|
|
return a.OpType
|
|
}
|
|
|
|
// LoadActUser loads a.ActUser
|
|
func (a *Action) LoadActUser() {
|
|
if a.ActUser != nil {
|
|
return
|
|
}
|
|
var err error
|
|
a.ActUser, err = GetUserByID(a.ActUserID)
|
|
if err == nil {
|
|
return
|
|
} else if IsErrUserNotExist(err) {
|
|
a.ActUser = NewGhostUser()
|
|
} else {
|
|
log.Error("GetUserByID(%d): %v", a.ActUserID, err)
|
|
}
|
|
}
|
|
|
|
func (a *Action) loadRepo() {
|
|
if a.Repo != nil {
|
|
return
|
|
}
|
|
var err error
|
|
a.Repo, err = GetRepositoryByID(a.RepoID)
|
|
if err != nil {
|
|
log.Error("GetRepositoryByID(%d): %v", a.RepoID, err)
|
|
}
|
|
}
|
|
|
|
// GetActFullName gets the action's user full name.
|
|
func (a *Action) GetActFullName() string {
|
|
a.LoadActUser()
|
|
return a.ActUser.FullName
|
|
}
|
|
|
|
// GetActUserName gets the action's user name.
|
|
func (a *Action) GetActUserName() string {
|
|
a.LoadActUser()
|
|
return a.ActUser.Name
|
|
}
|
|
|
|
// ShortActUserName gets the action's user name trimmed to max 20
|
|
// chars.
|
|
func (a *Action) ShortActUserName() string {
|
|
return base.EllipsisString(a.GetActUserName(), 20)
|
|
}
|
|
|
|
// GetDisplayName gets the action's display name based on DEFAULT_SHOW_FULL_NAME, or falls back to the username if it is blank.
|
|
func (a *Action) GetDisplayName() string {
|
|
if setting.UI.DefaultShowFullName {
|
|
trimmedFullName := strings.TrimSpace(a.GetActFullName())
|
|
if len(trimmedFullName) > 0 {
|
|
return trimmedFullName
|
|
}
|
|
}
|
|
return a.ShortActUserName()
|
|
}
|
|
|
|
// GetDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
|
|
func (a *Action) GetDisplayNameTitle() string {
|
|
if setting.UI.DefaultShowFullName {
|
|
return a.ShortActUserName()
|
|
}
|
|
return a.GetActFullName()
|
|
}
|
|
|
|
// GetRepoUserName returns the name of the action repository owner.
|
|
func (a *Action) GetRepoUserName() string {
|
|
a.loadRepo()
|
|
return a.Repo.OwnerName
|
|
}
|
|
|
|
// ShortRepoUserName returns the name of the action repository owner
|
|
// trimmed to max 20 chars.
|
|
func (a *Action) ShortRepoUserName() string {
|
|
return base.EllipsisString(a.GetRepoUserName(), 20)
|
|
}
|
|
|
|
// GetRepoName returns the name of the action repository.
|
|
func (a *Action) GetRepoName() string {
|
|
a.loadRepo()
|
|
return a.Repo.Name
|
|
}
|
|
|
|
// ShortRepoName returns the name of the action repository
|
|
// trimmed to max 33 chars.
|
|
func (a *Action) ShortRepoName() string {
|
|
return base.EllipsisString(a.GetRepoName(), 33)
|
|
}
|
|
|
|
// GetRepoPath returns the virtual path to the action repository.
|
|
func (a *Action) GetRepoPath() string {
|
|
return path.Join(a.GetRepoUserName(), a.GetRepoName())
|
|
}
|
|
|
|
// ShortRepoPath returns the virtual path to the action repository
|
|
// trimmed to max 20 + 1 + 33 chars.
|
|
func (a *Action) ShortRepoPath() string {
|
|
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
|
|
}
|
|
|
|
// GetRepoLink returns relative link to action repository.
|
|
func (a *Action) GetRepoLink() string {
|
|
if len(setting.AppSubURL) > 0 {
|
|
return path.Join(setting.AppSubURL, a.GetRepoPath())
|
|
}
|
|
return "/" + a.GetRepoPath()
|
|
}
|
|
|
|
// GetRepositoryFromMatch returns a *Repository from a username and repo strings
|
|
func GetRepositoryFromMatch(ownerName, repoName string) (*Repository, error) {
|
|
var err error
|
|
refRepo, err := GetRepositoryByOwnerAndName(ownerName, repoName)
|
|
if err != nil {
|
|
if IsErrRepoNotExist(err) {
|
|
log.Warn("Repository referenced in commit but does not exist: %v", err)
|
|
return nil, err
|
|
}
|
|
log.Error("GetRepositoryByOwnerAndName: %v", err)
|
|
return nil, err
|
|
}
|
|
return refRepo, nil
|
|
}
|
|
|
|
// GetCommentLink returns link to action comment.
|
|
func (a *Action) GetCommentLink() string {
|
|
return a.getCommentLink(db.GetEngine(db.DefaultContext))
|
|
}
|
|
|
|
func (a *Action) getCommentLink(e db.Engine) string {
|
|
if a == nil {
|
|
return "#"
|
|
}
|
|
if a.Comment == nil && a.CommentID != 0 {
|
|
a.Comment, _ = getCommentByID(e, a.CommentID)
|
|
}
|
|
if a.Comment != nil {
|
|
return a.Comment.HTMLURL()
|
|
}
|
|
if len(a.GetIssueInfos()) == 0 {
|
|
return "#"
|
|
}
|
|
// Return link to issue
|
|
issueIDString := a.GetIssueInfos()[0]
|
|
issueID, err := strconv.ParseInt(issueIDString, 10, 64)
|
|
if err != nil {
|
|
return "#"
|
|
}
|
|
|
|
issue, err := getIssueByID(e, issueID)
|
|
if err != nil {
|
|
return "#"
|
|
}
|
|
|
|
if err = issue.loadRepo(e); err != nil {
|
|
return "#"
|
|
}
|
|
|
|
return issue.HTMLURL()
|
|
}
|
|
|
|
// GetBranch returns the action's repository branch.
|
|
func (a *Action) GetBranch() string {
|
|
return strings.TrimPrefix(a.RefName, git.BranchPrefix)
|
|
}
|
|
|
|
// GetTag returns the action's repository tag.
|
|
func (a *Action) GetTag() string {
|
|
return strings.TrimPrefix(a.RefName, git.TagPrefix)
|
|
}
|
|
|
|
// GetContent returns the action's content.
|
|
func (a *Action) GetContent() string {
|
|
return a.Content
|
|
}
|
|
|
|
// GetCreate returns the action creation time.
|
|
func (a *Action) GetCreate() time.Time {
|
|
return a.CreatedUnix.AsTime()
|
|
}
|
|
|
|
// GetIssueInfos returns a list of issues associated with
|
|
// the action.
|
|
func (a *Action) GetIssueInfos() []string {
|
|
return strings.SplitN(a.Content, "|", 3)
|
|
}
|
|
|
|
// GetIssueTitle returns the title of first issue associated
|
|
// with the action.
|
|
func (a *Action) GetIssueTitle() string {
|
|
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
|
issue, err := GetIssueByIndex(a.RepoID, index)
|
|
if err != nil {
|
|
log.Error("GetIssueByIndex: %v", err)
|
|
return "500 when get issue"
|
|
}
|
|
return issue.Title
|
|
}
|
|
|
|
// GetIssueContent returns the content of first issue associated with
|
|
// this action.
|
|
func (a *Action) GetIssueContent() string {
|
|
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
|
issue, err := GetIssueByIndex(a.RepoID, index)
|
|
if err != nil {
|
|
log.Error("GetIssueByIndex: %v", err)
|
|
return "500 when get issue"
|
|
}
|
|
return issue.Content
|
|
}
|
|
|
|
// GetFeedsOptions options for retrieving feeds
|
|
type GetFeedsOptions struct {
|
|
RequestedUser *User // the user we want activity for
|
|
RequestedTeam *Team // the team we want activity for
|
|
Actor *User // the user viewing the activity
|
|
IncludePrivate bool // include private actions
|
|
OnlyPerformedBy bool // only actions performed by requested user
|
|
IncludeDeleted bool // include deleted actions
|
|
Date string // the day we want activity for: YYYY-MM-DD
|
|
}
|
|
|
|
// GetFeeds returns actions according to the provided options
|
|
func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
|
|
if !activityReadable(opts.RequestedUser, opts.Actor) {
|
|
return make([]*Action, 0), nil
|
|
}
|
|
|
|
cond, err := activityQueryCondition(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
actions := make([]*Action, 0, setting.UI.FeedPagingNum)
|
|
|
|
if err := db.GetEngine(db.DefaultContext).Limit(setting.UI.FeedPagingNum).Desc("created_unix").Where(cond).Find(&actions); err != nil {
|
|
return nil, fmt.Errorf("Find: %v", err)
|
|
}
|
|
|
|
if err := ActionList(actions).LoadAttributes(); err != nil {
|
|
return nil, fmt.Errorf("LoadAttributes: %v", err)
|
|
}
|
|
|
|
return actions, nil
|
|
}
|
|
|
|
func activityReadable(user, doer *User) bool {
|
|
var doerID int64
|
|
if doer != nil {
|
|
doerID = doer.ID
|
|
}
|
|
if doer == nil || !doer.IsAdmin {
|
|
if user.KeepActivityPrivate && doerID != user.ID {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func activityQueryCondition(opts GetFeedsOptions) (builder.Cond, error) {
|
|
cond := builder.NewCond()
|
|
|
|
var repoIDs []int64
|
|
var actorID int64
|
|
if opts.Actor != nil {
|
|
actorID = opts.Actor.ID
|
|
}
|
|
|
|
// check readable repositories by doer/actor
|
|
if opts.Actor == nil || !opts.Actor.IsAdmin {
|
|
if opts.RequestedUser.IsOrganization() {
|
|
env, err := opts.RequestedUser.AccessibleReposEnv(actorID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
|
|
}
|
|
if repoIDs, err = env.RepoIDs(1, opts.RequestedUser.NumRepos); err != nil {
|
|
return nil, fmt.Errorf("GetUserRepositories: %v", err)
|
|
}
|
|
cond = cond.And(builder.In("repo_id", repoIDs))
|
|
} else {
|
|
cond = cond.And(builder.In("repo_id", AccessibleRepoIDsQuery(opts.Actor)))
|
|
}
|
|
}
|
|
|
|
if opts.RequestedTeam != nil {
|
|
env := opts.RequestedUser.AccessibleTeamReposEnv(opts.RequestedTeam)
|
|
teamRepoIDs, err := env.RepoIDs(1, opts.RequestedUser.NumRepos)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetTeamRepositories: %v", err)
|
|
}
|
|
cond = cond.And(builder.In("repo_id", teamRepoIDs))
|
|
}
|
|
|
|
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
|
|
|
|
if opts.OnlyPerformedBy {
|
|
cond = cond.And(builder.Eq{"act_user_id": opts.RequestedUser.ID})
|
|
}
|
|
if !opts.IncludePrivate {
|
|
cond = cond.And(builder.Eq{"is_private": false})
|
|
}
|
|
if !opts.IncludeDeleted {
|
|
cond = cond.And(builder.Eq{"is_deleted": false})
|
|
}
|
|
|
|
if opts.Date != "" {
|
|
dateLow, err := time.ParseInLocation("2006-01-02", opts.Date, setting.DefaultUILocation)
|
|
if err != nil {
|
|
log.Warn("Unable to parse %s, filter not applied: %v", opts.Date, err)
|
|
} else {
|
|
dateHigh := dateLow.Add(86399000000000) // 23h59m59s
|
|
|
|
cond = cond.And(builder.Gte{"created_unix": dateLow.Unix()})
|
|
cond = cond.And(builder.Lte{"created_unix": dateHigh.Unix()})
|
|
}
|
|
}
|
|
|
|
return cond, nil
|
|
}
|
|
|
|
// DeleteOldActions deletes all old actions from database.
|
|
func DeleteOldActions(olderThan time.Duration) (err error) {
|
|
if olderThan <= 0 {
|
|
return nil
|
|
}
|
|
|
|
_, err = db.GetEngine(db.DefaultContext).Where("created_unix < ?", time.Now().Add(-olderThan).Unix()).Delete(&Action{})
|
|
return
|
|
}
|