|
|
|
@ -22,7 +22,6 @@ import (
|
|
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
|
|
|
|
|
|
"xorm.io/builder"
|
|
|
|
|
"xorm.io/xorm"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
@ -93,7 +92,7 @@ type FindNotificationOptions struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToCond will convert each condition into a xorm-Cond
|
|
|
|
|
func (opts *FindNotificationOptions) ToCond() builder.Cond {
|
|
|
|
|
func (opts FindNotificationOptions) ToConds() builder.Cond {
|
|
|
|
|
cond := builder.NewCond()
|
|
|
|
|
if opts.UserID != 0 {
|
|
|
|
|
cond = cond.And(builder.Eq{"notification.user_id": opts.UserID})
|
|
|
|
@ -105,7 +104,11 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
|
|
|
|
|
cond = cond.And(builder.Eq{"notification.issue_id": opts.IssueID})
|
|
|
|
|
}
|
|
|
|
|
if len(opts.Status) > 0 {
|
|
|
|
|
cond = cond.And(builder.In("notification.status", opts.Status))
|
|
|
|
|
if len(opts.Status) == 1 {
|
|
|
|
|
cond = cond.And(builder.Eq{"notification.status": opts.Status[0]})
|
|
|
|
|
} else {
|
|
|
|
|
cond = cond.And(builder.In("notification.status", opts.Status))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(opts.Source) > 0 {
|
|
|
|
|
cond = cond.And(builder.In("notification.source", opts.Source))
|
|
|
|
@ -119,24 +122,8 @@ func (opts *FindNotificationOptions) ToCond() builder.Cond {
|
|
|
|
|
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
|
|
|
|
|
func (opts *FindNotificationOptions) ToSession(ctx context.Context) *xorm.Session {
|
|
|
|
|
sess := db.GetEngine(ctx).Where(opts.ToCond())
|
|
|
|
|
if opts.Page != 0 {
|
|
|
|
|
sess = db.SetSessionPagination(sess, opts)
|
|
|
|
|
}
|
|
|
|
|
return sess
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetNotifications returns all notifications that fit to the given options.
|
|
|
|
|
func GetNotifications(ctx context.Context, options *FindNotificationOptions) (nl NotificationList, err error) {
|
|
|
|
|
err = options.ToSession(ctx).OrderBy("notification.updated_unix DESC").Find(&nl)
|
|
|
|
|
return nl, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CountNotifications count all notifications that fit to the given options and ignore pagination.
|
|
|
|
|
func CountNotifications(ctx context.Context, opts *FindNotificationOptions) (int64, error) {
|
|
|
|
|
return db.GetEngine(ctx).Where(opts.ToCond()).Count(&Notification{})
|
|
|
|
|
func (opts FindNotificationOptions) ToOrders() string {
|
|
|
|
|
return "notification.updated_unix DESC"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateRepoTransferNotification creates notification for the user a repository was transferred to
|
|
|
|
@ -192,7 +179,9 @@ func CreateOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
|
|
|
|
|
func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, notificationAuthorID, receiverID int64) error {
|
|
|
|
|
// init
|
|
|
|
|
var toNotify container.Set[int64]
|
|
|
|
|
notifications, err := getNotificationsByIssueID(ctx, issueID)
|
|
|
|
|
notifications, err := db.Find[Notification](ctx, FindNotificationOptions{
|
|
|
|
|
IssueID: issueID,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@ -273,23 +262,6 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getNotificationsByIssueID(ctx context.Context, issueID int64) (notifications []*Notification, err error) {
|
|
|
|
|
err = db.GetEngine(ctx).
|
|
|
|
|
Where("issue_id = ?", issueID).
|
|
|
|
|
Find(¬ifications)
|
|
|
|
|
return notifications, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(ctx context.Context, userID int64, issue *issues_model.Issue, commentID, updatedByID int64) error {
|
|
|
|
|
notification := &Notification{
|
|
|
|
|
UserID: userID,
|
|
|
|
@ -341,35 +313,6 @@ func GetIssueNotification(ctx context.Context, userID, issueID int64) (*Notifica
|
|
|
|
|
return notification, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NotificationsForUser returns notifications for a given user and status
|
|
|
|
|
func NotificationsForUser(ctx context.Context, user *user_model.User, statuses []NotificationStatus, page, perPage int) (notifications NotificationList, err error) {
|
|
|
|
|
if len(statuses) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sess := db.GetEngine(ctx).
|
|
|
|
|
Where("user_id = ?", user.ID).
|
|
|
|
|
In("status", statuses).
|
|
|
|
|
OrderBy("updated_unix DESC")
|
|
|
|
|
|
|
|
|
|
if page > 0 && perPage > 0 {
|
|
|
|
|
sess.Limit(perPage, (page-1)*perPage)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sess.Find(¬ifications)
|
|
|
|
|
return notifications, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CountUnread count unread notifications for a user
|
|
|
|
|
func CountUnread(ctx context.Context, userID int64) int64 {
|
|
|
|
|
exist, err := db.GetEngine(ctx).Where("user_id = ?", userID).And("status = ?", NotificationStatusUnread).Count(new(Notification))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error("countUnread", err)
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
return exist
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadAttributes load Repo Issue User and Comment if not loaded
|
|
|
|
|
func (n *Notification) LoadAttributes(ctx context.Context) (err error) {
|
|
|
|
|
if err = n.loadRepo(ctx); err != nil {
|
|
|
|
@ -481,17 +424,47 @@ func (n *Notification) APIURL() string {
|
|
|
|
|
return setting.AppURL + "api/v1/notifications/threads/" + strconv.FormatInt(n.ID, 10)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func notificationExists(notifications []*Notification, issueID, userID int64) bool {
|
|
|
|
|
for _, notification := range notifications {
|
|
|
|
|
if notification.IssueID == issueID && notification.UserID == userID {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UserIDCount is a simple coalition of UserID and Count
|
|
|
|
|
type UserIDCount struct {
|
|
|
|
|
UserID int64
|
|
|
|
|
Count int64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetUIDsAndNotificationCounts between the two provided times
|
|
|
|
|
func GetUIDsAndNotificationCounts(ctx context.Context, 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
|
|
|
|
|
return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NotificationList contains a list of notifications
|
|
|
|
|
type NotificationList []*Notification
|
|
|
|
|
|
|
|
|
|
// LoadAttributes load Repo Issue User and Comment if not loaded
|
|
|
|
|
func (nl NotificationList) LoadAttributes(ctx context.Context) error {
|
|
|
|
|
var err error
|
|
|
|
|
for i := 0; i < len(nl); i++ {
|
|
|
|
|
err = nl[i].LoadAttributes(ctx)
|
|
|
|
|
if err != nil && !issues_model.IsErrCommentNotExist(err) {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if _, _, err := nl.LoadRepos(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if _, err := nl.LoadIssues(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if _, err := nl.LoadUsers(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if _, err := nl.LoadComments(ctx); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
@ -665,6 +638,68 @@ func (nl NotificationList) getPendingCommentIDs() []int64 {
|
|
|
|
|
return ids.Values()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (nl NotificationList) getUserIDs() []int64 {
|
|
|
|
|
ids := make(container.Set[int64], len(nl))
|
|
|
|
|
for _, notification := range nl {
|
|
|
|
|
if notification.UserID == 0 || notification.User != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ids.Add(notification.UserID)
|
|
|
|
|
}
|
|
|
|
|
return ids.Values()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadUsers loads users from database
|
|
|
|
|
func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) {
|
|
|
|
|
if len(nl) == 0 {
|
|
|
|
|
return []int{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userIDs := nl.getUserIDs()
|
|
|
|
|
users := make(map[int64]*user_model.User, len(userIDs))
|
|
|
|
|
left := len(userIDs)
|
|
|
|
|
for left > 0 {
|
|
|
|
|
limit := db.DefaultMaxInSize
|
|
|
|
|
if left < limit {
|
|
|
|
|
limit = left
|
|
|
|
|
}
|
|
|
|
|
rows, err := db.GetEngine(ctx).
|
|
|
|
|
In("id", userIDs[:limit]).
|
|
|
|
|
Rows(new(user_model.User))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var user user_model.User
|
|
|
|
|
err = rows.Scan(&user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
rows.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
users[user.ID] = &user
|
|
|
|
|
}
|
|
|
|
|
_ = rows.Close()
|
|
|
|
|
|
|
|
|
|
left -= limit
|
|
|
|
|
userIDs = userIDs[limit:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
failures := []int{}
|
|
|
|
|
for i, notification := range nl {
|
|
|
|
|
if notification.UserID > 0 && notification.User == nil && users[notification.UserID] != nil {
|
|
|
|
|
notification.User = users[notification.UserID]
|
|
|
|
|
if notification.User == nil {
|
|
|
|
|
log.Error("Notification[%d]: UserID[%d] failed to load", notification.ID, notification.UserID)
|
|
|
|
|
failures = append(failures, i)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return failures, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LoadComments loads comments from database
|
|
|
|
|
func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
|
|
|
|
if len(nl) == 0 {
|
|
|
|
@ -717,30 +752,6 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) {
|
|
|
|
|
return failures, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetNotificationCount returns the notification count for user
|
|
|
|
|
func GetNotificationCount(ctx context.Context, user *user_model.User, status NotificationStatus) (count int64, err error) {
|
|
|
|
|
count, err = db.GetEngine(ctx).
|
|
|
|
|
Where("user_id = ?", user.ID).
|
|
|
|
|
And("status = ?", status).
|
|
|
|
|
Count(&Notification{})
|
|
|
|
|
return count, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UserIDCount is a simple coalition of UserID and Count
|
|
|
|
|
type UserIDCount struct {
|
|
|
|
|
UserID int64
|
|
|
|
|
Count int64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetUIDsAndNotificationCounts between the two provided times
|
|
|
|
|
func GetUIDsAndNotificationCounts(ctx context.Context, 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
|
|
|
|
|
return res, db.GetEngine(ctx).SQL(sql, since, until, NotificationStatusUnread).Find(&res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetIssueReadBy sets issue to be read by given user.
|
|
|
|
|
func SetIssueReadBy(ctx context.Context, issueID, userID int64) error {
|
|
|
|
|
if err := issues_model.UpdateIssueUserByRead(ctx, userID, issueID); err != nil {
|
|
|
|
|