diff --git a/item/event.go b/item/event.go index b84c1e0..8241645 100644 --- a/item/event.go +++ b/item/event.go @@ -10,7 +10,6 @@ import ( type EventBody struct { Title string `json:"title"` - Date Date `json:"date"` Time Time `json:"time"` Duration time.Duration `json:"duration"` } @@ -48,6 +47,7 @@ func (e *EventBody) UnmarshalJSON(data []byte) error { type Event struct { ID string `json:"id"` + Date Date `json:"date"` Recurrer Recurrer `json:"recurrer"` RecurNext Date `json:"recurNext"` EventBody @@ -64,6 +64,7 @@ func NewEvent(i Item) (Event, error) { } e.ID = i.ID + e.Date = i.Date e.Recurrer = i.Recurrer e.RecurNext = i.RecurNext @@ -79,6 +80,7 @@ func (e Event) Item() (Item, error) { return Item{ ID: e.ID, Kind: KindEvent, + Date: e.Date, Recurrer: e.Recurrer, RecurNext: e.RecurNext, Body: string(body), @@ -105,3 +107,10 @@ func EventDiff(a, b Event) string { return cmp.Diff(string(aJSON), string(bJSON)) } + +func EventDiffs(a, b []Event) string { + aJSON, _ := json.Marshal(a) + bJSON, _ := json.Marshal(b) + + return cmp.Diff(string(aJSON), string(bJSON)) +} diff --git a/item/event_test.go b/item/event_test.go index da86c2e..7c20bda 100644 --- a/item/event_test.go +++ b/item/event_test.go @@ -25,10 +25,10 @@ func TestNewEvent(t *testing.T) { name: "wrong kind", it: item.Item{ ID: "a", + Date: item.NewDate(2024, 9, 20), Kind: item.KindTask, Body: `{ "title":"title", - "date":"2024-09-20", "time":"08:00", "duration":"1h" }`, @@ -49,20 +49,20 @@ func TestNewEvent(t *testing.T) { it: item.Item{ ID: "a", Kind: item.KindEvent, + Date: item.NewDate(2024, 9, 20), Recurrer: item.NewRecurrer("2024-12-08, daily"), Body: `{ "title":"title", - "date":"2024-09-20", "time":"08:00", "duration":"1h" }`, }, expEvent: item.Event{ ID: "a", + Date: item.NewDate(2024, 9, 20), Recurrer: item.NewRecurrer("2024-12-08, daily"), EventBody: item.EventBody{ Title: "title", - Date: item.NewDate(2024, 9, 20), Time: item.NewTime(8, 0), Duration: oneHour, }, @@ -102,16 +102,16 @@ func TestEventItem(t *testing.T) { expItem: item.Item{ Kind: item.KindEvent, Updated: time.Time{}, - Body: `{"duration":"0s","title":"","date":"no date","time":"00:00"}`, + Body: `{"duration":"0s","title":"","time":"00:00"}`, }, }, { name: "normal", event: item.Event{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 9, 23), EventBody: item.EventBody{ Title: "title", - Date: item.NewDate(2024, 9, 23), Time: item.NewTime(8, 0), Duration: oneHour, }, @@ -120,7 +120,8 @@ func TestEventItem(t *testing.T) { ID: "a", Kind: item.KindEvent, Updated: time.Time{}, - Body: `{"duration":"1h0m0s","title":"title","date":"2024-09-23","time":"08:00"}`, + Date: item.NewDate(2024, 9, 23), + Body: `{"duration":"1h0m0s","title":"title","time":"08:00"}`, }, }, } { @@ -158,9 +159,9 @@ func TestEventValidate(t *testing.T) { { name: "missing title", event: item.Event{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 9, 20), EventBody: item.EventBody{ - Date: item.NewDate(2024, 9, 20), Time: item.NewTime(8, 0), Duration: oneHour, }, @@ -180,10 +181,10 @@ func TestEventValidate(t *testing.T) { { name: "no duration", event: item.Event{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 9, 20), EventBody: item.EventBody{ Title: "title", - Date: item.NewDate(2024, 9, 20), Time: item.NewTime(8, 0), }, }, @@ -191,10 +192,10 @@ func TestEventValidate(t *testing.T) { { name: "valid", event: item.Event{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 9, 20), EventBody: item.EventBody{ Title: "title", - Date: item.NewDate(2024, 9, 20), Time: item.NewTime(8, 0), Duration: oneHour, }, diff --git a/plan/command/add.go b/plan/command/add.go index c50c424..fd70261 100644 --- a/plan/command/add.go +++ b/plan/command/add.go @@ -3,7 +3,6 @@ package command import ( "fmt" "strings" - "time" "github.com/google/uuid" "go-mod.ewintr.nl/planner/item" @@ -24,12 +23,10 @@ func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage syncRepo: syncRepo, argSet: &ArgSet{ Flags: map[string]Flag{ - FlagOn: &FlagDate{}, - FlagAt: &FlagTime{}, - FlagFor: &FlagDuration{}, - FlagRecStart: &FlagDate{}, - FlagRecPeriod: &FlagPeriod{}, - FlagRecCount: &FlagInt{}, + FlagOn: &FlagDate{}, + FlagAt: &FlagTime{}, + FlagFor: &FlagDuration{}, + FlagRec: &FlagRecurrer{}, }, }, } @@ -71,48 +68,22 @@ func (add *Add) Execute(main []string, flags map[string]string) error { return fmt.Errorf("could not set duration to 24 hours") } } - var recCount int - for _, f := range []string{FlagRecStart, FlagRecPeriod, FlagRecCount} { - if as.IsSet(f) { - recCount++ - } - } - if recCount != 0 && recCount != 3 { - return fmt.Errorf("rec-start, rec-period and rec-count must either all be present, or none of them") - } return add.do() } func (add *Add) do() error { as := add.argSet - start := as.GetTime(FlagOn) - if as.IsSet(FlagAt) { - at := as.GetTime(FlagAt) - h := time.Duration(at.Hour()) * time.Hour - m := time.Duration(at.Minute()) * time.Minute - start = start.Add(h).Add(m) - } - e := item.Event{ - ID: uuid.New().String(), + ID: uuid.New().String(), + Date: as.GetDate(FlagOn), EventBody: item.EventBody{ - Title: as.Main, - Start: start, + Title: as.Main, + Time: as.GetTime(FlagAt), + Duration: as.GetDuration(FlagFor), }, } - if as.IsSet(FlagFor) { - e.Duration = as.GetDuration(FlagFor) - } - if as.IsSet(FlagRecStart) { - e.Recurrer = &item.Recur{ - Start: as.GetTime(FlagRecStart), - Period: as.GetRecurPeriod(FlagRecPeriod), - Count: as.GetInt(FlagRecCount), - } - } - if err := add.eventRepo.Store(e); err != nil { return fmt.Errorf("could not store event: %v", err) } diff --git a/plan/command/add_test.go b/plan/command/add_test.go index 3052627..212ad69 100644 --- a/plan/command/add_test.go +++ b/plan/command/add_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/storage/memory" @@ -13,13 +12,11 @@ import ( func TestAdd(t *testing.T) { t.Parallel() - aDateStr := "2024-11-02" - aDate := time.Date(2024, 11, 2, 0, 0, 0, 0, time.UTC) - aTimeStr := "12:00" + aDate := item.NewDate(2024, 11, 2) + aTime := item.NewTime(12, 0) aDay := time.Duration(24) * time.Hour anHourStr := "1h" anHour := time.Hour - aDateAndTime := time.Date(2024, 11, 2, 12, 0, 0, 0, time.UTC) for _, tc := range []struct { name string @@ -36,7 +33,7 @@ func TestAdd(t *testing.T) { name: "title missing", main: []string{"add"}, flags: map[string]string{ - command.FlagOn: aDateStr, + command.FlagOn: aDate.String(), }, expErr: true, }, @@ -49,46 +46,31 @@ func TestAdd(t *testing.T) { name: "only date", main: []string{"add", "title"}, flags: map[string]string{ - command.FlagOn: aDateStr, + command.FlagOn: aDate.String(), }, expEvent: item.Event{ - ID: "title", + ID: "title", + Date: aDate, EventBody: item.EventBody{ Title: "title", - Start: aDate, Duration: aDay, }, }, }, - { - name: "date and time", - main: []string{"add", "title"}, - flags: map[string]string{ - command.FlagOn: aDateStr, - command.FlagAt: aTimeStr, - }, - expEvent: item.Event{ - ID: "title", - EventBody: item.EventBody{ - Title: "title", - Start: aDateAndTime, - Duration: anHour, - }, - }, - }, { name: "date, time and duration", main: []string{"add", "title"}, flags: map[string]string{ - command.FlagOn: aDateStr, - command.FlagAt: aTimeStr, + command.FlagOn: aDate.String(), + command.FlagAt: aTime.String(), command.FlagFor: anHourStr, }, expEvent: item.Event{ - ID: "title", + ID: "title", + Date: aDate, EventBody: item.EventBody{ Title: "title", - Start: aDateAndTime, + Time: aTime, Duration: anHour, }, }, @@ -97,53 +79,11 @@ func TestAdd(t *testing.T) { name: "date and duration", main: []string{"add", "title"}, flags: map[string]string{ - command.FlagOn: aDateStr, + command.FlagOn: aDate.String(), command.FlagFor: anHourStr, }, expErr: true, }, - { - name: "rec-start without rec-period", - main: []string{"add", "title"}, - flags: map[string]string{ - command.FlagOn: aDateStr, - command.FlagRecStart: "2024-12-08", - }, - expErr: true, - }, - { - name: "rec-period without rec-start", - main: []string{"add", "title"}, - flags: map[string]string{ - command.FlagOn: aDateStr, - command.FlagRecPeriod: "day", - }, - expErr: true, - }, - { - name: "rec-start with rec-period and rec-count", - main: []string{"add", "title"}, - flags: map[string]string{ - command.FlagOn: aDateStr, - command.FlagRecStart: "2024-12-08", - command.FlagRecPeriod: "day", - command.FlagRecCount: "1", - }, - expEvent: item.Event{ - ID: "title", - Recurrer: &item.Recur{ - Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC), - Period: item.PeriodDay, - Count: 1, - }, - RecurNext: time.Time{}, - EventBody: item.EventBody{ - Title: "title", - Start: aDate, - Duration: aDay, - }, - }, - }, } { t.Run(tc.name, func(t *testing.T) { eventRepo := memory.NewEvent() @@ -181,7 +121,7 @@ func TestAdd(t *testing.T) { t.Errorf("exp string not te be empty") } tc.expEvent.ID = actEvents[0].ID - if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { + if diff := item.EventDiff(tc.expEvent, actEvents[0]); diff != "" { t.Errorf("(exp -, got +)\n%s", diff) } diff --git a/plan/command/argset.go b/plan/command/argset.go index 81c4e5f..7464d1f 100644 --- a/plan/command/argset.go +++ b/plan/command/argset.go @@ -40,14 +40,26 @@ func (as *ArgSet) GetString(name string) string { return val } -func (as *ArgSet) GetTime(name string) time.Time { +func (as *ArgSet) GetDate(name string) item.Date { flag, ok := as.Flags[name] if !ok { - return time.Time{} + return item.Date{} } - val, ok := flag.Get().(time.Time) + val, ok := flag.Get().(item.Date) if !ok { - return time.Time{} + return item.Date{} + } + return val +} + +func (as *ArgSet) GetTime(name string) item.Time { + flag, ok := as.Flags[name] + if !ok { + return item.Time{} + } + val, ok := flag.Get().(item.Time) + if !ok { + return item.Time{} } return val } @@ -64,14 +76,14 @@ func (as *ArgSet) GetDuration(name string) time.Duration { return val } -func (as *ArgSet) GetRecurPeriod(name string) item.RecurPeriod { +func (as *ArgSet) GetRecurrer(name string) item.Recurrer { flag, ok := as.Flags[name] if !ok { - return item.RecurPeriod("") + return nil } - val, ok := flag.Get().(item.RecurPeriod) + val, ok := flag.Get().(item.Recurrer) if !ok { - return item.RecurPeriod("") + return nil } return val } diff --git a/plan/command/argset_test.go b/plan/command/argset_test.go index 2261074..0e03ede 100644 --- a/plan/command/argset_test.go +++ b/plan/command/argset_test.go @@ -56,11 +56,11 @@ func TestArgSet(t *testing.T) { { name: "recur period flag success", flags: map[string]command.Flag{ - "period": &command.FlagPeriod{Name: "period"}, + "recur": &command.FlagRecurrer{Name: "recur"}, }, - flagName: "period", - setValue: "month", - exp: item.PeriodMonth, + flagName: "recur", + setValue: "2024-12-23, daily", + exp: item.NewRecurrer("2024-12-23, daily"), }, { name: "unknown flag error", @@ -95,26 +95,9 @@ func TestArgSet(t *testing.T) { return } - // Verify IsSet() returns true after setting if !as.IsSet(tt.flagName) { t.Errorf("ArgSet.IsSet() = false, want true for flag %s", tt.flagName) } - - // Verify the value was set correctly based on flag type - switch v := tt.exp.(type) { - case string: - if got := as.GetString(tt.flagName); got != v { - t.Errorf("ArgSet.GetString() = %v, want %v", got, v) - } - case time.Time: - if got := as.GetTime(tt.flagName); !got.Equal(v) { - t.Errorf("ArgSet.GetTime() = %v, want %v", got, v) - } - case time.Duration: - if got := as.GetDuration(tt.flagName); got != v { - t.Errorf("ArgSet.GetDuration() = %v, want %v", got, v) - } - } }) } } diff --git a/plan/command/command.go b/plan/command/command.go index 61aa520..6c1daa8 100644 --- a/plan/command/command.go +++ b/plan/command/command.go @@ -7,13 +7,11 @@ import ( ) const ( - FlagTitle = "title" - FlagOn = "on" - FlagAt = "at" - FlagFor = "for" - FlagRecStart = "rec-start" - FlagRecPeriod = "rec-period" - FlagRecCount = "rec-count" + FlagTitle = "title" + FlagOn = "on" + FlagAt = "at" + FlagFor = "for" + FlagRec = "rec" ) type Command interface { diff --git a/plan/command/delete_test.go b/plan/command/delete_test.go index 607516a..9005498 100644 --- a/plan/command/delete_test.go +++ b/plan/command/delete_test.go @@ -3,7 +3,6 @@ package command_test import ( "errors" "testing" - "time" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/command" @@ -15,10 +14,10 @@ func TestDelete(t *testing.T) { t.Parallel() e := item.Event{ - ID: "id", + ID: "id", + Date: item.NewDate(2024, 10, 7), EventBody: item.EventBody{ Title: "name", - Start: time.Date(2024, 10, 7, 9, 30, 0, 0, time.UTC), }, } diff --git a/plan/command/flag.go b/plan/command/flag.go index 3912b06..c771e60 100644 --- a/plan/command/flag.go +++ b/plan/command/flag.go @@ -3,7 +3,6 @@ package command import ( "errors" "fmt" - "slices" "strconv" "time" @@ -46,35 +45,35 @@ func (fs *FlagString) Get() any { type FlagDate struct { Name string - Value time.Time + Value item.Date } -func (ft *FlagDate) Set(val string) error { - d, err := time.Parse(DateFormat, val) - if err != nil { +func (fd *FlagDate) Set(val string) error { + d := item.NewDateFromString(val) + if d.IsZero() { return fmt.Errorf("could not parse date: %v", d) } - ft.Value = d + fd.Value = d return nil } -func (ft *FlagDate) IsSet() bool { - return !ft.Value.IsZero() +func (fd *FlagDate) IsSet() bool { + return !fd.Value.IsZero() } -func (fs *FlagDate) Get() any { - return fs.Value +func (fd *FlagDate) Get() any { + return fd.Value } type FlagTime struct { Name string - Value time.Time + Value item.Time } func (ft *FlagTime) Set(val string) error { - d, err := time.Parse(TimeFormat, val) - if err != nil { + d := item.NewTimeFromString(val) + if d.IsZero() { return fmt.Errorf("could not parse date: %v", d) } ft.Value = d @@ -112,25 +111,25 @@ func (fs *FlagDuration) Get() any { return fs.Value } -type FlagPeriod struct { +type FlagRecurrer struct { Name string - Value item.RecurPeriod + Value item.Recurrer } -func (fp *FlagPeriod) Set(val string) error { - if !slices.Contains(item.ValidPeriods, item.RecurPeriod(val)) { - return fmt.Errorf("not a valid period: %v", val) +func (fr *FlagRecurrer) Set(val string) error { + fr.Value = item.NewRecurrer(val) + if fr.Value == nil { + return fmt.Errorf("not a valid recurrer: %v", val) } - fp.Value = item.RecurPeriod(val) return nil } -func (fp *FlagPeriod) IsSet() bool { - return fp.Value != "" +func (fr *FlagRecurrer) IsSet() bool { + return fr.Value != nil } -func (fp *FlagPeriod) Get() any { - return fp.Value +func (fr *FlagRecurrer) Get() any { + return fr.Value } type FlagInt struct { diff --git a/plan/command/flag_test.go b/plan/command/flag_test.go index ae552f4..1a18e90 100644 --- a/plan/command/flag_test.go +++ b/plan/command/flag_test.go @@ -37,14 +37,13 @@ func TestFlagString(t *testing.T) { func TestFlagDate(t *testing.T) { t.Parallel() - valid := time.Date(2024, 11, 20, 0, 0, 0, 0, time.UTC) - validStr := "2024-11-20" + valid := item.NewDate(2024, 11, 20) f := command.FlagDate{} if f.IsSet() { t.Errorf("exp false, got true") } - if err := f.Set(validStr); err != nil { + if err := f.Set(valid.String()); err != nil { t.Errorf("exp nil, got %v", err) } @@ -52,26 +51,25 @@ func TestFlagDate(t *testing.T) { t.Errorf("exp true, got false") } - act, ok := f.Get().(time.Time) + act, ok := f.Get().(item.Date) if !ok { t.Errorf("exp true, got false") } - if act != valid { - t.Errorf("exp %v, got %v", valid, act) + if act.String() != valid.String() { + t.Errorf("exp %v, got %v", valid.String(), act.String()) } } func TestFlagTime(t *testing.T) { t.Parallel() - valid := time.Date(0, 1, 1, 12, 30, 0, 0, time.UTC) - validStr := "12:30" + valid := item.NewTime(12, 30) f := command.FlagTime{} if f.IsSet() { t.Errorf("exp false, got true") } - if err := f.Set(validStr); err != nil { + if err := f.Set(valid.String()); err != nil { t.Errorf("exp nil, got %v", err) } @@ -79,12 +77,12 @@ func TestFlagTime(t *testing.T) { t.Errorf("exp true, got false") } - act, ok := f.Get().(time.Time) + act, ok := f.Get().(item.Time) if !ok { t.Errorf("exp true, got false") } - if act != valid { - t.Errorf("exp %v, got %v", valid, act) + if act.String() != valid.String() { + t.Errorf("exp %v, got %v", valid.String(), act.String()) } } @@ -115,12 +113,12 @@ func TestFlagDurationTime(t *testing.T) { } } -func TestFlagPeriod(t *testing.T) { +func TestFlagRecurrer(t *testing.T) { t.Parallel() - valid := item.PeriodMonth - validStr := "month" - f := command.FlagPeriod{} + validStr := "2024-12-23, daily" + valid := item.NewRecurrer(validStr) + f := command.FlagRecurrer{} if f.IsSet() { t.Errorf("exp false, got true") } @@ -133,7 +131,7 @@ func TestFlagPeriod(t *testing.T) { t.Errorf("exp true, got false") } - act, ok := f.Get().(item.RecurPeriod) + act, ok := f.Get().(item.Recurrer) if !ok { t.Errorf("exp true, got false") } diff --git a/plan/command/list.go b/plan/command/list.go index e94ca91..a41ae66 100644 --- a/plan/command/list.go +++ b/plan/command/list.go @@ -2,7 +2,6 @@ package command import ( "fmt" - "time" "go-mod.ewintr.nl/planner/plan/storage" ) @@ -41,7 +40,7 @@ func (list *List) do() error { if !ok { return fmt.Errorf("could not find local id for %s", e.ID) } - fmt.Printf("%s\t%d\t%s\t%s\t%s\n", e.ID, lid, e.Title, e.Start.Format(time.DateTime), e.Duration.String()) + fmt.Printf("%s\t%d\t%s\t%s\t%s\n", e.ID, lid, e.Title, e.Date.String(), e.Duration.String()) } return nil diff --git a/plan/command/list_test.go b/plan/command/list_test.go index 53c8707..67033ad 100644 --- a/plan/command/list_test.go +++ b/plan/command/list_test.go @@ -2,7 +2,6 @@ package command_test import ( "testing" - "time" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/command" @@ -15,10 +14,10 @@ func TestList(t *testing.T) { eventRepo := memory.NewEvent() localRepo := memory.NewLocalID() e := item.Event{ - ID: "id", + ID: "id", + Date: item.NewDate(2024, 10, 7), EventBody: item.EventBody{ Title: "name", - Start: time.Date(2024, 10, 7, 9, 30, 0, 0, time.UTC), }, } if err := eventRepo.Store(e); err != nil { diff --git a/plan/command/sync_test.go b/plan/command/sync_test.go index c50151f..080d04a 100644 --- a/plan/command/sync_test.go +++ b/plan/command/sync_test.go @@ -137,10 +137,10 @@ func TestSyncReceive(t *testing.T) { }`, }}, expEvent: []item.Event{{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 10, 23), EventBody: item.EventBody{ Title: "title", - Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC), Duration: oneHour, }, }}, @@ -151,10 +151,10 @@ func TestSyncReceive(t *testing.T) { { name: "update existing", present: []item.Event{{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 10, 23), EventBody: item.EventBody{ Title: "title", - Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC), Duration: oneHour, }, }}, @@ -168,10 +168,10 @@ func TestSyncReceive(t *testing.T) { }`, }}, expEvent: []item.Event{{ - ID: "a", + ID: "a", + Date: item.NewDate(2024, 10, 23), EventBody: item.EventBody{ Title: "new title", - Start: time.Date(2024, 10, 23, 8, 0, 0, 0, time.UTC), Duration: oneHour, }, }}, @@ -210,7 +210,7 @@ func TestSyncReceive(t *testing.T) { if err != nil { t.Errorf("exp nil, got %v", err) } - if diff := cmp.Diff(tc.expEvent, actEvents); diff != "" { + if diff := item.EventDiffs(tc.expEvent, actEvents); diff != "" { t.Errorf("(exp +, got -)\n%s", diff) } actLocalIDs, err := localIDRepo.FindAll() diff --git a/plan/command/update.go b/plan/command/update.go index e958346..16a4749 100644 --- a/plan/command/update.go +++ b/plan/command/update.go @@ -4,9 +4,7 @@ import ( "fmt" "strconv" "strings" - "time" - "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/storage" ) @@ -25,12 +23,11 @@ func NewUpdate(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo st syncRepo: syncRepo, argSet: &ArgSet{ Flags: map[string]Flag{ - FlagTitle: &FlagString{}, - FlagOn: &FlagDate{}, - FlagAt: &FlagTime{}, - FlagFor: &FlagDuration{}, - FlagRecStart: &FlagDate{}, - FlagRecPeriod: &FlagPeriod{}, + FlagTitle: &FlagString{}, + FlagOn: &FlagDate{}, + FlagAt: &FlagTime{}, + FlagFor: &FlagDuration{}, + FlagRec: &FlagRecurrer{}, }, }, } @@ -87,35 +84,17 @@ func (update *Update) do() error { if as.Main != "" { e.Title = as.Main } - if as.IsSet(FlagOn) || as.IsSet(FlagAt) { - on := time.Date(e.Start.Year(), e.Start.Month(), e.Start.Day(), 0, 0, 0, 0, time.UTC) - atH := time.Duration(e.Start.Hour()) * time.Hour - atM := time.Duration(e.Start.Minute()) * time.Minute - - if as.IsSet(FlagOn) { - on = as.GetTime(FlagOn) - } - if as.IsSet(FlagAt) { - at := as.GetTime(FlagAt) - atH = time.Duration(at.Hour()) * time.Hour - atM = time.Duration(at.Minute()) * time.Minute - } - e.Start = on.Add(atH).Add(atM) + if as.IsSet(FlagOn) { + e.Date = as.GetDate(FlagOn) + } + if as.IsSet(FlagAt) { + e.Time = as.GetTime(FlagAt) } - if as.IsSet(FlagFor) { e.Duration = as.GetDuration(FlagFor) } - if as.IsSet(FlagRecStart) || as.IsSet(FlagRecPeriod) { - if e.Recurrer == nil { - e.Recurrer = &item.Recur{} - } - if as.IsSet(FlagRecStart) { - e.Recurrer.Start = as.GetTime(FlagRecStart) - } - if as.IsSet(FlagRecPeriod) { - e.Recurrer.Period = as.GetRecurPeriod(FlagRecPeriod) - } + if as.IsSet(FlagRec) { + e.Recurrer = as.GetRecurrer(FlagRec) } if !e.Valid() { diff --git a/plan/command/update_test.go b/plan/command/update_test.go index 14b5575..98db5c1 100644 --- a/plan/command/update_test.go +++ b/plan/command/update_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/storage/memory" @@ -21,7 +20,8 @@ func TestUpdateExecute(t *testing.T) { t.Errorf("exp nil, got %v", err) } title := "title" - start := time.Date(2024, 10, 6, 10, 0, 0, 0, time.UTC) + aDate := item.NewDate(2024, 10, 6) + aTime := item.NewTime(10, 0) twoHour, err := time.ParseDuration("2h") if err != nil { t.Errorf("exp nil, got %v", err) @@ -49,10 +49,11 @@ func TestUpdateExecute(t *testing.T) { localID: lid, main: []string{"update", fmt.Sprintf("%d", lid), "updated"}, expEvent: item.Event{ - ID: eid, + ID: eid, + Date: item.NewDate(2024, 10, 6), EventBody: item.EventBody{ Title: "updated", - Start: start, + Time: aTime, Duration: oneHour, }, }, @@ -74,10 +75,11 @@ func TestUpdateExecute(t *testing.T) { "on": "2024-10-02", }, expEvent: item.Event{ - ID: eid, + ID: eid, + Date: item.NewDate(2024, 10, 2), EventBody: item.EventBody{ Title: title, - Start: time.Date(2024, 10, 2, 10, 0, 0, 0, time.UTC), + Time: aTime, Duration: oneHour, }, }, @@ -99,10 +101,11 @@ func TestUpdateExecute(t *testing.T) { "at": "11:00", }, expEvent: item.Event{ - ID: eid, + ID: eid, + Date: item.NewDate(2024, 10, 6), EventBody: item.EventBody{ Title: title, - Start: time.Date(2024, 10, 6, 11, 0, 0, 0, time.UTC), + Time: item.NewTime(11, 0), Duration: oneHour, }, }, @@ -116,10 +119,11 @@ func TestUpdateExecute(t *testing.T) { "at": "11:00", }, expEvent: item.Event{ - ID: eid, + ID: eid, + Date: item.NewDate(2024, 10, 2), EventBody: item.EventBody{ Title: title, - Start: time.Date(2024, 10, 2, 11, 0, 0, 0, time.UTC), + Time: item.NewTime(11, 0), Duration: oneHour, }, }, @@ -141,62 +145,36 @@ func TestUpdateExecute(t *testing.T) { "for": "2h", }, expEvent: item.Event{ - ID: eid, + ID: eid, + Date: item.NewDate(2024, 10, 6), EventBody: item.EventBody{ Title: title, - Start: time.Date(2024, 10, 6, 10, 0, 0, 0, time.UTC), + Time: aTime, Duration: twoHour, }, }, }, { - name: "invalid rec start", + name: "invalid rec", main: []string{"update", fmt.Sprintf("%d", lid)}, flags: map[string]string{ - "rec-start": "invalud", + "rec": "invalud", }, expErr: true, }, { - name: "valid rec start", + name: "valid rec", main: []string{"update", fmt.Sprintf("%d", lid)}, flags: map[string]string{ - "rec-start": "2024-12-08", + "rec": "2024-12-08, daily", }, expEvent: item.Event{ - ID: eid, - Recurrer: &item.Recur{ - Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC), - }, + ID: eid, + Date: aDate, + Recurrer: item.NewRecurrer("2024-12-08, daily"), EventBody: item.EventBody{ Title: title, - Start: start, - Duration: oneHour, - }, - }, - }, - { - name: "invalid rec period", - main: []string{"update", fmt.Sprintf("%d", lid)}, - flags: map[string]string{ - "rec-period": "invalid", - }, - expErr: true, - }, - { - name: "valid rec period", - main: []string{"update", fmt.Sprintf("%d", lid)}, - flags: map[string]string{ - "rec-period": "month", - }, - expEvent: item.Event{ - ID: eid, - Recurrer: &item.Recur{ - Period: item.PeriodMonth, - }, - EventBody: item.EventBody{ - Title: title, - Start: start, + Time: aTime, Duration: oneHour, }, }, @@ -207,10 +185,11 @@ func TestUpdateExecute(t *testing.T) { localIDRepo := memory.NewLocalID() syncRepo := memory.NewSync() if err := eventRepo.Store(item.Event{ - ID: eid, + ID: eid, + Date: aDate, EventBody: item.EventBody{ Title: title, - Start: start, + Time: aTime, Duration: oneHour, }, }); err != nil { @@ -233,7 +212,7 @@ func TestUpdateExecute(t *testing.T) { if err != nil { t.Errorf("exp nil, got %v", err) } - if diff := cmp.Diff(tc.expEvent, actEvent); diff != "" { + if diff := item.EventDiff(tc.expEvent, actEvent); diff != "" { t.Errorf("(exp -, got +)\n%s", diff) } updated, err := syncRepo.FindAll() diff --git a/plan/storage/memory/event_test.go b/plan/storage/memory/event_test.go index 54645c3..a7dea4d 100644 --- a/plan/storage/memory/event_test.go +++ b/plan/storage/memory/event_test.go @@ -3,7 +3,6 @@ package memory import ( "testing" - "github.com/google/go-cmp/cmp" "go-mod.ewintr.nl/planner/item" ) @@ -50,7 +49,7 @@ func TestEvent(t *testing.T) { if actErr != nil { t.Errorf("exp nil, got %v", actErr) } - if diff := cmp.Diff([]item.Event{e1, e2}, actEvents); diff != "" { + if diff := item.EventDiffs([]item.Event{e1, e2}, actEvents); diff != "" { t.Errorf("(exp -, got +)\n%s", diff) } } diff --git a/plan/storage/sqlite/event.go b/plan/storage/sqlite/event.go index 70deea9..c8808b7 100644 --- a/plan/storage/sqlite/event.go +++ b/plan/storage/sqlite/event.go @@ -28,16 +28,17 @@ func (s *SqliteEvent) Store(event item.Event) error { INSERT INTO events (id, title, start, duration, recur) VALUES -(?, ?, ?, ?, ?) +(?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET title=?, -start=?, +date=?, +time=? duration=?, -recur=? +recurrer=? `, - event.ID, event.Title, event.Start.Format(timestampFormat), event.Duration.String(), recurStr, - event.Title, event.Start.Format(timestampFormat), event.Duration.String(), recurStr); err != nil { + event.ID, event.Title, event.Date.String(), event.Time.String(), event.Duration.String(), recurStr, + event.Title, event.Date.String(), event.Time.String(), event.Duration.String(), recurStr); err != nil { return fmt.Errorf("%w: %v", ErrSqliteFailure, err) } return nil @@ -45,37 +46,32 @@ recur=? func (s *SqliteEvent) Find(id string) (item.Event, error) { var event item.Event - var durStr string - var recurStr *string + var dateStr, timeStr, recurStr, durStr string err := s.db.QueryRow(` -SELECT id, title, start, duration, recur +SELECT id, title, date, time, duration, recurrer FROM events -WHERE id = ?`, id).Scan(&event.ID, &event.Title, &event.Start, &durStr, &recurStr) +WHERE id = ?`, id).Scan(&event.ID, &event.Title, &dateStr, &timeStr, &durStr, &recurStr) switch { case err == sql.ErrNoRows: return item.Event{}, fmt.Errorf("event not found: %w", err) case err != nil: return item.Event{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) } + event.Date = item.NewDateFromString(dateStr) + event.Time = item.NewTimeFromString(timeStr) dur, err := time.ParseDuration(durStr) if err != nil { return item.Event{}, fmt.Errorf("could not unmarshal recurrer: %v", err) } event.Duration = dur - if recurStr != nil { - var rec item.Recur - if err := json.Unmarshal([]byte(*recurStr), &rec); err != nil { - return item.Event{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - event.Recurrer = &rec - } + event.Recurrer = item.NewRecurrer(recurStr) return event, nil } func (s *SqliteEvent) FindAll() ([]item.Event, error) { rows, err := s.db.Query(` -SELECT id, title, start, duration, recur +SELECT id, title, date, time, duration, recurrer FROM events`) if err != nil { return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) @@ -84,23 +80,19 @@ FROM events`) defer rows.Close() for rows.Next() { var event item.Event - var durStr string - var recurStr *string - if err := rows.Scan(&event.ID, &event.Title, &event.Start, &durStr, &recurStr); err != nil { + var dateStr, timeStr, recurStr, durStr string + if err := rows.Scan(&event.ID, &event.Title, &dateStr, &timeStr, &durStr, &recurStr); err != nil { return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) } dur, err := time.ParseDuration(durStr) if err != nil { return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) } + event.Date = item.NewDateFromString(dateStr) + event.Time = item.NewTimeFromString(timeStr) event.Duration = dur - if recurStr != nil { - var rec item.Recur - if err := json.Unmarshal([]byte(*recurStr), &rec); err != nil { - return nil, fmt.Errorf("could not unmarshal recurrer: %v", err) - } - event.Recurrer = &rec - } + event.Recurrer = item.NewRecurrer(recurStr) + result = append(result, event) } diff --git a/plan/storage/sqlite/sqlite.go b/plan/storage/sqlite/sqlite.go index d631863..5c50dd7 100644 --- a/plan/storage/sqlite/sqlite.go +++ b/plan/storage/sqlite/sqlite.go @@ -35,6 +35,13 @@ var migrations = []string{ `ALTER TABLE events DROP COLUMN recur_next`, `ALTER TABLE events ADD COLUMN recur TEXT`, `ALTER TABLE items ADD COLUMN recurrer TEXT`, + `ALTER TABLE events DROP COLUMN recur`, + `ALTER TABLE events ADD COLUMN recurrer TEXT NOT NULL`, + `ALTER TABLE events ADD COLUMN recur_next TEXT NOT NULL`, + `ALTER TABLE events DROP COLUMN start`, + `ALTER TABLE events ADD COLUMN date TEXT NOT NULL`, + `ALTER TABLE events ADD COLUMN time TEXT NOT NULL`, + `ALTER TABLE items ADD COLUMN recur_next TEXT NOT NULL`, } var ( diff --git a/plan/storage/sqlite/sync.go b/plan/storage/sqlite/sync.go index 19fd1a6..9e1e5bf 100644 --- a/plan/storage/sqlite/sync.go +++ b/plan/storage/sqlite/sync.go @@ -2,7 +2,6 @@ package sqlite import ( "database/sql" - "encoding/json" "fmt" "time" @@ -18,7 +17,7 @@ func NewSqliteSync(db *sql.DB) *SqliteSync { } func (s *SqliteSync) FindAll() ([]item.Item, error) { - rows, err := s.db.Query("SELECT id, kind, updated, deleted, recurrer, body FROM items") + rows, err := s.db.Query("SELECT id, kind, updated, deleted, recurrer, recur_next, body FROM items") if err != nil { return nil, fmt.Errorf("%w: failed to query items: %v", ErrSqliteFailure, err) } @@ -27,8 +26,8 @@ func (s *SqliteSync) FindAll() ([]item.Item, error) { var items []item.Item for rows.Next() { var i item.Item - var updatedStr, recurStr string - err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &i.Body) + var updatedStr, recurStr, recurNextStr string + err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &recurNextStr, &i.Body) if err != nil { return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err) } @@ -36,13 +35,9 @@ func (s *SqliteSync) FindAll() ([]item.Item, error) { if err != nil { return nil, fmt.Errorf("failed to parse updated time: %v", err) } - if recurStr != "" { - var recurrer item.Recur - if err := json.Unmarshal([]byte(recurStr), &recurrer); err != nil { - return nil, fmt.Errorf("failed to unmarshal recurrer: %v", err) - } - i.Recurrer = &recurrer - } + i.Recurrer = item.NewRecurrer(recurStr) + i.RecurNext = item.NewDateFromString(recurNextStr) + items = append(items, i) } @@ -59,22 +54,15 @@ func (s *SqliteSync) Store(i item.Item) error { i.Updated = time.Now() } - var recurStr string - if i.Recurrer != nil { - recurBytes, err := json.Marshal(i.Recurrer) - if err != nil { - return fmt.Errorf("failed to marshal recurrer: %v", err) - } - recurStr = string(recurBytes) - } - _, err := s.db.Exec( - "INSERT OR REPLACE INTO items (id, kind, updated, deleted, recurrer, body) VALUES (?, ?, ?, ?, ?, ?)", + `INSERT OR REPLACE INTO items (id, kind, updated, deleted, recurrer, recur_next, body) + VALUES (?, ?, ?, ?, ?, ?)`, i.ID, i.Kind, i.Updated.UTC().Format(time.RFC3339), i.Deleted, - recurStr, + i.Recurrer.String(), + i.RecurNext.String(), sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL ) if err != nil {