githaven/models/action.go
zeripath 9302eba971
DBContext is just a Context (#17100)
* 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>
2021-09-23 23:45:36 +08:00

414 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
}