From a02b0d9d2a5bd21a43c9418c40f6c6517cd4f97c Mon Sep 17 00:00:00 2001 From: Alexander Simmerl Date: Fri, 24 Mar 2017 10:09:10 +0100 Subject: [PATCH] WIP --- service/invite/helper_test.go | 73 +++++++++++++++++++ service/invite/invite.go | 42 +++++++++++ service/invite/postgres.go | 123 ++++++++++++++++++++++++++++++++ service/invite/postgres_test.go | 52 ++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 service/invite/helper_test.go create mode 100644 service/invite/invite.go create mode 100644 service/invite/postgres.go create mode 100644 service/invite/postgres_test.go diff --git a/service/invite/helper_test.go b/service/invite/helper_test.go new file mode 100644 index 0000000..f1eb377 --- /dev/null +++ b/service/invite/helper_test.go @@ -0,0 +1,73 @@ +package invite + +import ( + "math/rand" + "reflect" + "testing" + + "github.com/tapglue/snaas/platform/generate" +) + +type prepareFunc func(t *testing.T, namespace string) Service + +func testServicePut(t *testing.T, p prepareFunc) { + var ( + invite = testInvite() + namespace = "service_put" + service = p(t, namespace) + ) + + created, err := service.Put(namespace, invite) + if err != nil { + t.Fatal(err) + } + + list, err := service.Query(namespace, QueryOptions{ + IDs: []uint64{ + created.ID, + }, + }) + if err != nil { + t.Fatal(err) + } + + if have, want := len(list), 1; have != want { + t.Fatalf("have %v, want %v", have, want) + } + if have, want := list[0], created; !reflect.DeepEqual(have, want) { + t.Errorf("have %v, want %v", have, want) + } + + created.Deleted = true + + updated, err := service.Put(namespace, created) + if err != nil { + t.Fatal(err) + } + + list, err = service.Query(namespace, QueryOptions{ + IDs: []uint64{ + updated.ID, + }, + }) + if err != nil { + t.Fatal(err) + } + + if have, want := list[0], updated; !reflect.DeepEqual(have, want) { + t.Errorf("have %v, want %v", have, want) + } +} + +func testServiceQuery(t *testing.T, p prepareFunc) { + t.Errorf("testServiceQuery not implementd") +} + +func testInvite() *Invite { + return &Invite{ + Deleted: false, + Key: generate.RandomStringSafe(24), + UserID: uint64(rand.Int63()), + Value: generate.RandomStringSafe(24), + } +} diff --git a/service/invite/invite.go b/service/invite/invite.go new file mode 100644 index 0000000..f2f5083 --- /dev/null +++ b/service/invite/invite.go @@ -0,0 +1,42 @@ +package invite + +import ( + "time" + + "github.com/tapglue/snaas/platform/service" +) + +// Invite is a loose promise to create a conection if the person assoicated with +// the social id key-value signs up. +type Invite struct { + Deleted bool + ID uint64 + Key string + UserID uint64 + Value string + CreatedAt time.Time + UpdatedAt time.Time +} + +// List is a collection of Invite. +type List []*Invite + +// QueryOptions to narrow-down Invite queries. +type QueryOptions struct { + Deleted *bool + IDs []uint64 + Keys []string + UserIDs []uint64 + Values []string +} + +// Service for Invite interactions. +type Service interface { + service.Lifecycle + + Put(namespace string, i *Invite) (*Invite, error) + Query(namespace string, opts QueryOptions) (List, error) +} + +// ServiceMiddleware is a chainable behaviour modifier for Service. +type ServiceMiddleware func(Service) Service diff --git a/service/invite/postgres.go b/service/invite/postgres.go new file mode 100644 index 0000000..6c89857 --- /dev/null +++ b/service/invite/postgres.go @@ -0,0 +1,123 @@ +package invite + +import ( + "fmt" + "time" + + "github.com/jmoiron/sqlx" + "github.com/tapglue/snaas/platform/flake" + "github.com/tapglue/snaas/platform/pg" +) + +const ( + pgInsertInvite = `INSERT INTO + %s.invites(deleted, id, key, user_id, value, created_at, updated_at) + VALUES($1, $2, $3, $4, $5, $6, $7)` + pgUpdateInvite = ` + UPDATE + %s.invites + SET + deleted = $2 + updated_at = $3 + WHERE + id = $1` + + pgClauseDeleted = `deleted = ?` + pgClauseKeys = `key IN (?)` + pgClauseUserIDs = `user_id IN (?)` + pgClauseValues = `value IN (?)` + + pgListInvites = ` + SELECT + deleted, id, key, user_id, value, created_at, updated_at + FROM + %s.invites + %s` + + pgOrderCreatedAt = `ORDER BY created_at DESC` + + pgCreateScheme = `CREATE SCHEMA IF NOT EXISTS %s` + pgCreateTable = `CREATE TABLE IF NOT EXISTS %s.invites( + deleted BOOL DEFAULT false, + id BIGINT NOT NULL UNIQUE, + key TEXT NOT NULL, + uesr_id BIGINT NOT NULL, + value TEXT NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL + )` + pgDropTable = `DROP TABLE IF EXISTS %s.invites` +) + +type pgService struct { + db *sqlx.DB +} + +// PostgresService returns a Postgres based Service implementation. +func PostgresService(db *sqlx.DB) Service { + return &pgService{ + db: db, + } +} + +func (s *pgService) Put(ns string, i *Invite) (*Invite, error) { + if i.ID == 0 { + return s.insert(ns, i) + } + + return nil, fmt.Errorf("invite.Put/update not implemented") +} + +func (s *pgService) Query(ns string, opts QueryOptions) (List, error) { + return nil, fmt.Errorf("invite.Query not implemented") +} + +func (s *pgService) Setup(ns string) error { + qs := []string{ + fmt.Sprintf(pgCreateScheme, ns), + fmt.Sprintf(pgCreateTable, ns), + } + + for _, q := range qs { + _, err := s.db.Exec(q) + if err != nil { + return fmt.Errorf("setup '%s': %s", q, err) + } + } + + return nil +} + +func (s *pgService) Teardown(ns string) error { + qs := []string{ + fmt.Sprintf(pgDropTable, ns), + } + + for _, q := range qs { + _, err := s.db.Exec(q) + if err != nil { + return fmt.Errorf("teardown '%s': %s", q, err) + } + } + + return nil +} + +func (s *pgService) insert(ns string, i *Invite) (*Invite, error) { + if i.CreatedAt.IsZero() { + i.CreatedAt = time.Now().UTC() + } + + ts, err := time.Parse(pg.TimeFormat, i.CreatedAt.UTC().Format(pg.TimeFormat)) + if err != nil { + return nil, err + } + + i.CreatedAt = ts + i.UpdatedAt = ts + + id, err := flake.NextID(flakeNamespace(ns)) + if err != nil { + return nil, err + } +} diff --git a/service/invite/postgres_test.go b/service/invite/postgres_test.go new file mode 100644 index 0000000..9983736 --- /dev/null +++ b/service/invite/postgres_test.go @@ -0,0 +1,52 @@ +// +build integration + +package invite + +import ( + "flag" + "fmt" + "os/user" + "testing" + + "github.com/jmoiron/sqlx" + "github.com/tapglue/snaas/platform/pg" +) + +var pgTestURL string + +func TestPostgresPut(t *testing.T) { + testServicePut(t, preparePostgres) +} + +func TestPostgresQuery(t *testing.T) { + testServiceQuery(t, preparePostgres) +} + +func preparePostgres(t *testing.T, namespace string) Service { + db, err := sqlx.Connect("postgres", pgTestURL) + if err != nil { + t.Fatal(err) + } + + s := PostgresService(db) + + if err := s.Teardown(namespace); err != nil { + t.Fatal(err) + } + + return s +} + +func init() { + u, err := user.Current() + if err != nil { + panic(err) + } + + d := fmt.Sprintf(pg.URLTest, u.Username) + + url := flag.String("postgres.url", d, "Postgres test connection URL") + flag.Parse() + + pgTestURL = *url +}