simgle package sync service
This commit is contained in:
parent
74775ac244
commit
204c712cc6
2
Makefile
2
Makefile
|
@ -1,3 +1,3 @@
|
||||||
|
|
||||||
run:
|
run:
|
||||||
PLANNER_PORT=8092 PLANNER_API_KEY=testKey go run .
|
PLANNER_PORT=8092 PLANNER_API_KEY=testKey go run ./sync-service/
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.ewintr.nl/planner/planner"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotFound = errors.New("not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Syncer interface {
|
|
||||||
Update(item planner.Syncable) error
|
|
||||||
Updated(t time.Time) ([]planner.Syncable, error)
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package handler
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -9,18 +9,15 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.ewintr.nl/planner/planner"
|
|
||||||
"code.ewintr.nl/planner/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
syncer storage.Syncer
|
syncer Syncer
|
||||||
apiKey string
|
apiKey string
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(syncer storage.Syncer, apiKey string, logger *slog.Logger) *Server {
|
func NewServer(syncer Syncer, apiKey string, logger *slog.Logger) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
syncer: syncer,
|
syncer: syncer,
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
|
@ -87,7 +84,7 @@ func (s *Server) SyncPost(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
var items []planner.Syncable
|
var items []Syncable
|
||||||
if err := json.Unmarshal(body, &items); err != nil {
|
if err := json.Unmarshal(body, &items); err != nil {
|
||||||
http.Error(w, fmtError(err), http.StatusBadRequest)
|
http.Error(w, fmtError(err), http.StatusBadRequest)
|
||||||
return
|
return
|
|
@ -1,4 +1,4 @@
|
||||||
package handler_test
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -13,17 +13,13 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.ewintr.nl/planner/handler"
|
|
||||||
"code.ewintr.nl/planner/planner"
|
|
||||||
"code.ewintr.nl/planner/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerServeHTTP(t *testing.T) {
|
func TestServerServeHTTP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
apiKey := "test"
|
apiKey := "test"
|
||||||
srv := handler.NewServer(storage.NewMemory(), apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
srv := NewServer(NewMemory(), apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -57,9 +53,9 @@ func TestSyncGet(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
mem := storage.NewMemory()
|
mem := NewMemory()
|
||||||
|
|
||||||
items := []planner.Syncable{
|
items := []Syncable{
|
||||||
{ID: "id-0", Updated: now.Add(-10 * time.Minute)},
|
{ID: "id-0", Updated: now.Add(-10 * time.Minute)},
|
||||||
{ID: "id-1", Updated: now.Add(-5 * time.Minute)},
|
{ID: "id-1", Updated: now.Add(-5 * time.Minute)},
|
||||||
{ID: "id-2", Updated: now.Add(time.Minute)},
|
{ID: "id-2", Updated: now.Add(time.Minute)},
|
||||||
|
@ -72,13 +68,13 @@ func TestSyncGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey := "test"
|
apiKey := "test"
|
||||||
srv := handler.NewServer(mem, apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
srv := NewServer(mem, apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
ts time.Time
|
ts time.Time
|
||||||
expStatus int
|
expStatus int
|
||||||
expItems []planner.Syncable
|
expItems []Syncable
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "full",
|
name: "full",
|
||||||
|
@ -89,7 +85,7 @@ func TestSyncGet(t *testing.T) {
|
||||||
name: "normal",
|
name: "normal",
|
||||||
ts: now.Add(-6 * time.Minute),
|
ts: now.Add(-6 * time.Minute),
|
||||||
expStatus: http.StatusOK,
|
expStatus: http.StatusOK,
|
||||||
expItems: []planner.Syncable{items[1], items[2]},
|
expItems: []Syncable{items[1], items[2]},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
@ -105,7 +101,7 @@ func TestSyncGet(t *testing.T) {
|
||||||
if res.Result().StatusCode != tc.expStatus {
|
if res.Result().StatusCode != tc.expStatus {
|
||||||
t.Errorf("exp %v, got %v", tc.expStatus, res.Result().StatusCode)
|
t.Errorf("exp %v, got %v", tc.expStatus, res.Result().StatusCode)
|
||||||
}
|
}
|
||||||
var actItems []planner.Syncable
|
var actItems []Syncable
|
||||||
actBody, err := io.ReadAll(res.Result().Body)
|
actBody, err := io.ReadAll(res.Result().Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
|
@ -139,7 +135,7 @@ func TestSyncPost(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
reqBody []byte
|
reqBody []byte
|
||||||
expStatus int
|
expStatus int
|
||||||
expItems []planner.Syncable
|
expItems []Syncable
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
|
@ -157,15 +153,15 @@ func TestSyncPost(t *testing.T) {
|
||||||
{"ID":"id-2","Updated":"2024-09-06T08:12:00Z","Deleted":false,"Item":""}
|
{"ID":"id-2","Updated":"2024-09-06T08:12:00Z","Deleted":false,"Item":""}
|
||||||
]`),
|
]`),
|
||||||
expStatus: http.StatusNoContent,
|
expStatus: http.StatusNoContent,
|
||||||
expItems: []planner.Syncable{
|
expItems: []Syncable{
|
||||||
{ID: "id-1", Updated: time.Date(2024, 9, 6, 8, 0, 0, 0, time.UTC)},
|
{ID: "id-1", Updated: time.Date(2024, 9, 6, 8, 0, 0, 0, time.UTC)},
|
||||||
{ID: "id-2", Updated: time.Date(2024, 9, 6, 12, 0, 0, 0, time.UTC)},
|
{ID: "id-2", Updated: time.Date(2024, 9, 6, 12, 0, 0, 0, time.UTC)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
mem := storage.NewMemory()
|
mem := NewMemory()
|
||||||
srv := handler.NewServer(mem, apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
srv := NewServer(mem, apiKey, slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
req, err := http.NewRequest(http.MethodPost, "/sync", bytes.NewBuffer(tc.reqBody))
|
req, err := http.NewRequest(http.MethodPost, "/sync", bytes.NewBuffer(tc.reqBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
|
@ -205,9 +201,9 @@ func TestSyncHandler(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
items []item
|
items []item
|
||||||
method string
|
method string
|
||||||
body handler.ChangeSummary
|
body .ChangeSummary
|
||||||
expStatus int
|
expStatus int
|
||||||
expBody handler.ChangeSummary
|
expBody .ChangeSummary
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
|
@ -218,11 +214,11 @@ func TestSyncHandler(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
mem := storage.NewMemory()
|
mem := NewMemory()
|
||||||
for _, i := range tc.items {
|
for _, i := range tc.items {
|
||||||
mem.Update(i)
|
mem.Update(i)
|
||||||
}
|
}
|
||||||
sh := handler.NewSyncHandler(mem)
|
sh := .NewSyncHandler(mem)
|
||||||
req, err := http.NewRequest(tc.method, "/sync", nil)
|
req, err := http.NewRequest(tc.method, "/sync", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
|
@ -8,9 +8,6 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"code.ewintr.nl/planner/handler"
|
|
||||||
"code.ewintr.nl/planner/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -25,10 +22,10 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
mem := storage.NewMemory()
|
mem := NewMemory()
|
||||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||||
|
|
||||||
go http.ListenAndServe(fmt.Sprintf(":%d", port), handler.NewServer(mem, apiKey, logger))
|
go http.ListenAndServe(fmt.Sprintf(":%d", port), NewServer(mem, apiKey, logger))
|
||||||
|
|
||||||
logger.Info("service started")
|
logger.Info("service started")
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
package storage
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.ewintr.nl/planner/planner"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Memory struct {
|
type Memory struct {
|
||||||
items map[string]planner.Syncable
|
items map[string]Syncable
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemory() *Memory {
|
func NewMemory() *Memory {
|
||||||
return &Memory{
|
return &Memory{
|
||||||
items: make(map[string]planner.Syncable),
|
items: make(map[string]Syncable),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Memory) Update(item planner.Syncable) error {
|
func (m *Memory) Update(item Syncable) error {
|
||||||
m.items[item.ID] = item
|
m.items[item.ID] = item
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Memory) Updated(timestamp time.Time) ([]planner.Syncable, error) {
|
func (m *Memory) Updated(timestamp time.Time) ([]Syncable, error) {
|
||||||
result := make([]planner.Syncable, 0)
|
result := make([]Syncable, 0)
|
||||||
|
|
||||||
for _, i := range m.items {
|
for _, i := range m.items {
|
||||||
if timestamp.IsZero() || i.Updated.Equal(timestamp) || i.Updated.After(timestamp) {
|
if timestamp.IsZero() || i.Updated.Equal(timestamp) || i.Updated.After(timestamp) {
|
|
@ -1,17 +1,14 @@
|
||||||
package storage_test
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.ewintr.nl/planner/planner"
|
|
||||||
"code.ewintr.nl/planner/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMemoryItem(t *testing.T) {
|
func TestMemoryItem(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
mem := storage.NewMemory()
|
mem := NewMemory()
|
||||||
|
|
||||||
t.Log("start empty")
|
t.Log("start empty")
|
||||||
actItems, actErr := mem.Updated(time.Time{})
|
actItems, actErr := mem.Updated(time.Time{})
|
||||||
|
@ -23,7 +20,7 @@ func TestMemoryItem(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("add one")
|
t.Log("add one")
|
||||||
t1 := planner.NewSyncable("test")
|
t1 := NewSyncable("test")
|
||||||
if actErr := mem.Update(t1); actErr != nil {
|
if actErr := mem.Update(t1); actErr != nil {
|
||||||
t.Errorf("exp nil, got %v", actErr)
|
t.Errorf("exp nil, got %v", actErr)
|
||||||
}
|
}
|
||||||
|
@ -41,7 +38,7 @@ func TestMemoryItem(t *testing.T) {
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
|
|
||||||
t.Log("add second")
|
t.Log("add second")
|
||||||
t2 := planner.NewSyncable("test 2")
|
t2 := NewSyncable("test 2")
|
||||||
if actErr := mem.Update(t2); actErr != nil {
|
if actErr := mem.Update(t2); actErr != nil {
|
||||||
t.Errorf("exp nil, got %v", actErr)
|
t.Errorf("exp nil, got %v", actErr)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package planner
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Syncer interface {
|
||||||
|
Update(item Syncable) error
|
||||||
|
Updated(t time.Time) ([]Syncable, error)
|
||||||
|
}
|
Loading…
Reference in New Issue