From 058f8dba7f80d3343d87a4d0a8749eee89364a3d Mon Sep 17 00:00:00 2001 From: Michael McGuinness Date: Wed, 29 Jan 2025 16:26:11 +0000 Subject: [PATCH] Merged in feature/logVersioning (pull request #40) Can Sync Versioning * started * client * jobcansync * tests --- api/queryService/client_test.go | 10 +- api/queryService/job_test.go | 4 +- .../migrations/00000000000003_clients.up.sql | 9 +- .../migrations/00000000000004_jobs.up.sql | 9 +- database/queries/client.sql | 16 ++- database/queries/job.sql | 15 ++- internal/client/create.go | 2 +- internal/client/service.go | 49 +++++--- internal/client/serviceprivate_test.go | 69 +++++++---- internal/client/update.go | 60 ++++++--- internal/client/update_test.go | 114 ++++++++++++++++-- internal/database/connection_test.go | 2 +- internal/database/repository/client.sql.go | 62 ++++++++-- internal/database/repository/client_test.go | 23 +++- internal/database/repository/job.sql.go | 68 +++++++---- internal/database/repository/job_test.go | 8 +- internal/database/repository/models.go | 20 ++- internal/job/update.go | 48 ++++++-- internal/job/update_test.go | 20 ++- internal/job/updateprivate_test.go | 80 ++++++++++++ test/queryService/testquery_test.go | 1 + 21 files changed, 543 insertions(+), 146 deletions(-) diff --git a/api/queryService/client_test.go b/api/queryService/client_test.go index d420298b..42880ccd 100644 --- a/api/queryService/client_test.go +++ b/api/queryService/client_test.go @@ -117,7 +117,10 @@ func TestUpdateClient(t *testing.T) { Client: client.New(db), }) - body := queryservice.QueryUpdate{} + cs := false + body := queryservice.ClientUpdate{ + CanSync: &cs, + } bodyBytes, err := json.Marshal(body) assert.Nil(t, err) @@ -135,9 +138,10 @@ func TestUpdateClient(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(id), "client_name", true), ) - - pool.ExpectExec("name: UpdateClient :exec").WithArgs("client_name", true, database.MustToDBUUID(id)). + pool.ExpectBegin() + pool.ExpectExec("name: AddClientCanSync :exec").WithArgs(*body.CanSync, database.MustToDBUUID(id)). WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() err = cons.UpdateClient(ctx, id) assert.Nil(t, err) diff --git a/api/queryService/job_test.go b/api/queryService/job_test.go index 88e86d3a..6d4519f3 100644 --- a/api/queryService/job_test.go +++ b/api/queryService/job_test.go @@ -178,8 +178,10 @@ func TestUpdateJob(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(j.ClientID), "client_name", true), ) - pool.ExpectExec("name: UpdateJob :exec").WithArgs(ucs, database.MustToDBUUID(j.ID)). + pool.ExpectBegin() + pool.ExpectExec("name: AddJobCanSync :exec").WithArgs(*body.CanSync, database.MustToDBUUID(j.ID)). WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() err = cons.UpdateJob(ctx, j.ID) assert.Nil(t, err) diff --git a/database/migrations/00000000000003_clients.up.sql b/database/migrations/00000000000003_clients.up.sql index 1b462f22..f0e1a9ee 100644 --- a/database/migrations/00000000000003_clients.up.sql +++ b/database/migrations/00000000000003_clients.up.sql @@ -1,6 +1,13 @@ CREATE TABLE clients ( id uuid primary key DEFAULT gen_random_uuid(), name TEXT not null, - canSync boolean not null default false, UNIQUE (name) +); + +CREATE TABLE clientCanSync ( + id uuid primary key DEFAULT gen_random_uuid(), + clientId uuid not null, + canSync boolean not null, + addedAt timestamp not null default current_timestamp, + foreign key (clientId) references clients(id) ); \ No newline at end of file diff --git a/database/migrations/00000000000004_jobs.up.sql b/database/migrations/00000000000004_jobs.up.sql index 4f43b3c1..cc4a299e 100644 --- a/database/migrations/00000000000004_jobs.up.sql +++ b/database/migrations/00000000000004_jobs.up.sql @@ -1,6 +1,13 @@ CREATE TABLE jobs ( id uuid primary key DEFAULT gen_random_uuid(), clientId uuid not null, - canSync boolean not null default false, foreign key (clientId) references clients(id) +); + +CREATE TABLE jobCanSync ( + id uuid primary key DEFAULT gen_random_uuid(), + jobId uuid not null, + canSync boolean not null, + addedAt timestamp not null default current_timestamp, + foreign key (jobId) references jobs(id) ); \ No newline at end of file diff --git a/database/queries/client.sql b/database/queries/client.sql index 629df93e..f7e919dd 100644 --- a/database/queries/client.sql +++ b/database/queries/client.sql @@ -2,7 +2,19 @@ INSERT INTO clients (name) VALUES ($1) RETURNING id; -- name: GetClient :one -SELECT id, name, canSync FROM clients WHERE id = $1; +SELECT c.id, c.name, coalesce(cs.canSync, false) as canSync + FROM clients as c + LEFT JOIN ( + SELECT clientId, canSync + FROM clientCanSync + WHERE clientId = $1 + ORDER BY addedAt DESC + LIMIT 1 + ) as cs on cs.clientId = c.id + WHERE c.id = $1; -- name: UpdateClient :exec -UPDATE clients SET name = $1, canSync = $2 WHERE id = $3; \ No newline at end of file +UPDATE clients SET name = $1 WHERE id = $2; + +-- name: AddClientCanSync :exec +INSERT INTO clientCanSync (canSync, clientId) VALUES ($1, $2); \ No newline at end of file diff --git a/database/queries/job.sql b/database/queries/job.sql index 3bf93c12..c92fcc56 100644 --- a/database/queries/job.sql +++ b/database/queries/job.sql @@ -1,8 +1,17 @@ -- name: GetJob :one -SELECT id, clientId, canSync FROM jobs WHERE id = $1 LIMIT 1; +SELECT j.id, j.clientId, coalesce(cs.canSync, false) as canSync + FROM jobs as j + LEFT JOIN ( + SELECT jobId, canSync + FROM jobCanSync + WHERE jobId = $1 + ORDER BY addedAt DESC + LIMIT 1 + ) as cs on cs.jobId = j.id + WHERE j.id = $1; -- name: CreateJob :one INSERT INTO jobs (clientId) VALUES ($1) RETURNING id; --- name: UpdateJob :exec -UPDATE jobs SET canSync = $1 WHERE id = $2; \ No newline at end of file +-- name: AddJobCanSync :exec +INSERT INTO jobCanSync (canSync, jobId) VALUES ($1, $2); \ No newline at end of file diff --git a/internal/client/create.go b/internal/client/create.go index 16d6ab3f..90983249 100644 --- a/internal/client/create.go +++ b/internal/client/create.go @@ -8,7 +8,7 @@ import ( ) func (s *Service) Create(ctx context.Context, name string) (uuid.UUID, error) { - name, err := normalizeName(name) + err := normalizeName(&name) if err != nil { return uuid.Nil, err } diff --git a/internal/client/service.go b/internal/client/service.go index 16199843..84c1c02a 100644 --- a/internal/client/service.go +++ b/internal/client/service.go @@ -16,40 +16,57 @@ type Client struct { CanSync bool } -func (c *Client) getCanSyncUpdate(n *bool) bool { - if n == nil { - return c.CanSync +func (c *Client) normalizeCanSyncUpdate(n **bool) { + if n == nil || *n == nil { + return } - return *n + if c.CanSync == **n { + *n = nil + } } -func (c *Client) getNameUpdate(n *string) (string, error) { - if n == nil { - return c.Name, nil +func (c *Client) normalizeNameUpdate(n **string) error { + if n == nil || *n == nil { + return nil } - return normalizeName(*n) + err := normalizeName(*n) + if err != nil { + return err + } + + if c.Name == **n { + *n = nil + } + + return nil } -func normalizeName(name string) (string, error) { - name = strings.TrimSpace(name) +func normalizeName(name *string) error { + if name == nil { + return nil + } - if name == "" { - return "", errors.New("name required") + uname := strings.TrimSpace(*name) + + if uname == "" { + return errors.New("name required") } spacere := regexp.MustCompile(`\s+`) - name = spacere.ReplaceAllString(name, " ") + uname = spacere.ReplaceAllString(uname, " ") reg := `^[a-zA-Z0-9\_\- ]+$` alphanumeric := regexp.MustCompile(reg) - if !alphanumeric.MatchString(name) { - return "", fmt.Errorf("name must have regex: %s", reg) + if !alphanumeric.MatchString(uname) { + return fmt.Errorf("name must have regex: %s", reg) } - return name, nil + *name = uname + + return nil } type Service struct { diff --git a/internal/client/serviceprivate_test.go b/internal/client/serviceprivate_test.go index b712dbc8..f1d119f7 100644 --- a/internal/client/serviceprivate_test.go +++ b/internal/client/serviceprivate_test.go @@ -8,67 +8,92 @@ import ( ) func TestNormalizeName(t *testing.T) { - name, err := normalizeName("name") + name := "name" + err := normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name", name) - _, err = normalizeName("") + name = "" + err = normalizeName(&name) assert.NotNil(t, err) - name, err = normalizeName(" name\t") + name = " name\t" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name", name) - name, err = normalizeName("name second") + name = "name second" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name second", name) - name, err = normalizeName("name\t second") + name = "name\t second" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name second", name) - name, err = normalizeName("name_second") + name = "name_second" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name_second", name) - name, err = normalizeName("name-second") + name = "name-second" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name-second", name) - name, err = normalizeName("name123") + name = "name123" + err = normalizeName(&name) assert.Nil(t, err) assert.Equal(t, "name123", name) } -func TestGetCanSyncUpdate(t *testing.T) { +func TestNormalizeCanSyncUpdate(t *testing.T) { c := Client{ ID: uuid.New(), CanSync: true, } - assert.Equal(t, true, c.getCanSyncUpdate(nil)) - val := false - assert.Equal(t, false, c.getCanSyncUpdate(&val)) - val = true - assert.Equal(t, true, c.getCanSyncUpdate(&val)) + c.normalizeCanSyncUpdate(nil) + var val *bool + c.normalizeCanSyncUpdate(&val) + assert.Nil(t, val) + + v := true + val = &v + c.normalizeCanSyncUpdate(&val) + assert.Nil(t, val) + + v = false + val = &v + c.normalizeCanSyncUpdate(&val) + assert.NotNil(t, *val) } -func TestGetNameUpdate(t *testing.T) { +func TestNormalizeNameUpdate(t *testing.T) { c := Client{ ID: uuid.New(), Name: "example_name", } - name, err := c.getNameUpdate(nil) + err := c.normalizeNameUpdate(nil) assert.Nil(t, err) - assert.Equal(t, "example_name", name) - val := "update_name" - name, err = c.getNameUpdate(&val) + v := "update_name" + val := &v + err = c.normalizeNameUpdate(&val) assert.Nil(t, err) - assert.Equal(t, "update_name", name) + assert.NotNil(t, val) + assert.Equal(t, "update_name", *val) - val = "###" - _, err = c.getNameUpdate(&val) + v = c.Name + val = &v + err = c.normalizeNameUpdate(&val) + assert.Nil(t, err) + assert.Nil(t, val) + + v = "###" + val = &v + err = c.normalizeNameUpdate(&val) assert.Error(t, err) } diff --git a/internal/client/update.go b/internal/client/update.go index 6f211ed5..1018b131 100644 --- a/internal/client/update.go +++ b/internal/client/update.go @@ -21,12 +21,12 @@ func (s *Service) Update(ctx context.Context, entity *Update) error { return err } - params, err := s.getUpdateParams(current, entity) + err = s.normalizeUpdateParams(current, entity) if err != nil { return err } - err = s.db.Queries.UpdateClient(ctx, params) + err = s.submitUpdate(ctx, entity) if err != nil { return err } @@ -34,21 +34,49 @@ func (s *Service) Update(ctx context.Context, entity *Update) error { return nil } -func (s *Service) getUpdateParams(current *Client, entity *Update) (*repository.UpdateClientParams, error) { - if entity == nil || current == nil || current.ID != entity.ID { - return nil, errors.New("no updates presented") - } +func (s *Service) submitUpdate(ctx context.Context, entity *Update) error { + return database.ExecuteTransaction(ctx, s.db, func(ctx context.Context, q *repository.Queries) error { + id := database.MustToDBUUID(entity.ID) - name, err := current.getNameUpdate(entity.Name) - if err != nil { - return nil, err - } + if entity.Name != nil { + err := q.UpdateClient(ctx, &repository.UpdateClientParams{ + ID: id, + Name: *entity.Name, + }) + if err != nil { + return err + } + } - canSync := current.getCanSyncUpdate(entity.CanSync) + if entity.CanSync != nil { + err := q.AddClientCanSync(ctx, &repository.AddClientCanSyncParams{ + Clientid: id, + Cansync: *entity.CanSync, + }) + if err != nil { + return err + } + } - return &repository.UpdateClientParams{ - ID: database.MustToDBUUID(current.ID), - Name: name, - Cansync: canSync, - }, nil + return nil + }) +} + +func (s *Service) normalizeUpdateParams(current *Client, entity *Update) error { + if entity == nil || current == nil || current.ID != entity.ID { + return errors.New("no updates presented") + } + + err := current.normalizeNameUpdate(&entity.Name) + if err != nil { + return err + } + + current.normalizeCanSyncUpdate(&entity.CanSync) + + if entity.Name == nil && entity.CanSync == nil { + return errors.New("no updates presented") + } + + return nil } diff --git a/internal/client/update_test.go b/internal/client/update_test.go index 136070c1..84d153f5 100644 --- a/internal/client/update_test.go +++ b/internal/client/update_test.go @@ -1,8 +1,7 @@ -package client_test +package client import ( "context" - "queryorchestration/internal/client" "queryorchestration/internal/database" "queryorchestration/internal/database/repository" "testing" @@ -25,14 +24,14 @@ func TestUpdate(t *testing.T) { Pool: pool, } - svc := client.New(db) + svc := New(db) - c := client.Client{ + c := Client{ ID: uuid.New(), Name: "example_name", CanSync: false, } - update := client.Update{ + update := Update{ ID: c.ID, } @@ -41,11 +40,9 @@ func TestUpdate(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(c.ID), c.Name, c.CanSync), ) - pool.ExpectExec("name: UpdateClient :exec").WithArgs(c.Name, c.CanSync, database.MustToDBUUID(update.ID)). - WillReturnResult(pgxmock.NewResult("", 1)) err = svc.Update(ctx, &update) - assert.Nil(t, err) + assert.Error(t, err) c.CanSync = false update.CanSync = &c.CanSync @@ -55,11 +52,9 @@ func TestUpdate(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(c.ID), c.Name, c.CanSync), ) - pool.ExpectExec("name: UpdateClient :exec").WithArgs(c.Name, *update.CanSync, database.MustToDBUUID(update.ID)). - WillReturnResult(pgxmock.NewResult("", 1)) err = svc.Update(ctx, &update) - assert.Nil(t, err) + assert.Error(t, err) c.Name = "updated_name" update.Name = &c.Name @@ -69,9 +64,104 @@ func TestUpdate(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(c.ID), c.Name, c.CanSync), ) - pool.ExpectExec("name: UpdateClient :exec").WithArgs(*update.Name, *update.CanSync, database.MustToDBUUID(update.ID)). + pool.ExpectExec("name: UpdateClient :exec").WithArgs(*update.Name, database.MustToDBUUID(update.ID)). WillReturnResult(pgxmock.NewResult("", 1)) err = svc.Update(ctx, &update) + assert.Error(t, err) +} + +func TestNormalizeUpdateParams(t *testing.T) { + svc := Service{} + + err := svc.normalizeUpdateParams(nil, nil) + assert.Error(t, err) + + current := &Client{ + ID: uuid.New(), + Name: "client", + } + update := &Update{} + + err = svc.normalizeUpdateParams(current, update) + assert.Error(t, err) + + n := "updated_client" + cs := true + update = &Update{ + ID: current.ID, + Name: &n, + CanSync: &cs, + } + err = svc.normalizeUpdateParams(current, update) + assert.Nil(t, err) + n = "updated_client" + cs = true + assert.EqualExportedValues(t, Update{ + ID: current.ID, + Name: &n, + CanSync: &cs, + }, *update) + + update = &Update{ + ID: current.ID, + Name: ¤t.Name, + CanSync: ¤t.CanSync, + } + err = svc.normalizeUpdateParams(current, update) + assert.Error(t, err) +} + +func TestSubmitUpdate(t *testing.T) { + ctx := context.Background() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + svc := New(db) + + c := Client{ + ID: uuid.New(), + Name: "example_name", + CanSync: false, + } + update := Update{ + ID: c.ID, + } + + pool.ExpectBegin() + pool.ExpectCommit() + + err = svc.submitUpdate(ctx, &update) + assert.Nil(t, err) + + c.CanSync = true + update.CanSync = &c.CanSync + + pool.ExpectBegin() + pool.ExpectExec("name: AddClientCanSync :exec").WithArgs(*update.CanSync, database.MustToDBUUID(update.ID)). + WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() + + err = svc.submitUpdate(ctx, &update) + assert.Nil(t, err) + + c.Name = "updated_name" + update.Name = &c.Name + update.CanSync = nil + + pool.ExpectBegin() + pool.ExpectExec("name: UpdateClient :exec").WithArgs(*update.Name, database.MustToDBUUID(update.ID)). + WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() + + err = svc.submitUpdate(ctx, &update) assert.Nil(t, err) } diff --git a/internal/database/connection_test.go b/internal/database/connection_test.go index fb4eccdb..0457d453 100644 --- a/internal/database/connection_test.go +++ b/internal/database/connection_test.go @@ -30,7 +30,7 @@ func TestDBConn(t *testing.T) { assert.NotNil(t, pool) } -func TestExecuteTransation(t *testing.T) { +func TestExecuteTransaction(t *testing.T) { ctx := context.Background() pool, err := pgxmock.NewPool() diff --git a/internal/database/repository/client.sql.go b/internal/database/repository/client.sql.go index 0057741e..5509a41c 100644 --- a/internal/database/repository/client.sql.go +++ b/internal/database/repository/client.sql.go @@ -11,6 +11,23 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const addClientCanSync = `-- name: AddClientCanSync :exec +INSERT INTO clientCanSync (canSync, clientId) VALUES ($1, $2) +` + +type AddClientCanSyncParams struct { + Cansync bool `db:"cansync"` + Clientid pgtype.UUID `db:"clientid"` +} + +// AddClientCanSync +// +// INSERT INTO clientCanSync (canSync, clientId) VALUES ($1, $2) +func (q *Queries) AddClientCanSync(ctx context.Context, arg *AddClientCanSyncParams) error { + _, err := q.db.Exec(ctx, addClientCanSync, arg.Cansync, arg.Clientid) + return err +} + const createClient = `-- name: CreateClient :one INSERT INTO clients (name) VALUES ($1) RETURNING id ` @@ -26,33 +43,56 @@ func (q *Queries) CreateClient(ctx context.Context, name string) (pgtype.UUID, e } const getClient = `-- name: GetClient :one -SELECT id, name, canSync FROM clients WHERE id = $1 +SELECT c.id, c.name, coalesce(cs.canSync, false) as canSync + FROM clients as c + LEFT JOIN ( + SELECT clientId, canSync + FROM clientCanSync + WHERE clientId = $1 + ORDER BY addedAt DESC + LIMIT 1 + ) as cs on cs.clientId = c.id + WHERE c.id = $1 ` +type GetClientRow struct { + ID pgtype.UUID `db:"id"` + Name string `db:"name"` + Cansync bool `db:"cansync"` +} + // GetClient // -// SELECT id, name, canSync FROM clients WHERE id = $1 -func (q *Queries) GetClient(ctx context.Context, id pgtype.UUID) (*Client, error) { - row := q.db.QueryRow(ctx, getClient, id) - var i Client +// SELECT c.id, c.name, coalesce(cs.canSync, false) as canSync +// FROM clients as c +// LEFT JOIN ( +// SELECT clientId, canSync +// FROM clientCanSync +// WHERE clientId = $1 +// ORDER BY addedAt DESC +// LIMIT 1 +// ) as cs on cs.clientId = c.id +// WHERE c.id = $1 +func (q *Queries) GetClient(ctx context.Context, clientid pgtype.UUID) (*GetClientRow, error) { + row := q.db.QueryRow(ctx, getClient, clientid) + var i GetClientRow err := row.Scan(&i.ID, &i.Name, &i.Cansync) return &i, err } const updateClient = `-- name: UpdateClient :exec -UPDATE clients SET name = $1, canSync = $2 WHERE id = $3 +UPDATE clients SET name = $1 WHERE id = $2 ` type UpdateClientParams struct { - Name string `db:"name"` - Cansync bool `db:"cansync"` - ID pgtype.UUID `db:"id"` + Name string `db:"name"` + ID pgtype.UUID `db:"id"` } // UpdateClient // -// UPDATE clients SET name = $1, canSync = $2 WHERE id = $3 +// UPDATE clients SET name = $1 WHERE id = $2 func (q *Queries) UpdateClient(ctx context.Context, arg *UpdateClientParams) error { - _, err := q.db.Exec(ctx, updateClient, arg.Name, arg.Cansync, arg.ID) + _, err := q.db.Exec(ctx, updateClient, arg.Name, arg.ID) return err } diff --git a/internal/database/repository/client_test.go b/internal/database/repository/client_test.go index ffa901e8..15c4f521 100644 --- a/internal/database/repository/client_test.go +++ b/internal/database/repository/client_test.go @@ -28,22 +28,35 @@ func TestClient(t *testing.T) { client, err := queries.GetClient(ctx, id) assert.Nil(t, err) - assert.EqualExportedValues(t, &repository.Client{ + assert.EqualExportedValues(t, &repository.GetClientRow{ ID: id, Name: "example_client", Cansync: false, }, client) err = queries.UpdateClient(ctx, &repository.UpdateClientParams{ - ID: id, - Name: "updated_client", - Cansync: true, + ID: id, + Name: "updated_client", }) assert.Nil(t, err) client, err = queries.GetClient(ctx, id) assert.Nil(t, err) - assert.EqualExportedValues(t, &repository.Client{ + assert.EqualExportedValues(t, &repository.GetClientRow{ + ID: id, + Name: "updated_client", + Cansync: false, + }, client) + + err = queries.AddClientCanSync(ctx, &repository.AddClientCanSyncParams{ + Clientid: id, + Cansync: true, + }) + assert.Nil(t, err) + + client, err = queries.GetClient(ctx, id) + assert.Nil(t, err) + assert.EqualExportedValues(t, &repository.GetClientRow{ ID: id, Name: "updated_client", Cansync: true, diff --git a/internal/database/repository/job.sql.go b/internal/database/repository/job.sql.go index 88a28134..3d987c34 100644 --- a/internal/database/repository/job.sql.go +++ b/internal/database/repository/job.sql.go @@ -11,6 +11,23 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const addJobCanSync = `-- name: AddJobCanSync :exec +INSERT INTO jobCanSync (canSync, jobId) VALUES ($1, $2) +` + +type AddJobCanSyncParams struct { + Cansync bool `db:"cansync"` + Jobid pgtype.UUID `db:"jobid"` +} + +// AddJobCanSync +// +// INSERT INTO jobCanSync (canSync, jobId) VALUES ($1, $2) +func (q *Queries) AddJobCanSync(ctx context.Context, arg *AddJobCanSyncParams) error { + _, err := q.db.Exec(ctx, addJobCanSync, arg.Cansync, arg.Jobid) + return err +} + const createJob = `-- name: CreateJob :one INSERT INTO jobs (clientId) VALUES ($1) RETURNING id ` @@ -26,32 +43,39 @@ func (q *Queries) CreateJob(ctx context.Context, clientid pgtype.UUID) (pgtype.U } const getJob = `-- name: GetJob :one -SELECT id, clientId, canSync FROM jobs WHERE id = $1 LIMIT 1 +SELECT j.id, j.clientId, coalesce(cs.canSync, false) as canSync + FROM jobs as j + LEFT JOIN ( + SELECT jobId, canSync + FROM jobCanSync + WHERE jobId = $1 + ORDER BY addedAt DESC + LIMIT 1 + ) as cs on cs.jobId = j.id + WHERE j.id = $1 ` +type GetJobRow struct { + ID pgtype.UUID `db:"id"` + Clientid pgtype.UUID `db:"clientid"` + Cansync bool `db:"cansync"` +} + // GetJob // -// SELECT id, clientId, canSync FROM jobs WHERE id = $1 LIMIT 1 -func (q *Queries) GetJob(ctx context.Context, id pgtype.UUID) (*Job, error) { - row := q.db.QueryRow(ctx, getJob, id) - var i Job +// SELECT j.id, j.clientId, coalesce(cs.canSync, false) as canSync +// FROM jobs as j +// LEFT JOIN ( +// SELECT jobId, canSync +// FROM jobCanSync +// WHERE jobId = $1 +// ORDER BY addedAt DESC +// LIMIT 1 +// ) as cs on cs.jobId = j.id +// WHERE j.id = $1 +func (q *Queries) GetJob(ctx context.Context, jobid pgtype.UUID) (*GetJobRow, error) { + row := q.db.QueryRow(ctx, getJob, jobid) + var i GetJobRow err := row.Scan(&i.ID, &i.Clientid, &i.Cansync) return &i, err } - -const updateJob = `-- name: UpdateJob :exec -UPDATE jobs SET canSync = $1 WHERE id = $2 -` - -type UpdateJobParams struct { - Cansync bool `db:"cansync"` - ID pgtype.UUID `db:"id"` -} - -// UpdateJob -// -// UPDATE jobs SET canSync = $1 WHERE id = $2 -func (q *Queries) UpdateJob(ctx context.Context, arg *UpdateJobParams) error { - _, err := q.db.Exec(ctx, updateJob, arg.Cansync, arg.ID) - return err -} diff --git a/internal/database/repository/job_test.go b/internal/database/repository/job_test.go index 6f1d8ff5..f586320e 100644 --- a/internal/database/repository/job_test.go +++ b/internal/database/repository/job_test.go @@ -31,21 +31,21 @@ func TestJob(t *testing.T) { job, err := queries.GetJob(ctx, id) assert.Nil(t, err) - assert.EqualExportedValues(t, &repository.Job{ + assert.EqualExportedValues(t, &repository.GetJobRow{ ID: id, Clientid: clientId, Cansync: false, }, job) - err = queries.UpdateJob(ctx, &repository.UpdateJobParams{ + err = queries.AddJobCanSync(ctx, &repository.AddJobCanSyncParams{ Cansync: true, - ID: id, + Jobid: id, }) assert.Nil(t, err) job, err = queries.GetJob(ctx, id) assert.Nil(t, err) - assert.EqualExportedValues(t, &repository.Job{ + assert.EqualExportedValues(t, &repository.GetJobRow{ ID: id, Clientid: clientId, Cansync: true, diff --git a/internal/database/repository/models.go b/internal/database/repository/models.go index f6e8e58d..df0d1e58 100644 --- a/internal/database/repository/models.go +++ b/internal/database/repository/models.go @@ -63,9 +63,15 @@ func (e Querytype) Valid() bool { } type Client struct { - ID pgtype.UUID `db:"id"` - Name string `db:"name"` - Cansync bool `db:"cansync"` + ID pgtype.UUID `db:"id"` + Name string `db:"name"` +} + +type Clientcansync struct { + ID pgtype.UUID `db:"id"` + Clientid pgtype.UUID `db:"clientid"` + Cansync bool `db:"cansync"` + Addedat pgtype.Timestamp `db:"addedat"` } type Collector struct { @@ -130,7 +136,13 @@ type Fullactivequery struct { type Job struct { ID pgtype.UUID `db:"id"` Clientid pgtype.UUID `db:"clientid"` - Cansync bool `db:"cansync"` +} + +type Jobcansync struct { + ID pgtype.UUID `db:"id"` + Jobid pgtype.UUID `db:"jobid"` + Cansync bool `db:"cansync"` + Addedat pgtype.Timestamp `db:"addedat"` } type Query struct { diff --git a/internal/job/update.go b/internal/job/update.go index 386ff2c1..3c19893e 100644 --- a/internal/job/update.go +++ b/internal/job/update.go @@ -24,7 +24,21 @@ func (s *Service) Update(ctx context.Context, update *Update) error { return err } - err = s.normalizeCanSync(ctx, current, update) + err = s.normalizeUpdate(ctx, current, update) + if err != nil { + return err + } + + err = s.submitUpdate(ctx, update) + if err != nil { + return err + } + + return nil +} + +func (s *Service) normalizeUpdate(ctx context.Context, current *Job, update *Update) error { + err := s.normalizeCanSync(ctx, current, update) if err != nil { return err } @@ -33,17 +47,27 @@ func (s *Service) Update(ctx context.Context, update *Update) error { return errors.New("no changes") } - err = s.db.Queries.UpdateJob(ctx, &repository.UpdateJobParams{ - ID: database.MustToDBUUID(update.ID), - Cansync: *update.CanSync, - }) - if err != nil { - return err - } - return nil } +func (s *Service) submitUpdate(ctx context.Context, update *Update) error { + err := database.ExecuteTransaction(ctx, s.db, func(ctx context.Context, q *repository.Queries) error { + if update.CanSync != nil { + err := q.AddJobCanSync(ctx, &repository.AddJobCanSyncParams{ + Jobid: database.MustToDBUUID(update.ID), + Cansync: *update.CanSync, + }) + if err != nil { + return err + } + } + + return nil + }) + + return err +} + func (s *Service) normalizeCanSync(ctx context.Context, current *Job, update *Update) error { if update.CanSync == nil { return nil @@ -52,10 +76,14 @@ func (s *Service) normalizeCanSync(ctx context.Context, current *Job, update *Up return nil } + if !*update.CanSync { + return nil + } + client, err := s.svc.Client.Get(ctx, current.ClientID) if err != nil { return err - } else if !client.CanSync && *update.CanSync { + } else if !client.CanSync { return errors.New("client not allowing sync") } diff --git a/internal/job/update_test.go b/internal/job/update_test.go index 951ecdb5..2d715517 100644 --- a/internal/job/update_test.go +++ b/internal/job/update_test.go @@ -35,6 +35,11 @@ func TestUpdate(t *testing.T) { ClientID: uuid.New(), CanSync: true, } + ucs := !j.CanSync + u := &job.Update{ + ID: j.ID, + CanSync: &ucs, + } pool.ExpectQuery("name: GetJob :one").WithArgs(database.MustToDBUUID(j.ID)).WillReturnRows( pgxmock.NewRows([]string{"id", "clientId", "canSync"}). @@ -45,18 +50,11 @@ func TestUpdate(t *testing.T) { pgxmock.NewRows([]string{"id", "name", "canSync"}). AddRow(database.MustToDBUUID(j.ClientID), "client_name", true), ) - pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(j.ClientID)). - WillReturnRows( - pgxmock.NewRows([]string{"id", "name", "canSync"}). - AddRow(database.MustToDBUUID(j.ClientID), "client_name", true), - ) - ucs := !j.CanSync - pool.ExpectExec("name: UpdateJob :exec").WithArgs(ucs, database.MustToDBUUID(j.ID)). + pool.ExpectBegin() + pool.ExpectExec("name: AddJobCanSync :exec").WithArgs(*u.CanSync, database.MustToDBUUID(j.ID)). WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() - err = svc.Update(ctx, &job.Update{ - ID: j.ID, - CanSync: &ucs, - }) + err = svc.Update(ctx, u) assert.Nil(t, err) } diff --git a/internal/job/updateprivate_test.go b/internal/job/updateprivate_test.go index a5742a61..3b1a592d 100644 --- a/internal/job/updateprivate_test.go +++ b/internal/job/updateprivate_test.go @@ -91,3 +91,83 @@ func TestNormalizeCanSync(t *testing.T) { assert.Nil(t, err) assert.Nil(t, update.CanSync) } + +func TestNormalizeUpdate(t *testing.T) { + ctx := context.Background() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + svc := New(db, &Services{ + Client: client.New(db), + }) + + j := Job{ + ID: uuid.New(), + ClientID: uuid.New(), + CanSync: true, + } + + ucs := false + update := Update{ + ID: j.ID, + CanSync: &ucs, + } + + err = svc.normalizeUpdate(ctx, &j, &update) + assert.Nil(t, err) + ucs = !ucs + assert.EqualExportedValues(t, Update{ID: update.ID, CanSync: &ucs}, update) +} + +func TestSubmitUpdate(t *testing.T) { + ctx := context.Background() + + pool, err := pgxmock.NewPool() + if err != nil { + t.Fatalf("failed to open pgxmock database: %v", err) + } + queries := repository.New(pool) + db := &database.Connection{ + Queries: queries, + Pool: pool, + } + + svc := Service{ + db: db, + } + + j := Job{ + ID: uuid.New(), + } + u := &Update{ + ID: j.ID, + } + + pool.ExpectBegin() + pool.ExpectCommit() + + err = svc.submitUpdate(ctx, u) + assert.Nil(t, err) + + ucs := true + u = &Update{ + ID: j.ID, + CanSync: &ucs, + } + + pool.ExpectBegin() + pool.ExpectExec("name: AddJobCanSync :exec").WithArgs(*u.CanSync, database.MustToDBUUID(j.ID)). + WillReturnResult(pgxmock.NewResult("", 1)) + pool.ExpectCommit() + + err = svc.submitUpdate(ctx, u) + assert.Nil(t, err) +} diff --git a/test/queryService/testquery_test.go b/test/queryService/testquery_test.go index 2c224a16..2d5b2c41 100644 --- a/test/queryService/testquery_test.go +++ b/test/queryService/testquery_test.go @@ -34,6 +34,7 @@ func TestQueryServiceTest(t *testing.T) { }) assert.Nil(t, err) assert.NotNil(t, testRes) + // TODO // assert.NotNil(t, testRes.JSON200) // assert.Equal(t, "hey", testRes.JSON200.Value) }