diff --git a/plan/command/sync_test.go b/plan/command/sync_test.go new file mode 100644 index 0000000..60d0617 --- /dev/null +++ b/plan/command/sync_test.go @@ -0,0 +1,41 @@ +package command_test + +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" + "go-mod.ewintr.nl/planner/sync/client" +) + +func TestSync(t *testing.T) { + t.Parallel() + + syncClient := client.NewMemory() + syncRepo := memory.NewSync() + localIDRepo := memory.NewLocalID() + eventRepo := memory.NewEvent() + + // now := time.Now() + + it := item.Item{ + ID: "a", + Kind: item.KindEvent, + Body: `{}`, + } + syncClient.Update([]item.Item{it}) + + if err := command.Sync(syncClient, syncRepo, localIDRepo, eventRepo, false); err != nil { + t.Errorf("exp nil, got %v", err) + } + actItems, actErr := syncClient.Updated([]item.Kind{item.KindEvent}, time.Time{}) + if actErr != nil { + t.Errorf("exp nil, got %v", actErr) + } + if diff := cmp.Diff([]item.Item{it}, actItems); diff != "" { + t.Errorf("(exp +, got -)\n%s", diff) + } +} diff --git a/sync/client/http.go b/sync/client/http.go new file mode 100644 index 0000000..a94aeba --- /dev/null +++ b/sync/client/http.go @@ -0,0 +1,89 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "go-mod.ewintr.nl/planner/item" +) + +type HTTP struct { + baseURL string + apiKey string + c *http.Client +} + +func New(url, apiKey string) *HTTP { + return &HTTP{ + baseURL: url, + apiKey: apiKey, + c: &http.Client{ + Timeout: 10 * time.Second, + }, + } +} + +func (c *HTTP) Update(items []item.Item) error { + body, err := json.Marshal(items) + if err != nil { + return fmt.Errorf("could not marhal body: %v", err) + } + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/sync", c.baseURL), bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("could not create request: %v", err) + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + + res, err := c.c.Do(req) + if err != nil { + return fmt.Errorf("could not make request: %v", err) + } + if res.StatusCode != http.StatusNoContent { + return fmt.Errorf("server returned status %d", res.StatusCode) + } + + return nil +} + +func (c *HTTP) Updated(ks []item.Kind, ts time.Time) ([]item.Item, error) { + ksStr := make([]string, 0, len(ks)) + for _, k := range ks { + ksStr = append(ksStr, string(k)) + } + u := fmt.Sprintf("%s/sync?ks=%s", c.baseURL, strings.Join(ksStr, ",")) + if !ts.IsZero() { + u = fmt.Sprintf("%s&ts=", url.QueryEscape(ts.Format(time.RFC3339))) + } + req, err := http.NewRequest(http.MethodGet, u, nil) + if err != nil { + return nil, fmt.Errorf("could not create request: %v", err) + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + + res, err := c.c.Do(req) + if err != nil { + return nil, fmt.Errorf("could not get response: %v", err) + } + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned status %d", res.StatusCode) + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("could not read response body: %v", err) + } + + var items []item.Item + if err := json.Unmarshal(body, &items); err != nil { + return nil, fmt.Errorf("could not unmarshal response body: %v", err) + } + + return items, nil +}