Send mentioned notifications on edit but only when the user hasn't been mentioned previously
This commit is contained in:
parent
6243185624
commit
04e0adc55b
43
pkg/migration/20210729142940.go
Normal file
43
pkg/migration/20210729142940.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type notifications20210729142940 struct {
|
||||
SubjectID int64 `xorm:"bigint null" json:"-"`
|
||||
}
|
||||
|
||||
func (notifications20210729142940) TableName() string {
|
||||
return "notifications"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20210729142940",
|
||||
Description: "Add subject id to notification",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(notifications20210729142940{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
@ -82,6 +82,18 @@ func (t *TaskCommentCreatedEvent) Name() string {
|
||||
return "task.comment.created"
|
||||
}
|
||||
|
||||
// TaskCommentUpdatedEvent represents a TaskCommentUpdatedEvent event
|
||||
type TaskCommentUpdatedEvent struct {
|
||||
Task *Task
|
||||
Comment *TaskComment
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskCommentUpdatedEvent
|
||||
func (t *TaskCommentUpdatedEvent) Name() string {
|
||||
return "task.comment.edited"
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Namespace Events //
|
||||
//////////////////////
|
||||
|
@ -17,7 +17,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"encoding/json"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/events"
|
||||
@ -44,6 +46,7 @@ func RegisterListeners() {
|
||||
events.RegisterListener((&ListCreatedEvent{}).Name(), &SendListCreatedNotification{})
|
||||
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SubscribeAssigneeToTask{})
|
||||
events.RegisterListener((&TeamMemberAddedEvent{}).Name(), &SendTeamMemberAddedNotification{})
|
||||
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{})
|
||||
}
|
||||
|
||||
//////
|
||||
@ -77,6 +80,48 @@ func (s *DecreaseTaskCounter) Handle(msg *message.Message) (err error) {
|
||||
return keyvalue.DecrBy(metrics.TaskCountKey, 1)
|
||||
}
|
||||
|
||||
func notifyMentionedUsers(sess *xorm.Session, task *Task, comment *TaskComment, doer *user.User) (users map[int64]*user.User, err error) {
|
||||
users, err = FindMentionedUsersInText(sess, comment.Comment)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
outer:
|
||||
for _, u := range users {
|
||||
can, _, err := task.CanRead(sess, u)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
|
||||
if !can {
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't notify a user if they were already notified
|
||||
dbn, err := notifications.GetNotificationsForEventAndUser(sess, u.ID, (&TaskCommentNotification{}).Name(), comment.ID)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
|
||||
if len(dbn) > 0 {
|
||||
continue outer
|
||||
}
|
||||
|
||||
n := &TaskCommentNotification{
|
||||
Doer: doer,
|
||||
Task: task,
|
||||
Comment: comment,
|
||||
Mentioned: true,
|
||||
}
|
||||
err = notifications.Notify(u, n)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SendTaskCommentNotification represents a listener
|
||||
type SendTaskCommentNotification struct {
|
||||
}
|
||||
@ -97,33 +142,11 @@ func (s *SendTaskCommentNotification) Handle(msg *message.Message) (err error) {
|
||||
sess := db.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
mentionedUsers, err := FindMentionedUsersInText(sess, event.Comment.Comment)
|
||||
mentionedUsers, err := notifyMentionedUsers(sess, event.Task, event.Comment, event.Doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range mentionedUsers {
|
||||
can, _, err := event.Task.CanRead(sess, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !can {
|
||||
continue
|
||||
}
|
||||
|
||||
n := &TaskCommentNotification{
|
||||
Doer: event.Doer,
|
||||
Task: event.Task,
|
||||
Comment: event.Comment,
|
||||
Mentioned: true,
|
||||
}
|
||||
err = notifications.Notify(user, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityTask, event.Task.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -154,6 +177,30 @@ func (s *SendTaskCommentNotification) Handle(msg *message.Message) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// HandleTaskCommentEditMentions represents a listener
|
||||
type HandleTaskCommentEditMentions struct {
|
||||
}
|
||||
|
||||
// Name defines the name for the HandleTaskCommentEditMentions listener
|
||||
func (s *HandleTaskCommentEditMentions) Name() string {
|
||||
return "handle.task.comment.edit.mentions"
|
||||
}
|
||||
|
||||
// Handle is executed when the event HandleTaskCommentEditMentions listens on is fired
|
||||
func (s *HandleTaskCommentEditMentions) Handle(msg *message.Message) (err error) {
|
||||
event := &TaskCommentUpdatedEvent{}
|
||||
err = json.Unmarshal(msg.Payload, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := db.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
_, err = notifyMentionedUsers(sess, event.Task, event.Comment, event.Doer)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendTaskAssignedNotification represents a listener
|
||||
type SendTaskAssignedNotification struct {
|
||||
}
|
||||
|
@ -64,6 +64,10 @@ type TaskCommentNotification struct {
|
||||
Mentioned bool `json:"mentioned"`
|
||||
}
|
||||
|
||||
func (n *TaskCommentNotification) SubjectID() int64 {
|
||||
return n.Comment.ID
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for TaskCommentNotification
|
||||
func (n *TaskCommentNotification) ToMail() *notifications.Mail {
|
||||
|
||||
|
@ -132,7 +132,21 @@ func (tc *TaskComment) Update(s *xorm.Session, a web.Auth) error {
|
||||
if updated == 0 {
|
||||
return ErrTaskCommentDoesNotExist{ID: tc.ID}
|
||||
}
|
||||
return err
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := GetTaskSimple(s, &Task{ID: tc.TaskID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return events.Dispatch(&TaskCommentUpdatedEvent{
|
||||
Task: &task,
|
||||
Comment: tc,
|
||||
Doer: tc.Author,
|
||||
})
|
||||
}
|
||||
|
||||
// ReadOne handles getting a single comment
|
||||
|
@ -33,6 +33,8 @@ type DatabaseNotification struct {
|
||||
Notification interface{} `xorm:"json not null" json:"notification"`
|
||||
// The name of the notification
|
||||
Name string `xorm:"varchar(250) index not null" json:"name"`
|
||||
// The thing the notification is about. Used to check if a notification for this thing already happend or not.
|
||||
SubjectID int64 `xorm:"bigint null" json:"-"`
|
||||
|
||||
// When this notification is marked as read, this will be updated with the current timestamp.
|
||||
ReadAt time.Time `xorm:"datetime null" json:"read_at"`
|
||||
@ -65,6 +67,13 @@ func GetNotificationsForUser(s *xorm.Session, notifiableID int64, limit, start i
|
||||
return notifications, len(notifications), total, err
|
||||
}
|
||||
|
||||
func GetNotificationsForEventAndUser(s *xorm.Session, notifiableID int64, event string, subjectID int64) (notifications []*DatabaseNotification, err error) {
|
||||
notifications = []*DatabaseNotification{}
|
||||
err = s.Where("notifiable_id = ? AND name = ? AND subject_id = ?", notifiableID, event, subjectID).
|
||||
Find(¬ifications)
|
||||
return
|
||||
}
|
||||
|
||||
// CanMarkNotificationAsRead checks if a user can mark a notification as read.
|
||||
func CanMarkNotificationAsRead(s *xorm.Session, notification *DatabaseNotification, notifiableID int64) (can bool, err error) {
|
||||
can, err = s.
|
||||
|
@ -29,6 +29,10 @@ type Notification interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
type SubjectID interface {
|
||||
SubjectID() int64
|
||||
}
|
||||
|
||||
// Notifiable is an entity which can be notified. Usually a user.
|
||||
type Notifiable interface {
|
||||
// Should return the email address this notifiable has.
|
||||
@ -82,6 +86,10 @@ func notifyDB(notifiable Notifiable, notification Notification) (err error) {
|
||||
Name: notification.Name(),
|
||||
}
|
||||
|
||||
if subject, is := notification.(SubjectID); is {
|
||||
dbNotification.SubjectID = subject.SubjectID()
|
||||
}
|
||||
|
||||
_, err = s.Insert(dbNotification)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
|
Loading…
x
Reference in New Issue
Block a user