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{},
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const (
|
|||
FlagFor = "for"
|
||||
FlagRecStart = "rec-start"
|
||||
FlagRecPeriod = "rec-period"
|
||||
FlagRecCount = "rec-count"
|
||||
)
|
||||
|
||||
type Command interface {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
@ -57,8 +58,17 @@ func NewPostgres(host, port, dbname, user, password string) (*Postgres, error) {
|
|||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Postgres) Update(item item.Item, ts time.Time) error {
|
||||
_, err := p.db.Exec(`
|
||||
func (p *Postgres) Update(i item.Item, ts time.Time) error {
|
||||
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)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
|
@ -68,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, item.Recurrer, item.RecurNext)
|
||||
i.ID, i.Kind, ts, i.Deleted, i.Body, recurrerJSON, i.RecurNext)
|
||||
if err != nil {
|
||||
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)
|
||||
for rows.Next() {
|
||||
var item item.Item
|
||||
var i item.Item
|
||||
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)
|
||||
}
|
||||
if recurNext.Valid {
|
||||
item.RecurNext = recurNext.Time
|
||||
if len(recurrerJSON) > 0 {
|
||||
var recurrer item.Recur
|
||||
if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||
}
|
||||
i.Recurrer = &recurrer
|
||||
}
|
||||
result = append(result, item)
|
||||
if recurNext.Valid {
|
||||
i.RecurNext = recurNext.Time
|
||||
}
|
||||
result = append(result, i)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
@ -127,15 +145,23 @@ 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
|
||||
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)
|
||||
}
|
||||
if recurNext.Valid {
|
||||
item.RecurNext = recurNext.Time
|
||||
if len(recurrerJSON) > 0 {
|
||||
var recurrer item.Recur
|
||||
if err := json.Unmarshal(recurrerJSON, &recurrer); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrPostgresFailure, err)
|
||||
}
|
||||
i.Recurrer = &recurrer
|
||||
}
|
||||
result = append(result, item)
|
||||
if recurNext.Valid {
|
||||
i.RecurNext = recurNext.Time
|
||||
}
|
||||
result = append(result, i)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
|
Loading…
Reference in New Issue