aboutsummaryrefslogblamecommitdiffstats
path: root/internal/notifier/service/planner.go
blob: 2f7f20772c22250b1a6e1da30b8a35bda2be87c7 (plain) (tree)

















































                                                                                                                


                                     










                                                                                                        


                                                                                                     























                                                                                                              
                                                                                                                                            
























                                                                                                                 
                                                                                                                                                                                            



























                                                                                                                  
                                                                                                                                                                                                  







                                                                           












































                                                                                                                                                           
package service

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	chModel "donetick.com/core/internal/chore/model"
	cModel "donetick.com/core/internal/circle/model"
	cRepo "donetick.com/core/internal/circle/repo"
	nModel "donetick.com/core/internal/notifier/model"
	nRepo "donetick.com/core/internal/notifier/repo"
	"donetick.com/core/logging"
)

type NotificationPlanner struct {
	nRepo *nRepo.NotificationRepository
	cRepo *cRepo.CircleRepository
}

func NewNotificationPlanner(nr *nRepo.NotificationRepository, cr *cRepo.CircleRepository) *NotificationPlanner {
	return &NotificationPlanner{nRepo: nr,
		cRepo: cr,
	}
}

func (n *NotificationPlanner) GenerateNotifications(c context.Context, chore *chModel.Chore) bool {
	log := logging.FromContext(c)
	circleMembers, err := n.cRepo.GetCircleUsers(c, chore.CircleID)
	assignees := make([]*cModel.UserCircleDetail, 0)
	for _, member := range circleMembers {
		if member.ID == chore.AssignedTo {
			assignees = append(assignees, member)
		}
	}

	if err != nil {
		log.Error("Error getting circle members", err)
		return false
	}
	n.nRepo.DeleteAllChoreNotifications(chore.ID)
	notifications := make([]*nModel.Notification, 0)
	if !chore.Notification || chore.FrequencyType == "trigger" {

		return true
	}
	var mt *chModel.NotificationMetadata
	if err := json.Unmarshal([]byte(*chore.NotificationMetadata), &mt); err != nil {
		log.Error("Error unmarshalling notification metadata", err)
		return false
	}
	if chore.NextDueDate == nil {
		return true
	}
	if mt.DueDate {
		notifications = append(notifications, generateDueNotifications(chore, assignees)...)
	}
	if mt.PreDue {
		notifications = append(notifications, generatePreDueNotifications(chore, assignees)...)
	}
	if mt.Nagging {
		notifications = append(notifications, generateOverdueNotifications(chore, assignees)...)
	}
	if mt.CircleGroup {
		notifications = append(notifications, generateCircleGroupNotifications(chore, mt)...)
	}

	n.nRepo.BatchInsertNotifications(notifications)
	return true
}

func generateDueNotifications(chore *chModel.Chore, users []*cModel.UserCircleDetail) []*nModel.Notification {
	var assignee *cModel.UserCircleDetail
	notifications := make([]*nModel.Notification, 0)
	for _, user := range users {
		if user.ID == chore.AssignedTo {
			assignee = user
			break
		}
	}
	for _, user := range users {

		notification := &nModel.Notification{
			ChoreID:      chore.ID,
			IsSent:       false,
			ScheduledFor: *chore.NextDueDate,
			CreatedAt:    time.Now().UTC(),
			TypeID:       1,
			UserID:       user.ID,
			TargetID:     fmt.Sprint(user.ChatID),
			Text:         fmt.Sprintf("📅 Reminder: *%s* is due today and assigned to %s.", chore.Name, assignee.DisplayName),
		}
		notifications = append(notifications, notification)
	}

	return notifications
}

func generatePreDueNotifications(chore *chModel.Chore, users []*cModel.UserCircleDetail) []*nModel.Notification {
	var assignee *cModel.UserCircleDetail
	for _, user := range users {
		if user.ID == chore.AssignedTo {
			assignee = user
			break
		}
	}
	notifications := make([]*nModel.Notification, 0)
	for _, user := range users {
		notification := &nModel.Notification{
			ChoreID:      chore.ID,
			IsSent:       false,
			ScheduledFor: *chore.NextDueDate,
			CreatedAt:    time.Now().UTC().Add(-time.Hour * 3),
			TypeID:       3,
			UserID:       user.ID,
			TargetID:     fmt.Sprint(user.ChatID),
			Text:         fmt.Sprintf("📢 Heads up! *%s* is due soon (on %s) and assigned to %s.", chore.Name, chore.NextDueDate.Format("January 2nd"), assignee.DisplayName),
		}
		notifications = append(notifications, notification)

	}
	return notifications

}

func generateOverdueNotifications(chore *chModel.Chore, users []*cModel.UserCircleDetail) []*nModel.Notification {
	var assignee *cModel.UserCircleDetail
	for _, user := range users {
		if user.ID == chore.AssignedTo {
			assignee = user
			break
		}
	}
	notifications := make([]*nModel.Notification, 0)
	for _, hours := range []int{24, 48, 72} {
		scheduleTime := chore.NextDueDate.Add(time.Hour * time.Duration(hours))
		for _, user := range users {
			notification := &nModel.Notification{
				ChoreID:      chore.ID,
				IsSent:       false,
				ScheduledFor: scheduleTime,
				CreatedAt:    time.Now().UTC(),
				TypeID:       2,
				UserID:       user.ID,
				TargetID:     fmt.Sprint(user.ChatID),
				Text:         fmt.Sprintf("🚨 *%s* is now %d hours overdue. Please complete it as soon as possible. (Assigned to %s)", chore.Name, hours, assignee.DisplayName),
			}
			notifications = append(notifications, notification)
		}
	}

	return notifications

}

func generateCircleGroupNotifications(chore *chModel.Chore, mt *chModel.NotificationMetadata) []*nModel.Notification {
	var notifications []*nModel.Notification
	if !mt.CircleGroup || mt.CircleGroupID == nil || *mt.CircleGroupID == 0 {
		return notifications
	}
	if mt.DueDate {
		notifications = append(notifications, &nModel.Notification{
			ChoreID:      chore.ID,
			IsSent:       false,
			ScheduledFor: *chore.NextDueDate,
			CreatedAt:    time.Now().UTC(),
			TypeID:       1,
			TargetID:     fmt.Sprint(*mt.CircleGroupID),
			Text:         fmt.Sprintf("📅 Reminder: *%s* is due today.", chore.Name),
		})
	}
	if mt.PreDue {
		notifications = append(notifications, &nModel.Notification{
			ChoreID:      chore.ID,
			IsSent:       false,
			ScheduledFor: *chore.NextDueDate,
			CreatedAt:    time.Now().UTC().Add(-time.Hour * 3),
			TypeID:       3,
			TargetID:     fmt.Sprint(*mt.CircleGroupID),
			Text:         fmt.Sprintf("📢 Heads up! *%s* is due soon (on %s).", chore.Name, chore.NextDueDate.Format("January 2nd")),
		})
	}
	if mt.Nagging {
		for _, hours := range []int{24, 48, 72} {
			scheduleTime := chore.NextDueDate.Add(time.Hour * time.Duration(hours))
			notifications = append(notifications, &nModel.Notification{
				ChoreID:      chore.ID,
				IsSent:       false,
				ScheduledFor: scheduleTime,
				CreatedAt:    time.Now().UTC(),
				TypeID:       2,
				TargetID:     fmt.Sprint(*mt.CircleGroupID),
				Text:         fmt.Sprintf("🚨 *%s* is now %d hours overdue. Please complete it as soon as possible.", chore.Name, hours),
			})
		}
	}

	return notifications
}