Compare commits
No commits in common. "5b10f63e657fc9d0564c9c4c1cd4d2d6e19d6435" and "6808c794464c6307a2aa4f22f0f3dde8805dfd80" have entirely different histories.
5b10f63e65
...
6808c79446
14 changed files with 31 additions and 226 deletions
|
@ -8,12 +8,6 @@ pushover:
|
||||||
database:
|
database:
|
||||||
type: "sqlite"
|
type: "sqlite"
|
||||||
migration: true
|
migration: true
|
||||||
# these are only required for postgres
|
|
||||||
host: "secret"
|
|
||||||
port: 5432
|
|
||||||
user: "secret"
|
|
||||||
password: "secret"
|
|
||||||
name: "secret"
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: "secret"
|
secret: "secret"
|
||||||
session_time: 168h
|
session_time: 168h
|
||||||
|
|
|
@ -8,12 +8,6 @@ pushover:
|
||||||
database:
|
database:
|
||||||
type: "sqlite"
|
type: "sqlite"
|
||||||
migration: true
|
migration: true
|
||||||
# these are only required for postgres
|
|
||||||
host: "secret"
|
|
||||||
port: 5432
|
|
||||||
user: "secret"
|
|
||||||
password: "secret"
|
|
||||||
name: "secret"
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: "secret"
|
secret: "secret"
|
||||||
session_time: 168h
|
session_time: 168h
|
||||||
|
|
|
@ -231,13 +231,7 @@ func (h *Handler) createChore(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stringNotificationMetadata := string(notificationMetadataBytes)
|
stringNotificationMetadata := string(notificationMetadataBytes)
|
||||||
if stringNotificationMetadata == "null" {
|
|
||||||
// TODO: Clean this update after 0.1.38.. there is a bug in the frontend that sends null instead of empty object
|
|
||||||
// this is a temporary fix to avoid breaking changes
|
|
||||||
// once change we can change notificationMetadataBytes to var notificationMetadataMap map[string]interface{} to gernerate empty object
|
|
||||||
// and remove this check
|
|
||||||
stringNotificationMetadata = "{}"
|
|
||||||
}
|
|
||||||
var stringLabels *string
|
var stringLabels *string
|
||||||
if len(choreReq.Labels) > 0 {
|
if len(choreReq.Labels) > 0 {
|
||||||
var escapedLabels []string
|
var escapedLabels []string
|
||||||
|
@ -470,13 +464,6 @@ func (h *Handler) editChore(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stringNotificationMetadata := string(notificationMetadataBytes)
|
stringNotificationMetadata := string(notificationMetadataBytes)
|
||||||
if stringNotificationMetadata == "null" {
|
|
||||||
// TODO: Clean this update after 0.1.38.. there is a bug in the frontend that sends null instead of empty object
|
|
||||||
// this is a temporary fix to avoid breaking changes
|
|
||||||
// once change we can change notificationMetadataBytes to var notificationMetadataMap map[string]interface{} to gernerate empty object
|
|
||||||
// and remove this check
|
|
||||||
stringNotificationMetadata = "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// escape special characters in labels and store them as a string :
|
// escape special characters in labels and store them as a string :
|
||||||
var stringLabels *string
|
var stringLabels *string
|
||||||
|
|
|
@ -38,8 +38,9 @@ func scheduleNextDueDate(chore *chModel.Chore, completedDate time.Time) (*time.T
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error parsing time in frequency metadata: %w", err)
|
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)
|
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 the time is in the past today, move it to tomorrow
|
||||||
if baseDate.Before(completedDate) {
|
if baseDate.Before(completedDate) {
|
||||||
baseDate = baseDate.AddDate(0, 0, 1)
|
baseDate = baseDate.AddDate(0, 0, 1)
|
||||||
|
|
|
@ -119,7 +119,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
||||||
FrequencyMetadata: jsonPtr(`{"unit": "days","time":"2024-07-07T14:30:00-04:00"}`),
|
FrequencyMetadata: jsonPtr(`{"unit": "days","time":"2024-07-07T14:30:00-04:00"}`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: timePtr(truncateToDay(now.AddDate(0, 0, 2)).Add(18*time.Hour + 30*time.Minute)),
|
want: timePtr(truncateToDay(now.AddDate(0, 0, 2)).Add(14*time.Hour + 30*time.Minute)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Interval - 4 Weeks",
|
name: "Interval - 4 Weeks",
|
||||||
|
@ -129,7 +129,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
||||||
FrequencyMetadata: jsonPtr(`{"unit": "weeks","time":"2024-07-07T14:30:00-04:00"}`),
|
FrequencyMetadata: jsonPtr(`{"unit": "weeks","time":"2024-07-07T14:30:00-04:00"}`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: timePtr(truncateToDay(now.AddDate(0, 0, 4*7)).Add(18*time.Hour + 30*time.Minute)),
|
want: timePtr(truncateToDay(now.AddDate(0, 0, 4*7)).Add(14*time.Hour + 30*time.Minute)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Interval - 3 Months",
|
name: "Interval - 3 Months",
|
||||||
|
@ -139,7 +139,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
||||||
FrequencyMetadata: jsonPtr(`{"unit": "months","time":"2024-07-07T14:30:00-04:00"}`),
|
FrequencyMetadata: jsonPtr(`{"unit": "months","time":"2024-07-07T14:30:00-04:00"}`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: timePtr(truncateToDay(now.AddDate(0, 3, 0)).Add(18*time.Hour + 30*time.Minute)),
|
want: timePtr(truncateToDay(now.AddDate(0, 3, 0)).Add(14*time.Hour + 30*time.Minute)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Interval - 2 Years",
|
name: "Interval - 2 Years",
|
||||||
|
@ -149,7 +149,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
||||||
FrequencyMetadata: jsonPtr(`{"unit": "years","time":"2024-07-07T14:30:00-04:00"}`),
|
FrequencyMetadata: jsonPtr(`{"unit": "years","time":"2024-07-07T14:30:00-04:00"}`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: timePtr(truncateToDay(now.AddDate(2, 0, 0)).Add(18*time.Hour + 30*time.Minute)),
|
want: timePtr(truncateToDay(now.AddDate(2, 0, 0)).Add(14*time.Hour + 30*time.Minute)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
executeTestTable(t, tests)
|
executeTestTable(t, tests)
|
||||||
|
@ -169,13 +169,13 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
||||||
chore: chModel.Chore{
|
chore: chModel.Chore{
|
||||||
FrequencyType: chModel.FrequencyTypeDayOfTheWeek,
|
FrequencyType: chModel.FrequencyTypeDayOfTheWeek,
|
||||||
|
|
||||||
FrequencyMetadata: jsonPtr(`{"days": ["monday"], "time": "2025-01-20T01:00:00-05:00"}`),
|
FrequencyMetadata: jsonPtr(`{"days": ["monday"], "time": "2025-01-20T18:00:00-05:00"}`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: func() *time.Time {
|
want: func() *time.Time {
|
||||||
// Calculate next Monday at 18:00 EST
|
// Calculate next Monday at 18:00 EST
|
||||||
nextMonday := now.AddDate(0, 0, (int(time.Monday)-int(now.Weekday())+7)%7)
|
nextMonday := now.AddDate(0, 0, (int(time.Monday)-int(now.Weekday())+7)%7)
|
||||||
nextMonday = truncateToDay(nextMonday).Add(6*time.Hour + 0*time.Minute)
|
nextMonday = truncateToDay(nextMonday).Add(18*time.Hour + 0*time.Minute)
|
||||||
return &nextMonday
|
return &nextMonday
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
@ -184,7 +184,7 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
||||||
chore: chModel.Chore{
|
chore: chModel.Chore{
|
||||||
FrequencyType: chModel.FrequencyTypeDayOfTheWeek,
|
FrequencyType: chModel.FrequencyTypeDayOfTheWeek,
|
||||||
IsRolling: true,
|
IsRolling: true,
|
||||||
FrequencyMetadata: jsonPtr(`{"days": ["monday"], "time": "2025-01-20T01:00:00-05:00"}`),
|
FrequencyMetadata: jsonPtr(`{"days": ["monday"], "time": "2025-01-20T18:00:00-05:00"}`),
|
||||||
},
|
},
|
||||||
|
|
||||||
completedDate: now.AddDate(0, 1, 0),
|
completedDate: now.AddDate(0, 1, 0),
|
||||||
|
@ -192,7 +192,7 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
||||||
// Calculate next Thursday at 18:00 EST
|
// Calculate next Thursday at 18:00 EST
|
||||||
completedDate := now.AddDate(0, 1, 0)
|
completedDate := now.AddDate(0, 1, 0)
|
||||||
nextMonday := completedDate.AddDate(0, 0, (int(time.Monday)-int(completedDate.Weekday())+7)%7)
|
nextMonday := completedDate.AddDate(0, 0, (int(time.Monday)-int(completedDate.Weekday())+7)%7)
|
||||||
nextMonday = truncateToDay(nextMonday).Add(6*time.Hour + 0*time.Minute)
|
nextMonday = truncateToDay(nextMonday).Add(18*time.Hour + 0*time.Minute)
|
||||||
return &nextMonday
|
return &nextMonday
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
@ -214,10 +214,10 @@ func TestScheduleNextDueDateDayOfMonth(t *testing.T) {
|
||||||
chore: chModel.Chore{
|
chore: chModel.Chore{
|
||||||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||||
Frequency: 15,
|
Frequency: 15,
|
||||||
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T14:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T18:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
||||||
},
|
},
|
||||||
completedDate: now,
|
completedDate: now,
|
||||||
want: timePtr(time.Date(2025, 1, 15, 19, 0, 0, 0, location)),
|
want: timePtr(time.Date(2025, 1, 15, 18, 0, 0, 0, location)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Day of the month - 15th of January(isRolling)",
|
name: "Day of the month - 15th of January(isRolling)",
|
||||||
|
@ -225,10 +225,10 @@ func TestScheduleNextDueDateDayOfMonth(t *testing.T) {
|
||||||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||||
Frequency: 15,
|
Frequency: 15,
|
||||||
IsRolling: true,
|
IsRolling: true,
|
||||||
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T02:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T18:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
||||||
},
|
},
|
||||||
completedDate: now.AddDate(1, 1, 0),
|
completedDate: now.AddDate(1, 1, 0),
|
||||||
want: timePtr(time.Date(2027, 1, 15, 7, 0, 0, 0, location)),
|
want: timePtr(time.Date(2027, 1, 15, 18, 0, 0, 0, location)),
|
||||||
},
|
},
|
||||||
// test if completed before the 15th of the month:
|
// test if completed before the 15th of the month:
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,9 +3,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
migrate "github.com/rubenv/sql-migrate"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
|
|
||||||
"donetick.com/core/config"
|
"donetick.com/core/config"
|
||||||
chModel "donetick.com/core/internal/chore/model"
|
chModel "donetick.com/core/internal/chore/model"
|
||||||
|
@ -15,7 +13,9 @@ import (
|
||||||
stModel "donetick.com/core/internal/subtask/model"
|
stModel "donetick.com/core/internal/subtask/model"
|
||||||
tModel "donetick.com/core/internal/thing/model"
|
tModel "donetick.com/core/internal/thing/model"
|
||||||
uModel "donetick.com/core/internal/user/model" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
|
uModel "donetick.com/core/internal/user/model" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
|
||||||
"donetick.com/core/migrations"
|
migrations "donetick.com/core/migrations"
|
||||||
|
migrate "github.com/rubenv/sql-migrate"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations/*.sql
|
||||||
|
@ -52,14 +52,9 @@ func MigrationScripts(gormDB *gorm.DB, cfg *config.Config) error {
|
||||||
Root: "migrations",
|
Root: "migrations",
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialect string
|
path := os.Getenv("DT_SQLITE_PATH")
|
||||||
switch cfg.Database.Type {
|
if path == "" {
|
||||||
case "postgres":
|
path = "donetick.db"
|
||||||
dialect = "postgres"
|
|
||||||
case "sqlite":
|
|
||||||
dialect = "sqlite3"
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported database type: %s", cfg.Database.Type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := gormDB.DB()
|
db, err := gormDB.DB()
|
||||||
|
@ -67,7 +62,7 @@ func MigrationScripts(gormDB *gorm.DB, cfg *config.Config) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
|
n, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func NewEmailSender(conf *config.Config) *EmailSender {
|
||||||
func (es *EmailSender) SendVerificationEmail(to, code string) error {
|
func (es *EmailSender) SendVerificationEmail(to, code string) error {
|
||||||
// msg := []byte(fmt.Sprintf("To: %s\r\nSubject: %s\r\n\r\n%s\r\n", to, subject, body))
|
// msg := []byte(fmt.Sprintf("To: %s\r\nSubject: %s\r\n\r\n%s\r\n", to, subject, body))
|
||||||
msg := gomail.NewMessage()
|
msg := gomail.NewMessage()
|
||||||
msg.SetHeader("From", es.client.Username)
|
msg.SetHeader("From", "no-reply@donetick.com")
|
||||||
msg.SetHeader("To", to)
|
msg.SetHeader("To", to)
|
||||||
msg.SetHeader("Subject", "Welcome to Donetick! Verifiy you email")
|
msg.SetHeader("Subject", "Welcome to Donetick! Verifiy you email")
|
||||||
// text/html for a html email
|
// text/html for a html email
|
||||||
|
@ -259,7 +259,7 @@ func (es *EmailSender) SendVerificationEmail(to, code string) error {
|
||||||
|
|
||||||
func (es *EmailSender) SendResetPasswordEmail(c context.Context, to, code string) error {
|
func (es *EmailSender) SendResetPasswordEmail(c context.Context, to, code string) error {
|
||||||
msg := gomail.NewMessage()
|
msg := gomail.NewMessage()
|
||||||
msg.SetHeader("From", es.client.Username)
|
msg.SetHeader("From", "no-reply@donetick.com")
|
||||||
msg.SetHeader("To", to)
|
msg.SetHeader("To", to)
|
||||||
msg.SetHeader("Subject", "Donetick! Password Reset")
|
msg.SetHeader("Subject", "Donetick! Password Reset")
|
||||||
htmlBody := `
|
htmlBody := `
|
||||||
|
|
|
@ -37,7 +37,6 @@ const (
|
||||||
NotificationPlatformTelegram
|
NotificationPlatformTelegram
|
||||||
NotificationPlatformPushover
|
NotificationPlatformPushover
|
||||||
NotificationPlatformWebhook
|
NotificationPlatformWebhook
|
||||||
NotificationPlatformDiscord
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONB map[string]interface{}
|
type JSONB map[string]interface{}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
|
|
||||||
"donetick.com/core/internal/events"
|
"donetick.com/core/internal/events"
|
||||||
nModel "donetick.com/core/internal/notifier/model"
|
nModel "donetick.com/core/internal/notifier/model"
|
||||||
"donetick.com/core/internal/notifier/service/discord"
|
|
||||||
pushover "donetick.com/core/internal/notifier/service/pushover"
|
pushover "donetick.com/core/internal/notifier/service/pushover"
|
||||||
telegram "donetick.com/core/internal/notifier/service/telegram"
|
telegram "donetick.com/core/internal/notifier/service/telegram"
|
||||||
|
|
||||||
|
@ -15,16 +14,14 @@ import (
|
||||||
type Notifier struct {
|
type Notifier struct {
|
||||||
Telegram *telegram.TelegramNotifier
|
Telegram *telegram.TelegramNotifier
|
||||||
Pushover *pushover.Pushover
|
Pushover *pushover.Pushover
|
||||||
discord *discord.DiscordNotifier
|
|
||||||
eventsProducer *events.EventsProducer
|
eventsProducer *events.EventsProducer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotifier(t *telegram.TelegramNotifier, p *pushover.Pushover, ep *events.EventsProducer, d *discord.DiscordNotifier) *Notifier {
|
func NewNotifier(t *telegram.TelegramNotifier, p *pushover.Pushover, ep *events.EventsProducer) *Notifier {
|
||||||
return &Notifier{
|
return &Notifier{
|
||||||
Telegram: t,
|
Telegram: t,
|
||||||
Pushover: p,
|
Pushover: p,
|
||||||
eventsProducer: ep,
|
eventsProducer: ep,
|
||||||
discord: d,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +41,6 @@ func (n *Notifier) SendNotification(c context.Context, notification *nModel.Noti
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err = n.Pushover.SendNotification(c, notification)
|
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:
|
case nModel.NotificationPlatformWebhook:
|
||||||
// TODO: Implement webhook notification
|
// TODO: Implement webhook notification
|
||||||
// currently we have eventProducer to send events always as a webhook
|
// currently we have eventProducer to send events always as a webhook
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
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,5 +9,4 @@ type SubTask struct {
|
||||||
Name string `json:"name" gorm:"column:name"`
|
Name string `json:"name" gorm:"column:name"`
|
||||||
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"`
|
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"`
|
||||||
CompletedBy int `json:"completedBy" gorm:"column:completed_by"`
|
CompletedBy int `json:"completedBy" gorm:"column:completed_by"`
|
||||||
ParentId *int `json:"parentId" gorm:"column:parent_id"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,7 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
||||||
var insertions []stModel.SubTask
|
var insertions []stModel.SubTask
|
||||||
var updates []stModel.SubTask
|
var updates []stModel.SubTask
|
||||||
for _, subtask := range toBeAdd {
|
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)
|
insertions = append(insertions, subtask)
|
||||||
} else {
|
} else {
|
||||||
updates = append(updates, subtask)
|
updates = append(updates, subtask)
|
||||||
|
@ -53,14 +51,7 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
||||||
}
|
}
|
||||||
if len(updates) > 0 {
|
if len(updates) > 0 {
|
||||||
for _, subtask := range updates {
|
for _, subtask := range updates {
|
||||||
values := map[string]interface{}{
|
if err := tx.Model(&stModel.SubTask{}).Where("chore_id = ? AND id = ?", choreId, subtask.ID).Updates(subtask).Error; err != nil {
|
||||||
"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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,29 +61,13 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SubTasksRepository) DeleteSubtask(c context.Context, tx *gorm.DB, subtaskID int) error {
|
func (r *SubTasksRepository) DeleteSubtask(c context.Context, tx *gorm.DB, subtaskID int) error {
|
||||||
if tx != nil {
|
if tx != nil {
|
||||||
return r.deleteSubtaskWithChildren(c, tx, subtaskID)
|
|
||||||
}
|
|
||||||
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
|
return tx.Delete(&stModel.SubTask{}, subtaskID).Error
|
||||||
}
|
}
|
||||||
|
return r.db.WithContext(c).Delete(&stModel.SubTask{}, subtaskID).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (r *SubTasksRepository) UpdateSubTaskStatus(c context.Context, userID int, subtaskID int, completedAt *time.Time) error {
|
func (r *SubTasksRepository) UpdateSubTaskStatus(c context.Context, userID int, subtaskID int, completedAt *time.Time) error {
|
||||||
return r.db.Model(&stModel.SubTask{}).Where("id = ?", subtaskID).Updates(map[string]interface{}{
|
return r.db.Model(&stModel.SubTask{}).Where("id = ?", subtaskID).Updates(map[string]interface{}{
|
||||||
|
|
2
main.go
2
main.go
|
@ -31,7 +31,6 @@ import (
|
||||||
notifier "donetick.com/core/internal/notifier"
|
notifier "donetick.com/core/internal/notifier"
|
||||||
nRepo "donetick.com/core/internal/notifier/repo"
|
nRepo "donetick.com/core/internal/notifier/repo"
|
||||||
nps "donetick.com/core/internal/notifier/service"
|
nps "donetick.com/core/internal/notifier/service"
|
||||||
discord "donetick.com/core/internal/notifier/service/discord"
|
|
||||||
"donetick.com/core/internal/notifier/service/pushover"
|
"donetick.com/core/internal/notifier/service/pushover"
|
||||||
telegram "donetick.com/core/internal/notifier/service/telegram"
|
telegram "donetick.com/core/internal/notifier/service/telegram"
|
||||||
pRepo "donetick.com/core/internal/points/repo"
|
pRepo "donetick.com/core/internal/points/repo"
|
||||||
|
@ -74,7 +73,6 @@ func main() {
|
||||||
// add notifier
|
// add notifier
|
||||||
fx.Provide(pushover.NewPushover),
|
fx.Provide(pushover.NewPushover),
|
||||||
fx.Provide(telegram.NewTelegramNotifier),
|
fx.Provide(telegram.NewTelegramNotifier),
|
||||||
fx.Provide(discord.NewDiscordNotifier),
|
|
||||||
fx.Provide(notifier.NewNotifier),
|
fx.Provide(notifier.NewNotifier),
|
||||||
fx.Provide(events.NewEventsProducer),
|
fx.Provide(events.NewEventsProducer),
|
||||||
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"donetick.com/core/logging"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MigrateFixNotificationMetadataExperimentModal20241212 struct{}
|
|
||||||
|
|
||||||
func (m MigrateFixNotificationMetadataExperimentModal20241212) ID() string {
|
|
||||||
return "20250314_fix_notification_metadata_experiment_modal"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m MigrateFixNotificationMetadataExperimentModal20241212) Description() string {
|
|
||||||
return `Fix notification metadata for experiment modal, where notification metadata is a null string 'null' to empty json {}`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m MigrateFixNotificationMetadataExperimentModal20241212) Down(ctx context.Context, db *gorm.DB) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m MigrateFixNotificationMetadataExperimentModal20241212) Up(ctx context.Context, db *gorm.DB) error {
|
|
||||||
log := logging.FromContext(ctx)
|
|
||||||
|
|
||||||
// Start a transaction
|
|
||||||
return db.Transaction(func(tx *gorm.DB) error {
|
|
||||||
// Update all chore where notification metadata is a null stirng 'null' to empty json {}:
|
|
||||||
|
|
||||||
if err := tx.Table("chores").Where("notification_meta = ?", "null").Update("notification_meta", "{}").Error; err != nil {
|
|
||||||
log.Errorf("Failed to update chores with null notification metadata: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register this migration
|
|
||||||
func init() {
|
|
||||||
Register(MigrateFixNotificationMetadataExperimentModal20241212{})
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue