2014-03-13 13:16:14 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-05-01 12:21:05 -04:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-03-13 13:16:14 +08:00
2022-08-25 10:31:57 +08:00
package activities
2014-03-13 13:16:14 +08:00
import (
2021-12-10 09:27:50 +08:00
"context"
2014-05-06 11:50:31 -04:00
"fmt"
2021-11-16 18:18:25 +00:00
"net/url"
2014-07-26 00:24:27 -04:00
"path"
2017-06-25 20:20:29 +02:00
"strconv"
2014-04-13 22:20:28 -04:00
"strings"
2014-03-13 13:16:14 +08:00
"time"
2014-03-22 06:20:00 -04:00
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2022-06-13 17:37:59 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 14:29:02 +08:00
"code.gitea.io/gitea/models/organization"
2022-05-11 18:09:36 +08:00
access_model "code.gitea.io/gitea/models/perm/access"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-12-12 23:48:20 +08:00
"code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/base"
2020-12-14 17:08:37 +00:00
"code.gitea.io/gitea/modules/git"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2022-03-10 15:54:51 +01:00
"code.gitea.io/gitea/modules/structs"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2017-12-02 18:20:12 -08:00
2019-06-23 23:22:43 +08:00
"xorm.io/builder"
2022-06-18 09:46:50 +01:00
"xorm.io/xorm/schemas"
2014-03-13 13:16:14 +08:00
)
2016-11-22 11:43:30 +01:00
// ActionType represents the type of an action.
2014-07-26 00:24:27 -04:00
type ActionType int
2016-11-22 11:43:30 +01:00
// Possible action types.
2014-03-13 13:16:14 +08:00
const (
2021-06-23 06:14:22 +02: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 16:49:00 +01:00
ActionAutoMergePullRequest // 27
2014-03-13 13:16:14 +08:00
)
2023-04-04 21:35:31 +08: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 22:23:13 +08:00
func ( at ActionType ) InActions ( actions ... string ) bool {
for _ , action := range actions {
if action == at . String ( ) {
return true
}
}
return false
}
2016-11-22 11:43:30 +01: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 13:16:14 +08:00
type Action struct {
2017-05-25 21:38:18 -04:00
ID int64 ` xorm:"pk autoincr" `
2023-10-04 03:41:25 +02:00
UserID int64 ` xorm:"INDEX" ` // Receiver user id.
2017-05-25 21:38:18 -04:00
OpType ActionType
2022-06-18 09:46:50 +01:00
ActUserID int64 // Action user id.
ActUser * user_model . User ` xorm:"-" `
RepoID int64
2021-12-10 09:27:50 +08:00
Repo * repo_model . Repository ` xorm:"-" `
CommentID int64 ` xorm:"INDEX" `
2022-06-13 17:37:59 +08:00
Comment * issues_model . Comment ` xorm:"-" `
2024-03-12 12:57:19 +08:00
Issue * issues_model . Issue ` xorm:"-" ` // get the issue id from content
2022-06-18 09:46:50 +01:00
IsDeleted bool ` xorm:"NOT NULL DEFAULT false" `
2017-05-25 21:38:18 -04:00
RefName string
2022-06-18 09:46:50 +01:00
IsPrivate bool ` xorm:"NOT NULL DEFAULT false" `
2019-08-15 22:46:21 +08:00
Content string ` xorm:"TEXT" `
2022-06-18 09:46:50 +01:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2015-08-20 00:12:43 +08:00
}
2021-09-19 19:49:59 +08:00
func init ( ) {
db . RegisterModel ( new ( Action ) )
}
2022-06-18 09:46:50 +01:00
// TableIndices implements xorm's TableIndices interface
func ( a * Action ) TableIndices ( ) [ ] * schemas . Index {
2022-07-01 17:04:01 +01:00
repoIndex := schemas . NewIndex ( "r_u_d" , schemas . IndexType )
repoIndex . AddColumn ( "repo_id" , "user_id" , "is_deleted" )
2022-06-18 09:46:50 +01: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 23:44:33 +08:00
cudIndex := schemas . NewIndex ( "c_u_d" , schemas . IndexType )
cudIndex . AddColumn ( "created_unix" , "user_id" , "is_deleted" )
2024-11-06 14:04:48 -08:00
cuIndex := schemas . NewIndex ( "c_u" , schemas . IndexType )
cuIndex . AddColumn ( "user_id" , "is_deleted" )
indices := [ ] * schemas . Index { actUserIndex , repoIndex , cudIndex , cuIndex }
2022-09-03 17:27:59 +01:00
return indices
2022-06-18 09:46:50 +01:00
}
2016-11-22 11:43:30 +01:00
// GetOpType gets the ActionType of this action.
2017-09-19 18:22:42 -07:00
func ( a * Action ) GetOpType ( ) ActionType {
return a . OpType
2014-03-15 00:50:51 -04:00
}
2020-12-09 06:11:15 +01:00
// LoadActUser loads a.ActUser
2022-12-03 10:48:26 +08:00
func ( a * Action ) LoadActUser ( ctx context . Context ) {
2017-05-25 21:38:18 -04:00
if a . ActUser != nil {
return
}
var err error
2022-12-03 10:48:26 +08:00
a . ActUser , err = user_model . GetUserByID ( ctx , a . ActUserID )
2017-05-25 21:38:18 -04:00
if err == nil {
return
2021-11-24 17:49:20 +08:00
} else if user_model . IsErrUserNotExist ( err ) {
a . ActUser = user_model . NewGhostUser ( )
2017-05-25 21:38:18 -04:00
} else {
2019-04-02 08:48:31 +01:00
log . Error ( "GetUserByID(%d): %v" , a . ActUserID , err )
2017-05-25 21:38:18 -04:00
}
}
2024-11-24 16:18:57 +08:00
func ( a * Action ) LoadRepo ( ctx context . Context ) {
2017-06-13 20:37:50 -04:00
if a . Repo != nil {
2017-05-25 21:38:18 -04:00
return
}
var err error
2022-12-03 10:48:26 +08:00
a . Repo , err = repo_model . GetRepositoryByID ( ctx , a . RepoID )
2017-05-25 21:38:18 -04:00
if err != nil {
2021-12-10 09:27:50 +08:00
log . Error ( "repo_model.GetRepositoryByID(%d): %v" , a . RepoID , err )
2017-05-25 21:38:18 -04:00
}
}
2018-07-05 19:48:18 +02:00
// GetActFullName gets the action's user full name.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetActFullName ( ctx context . Context ) string {
a . LoadActUser ( ctx )
2018-07-05 19:48:18 +02:00
return a . ActUser . FullName
}
2016-11-22 11:43:30 +01:00
// GetActUserName gets the action's user name.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetActUserName ( ctx context . Context ) string {
a . LoadActUser ( ctx )
2017-05-25 21:38:18 -04:00
return a . ActUser . Name
2014-03-15 00:50:51 -04:00
}
2016-11-22 11:43:30 +01:00
// ShortActUserName gets the action's user name trimmed to max 20
// chars.
2023-09-29 14:12:54 +02:00
func ( a * Action ) ShortActUserName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetActUserName ( ctx ) , 20 )
2016-01-11 20:41:43 +08:00
}
2024-03-02 23:30:18 +08: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 10:41:35 +02:00
if setting . UI . DefaultShowFullName {
2023-09-29 14:12:54 +02:00
trimmedFullName := strings . TrimSpace ( a . GetActFullName ( ctx ) )
2020-02-26 22:08:24 +00:00
if len ( trimmedFullName ) > 0 {
return trimmedFullName
}
2019-05-08 10:41:35 +02:00
}
2023-09-29 14:12:54 +02:00
return a . ShortActUserName ( ctx )
2019-05-08 10:41:35 +02:00
}
2024-03-02 23:30:18 +08: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 10:41:35 +02:00
if setting . UI . DefaultShowFullName {
2023-09-29 14:12:54 +02:00
return a . ShortActUserName ( ctx )
2019-05-08 10:41:35 +02:00
}
2023-09-29 14:12:54 +02:00
return a . GetActFullName ( ctx )
2019-05-08 10:41:35 +02:00
}
2016-11-22 11:43:30 +01:00
// GetRepoUserName returns the name of the action repository owner.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetRepoUserName ( ctx context . Context ) string {
2024-11-24 16:18:57 +08:00
a . LoadRepo ( ctx )
2024-11-14 12:17:58 +08:00
if a . Repo == nil {
return "(non-existing-repo)"
}
2020-01-12 17:36:21 +08:00
return a . Repo . OwnerName
2014-05-09 14:42:50 +08:00
}
2016-11-22 11:43:30 +01:00
// ShortRepoUserName returns the name of the action repository owner
// trimmed to max 20 chars.
2023-09-29 14:12:54 +02:00
func ( a * Action ) ShortRepoUserName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetRepoUserName ( ctx ) , 20 )
2016-01-11 20:41:43 +08:00
}
2016-11-22 11:43:30 +01:00
// GetRepoName returns the name of the action repository.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetRepoName ( ctx context . Context ) string {
2024-11-24 16:18:57 +08:00
a . LoadRepo ( ctx )
2024-11-14 12:17:58 +08:00
if a . Repo == nil {
return "(non-existing-repo)"
}
2017-05-25 21:38:18 -04:00
return a . Repo . Name
2014-03-13 13:16:14 +08:00
}
2016-11-22 11:43:30 +01:00
// ShortRepoName returns the name of the action repository
// trimmed to max 33 chars.
2023-09-29 14:12:54 +02:00
func ( a * Action ) ShortRepoName ( ctx context . Context ) string {
return base . EllipsisString ( a . GetRepoName ( ctx ) , 33 )
2016-01-11 20:41:43 +08:00
}
2016-11-22 11:43:30 +01:00
// GetRepoPath returns the virtual path to the action repository.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetRepoPath ( ctx context . Context ) string {
return path . Join ( a . GetRepoUserName ( ctx ) , a . GetRepoName ( ctx ) )
2016-01-15 18:00:39 +08:00
}
2016-11-22 11:43:30 +01:00
// ShortRepoPath returns the virtual path to the action repository
2017-01-04 19:50:34 -05:00
// trimmed to max 20 + 1 + 33 chars.
2023-09-29 14:12:54 +02:00
func ( a * Action ) ShortRepoPath ( ctx context . Context ) string {
return path . Join ( a . ShortRepoUserName ( ctx ) , a . ShortRepoName ( ctx ) )
2015-03-12 16:01:23 -04:00
}
2016-11-22 11:43:30 +01:00
// GetRepoLink returns relative link to action repository.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetRepoLink ( ctx context . Context ) string {
2021-11-16 18:18:25 +00:00
// path.Join will skip empty strings
2023-09-29 14:12:54 +02:00
return path . Join ( setting . AppSubURL , "/" , url . PathEscape ( a . GetRepoUserName ( ctx ) ) , url . PathEscape ( a . GetRepoName ( ctx ) ) )
2014-07-26 00:24:27 -04:00
}
2022-09-21 22:51:42 +02:00
// GetRepoAbsoluteLink returns the absolute link to action repository.
2023-09-29 14:12:54 +02: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 22:51:42 +02:00
}
2023-02-07 02:09:18 +08: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
}
2024-03-12 12:57:19 +08:00
// GetCommentHTMLURL returns link to action comment.
func ( a * Action ) GetCommentHTMLURL ( ctx context . Context ) string {
2023-02-07 02:09:18 +08:00
if a == nil {
return "#"
}
_ = a . loadComment ( ctx )
if a . Comment != nil {
2023-09-29 14:12:54 +02:00
return a . Comment . HTMLURL ( ctx )
2023-02-07 02:09:18 +08:00
}
2024-03-12 12:57:19 +08:00
if err := a . LoadIssue ( ctx ) ; err != nil || a . Issue == nil {
2023-02-07 02:09:18 +08:00
return "#"
}
2024-03-12 12:57:19 +08:00
if err := a . Issue . LoadRepo ( ctx ) ; err != nil {
2023-02-07 02:09:18 +08:00
return "#"
}
2024-03-12 12:57:19 +08:00
return a . Issue . HTMLURL ( )
2023-02-07 02:09:18 +08:00
}
2017-06-25 20:20:29 +02:00
// GetCommentLink returns link to action comment.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetCommentLink ( ctx context . Context ) string {
2017-06-25 20:20:29 +02:00
if a == nil {
return "#"
}
2023-02-07 02:09:18 +08:00
_ = a . loadComment ( ctx )
2017-06-25 20:20:29 +02:00
if a . Comment != nil {
2023-09-29 14:12:54 +02:00
return a . Comment . Link ( ctx )
2017-06-25 20:20:29 +02:00
}
2024-03-12 12:57:19 +08:00
if err := a . LoadIssue ( ctx ) ; err != nil || a . Issue == nil {
2017-06-25 20:20:29 +02:00
return "#"
}
2024-03-12 12:57:19 +08:00
if err := a . Issue . LoadRepo ( ctx ) ; err != nil {
2018-12-13 23:55:43 +08:00
return "#"
}
2024-03-12 12:57:19 +08:00
return a . Issue . Link ( )
2017-06-25 20:20:29 +02:00
}
2016-11-22 11:43:30 +01:00
// GetBranch returns the action's repository branch.
2016-01-11 20:41:43 +08:00
func ( a * Action ) GetBranch ( ) string {
2020-12-14 17:08:37 +00:00
return strings . TrimPrefix ( a . RefName , git . BranchPrefix )
2014-03-16 11:30:35 -04:00
}
2021-12-16 19:01:14 +00:00
// GetRefLink returns the action's ref link.
2023-09-29 14:12:54 +02:00
func ( a * Action ) GetRefLink ( ctx context . Context ) string {
return git . RefURL ( a . GetRepoLink ( ctx ) , a . RefName )
2021-12-16 19:01:14 +00:00
}
2020-12-20 07:46:28 +08:00
// GetTag returns the action's repository tag.
func ( a * Action ) GetTag ( ) string {
return strings . TrimPrefix ( a . RefName , git . TagPrefix )
}
2016-11-22 11:43:30 +01:00
// GetContent returns the action's content.
2016-01-11 20:41:43 +08:00
func ( a * Action ) GetContent ( ) string {
2014-03-23 06:27:01 -04:00
return a . Content
2014-03-23 18:00:09 +08:00
}
2016-11-22 11:43:30 +01:00
// GetCreate returns the action creation time.
2016-01-11 20:41:43 +08:00
func ( a * Action ) GetCreate ( ) time . Time {
2017-12-11 12:37:04 +08:00
return a . CreatedUnix . AsTime ( )
2014-07-26 00:24:27 -04:00
}
2024-03-12 12:57:19 +08:00
func ( a * Action ) IsIssueEvent ( ) bool {
return a . OpType . InActions ( "comment_issue" , "approve_pull_request" , "reject_pull_request" , "comment_pull" , "merge_pull_request" )
}
2024-03-06 21:12:44 +08:00
// GetIssueInfos returns a list of associated information with the action.
2016-01-11 20:41:43 +08:00
func ( a * Action ) GetIssueInfos ( ) [ ] string {
2024-03-06 21:12:44 +08: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 00:24:27 -04:00
}
2024-03-12 12:57:19 +08:00
func ( a * Action ) getIssueIndex ( ) int64 {
infos := a . GetIssueInfos ( )
if len ( infos ) == 0 {
return 0
}
index , _ := strconv . ParseInt ( infos [ 0 ] , 10 , 64 )
return index
}
func ( a * Action ) LoadIssue ( ctx context . Context ) error {
if a . Issue != nil {
return nil
}
if index := a . getIssueIndex ( ) ; index > 0 {
issue , err := issues_model . GetIssueByIndex ( ctx , a . RepoID , index )
if err != nil {
return err
}
a . Issue = issue
a . Issue . Repo = a . Repo
}
return nil
}
2023-09-29 14:12:54 +02:00
// GetIssueTitle returns the title of first issue associated with the action.
func ( a * Action ) GetIssueTitle ( ctx context . Context ) string {
2024-03-12 12:57:19 +08:00
if err := a . LoadIssue ( ctx ) ; err != nil {
log . Error ( "LoadIssue: %v" , err )
return "<500 when get issue>"
}
if a . Issue == nil {
return "<Issue not found>"
2015-11-13 00:16:51 +03:00
}
2024-03-12 12:57:19 +08:00
return a . Issue . Title
2015-11-12 23:09:48 +03:00
}
2024-03-12 12:57:19 +08:00
// GetIssueContent returns the content of first issue associated with this action.
2023-07-22 22:14:27 +08:00
func ( a * Action ) GetIssueContent ( ctx context . Context ) string {
2024-03-12 12:57:19 +08:00
if err := a . LoadIssue ( ctx ) ; err != nil {
log . Error ( "LoadIssue: %v" , err )
return "<500 when get issue>"
}
if a . Issue == nil {
return "<Content not found>"
2015-11-13 12:11:45 -05:00
}
2024-03-12 12:57:19 +08:00
return a . Issue . Content
2015-11-13 12:11:45 -05:00
}
2017-06-01 20:42:25 -04:00
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
2022-03-10 15:54:51 +01:00
db . ListOptions
RequestedUser * user_model . User // the user we want activity for
2022-03-29 14:29:02 +08:00
RequestedTeam * organization . Team // the team we want activity for
2022-03-10 15:54:51 +01: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-01 20:42:25 -04:00
}
2022-08-25 10:31:57 +08:00
// ActivityReadable return whether doer can read activities of user
func ActivityReadable ( user , doer * user_model . User ) bool {
2022-03-10 15:54:51 +01:00
return ! user . KeepActivityPrivate ||
doer != nil && ( doer . IsAdmin || user . ID == doer . ID )
2020-12-22 02:53:37 +00:00
}
2017-08-23 03:30:54 +02:00
2024-11-29 09:53:49 -08:00
func ActivityQueryCondition ( ctx context . Context , opts GetFeedsOptions ) ( builder . Cond , error ) {
2020-12-22 02:53:37 +00:00
cond := builder . NewCond ( )
2022-03-10 15:54:51 +01:00
if opts . RequestedTeam != nil && opts . RequestedUser == nil {
2023-09-29 14:12:54 +02:00
org , err := user_model . GetUserByID ( ctx , opts . RequestedTeam . OrgID )
2022-03-10 15:54:51 +01: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 12:14:58 +08: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 03:30:54 +02:00
}
2020-12-22 02:53:37 +00:00
// check readable repositories by doer/actor
2020-06-05 22:01:53 +02:00
if opts . Actor == nil || ! opts . Actor . IsAdmin {
2022-06-06 16:01:49 +08:00
cond = cond . And ( builder . In ( "repo_id" , repo_model . AccessibleRepoIDsQuery ( opts . Actor ) ) )
2022-03-10 15:54:51 +01:00
}
if opts . RequestedRepo != nil {
2024-05-14 21:47:03 +08:00
// repo's actions could have duplicate items, see the comment of NotifyWatchers
// so here we only filter the "original items", aka: user_id == act_user_id
cond = cond . And (
builder . Eq { "`action`.repo_id" : opts . RequestedRepo . ID } ,
builder . Expr ( "`action`.user_id = `action`.act_user_id" ) ,
)
2020-06-05 22:01:53 +02:00
}
2020-12-27 20:58:03 +01:00
if opts . RequestedTeam != nil {
2023-10-03 12:30:41 +02:00
env := organization . OrgFromUser ( opts . RequestedUser ) . AccessibleTeamReposEnv ( ctx , opts . RequestedTeam )
2020-12-27 20:58:03 +01:00
teamRepoIDs , err := env . RepoIDs ( 1 , opts . RequestedUser . NumRepos )
if err != nil {
2022-10-24 21:29:17 +02:00
return nil , fmt . Errorf ( "GetTeamRepositories: %w" , err )
2020-12-27 20:58:03 +01:00
}
cond = cond . And ( builder . In ( "repo_id" , teamRepoIDs ) )
}
2022-03-10 15:54:51 +01:00
if opts . RequestedUser != nil {
cond = cond . And ( builder . Eq { "user_id" : opts . RequestedUser . ID } )
2016-02-06 07:52:21 +00:00
2022-03-10 15:54:51 +01:00
if opts . OnlyPerformedBy {
cond = cond . And ( builder . Eq { "act_user_id" : opts . RequestedUser . ID } )
}
2017-06-01 20:42:25 -04:00
}
2022-03-10 15:54:51 +01:00
2017-06-01 20:42:25 -04:00
if ! opts . IncludePrivate {
2022-05-05 16:39:26 +01:00
cond = cond . And ( builder . Eq { "`action`.is_private" : false } )
2017-06-01 20:42:25 -04:00
}
2017-06-25 20:20:29 +02:00
if ! opts . IncludeDeleted {
2017-08-23 03:30:54 +02:00
cond = cond . And ( builder . Eq { "is_deleted" : false } )
2017-06-25 20:20:29 +02:00
}
2021-02-20 23:08:58 +01:00
if opts . Date != "" {
2021-04-01 18:52:17 +08:00
dateLow , err := time . ParseInLocation ( "2006-01-02" , opts . Date , setting . DefaultUILocation )
2021-02-20 23:08:58 +01: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 16:39:26 +01:00
cond = cond . And ( builder . Gte { "`action`.created_unix" : dateLow . Unix ( ) } )
cond = cond . And ( builder . Lte { "`action`.created_unix" : dateHigh . Unix ( ) } )
2021-02-20 23:08:58 +01:00
}
}
2020-12-22 02:53:37 +00:00
return cond , nil
2014-03-13 13:16:14 +08:00
}
2021-05-01 20:17:02 +08:00
// DeleteOldActions deletes all old actions from database.
2023-09-29 14:12:54 +02:00
func DeleteOldActions ( ctx context . Context , olderThan time . Duration ) ( err error ) {
2021-05-01 20:17:02 +08:00
if olderThan <= 0 {
return nil
}
2023-09-29 14:12:54 +02:00
_ , err = db . GetEngine ( ctx ) . Where ( "created_unix < ?" , time . Now ( ) . Add ( - olderThan ) . Unix ( ) ) . Delete ( & Action { } )
2022-06-20 12:02:49 +02:00
return err
2021-05-01 20:17:02 +08:00
}
2021-12-12 23:48:20 +08:00
2022-11-19 09:12:33 +01:00
// NotifyWatchers creates batch of actions for every watcher.
2024-05-14 21:47:03 +08:00
// It could insert duplicate actions for a repository action, like this:
// * Original action: UserID=1 (the real actor), ActUserID=1
// * Organization action: UserID=100 (the repo's org), ActUserID=1
// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1
2022-11-19 09:12:33 +01:00
func NotifyWatchers ( ctx context . Context , actions ... * Action ) error {
2021-12-12 23:48:20 +08: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.
watchers , err = repo_model . GetWatchers ( ctx , act . RepoID )
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "get watchers: %w" , err )
2021-12-12 23:48:20 +08:00
}
}
// Add feed for actioner.
act . UserID = act . ActUserID
if _ , err = e . Insert ( act ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "insert new actioner: %w" , err )
2021-12-12 23:48:20 +08:00
}
if repoChanged {
2024-11-24 16:18:57 +08:00
act . LoadRepo ( ctx )
2021-12-12 23:48:20 +08:00
repo = act . Repo
// check repo owner exist.
2023-02-18 21:11:03 +09:00
if err := act . Repo . LoadOwner ( ctx ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "can't get repo owner: %w" , err )
2021-12-12 23:48:20 +08: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-05 03:18:50 +08:00
if err = db . Insert ( ctx , act ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "insert new actioner: %w" , err )
2021-12-12 23:48:20 +08: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 10:48:26 +08:00
user , err := user_model . GetUserByID ( ctx , watcher . UserID )
2021-12-12 23:48:20 +08:00
if err != nil {
permCode [ i ] = false
permIssue [ i ] = false
permPR [ i ] = false
continue
}
2022-05-11 18:09:36 +08:00
perm , err := access_model . GetUserRepoPermission ( ctx , repo , user )
2021-12-12 23:48:20 +08: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 16:49:00 +01:00
case ActionCreatePullRequest , ActionCommentPull , ActionMergePullRequest , ActionClosePullRequest , ActionReopenPullRequest , ActionAutoMergePullRequest :
2021-12-12 23:48:20 +08:00
if ! permPR [ i ] {
continue
}
}
2022-06-05 03:18:50 +08:00
if err = db . Insert ( ctx , act ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "insert new action: %w" , err )
2021-12-12 23:48:20 +08:00
}
}
}
return nil
}
// NotifyWatchersActions creates batch of actions for every watcher.
2023-09-29 14:12:54 +02:00
func NotifyWatchersActions ( ctx context . Context , acts [ ] * Action ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-12-12 23:48:20 +08:00
if err != nil {
return err
}
defer committer . Close ( )
for _ , act := range acts {
2022-11-19 09:12:33 +01:00
if err := NotifyWatchers ( ctx , act ) ; err != nil {
2021-12-12 23:48:20 +08:00
return err
}
}
return committer . Commit ( )
}
2022-06-13 17:37:59 +08:00
// DeleteIssueActions delete all actions related with issueID
2023-08-07 18:23:59 +08:00
func DeleteIssueActions ( ctx context . Context , repoID , issueID , issueIndex int64 ) error {
2022-06-13 17:37:59 +08:00
// delete actions assigned to this issue
2023-08-07 18:23:59 +08: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 04:54:59 +02:00
lastCommentID = commentIDs [ len ( commentIDs ) - 1 ]
2022-06-13 17:37:59 +08:00
}
2023-08-07 18:23:59 +08:00
_ , err := e . Where ( "repo_id = ?" , repoID ) .
2022-06-13 17:37:59 +08:00
In ( "op_type" , ActionCreateIssue , ActionCreatePullRequest ) .
2023-08-07 18:23:59 +08:00
Where ( "content LIKE ?" , strconv . FormatInt ( issueIndex , 10 ) + "|%" ) . // "IssueIndex|content..."
2022-06-13 17:37:59 +08:00
Delete ( & Action { } )
return err
}
2022-08-25 10:31:57 +08:00
// CountActionCreatedUnixString count actions where created_unix is an empty string
2022-11-19 09:12:33 +01:00
func CountActionCreatedUnixString ( ctx context . Context ) ( int64 , error ) {
2023-03-07 18:51:06 +08:00
if setting . Database . Type . IsSQLite3 ( ) {
2024-11-26 03:04:55 +08:00
return db . GetEngine ( ctx ) . Where ( ` created_unix = '' ` ) . Count ( new ( Action ) )
2022-08-25 10:31:57 +08:00
}
return 0 , nil
}
// FixActionCreatedUnixString set created_unix to zero if it is an empty string
2022-11-19 09:12:33 +01:00
func FixActionCreatedUnixString ( ctx context . Context ) ( int64 , error ) {
2023-03-07 18:51:06 +08:00
if setting . Database . Type . IsSQLite3 ( ) {
2024-11-26 03:04:55 +08:00
res , err := db . GetEngine ( ctx ) . Exec ( ` UPDATE action SET created_unix = 0 WHERE created_unix = '' ` )
2022-08-25 10:31:57 +08:00
if err != nil {
return 0 , err
}
return res . RowsAffected ( )
}
return 0 , nil
}