Compare commits
3 Commits
55fe158f79
...
95a847d97c
Author | SHA1 | Date |
---|---|---|
Erik Winter | 95a847d97c | |
Erik Winter (aider) | 7e094d04cc | |
Erik Winter (aider) | f4ca723102 |
|
@ -29,6 +29,7 @@ func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage
|
||||||
FlagFor: &FlagDuration{},
|
FlagFor: &FlagDuration{},
|
||||||
FlagRecStart: &FlagDate{},
|
FlagRecStart: &FlagDate{},
|
||||||
FlagRecPeriod: &FlagPeriod{},
|
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")
|
return fmt.Errorf("could not set duration to 24 hours")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if as.IsSet(FlagRecStart) != as.IsSet(FlagRecPeriod) {
|
var recCount int
|
||||||
return fmt.Errorf("rec-start required rec-period and vice versa")
|
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()
|
return add.do()
|
||||||
|
@ -102,6 +109,7 @@ func (add *Add) do() error {
|
||||||
e.Recurrer = &item.Recur{
|
e.Recurrer = &item.Recur{
|
||||||
Start: as.GetTime(FlagRecStart),
|
Start: as.GetTime(FlagRecStart),
|
||||||
Period: as.GetRecurPeriod(FlagRecPeriod),
|
Period: as.GetRecurPeriod(FlagRecPeriod),
|
||||||
|
Count: as.GetInt(FlagRecCount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,18 +121,20 @@ func TestAdd(t *testing.T) {
|
||||||
expErr: true,
|
expErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rec-start with rec-period",
|
name: "rec-start with rec-period and rec-count",
|
||||||
main: []string{"add", "title"},
|
main: []string{"add", "title"},
|
||||||
flags: map[string]string{
|
flags: map[string]string{
|
||||||
command.FlagOn: aDateStr,
|
command.FlagOn: aDateStr,
|
||||||
command.FlagRecStart: "2024-12-08",
|
command.FlagRecStart: "2024-12-08",
|
||||||
command.FlagRecPeriod: "day",
|
command.FlagRecPeriod: "day",
|
||||||
|
command.FlagRecCount: "1",
|
||||||
},
|
},
|
||||||
expEvent: item.Event{
|
expEvent: item.Event{
|
||||||
ID: "title",
|
ID: "title",
|
||||||
Recurrer: &item.Recur{
|
Recurrer: &item.Recur{
|
||||||
Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC),
|
Start: time.Date(2024, 12, 8, 0, 0, 0, 0, time.UTC),
|
||||||
Period: item.PeriodDay,
|
Period: item.PeriodDay,
|
||||||
|
Count: 1,
|
||||||
},
|
},
|
||||||
RecurNext: time.Time{},
|
RecurNext: time.Time{},
|
||||||
EventBody: item.EventBody{
|
EventBody: item.EventBody{
|
||||||
|
|
|
@ -75,3 +75,15 @@ func (as *ArgSet) GetRecurPeriod(name string) item.RecurPeriod {
|
||||||
}
|
}
|
||||||
return val
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const (
|
||||||
FlagFor = "for"
|
FlagFor = "for"
|
||||||
FlagRecStart = "rec-start"
|
FlagRecStart = "rec-start"
|
||||||
FlagRecPeriod = "rec-period"
|
FlagRecPeriod = "rec-period"
|
||||||
|
FlagRecCount = "rec-count"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Command interface {
|
type Command interface {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-mod.ewintr.nl/planner/item"
|
"go-mod.ewintr.nl/planner/item"
|
||||||
|
@ -131,3 +132,26 @@ func (fp *FlagPeriod) IsSet() bool {
|
||||||
func (fp *FlagPeriod) Get() any {
|
func (fp *FlagPeriod) Get() any {
|
||||||
return fp.Value
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ var migrations = []string{
|
||||||
`ALTER TABLE events DROP COLUMN recur_start`,
|
`ALTER TABLE events DROP COLUMN recur_start`,
|
||||||
`ALTER TABLE events DROP COLUMN recur_next`,
|
`ALTER TABLE events DROP COLUMN recur_next`,
|
||||||
`ALTER TABLE events ADD COLUMN recur TEXT`,
|
`ALTER TABLE events ADD COLUMN recur TEXT`,
|
||||||
|
`ALTER TABLE items ADD COLUMN recurrer TEXT`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -2,6 +2,7 @@ package sqlite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ func NewSqliteSync(db *sql.DB) *SqliteSync {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SqliteSync) FindAll() ([]item.Item, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: failed to query items: %v", ErrSqliteFailure, err)
|
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
|
var items []item.Item
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i item.Item
|
var i item.Item
|
||||||
var updatedStr string
|
var updatedStr, recurStr string
|
||||||
err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &i.Body)
|
err := rows.Scan(&i.ID, &i.Kind, &updatedStr, &i.Deleted, &recurStr, &i.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err)
|
return nil, fmt.Errorf("%w: failed to scan item: %v", ErrSqliteFailure, err)
|
||||||
}
|
}
|
||||||
i.Updated, err = time.Parse(time.RFC3339, updatedStr)
|
i.Updated, err = time.Parse(time.RFC3339, updatedStr)
|
||||||
if err != nil {
|
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)
|
items = append(items, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = rows.Err(); err != nil {
|
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
|
return items, nil
|
||||||
|
@ -51,12 +59,22 @@ func (s *SqliteSync) Store(i item.Item) error {
|
||||||
i.Updated = time.Now()
|
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(
|
_, 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.ID,
|
||||||
i.Kind,
|
i.Kind,
|
||||||
i.Updated.UTC().Format(time.RFC3339),
|
i.Updated.UTC().Format(time.RFC3339),
|
||||||
i.Deleted,
|
i.Deleted,
|
||||||
|
recurStr,
|
||||||
sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL
|
sql.NullString{String: i.Body, Valid: i.Body != ""}, // This allows empty string but not NULL
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -45,7 +45,8 @@ func (c *HTTP) Update(items []item.Item) error {
|
||||||
return fmt.Errorf("could not make request: %v", err)
|
return fmt.Errorf("could not make request: %v", err)
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusNoContent {
|
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
|
return nil
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -57,8 +58,17 @@ func NewPostgres(host, port, dbname, user, password string) (*Postgres, error) {
|
||||||
return p, nil
|
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 {
|
||||||
_, err := p.db.Exec(`
|
var recurrerJSON []byte
|
||||||
|
var err error
|
||||||
|
if i.Recurrer != nil {
|
||||||
|
recurrerJSON, err = json.Marshal(i.Recurrer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.db.Exec(`
|
||||||
INSERT INTO items (id, kind, updated, deleted, body, recurrer, recur_next)
|
INSERT INTO items (id, kind, updated, deleted, body, recurrer, recur_next)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
ON CONFLICT (id) DO UPDATE
|
ON CONFLICT (id) DO UPDATE
|
||||||
|
@ -68,7 +78,7 @@ func (p *Postgres) Update(item item.Item, ts time.Time) error {
|
||||||
body = EXCLUDED.body,
|
body = EXCLUDED.body,
|
||||||
recurrer = EXCLUDED.recurrer,
|
recurrer = EXCLUDED.recurrer,
|
||||||
recur_next = EXCLUDED.recur_next`,
|
recur_next = EXCLUDED.recur_next`,
|
||||||
item.ID, item.Kind, ts, item.Deleted, item.Body, item.Recurrer, item.RecurNext)
|
i.ID, i.Kind, ts, i.Deleted, i.Body, recurrerJSON, i.RecurNext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
return fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
|
@ -99,15 +109,23 @@ func (p *Postgres) Updated(ks []item.Kind, t time.Time) ([]item.Item, error) {
|
||||||
|
|
||||||
result := make([]item.Item, 0)
|
result := make([]item.Item, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item item.Item
|
var i item.Item
|
||||||
var recurNext sql.NullTime
|
var recurNext sql.NullTime
|
||||||
if err := rows.Scan(&item.ID, &item.Kind, &item.Updated, &item.Deleted, &item.Body, &item.Recurrer, &recurNext); err != nil {
|
var recurrerJSON []byte
|
||||||
|
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)
|
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
if recurNext.Valid {
|
if len(recurrerJSON) > 0 {
|
||||||
item.RecurNext = recurNext.Time
|
var recurrer item.Recur
|
||||||
|
if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
i.Recurrer = &recurrer
|
||||||
|
}
|
||||||
|
if recurNext.Valid {
|
||||||
|
i.RecurNext = recurNext.Time
|
||||||
|
}
|
||||||
|
result = append(result, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -127,15 +145,23 @@ func (p *Postgres) RecursBefore(date time.Time) ([]item.Item, error) {
|
||||||
|
|
||||||
result := make([]item.Item, 0)
|
result := make([]item.Item, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item item.Item
|
var i item.Item
|
||||||
var recurNext sql.NullTime
|
var recurNext sql.NullTime
|
||||||
if err := rows.Scan(&item.ID, &item.Kind, &item.Updated, &item.Deleted, &item.Body, &item.Recurrer, &recurNext); err != nil {
|
var recurrerJSON []byte
|
||||||
|
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)
|
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
if recurNext.Valid {
|
if len(recurrerJSON) > 0 {
|
||||||
item.RecurNext = recurNext.Time
|
var recurrer item.Recur
|
||||||
|
if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||||
}
|
}
|
||||||
result = append(result, item)
|
i.Recurrer = &recurrer
|
||||||
|
}
|
||||||
|
if recurNext.Valid {
|
||||||
|
i.RecurNext = recurNext.Time
|
||||||
|
}
|
||||||
|
result = append(result, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
Loading…
Reference in New Issue