vikunja-api/pkg/models/listeners.go

565 lines
15 KiB
Go

// 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 models
import (
"encoding/json"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/metrics"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/api/pkg/notifications"
"code.vikunja.io/api/pkg/user"
"github.com/ThreeDotsLabs/watermill/message"
"xorm.io/xorm"
)
// RegisterListeners registers all event listeners
func RegisterListeners() {
events.RegisterListener((&ListCreatedEvent{}).Name(), &IncreaseListCounter{})
events.RegisterListener((&ListDeletedEvent{}).Name(), &DecreaseListCounter{})
events.RegisterListener((&NamespaceCreatedEvent{}).Name(), &IncreaseNamespaceCounter{})
events.RegisterListener((&NamespaceDeletedEvent{}).Name(), &DecreaseNamespaceCounter{})
events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{})
events.RegisterListener((&TaskDeletedEvent{}).Name(), &DecreaseTaskCounter{})
events.RegisterListener((&TeamDeletedEvent{}).Name(), &DecreaseTeamCounter{})
events.RegisterListener((&TeamCreatedEvent{}).Name(), &IncreaseTeamCounter{})
events.RegisterListener((&TaskCommentCreatedEvent{}).Name(), &SendTaskCommentNotification{})
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SendTaskAssignedNotification{})
events.RegisterListener((&TaskDeletedEvent{}).Name(), &SendTaskDeletedNotification{})
events.RegisterListener((&ListCreatedEvent{}).Name(), &SendListCreatedNotification{})
events.RegisterListener((&TaskAssigneeCreatedEvent{}).Name(), &SubscribeAssigneeToTask{})
events.RegisterListener((&TeamMemberAddedEvent{}).Name(), &SendTeamMemberAddedNotification{})
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{})
events.RegisterListener((&TaskCreatedEvent{}).Name(), &HandleTaskCreateMentions{})
events.RegisterListener((&TaskUpdatedEvent{}).Name(), &HandleTaskUpdatedMentions{})
}
//////
// Task Events
// IncreaseTaskCounter represents a listener
type IncreaseTaskCounter struct {
}
// Name defines the name for the IncreaseTaskCounter listener
func (s *IncreaseTaskCounter) Name() string {
return "task.counter.increase"
}
// Handle is executed when the event IncreaseTaskCounter listens on is fired
func (s *IncreaseTaskCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.TaskCountKey, 1)
}
// DecreaseTaskCounter represents a listener
type DecreaseTaskCounter struct {
}
// Name defines the name for the DecreaseTaskCounter listener
func (s *DecreaseTaskCounter) Name() string {
return "task.counter.decrease"
}
// Handle is executed when the event DecreaseTaskCounter listens on is fired
func (s *DecreaseTaskCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.TaskCountKey, 1)
}
func notifyMentionedUsers(sess *xorm.Session, task *Task, text string, n notifications.NotificationWithSubject) (users map[int64]*user.User, err error) {
users, err = FindMentionedUsersInText(sess, text)
if err != nil {
return
}
if len(users) == 0 {
return
}
log.Debugf("Processing %d mentioned users for text %d", len(users), n.SubjectID())
var notified int
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.GetNotificationsForNameAndUser(sess, u.ID, n.Name(), n.SubjectID())
if err != nil {
return users, err
}
if len(dbn) > 0 {
continue
}
err = notifications.Notify(u, n)
if err != nil {
return users, err
}
notified++
}
log.Debugf("Notified %d mentioned users for text %d", notified, n.SubjectID())
return
}
// SendTaskCommentNotification represents a listener
type SendTaskCommentNotification struct {
}
// Name defines the name for the SendTaskCommentNotification listener
func (s *SendTaskCommentNotification) Name() string {
return "task.comment.notification.send"
}
// Handle is executed when the event SendTaskCommentNotification listens on is fired
func (s *SendTaskCommentNotification) Handle(msg *message.Message) (err error) {
event := &TaskCommentCreatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
n := &TaskCommentNotification{
Doer: event.Doer,
Task: event.Task,
Comment: event.Comment,
Mentioned: true,
}
mentionedUsers, err := notifyMentionedUsers(sess, event.Task, event.Comment.Comment, n)
if err != nil {
return err
}
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityTask, event.Task.ID)
if err != nil {
return err
}
log.Debugf("Sending task comment notifications to %d subscribers for task %d", len(subscribers), event.Task.ID)
for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID {
continue
}
if _, has := mentionedUsers[subscriber.UserID]; has {
continue
}
n := &TaskCommentNotification{
Doer: event.Doer,
Task: event.Task,
Comment: event.Comment,
}
err = notifications.Notify(subscriber.User, n)
if err != nil {
return
}
}
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()
n := &TaskCommentNotification{
Doer: event.Doer,
Task: event.Task,
Comment: event.Comment,
Mentioned: true,
}
_, err = notifyMentionedUsers(sess, event.Task, event.Comment.Comment, n)
return err
}
// SendTaskAssignedNotification represents a listener
type SendTaskAssignedNotification struct {
}
// Name defines the name for the SendTaskAssignedNotification listener
func (s *SendTaskAssignedNotification) Name() string {
return "task.assigned.notification.send"
}
// Handle is executed when the event SendTaskAssignedNotification listens on is fired
func (s *SendTaskAssignedNotification) Handle(msg *message.Message) (err error) {
event := &TaskAssigneeCreatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityTask, event.Task.ID)
if err != nil {
return err
}
log.Debugf("Sending task assigned notifications to %d subscribers for task %d", len(subscribers), event.Task.ID)
for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID {
continue
}
n := &TaskAssignedNotification{
Doer: event.Doer,
Task: event.Task,
Assignee: event.Assignee,
}
err = notifications.Notify(subscriber.User, n)
if err != nil {
return
}
}
return nil
}
// SendTaskDeletedNotification represents a listener
type SendTaskDeletedNotification struct {
}
// Name defines the name for the SendTaskDeletedNotification listener
func (s *SendTaskDeletedNotification) Name() string {
return "task.deleted.notification.send"
}
// Handle is executed when the event SendTaskDeletedNotification listens on is fired
func (s *SendTaskDeletedNotification) Handle(msg *message.Message) (err error) {
event := &TaskDeletedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityTask, event.Task.ID)
if err != nil {
return err
}
log.Debugf("Sending task deleted notifications to %d subscribers for task %d", len(subscribers), event.Task.ID)
for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID {
continue
}
n := &TaskDeletedNotification{
Doer: event.Doer,
Task: event.Task,
}
err = notifications.Notify(subscriber.User, n)
if err != nil {
return
}
}
return nil
}
type SubscribeAssigneeToTask struct {
}
// Name defines the name for the SubscribeAssigneeToTask listener
func (s *SubscribeAssigneeToTask) Name() string {
return "task.assignee.subscribe"
}
// Handle is executed when the event SubscribeAssigneeToTask listens on is fired
func (s *SubscribeAssigneeToTask) Handle(msg *message.Message) (err error) {
event := &TaskAssigneeCreatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sub := &Subscription{
UserID: event.Assignee.ID,
EntityType: SubscriptionEntityTask,
EntityID: event.Task.ID,
}
sess := db.NewSession()
defer sess.Close()
err = sub.Create(sess, event.Assignee)
if err != nil && !IsErrSubscriptionAlreadyExists(err) {
return err
}
return sess.Commit()
}
// HandleTaskCreateMentions represents a listener
type HandleTaskCreateMentions struct {
}
// Name defines the name for the HandleTaskCreateMentions listener
func (s *HandleTaskCreateMentions) Name() string {
return "task.created.mentions"
}
// Handle is executed when the event HandleTaskCreateMentions listens on is fired
func (s *HandleTaskCreateMentions) Handle(msg *message.Message) (err error) {
event := &TaskCreatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
n := &UserMentionedInTaskNotification{
Task: event.Task,
Doer: event.Doer,
IsNew: true,
}
_, err = notifyMentionedUsers(sess, event.Task, event.Task.Description, n)
return err
}
// HandleTaskUpdatedMentions represents a listener
type HandleTaskUpdatedMentions struct {
}
// Name defines the name for the HandleTaskUpdatedMentions listener
func (s *HandleTaskUpdatedMentions) Name() string {
return "task.updated.mentions"
}
// Handle is executed when the event HandleTaskUpdatedMentions listens on is fired
func (s *HandleTaskUpdatedMentions) Handle(msg *message.Message) (err error) {
event := &TaskUpdatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
n := &UserMentionedInTaskNotification{
Task: event.Task,
Doer: event.Doer,
IsNew: false,
}
_, err = notifyMentionedUsers(sess, event.Task, event.Task.Description, n)
return err
}
///////
// List Event Listeners
type IncreaseListCounter struct {
}
func (s *IncreaseListCounter) Name() string {
return "list.counter.increase"
}
func (s *IncreaseListCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.ListCountKey, 1)
}
type DecreaseListCounter struct {
}
func (s *DecreaseListCounter) Name() string {
return "list.counter.decrease"
}
func (s *DecreaseListCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.ListCountKey, 1)
}
// SendListCreatedNotification represents a listener
type SendListCreatedNotification struct {
}
// Name defines the name for the SendListCreatedNotification listener
func (s *SendListCreatedNotification) Name() string {
return "send.list.created.notification"
}
// Handle is executed when the event SendListCreatedNotification listens on is fired
func (s *SendListCreatedNotification) Handle(msg *message.Message) (err error) {
event := &ListCreatedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
sess := db.NewSession()
defer sess.Close()
subscribers, err := getSubscribersForEntity(sess, SubscriptionEntityList, event.List.ID)
if err != nil {
return err
}
log.Debugf("Sending list created notifications to %d subscribers for list %d", len(subscribers), event.List.ID)
for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID {
continue
}
n := &ListCreatedNotification{
Doer: event.Doer,
List: event.List,
}
err = notifications.Notify(subscriber.User, n)
if err != nil {
return
}
}
return nil
}
//////
// Namespace events
// IncreaseNamespaceCounter represents a listener
type IncreaseNamespaceCounter struct {
}
// Name defines the name for the IncreaseNamespaceCounter listener
func (s *IncreaseNamespaceCounter) Name() string {
return "namespace.counter.increase"
}
// Hanlde is executed when the event IncreaseNamespaceCounter listens on is fired
func (s *IncreaseNamespaceCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.NamespaceCountKey, 1)
}
// DecreaseNamespaceCounter represents a listener
type DecreaseNamespaceCounter struct {
}
// Name defines the name for the DecreaseNamespaceCounter listener
func (s *DecreaseNamespaceCounter) Name() string {
return "namespace.counter.decrease"
}
// Hanlde is executed when the event DecreaseNamespaceCounter listens on is fired
func (s *DecreaseNamespaceCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.NamespaceCountKey, 1)
}
///////
// Team Events
// IncreaseTeamCounter represents a listener
type IncreaseTeamCounter struct {
}
// Name defines the name for the IncreaseTeamCounter listener
func (s *IncreaseTeamCounter) Name() string {
return "team.counter.increase"
}
// Hanlde is executed when the event IncreaseTeamCounter listens on is fired
func (s *IncreaseTeamCounter) Handle(msg *message.Message) (err error) {
return keyvalue.IncrBy(metrics.TeamCountKey, 1)
}
// DecreaseTeamCounter represents a listener
type DecreaseTeamCounter struct {
}
// Name defines the name for the DecreaseTeamCounter listener
func (s *DecreaseTeamCounter) Name() string {
return "team.counter.decrease"
}
// Hanlde is executed when the event DecreaseTeamCounter listens on is fired
func (s *DecreaseTeamCounter) Handle(msg *message.Message) (err error) {
return keyvalue.DecrBy(metrics.TeamCountKey, 1)
}
// SendTeamMemberAddedNotification represents a listener
type SendTeamMemberAddedNotification struct {
}
// Name defines the name for the SendTeamMemberAddedNotification listener
func (s *SendTeamMemberAddedNotification) Name() string {
return "team.member.added.notification"
}
// Handle is executed when the event SendTeamMemberAddedNotification listens on is fired
func (s *SendTeamMemberAddedNotification) Handle(msg *message.Message) (err error) {
event := &TeamMemberAddedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
// Don't notify the user themselves
if event.Doer.ID == event.Member.ID {
return nil
}
return notifications.Notify(event.Member, &TeamMemberAddedNotification{
Member: event.Member,
Doer: event.Doer,
Team: event.Team,
})
}