Support Reminder events
Move code to schedule to have event independent from NotificationPlatform
This commit is contained in:
parent
04d1894aea
commit
80afab3bc0
7 changed files with 61 additions and 28 deletions
|
@ -24,13 +24,13 @@ const (
|
|||
type EventType string
|
||||
|
||||
const (
|
||||
EventTypeUnknown EventType = ""
|
||||
EventTypeChoreCreated EventType = "CREATED"
|
||||
EventTypeChoreReminder EventType = "REMINDER"
|
||||
EventTypeChoreUpdated EventType = "UPDATED"
|
||||
EventTypeChoreCompleted EventType = "COMPLETED"
|
||||
EventTypeChoreReassigned EventType = "REASSIGNED"
|
||||
EventTypeChoreSkipped EventType = "SKIPPED"
|
||||
EventTypeUnknown EventType = ""
|
||||
EventTypeTaskCreated EventType = "task.created"
|
||||
EventTypeTaskReminder EventType = "task.reminder"
|
||||
EventTypeTaskUpdated EventType = "task.updated"
|
||||
EventTypeTaskCompleted EventType = "task.completed"
|
||||
EventTypeTaskReassigned EventType = "task.reassigned"
|
||||
EventTypeTaskSkipped EventType = "task.skipped"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
|
@ -122,7 +122,7 @@ func (p *EventsProducer) ChoreCompleted(ctx context.Context, webhookURL *string,
|
|||
}
|
||||
|
||||
event := Event{
|
||||
Type: EventTypeChoreCompleted,
|
||||
Type: EventTypeTaskCompleted,
|
||||
URL: *webhookURL,
|
||||
Timestamp: time.Now(),
|
||||
Data: ChoreData{Chore: chore,
|
||||
|
@ -140,7 +140,7 @@ func (p *EventsProducer) ChoreSkipped(ctx context.Context, webhookURL *string, c
|
|||
}
|
||||
|
||||
event := Event{
|
||||
Type: EventTypeChoreSkipped,
|
||||
Type: EventTypeTaskSkipped,
|
||||
URL: *webhookURL,
|
||||
Timestamp: time.Now(),
|
||||
Data: ChoreData{Chore: chore,
|
||||
|
@ -151,13 +151,13 @@ func (p *EventsProducer) ChoreSkipped(ctx context.Context, webhookURL *string, c
|
|||
p.publishEvent(event)
|
||||
}
|
||||
|
||||
func (p *EventsProducer) NotificaitonEvent(ctx context.Context, url string, event interface{}) {
|
||||
func (p *EventsProducer) NotificationEvent(ctx context.Context, url string, event interface{}) {
|
||||
// print the event and the url :
|
||||
p.logger.Debug("Sending notification event")
|
||||
|
||||
p.publishEvent(Event{
|
||||
URL: url,
|
||||
Type: EventTypeChoreReminder,
|
||||
Type: EventTypeTaskReminder,
|
||||
Timestamp: time.Now(),
|
||||
Data: event,
|
||||
})
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
ID int `json:"id" gorm:"primaryKey"`
|
||||
|
@ -13,7 +18,7 @@ type Notification struct {
|
|||
TypeID NotificationPlatform `json:"type" gorm:"column:type"`
|
||||
ScheduledFor time.Time `json:"scheduled_for" gorm:"column:scheduled_for;index"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at"`
|
||||
RawEvent interface{} `json:"raw_event" gorm:"column:raw_event;type:jsonb"`
|
||||
RawEvent JSONB `json:"raw_event" gorm:"column:raw_event;type:jsonb"`
|
||||
}
|
||||
type NotificationDetails struct {
|
||||
Notification
|
||||
|
@ -32,3 +37,24 @@ const (
|
|||
NotificationPlatformTelegram
|
||||
NotificationPlatformPushover
|
||||
)
|
||||
|
||||
type JSONB map[string]interface{}
|
||||
|
||||
func (j JSONB) Value() (driver.Value, error) {
|
||||
value, err := json.Marshal(j)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(value), nil
|
||||
}
|
||||
|
||||
func (j *JSONB) Scan(value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return json.Unmarshal(v, j)
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), j)
|
||||
default:
|
||||
return errors.New("type assertion to []byte or string failed")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,10 +45,6 @@ func (n *Notifier) SendNotification(c context.Context, notification *nModel.Noti
|
|||
if err != nil {
|
||||
log.Error("Failed to send notification", "err", err)
|
||||
}
|
||||
if notification.RawEvent != nil && notification.WebhookURL != nil {
|
||||
// if we have a webhook url, we should send the event to the webhook
|
||||
n.eventsProducer.NotificaitonEvent(c, *notification.WebhookURL, notification.RawEvent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"donetick.com/core/config"
|
||||
chRepo "donetick.com/core/internal/chore/repo"
|
||||
"donetick.com/core/internal/events"
|
||||
nRepo "donetick.com/core/internal/notifier/repo"
|
||||
uRepo "donetick.com/core/internal/user/repo"
|
||||
"donetick.com/core/logging"
|
||||
|
@ -23,17 +24,19 @@ type Scheduler struct {
|
|||
userRepo *uRepo.UserRepository
|
||||
stopChan chan bool
|
||||
notifier *Notifier
|
||||
eventsProducer *events.EventsProducer
|
||||
notificationRepo *nRepo.NotificationRepository
|
||||
SchedulerJobs config.SchedulerConfig
|
||||
}
|
||||
|
||||
func NewScheduler(cfg *config.Config, ur *uRepo.UserRepository, cr *chRepo.ChoreRepository, n *Notifier, nr *nRepo.NotificationRepository) *Scheduler {
|
||||
func NewScheduler(cfg *config.Config, ur *uRepo.UserRepository, cr *chRepo.ChoreRepository, n *Notifier, nr *nRepo.NotificationRepository, ep *events.EventsProducer) *Scheduler {
|
||||
return &Scheduler{
|
||||
choreRepo: cr,
|
||||
userRepo: ur,
|
||||
stopChan: make(chan bool),
|
||||
notifier: n,
|
||||
notificationRepo: nr,
|
||||
eventsProducer: ep,
|
||||
SchedulerJobs: cfg.SchedulerJobs,
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +75,11 @@ func (s *Scheduler) loadAndSendNotificationJob(c context.Context) (time.Duration
|
|||
log.Error("Error sending notification", err)
|
||||
continue
|
||||
}
|
||||
if notification.RawEvent != nil && notification.WebhookURL != nil {
|
||||
// if we have a webhook url, we should send the event to the webhook
|
||||
s.eventsProducer.NotificationEvent(c, *notification.WebhookURL, notification.RawEvent)
|
||||
}
|
||||
|
||||
notification.IsSent = true
|
||||
}
|
||||
|
||||
|
|
|
@ -87,12 +87,13 @@ func generateDueNotifications(chore *chModel.Chore, users []*cModel.UserCircleDe
|
|||
CreatedAt: time.Now().UTC(),
|
||||
TypeID: user.NotificationType,
|
||||
UserID: user.ID,
|
||||
CircleID: user.CircleID,
|
||||
TargetID: user.TargetID,
|
||||
Text: fmt.Sprintf("📅 Reminder: *%s* is due today and assigned to %s.", chore.Name, assignee.DisplayName),
|
||||
RawEvent: map[string]interface{}{
|
||||
"id": chore.ID,
|
||||
"name": chore.Name,
|
||||
"due_date": chore.NextDueDate.Format("January 2nd"),
|
||||
"due_date": chore.NextDueDate,
|
||||
"assignee": assignee.DisplayName,
|
||||
"assignee_username": assignee.Username,
|
||||
},
|
||||
|
@ -122,12 +123,13 @@ func generatePreDueNotifications(chore *chModel.Chore, users []*cModel.UserCircl
|
|||
CreatedAt: time.Now().UTC().Add(-time.Hour * 3),
|
||||
TypeID: user.NotificationType,
|
||||
UserID: user.ID,
|
||||
CircleID: user.CircleID,
|
||||
TargetID: user.TargetID,
|
||||
Text: fmt.Sprintf("📢 Heads up! *%s* is due soon (on %s) and assigned to %s.", chore.Name, chore.NextDueDate.Format("January 2nd"), assignee.DisplayName),
|
||||
RawEvent: map[string]interface{}{
|
||||
"id": chore.ID,
|
||||
"name": chore.Name,
|
||||
"due_date": chore.NextDueDate.Format("January 2nd"),
|
||||
"due_date": chore.NextDueDate,
|
||||
"assignee": assignee.DisplayName,
|
||||
"assignee_username": assignee.Username,
|
||||
},
|
||||
|
@ -160,13 +162,14 @@ func generateOverdueNotifications(chore *chModel.Chore, users []*cModel.UserCirc
|
|||
CreatedAt: time.Now().UTC(),
|
||||
TypeID: user.NotificationType,
|
||||
UserID: user.ID,
|
||||
CircleID: user.CircleID,
|
||||
TargetID: fmt.Sprint(user.TargetID),
|
||||
Text: fmt.Sprintf("🚨 *%s* is now %d hours overdue. Please complete it as soon as possible. (Assigned to %s)", chore.Name, hours, assignee.DisplayName),
|
||||
RawEvent: map[string]interface{}{
|
||||
"id": chore.ID,
|
||||
"type": EventTypeOverdue,
|
||||
"name": chore.Name,
|
||||
"due_date": chore.NextDueDate.Format("January 2nd"),
|
||||
"due_date": chore.NextDueDate,
|
||||
"assignee": assignee.DisplayName,
|
||||
"assignee_username": assignee.Username,
|
||||
},
|
||||
|
|
|
@ -216,7 +216,7 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
|
|||
// create a random password for the user using crypto/rand:
|
||||
password := auth.GenerateRandomPassword(12)
|
||||
encodedPassword, err := auth.EncodePassword(password)
|
||||
acc = &uModel.User{
|
||||
account := &uModel.User{
|
||||
Username: userinfo.Id,
|
||||
Email: userinfo.Email,
|
||||
Image: userinfo.Picture,
|
||||
|
@ -224,7 +224,7 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
|
|||
DisplayName: userinfo.GivenName,
|
||||
Provider: uModel.AuthProviderGoogle,
|
||||
}
|
||||
createdUser, err := h.userRepo.CreateUser(c, acc)
|
||||
createdUser, err := h.userRepo.CreateUser(c, account)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Unable to create user",
|
||||
|
@ -316,14 +316,14 @@ func (h *Handler) thirdPartyAuthCallback(c *gin.Context) {
|
|||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Password encoding failed"})
|
||||
return
|
||||
}
|
||||
acc = &uModel.User{
|
||||
account := &uModel.User{
|
||||
Username: claims.Email,
|
||||
Email: claims.Email,
|
||||
Password: encodedPassword,
|
||||
DisplayName: claims.DisplayName,
|
||||
Provider: uModel.AuthProviderOAuth2,
|
||||
}
|
||||
createdUser, err := h.userRepo.CreateUser(c, acc)
|
||||
createdUser, err := h.userRepo.CreateUser(c, account)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Unable to create user",
|
||||
|
|
|
@ -75,9 +75,9 @@ func (r *UserRepository) UpdateUserCircle(c context.Context, userID, circleID in
|
|||
return r.db.WithContext(c).Model(&uModel.User{}).Where("id = ?", userID).Update("circle_id", circleID).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByEmail(c context.Context, email string) (*uModel.User, error) {
|
||||
var user *uModel.User
|
||||
if err := r.db.WithContext(c).Where("email = ?", email).First(&user).Error; err != nil {
|
||||
func (r *UserRepository) FindByEmail(c context.Context, email string) (*uModel.UserDetails, error) {
|
||||
var user *uModel.UserDetails
|
||||
if err := r.db.WithContext(c).Table("users u").Select("u.*, c.webhook_url as webhook_url").Joins("left join circles c on c.id = u.circle_id").Where("email = ?", email).First(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
|
|
Loading…
Add table
Reference in a new issue