From 95a847d97cd8569a26748d471310dea9a9564e0a Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Fri, 20 Dec 2024 15:37:52 +0100 Subject: [PATCH] pq error on server --- plan/command/add.go | 12 ++++++++++-- plan/command/add_test.go | 4 +++- plan/command/argset.go | 12 ++++++++++++ plan/command/command.go | 1 + plan/command/flag.go | 24 ++++++++++++++++++++++++ plan/storage/sqlite/sqlite.go | 1 + plan/storage/sqlite/sync.go | 30 ++++++++++++++++++++++++------ sync/client/http.go | 3 ++- sync/service/postgres.go | 28 ++++++++++++++-------------- 9 files changed, 91 insertions(+), 24 deletions(-) diff --git a/plan/command/add.go b/plan/command/add.go index e8e9875..c50c424 100644 --- a/plan/command/add.go +++ b/plan/command/add.go @@ -29,6 +29,7 @@ func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage FlagFor: &FlagDuration{}, FlagRecStart: &FlagDate{}, FlagRecPeriod: &FlagPeriod{}, + FlagRecCount: &FlagInt{}, }, }, } @@ -70,8 +71,14 @@ func (add *Add) Execute(main []string, flags map[string]string) error { return fmt.Errorf("could not set duration to 24 hours") } } - if as.IsSet(FlagRecStart) != as.IsSet(FlagRecPeriod) { - return fmt.Errorf("rec-start required rec-period and vice versa") + 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() @@ -102,6 +109,7 @@ func (add *Add) do() error { e.Recurrer = &item.Recur{ Start: as.GetTime(FlagRecStart), Period: as.GetRecurPeriod(FlagRecPeriod), + Count: as.GetInt(FlagRecCount), } } diff --git a/plan/command/add_test.go b/plan/command/add_test.go index d82ef81..3052627 100644 --- a/plan/command/add_test.go +++ b/plan/command/add_test.go @@ -121,18 +121,20 @@ func TestAdd(t *testing.T) { expErr: true, }, { - name: "rec-start with rec-period", + 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{ diff --git a/plan/command/argset.go b/plan/command/argset.go index 0667e34..81c4e5f 100644 --- a/plan/command/argset.go +++ b/plan/command/argset.go @@ -75,3 +75,15 @@ func (as *ArgSet) GetRecurPeriod(name string) item.RecurPeriod { } return val } + +func (as *ArgSet) GetInt(name string) int { + flag, ok := as.Flags[name] + if !ok { + return 0 + } + val, ok := flag.Get().(int) + if !ok { + return 0 + } + return val +} diff --git a/plan/command/command.go b/plan/command/command.go index 0ec2a03..61aa520 100644 --- a/plan/command/command.go +++ b/plan/command/command.go @@ -13,6 +13,7 @@ const ( FlagFor = "for" FlagRecStart = "rec-start" FlagRecPeriod = "rec-period" + FlagRecCount = "rec-count" ) type Command interface { diff --git a/plan/command/flag.go b/plan/command/flag.go index d50f376..3912b06 100644 --- a/plan/command/flag.go +++ b/plan/command/flag.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "slices" + "strconv" "time" "go-mod.ewintr.nl/planner/item" @@ -131,3 +132,26 @@ func (fp *FlagPeriod) IsSet() bool { func (fp *FlagPeriod) Get() any { return fp.Value } + +type FlagInt struct { + Name string + Value int +} + +func (fi *FlagInt) Set(val string) error { + i, err := strconv.Atoi(val) + if err != nil { + return fmt.Errorf("not a valid integer: %v", val) + } + + fi.Value = i + return nil +} + +func (fi *FlagInt) IsSet() bool { + return fi.Value != 0 +} + +func (fi *FlagInt) Get() any { + return fi.Value +} diff --git a/plan/storage/sqlite/sqlite.go b/plan/storage/sqlite/sqlite.go index cfdd967..d631863 100644 --- a/plan/storage/sqlite/sqlite.go +++ b/plan/storage/sqlite/sqlite.go @@ -34,6 +34,7 @@ var migrations = []string{ `ALTER TABLE events DROP COLUMN recur_start`, `ALTER TABLE events DROP COLUMN recur_next`, `ALTER TABLE events ADD COLUMN recur TEXT`, + `ALTER TABLE items ADD COLUMN recurrer TEXT`, } var ( diff --git a/plan/storage/sqlite/sync.go b/plan/storage/sqlite/sync.go index 76669d6..19fd1a6 100644 --- a/plan/storage/sqlite/sync.go +++ b/plan/storage/sqlite/sync.go @@ -2,6 +2,7 @@ package sqlite import ( "database/sql" + "encoding/json" "fmt" "time" @@ -17,7 +18,7 @@ func NewSqliteSync(db *sql.DB) *SqliteSync { } func (s *SqliteSync) FindAll() ([]item.Item, error) { - rows, err := s.db.Query("SELECT id, kind, updated, deleted, body FROM items") + rows, err := s.db.Query("SELECT id, kind, updated, deleted, recurrer, body FROM items") if err != nil { return nil, fmt.Errorf("%w: failed to query items: %v", ErrSqliteFailure, err) } @@ -26,20 +27,27 @@ func (s *SqliteSync) FindAll() ([]item.Item, error) { var items []item.Item for rows.Next() { var i item.Item - var updatedStr string - err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &i.Body) + var updatedStr, recurStr string + err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &i.Body) if err != nil { return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err) } i.Updated, err = time.Parse(time.RFC3339, updatedStr) if err != nil { - return nil, fmt.Errorf("%w: failed to parse updated time: %v", ErrSqliteFailure, err) + 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 } items = append(items, i) } if err = rows.Err(); err != nil { - return nil, fmt.Errorf("%w: error iterating over rows: %v", ErrSqliteFailure, err) + return nil, fmt.Errorf("error iterating over rows: %v", err) } return items, nil @@ -51,12 +59,22 @@ 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, body) VALUES (?, ?, ?, ?, ?)", + "INSERT OR REPLACE INTO items (id, kind, updated, deleted, recurrer, body) VALUES (?, ?, ?, ?, ?, ?)", i.ID, i.Kind, i.Updated.UTC().Format(time.RFC3339), i.Deleted, + recurStr, sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL ) if err != nil { diff --git a/sync/client/http.go b/sync/client/http.go index a94aeba..324c490 100644 --- a/sync/client/http.go +++ b/sync/client/http.go @@ -45,7 +45,8 @@ func (c *HTTP) Update(items []item.Item) error { return fmt.Errorf("could not make request: %v", err) } if res.StatusCode != http.StatusNoContent { - return fmt.Errorf("server returned status %d", res.StatusCode) + body, _ := io.ReadAll(res.Body) + return fmt.Errorf("server returned status %d, body: %s", res.StatusCode, body) } return nil diff --git a/sync/service/postgres.go b/sync/service/postgres.go index 62b6b86..2d0d8ba 100644 --- a/sync/service/postgres.go +++ b/sync/service/postgres.go @@ -58,11 +58,11 @@ func NewPostgres(host, port, dbname, user, password string) (*Postgres, error) { return p, nil } -func (p *Postgres) Update(item item.Item, ts time.Time) error { +func (p *Postgres) Update(i item.Item, ts time.Time) error { var recurrerJSON []byte var err error - if item.Recurrer != nil { - recurrerJSON, err = json.Marshal(item.Recurrer) + if i.Recurrer != nil { + recurrerJSON, err = json.Marshal(i.Recurrer) if err != nil { return fmt.Errorf("%w: %v", ErrPostgresFailure, err) } @@ -78,7 +78,7 @@ func (p *Postgres) Update(item item.Item, ts time.Time) error { body = EXCLUDED.body, recurrer = EXCLUDED.recurrer, recur_next = EXCLUDED.recur_next`, - item.ID, item.Kind, ts, item.Deleted, item.Body, recurrerJSON, item.RecurNext) + i.ID, i.Kind, ts, i.Deleted, i.Body, recurrerJSON, i.RecurNext) if err != nil { return fmt.Errorf("%w: %v", ErrPostgresFailure, err) } @@ -109,10 +109,10 @@ func (p *Postgres) Updated(ks []item.Kind, t time.Time) ([]item.Item, error) { result := make([]item.Item, 0) for rows.Next() { - var item item.Item + var i item.Item var recurNext sql.NullTime var recurrerJSON []byte - if err := rows.Scan(&item.ID, &item.Kind, &item.Updated, &item.Deleted, &item.Body, &recurrerJSON, &recurNext); err != nil { + if err := rows.Scan(&i.ID, &i.Kind, &i.Updated, &i.Deleted, &i.Body, &recurrerJSON, &recurNext); err != nil { return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err) } if len(recurrerJSON) > 0 { @@ -120,12 +120,12 @@ func (p *Postgres) Updated(ks []item.Kind, t time.Time) ([]item.Item, error) { if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil { return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err) } - item.Recurrer = &recurrer + i.Recurrer = &recurrer } if recurNext.Valid { - item.RecurNext = recurNext.Time + i.RecurNext = recurNext.Time } - result = append(result, item) + result = append(result, i) } return result, nil @@ -145,10 +145,10 @@ func (p *Postgres) RecursBefore(date time.Time) ([]item.Item, error) { result := make([]item.Item, 0) for rows.Next() { - var item item.Item + var i item.Item var recurNext sql.NullTime var recurrerJSON []byte - if err := rows.Scan(&item.ID, &item.Kind, &item.Updated, &item.Deleted, &item.Body, &recurrerJSON, &recurNext); err != nil { + if err := rows.Scan(&i.ID, &i.Kind, &i.Updated, &i.Deleted, &i.Body, &recurrerJSON, &recurNext); err != nil { return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err) } if len(recurrerJSON) > 0 { @@ -156,12 +156,12 @@ func (p *Postgres) RecursBefore(date time.Time) ([]item.Item, error) { if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil { return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err) } - item.Recurrer = &recurrer + i.Recurrer = &recurrer } if recurNext.Valid { - item.RecurNext = recurNext.Time + i.RecurNext = recurNext.Time } - result = append(result, item) + result = append(result, i) } return result, nil