simgle package sync service

This commit is contained in:
Erik Winter 2024-09-08 10:09:54 +02:00
parent 74775ac244
commit 204c712cc6
9 changed files with 49 additions and 66 deletions

View File

@ -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/

View File

@ -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)
}

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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) {

View File

@ -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)
} }

View File

@ -1,4 +1,4 @@
package planner package main
import ( import (
"time" "time"

15
sync-service/storage.go Normal file
View File

@ -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)
}