2016-12-30 14:44:54 -02:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-12-30 14:44:54 -02:00
2022-08-25 10:31:57 +08:00
package activities
2016-12-30 14:44:54 -02:00
import (
2021-11-19 21:39:57 +08:00
"context"
2017-01-12 02:27:09 -02:00
"fmt"
2021-11-16 18:18:25 +00:00
"net/url"
2021-02-19 21:36:43 +00:00
"strconv"
2017-12-11 12:37:04 +08: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-08-25 10:31:57 +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-11-10 03:57:58 +08:00
"code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2022-03-31 17:20:39 +08:00
"code.gitea.io/gitea/modules/container"
2020-01-14 16:37:19 +01:00
"code.gitea.io/gitea/modules/log"
2020-01-09 12:56:32 +01:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2020-01-09 12:56:32 +01:00
"xorm.io/builder"
"xorm.io/xorm"
2016-12-30 14:44:54 -02:00
)
type (
// NotificationStatus is the status of the notification (read or unread)
NotificationStatus uint8
// NotificationSource is the source of the notification (issue, PR, commit, etc)
NotificationSource uint8
)
const (
// NotificationStatusUnread represents an unread notification
NotificationStatusUnread NotificationStatus = iota + 1
// NotificationStatusRead represents a read notification
NotificationStatusRead
2017-01-12 02:27:09 -02:00
// NotificationStatusPinned represents a pinned notification
NotificationStatusPinned
2016-12-30 14:44:54 -02:00
)
const (
// NotificationSourceIssue is a notification of an issue
NotificationSourceIssue NotificationSource = iota + 1
// NotificationSourcePullRequest is a notification of a pull request
NotificationSourcePullRequest
// NotificationSourceCommit is a notification of a commit
NotificationSourceCommit
2021-03-01 01:47:30 +01:00
// NotificationSourceRepository is a notification for a repository
NotificationSourceRepository
2016-12-30 14:44:54 -02:00
)
// Notification represents a notification
type Notification struct {
ID int64 ` xorm:"pk autoincr" `
UserID int64 ` xorm:"INDEX NOT NULL" `
RepoID int64 ` xorm:"INDEX NOT NULL" `
Status NotificationStatus ` xorm:"SMALLINT INDEX NOT NULL" `
Source NotificationSource ` xorm:"SMALLINT INDEX NOT NULL" `
2019-11-12 16:33:34 +08:00
IssueID int64 ` xorm:"INDEX NOT NULL" `
CommitID string ` xorm:"INDEX" `
CommentID int64
2016-12-30 14:44:54 -02:00
UpdatedBy int64 ` xorm:"INDEX NOT NULL" `
2022-06-13 17:37:59 +08:00
Issue * issues_model . Issue ` xorm:"-" `
2021-12-10 09:27:50 +08:00
Repository * repo_model . Repository ` xorm:"-" `
2022-06-13 17:37:59 +08:00
Comment * issues_model . Comment ` xorm:"-" `
2021-12-10 09:27:50 +08:00
User * user_model . User ` xorm:"-" `
2016-12-30 14:44:54 -02:00
2019-08-15 22:46:21 +08:00
CreatedUnix timeutil . TimeStamp ` xorm:"created INDEX NOT NULL" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated INDEX NOT NULL" `
2016-12-30 14:44:54 -02:00
}
2021-09-19 19:49:59 +08:00
func init ( ) {
db . RegisterModel ( new ( Notification ) )
}
2020-01-09 12:56:32 +01:00
// FindNotificationOptions represent the filters for notifications. If an ID is 0 it will be ignored.
type FindNotificationOptions struct {
2021-09-24 19:32:56 +08:00
db . ListOptions
2020-01-09 12:56:32 +01:00
UserID int64
RepoID int64
IssueID int64
2020-07-11 22:46:01 +01:00
Status [ ] NotificationStatus
2021-06-16 19:04:37 +02:00
Source [ ] NotificationSource
2020-01-09 12:56:32 +01:00
UpdatedAfterUnix int64
UpdatedBeforeUnix int64
}
// ToCond will convert each condition into a xorm-Cond
func ( opts * FindNotificationOptions ) ToCond ( ) builder . Cond {
cond := builder . NewCond ( )
if opts . UserID != 0 {
cond = cond . And ( builder . Eq { "notification.user_id" : opts . UserID } )
}
if opts . RepoID != 0 {
cond = cond . And ( builder . Eq { "notification.repo_id" : opts . RepoID } )
}
if opts . IssueID != 0 {
cond = cond . And ( builder . Eq { "notification.issue_id" : opts . IssueID } )
}
2020-07-11 22:46:01 +01:00
if len ( opts . Status ) > 0 {
cond = cond . And ( builder . In ( "notification.status" , opts . Status ) )
2020-01-09 12:56:32 +01:00
}
2021-06-16 19:04:37 +02:00
if len ( opts . Source ) > 0 {
cond = cond . And ( builder . In ( "notification.source" , opts . Source ) )
}
2020-01-09 12:56:32 +01:00
if opts . UpdatedAfterUnix != 0 {
cond = cond . And ( builder . Gte { "notification.updated_unix" : opts . UpdatedAfterUnix } )
}
if opts . UpdatedBeforeUnix != 0 {
cond = cond . And ( builder . Lte { "notification.updated_unix" : opts . UpdatedBeforeUnix } )
}
return cond
}
// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
2022-05-20 22:08:52 +08:00
func ( opts * FindNotificationOptions ) ToSession ( ctx context . Context ) * xorm . Session {
sess := db . GetEngine ( ctx ) . Where ( opts . ToCond ( ) )
2020-04-21 23:21:46 +02:00
if opts . Page != 0 {
2021-09-24 19:32:56 +08:00
sess = db . SetSessionPagination ( sess , opts )
2020-04-21 23:21:46 +02:00
}
return sess
2020-01-09 12:56:32 +01:00
}
// GetNotifications returns all notifications that fit to the given options.
2022-05-20 22:08:52 +08:00
func GetNotifications ( ctx context . Context , options * FindNotificationOptions ) ( nl NotificationList , err error ) {
err = options . ToSession ( ctx ) . OrderBy ( "notification.updated_unix DESC" ) . Find ( & nl )
2022-06-20 12:02:49 +02:00
return nl , err
2020-01-09 12:56:32 +01:00
}
2021-08-12 14:43:08 +02:00
// CountNotifications count all notifications that fit to the given options and ignore pagination.
2022-11-19 09:12:33 +01:00
func CountNotifications ( ctx context . Context , opts * FindNotificationOptions ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Where ( opts . ToCond ( ) ) . Count ( & Notification { } )
2021-08-12 14:43:08 +02:00
}
2021-03-01 01:47:30 +01:00
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
2022-11-19 09:12:33 +01:00
func CreateRepoTransferNotification ( ctx context . Context , doer , newOwner * user_model . User , repo * repo_model . Repository ) error {
return db . AutoTx ( ctx , func ( ctx context . Context ) error {
var notify [ ] * Notification
2021-03-01 01:47:30 +01:00
2022-11-19 09:12:33 +01:00
if newOwner . IsOrganization ( ) {
users , err := organization . GetUsersWhoCanCreateOrgRepo ( ctx , newOwner . ID )
if err != nil || len ( users ) == 0 {
return err
}
for i := range users {
notify = append ( notify , & Notification {
UserID : users [ i ] . ID ,
RepoID : repo . ID ,
Status : NotificationStatusUnread ,
UpdatedBy : doer . ID ,
Source : NotificationSourceRepository ,
} )
}
} else {
notify = [ ] * Notification { {
UserID : newOwner . ID ,
2021-03-01 01:47:30 +01:00
RepoID : repo . ID ,
Status : NotificationStatusUnread ,
UpdatedBy : doer . ID ,
Source : NotificationSourceRepository ,
2022-11-19 09:12:33 +01:00
} }
2021-03-01 01:47:30 +01:00
}
2022-11-19 09:12:33 +01:00
return db . Insert ( ctx , notify )
} )
2021-03-01 01:47:30 +01:00
}
2016-12-30 14:44:54 -02:00
// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
2022-01-10 04:32:37 -05:00
// receiverID > 0 just send to receiver, else send to all watcher
2020-04-07 00:33:34 +08:00
func CreateOrUpdateIssueNotifications ( issueID , commentID , notificationAuthorID , receiverID int64 ) error {
2022-11-13 04:18:50 +08:00
ctx , committer , err := db . TxContext ( db . DefaultContext )
2021-11-21 23:41:00 +08:00
if err != nil {
2016-12-30 14:44:54 -02:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2016-12-30 14:44:54 -02:00
2021-12-10 09:27:50 +08:00
if err := createOrUpdateIssueNotifications ( ctx , issueID , commentID , notificationAuthorID , receiverID ) ; err != nil {
2016-12-30 14:44:54 -02:00
return err
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
2016-12-30 14:44:54 -02:00
}
2021-12-10 09:27:50 +08:00
func createOrUpdateIssueNotifications ( ctx context . Context , issueID , commentID , notificationAuthorID , receiverID int64 ) error {
2020-02-26 07:32:22 +01:00
// init
2022-10-12 07:18:26 +02:00
var toNotify container . Set [ int64 ]
2022-05-20 22:08:52 +08:00
notifications , err := getNotificationsByIssueID ( ctx , issueID )
2019-11-12 16:33:34 +08:00
if err != nil {
return err
}
2020-04-07 00:33:34 +08:00
2022-06-13 17:37:59 +08:00
issue , err := issues_model . GetIssueByID ( ctx , issueID )
2017-03-29 20:54:57 -03:00
if err != nil {
return err
}
2020-04-07 00:33:34 +08:00
if receiverID > 0 {
2022-10-12 07:18:26 +02:00
toNotify = make ( container . Set [ int64 ] , 1 )
toNotify . Add ( receiverID )
2020-04-07 00:33:34 +08:00
} else {
2022-10-12 07:18:26 +02:00
toNotify = make ( container . Set [ int64 ] , 32 )
2022-06-13 17:37:59 +08:00
issueWatches , err := issues_model . GetIssueWatchersIDs ( ctx , issueID , true )
2020-04-07 00:33:34 +08:00
if err != nil {
return err
}
2022-10-12 07:18:26 +02:00
toNotify . AddMultiple ( issueWatches ... )
2022-06-13 17:37:59 +08:00
if ! ( issue . IsPull && issues_model . HasWorkInProgressPrefix ( issue . Title ) ) {
2021-12-12 23:48:20 +08:00
repoWatches , err := repo_model . GetRepoWatchersIDs ( ctx , issue . RepoID )
2021-06-23 06:14:22 +02:00
if err != nil {
return err
}
2022-10-12 07:18:26 +02:00
toNotify . AddMultiple ( repoWatches ... )
2020-04-07 00:33:34 +08:00
}
2022-06-13 17:37:59 +08:00
issueParticipants , err := issue . GetParticipantIDsByIssue ( ctx )
2020-04-07 00:33:34 +08:00
if err != nil {
return err
}
2022-10-12 07:18:26 +02:00
toNotify . AddMultiple ( issueParticipants ... )
2017-03-29 20:54:57 -03:00
2020-04-07 00:33:34 +08:00
// dont notify user who cause notification
delete ( toNotify , notificationAuthorID )
// explicit unwatch on issue
2022-06-13 17:37:59 +08:00
issueUnWatches , err := issues_model . GetIssueWatchersIDs ( ctx , issueID , false )
2020-04-07 00:33:34 +08:00
if err != nil {
return err
}
for _ , id := range issueUnWatches {
2022-10-12 07:18:26 +02:00
toNotify . Remove ( id )
2020-04-07 00:33:34 +08:00
}
2016-12-30 14:44:54 -02:00
}
2022-04-08 17:11:15 +08:00
err = issue . LoadRepo ( ctx )
2019-06-12 21:41:28 +02:00
if err != nil {
return err
}
2018-06-21 18:00:13 +02:00
2020-02-26 07:32:22 +01:00
// notify
for userID := range toNotify {
2018-06-21 18:00:13 +02:00
issue . Repo . Units = nil
2022-12-03 10:48:26 +08:00
user , err := user_model . GetUserByID ( ctx , userID )
2020-09-16 07:49:34 +08:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2020-09-16 07:49:34 +08:00
continue
}
return err
}
2022-08-25 10:31:57 +08:00
if issue . IsPull && ! access_model . CheckRepoUnitUser ( ctx , issue . Repo , user , unit . TypePullRequests ) {
2018-06-21 18:00:13 +02:00
continue
}
2022-08-25 10:31:57 +08:00
if ! issue . IsPull && ! access_model . CheckRepoUnitUser ( ctx , issue . Repo , user , unit . TypeIssues ) {
2018-06-21 18:00:13 +02:00
continue
}
2020-02-26 07:32:22 +01:00
if notificationExists ( notifications , issue . ID , userID ) {
2022-05-20 22:08:52 +08:00
if err = updateIssueNotification ( ctx , userID , issue . ID , commentID , notificationAuthorID ) ; err != nil {
2020-02-26 07:32:22 +01:00
return err
}
continue
}
2022-05-20 22:08:52 +08:00
if err = createIssueNotification ( ctx , userID , issue , commentID , notificationAuthorID ) ; err != nil {
2017-03-29 20:54:57 -03:00
return err
}
}
2016-12-30 14:44:54 -02:00
return nil
}
2022-05-20 22:08:52 +08:00
func getNotificationsByIssueID ( ctx context . Context , issueID int64 ) ( notifications [ ] * Notification , err error ) {
err = db . GetEngine ( ctx ) .
2016-12-30 14:44:54 -02:00
Where ( "issue_id = ?" , issueID ) .
Find ( & notifications )
2022-06-20 12:02:49 +02:00
return notifications , err
2016-12-30 14:44:54 -02:00
}
func notificationExists ( notifications [ ] * Notification , issueID , userID int64 ) bool {
for _ , notification := range notifications {
if notification . IssueID == issueID && notification . UserID == userID {
return true
}
}
return false
}
2022-06-13 17:37:59 +08:00
func createIssueNotification ( ctx context . Context , userID int64 , issue * issues_model . Issue , commentID , updatedByID int64 ) error {
2016-12-30 14:44:54 -02:00
notification := & Notification {
UserID : userID ,
RepoID : issue . RepoID ,
Status : NotificationStatusUnread ,
IssueID : issue . ID ,
2019-11-12 16:33:34 +08:00
CommentID : commentID ,
2016-12-30 14:44:54 -02:00
UpdatedBy : updatedByID ,
}
if issue . IsPull {
notification . Source = NotificationSourcePullRequest
} else {
notification . Source = NotificationSourceIssue
}
2022-05-20 22:08:52 +08:00
return db . Insert ( ctx , notification )
2016-12-30 14:44:54 -02:00
}
2022-05-20 22:08:52 +08:00
func updateIssueNotification ( ctx context . Context , userID , issueID , commentID , updatedByID int64 ) error {
notification , err := getIssueNotification ( ctx , userID , issueID )
2016-12-30 14:44:54 -02:00
if err != nil {
return err
}
2019-11-12 16:33:34 +08:00
// NOTICE: Only update comment id when the before notification on this issue is read, otherwise you may miss some old comments.
// But we need update update_by so that the notification will be reorder
var cols [ ] string
if notification . Status == NotificationStatusRead {
notification . Status = NotificationStatusUnread
notification . CommentID = commentID
cols = [ ] string { "status" , "update_by" , "comment_id" }
} else {
notification . UpdatedBy = updatedByID
cols = [ ] string { "update_by" }
}
2016-12-30 14:44:54 -02:00
2022-05-20 22:08:52 +08:00
_ , err = db . GetEngine ( ctx ) . ID ( notification . ID ) . Cols ( cols ... ) . Update ( notification )
2016-12-30 14:44:54 -02:00
return err
}
2022-05-20 22:08:52 +08:00
func getIssueNotification ( ctx context . Context , userID , issueID int64 ) ( * Notification , error ) {
2016-12-30 14:44:54 -02:00
notification := new ( Notification )
2022-05-20 22:08:52 +08:00
_ , err := db . GetEngine ( ctx ) .
2016-12-30 14:44:54 -02:00
Where ( "user_id = ?" , userID ) .
And ( "issue_id = ?" , issueID ) .
Get ( notification )
return notification , err
}
// NotificationsForUser returns notifications for a given user and status
2022-05-20 22:08:52 +08:00
func NotificationsForUser ( ctx context . Context , user * user_model . User , statuses [ ] NotificationStatus , page , perPage int ) ( notifications NotificationList , err error ) {
2017-02-16 12:07:00 +08:00
if len ( statuses ) == 0 {
return
2017-01-12 02:27:09 -02:00
}
2022-05-20 22:08:52 +08:00
sess := db . GetEngine ( ctx ) .
2016-12-30 14:44:54 -02:00
Where ( "user_id = ?" , user . ID ) .
2017-02-16 12:07:00 +08:00
In ( "status" , statuses ) .
2017-01-03 17:09:36 -02:00
OrderBy ( "updated_unix DESC" )
if page > 0 && perPage > 0 {
sess . Limit ( perPage , ( page - 1 ) * perPage )
}
2017-02-16 12:07:00 +08:00
err = sess . Find ( & notifications )
2022-06-20 12:02:49 +02:00
return notifications , err
2016-12-30 14:44:54 -02:00
}
2020-01-14 16:37:19 +01:00
// CountUnread count unread notifications for a user
2022-05-20 22:08:52 +08:00
func CountUnread ( ctx context . Context , userID int64 ) int64 {
exist , err := db . GetEngine ( ctx ) . Where ( "user_id = ?" , userID ) . And ( "status = ?" , NotificationStatusUnread ) . Count ( new ( Notification ) )
2020-01-14 16:37:19 +01:00
if err != nil {
log . Error ( "countUnread" , err )
return 0
}
return exist
}
2020-01-09 12:56:32 +01:00
// LoadAttributes load Repo Issue User and Comment if not loaded
2022-11-19 09:12:33 +01:00
func ( n * Notification ) LoadAttributes ( ctx context . Context ) ( err error ) {
2021-12-10 09:27:50 +08:00
if err = n . loadRepo ( ctx ) ; err != nil {
2020-01-09 12:56:32 +01:00
return
}
2021-11-19 21:39:57 +08:00
if err = n . loadIssue ( ctx ) ; err != nil {
2020-01-09 12:56:32 +01:00
return
}
2022-05-20 22:08:52 +08:00
if err = n . loadUser ( ctx ) ; err != nil {
2020-01-09 12:56:32 +01:00
return
}
2022-05-20 22:08:52 +08:00
if err = n . loadComment ( ctx ) ; err != nil {
2020-01-09 12:56:32 +01:00
return
}
2022-06-20 12:02:49 +02:00
return err
2020-01-09 12:56:32 +01:00
}
2021-12-10 09:27:50 +08:00
func ( n * Notification ) loadRepo ( ctx context . Context ) ( err error ) {
2020-01-09 12:56:32 +01:00
if n . Repository == nil {
2022-12-03 10:48:26 +08:00
n . Repository , err = repo_model . GetRepositoryByID ( ctx , n . RepoID )
2020-01-09 12:56:32 +01:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "getRepositoryByID [%d]: %w" , n . RepoID , err )
2020-01-09 12:56:32 +01:00
}
}
return nil
}
2021-11-19 21:39:57 +08:00
func ( n * Notification ) loadIssue ( ctx context . Context ) ( err error ) {
2021-03-01 01:47:30 +01:00
if n . Issue == nil && n . IssueID != 0 {
2022-06-13 17:37:59 +08:00
n . Issue , err = issues_model . GetIssueByID ( ctx , n . IssueID )
2020-01-09 12:56:32 +01:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "getIssueByID [%d]: %w" , n . IssueID , err )
2020-01-09 12:56:32 +01:00
}
2022-06-13 17:37:59 +08:00
return n . Issue . LoadAttributes ( ctx )
2020-01-09 12:56:32 +01:00
}
return nil
}
2022-05-20 22:08:52 +08:00
func ( n * Notification ) loadComment ( ctx context . Context ) ( err error ) {
2021-03-01 01:47:30 +01:00
if n . Comment == nil && n . CommentID != 0 {
2022-06-13 17:37:59 +08:00
n . Comment , err = issues_model . GetCommentByID ( ctx , n . CommentID )
2020-01-09 12:56:32 +01:00
if err != nil {
2022-06-13 17:37:59 +08:00
if issues_model . IsErrCommentNotExist ( err ) {
return issues_model . ErrCommentNotExist {
2021-11-10 13:48:45 +08:00
ID : n . CommentID ,
IssueID : n . IssueID ,
}
}
return err
2020-01-09 12:56:32 +01:00
}
}
return nil
}
2022-05-20 22:08:52 +08:00
func ( n * Notification ) loadUser ( ctx context . Context ) ( err error ) {
2020-01-09 12:56:32 +01:00
if n . User == nil {
2022-12-03 10:48:26 +08:00
n . User , err = user_model . GetUserByID ( ctx , n . UserID )
2020-01-09 12:56:32 +01:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "getUserByID [%d]: %w" , n . UserID , err )
2020-01-09 12:56:32 +01:00
}
}
return nil
}
2016-12-30 14:44:54 -02:00
// GetRepo returns the repo of the notification
2021-12-10 09:27:50 +08:00
func ( n * Notification ) GetRepo ( ) ( * repo_model . Repository , error ) {
return n . Repository , n . loadRepo ( db . DefaultContext )
2016-12-30 14:44:54 -02:00
}
// GetIssue returns the issue of the notification
2022-06-13 17:37:59 +08:00
func ( n * Notification ) GetIssue ( ) ( * issues_model . Issue , error ) {
2021-11-19 21:39:57 +08:00
return n . Issue , n . loadIssue ( db . DefaultContext )
2016-12-30 14:44:54 -02:00
}
2019-11-12 16:33:34 +08:00
// HTMLURL formats a URL-string to the notification
func ( n * Notification ) HTMLURL ( ) string {
2021-03-01 01:47:30 +01:00
switch n . Source {
case NotificationSourceIssue , NotificationSourcePullRequest :
if n . Comment != nil {
return n . Comment . HTMLURL ( )
}
return n . Issue . HTMLURL ( )
case NotificationSourceCommit :
2021-11-16 18:18:25 +00:00
return n . Repository . HTMLURL ( ) + "/commit/" + url . PathEscape ( n . CommitID )
2021-03-01 01:47:30 +01:00
case NotificationSourceRepository :
return n . Repository . HTMLURL ( )
2019-11-12 16:33:34 +08:00
}
2021-03-01 01:47:30 +01:00
return ""
2019-11-12 16:33:34 +08:00
}
2020-01-09 12:56:32 +01:00
// APIURL formats a URL-string to the notification
func ( n * Notification ) APIURL ( ) string {
2021-02-19 21:36:43 +00:00
return setting . AppURL + "api/v1/notifications/threads/" + strconv . FormatInt ( n . ID , 10 )
2020-01-09 12:56:32 +01:00
}
2019-11-12 16:33:34 +08:00
// NotificationList contains a list of notifications
type NotificationList [ ] * Notification
2020-01-09 12:56:32 +01:00
// LoadAttributes load Repo Issue User and Comment if not loaded
2022-11-19 09:12:33 +01:00
func ( nl NotificationList ) LoadAttributes ( ctx context . Context ) error {
2022-03-03 16:18:26 +01:00
var err error
2020-01-09 12:56:32 +01:00
for i := 0 ; i < len ( nl ) ; i ++ {
2022-11-19 09:12:33 +01:00
err = nl [ i ] . LoadAttributes ( ctx )
2022-06-13 17:37:59 +08:00
if err != nil && ! issues_model . IsErrCommentNotExist ( err ) {
2022-03-03 16:18:26 +01:00
return err
2020-01-09 12:56:32 +01:00
}
}
2022-03-03 16:18:26 +01:00
return nil
2020-01-09 12:56:32 +01:00
}
2019-11-12 16:33:34 +08:00
func ( nl NotificationList ) getPendingRepoIDs ( ) [ ] int64 {
2022-10-12 07:18:26 +02:00
ids := make ( container . Set [ int64 ] , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . Repository != nil {
continue
}
2022-10-12 07:18:26 +02:00
ids . Add ( notification . RepoID )
2019-11-12 16:33:34 +08:00
}
2022-10-12 07:18:26 +02:00
return ids . Values ( )
2019-11-12 16:33:34 +08:00
}
// LoadRepos loads repositories from database
2022-11-19 09:12:33 +01:00
func ( nl NotificationList ) LoadRepos ( ctx context . Context ) ( repo_model . RepositoryList , [ ] int , error ) {
2019-11-12 16:33:34 +08:00
if len ( nl ) == 0 {
2022-06-06 16:01:49 +08:00
return repo_model . RepositoryList { } , [ ] int { } , nil
2019-11-12 16:33:34 +08:00
}
2021-03-15 02:52:12 +08:00
repoIDs := nl . getPendingRepoIDs ( )
2021-12-10 09:27:50 +08:00
repos := make ( map [ int64 ] * repo_model . Repository , len ( repoIDs ) )
2021-03-15 02:52:12 +08:00
left := len ( repoIDs )
2019-11-12 16:33:34 +08:00
for left > 0 {
2022-06-13 17:37:59 +08:00
limit := db . DefaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2022-11-19 09:12:33 +01:00
rows , err := db . GetEngine ( ctx ) .
2019-11-12 16:33:34 +08:00
In ( "id" , repoIDs [ : limit ] ) .
2021-12-10 09:27:50 +08:00
Rows ( new ( repo_model . Repository ) )
2019-11-12 16:33:34 +08:00
if err != nil {
2020-03-29 20:51:14 +01:00
return nil , nil , err
2019-11-12 16:33:34 +08:00
}
for rows . Next ( ) {
2021-12-10 09:27:50 +08:00
var repo repo_model . Repository
2019-11-12 16:33:34 +08:00
err = rows . Scan ( & repo )
if err != nil {
rows . Close ( )
2020-03-29 20:51:14 +01:00
return nil , nil , err
2019-11-12 16:33:34 +08:00
}
repos [ repo . ID ] = & repo
}
_ = rows . Close ( )
left -= limit
repoIDs = repoIDs [ limit : ]
}
2020-03-29 20:51:14 +01:00
failed := [ ] int { }
2022-06-06 16:01:49 +08:00
reposList := make ( repo_model . RepositoryList , 0 , len ( repoIDs ) )
2020-03-29 20:51:14 +01:00
for i , notification := range nl {
2019-11-12 16:33:34 +08:00
if notification . Repository == nil {
notification . Repository = repos [ notification . RepoID ]
}
2020-03-29 20:51:14 +01:00
if notification . Repository == nil {
log . Error ( "Notification[%d]: RepoID: %d not found" , notification . ID , notification . RepoID )
failed = append ( failed , i )
continue
}
2019-11-12 16:33:34 +08:00
var found bool
for _ , r := range reposList {
2020-03-29 20:51:14 +01:00
if r . ID == notification . RepoID {
2019-11-12 16:33:34 +08:00
found = true
break
}
}
if ! found {
reposList = append ( reposList , notification . Repository )
}
}
2020-03-29 20:51:14 +01:00
return reposList , failed , nil
2019-11-12 16:33:34 +08:00
}
func ( nl NotificationList ) getPendingIssueIDs ( ) [ ] int64 {
2022-10-12 07:18:26 +02:00
ids := make ( container . Set [ int64 ] , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . Issue != nil {
continue
}
2022-10-12 07:18:26 +02:00
ids . Add ( notification . IssueID )
2019-11-12 16:33:34 +08:00
}
2022-10-12 07:18:26 +02:00
return ids . Values ( )
2019-11-12 16:33:34 +08:00
}
// LoadIssues loads issues from database
2022-11-19 09:12:33 +01:00
func ( nl NotificationList ) LoadIssues ( ctx context . Context ) ( [ ] int , error ) {
2019-11-12 16:33:34 +08:00
if len ( nl ) == 0 {
2020-03-29 20:51:14 +01:00
return [ ] int { } , nil
2019-11-12 16:33:34 +08:00
}
2021-03-15 02:52:12 +08:00
issueIDs := nl . getPendingIssueIDs ( )
2022-06-13 17:37:59 +08:00
issues := make ( map [ int64 ] * issues_model . Issue , len ( issueIDs ) )
2021-03-15 02:52:12 +08:00
left := len ( issueIDs )
2019-11-12 16:33:34 +08:00
for left > 0 {
2022-06-13 17:37:59 +08:00
limit := db . DefaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2022-11-19 09:12:33 +01:00
rows , err := db . GetEngine ( ctx ) .
2019-11-12 16:33:34 +08:00
In ( "id" , issueIDs [ : limit ] ) .
2022-06-13 17:37:59 +08:00
Rows ( new ( issues_model . Issue ) )
2019-11-12 16:33:34 +08:00
if err != nil {
2020-03-29 20:51:14 +01:00
return nil , err
2019-11-12 16:33:34 +08:00
}
for rows . Next ( ) {
2022-06-13 17:37:59 +08:00
var issue issues_model . Issue
2019-11-12 16:33:34 +08:00
err = rows . Scan ( & issue )
if err != nil {
rows . Close ( )
2020-03-29 20:51:14 +01:00
return nil , err
2019-11-12 16:33:34 +08:00
}
issues [ issue . ID ] = & issue
}
_ = rows . Close ( )
left -= limit
issueIDs = issueIDs [ limit : ]
}
2020-03-29 20:51:14 +01:00
failures := [ ] int { }
for i , notification := range nl {
2019-11-12 16:33:34 +08:00
if notification . Issue == nil {
notification . Issue = issues [ notification . IssueID ]
2020-03-29 20:51:14 +01:00
if notification . Issue == nil {
2021-03-01 01:47:30 +01:00
if notification . IssueID != 0 {
log . Error ( "Notification[%d]: IssueID: %d Not Found" , notification . ID , notification . IssueID )
failures = append ( failures , i )
}
2020-03-29 20:51:14 +01:00
continue
}
2019-11-12 16:33:34 +08:00
notification . Issue . Repo = notification . Repository
}
}
2020-03-29 20:51:14 +01:00
return failures , nil
}
// Without returns the notification list without the failures
func ( nl NotificationList ) Without ( failures [ ] int ) NotificationList {
2020-03-30 19:52:45 +01:00
if len ( failures ) == 0 {
2020-03-29 20:51:14 +01:00
return nl
}
remaining := make ( [ ] * Notification , 0 , len ( nl ) )
last := - 1
var i int
for _ , i = range failures {
remaining = append ( remaining , nl [ last + 1 : i ] ... )
last = i
}
if len ( nl ) > i {
remaining = append ( remaining , nl [ i + 1 : ] ... )
}
return remaining
2019-11-12 16:33:34 +08:00
}
func ( nl NotificationList ) getPendingCommentIDs ( ) [ ] int64 {
2022-10-12 07:18:26 +02:00
ids := make ( container . Set [ int64 ] , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . CommentID == 0 || notification . Comment != nil {
continue
}
2022-10-12 07:18:26 +02:00
ids . Add ( notification . CommentID )
2019-11-12 16:33:34 +08:00
}
2022-10-12 07:18:26 +02:00
return ids . Values ( )
2019-11-12 16:33:34 +08:00
}
// LoadComments loads comments from database
2022-11-19 09:12:33 +01:00
func ( nl NotificationList ) LoadComments ( ctx context . Context ) ( [ ] int , error ) {
2019-11-12 16:33:34 +08:00
if len ( nl ) == 0 {
2020-03-29 20:51:14 +01:00
return [ ] int { } , nil
2019-11-12 16:33:34 +08:00
}
2021-03-15 02:52:12 +08:00
commentIDs := nl . getPendingCommentIDs ( )
2022-06-13 17:37:59 +08:00
comments := make ( map [ int64 ] * issues_model . Comment , len ( commentIDs ) )
2021-03-15 02:52:12 +08:00
left := len ( commentIDs )
2019-11-12 16:33:34 +08:00
for left > 0 {
2022-06-13 17:37:59 +08:00
limit := db . DefaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2022-11-19 09:12:33 +01:00
rows , err := db . GetEngine ( ctx ) .
2019-11-12 16:33:34 +08:00
In ( "id" , commentIDs [ : limit ] ) .
2022-06-13 17:37:59 +08:00
Rows ( new ( issues_model . Comment ) )
2019-11-12 16:33:34 +08:00
if err != nil {
2020-03-29 20:51:14 +01:00
return nil , err
2019-11-12 16:33:34 +08:00
}
for rows . Next ( ) {
2022-06-13 17:37:59 +08:00
var comment issues_model . Comment
2019-11-12 16:33:34 +08:00
err = rows . Scan ( & comment )
if err != nil {
rows . Close ( )
2020-03-29 20:51:14 +01:00
return nil , err
2019-11-12 16:33:34 +08:00
}
comments [ comment . ID ] = & comment
}
_ = rows . Close ( )
left -= limit
commentIDs = commentIDs [ limit : ]
}
2020-03-29 20:51:14 +01:00
failures := [ ] int { }
for i , notification := range nl {
2019-12-13 05:08:34 +03:00
if notification . CommentID > 0 && notification . Comment == nil && comments [ notification . CommentID ] != nil {
2019-11-12 16:33:34 +08:00
notification . Comment = comments [ notification . CommentID ]
2020-03-29 20:51:14 +01:00
if notification . Comment == nil {
log . Error ( "Notification[%d]: CommentID[%d] failed to load" , notification . ID , notification . CommentID )
failures = append ( failures , i )
continue
}
2019-11-12 16:33:34 +08:00
notification . Comment . Issue = notification . Issue
}
}
2020-03-29 20:51:14 +01:00
return failures , nil
2019-11-12 16:33:34 +08:00
}
2016-12-30 14:44:54 -02:00
// GetNotificationCount returns the notification count for user
2022-05-20 22:08:52 +08:00
func GetNotificationCount ( ctx context . Context , user * user_model . User , status NotificationStatus ) ( count int64 , err error ) {
count , err = db . GetEngine ( ctx ) .
2016-12-30 14:44:54 -02:00
Where ( "user_id = ?" , user . ID ) .
And ( "status = ?" , status ) .
Count ( & Notification { } )
2022-06-20 12:02:49 +02:00
return count , err
2016-12-30 14:44:54 -02:00
}
2020-05-07 22:49:00 +01:00
// UserIDCount is a simple coalition of UserID and Count
type UserIDCount struct {
UserID int64
Count int64
}
// GetUIDsAndNotificationCounts between the two provided times
func GetUIDsAndNotificationCounts ( since , until timeutil . TimeStamp ) ( [ ] UserIDCount , error ) {
sql := ` SELECT user_id, count(*) AS count FROM notification ` +
` WHERE user_id IN (SELECT user_id FROM notification WHERE updated_unix >= ? AND ` +
` updated_unix < ?) AND status = ? GROUP BY user_id `
var res [ ] UserIDCount
2021-09-23 16:45:36 +01:00
return res , db . GetEngine ( db . DefaultContext ) . SQL ( sql , since , until , NotificationStatusUnread ) . Find ( & res )
2020-05-07 22:49:00 +01:00
}
2022-06-13 17:37:59 +08:00
// SetIssueReadBy sets issue to be read by given user.
func SetIssueReadBy ( ctx context . Context , issueID , userID int64 ) error {
if err := issues_model . UpdateIssueUserByRead ( userID , issueID ) ; err != nil {
return err
}
return setIssueNotificationStatusReadIfUnread ( ctx , userID , issueID )
}
2022-05-20 22:08:52 +08:00
func setIssueNotificationStatusReadIfUnread ( ctx context . Context , userID , issueID int64 ) error {
notification , err := getIssueNotification ( ctx , userID , issueID )
2016-12-30 14:44:54 -02:00
// ignore if not exists
if err != nil {
return nil
}
2017-01-12 02:27:09 -02:00
if notification . Status != NotificationStatusUnread {
return nil
}
2016-12-30 14:44:54 -02:00
notification . Status = NotificationStatusRead
2022-05-20 22:08:52 +08:00
_ , err = db . GetEngine ( ctx ) . ID ( notification . ID ) . Update ( notification )
2016-12-30 14:44:54 -02:00
return err
}
2017-01-12 02:27:09 -02:00
2022-05-20 22:08:52 +08:00
// SetRepoReadBy sets repo to be visited by given user.
func SetRepoReadBy ( ctx context . Context , userID , repoID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Where ( builder . Eq {
2021-03-01 01:47:30 +01:00
"user_id" : userID ,
"status" : NotificationStatusUnread ,
"source" : NotificationSourceRepository ,
"repo_id" : repoID ,
} ) . Cols ( "status" ) . Update ( & Notification { Status : NotificationStatusRead } )
return err
}
2017-01-12 02:27:09 -02:00
// SetNotificationStatus change the notification status
2022-11-19 09:12:33 +01:00
func SetNotificationStatus ( ctx context . Context , notificationID int64 , user * user_model . User , status NotificationStatus ) ( * Notification , error ) {
notification , err := GetNotificationByID ( ctx , notificationID )
2017-01-12 02:27:09 -02:00
if err != nil {
2021-09-18 01:40:50 +02:00
return notification , err
2017-01-12 02:27:09 -02:00
}
if notification . UserID != user . ID {
2021-09-18 01:40:50 +02:00
return nil , fmt . Errorf ( "Can't change notification of another user: %d, %d" , notification . UserID , user . ID )
2017-01-12 02:27:09 -02:00
}
notification . Status = status
2022-11-19 09:12:33 +01:00
_ , err = db . GetEngine ( ctx ) . ID ( notificationID ) . Update ( notification )
2021-09-18 01:40:50 +02:00
return notification , err
2017-01-12 02:27:09 -02:00
}
2020-01-09 12:56:32 +01:00
// GetNotificationByID return notification by ID
2022-11-19 09:12:33 +01:00
func GetNotificationByID ( ctx context . Context , notificationID int64 ) ( * Notification , error ) {
2017-01-12 02:27:09 -02:00
notification := new ( Notification )
2022-05-20 22:08:52 +08:00
ok , err := db . GetEngine ( ctx ) .
2017-01-12 02:27:09 -02:00
Where ( "id = ?" , notificationID ) .
Get ( notification )
if err != nil {
return nil , err
}
if ! ok {
2022-10-18 06:50:37 +01:00
return nil , db . ErrNotExist { Resource : "notification" , ID : notificationID }
2017-01-12 02:27:09 -02:00
}
return notification , nil
}
2017-12-07 12:52:57 +07:00
// UpdateNotificationStatuses updates the statuses of all of a user's notifications that are of the currentStatus type to the desiredStatus
2022-11-19 09:12:33 +01:00
func UpdateNotificationStatuses ( ctx context . Context , user * user_model . User , currentStatus , desiredStatus NotificationStatus ) error {
2017-12-07 12:52:57 +07:00
n := & Notification { Status : desiredStatus , UpdatedBy : user . ID }
2022-11-19 09:12:33 +01:00
_ , err := db . GetEngine ( ctx ) .
2017-12-07 12:52:57 +07:00
Where ( "user_id = ? AND status = ?" , user . ID , currentStatus ) .
Cols ( "status" , "updated_by" , "updated_unix" ) .
Update ( n )
return err
}