2016-12-30 19:44:54 +03: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 (
2017-01-12 07:27:09 +03:00
"fmt"
2017-12-11 07:37:04 +03:00
"code.gitea.io/gitea/modules/util"
2016-12-30 19:44:54 +03: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 07:27:09 +03:00
// NotificationStatusPinned represents a pinned notification
NotificationStatusPinned
2016-12-30 19:44:54 +03: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
)
// 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" `
IssueID int64 ` xorm:"INDEX NOT NULL" `
CommitID string ` xorm:"INDEX" `
UpdatedBy int64 ` xorm:"INDEX NOT NULL" `
Issue * Issue ` xorm:"-" `
Repository * Repository ` xorm:"-" `
2017-12-11 07:37:04 +03:00
CreatedUnix util . TimeStamp ` xorm:"created INDEX NOT NULL" `
UpdatedUnix util . TimeStamp ` xorm:"updated INDEX NOT NULL" `
2016-12-30 19:44:54 +03:00
}
// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
func CreateOrUpdateIssueNotifications ( issue * Issue , notificationAuthorID int64 ) error {
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
if err := createOrUpdateIssueNotifications ( sess , issue , notificationAuthorID ) ; err != nil {
return err
}
return sess . Commit ( )
}
func createOrUpdateIssueNotifications ( e Engine , issue * Issue , notificationAuthorID int64 ) error {
2017-03-30 02:54:57 +03:00
issueWatches , err := getIssueWatchers ( e , issue . ID )
if err != nil {
return err
}
2016-12-30 19:44:54 +03:00
watches , err := getWatchers ( e , issue . RepoID )
if err != nil {
return err
}
notifications , err := getNotificationsByIssueID ( e , issue . ID )
if err != nil {
return err
}
2017-03-30 02:54:57 +03:00
alreadyNotified := make ( map [ int64 ] struct { } , len ( issueWatches ) + len ( watches ) )
notifyUser := func ( userID int64 ) error {
2016-12-30 19:44:54 +03:00
// do not send notification for the own issuer/commenter
2017-03-30 02:54:57 +03:00
if userID == notificationAuthorID {
return nil
2016-12-30 19:44:54 +03:00
}
2017-03-30 02:54:57 +03:00
if _ , ok := alreadyNotified [ userID ] ; ok {
return nil
2016-12-30 19:44:54 +03:00
}
2017-03-30 02:54:57 +03:00
alreadyNotified [ userID ] = struct { } { }
2016-12-30 19:44:54 +03:00
2017-03-30 02:54:57 +03:00
if notificationExists ( notifications , issue . ID , userID ) {
return updateIssueNotification ( e , userID , issue . ID , notificationAuthorID )
}
return createIssueNotification ( e , userID , issue , notificationAuthorID )
}
for _ , issueWatch := range issueWatches {
// ignore if user unwatched the issue
if ! issueWatch . IsWatching {
alreadyNotified [ issueWatch . UserID ] = struct { } { }
continue
}
if err := notifyUser ( issueWatch . UserID ) ; err != nil {
2016-12-30 19:44:54 +03:00
return err
}
}
2019-06-12 22:41:28 +03:00
err = issue . loadRepo ( e )
if err != nil {
return err
}
2018-06-21 19:00:13 +03:00
2017-03-30 02:54:57 +03:00
for _ , watch := range watches {
2018-06-21 19:00:13 +03:00
issue . Repo . Units = nil
2018-10-25 13:55:16 +03:00
if issue . IsPull && ! issue . Repo . checkUnitUser ( e , watch . UserID , false , UnitTypePullRequests ) {
2018-06-21 19:00:13 +03:00
continue
}
2018-10-25 13:55:16 +03:00
if ! issue . IsPull && ! issue . Repo . checkUnitUser ( e , watch . UserID , false , UnitTypeIssues ) {
2018-06-21 19:00:13 +03:00
continue
}
2017-03-30 02:54:57 +03:00
if err := notifyUser ( watch . UserID ) ; err != nil {
return err
}
}
2016-12-30 19:44:54 +03:00
return nil
}
func getNotificationsByIssueID ( e Engine , issueID int64 ) ( notifications [ ] * Notification , err error ) {
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
}
func createIssueNotification ( e Engine , userID int64 , issue * Issue , updatedByID int64 ) error {
notification := & Notification {
UserID : userID ,
RepoID : issue . RepoID ,
Status : NotificationStatusUnread ,
IssueID : issue . ID ,
UpdatedBy : updatedByID ,
}
if issue . IsPull {
notification . Source = NotificationSourcePullRequest
} else {
notification . Source = NotificationSourceIssue
}
_ , err := e . Insert ( notification )
return err
}
func updateIssueNotification ( e Engine , userID , issueID , updatedByID int64 ) error {
notification , err := getIssueNotification ( e , userID , issueID )
if err != nil {
return err
}
notification . Status = NotificationStatusUnread
notification . UpdatedBy = updatedByID
2017-10-05 07:43:04 +03:00
_ , err = e . ID ( notification . ID ) . Update ( notification )
2016-12-30 19:44:54 +03:00
return err
}
func getIssueNotification ( e Engine , userID , issueID int64 ) ( * Notification , error ) {
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
2017-01-12 07:27:09 +03:00
func NotificationsForUser ( user * User , statuses [ ] NotificationStatus , page , perPage int ) ( [ ] * Notification , error ) {
return notificationsForUser ( x , user , statuses , page , perPage )
2016-12-30 19:44:54 +03:00
}
2017-12-11 07:37:04 +03:00
2017-01-12 07:27:09 +03:00
func notificationsForUser ( e Engine , user * User , statuses [ ] NotificationStatus , page , perPage int ) ( notifications [ ] * Notification , err error ) {
2017-02-16 07:07:00 +03:00
if len ( statuses ) == 0 {
return
2017-01-12 07:27:09 +03:00
}
2017-01-03 22:09:36 +03:00
sess := e .
2016-12-30 19:44:54 +03:00
Where ( "user_id = ?" , user . ID ) .
2017-02-16 07:07:00 +03:00
In ( "status" , statuses ) .
2017-01-03 22:09:36 +03:00
OrderBy ( "updated_unix DESC" )
if page > 0 && perPage > 0 {
sess . Limit ( perPage , ( page - 1 ) * perPage )
}
2017-02-16 07:07:00 +03:00
err = sess . Find ( & notifications )
2016-12-30 19:44:54 +03:00
return
}
// GetRepo returns the repo of the notification
func ( n * Notification ) GetRepo ( ) ( * Repository , error ) {
n . Repository = new ( Repository )
_ , err := x .
Where ( "id = ?" , n . RepoID ) .
Get ( n . Repository )
return n . Repository , err
}
// GetIssue returns the issue of the notification
func ( n * Notification ) GetIssue ( ) ( * Issue , error ) {
n . Issue = new ( Issue )
_ , err := x .
Where ( "id = ?" , n . IssueID ) .
Get ( n . Issue )
return n . Issue , err
}
// GetNotificationCount returns the notification count for user
func GetNotificationCount ( user * User , status NotificationStatus ) ( int64 , error ) {
return getNotificationCount ( x , user , status )
}
func getNotificationCount ( e Engine , user * User , status NotificationStatus ) ( count int64 , err error ) {
count , err = e .
Where ( "user_id = ?" , user . ID ) .
And ( "status = ?" , status ) .
Count ( & Notification { } )
return
}
2017-01-12 07:27:09 +03:00
func setNotificationStatusReadIfUnread ( e Engine , userID , issueID int64 ) error {
2016-12-30 19:44:54 +03:00
notification , err := getIssueNotification ( e , userID , issueID )
// ignore if not exists
if err != nil {
return nil
}
2017-01-12 07:27:09 +03:00
if notification . Status != NotificationStatusUnread {
return nil
}
2016-12-30 19:44:54 +03:00
notification . Status = NotificationStatusRead
2017-10-05 07:43:04 +03:00
_ , err = e . ID ( notification . ID ) . Update ( notification )
2016-12-30 19:44:54 +03:00
return err
}
2017-01-12 07:27:09 +03:00
// SetNotificationStatus change the notification status
func SetNotificationStatus ( notificationID int64 , user * User , status NotificationStatus ) error {
notification , err := getNotificationByID ( notificationID )
if err != nil {
return err
}
if notification . UserID != user . ID {
return fmt . Errorf ( "Can't change notification of another user: %d, %d" , notification . UserID , user . ID )
}
notification . Status = status
2017-10-05 07:43:04 +03:00
_ , err = x . ID ( notificationID ) . Update ( notification )
2017-01-12 07:27:09 +03:00
return err
}
func getNotificationByID ( notificationID int64 ) ( * Notification , error ) {
notification := new ( Notification )
ok , err := x .
Where ( "id = ?" , notificationID ) .
Get ( notification )
if err != nil {
return nil , err
}
if ! ok {
return nil , fmt . Errorf ( "Notification %d does not exists" , notificationID )
}
return notification , nil
}
2017-12-07 08:52:57 +03:00
// UpdateNotificationStatuses updates the statuses of all of a user's notifications that are of the currentStatus type to the desiredStatus
func UpdateNotificationStatuses ( user * User , currentStatus NotificationStatus , desiredStatus NotificationStatus ) error {
n := & Notification { Status : desiredStatus , UpdatedBy : user . ID }
_ , err := x .
Where ( "user_id = ? AND status = ?" , user . ID , currentStatus ) .
Cols ( "status" , "updated_by" , "updated_unix" ) .
Update ( n )
return err
}