Add subtask model and repository, implement webhook notification handling
fix Issue with Scheduler Support NotificationPlatformWebhook support Discord as notification target
This commit is contained in:
parent
8db572f1ec
commit
81acbd8eba
8 changed files with 136 additions and 11 deletions
|
@ -243,7 +243,6 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
stringLabels = &labels
|
||||
}
|
||||
createdChore := &chModel.Chore{
|
||||
|
||||
Name: choreReq.Name,
|
||||
FrequencyType: choreReq.FrequencyType,
|
||||
Frequency: choreReq.Frequency,
|
||||
|
@ -264,6 +263,7 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
CompletionWindow: choreReq.CompletionWindow,
|
||||
Description: choreReq.Description,
|
||||
SubTasks: choreReq.SubTasks,
|
||||
Priority: choreReq.Priority,
|
||||
}
|
||||
id, err := h.choreRepo.CreateChore(c, createdChore)
|
||||
createdChore.ID = id
|
||||
|
@ -547,6 +547,7 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
if choreReq.SubTasks == nil {
|
||||
choreReq.SubTasks = &[]stModel.SubTask{}
|
||||
}
|
||||
// check what subtask needed to be removed:
|
||||
for _, existedSubTask := range *oldChore.SubTasks {
|
||||
found := false
|
||||
for _, newSubTask := range *choreReq.SubTasks {
|
||||
|
@ -559,14 +560,16 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
ToBeRemoved = append(ToBeRemoved, existedSubTask)
|
||||
}
|
||||
}
|
||||
|
||||
// check what subtask needed to be added or updated:
|
||||
for _, newSubTask := range *choreReq.SubTasks {
|
||||
found := false
|
||||
newSubTask.ChoreID = oldChore.ID
|
||||
|
||||
for _, existedSubTask := range *oldChore.SubTasks {
|
||||
if existedSubTask.ID == newSubTask.ID {
|
||||
if existedSubTask.Name != newSubTask.Name || existedSubTask.OrderID != newSubTask.OrderID {
|
||||
if existedSubTask.Name != newSubTask.Name ||
|
||||
existedSubTask.OrderID != newSubTask.OrderID ||
|
||||
existedSubTask.ParentId != newSubTask.ParentId {
|
||||
// there is a change in the subtask, update it
|
||||
break
|
||||
}
|
||||
|
|
|
@ -38,9 +38,8 @@ func scheduleNextDueDate(chore *chModel.Chore, completedDate time.Time) (*time.T
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing time in frequency metadata: %w", err)
|
||||
}
|
||||
|
||||
t = t.UTC()
|
||||
baseDate = time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), t.Hour(), t.Minute(), t.Second(), 0, time.UTC)
|
||||
|
||||
// If the time is in the past today, move it to tomorrow
|
||||
if baseDate.Before(completedDate) {
|
||||
baseDate = baseDate.AddDate(0, 0, 1)
|
||||
|
|
|
@ -37,6 +37,7 @@ const (
|
|||
NotificationPlatformTelegram
|
||||
NotificationPlatformPushover
|
||||
NotificationPlatformWebhook
|
||||
NotificationPlatformDiscord
|
||||
)
|
||||
|
||||
type JSONB map[string]interface{}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"donetick.com/core/internal/events"
|
||||
nModel "donetick.com/core/internal/notifier/model"
|
||||
"donetick.com/core/internal/notifier/service/discord"
|
||||
pushover "donetick.com/core/internal/notifier/service/pushover"
|
||||
telegram "donetick.com/core/internal/notifier/service/telegram"
|
||||
|
||||
|
@ -14,14 +15,16 @@ import (
|
|||
type Notifier struct {
|
||||
Telegram *telegram.TelegramNotifier
|
||||
Pushover *pushover.Pushover
|
||||
discord *discord.DiscordNotifier
|
||||
eventsProducer *events.EventsProducer
|
||||
}
|
||||
|
||||
func NewNotifier(t *telegram.TelegramNotifier, p *pushover.Pushover, ep *events.EventsProducer) *Notifier {
|
||||
func NewNotifier(t *telegram.TelegramNotifier, p *pushover.Pushover, ep *events.EventsProducer, d *discord.DiscordNotifier) *Notifier {
|
||||
return &Notifier{
|
||||
Telegram: t,
|
||||
Pushover: p,
|
||||
eventsProducer: ep,
|
||||
discord: d,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +44,13 @@ func (n *Notifier) SendNotification(c context.Context, notification *nModel.Noti
|
|||
return nil
|
||||
}
|
||||
err = n.Pushover.SendNotification(c, notification)
|
||||
case nModel.NotificationPlatformDiscord:
|
||||
if n.discord == nil {
|
||||
log.Error("Discord is not initialized, Skipping sending message")
|
||||
return nil
|
||||
}
|
||||
err = n.discord.SendNotification(c, notification)
|
||||
|
||||
case nModel.NotificationPlatformWebhook:
|
||||
// TODO: Implement webhook notification
|
||||
// currently we have eventProducer to send events always as a webhook
|
||||
|
|
84
internal/notifier/service/discord/discord.go
Normal file
84
internal/notifier/service/discord/discord.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"donetick.com/core/config"
|
||||
chModel "donetick.com/core/internal/chore/model"
|
||||
nModel "donetick.com/core/internal/notifier/model"
|
||||
uModel "donetick.com/core/internal/user/model"
|
||||
"donetick.com/core/logging"
|
||||
)
|
||||
|
||||
type DiscordNotifier struct {
|
||||
}
|
||||
|
||||
func NewDiscordNotifier(config *config.Config) *DiscordNotifier {
|
||||
return &DiscordNotifier{}
|
||||
}
|
||||
|
||||
func (dn *DiscordNotifier) SendChoreCompletion(c context.Context, chore *chModel.Chore, user *uModel.User) {
|
||||
log := logging.FromContext(c)
|
||||
if dn == nil {
|
||||
log.Error("Discord notifier is not initialized, skipping message sending")
|
||||
return
|
||||
}
|
||||
|
||||
var mt *chModel.NotificationMetadata
|
||||
if err := json.Unmarshal([]byte(*chore.NotificationMetadata), &mt); err != nil {
|
||||
log.Error("Error unmarshalling notification metadata", err)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("🎉 **%s** is completed! Great job, %s! 🌟", chore.Name, user.DisplayName)
|
||||
err := dn.sendMessage(c, user.UserNotificationTargets.TargetID, message)
|
||||
if err != nil {
|
||||
log.Error("Error sending Discord message:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (dn *DiscordNotifier) SendNotification(c context.Context, notification *nModel.NotificationDetails) error {
|
||||
|
||||
if dn == nil {
|
||||
return errors.New("Discord notifier is not initialized")
|
||||
}
|
||||
|
||||
if notification.Text == "" {
|
||||
return errors.New("unable to send notification, text is empty")
|
||||
}
|
||||
|
||||
return dn.sendMessage(c, notification.TargetID, notification.Text)
|
||||
}
|
||||
|
||||
func (dn *DiscordNotifier) sendMessage(c context.Context, webhookURL string, message string) error {
|
||||
log := logging.FromContext(c)
|
||||
|
||||
if webhookURL == "" {
|
||||
return errors.New("unable to send notification, webhook URL is empty")
|
||||
}
|
||||
|
||||
payload := map[string]string{"content": message}
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
log.Error("Error marshalling JSON:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
log.Error("Error sending message to Discord:", err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
||||
log.Error("Discord webhook returned unexpected status:", resp.Status)
|
||||
return errors.New("failed to send Discord message")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,4 +9,5 @@ type SubTask struct {
|
|||
Name string `json:"name" gorm:"column:name"`
|
||||
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"`
|
||||
CompletedBy int `json:"completedBy" gorm:"column:completed_by"`
|
||||
ParentId *int `json:"parentId" gorm:"column:parent_id"`
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
|||
var insertions []stModel.SubTask
|
||||
var updates []stModel.SubTask
|
||||
for _, subtask := range toBeAdd {
|
||||
if subtask.ID == 0 {
|
||||
if subtask.ID <= 0 {
|
||||
// we interpret this as a new subtask
|
||||
subtask.ID = 0
|
||||
insertions = append(insertions, subtask)
|
||||
} else {
|
||||
updates = append(updates, subtask)
|
||||
|
@ -51,7 +53,14 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
|||
}
|
||||
if len(updates) > 0 {
|
||||
for _, subtask := range updates {
|
||||
if err := tx.Model(&stModel.SubTask{}).Where("chore_id = ? AND id = ?", choreId, subtask.ID).Updates(subtask).Error; err != nil {
|
||||
values := map[string]interface{}{
|
||||
"name": subtask.Name,
|
||||
"order_id": subtask.OrderID,
|
||||
"completed_at": subtask.CompletedAt,
|
||||
"completed_by": subtask.CompletedBy,
|
||||
"parent_id": subtask.ParentId,
|
||||
}
|
||||
if err := tx.Model(&stModel.SubTask{}).Where("chore_id = ? AND id = ?", choreId, subtask.ID).Updates(values).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +70,28 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
|||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) DeleteSubtask(c context.Context, tx *gorm.DB, subtaskID int) error {
|
||||
if tx != nil {
|
||||
return tx.Delete(&stModel.SubTask{}, subtaskID).Error
|
||||
return r.deleteSubtaskWithChildren(c, tx, subtaskID)
|
||||
}
|
||||
return r.db.WithContext(c).Delete(&stModel.SubTask{}, subtaskID).Error
|
||||
return r.db.WithContext(c).Transaction(func(tx *gorm.DB) error {
|
||||
return r.deleteSubtaskWithChildren(c, tx, subtaskID)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) deleteSubtaskWithChildren(c context.Context, tx *gorm.DB, subtaskID int) error {
|
||||
var childSubtasks []stModel.SubTask
|
||||
if err := tx.Where("parent_id = ?", subtaskID).Find(&childSubtasks).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, child := range childSubtasks {
|
||||
if err := r.deleteSubtaskWithChildren(c, tx, child.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Delete(&stModel.SubTask{}, subtaskID).Error
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) UpdateSubTaskStatus(c context.Context, userID int, subtaskID int, completedAt *time.Time) error {
|
||||
|
|
2
main.go
2
main.go
|
@ -31,6 +31,7 @@ import (
|
|||
notifier "donetick.com/core/internal/notifier"
|
||||
nRepo "donetick.com/core/internal/notifier/repo"
|
||||
nps "donetick.com/core/internal/notifier/service"
|
||||
discord "donetick.com/core/internal/notifier/service/discord"
|
||||
"donetick.com/core/internal/notifier/service/pushover"
|
||||
telegram "donetick.com/core/internal/notifier/service/telegram"
|
||||
pRepo "donetick.com/core/internal/points/repo"
|
||||
|
@ -73,6 +74,7 @@ func main() {
|
|||
// add notifier
|
||||
fx.Provide(pushover.NewPushover),
|
||||
fx.Provide(telegram.NewTelegramNotifier),
|
||||
fx.Provide(discord.NewDiscordNotifier),
|
||||
fx.Provide(notifier.NewNotifier),
|
||||
fx.Provide(events.NewEventsProducer),
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue