2014-03-13 09:16:14 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-05-01 19:21:05 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2014-03-13 09:16:14 +04:00
2022-08-25 05:31:57 +03:00
package activities
2014-03-13 09:16:14 +04:00
import (
2021-12-10 04:27:50 +03:00
"context"
2014-05-06 19:50:31 +04:00
"fmt"
2021-11-16 21:18:25 +03:00
"net/url"
2014-07-26 08:24:27 +04:00
"path"
2024-01-15 02:22:06 +03:00
"slices"
2017-06-25 21:20:29 +03:00
"strconv"
2014-04-14 06:20:28 +04:00
"strings"
2014-03-13 09:16:14 +04:00
"time"
2014-03-22 14:20:00 +04:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
2022-05-11 13:09:36 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-12-12 18:48:20 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
2024-01-15 02:22:06 +03:00
"code.gitea.io/gitea/modules/container"
2020-12-14 20:08:37 +03:00
"code.gitea.io/gitea/modules/git"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2022-03-10 17:54:51 +03:00
"code.gitea.io/gitea/modules/structs"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2017-12-03 05:20:12 +03:00
2019-06-23 18:22:43 +03:00
"xorm.io/builder"
2022-06-18 11:46:50 +03:00
"xorm.io/xorm/schemas"
2014-03-13 09:16:14 +04:00
)
2016-11-22 13:43:30 +03:00
// ActionType represents the type of an action.
2014-07-26 08:24:27 +04:00
type ActionType int
2016-11-22 13:43:30 +03:00
// Possible action types.
2014-03-13 09:16:14 +04:00
const (
2021-06-23 07:14:22 +03:00
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
2022-11-03 18:49:00 +03:00
ActionAutoMergePullRequest // 27
2014-03-13 09:16:14 +04:00
)
2023-04-04 16:35:31 +03:00
func ( at ActionType ) String ( ) string {
switch at {
case ActionCreateRepo :
return "create_repo"
case ActionRenameRepo :
return "rename_repo"
case ActionStarRepo :
return "star_repo"
case ActionWatchRepo :
return "watch_repo"
case ActionCommitRepo :
return "commit_repo"
case ActionCreateIssue :
return "create_issue"
case ActionCreatePullRequest :
return "create_pull_request"
case ActionTransferRepo :
return "transfer_repo"
case ActionPushTag :
return "push_tag"
case ActionCommentIssue :
return "comment_issue"
case ActionMergePullRequest :
return "merge_pull_request"
case ActionCloseIssue :
return "close_issue"
case ActionReopenIssue :
return "reopen_issue"
case ActionClosePullRequest :
return "close_pull_request"
case ActionReopenPullRequest :
return "reopen_pull_request"
case ActionDeleteTag :
return "delete_tag"
case ActionDeleteBranch :
return "delete_branch"
case ActionMirrorSyncPush :
return "mirror_sync_push"
case ActionMirrorSyncCreate :
return "mirror_sync_create"
case ActionMirrorSyncDelete :
return "mirror_sync_delete"
case ActionApprovePullRequest :
return "approve_pull_request"
case ActionRejectPullRequest :
return "reject_pull_request"
case ActionCommentPull :
return "comment_pull"
case ActionPublishRelease :
return "publish_release"
case ActionPullReviewDismissed :
return "pull_review_dismissed"
case ActionPullRequestReadyForReview :
return "pull_request_ready_for_review"
case ActionAutoMergePullRequest :
return "auto_merge_pull_request"
default :
return "action-" + strconv . Itoa ( int ( at ) )
}
}
2023-09-07 17:23:13 +03:00
func ( at ActionType ) InActions ( actions ... string ) bool {
for _ , action := range actions {
if action == at . String ( ) {
return true
}
}
return false
}
2016-11-22 13:43:30 +03:00
// Action represents user operation type and other information to
// repository. It implemented interface base.Actioner so that can be
// used in template render.
2014-03-13 09:16:14 +04:00
type Action struct {
2017-05-26 04:38:18 +03:00
ID int64 ` xorm:"pk autoincr" `
2023-10-04 04:41:25 +03:00
UserID int64 ` xorm:"INDEX" ` // Receiver user id.
2017-05-26 04:38:18 +03:00
OpType ActionType
2022-06-18 11:46:50 +03:00
ActUserID int64 // Action user id.
ActUser * user_model . User ` xorm:"-" `
RepoID int64
2021-12-10 04:27:50 +03:00
Repo * repo_model . Repository ` xorm:"-" `
CommentID int64 ` xorm:"INDEX" `
2022-06-13 12:37:59 +03:00
Comment * issues_model . Comment ` xorm:"-" `
2022-06-18 11:46:50 +03:00
IsDeleted bool ` xorm:"NOT NULL DEFAULT false" `
2017-05-26 04:38:18 +03:00
RefName string
2022-06-18 11:46:50 +03:00
IsPrivate bool ` xorm:"NOT NULL DEFAULT false" `
2019-08-15 17:46:21 +03:00
Content string ` xorm:"TEXT" `
2022-06-18 11:46:50 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2015-08-19 19:12:43 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( Action ) )
}
2022-06-18 11:46:50 +03:00
// TableIndices implements xorm's TableIndices interface
func ( a * Action ) TableIndices ( ) [ ] * schemas . Index {
2022-07-01 19:04:01 +03:00
repoIndex := schemas . NewIndex ( "r_u_d" , schemas . IndexType )
repoIndex . AddColumn ( "repo_id" , "user_id" , "is_deleted" )
2022-06-18 11:46:50 +03:00
actUserIndex := schemas . NewIndex ( "au_r_c_u_d" , schemas . IndexType )
actUserIndex . AddColumn ( "act_user_id" , "repo_id" , "created_unix" , "user_id" , "is_deleted" )
2023-03-24 18:44:33 +03:00
cudIndex := schemas . NewIndex ( "c_u_d" , schemas . IndexType )
cudIndex . AddColumn ( "created_unix" , "user_id" , "is_deleted" )
indices := [ ] * schemas . Index { actUserIndex , repoIndex , cudIndex }
2022-09-03 19:27:59 +03:00
return indices
2022-06-18 11:46:50 +03:00
}
2016-11-22 13:43:30 +03:00
// GetOpType gets the ActionType of this action.
2017-09-20 04:22:42 +03:00
func ( a * Action ) GetOpType ( ) ActionType {
return a . OpType
2014-03-15 08:50:51 +04:00
}
2020-12-09 08:11:15 +03:00
// LoadActUser loads a.ActUser
2022-12-03 05:48:26 +03:00
func ( a * Action ) LoadActUser ( ctx context . Context ) {
2017-05-26 04:38:18 +03:00
if a . ActUser != nil {
return
}
var err error
2022-12-03 05:48:26 +03:00
a . ActUser , err = user_model . GetUserByID ( ctx , a . ActUserID )
2017-05-26 04:38:18 +03:00
if err == nil {
return
2021-11-24 12:49:20 +03:00
} else if user_model . IsErrUserNotExist ( err ) {
a . ActUser = user_model . NewGhostUser ( )
2017-05-26 04:38:18 +03:00
} else {
2019-04-02 10:48:31 +03:00
log . Error ( "GetUserByID(%d): %v" , a . ActUserID , err )
2017-05-26 04:38:18 +03:00
}
}
2022-12-03 05:48:26 +03:00
func ( a * Action ) loadRepo ( ctx context . Context ) {
2017-06-14 03:37:50 +03:00
if a . Repo != nil {
2017-05-26 04:38:18 +03:00
return
}
var err error
2022-12-03 05:48:26 +03:00
a . Repo , err = repo_model . GetRepositoryByID ( ctx , a . RepoID )
2017-05-26 04:38:18 +03:00
if err != nil {
2021-12-10 04:27:50 +03:00
log . Error ( "repo_model.GetRepositoryByID(%d): %v" , a . RepoID , err )
2017-05-26 04:38:18 +03:00
}
}
2018-07-05 20:48:18 +03:00
// GetActFullName gets the action's user full name.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetActFullName ( ctx context . Context ) string {
a . LoadActUser ( ctx )
2018-07-05 20:48:18 +03:00
return a . ActUser . FullName
}
2016-11-22 13:43:30 +03:00
// GetActUserName gets the action's user name.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetActUserName ( ctx context . Context ) string {
a . LoadActUser ( ctx )
2017-05-26 04:38:18 +03:00
return a . ActUser . Name
2014-03-15 08:50:51 +04:00
}
2016-11-22 13:43:30 +03:00
// ShortActUserName gets the action's user name trimmed to max 20
// chars.
2023-09-29 15:12:54 +03:00
func ( a * Action ) ShortActUserName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetActUserName ( ctx ) , 20 )
2016-01-11 15:41:43 +03:00
}
2024-03-02 18:30:18 +03:00
// GetActDisplayName 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 ) GetActDisplayName ( ctx context . Context ) string {
2019-05-08 11:41:35 +03:00
if setting . UI . DefaultShowFullName {
2023-09-29 15:12:54 +03:00
trimmedFullName := strings . TrimSpace ( a . GetActFullName ( ctx ) )
2020-02-27 01:08:24 +03:00
if len ( trimmedFullName ) > 0 {
return trimmedFullName
}
2019-05-08 11:41:35 +03:00
}
2023-09-29 15:12:54 +03:00
return a . ShortActUserName ( ctx )
2019-05-08 11:41:35 +03:00
}
2024-03-02 18:30:18 +03:00
// GetActDisplayNameTitle gets the action's display name used for the title (tooltip) based on DEFAULT_SHOW_FULL_NAME
func ( a * Action ) GetActDisplayNameTitle ( ctx context . Context ) string {
2019-05-08 11:41:35 +03:00
if setting . UI . DefaultShowFullName {
2023-09-29 15:12:54 +03:00
return a . ShortActUserName ( ctx )
2019-05-08 11:41:35 +03:00
}
2023-09-29 15:12:54 +03:00
return a . GetActFullName ( ctx )
2019-05-08 11:41:35 +03:00
}
2016-11-22 13:43:30 +03:00
// GetRepoUserName returns the name of the action repository owner.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRepoUserName ( ctx context . Context ) string {
a . loadRepo ( ctx )
2020-01-12 12:36:21 +03:00
return a . Repo . OwnerName
2014-05-09 10:42:50 +04:00
}
2016-11-22 13:43:30 +03:00
// ShortRepoUserName returns the name of the action repository owner
// trimmed to max 20 chars.
2023-09-29 15:12:54 +03:00
func ( a * Action ) ShortRepoUserName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetRepoUserName ( ctx ) , 20 )
2016-01-11 15:41:43 +03:00
}
2016-11-22 13:43:30 +03:00
// GetRepoName returns the name of the action repository.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRepoName ( ctx context . Context ) string {
a . loadRepo ( ctx )
2017-05-26 04:38:18 +03:00
return a . Repo . Name
2014-03-13 09:16:14 +04:00
}
2016-11-22 13:43:30 +03:00
// ShortRepoName returns the name of the action repository
// trimmed to max 33 chars.
2023-09-29 15:12:54 +03:00
func ( a * Action ) ShortRepoName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetRepoName ( ctx ) , 33 )
2016-01-11 15:41:43 +03:00
}
2016-11-22 13:43:30 +03:00
// GetRepoPath returns the virtual path to the action repository.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRepoPath ( ctx context . Context ) string {
return path . Join ( a . GetRepoUserName ( ctx ) , a . GetRepoName ( ctx ) )
2016-01-15 13:00:39 +03:00
}
2016-11-22 13:43:30 +03:00
// ShortRepoPath returns the virtual path to the action repository
2017-01-05 03:50:34 +03:00
// trimmed to max 20 + 1 + 33 chars.
2023-09-29 15:12:54 +03:00
func ( a * Action ) ShortRepoPath ( ctx context . Context ) string {
return path . Join ( a . ShortRepoUserName ( ctx ) , a . ShortRepoName ( ctx ) )
2015-03-12 23:01:23 +03:00
}
2016-11-22 13:43:30 +03:00
// GetRepoLink returns relative link to action repository.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRepoLink ( ctx context . Context ) string {
2021-11-16 21:18:25 +03:00
// path.Join will skip empty strings
2023-09-29 15:12:54 +03:00
return path . Join ( setting . AppSubURL , "/" , url . PathEscape ( a . GetRepoUserName ( ctx ) ) , url . PathEscape ( a . GetRepoName ( ctx ) ) )
2014-07-26 08:24:27 +04:00
}
2022-09-21 23:51:42 +03:00
// GetRepoAbsoluteLink returns the absolute link to action repository.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRepoAbsoluteLink ( ctx context . Context ) string {
return setting . AppURL + url . PathEscape ( a . GetRepoUserName ( ctx ) ) + "/" + url . PathEscape ( a . GetRepoName ( ctx ) )
2022-09-21 23:51:42 +03:00
}
2023-02-06 21:09:18 +03:00
// GetCommentHTMLURL returns link to action comment.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetCommentHTMLURL ( ctx context . Context ) string {
return a . getCommentHTMLURL ( ctx )
2023-02-06 21:09:18 +03:00
}
func ( a * Action ) loadComment ( ctx context . Context ) ( err error ) {
if a . CommentID == 0 || a . Comment != nil {
return nil
}
a . Comment , err = issues_model . GetCommentByID ( ctx , a . CommentID )
return err
}
func ( a * Action ) getCommentHTMLURL ( ctx context . Context ) string {
if a == nil {
return "#"
}
_ = a . loadComment ( ctx )
if a . Comment != nil {
2023-09-29 15:12:54 +03:00
return a . Comment . HTMLURL ( ctx )
2023-02-06 21:09:18 +03:00
}
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 := issues_model . GetIssueByID ( ctx , issueID )
if err != nil {
return "#"
}
if err = issue . LoadRepo ( ctx ) ; err != nil {
return "#"
}
return issue . HTMLURL ( )
}
2017-06-25 21:20:29 +03:00
// GetCommentLink returns link to action comment.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetCommentLink ( ctx context . Context ) string {
return a . getCommentLink ( ctx )
2018-12-13 18:55:43 +03:00
}
2021-12-10 04:27:50 +03:00
func ( a * Action ) getCommentLink ( ctx context . Context ) string {
2017-06-25 21:20:29 +03:00
if a == nil {
return "#"
}
2023-02-06 21:09:18 +03:00
_ = a . loadComment ( ctx )
2017-06-25 21:20:29 +03:00
if a . Comment != nil {
2023-09-29 15:12:54 +03:00
return a . Comment . Link ( ctx )
2017-06-25 21:20:29 +03:00
}
if len ( a . GetIssueInfos ( ) ) == 0 {
return "#"
}
2021-03-14 21:52:12 +03:00
// Return link to issue
2017-06-25 21:20:29 +03:00
issueIDString := a . GetIssueInfos ( ) [ 0 ]
issueID , err := strconv . ParseInt ( issueIDString , 10 , 64 )
if err != nil {
return "#"
}
2022-06-13 12:37:59 +03:00
issue , err := issues_model . GetIssueByID ( ctx , issueID )
2017-06-25 21:20:29 +03:00
if err != nil {
return "#"
}
2022-04-08 12:11:15 +03:00
if err = issue . LoadRepo ( ctx ) ; err != nil {
2018-12-13 18:55:43 +03:00
return "#"
}
2023-02-06 21:09:18 +03:00
return issue . Link ( )
2017-06-25 21:20:29 +03:00
}
2016-11-22 13:43:30 +03:00
// GetBranch returns the action's repository branch.
2016-01-11 15:41:43 +03:00
func ( a * Action ) GetBranch ( ) string {
2020-12-14 20:08:37 +03:00
return strings . TrimPrefix ( a . RefName , git . BranchPrefix )
2014-03-16 19:30:35 +04:00
}
2021-12-16 22:01:14 +03:00
// GetRefLink returns the action's ref link.
2023-09-29 15:12:54 +03:00
func ( a * Action ) GetRefLink ( ctx context . Context ) string {
return git . RefURL ( a . GetRepoLink ( ctx ) , a . RefName )
2021-12-16 22:01:14 +03:00
}
2020-12-20 02:46:28 +03:00
// GetTag returns the action's repository tag.
func ( a * Action ) GetTag ( ) string {
return strings . TrimPrefix ( a . RefName , git . TagPrefix )
}
2016-11-22 13:43:30 +03:00
// GetContent returns the action's content.
2016-01-11 15:41:43 +03:00
func ( a * Action ) GetContent ( ) string {
2014-03-23 14:27:01 +04:00
return a . Content
2014-03-23 14:00:09 +04:00
}
2016-11-22 13:43:30 +03:00
// GetCreate returns the action creation time.
2016-01-11 15:41:43 +03:00
func ( a * Action ) GetCreate ( ) time . Time {
2017-12-11 07:37:04 +03:00
return a . CreatedUnix . AsTime ( )
2014-07-26 08:24:27 +04:00
}
2024-03-06 16:12:44 +03:00
// GetIssueInfos returns a list of associated information with the action.
2016-01-11 15:41:43 +03:00
func ( a * Action ) GetIssueInfos ( ) [ ] string {
2024-03-06 16:12:44 +03:00
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
ret := strings . SplitN ( a . Content , "|" , 3 )
for len ( ret ) < 3 {
ret = append ( ret , "" )
}
return ret
2014-07-26 08:24:27 +04:00
}
2023-09-29 15:12:54 +03:00
// GetIssueTitle returns the title of first issue associated with the action.
func ( a * Action ) GetIssueTitle ( ctx context . Context ) string {
2020-12-25 12:59:32 +03:00
index , _ := strconv . ParseInt ( a . GetIssueInfos ( ) [ 0 ] , 10 , 64 )
2023-09-29 15:12:54 +03:00
issue , err := issues_model . GetIssueByIndex ( ctx , a . RepoID , index )
2015-11-13 00:16:51 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetIssueByIndex: %v" , err )
2015-11-13 20:11:45 +03:00
return "500 when get issue"
2015-11-13 00:16:51 +03:00
}
2016-08-14 13:32:24 +03:00
return issue . Title
2015-11-12 23:09:48 +03:00
}
2016-11-22 13:43:30 +03:00
// GetIssueContent returns the content of first issue associated with
// this action.
2023-07-22 17:14:27 +03:00
func ( a * Action ) GetIssueContent ( ctx context . Context ) string {
2020-12-25 12:59:32 +03:00
index , _ := strconv . ParseInt ( a . GetIssueInfos ( ) [ 0 ] , 10 , 64 )
2023-07-22 17:14:27 +03:00
issue , err := issues_model . GetIssueByIndex ( ctx , a . RepoID , index )
2015-11-13 20:11:45 +03:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetIssueByIndex: %v" , err )
2015-11-13 20:11:45 +03:00
return "500 when get issue"
}
return issue . Content
}
2017-06-02 03:42:25 +03:00
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
2022-03-10 17:54:51 +03:00
db . ListOptions
RequestedUser * user_model . User // the user we want activity for
2022-03-29 09:29:02 +03:00
RequestedTeam * organization . Team // the team we want activity for
2022-03-10 17:54:51 +03:00
RequestedRepo * repo_model . Repository // the repo we want activity for
Actor * user_model . 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
2017-06-02 03:42:25 +03:00
}
// GetFeeds returns actions according to the provided options
2023-02-25 00:15:10 +03:00
func GetFeeds ( ctx context . Context , opts GetFeedsOptions ) ( ActionList , int64 , error ) {
2022-03-10 17:54:51 +03:00
if opts . RequestedUser == nil && opts . RequestedTeam == nil && opts . RequestedRepo == nil {
2023-02-25 00:15:10 +03:00
return nil , 0 , fmt . Errorf ( "need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo" )
2020-12-22 05:53:37 +03:00
}
2017-08-23 04:30:54 +03:00
2023-09-29 15:12:54 +03:00
cond , err := activityQueryCondition ( ctx , opts )
2020-12-22 05:53:37 +03:00
if err != nil {
2023-02-25 00:15:10 +03:00
return nil , 0 , err
2020-12-22 05:53:37 +03:00
}
2020-01-13 20:33:46 +03:00
2024-02-01 09:25:37 +03:00
sess := db . GetEngine ( ctx ) . Where ( cond ) .
Select ( "`action`.*" ) . // this line will avoid select other joined table's columns
Join ( "INNER" , "repository" , "`repository`.id = `action`.repo_id" )
2020-12-22 05:53:37 +03:00
2022-03-10 17:54:51 +03:00
opts . SetDefaultValues ( )
sess = db . SetSessionPagination ( sess , & opts )
actions := make ( [ ] * Action , 0 , opts . PageSize )
2023-02-25 00:15:10 +03:00
count , err := sess . Desc ( "`action`.created_unix" ) . FindAndCount ( & actions )
if err != nil {
return nil , 0 , fmt . Errorf ( "FindAndCount: %w" , err )
2020-01-13 20:33:46 +03:00
}
2022-05-20 17:08:52 +03:00
if err := ActionList ( actions ) . loadAttributes ( ctx ) ; err != nil {
2023-02-25 00:15:10 +03:00
return nil , 0 , fmt . Errorf ( "LoadAttributes: %w" , err )
2020-12-22 05:53:37 +03:00
}
2023-02-25 00:15:10 +03:00
return actions , count , nil
2020-12-22 05:53:37 +03:00
}
2022-08-25 05:31:57 +03:00
// ActivityReadable return whether doer can read activities of user
func ActivityReadable ( user , doer * user_model . User ) bool {
2022-03-10 17:54:51 +03:00
return ! user . KeepActivityPrivate ||
doer != nil && ( doer . IsAdmin || user . ID == doer . ID )
2020-12-22 05:53:37 +03:00
}
2017-08-23 04:30:54 +03:00
2023-09-29 15:12:54 +03:00
func activityQueryCondition ( ctx context . Context , opts GetFeedsOptions ) ( builder . Cond , error ) {
2020-12-22 05:53:37 +03:00
cond := builder . NewCond ( )
2022-03-10 17:54:51 +03:00
if opts . RequestedTeam != nil && opts . RequestedUser == nil {
2023-09-29 15:12:54 +03:00
org , err := user_model . GetUserByID ( ctx , opts . RequestedTeam . OrgID )
2022-03-10 17:54:51 +03:00
if err != nil {
return nil , err
}
opts . RequestedUser = org
}
// check activity visibility for actor ( similar to activityReadable() )
if opts . Actor == nil {
cond = cond . And ( builder . In ( "act_user_id" ,
builder . Select ( "`user`.id" ) . Where (
builder . Eq { "keep_activity_private" : false , "visibility" : structs . VisibleTypePublic } ,
) . From ( "`user`" ) ,
) )
} else if ! opts . Actor . IsAdmin {
2023-05-10 07:14:58 +03:00
uidCond := builder . Select ( "`user`.id" ) . From ( "`user`" ) . Where (
builder . Eq { "keep_activity_private" : false } .
And ( builder . In ( "visibility" , structs . VisibleTypePublic , structs . VisibleTypeLimited ) ) ) .
Or ( builder . Eq { "id" : opts . Actor . ID } )
if opts . RequestedUser != nil {
if opts . RequestedUser . IsOrganization ( ) {
// An organization can always see the activities whose `act_user_id` is the same as its id.
uidCond = uidCond . Or ( builder . Eq { "id" : opts . RequestedUser . ID } )
} else {
// A user can always see the activities of the organizations to which the user belongs.
uidCond = uidCond . Or (
builder . Eq { "type" : user_model . UserTypeOrganization } .
And ( builder . In ( "`user`.id" , builder . Select ( "org_id" ) .
Where ( builder . Eq { "uid" : opts . RequestedUser . ID } ) .
From ( "team_user" ) ) ) ,
)
}
}
cond = cond . And ( builder . In ( "act_user_id" , uidCond ) )
2017-08-23 04:30:54 +03:00
}
2020-12-22 05:53:37 +03:00
// check readable repositories by doer/actor
2020-06-05 23:01:53 +03:00
if opts . Actor == nil || ! opts . Actor . IsAdmin {
2022-06-06 11:01:49 +03:00
cond = cond . And ( builder . In ( "repo_id" , repo_model . AccessibleRepoIDsQuery ( opts . Actor ) ) )
2022-03-10 17:54:51 +03:00
}
if opts . RequestedRepo != nil {
cond = cond . And ( builder . Eq { "repo_id" : opts . RequestedRepo . ID } )
2020-06-05 23:01:53 +03:00
}
2020-12-27 22:58:03 +03:00
if opts . RequestedTeam != nil {
2023-10-03 13:30:41 +03:00
env := organization . OrgFromUser ( opts . RequestedUser ) . AccessibleTeamReposEnv ( ctx , opts . RequestedTeam )
2020-12-27 22:58:03 +03:00
teamRepoIDs , err := env . RepoIDs ( 1 , opts . RequestedUser . NumRepos )
if err != nil {
2022-10-24 22:29:17 +03:00
return nil , fmt . Errorf ( "GetTeamRepositories: %w" , err )
2020-12-27 22:58:03 +03:00
}
cond = cond . And ( builder . In ( "repo_id" , teamRepoIDs ) )
}
2022-03-10 17:54:51 +03:00
if opts . RequestedUser != nil {
cond = cond . And ( builder . Eq { "user_id" : opts . RequestedUser . ID } )
2016-02-06 10:52:21 +03:00
2022-03-10 17:54:51 +03:00
if opts . OnlyPerformedBy {
cond = cond . And ( builder . Eq { "act_user_id" : opts . RequestedUser . ID } )
}
2017-06-02 03:42:25 +03:00
}
2022-03-10 17:54:51 +03:00
2017-06-02 03:42:25 +03:00
if ! opts . IncludePrivate {
2022-05-05 18:39:26 +03:00
cond = cond . And ( builder . Eq { "`action`.is_private" : false } )
2017-06-02 03:42:25 +03:00
}
2017-06-25 21:20:29 +03:00
if ! opts . IncludeDeleted {
2017-08-23 04:30:54 +03:00
cond = cond . And ( builder . Eq { "is_deleted" : false } )
2017-06-25 21:20:29 +03:00
}
2021-02-21 01:08:58 +03:00
if opts . Date != "" {
2021-04-01 13:52:17 +03:00
dateLow , err := time . ParseInLocation ( "2006-01-02" , opts . Date , setting . DefaultUILocation )
2021-02-21 01:08:58 +03:00
if err != nil {
log . Warn ( "Unable to parse %s, filter not applied: %v" , opts . Date , err )
} else {
dateHigh := dateLow . Add ( 86399000000000 ) // 23h59m59s
2022-05-05 18:39:26 +03:00
cond = cond . And ( builder . Gte { "`action`.created_unix" : dateLow . Unix ( ) } )
cond = cond . And ( builder . Lte { "`action`.created_unix" : dateHigh . Unix ( ) } )
2021-02-21 01:08:58 +03:00
}
}
2020-12-22 05:53:37 +03:00
return cond , nil
2014-03-13 09:16:14 +04:00
}
2021-05-01 15:17:02 +03:00
// DeleteOldActions deletes all old actions from database.
2023-09-29 15:12:54 +03:00
func DeleteOldActions ( ctx context . Context , olderThan time . Duration ) ( err error ) {
2021-05-01 15:17:02 +03:00
if olderThan <= 0 {
return nil
}
2023-09-29 15:12:54 +03:00
_ , err = db . GetEngine ( ctx ) . Where ( "created_unix < ?" , time . Now ( ) . Add ( - olderThan ) . Unix ( ) ) . Delete ( & Action { } )
2022-06-20 13:02:49 +03:00
return err
2021-05-01 15:17:02 +03:00
}
2021-12-12 18:48:20 +03:00
2022-11-19 11:12:33 +03:00
// NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers ( ctx context . Context , actions ... * Action ) error {
2021-12-12 18:48:20 +03:00
var watchers [ ] * repo_model . Watch
var repo * repo_model . Repository
var err error
var permCode [ ] bool
var permIssue [ ] bool
var permPR [ ] bool
e := db . GetEngine ( ctx )
for _ , act := range actions {
repoChanged := repo == nil || repo . ID != act . RepoID
if repoChanged {
// Add feeds for user self and all watchers.
2024-01-15 02:22:06 +03:00
watchers , err = repo_model . GetWatchers ( ctx , act . RepoID )
2021-12-12 18:48:20 +03:00
if err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "get watchers: %w" , err )
2021-12-12 18:48:20 +03:00
}
2024-01-15 02:22:06 +03:00
// Be aware that optimizing this correctly into the `GetWatchers` SQL
// query is for most cases less performant than doing this.
blockedDoerUserIDs , err := user_model . ListBlockedByUsersID ( ctx , act . ActUserID )
if err != nil {
return fmt . Errorf ( "user_model.ListBlockedByUsersID: %w" , err )
}
if len ( blockedDoerUserIDs ) > 0 {
excludeWatcherIDs := make ( container . Set [ int64 ] , len ( blockedDoerUserIDs ) )
excludeWatcherIDs . AddMultiple ( blockedDoerUserIDs ... )
watchers = slices . DeleteFunc ( watchers , func ( v * repo_model . Watch ) bool {
return excludeWatcherIDs . Contains ( v . UserID )
} )
}
2021-12-12 18:48:20 +03:00
}
// Add feed for actioner.
act . UserID = act . ActUserID
if _ , err = e . Insert ( act ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "insert new actioner: %w" , err )
2021-12-12 18:48:20 +03:00
}
if repoChanged {
2022-12-03 05:48:26 +03:00
act . loadRepo ( ctx )
2021-12-12 18:48:20 +03:00
repo = act . Repo
// check repo owner exist.
2023-02-18 15:11:03 +03:00
if err := act . Repo . LoadOwner ( ctx ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "can't get repo owner: %w" , err )
2021-12-12 18:48:20 +03:00
}
} else if act . Repo == nil {
act . Repo = repo
}
// Add feed for organization
if act . Repo . Owner . IsOrganization ( ) && act . ActUserID != act . Repo . Owner . ID {
act . ID = 0
act . UserID = act . Repo . Owner . ID
2022-06-04 22:18:50 +03:00
if err = db . Insert ( ctx , act ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "insert new actioner: %w" , err )
2021-12-12 18:48:20 +03:00
}
}
if repoChanged {
permCode = make ( [ ] bool , len ( watchers ) )
permIssue = make ( [ ] bool , len ( watchers ) )
permPR = make ( [ ] bool , len ( watchers ) )
for i , watcher := range watchers {
2022-12-03 05:48:26 +03:00
user , err := user_model . GetUserByID ( ctx , watcher . UserID )
2021-12-12 18:48:20 +03:00
if err != nil {
permCode [ i ] = false
permIssue [ i ] = false
permPR [ i ] = false
continue
}
2022-05-11 13:09:36 +03:00
perm , err := access_model . GetUserRepoPermission ( ctx , repo , user )
2021-12-12 18:48:20 +03:00
if err != nil {
permCode [ i ] = false
permIssue [ i ] = false
permPR [ i ] = false
continue
}
permCode [ i ] = perm . CanRead ( unit . TypeCode )
permIssue [ i ] = perm . CanRead ( unit . TypeIssues )
permPR [ i ] = perm . CanRead ( unit . TypePullRequests )
}
}
for i , watcher := range watchers {
if act . ActUserID == watcher . UserID {
continue
}
act . ID = 0
act . UserID = watcher . UserID
act . Repo . Units = nil
switch act . OpType {
case ActionCommitRepo , ActionPushTag , ActionDeleteTag , ActionPublishRelease , ActionDeleteBranch :
if ! permCode [ i ] {
continue
}
case ActionCreateIssue , ActionCommentIssue , ActionCloseIssue , ActionReopenIssue :
if ! permIssue [ i ] {
continue
}
2022-11-03 18:49:00 +03:00
case ActionCreatePullRequest , ActionCommentPull , ActionMergePullRequest , ActionClosePullRequest , ActionReopenPullRequest , ActionAutoMergePullRequest :
2021-12-12 18:48:20 +03:00
if ! permPR [ i ] {
continue
}
}
2022-06-04 22:18:50 +03:00
if err = db . Insert ( ctx , act ) ; err != nil {
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "insert new action: %w" , err )
2021-12-12 18:48:20 +03:00
}
}
}
return nil
}
// NotifyWatchersActions creates batch of actions for every watcher.
2023-09-29 15:12:54 +03:00
func NotifyWatchersActions ( ctx context . Context , acts [ ] * Action ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-12-12 18:48:20 +03:00
if err != nil {
return err
}
defer committer . Close ( )
for _ , act := range acts {
2022-11-19 11:12:33 +03:00
if err := NotifyWatchers ( ctx , act ) ; err != nil {
2021-12-12 18:48:20 +03:00
return err
}
}
return committer . Commit ( )
}
2022-06-13 12:37:59 +03:00
// DeleteIssueActions delete all actions related with issueID
2023-08-07 13:23:59 +03:00
func DeleteIssueActions ( ctx context . Context , repoID , issueID , issueIndex int64 ) error {
2022-06-13 12:37:59 +03:00
// delete actions assigned to this issue
2023-08-07 13:23:59 +03:00
e := db . GetEngine ( ctx )
// MariaDB has a performance bug: https://jira.mariadb.org/browse/MDEV-16289
// so here it uses "DELETE ... WHERE IN" with pre-queried IDs.
var lastCommentID int64
commentIDs := make ( [ ] int64 , 0 , db . DefaultMaxInSize )
for {
commentIDs = commentIDs [ : 0 ]
err := e . Select ( "`id`" ) . Table ( & issues_model . Comment { } ) .
Where ( builder . Eq { "issue_id" : issueID } ) . And ( "`id` > ?" , lastCommentID ) .
OrderBy ( "`id`" ) . Limit ( db . DefaultMaxInSize ) .
Find ( & commentIDs )
if err != nil {
return err
} else if len ( commentIDs ) == 0 {
break
} else if _ , err = db . GetEngine ( ctx ) . In ( "comment_id" , commentIDs ) . Delete ( & Action { } ) ; err != nil {
return err
}
2023-10-24 05:54:59 +03:00
lastCommentID = commentIDs [ len ( commentIDs ) - 1 ]
2022-06-13 12:37:59 +03:00
}
2023-08-07 13:23:59 +03:00
_ , err := e . Where ( "repo_id = ?" , repoID ) .
2022-06-13 12:37:59 +03:00
In ( "op_type" , ActionCreateIssue , ActionCreatePullRequest ) .
2023-08-07 13:23:59 +03:00
Where ( "content LIKE ?" , strconv . FormatInt ( issueIndex , 10 ) + "|%" ) . // "IssueIndex|content..."
2022-06-13 12:37:59 +03:00
Delete ( & Action { } )
return err
}
2022-08-25 05:31:57 +03:00
// CountActionCreatedUnixString count actions where created_unix is an empty string
2022-11-19 11:12:33 +03:00
func CountActionCreatedUnixString ( ctx context . Context ) ( int64 , error ) {
2023-03-07 13:51:06 +03:00
if setting . Database . Type . IsSQLite3 ( ) {
2022-11-19 11:12:33 +03:00
return db . GetEngine ( ctx ) . Where ( ` created_unix = "" ` ) . Count ( new ( Action ) )
2022-08-25 05:31:57 +03:00
}
return 0 , nil
}
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
2022-11-19 11:12:33 +03:00
func FixActionCreatedUnixString ( ctx context . Context ) ( int64 , error ) {
2023-03-07 13:51:06 +03:00
if setting . Database . Type . IsSQLite3 ( ) {
2022-11-19 11:12:33 +03:00
res , err := db . GetEngine ( ctx ) . Exec ( ` UPDATE action SET created_unix = 0 WHERE created_unix = "" ` )
2022-08-25 05:31:57 +03:00
if err != nil {
return 0 , err
}
return res . RowsAffected ( )
}
return 0 , nil
}