2016-12-30 14:44:54 -02:00
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
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"
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"
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" `
2021-12-10 09:27:50 +08:00
Issue * Issue ` xorm:"-" `
Repository * repo_model . Repository ` xorm:"-" `
Comment * Comment ` xorm:"-" `
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
2021-09-19 19:49:59 +08:00
func ( opts * FindNotificationOptions ) ToSession ( e db . Engine ) * xorm . Session {
2020-04-21 23:21:46 +02:00
sess := e . Where ( opts . ToCond ( ) )
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
}
2021-09-19 19:49:59 +08:00
func getNotifications ( e db . Engine , options * FindNotificationOptions ) ( nl NotificationList , err error ) {
2020-01-09 12:56:32 +01:00
err = options . ToSession ( e ) . OrderBy ( "notification.updated_unix DESC" ) . Find ( & nl )
return
}
// GetNotifications returns all notifications that fit to the given options.
2021-06-16 19:04:37 +02:00
func GetNotifications ( opts * FindNotificationOptions ) ( NotificationList , error ) {
2021-09-23 16:45:36 +01:00
return getNotifications ( db . GetEngine ( db . DefaultContext ) , opts )
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.
func CountNotifications ( opts * FindNotificationOptions ) ( int64 , error ) {
2021-09-23 16:45:36 +01:00
return db . GetEngine ( db . DefaultContext ) . 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
2021-12-10 09:27:50 +08:00
func CreateRepoTransferNotification ( doer , newOwner * user_model . User , repo * repo_model . Repository ) error {
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2021-03-01 01:47:30 +01:00
return err
}
2021-11-21 23:41:00 +08:00
defer committer . Close ( )
2021-03-01 01:47:30 +01:00
var notify [ ] * Notification
if newOwner . IsOrganization ( ) {
2021-11-21 23:41:00 +08:00
users , err := getUsersWhoCanCreateOrgRepo ( db . GetEngine ( ctx ) , newOwner . ID )
2021-03-01 01:47:30 +01:00
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 ,
RepoID : repo . ID ,
Status : NotificationStatusUnread ,
UpdatedBy : doer . ID ,
Source : NotificationSourceRepository ,
} }
}
2021-11-21 23:41:00 +08:00
if err := db . Insert ( ctx , notify ) ; err != nil {
2021-03-01 01:47:30 +01:00
return err
}
2021-11-21 23:41:00 +08:00
return committer . Commit ( )
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 {
2021-11-21 23:41:00 +08:00
ctx , committer , err := db . TxContext ( )
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 {
e := db . GetEngine ( ctx )
2020-02-26 07:32:22 +01:00
// init
2020-04-07 00:33:34 +08:00
var toNotify map [ int64 ] struct { }
2020-02-26 07:32:22 +01:00
notifications , err := getNotificationsByIssueID ( e , issueID )
2019-11-12 16:33:34 +08:00
if err != nil {
return err
}
2020-04-07 00:33:34 +08:00
2019-11-12 16:33:34 +08:00
issue , err := getIssueByID ( e , 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 {
toNotify = make ( map [ int64 ] struct { } , 1 )
toNotify [ receiverID ] = struct { } { }
} else {
toNotify = make ( map [ int64 ] struct { } , 32 )
issueWatches , err := getIssueWatchersIDs ( e , issueID , true )
if err != nil {
return err
}
for _ , id := range issueWatches {
toNotify [ id ] = struct { } { }
}
2021-06-23 06:14:22 +02:00
if ! ( issue . IsPull && 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
}
for _ , id := range repoWatches {
toNotify [ id ] = struct { } { }
}
2020-04-07 00:33:34 +08:00
}
issueParticipants , err := issue . getParticipantIDsByIssue ( e )
if err != nil {
return err
}
for _ , id := range issueParticipants {
toNotify [ id ] = struct { } { }
}
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
issueUnWatches , err := getIssueWatchersIDs ( e , issueID , false )
if err != nil {
return err
}
for _ , id := range issueUnWatches {
delete ( toNotify , id )
}
2016-12-30 14:44:54 -02:00
}
2021-12-10 09:27:50 +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
2021-11-24 17:49:20 +08:00
user , err := user_model . GetUserByIDEngine ( e , 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
}
2021-12-10 09:27:50 +08:00
if issue . IsPull && ! checkRepoUnitUser ( ctx , issue . Repo , user , unit . TypePullRequests ) {
2018-06-21 18:00:13 +02:00
continue
}
2021-12-10 09:27:50 +08:00
if ! issue . IsPull && ! 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 ) {
if err = updateIssueNotification ( e , userID , issue . ID , commentID , notificationAuthorID ) ; err != nil {
return err
}
continue
}
if err = createIssueNotification ( e , 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
}
2021-09-19 19:49:59 +08:00
func getNotificationsByIssueID ( e db . Engine , issueID int64 ) ( notifications [ ] * Notification , err error ) {
2016-12-30 14:44:54 -02:00
err = e .
Where ( "issue_id = ?" , issueID ) .
Find ( & notifications )
return
}
func notificationExists ( notifications [ ] * Notification , issueID , userID int64 ) bool {
for _ , notification := range notifications {
if notification . IssueID == issueID && notification . UserID == userID {
return true
}
}
return false
}
2021-09-19 19:49:59 +08:00
func createIssueNotification ( e db . Engine , userID int64 , issue * 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
}
_ , err := e . Insert ( notification )
return err
}
2021-09-19 19:49:59 +08:00
func updateIssueNotification ( e db . Engine , userID , issueID , commentID , updatedByID int64 ) error {
2016-12-30 14:44:54 -02:00
notification , err := getIssueNotification ( e , userID , issueID )
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
2019-11-12 16:33:34 +08:00
_ , err = e . ID ( notification . ID ) . Cols ( cols ... ) . Update ( notification )
2016-12-30 14:44:54 -02:00
return err
}
2021-09-19 19:49:59 +08:00
func getIssueNotification ( e db . Engine , userID , issueID int64 ) ( * Notification , error ) {
2016-12-30 14:44:54 -02:00
notification := new ( Notification )
_ , err := e .
Where ( "user_id = ?" , userID ) .
And ( "issue_id = ?" , issueID ) .
Get ( notification )
return notification , err
}
// NotificationsForUser returns notifications for a given user and status
2021-11-24 17:49:20 +08:00
func NotificationsForUser ( user * user_model . User , statuses [ ] NotificationStatus , page , perPage int ) ( NotificationList , error ) {
2021-09-23 16:45:36 +01:00
return notificationsForUser ( db . GetEngine ( db . DefaultContext ) , user , statuses , page , perPage )
2016-12-30 14:44:54 -02:00
}
2017-12-11 12:37:04 +08:00
2021-11-24 17:49:20 +08:00
func notificationsForUser ( e db . Engine , user * user_model . User , statuses [ ] NotificationStatus , page , perPage int ) ( notifications [ ] * Notification , err error ) {
2017-02-16 12:07:00 +08:00
if len ( statuses ) == 0 {
return
2017-01-12 02:27:09 -02:00
}
2017-01-03 17:09:36 -02:00
sess := e .
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 )
2016-12-30 14:44:54 -02:00
return
}
2020-01-14 16:37:19 +01:00
// CountUnread count unread notifications for a user
2021-11-24 17:49:20 +08:00
func CountUnread ( user * user_model . User ) int64 {
2021-09-23 16:45:36 +01:00
return countUnread ( db . GetEngine ( db . DefaultContext ) , user . ID )
2020-01-14 16:37:19 +01:00
}
2021-09-19 19:49:59 +08:00
func countUnread ( e db . Engine , userID int64 ) int64 {
2020-01-14 16:37:19 +01:00
exist , err := e . Where ( "user_id = ?" , userID ) . And ( "status = ?" , NotificationStatusUnread ) . Count ( new ( Notification ) )
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
func ( n * Notification ) LoadAttributes ( ) ( err error ) {
2021-11-19 21:39:57 +08:00
return n . loadAttributes ( db . DefaultContext )
2020-01-09 12:56:32 +01:00
}
2021-11-19 21:39:57 +08:00
func ( n * Notification ) loadAttributes ( ctx context . Context ) ( err error ) {
e := db . GetEngine ( ctx )
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
}
if err = n . loadUser ( e ) ; err != nil {
return
}
if err = n . loadComment ( e ) ; err != nil {
return
}
return
}
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 {
2021-12-10 09:27:50 +08:00
n . Repository , err = repo_model . GetRepositoryByIDCtx ( ctx , n . RepoID )
2020-01-09 12:56:32 +01:00
if err != nil {
return fmt . Errorf ( "getRepositoryByID [%d]: %v" , n . RepoID , err )
}
}
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 {
2021-11-19 21:39:57 +08:00
n . Issue , err = getIssueByID ( db . GetEngine ( ctx ) , n . IssueID )
2020-01-09 12:56:32 +01:00
if err != nil {
return fmt . Errorf ( "getIssueByID [%d]: %v" , n . IssueID , err )
}
2021-11-19 21:39:57 +08:00
return n . Issue . loadAttributes ( ctx )
2020-01-09 12:56:32 +01:00
}
return nil
}
2021-09-19 19:49:59 +08:00
func ( n * Notification ) loadComment ( e db . Engine ) ( err error ) {
2021-03-01 01:47:30 +01:00
if n . Comment == nil && n . CommentID != 0 {
2020-02-28 00:10:27 +01:00
n . Comment , err = getCommentByID ( e , n . CommentID )
2020-01-09 12:56:32 +01:00
if err != nil {
2021-11-10 13:48:45 +08:00
if IsErrCommentNotExist ( err ) {
return ErrCommentNotExist {
ID : n . CommentID ,
IssueID : n . IssueID ,
}
}
return err
2020-01-09 12:56:32 +01:00
}
}
return nil
}
2021-09-19 19:49:59 +08:00
func ( n * Notification ) loadUser ( e db . Engine ) ( err error ) {
2020-01-09 12:56:32 +01:00
if n . User == nil {
2021-11-24 17:49:20 +08:00
n . User , err = user_model . GetUserByIDEngine ( e , n . UserID )
2020-01-09 12:56:32 +01:00
if err != nil {
return fmt . Errorf ( "getUserByID [%d]: %v" , n . UserID , err )
}
}
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
func ( n * Notification ) GetIssue ( ) ( * 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-03-03 16:18:26 +01:00
func ( nl NotificationList ) LoadAttributes ( ) error {
var err error
2020-01-09 12:56:32 +01:00
for i := 0 ; i < len ( nl ) ; i ++ {
err = nl [ i ] . LoadAttributes ( )
2021-11-10 13:48:45 +08:00
if err != nil && ! 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 {
2021-03-15 02:52:12 +08:00
ids := make ( map [ int64 ] struct { } , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . Repository != nil {
continue
}
if _ , ok := ids [ notification . RepoID ] ; ! ok {
ids [ notification . RepoID ] = struct { } { }
}
}
return keysInt64 ( ids )
}
// LoadRepos loads repositories from database
2020-03-29 20:51:14 +01:00
func ( nl NotificationList ) LoadRepos ( ) ( RepositoryList , [ ] int , error ) {
2019-11-12 16:33:34 +08:00
if len ( nl ) == 0 {
2020-03-29 20:51:14 +01:00
return 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 {
2021-03-15 02:52:12 +08:00
limit := defaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2021-09-23 16:45:36 +01:00
rows , err := db . GetEngine ( db . DefaultContext ) .
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 { }
2021-03-15 02:52:12 +08:00
reposList := make ( 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 {
2021-03-15 02:52:12 +08:00
ids := make ( map [ int64 ] struct { } , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . Issue != nil {
continue
}
if _ , ok := ids [ notification . IssueID ] ; ! ok {
ids [ notification . IssueID ] = struct { } { }
}
}
return keysInt64 ( ids )
}
// LoadIssues loads issues from database
2020-03-29 20:51:14 +01:00
func ( nl NotificationList ) LoadIssues ( ) ( [ ] 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 ( )
issues := make ( map [ int64 ] * Issue , len ( issueIDs ) )
left := len ( issueIDs )
2019-11-12 16:33:34 +08:00
for left > 0 {
2021-03-15 02:52:12 +08:00
limit := defaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2021-09-23 16:45:36 +01:00
rows , err := db . GetEngine ( db . DefaultContext ) .
2019-11-12 16:33:34 +08:00
In ( "id" , issueIDs [ : limit ] ) .
Rows ( new ( Issue ) )
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 ( ) {
var issue Issue
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 {
2021-03-15 02:52:12 +08:00
ids := make ( map [ int64 ] struct { } , len ( nl ) )
2019-11-12 16:33:34 +08:00
for _ , notification := range nl {
if notification . CommentID == 0 || notification . Comment != nil {
continue
}
if _ , ok := ids [ notification . CommentID ] ; ! ok {
ids [ notification . CommentID ] = struct { } { }
}
}
return keysInt64 ( ids )
}
// LoadComments loads comments from database
2020-03-29 20:51:14 +01:00
func ( nl NotificationList ) LoadComments ( ) ( [ ] 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 ( )
comments := make ( map [ int64 ] * Comment , len ( commentIDs ) )
left := len ( commentIDs )
2019-11-12 16:33:34 +08:00
for left > 0 {
2021-03-15 02:52:12 +08:00
limit := defaultMaxInSize
2019-11-12 16:33:34 +08:00
if left < limit {
limit = left
}
2021-09-23 16:45:36 +01:00
rows , err := db . GetEngine ( db . DefaultContext ) .
2019-11-12 16:33:34 +08:00
In ( "id" , commentIDs [ : limit ] ) .
Rows ( new ( Comment ) )
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 ( ) {
var comment Comment
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
2021-11-24 17:49:20 +08:00
func GetNotificationCount ( user * user_model . User , status NotificationStatus ) ( int64 , error ) {
2021-09-23 16:45:36 +01:00
return getNotificationCount ( db . GetEngine ( db . DefaultContext ) , user , status )
2016-12-30 14:44:54 -02:00
}
2021-11-24 17:49:20 +08:00
func getNotificationCount ( e db . Engine , user * user_model . User , status NotificationStatus ) ( count int64 , err error ) {
2016-12-30 14:44:54 -02:00
count , err = e .
Where ( "user_id = ?" , user . ID ) .
And ( "status = ?" , status ) .
Count ( & Notification { } )
return
}
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
}
2021-09-19 19:49:59 +08:00
func setIssueNotificationStatusReadIfUnread ( e db . Engine , userID , issueID int64 ) error {
2016-12-30 14:44:54 -02:00
notification , err := getIssueNotification ( e , userID , issueID )
// 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
2017-10-04 21:43:04 -07:00
_ , err = e . ID ( notification . ID ) . Update ( notification )
2016-12-30 14:44:54 -02:00
return err
}
2017-01-12 02:27:09 -02:00
2021-09-19 19:49:59 +08:00
func setRepoNotificationStatusReadIfUnread ( e db . Engine , userID , repoID int64 ) error {
2021-03-01 01:47:30 +01:00
_ , err := e . Where ( builder . Eq {
"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
2021-11-24 17:49:20 +08:00
func SetNotificationStatus ( notificationID int64 , user * user_model . User , status NotificationStatus ) ( * Notification , error ) {
2021-09-23 16:45:36 +01:00
notification , err := getNotificationByID ( db . GetEngine ( db . DefaultContext ) , 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
2021-09-23 16:45:36 +01:00
_ , err = db . GetEngine ( db . DefaultContext ) . 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
func GetNotificationByID ( notificationID int64 ) ( * Notification , error ) {
2021-09-23 16:45:36 +01:00
return getNotificationByID ( db . GetEngine ( db . DefaultContext ) , notificationID )
2020-01-09 12:56:32 +01:00
}
2021-09-19 19:49:59 +08:00
func getNotificationByID ( e db . Engine , notificationID int64 ) ( * Notification , error ) {
2017-01-12 02:27:09 -02:00
notification := new ( Notification )
2020-01-09 12:56:32 +01:00
ok , err := e .
2017-01-12 02:27:09 -02:00
Where ( "id = ?" , notificationID ) .
Get ( notification )
if err != nil {
return nil , err
}
if ! ok {
2020-01-09 12:56:32 +01:00
return nil , ErrNotExist { 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
2021-11-24 17:49:20 +08:00
func UpdateNotificationStatuses ( user * user_model . User , currentStatus , desiredStatus NotificationStatus ) error {
2017-12-07 12:52:57 +07:00
n := & Notification { Status : desiredStatus , UpdatedBy : user . ID }
2021-09-23 16:45:36 +01:00
_ , err := db . GetEngine ( db . DefaultContext ) .
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
}