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:
|
||||
type: "sqlite"
|
||||
migration: true
|
||||
# these are only required for postgres
|
||||
host: "secret"
|
||||
port: 5432
|
||||
user: "secret"
|
||||
password: "secret"
|
||||
name: "secret"
|
||||
jwt:
|
||||
secret: "secret"
|
||||
session_time: 168h
|
||||
|
|
|
@ -8,12 +8,6 @@ pushover:
|
|||
database:
|
||||
type: "sqlite"
|
||||
migration: true
|
||||
# these are only required for postgres
|
||||
host: "secret"
|
||||
port: 5432
|
||||
user: "secret"
|
||||
password: "secret"
|
||||
name: "secret"
|
||||
jwt:
|
||||
secret: "secret"
|
||||
session_time: 168h
|
||||
|
|
|
@ -231,13 +231,7 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
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
|
||||
if len(choreReq.Labels) > 0 {
|
||||
var escapedLabels []string
|
||||
|
@ -470,13 +464,6 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
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 :
|
||||
var stringLabels *string
|
||||
|
|
|
@ -38,8 +38,9 @@ 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)
|
||||
|
|
|
@ -119,7 +119,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
|||
FrequencyMetadata: jsonPtr(`{"unit": "days","time":"2024-07-07T14:30:00-04:00"}`),
|
||||
},
|
||||
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",
|
||||
|
@ -129,7 +129,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
|||
FrequencyMetadata: jsonPtr(`{"unit": "weeks","time":"2024-07-07T14:30:00-04:00"}`),
|
||||
},
|
||||
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",
|
||||
|
@ -139,7 +139,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
|||
FrequencyMetadata: jsonPtr(`{"unit": "months","time":"2024-07-07T14:30:00-04:00"}`),
|
||||
},
|
||||
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",
|
||||
|
@ -149,7 +149,7 @@ func TestScheduleNextDueDateInterval(t *testing.T) {
|
|||
FrequencyMetadata: jsonPtr(`{"unit": "years","time":"2024-07-07T14:30:00-04:00"}`),
|
||||
},
|
||||
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)
|
||||
|
@ -169,13 +169,13 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
|||
chore: chModel.Chore{
|
||||
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,
|
||||
want: func() *time.Time {
|
||||
// Calculate next Monday at 18:00 EST
|
||||
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
|
||||
}(),
|
||||
},
|
||||
|
@ -184,7 +184,7 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
|||
chore: chModel.Chore{
|
||||
FrequencyType: chModel.FrequencyTypeDayOfTheWeek,
|
||||
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),
|
||||
|
@ -192,7 +192,7 @@ func TestScheduleNextDueDateDayOfWeek(t *testing.T) {
|
|||
// Calculate next Thursday at 18:00 EST
|
||||
completedDate := now.AddDate(0, 1, 0)
|
||||
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
|
||||
}(),
|
||||
},
|
||||
|
@ -214,10 +214,10 @@ func TestScheduleNextDueDateDayOfMonth(t *testing.T) {
|
|||
chore: chModel.Chore{
|
||||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||
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,
|
||||
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)",
|
||||
|
@ -225,10 +225,10 @@ func TestScheduleNextDueDateDayOfMonth(t *testing.T) {
|
|||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||
Frequency: 15,
|
||||
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),
|
||||
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:
|
||||
{
|
||||
|
|
|
@ -3,9 +3,7 @@ package database
|
|||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
|
||||
"donetick.com/core/config"
|
||||
chModel "donetick.com/core/internal/chore/model"
|
||||
|
@ -15,7 +13,9 @@ import (
|
|||
stModel "donetick.com/core/internal/subtask/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
|
||||
"donetick.com/core/migrations"
|
||||
migrations "donetick.com/core/migrations"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
|
@ -52,14 +52,9 @@ func MigrationScripts(gormDB *gorm.DB, cfg *config.Config) error {
|
|||
Root: "migrations",
|
||||
}
|
||||
|
||||
var dialect string
|
||||
switch cfg.Database.Type {
|
||||
case "postgres":
|
||||
dialect = "postgres"
|
||||
case "sqlite":
|
||||
dialect = "sqlite3"
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type: %s", cfg.Database.Type)
|
||||
path := os.Getenv("DT_SQLITE_PATH")
|
||||
if path == "" {
|
||||
path = "donetick.db"
|
||||
}
|
||||
|
||||
db, err := gormDB.DB()
|
||||
|
@ -67,7 +62,7 @@ func MigrationScripts(gormDB *gorm.DB, cfg *config.Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
|
||||
n, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func NewEmailSender(conf *config.Config) *EmailSender {
|
|||
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 := gomail.NewMessage()
|
||||
msg.SetHeader("From", es.client.Username)
|
||||
msg.SetHeader("From", "no-reply@donetick.com")
|
||||
msg.SetHeader("To", to)
|
||||
msg.SetHeader("Subject", "Welcome to Donetick! Verifiy you 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 {
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("From", es.client.Username)
|
||||
msg.SetHeader("From", "no-reply@donetick.com")
|
||||
msg.SetHeader("To", to)
|
||||
msg.SetHeader("Subject", "Donetick! Password Reset")
|
||||
htmlBody := `
|
||||
|
|
|
@ -37,7 +37,6 @@ const (
|
|||
NotificationPlatformTelegram
|
||||
NotificationPlatformPushover
|
||||
NotificationPlatformWebhook
|
||||
NotificationPlatformDiscord
|
||||
)
|
||||
|
||||
type JSONB map[string]interface{}
|
||||
|
|
|
@ -5,7 +5,6 @@ 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"
|
||||
|
||||
|
@ -15,16 +14,14 @@ 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, d *discord.DiscordNotifier) *Notifier {
|
||||
func NewNotifier(t *telegram.TelegramNotifier, p *pushover.Pushover, ep *events.EventsProducer) *Notifier {
|
||||
return &Notifier{
|
||||
Telegram: t,
|
||||
Pushover: p,
|
||||
eventsProducer: ep,
|
||||
discord: d,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,13 +41,6 @@ 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
|
||||
|
|
|
@ -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"`
|
||||
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,9 +37,7 @@ 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 {
|
||||
// we interpret this as a new subtask
|
||||
subtask.ID = 0
|
||||
if subtask.ID == 0 {
|
||||
insertions = append(insertions, subtask)
|
||||
} else {
|
||||
updates = append(updates, subtask)
|
||||
|
@ -53,14 +51,7 @@ func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeR
|
|||
}
|
||||
if len(updates) > 0 {
|
||||
for _, subtask := range updates {
|
||||
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 {
|
||||
if err := tx.Model(&stModel.SubTask{}).Where("chore_id = ? AND id = ?", choreId, subtask.ID).Updates(subtask).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -70,28 +61,12 @@ 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 r.deleteSubtaskWithChildren(c, tx, subtaskID)
|
||||
return tx.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
|
||||
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 {
|
||||
|
|
2
main.go
2
main.go
|
@ -31,7 +31,6 @@ 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"
|
||||
|
@ -74,7 +73,6 @@ func main() {
|
|||
// add notifier
|
||||
fx.Provide(pushover.NewPushover),
|
||||
fx.Provide(telegram.NewTelegramNotifier),
|
||||
fx.Provide(discord.NewDiscordNotifier),
|
||||
fx.Provide(notifier.NewNotifier),
|
||||
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