diff --git a/api/queryService/api.gen.go b/api/queryService/api.gen.go index b8a8651e..03aa3521 100644 --- a/api/queryService/api.gen.go +++ b/api/queryService/api.gen.go @@ -43,6 +43,21 @@ const ( JSONEXTRACTOR QueryType = "JSON_EXTRACTOR" ) +// ClientCreate defines model for ClientCreate. +type ClientCreate struct { + // Name The client name + Name string `json:"name"` +} + +// ClientUpdate defines model for ClientUpdate. +type ClientUpdate struct { + // CanSync If the client is allowing active syncs + CanSync *bool `json:"can_sync,omitempty"` + + // Name The client name + Name *string `json:"name,omitempty"` +} + // ExportDetails Payload for export trigger response. type ExportDetails struct { // JobId The job id relative to the export. @@ -97,6 +112,18 @@ type IdMessage struct { Id string `json:"id"` } +// JobClient defines model for JobClient. +type JobClient struct { + // CanSync If the client is allowing active syncs + CanSync bool `json:"can_sync"` + + // Id The client id + Id string `json:"id"` + + // Name The client name + Name string `json:"name"` +} + // JobCollector JobCollector model. type JobCollector struct { // ActiveVersion The active version of the collector. @@ -211,6 +238,12 @@ type QueryUpdate struct { RequiredQueries *[]string `json:"required_queries,omitempty"` } +// CreateClientJSONRequestBody defines body for CreateClient for application/json ContentType. +type CreateClientJSONRequestBody = ClientCreate + +// UpdateClientJSONRequestBody defines body for UpdateClient for application/json ContentType. +type UpdateClientJSONRequestBody = ClientUpdate + // TriggerExportJSONRequestBody defines body for TriggerExport for application/json ContentType. type TriggerExportJSONRequestBody = ExportTrigger @@ -228,6 +261,15 @@ type TestQueryJSONRequestBody = QueryTestRequest // ServerInterface represents all server handlers. type ServerInterface interface { + // Create a new client + // (POST /clients) + CreateClient(ctx echo.Context) error + // Get a client by ID + // (GET /clients/{id}) + GetClient(ctx echo.Context, id string) error + // Update a client + // (PATCH /clients/{id}) + UpdateClient(ctx echo.Context, id string) error // Trigger an export // (POST /job/export) TriggerExport(ctx echo.Context) error @@ -246,12 +288,9 @@ type ServerInterface interface { // Create a new query // (POST /queries) CreateQuery(ctx echo.Context) error - // Deprecate a query - // (DELETE /queries/{id}) - DeprecateQuery(ctx echo.Context, id string) error // Get a query by ID // (GET /queries/{id}) - GetQueryById(ctx echo.Context, id string) error + GetQuery(ctx echo.Context, id string) error // Update a query // (PATCH /queries/{id}) UpdateQuery(ctx echo.Context, id string) error @@ -265,6 +304,47 @@ type ServerInterfaceWrapper struct { Handler ServerInterface } +// CreateClient converts echo context to params. +func (w *ServerInterfaceWrapper) CreateClient(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.CreateClient(ctx) + return err +} + +// GetClient converts echo context to params. +func (w *ServerInterfaceWrapper) GetClient(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetClient(ctx, id) + return err +} + +// UpdateClient converts echo context to params. +func (w *ServerInterfaceWrapper) UpdateClient(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.UpdateClient(ctx, id) + return err +} + // TriggerExport converts echo context to params. func (w *ServerInterfaceWrapper) TriggerExport(ctx echo.Context) error { var err error @@ -340,8 +420,8 @@ func (w *ServerInterfaceWrapper) CreateQuery(ctx echo.Context) error { return err } -// DeprecateQuery converts echo context to params. -func (w *ServerInterfaceWrapper) DeprecateQuery(ctx echo.Context) error { +// GetQuery converts echo context to params. +func (w *ServerInterfaceWrapper) GetQuery(ctx echo.Context) error { var err error // ------------- Path parameter "id" ------------- var id string @@ -352,23 +432,7 @@ func (w *ServerInterfaceWrapper) DeprecateQuery(ctx echo.Context) error { } // Invoke the callback with all the unmarshaled arguments - err = w.Handler.DeprecateQuery(ctx, id) - return err -} - -// GetQueryById converts echo context to params. -func (w *ServerInterfaceWrapper) GetQueryById(ctx echo.Context) error { - var err error - // ------------- Path parameter "id" ------------- - var id string - - err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) - } - - // Invoke the callback with all the unmarshaled arguments - err = w.Handler.GetQueryById(ctx, id) + err = w.Handler.GetQuery(ctx, id) return err } @@ -432,14 +496,16 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL Handler: si, } + router.POST(baseURL+"/clients", wrapper.CreateClient) + router.GET(baseURL+"/clients/:id", wrapper.GetClient) + router.PATCH(baseURL+"/clients/:id", wrapper.UpdateClient) router.POST(baseURL+"/job/export", wrapper.TriggerExport) router.GET(baseURL+"/job/export/:id", wrapper.ExportState) router.GET(baseURL+"/job/:id/collector", wrapper.GetJobCollectorByJobId) router.PATCH(baseURL+"/job/:id/collector", wrapper.UpdateJobCollectorByJobId) router.GET(baseURL+"/queries", wrapper.ListQueries) router.POST(baseURL+"/queries", wrapper.CreateQuery) - router.DELETE(baseURL+"/queries/:id", wrapper.DeprecateQuery) - router.GET(baseURL+"/queries/:id", wrapper.GetQueryById) + router.GET(baseURL+"/queries/:id", wrapper.GetQuery) router.PATCH(baseURL+"/queries/:id", wrapper.UpdateQuery) router.POST(baseURL+"/queries/:id/test", wrapper.TestQuery) @@ -448,42 +514,45 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "UleSoTLDcCUS1p6q7L7u/h8AAP//+pSl8AgsAAA=", + "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", + "AA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/queryService/client.go b/api/queryService/client.go new file mode 100644 index 00000000..e2232070 --- /dev/null +++ b/api/queryService/client.go @@ -0,0 +1,67 @@ +package queryservice + +import ( + "fmt" + "net/http" + "queryorchestration/internal/client" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" +) + +func (s *Controllers) CreateClient(ctx echo.Context) error { + req := ClientCreate{} + if err := ctx.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } + + id, err := s.svc.Client.Create(ctx.Request().Context(), req.Name) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to create client: %s", err)) + } + + return ctx.JSON(http.StatusCreated, IdMessage{ + Id: id.String(), + }) +} + +func (s *Controllers) GetClient(ctx echo.Context, id string) error { + uid, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") + } + + client, err := s.svc.Client.Get(ctx.Request().Context(), uid) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Unable to get client: %s", err)) + } + + return ctx.JSON(http.StatusOK, JobClient{ + Id: client.ID.String(), + Name: client.Name, + CanSync: client.CanSync, + }) +} + +func (s *Controllers) UpdateClient(ctx echo.Context, id string) error { + req := ClientUpdate{} + if err := ctx.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err) + } + + uid, err := uuid.Parse(id) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") + } + + err = s.svc.Client.Update(ctx.Request().Context(), &client.Update{ + ID: uid, + Name: req.Name, + CanSync: req.CanSync, + }) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to update query: %s", err)) + } + + return ctx.NoContent(http.StatusOK) +} diff --git a/api/queryService/client_test.go b/api/queryService/client_test.go new file mode 100644 index 00000000..cffd4f16 --- /dev/null +++ b/api/queryService/client_test.go @@ -0,0 +1,146 @@ +package queryservice_test + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + queryservice "queryorchestration/api/queryService" + "queryorchestration/internal/client" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "strings" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestCreateClient(t *testing.T) { + 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, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Client: client.New(db), + }) + + body := queryservice.ClientCreate{ + Name: "example_name", + } + bodyBytes, err := json.Marshal(body) + assert.Nil(t, err) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(bodyBytes))) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + pool.ExpectQuery("-- name: CreateClient :one").WithArgs(body.Name).WillReturnRows( + pgxmock.NewRows([]string{"id"}). + AddRow(database.MustToDBUUID(id)), + ) + + err = cons.CreateClient(ctx) + assert.Nil(t, err) + assert.Equal(t, http.StatusCreated, rec.Code) + assert.Equal(t, fmt.Sprintf("{\"id\":\"%s\"}\n", id), rec.Body.String()) +} + +func TestGetClient(t *testing.T) { + 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, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Client: client.New(db), + }) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + ctx.Set("id", id) + + pool.ExpectQuery("-- name: GetClient :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( + pgxmock.NewRows([]string{"id", "name", "canSync"}). + AddRow(database.MustToDBUUID(id), "client_name", true), + ) + + err = cons.GetClient(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + + var res queryservice.JobClient + err = json.Unmarshal(rec.Body.Bytes(), &res) + assert.Nil(t, err) + assert.EqualExportedValues(t, queryservice.JobClient{ + Id: id.String(), + Name: "client_name", + CanSync: true, + }, res) +} + +func TestUpdateClient(t *testing.T) { + 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, + } + + cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ + Client: client.New(db), + }) + + body := queryservice.QueryUpdate{} + bodyBytes, err := json.Marshal(body) + assert.Nil(t, err) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(bodyBytes))) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + ctx := e.NewContext(req, rec) + + id := uuid.New() + + ctx.Set("id", id) + + pool.ExpectQuery("-- name: GetClient :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( + 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)). + WillReturnResult(pgxmock.NewResult("", 1)) + + err = cons.UpdateClient(ctx, id.String()) + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.Empty(t, rec.Body.String()) +} diff --git a/api/queryService/controllers.go b/api/queryService/controllers.go index 1ff96354..d19c0729 100644 --- a/api/queryService/controllers.go +++ b/api/queryService/controllers.go @@ -1,6 +1,7 @@ package queryservice import ( + "queryorchestration/internal/client" "queryorchestration/internal/export" "queryorchestration/internal/job/collector" "queryorchestration/internal/query" @@ -12,6 +13,7 @@ type Services struct { Export *export.Service JobCollector *collector.Service Query *query.Service + Client *client.Service } type Controllers struct { diff --git a/api/queryService/query.go b/api/queryService/query.go index a056aa1b..eba054d0 100644 --- a/api/queryService/query.go +++ b/api/queryService/query.go @@ -28,7 +28,7 @@ func (s *Controllers) ListQueries(ctx echo.Context) error { }) } -func (s *Controllers) GetQueryById(ctx echo.Context, id string) error { +func (s *Controllers) GetQuery(ctx echo.Context, id string) error { uid, err := uuid.Parse(id) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") @@ -106,20 +106,6 @@ func (s *Controllers) UpdateQuery(ctx echo.Context, id string) error { return ctx.NoContent(http.StatusOK) } -func (s *Controllers) DeprecateQuery(ctx echo.Context, id string) error { - uid, err := uuid.Parse(id) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID") - } - - err = s.svc.Query.Deprecate(ctx.Request().Context(), uid) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unable to deprecate query: %s", err)) - } - - return ctx.NoContent(http.StatusOK) -} - func (s *Controllers) TestQuery(ctx echo.Context, id string) error { queryId, err := uuid.Parse(id) if err != nil { diff --git a/api/queryService/query_test.go b/api/queryService/query_test.go index f6b91161..9e671a62 100644 --- a/api/queryService/query_test.go +++ b/api/queryService/query_test.go @@ -21,39 +21,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDeprecateQuery(t *testing.T) { - 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, - } - - cons := queryservice.NewControllers(validator.New(), &queryservice.Services{ - Query: query.New(db), - }) - - id := uuid.New() - - e := echo.New() - req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("")) - rec := httptest.NewRecorder() - ctx := e.NewContext(req, rec) - - pool.ExpectQuery("-- name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( - pgxmock.NewRows([]string{"isdeprecated"}). - AddRow(true), - ) - - err = cons.DeprecateQuery(ctx, id.String()) - assert.Nil(t, err) - assert.Equal(t, http.StatusOK, rec.Code) - assert.Empty(t, rec.Body.String()) -} - func TestCreateQuery(t *testing.T) { pool, err := pgxmock.NewPool() if err != nil { @@ -141,7 +108,7 @@ func TestListQueries(t *testing.T) { }) } -func TestGetQueryById(t *testing.T) { +func TestGetQuery(t *testing.T) { pool, err := pgxmock.NewPool() if err != nil { t.Fatalf("failed to open pgxmock database: %v", err) @@ -170,7 +137,7 @@ func TestGetQueryById(t *testing.T) { AddRow(database.MustToDBUUID(id), repository.QuerytypeContextFull, int32(1), int32(2), nil, []pgtype.UUID{}), ) - err = cons.GetQueryById(ctx, id.String()) + err = cons.GetQuery(ctx, id.String()) assert.Nil(t, err) assert.Equal(t, http.StatusOK, rec.Code) diff --git a/cmd/queryService/main.go b/cmd/queryService/main.go index 0bd85322..e24ff900 100644 --- a/cmd/queryService/main.go +++ b/cmd/queryService/main.go @@ -4,6 +4,7 @@ import ( "context" "log" queryservice "queryorchestration/api/queryService" + "queryorchestration/internal/client" "queryorchestration/internal/export" "queryorchestration/internal/job/collector" "queryorchestration/internal/query" @@ -21,11 +22,13 @@ func main() { exp := export.New(cfg.Database) col := collector.New(cfg.Database) que := query.New(cfg.Database) + cli := client.New(cfg.Database) services := &queryservice.Services{ Export: exp, JobCollector: col, Query: que, + Client: cli, } cons := queryservice.NewControllers(cfg.Validator, services) diff --git a/database/migrations/20241218183020_create_client_table.down.sql b/database/migrations/20241218183020_create_client_table.down.sql new file mode 100644 index 00000000..05c08227 --- /dev/null +++ b/database/migrations/20241218183020_create_client_table.down.sql @@ -0,0 +1 @@ +DROP TABLE clients; \ No newline at end of file diff --git a/database/migrations/20241218183020_create_client_table.up.sql b/database/migrations/20241218183020_create_client_table.up.sql new file mode 100644 index 00000000..1b462f22 --- /dev/null +++ b/database/migrations/20241218183020_create_client_table.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE clients ( + id uuid primary key DEFAULT gen_random_uuid(), + name TEXT not null, + canSync boolean not null default false, + UNIQUE (name) +); \ No newline at end of file diff --git a/database/migrations/20250103132852_query_deprecation_table.down.sql b/database/migrations/20250103132852_query_deprecation_table.down.sql deleted file mode 100644 index c0590869..00000000 --- a/database/migrations/20250103132852_query_deprecation_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE queryDeprecations; \ No newline at end of file diff --git a/database/migrations/20250103132852_query_deprecation_table.up.sql b/database/migrations/20250103132852_query_deprecation_table.up.sql deleted file mode 100644 index efe4d7d4..00000000 --- a/database/migrations/20250103132852_query_deprecation_table.up.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE TABLE queryDeprecations ( - id uuid primary key DEFAULT gen_random_uuid(), - queryId uuid not null, - time timestamp not null DEFAULT NOW(), - removedAt timestamp, - foreign key (queryId) references queries(id), - unique (queryId, removedAt) -); \ No newline at end of file diff --git a/database/queries/client.sql b/database/queries/client.sql new file mode 100644 index 00000000..629df93e --- /dev/null +++ b/database/queries/client.sql @@ -0,0 +1,8 @@ +-- name: CreateClient :one +INSERT INTO clients (name) VALUES ($1) RETURNING id; + +-- name: GetClient :one +SELECT id, name, canSync FROM clients WHERE id = $1; + +-- name: UpdateClient :exec +UPDATE clients SET name = $1, canSync = $2 WHERE id = $3; \ No newline at end of file diff --git a/database/queries/query.sql b/database/queries/query.sql index bf09f1b1..6ae17076 100644 --- a/database/queries/query.sql +++ b/database/queries/query.sql @@ -1,14 +1,6 @@ -- name: GetQueryConfig :one SELECT id, config FROM queryConfigs where queryId = $1 and addedVersion >= $2 and COALESCE(removedVersion, $2 - 1) < $2; --- name: DeprecateQuery :exec -INSERT INTO queryDeprecations (queryId) VALUES ($1); - --- name: IsQueryDeprecated :one -SELECT EXISTS ( - SELECT 1 FROM queryDeprecations where queryId = $1 and removedAt is null LIMIT 1 -); - -- name: GetQuery :one SELECT * FROM fullActiveQueries WHERE id = $1; diff --git a/internal/client/create.go b/internal/client/create.go new file mode 100644 index 00000000..16d6ab3f --- /dev/null +++ b/internal/client/create.go @@ -0,0 +1,22 @@ +package client + +import ( + "context" + "queryorchestration/internal/database" + + "github.com/google/uuid" +) + +func (s *Service) Create(ctx context.Context, name string) (uuid.UUID, error) { + name, err := normalizeName(name) + if err != nil { + return uuid.Nil, err + } + + id, err := s.db.Queries.CreateClient(ctx, name) + if err != nil { + return uuid.Nil, err + } + + return database.MustToUUID(id), nil +} diff --git a/internal/client/create_test.go b/internal/client/create_test.go new file mode 100644 index 00000000..0370c4a8 --- /dev/null +++ b/internal/client/create_test.go @@ -0,0 +1,42 @@ +package client_test + +import ( + "context" + "queryorchestration/internal/client" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "testing" + + "github.com/google/uuid" + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestCreate(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 := client.New(db) + + name := "client_name" + aid := uuid.New() + + pool.ExpectQuery("name: CreateClient :one").WithArgs(name). + WillReturnRows( + pgxmock.NewRows([]string{"id"}). + AddRow(database.MustToDBUUID(aid)), + ) + + id, err := svc.Create(ctx, name) + assert.Nil(t, err) + assert.Equal(t, aid, id) +} diff --git a/internal/client/get.go b/internal/client/get.go new file mode 100644 index 00000000..50ff237d --- /dev/null +++ b/internal/client/get.go @@ -0,0 +1,21 @@ +package client + +import ( + "context" + "queryorchestration/internal/database" + + "github.com/google/uuid" +) + +func (s *Service) Get(ctx context.Context, id uuid.UUID) (*Client, error) { + client, err := s.db.Queries.GetClient(ctx, database.MustToDBUUID(id)) + if err != nil { + return nil, err + } + + return &Client{ + ID: database.MustToUUID(client.ID), + Name: client.Name, + CanSync: client.Cansync, + }, nil +} diff --git a/internal/client/get_test.go b/internal/client/get_test.go new file mode 100644 index 00000000..f1567f11 --- /dev/null +++ b/internal/client/get_test.go @@ -0,0 +1,53 @@ +package client_test + +import ( + "context" + "errors" + "queryorchestration/internal/client" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "testing" + + "github.com/google/uuid" + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestGet(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 := client.New(db) + + id := uuid.New() + + pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(id)). + WillReturnRows( + pgxmock.NewRows([]string{"id", "name", "canSync"}). + AddRow(database.MustToDBUUID(id), "client_name", false), + ) + + cli, err := svc.Get(ctx, id) + assert.Nil(t, err) + assert.EqualExportedValues(t, &client.Client{ + ID: id, + Name: "client_name", + CanSync: false, + }, cli) + + dberr := "database failure" + pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(id)). + WillReturnError(errors.New(dberr)) + + _, err = svc.Get(ctx, id) + assert.EqualError(t, err, dberr) +} diff --git a/internal/client/service.go b/internal/client/service.go new file mode 100644 index 00000000..16199843 --- /dev/null +++ b/internal/client/service.go @@ -0,0 +1,63 @@ +package client + +import ( + "errors" + "fmt" + "queryorchestration/internal/database" + "regexp" + "strings" + + "github.com/google/uuid" +) + +type Client struct { + ID uuid.UUID + Name string + CanSync bool +} + +func (c *Client) getCanSyncUpdate(n *bool) bool { + if n == nil { + return c.CanSync + } + + return *n +} + +func (c *Client) getNameUpdate(n *string) (string, error) { + if n == nil { + return c.Name, nil + } + + return normalizeName(*n) +} + +func normalizeName(name string) (string, error) { + name = strings.TrimSpace(name) + + if name == "" { + return "", errors.New("name required") + } + + spacere := regexp.MustCompile(`\s+`) + name = spacere.ReplaceAllString(name, " ") + + reg := `^[a-zA-Z0-9\_\- ]+$` + + alphanumeric := regexp.MustCompile(reg) + if !alphanumeric.MatchString(name) { + return "", fmt.Errorf("name must have regex: %s", reg) + } + + return name, nil +} + +type Service struct { + db *database.Connection +} + +func New(db *database.Connection) *Service { + return &Service{ + db, + } +} diff --git a/internal/client/service_test.go b/internal/client/service_test.go new file mode 100644 index 00000000..5a9ea9ff --- /dev/null +++ b/internal/client/service_test.go @@ -0,0 +1,26 @@ +package client_test + +import ( + "queryorchestration/internal/client" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "testing" + + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestService(t *testing.T) { + 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 := client.New(db) + assert.NotNil(t, svc) +} diff --git a/internal/client/serviceprivate_test.go b/internal/client/serviceprivate_test.go new file mode 100644 index 00000000..b712dbc8 --- /dev/null +++ b/internal/client/serviceprivate_test.go @@ -0,0 +1,74 @@ +package client + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestNormalizeName(t *testing.T) { + name, err := normalizeName("name") + assert.Nil(t, err) + assert.Equal(t, "name", name) + + _, err = normalizeName("") + assert.NotNil(t, err) + + name, err = normalizeName(" name\t") + assert.Nil(t, err) + assert.Equal(t, "name", name) + + name, err = normalizeName("name second") + assert.Nil(t, err) + assert.Equal(t, "name second", name) + + name, err = normalizeName("name\t second") + assert.Nil(t, err) + assert.Equal(t, "name second", name) + + name, err = normalizeName("name_second") + assert.Nil(t, err) + assert.Equal(t, "name_second", name) + + name, err = normalizeName("name-second") + assert.Nil(t, err) + assert.Equal(t, "name-second", name) + + name, err = normalizeName("name123") + assert.Nil(t, err) + assert.Equal(t, "name123", name) +} + +func TestGetCanSyncUpdate(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)) +} + +func TestGetNameUpdate(t *testing.T) { + c := Client{ + ID: uuid.New(), + Name: "example_name", + } + + name, err := c.getNameUpdate(nil) + assert.Nil(t, err) + assert.Equal(t, "example_name", name) + + val := "update_name" + name, err = c.getNameUpdate(&val) + assert.Nil(t, err) + assert.Equal(t, "update_name", name) + + val = "###" + _, err = c.getNameUpdate(&val) + assert.Error(t, err) +} diff --git a/internal/client/update.go b/internal/client/update.go new file mode 100644 index 00000000..6f211ed5 --- /dev/null +++ b/internal/client/update.go @@ -0,0 +1,54 @@ +package client + +import ( + "context" + "errors" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + + "github.com/google/uuid" +) + +type Update struct { + ID uuid.UUID + Name *string + CanSync *bool +} + +func (s *Service) Update(ctx context.Context, entity *Update) error { + current, err := s.Get(ctx, entity.ID) + if err != nil { + return err + } + + params, err := s.getUpdateParams(current, entity) + if err != nil { + return err + } + + err = s.db.Queries.UpdateClient(ctx, params) + if err != nil { + return err + } + + 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") + } + + name, err := current.getNameUpdate(entity.Name) + if err != nil { + return nil, err + } + + canSync := current.getCanSyncUpdate(entity.CanSync) + + return &repository.UpdateClientParams{ + ID: database.MustToDBUUID(current.ID), + Name: name, + Cansync: canSync, + }, nil +} diff --git a/internal/client/update_test.go b/internal/client/update_test.go new file mode 100644 index 00000000..136070c1 --- /dev/null +++ b/internal/client/update_test.go @@ -0,0 +1,77 @@ +package client_test + +import ( + "context" + "queryorchestration/internal/client" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "testing" + + "github.com/google/uuid" + "github.com/pashagolub/pgxmock/v3" + "github.com/stretchr/testify/assert" +) + +func TestUpdate(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 := client.New(db) + + c := client.Client{ + ID: uuid.New(), + Name: "example_name", + CanSync: false, + } + update := client.Update{ + ID: c.ID, + } + + pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(c.ID)). + WillReturnRows( + 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) + + c.CanSync = false + update.CanSync = &c.CanSync + + pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(c.ID)). + WillReturnRows( + 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) + + c.Name = "updated_name" + update.Name = &c.Name + + pool.ExpectQuery("name: GetClient :one").WithArgs(database.MustToDBUUID(c.ID)). + WillReturnRows( + 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)). + WillReturnResult(pgxmock.NewResult("", 1)) + + err = svc.Update(ctx, &update) + assert.Nil(t, err) +} diff --git a/internal/database/repository/client.sql.go b/internal/database/repository/client.sql.go new file mode 100644 index 00000000..0057741e --- /dev/null +++ b/internal/database/repository/client.sql.go @@ -0,0 +1,58 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: client.sql + +package repository + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createClient = `-- name: CreateClient :one +INSERT INTO clients (name) VALUES ($1) RETURNING id +` + +// CreateClient +// +// INSERT INTO clients (name) VALUES ($1) RETURNING id +func (q *Queries) CreateClient(ctx context.Context, name string) (pgtype.UUID, error) { + row := q.db.QueryRow(ctx, createClient, name) + var id pgtype.UUID + err := row.Scan(&id) + return id, err +} + +const getClient = `-- name: GetClient :one +SELECT id, name, canSync FROM clients WHERE id = $1 +` + +// 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 + 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 +` + +type UpdateClientParams struct { + Name string `db:"name"` + Cansync bool `db:"cansync"` + ID pgtype.UUID `db:"id"` +} + +// UpdateClient +// +// UPDATE clients SET name = $1, canSync = $2 WHERE id = $3 +func (q *Queries) UpdateClient(ctx context.Context, arg *UpdateClientParams) error { + _, err := q.db.Exec(ctx, updateClient, arg.Name, arg.Cansync, arg.ID) + return err +} diff --git a/internal/database/repository/client_test.go b/internal/database/repository/client_test.go new file mode 100644 index 00000000..ffa901e8 --- /dev/null +++ b/internal/database/repository/client_test.go @@ -0,0 +1,51 @@ +package repository_test + +import ( + "context" + "os" + "path" + "queryorchestration/internal/database" + "queryorchestration/internal/database/repository" + "queryorchestration/internal/test" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClient(t *testing.T) { + ctx := context.Background() + db, cleanup := test.CreateDB(t, ctx, &test.CreateDatabaseConfig{ + Migrations: &database.MigrationConfig{ + BasePath: path.Join(os.Getenv("PWD"), "../../.."), + }}) + defer cleanup() + + queries := repository.New(db.Pool) + + id, err := queries.CreateClient(ctx, "example_client") + assert.Nil(t, err) + assert.NotEmpty(t, id) + + client, err := queries.GetClient(ctx, id) + assert.Nil(t, err) + assert.EqualExportedValues(t, &repository.Client{ + ID: id, + Name: "example_client", + Cansync: false, + }, client) + + err = queries.UpdateClient(ctx, &repository.UpdateClientParams{ + ID: id, + Name: "updated_client", + Cansync: true, + }) + assert.Nil(t, err) + + client, err = queries.GetClient(ctx, id) + assert.Nil(t, err) + assert.EqualExportedValues(t, &repository.Client{ + ID: id, + Name: "updated_client", + Cansync: true, + }, client) +} diff --git a/internal/database/repository/collector_test.go b/internal/database/repository/collector_test.go index b4edd98e..826d2f6b 100644 --- a/internal/database/repository/collector_test.go +++ b/internal/database/repository/collector_test.go @@ -49,25 +49,25 @@ func TestCollector(t *testing.T) { coll, err := queries.GetCollector(ctx, collId) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivecollector{ + assert.EqualExportedValues(t, &repository.Fullactivecollector{ ID: collId, Jobid: jobId, Mincleanversion: minCleanVersion, Mintextversion: minTextVersion, Activeversion: 1, Latestversion: 1, - }, *coll) + }, coll) coll, err = queries.GetCollectorByJobID(ctx, jobId) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivecollector{ + assert.EqualExportedValues(t, &repository.Fullactivecollector{ ID: collId, Jobid: jobId, Mincleanversion: minCleanVersion, Mintextversion: minTextVersion, Activeversion: 1, Latestversion: 1, - }, *coll) + }, coll) err = queries.AddCollectorQuery(ctx, &repository.AddCollectorQueryParams{ Collectorid: collId, @@ -79,7 +79,7 @@ func TestCollector(t *testing.T) { coll, err = queries.GetCollector(ctx, collId) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivecollector{ + assert.EqualExportedValues(t, &repository.Fullactivecollector{ ID: collId, Jobid: jobId, Mincleanversion: minCleanVersion, @@ -87,7 +87,7 @@ func TestCollector(t *testing.T) { Activeversion: 1, Latestversion: 1, Fields: []byte(fmt.Sprintf("{\"example_key\": \"%s\"}", database.MustToUUID(jsonId).String())), - }, *coll) + }, coll) qs, err := queries.ListCollectorQueries(ctx, collId) assert.Nil(t, err) diff --git a/internal/database/repository/models.go b/internal/database/repository/models.go index 21d9f875..8f73bb95 100644 --- a/internal/database/repository/models.go +++ b/internal/database/repository/models.go @@ -68,6 +68,12 @@ type Activecollectorswithrequiredid struct { Queryids interface{} `db:"queryids"` } +type Client struct { + ID pgtype.UUID `db:"id"` + Name string `db:"name"` + Cansync bool `db:"cansync"` +} + type Collector struct { ID pgtype.UUID `db:"id"` Jobid pgtype.UUID `db:"jobid"` @@ -128,13 +134,6 @@ type Queryconfig struct { Removedversion *int32 `db:"removedversion"` } -type Querydeprecation struct { - ID pgtype.UUID `db:"id"` - Queryid pgtype.UUID `db:"queryid"` - Time pgtype.Timestamp `db:"time"` - Removedat pgtype.Timestamp `db:"removedat"` -} - type Requiredquery struct { ID pgtype.UUID `db:"id"` Queryid pgtype.UUID `db:"queryid"` diff --git a/internal/database/repository/query.sql.go b/internal/database/repository/query.sql.go index 864a8344..d4d7a35d 100644 --- a/internal/database/repository/query.sql.go +++ b/internal/database/repository/query.sql.go @@ -79,18 +79,6 @@ func (q *Queries) CreateQuery(ctx context.Context, type_ Querytype) (pgtype.UUID return id, err } -const deprecateQuery = `-- name: DeprecateQuery :exec -INSERT INTO queryDeprecations (queryId) VALUES ($1) -` - -// DeprecateQuery -// -// INSERT INTO queryDeprecations (queryId) VALUES ($1) -func (q *Queries) DeprecateQuery(ctx context.Context, queryid pgtype.UUID) error { - _, err := q.db.Exec(ctx, deprecateQuery, queryid) - return err -} - const getQuery = `-- name: GetQuery :one SELECT id, type, activeversion, latestversion, config, requiredids FROM fullActiveQueries WHERE id = $1 ` @@ -136,24 +124,6 @@ func (q *Queries) GetQueryConfig(ctx context.Context, arg *GetQueryConfigParams) return &i, err } -const isQueryDeprecated = `-- name: IsQueryDeprecated :one -SELECT EXISTS ( - SELECT 1 FROM queryDeprecations where queryId = $1 and removedAt is null LIMIT 1 -) -` - -// IsQueryDeprecated -// -// SELECT EXISTS ( -// SELECT 1 FROM queryDeprecations where queryId = $1 and removedAt is null LIMIT 1 -// ) -func (q *Queries) IsQueryDeprecated(ctx context.Context, queryid pgtype.UUID) (bool, error) { - row := q.db.QueryRow(ctx, isQueryDeprecated, queryid) - var exists bool - err := row.Scan(&exists) - return exists, err -} - const listQueries = `-- name: ListQueries :many SELECT id, type, activeversion, latestversion, config, requiredids FROM fullActiveQueries ` diff --git a/internal/database/repository/query_test.go b/internal/database/repository/query_test.go index 14b7d705..2226bc70 100644 --- a/internal/database/repository/query_test.go +++ b/internal/database/repository/query_test.go @@ -35,14 +35,14 @@ func TestQueries(t *testing.T) { jsonQuery, err := queries.GetQuery(ctx, jsonQueryID) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivequery{ + assert.EqualExportedValues(t, &repository.Fullactivequery{ ID: jsonQueryID, Type: repository.QuerytypeJsonExtractor, Activeversion: 1, Latestversion: 1, Config: nil, Requiredids: []pgtype.UUID{database.MustToDBUUID(uuid.Nil)}, - }, *jsonQuery) + }, jsonQuery) err = queries.UpdateQuery(ctx, &repository.UpdateQueryParams{ Latestversion: 2, @@ -97,14 +97,14 @@ func TestQueries(t *testing.T) { jsonQuery, err = queries.GetQuery(ctx, jsonQueryID) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivequery{ + assert.EqualExportedValues(t, &repository.Fullactivequery{ ID: jsonQueryID, Type: repository.QuerytypeJsonExtractor, Activeversion: 1, Latestversion: 2, Config: jsonConfig, Requiredids: []pgtype.UUID{contextQueryID}, - }, *jsonQuery) + }, jsonQuery) err = queries.UpdateQuery(ctx, &repository.UpdateQueryParams{ Activeversion: 2, @@ -115,14 +115,14 @@ func TestQueries(t *testing.T) { jsonQuery, err = queries.GetQuery(ctx, jsonQueryID) assert.Nil(t, err) - assert.EqualExportedValues(t, repository.Fullactivequery{ + assert.EqualExportedValues(t, &repository.Fullactivequery{ ID: jsonQueryID, Type: repository.QuerytypeJsonExtractor, Activeversion: 2, Latestversion: 2, Config: nil, Requiredids: []pgtype.UUID{database.MustToDBUUID(uuid.Nil)}, - }, *jsonQuery) + }, jsonQuery) all_exist, err := queries.AllQueriesExist(ctx, []pgtype.UUID{}) assert.Nil(t, err) @@ -143,17 +143,6 @@ func TestQueries(t *testing.T) { all_exist, err = queries.AllQueriesExist(ctx, []pgtype.UUID{jsonQueryID, database.MustToDBUUID(uuid.New())}) assert.Nil(t, err) assert.False(t, all_exist) - - isDeprecated, err := queries.IsQueryDeprecated(ctx, jsonQueryID) - assert.Nil(t, err) - assert.False(t, isDeprecated) - - err = queries.DeprecateQuery(ctx, jsonQueryID) - assert.Nil(t, err) - - isDeprecated, err = queries.IsQueryDeprecated(ctx, jsonQueryID) - assert.Nil(t, err) - assert.True(t, isDeprecated) } func TestQueriesList(t *testing.T) { diff --git a/internal/query/createprivate_test.go b/internal/query/createprivate_test.go index 4fb166f6..f7744e14 100644 --- a/internal/query/createprivate_test.go +++ b/internal/query/createprivate_test.go @@ -176,10 +176,8 @@ func TestNormalizeCreate(t *testing.T) { } svc := New(db) - cfg := "{}" create := &queryprocessor.Create{ Type: queryprocessor.TypeJsonExtractor, - Config: &cfg, RequiredQueryIDs: &[]uuid.UUID{}, } diff --git a/internal/query/deprecate.go b/internal/query/deprecate.go deleted file mode 100644 index 9fe06525..00000000 --- a/internal/query/deprecate.go +++ /dev/null @@ -1,26 +0,0 @@ -package query - -import ( - "context" - "queryorchestration/internal/database" - - "github.com/google/uuid" -) - -func (s *Service) Deprecate(ctx context.Context, id uuid.UUID) error { - dbId := database.MustToDBUUID(id) - - exists, err := s.db.Queries.IsQueryDeprecated(ctx, dbId) - if err != nil { - return err - } else if exists { - return nil - } - - err = s.db.Queries.DeprecateQuery(ctx, dbId) - if err != nil { - return err - } - - return nil -} diff --git a/internal/query/deprecate_test.go b/internal/query/deprecate_test.go deleted file mode 100644 index a1da1601..00000000 --- a/internal/query/deprecate_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package query_test - -import ( - "context" - "errors" - "queryorchestration/internal/database" - "queryorchestration/internal/database/repository" - "queryorchestration/internal/query" - "testing" - - "github.com/google/uuid" - "github.com/pashagolub/pgxmock/v3" - "github.com/stretchr/testify/assert" -) - -func TestDeprecate(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 := query.New(db) - - id := uuid.New() - - pool.ExpectQuery("name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( - pgxmock.NewRows([]string{"exists"}). - AddRow(false), - ) - pool.ExpectExec("name: DeprecateQuery :exec").WithArgs(database.MustToDBUUID(id)). - WillReturnResult(pgxmock.NewResult("", 1)) - - err = svc.Deprecate(ctx, id) - assert.Nil(t, err) - - pool.ExpectQuery("name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( - pgxmock.NewRows([]string{"exists"}). - AddRow(true), - ) - - err = svc.Deprecate(ctx, id) - assert.Nil(t, err) - - pool.ExpectQuery("name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( - pgxmock.NewRows([]string{"exists"}), - ) - - err = svc.Deprecate(ctx, id) - assert.NotNil(t, err) - - pool.ExpectQuery("name: IsQueryDeprecated :one").WithArgs(database.MustToDBUUID(id)).WillReturnRows( - pgxmock.NewRows([]string{"exists"}). - AddRow(false), - ) - pool.ExpectExec("name: DeprecateQuery :exec").WithArgs(database.MustToDBUUID(id)). - WillReturnError(errors.New("unable to deprecate query")) - - err = svc.Deprecate(ctx, id) - assert.NotNil(t, err) -} diff --git a/internal/query/normalize.go b/internal/query/normalize.go index fe5e3d56..29cbf42c 100644 --- a/internal/query/normalize.go +++ b/internal/query/normalize.go @@ -45,11 +45,7 @@ func (s *Service) normalizeConfig(config Config) error { strJSON := string(prettyJSON) - if strJSON == "{}" { - config.SetConfig(nil) - } else { - config.SetConfig(&strJSON) - } + config.SetConfig(&strJSON) return nil } diff --git a/internal/query/normalize_test.go b/internal/query/normalize_test.go index 8ab3d517..259c1752 100644 --- a/internal/query/normalize_test.go +++ b/internal/query/normalize_test.go @@ -42,7 +42,7 @@ func TestNormalizeConfig(t *testing.T) { entity.Config = &cfg err = s.normalizeConfig(&entity) assert.Nil(t, err) - assert.Nil(t, entity.Config) + assert.Equal(t, "{}", *(entity.Config)) cfg = "{\"hello\":\"bye\"}" entity.Config = &cfg diff --git a/internal/query/updateprivate_test.go b/internal/query/updateprivate_test.go index 2fb1f997..397e3b12 100644 --- a/internal/query/updateprivate_test.go +++ b/internal/query/updateprivate_test.go @@ -297,7 +297,7 @@ func TestNormalizeUpdate(t *testing.T) { assert.EqualExportedValues(t, queryprocessor.Update{ ID: current.ID, ActiveVersion: &aV, - Config: nil, + Config: &cfg, RequiredQueryIDs: nil, }, *update) } diff --git a/pkg/queryService/api.gen.go b/pkg/queryService/api.gen.go index 3b83dc7e..e163dbe3 100644 --- a/pkg/queryService/api.gen.go +++ b/pkg/queryService/api.gen.go @@ -41,6 +41,21 @@ const ( JSONEXTRACTOR QueryType = "JSON_EXTRACTOR" ) +// ClientCreate defines model for ClientCreate. +type ClientCreate struct { + // Name The client name + Name string `json:"name"` +} + +// ClientUpdate defines model for ClientUpdate. +type ClientUpdate struct { + // CanSync If the client is allowing active syncs + CanSync *bool `json:"can_sync,omitempty"` + + // Name The client name + Name *string `json:"name,omitempty"` +} + // ExportDetails Payload for export trigger response. type ExportDetails struct { // JobId The job id relative to the export. @@ -95,6 +110,18 @@ type IdMessage struct { Id string `json:"id"` } +// JobClient defines model for JobClient. +type JobClient struct { + // CanSync If the client is allowing active syncs + CanSync bool `json:"can_sync"` + + // Id The client id + Id string `json:"id"` + + // Name The client name + Name string `json:"name"` +} + // JobCollector JobCollector model. type JobCollector struct { // ActiveVersion The active version of the collector. @@ -209,6 +236,12 @@ type QueryUpdate struct { RequiredQueries *[]string `json:"required_queries,omitempty"` } +// CreateClientJSONRequestBody defines body for CreateClient for application/json ContentType. +type CreateClientJSONRequestBody = ClientCreate + +// UpdateClientJSONRequestBody defines body for UpdateClient for application/json ContentType. +type UpdateClientJSONRequestBody = ClientUpdate + // TriggerExportJSONRequestBody defines body for TriggerExport for application/json ContentType. type TriggerExportJSONRequestBody = ExportTrigger @@ -297,6 +330,19 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // CreateClientWithBody request with any body + CreateClientWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateClient(ctx context.Context, body CreateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetClient request + GetClient(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateClientWithBody request with any body + UpdateClientWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateClient(ctx context.Context, id string, body UpdateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // TriggerExportWithBody request with any body TriggerExportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -321,11 +367,8 @@ type ClientInterface interface { CreateQuery(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // DeprecateQuery request - DeprecateQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetQueryById request - GetQueryById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetQuery request + GetQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // UpdateQueryWithBody request with any body UpdateQueryWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -338,6 +381,66 @@ type ClientInterface interface { TestQuery(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } +func (c *Client) CreateClientWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateClientRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateClient(ctx context.Context, body CreateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateClientRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetClient(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetClientRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateClientWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateClientRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateClient(ctx context.Context, id string, body UpdateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateClientRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) TriggerExportWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewTriggerExportRequestWithBody(c.Server, contentType, body) if err != nil { @@ -446,20 +549,8 @@ func (c *Client) CreateQuery(ctx context.Context, body CreateQueryJSONRequestBod return c.Client.Do(req) } -func (c *Client) DeprecateQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeprecateQueryRequest(c.Server, id) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetQueryById(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetQueryByIdRequest(c.Server, id) +func (c *Client) GetQuery(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetQueryRequest(c.Server, id) if err != nil { return nil, err } @@ -518,6 +609,127 @@ func (c *Client) TestQuery(ctx context.Context, id string, body TestQueryJSONReq return c.Client.Do(req) } +// NewCreateClientRequest calls the generic CreateClient builder with application/json body +func NewCreateClientRequest(server string, body CreateClientJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateClientRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateClientRequestWithBody generates requests for CreateClient with any type of body +func NewCreateClientRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/clients") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetClientRequest generates requests for GetClient +func NewGetClientRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/clients/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateClientRequest calls the generic UpdateClient builder with application/json body +func NewUpdateClientRequest(server string, id string, body UpdateClientJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateClientRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewUpdateClientRequestWithBody generates requests for UpdateClient with any type of body +func NewUpdateClientRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/clients/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewTriggerExportRequest calls the generic TriggerExport builder with application/json body func NewTriggerExportRequest(server string, body TriggerExportJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -740,42 +952,8 @@ func NewCreateQueryRequestWithBody(server string, contentType string, body io.Re return req, nil } -// NewDeprecateQueryRequest generates requests for DeprecateQuery -func NewDeprecateQueryRequest(server string, id string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/queries/%s", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetQueryByIdRequest generates requests for GetQueryById -func NewGetQueryByIdRequest(server string, id string) (*http.Request, error) { +// NewGetQueryRequest generates requests for GetQuery +func NewGetQueryRequest(server string, id string) (*http.Request, error) { var err error var pathParam0 string @@ -945,6 +1123,19 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // CreateClientWithBodyWithResponse request with any body + CreateClientWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateClientResponse, error) + + CreateClientWithResponse(ctx context.Context, body CreateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateClientResponse, error) + + // GetClientWithResponse request + GetClientWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetClientResponse, error) + + // UpdateClientWithBodyWithResponse request with any body + UpdateClientWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) + + UpdateClientWithResponse(ctx context.Context, id string, body UpdateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) + // TriggerExportWithBodyWithResponse request with any body TriggerExportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) @@ -969,11 +1160,8 @@ type ClientWithResponsesInterface interface { CreateQueryWithResponse(ctx context.Context, body CreateQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateQueryResponse, error) - // DeprecateQueryWithResponse request - DeprecateQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeprecateQueryResponse, error) - - // GetQueryByIdWithResponse request - GetQueryByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryByIdResponse, error) + // GetQueryWithResponse request + GetQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryResponse, error) // UpdateQueryWithBodyWithResponse request with any body UpdateQueryWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateQueryResponse, error) @@ -986,6 +1174,71 @@ type ClientWithResponsesInterface interface { TestQueryWithResponse(ctx context.Context, id string, body TestQueryJSONRequestBody, reqEditors ...RequestEditorFn) (*TestQueryResponse, error) } +type CreateClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *IdMessage +} + +// Status returns HTTPResponse.Status +func (r CreateClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetClientResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *JobClient +} + +// Status returns HTTPResponse.Status +func (r GetClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateClientResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r UpdateClientResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateClientResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type TriggerExportResponse struct { Body []byte HTTPResponse *http.Response @@ -1117,35 +1370,14 @@ func (r CreateQueryResponse) StatusCode() int { return 0 } -type DeprecateQueryResponse struct { - Body []byte - HTTPResponse *http.Response -} - -// Status returns HTTPResponse.Status -func (r DeprecateQueryResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeprecateQueryResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetQueryByIdResponse struct { +type GetQueryResponse struct { Body []byte HTTPResponse *http.Response JSON200 *Query } // Status returns HTTPResponse.Status -func (r GetQueryByIdResponse) Status() string { +func (r GetQueryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1153,7 +1385,7 @@ func (r GetQueryByIdResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetQueryByIdResponse) StatusCode() int { +func (r GetQueryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -1203,6 +1435,49 @@ func (r TestQueryResponse) StatusCode() int { return 0 } +// CreateClientWithBodyWithResponse request with arbitrary body returning *CreateClientResponse +func (c *ClientWithResponses) CreateClientWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateClientResponse, error) { + rsp, err := c.CreateClientWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateClientResponse(rsp) +} + +func (c *ClientWithResponses) CreateClientWithResponse(ctx context.Context, body CreateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateClientResponse, error) { + rsp, err := c.CreateClient(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateClientResponse(rsp) +} + +// GetClientWithResponse request returning *GetClientResponse +func (c *ClientWithResponses) GetClientWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetClientResponse, error) { + rsp, err := c.GetClient(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetClientResponse(rsp) +} + +// UpdateClientWithBodyWithResponse request with arbitrary body returning *UpdateClientResponse +func (c *ClientWithResponses) UpdateClientWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) { + rsp, err := c.UpdateClientWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateClientResponse(rsp) +} + +func (c *ClientWithResponses) UpdateClientWithResponse(ctx context.Context, id string, body UpdateClientJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateClientResponse, error) { + rsp, err := c.UpdateClient(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateClientResponse(rsp) +} + // TriggerExportWithBodyWithResponse request with arbitrary body returning *TriggerExportResponse func (c *ClientWithResponses) TriggerExportWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TriggerExportResponse, error) { rsp, err := c.TriggerExportWithBody(ctx, contentType, body, reqEditors...) @@ -1281,22 +1556,13 @@ func (c *ClientWithResponses) CreateQueryWithResponse(ctx context.Context, body return ParseCreateQueryResponse(rsp) } -// DeprecateQueryWithResponse request returning *DeprecateQueryResponse -func (c *ClientWithResponses) DeprecateQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeprecateQueryResponse, error) { - rsp, err := c.DeprecateQuery(ctx, id, reqEditors...) +// GetQueryWithResponse request returning *GetQueryResponse +func (c *ClientWithResponses) GetQueryWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryResponse, error) { + rsp, err := c.GetQuery(ctx, id, reqEditors...) if err != nil { return nil, err } - return ParseDeprecateQueryResponse(rsp) -} - -// GetQueryByIdWithResponse request returning *GetQueryByIdResponse -func (c *ClientWithResponses) GetQueryByIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetQueryByIdResponse, error) { - rsp, err := c.GetQueryById(ctx, id, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetQueryByIdResponse(rsp) + return ParseGetQueryResponse(rsp) } // UpdateQueryWithBodyWithResponse request with arbitrary body returning *UpdateQueryResponse @@ -1333,6 +1599,74 @@ func (c *ClientWithResponses) TestQueryWithResponse(ctx context.Context, id stri return ParseTestQueryResponse(rsp) } +// ParseCreateClientResponse parses an HTTP response from a CreateClientWithResponse call +func ParseCreateClientResponse(rsp *http.Response) (*CreateClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest IdMessage + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetClientResponse parses an HTTP response from a GetClientWithResponse call +func ParseGetClientResponse(rsp *http.Response) (*GetClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest JobClient + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateClientResponse parses an HTTP response from a UpdateClientWithResponse call +func ParseUpdateClientResponse(rsp *http.Response) (*UpdateClientResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateClientResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseTriggerExportResponse parses an HTTP response from a TriggerExportWithResponse call func ParseTriggerExportResponse(rsp *http.Response) (*TriggerExportResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1479,31 +1813,15 @@ func ParseCreateQueryResponse(rsp *http.Response) (*CreateQueryResponse, error) return response, nil } -// ParseDeprecateQueryResponse parses an HTTP response from a DeprecateQueryWithResponse call -func ParseDeprecateQueryResponse(rsp *http.Response) (*DeprecateQueryResponse, error) { +// ParseGetQueryResponse parses an HTTP response from a GetQueryWithResponse call +func ParseGetQueryResponse(rsp *http.Response) (*GetQueryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeprecateQueryResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - return response, nil -} - -// ParseGetQueryByIdResponse parses an HTTP response from a GetQueryByIdWithResponse call -func ParseGetQueryByIdResponse(rsp *http.Response) (*GetQueryByIdResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetQueryByIdResponse{ + response := &GetQueryResponse{ Body: bodyBytes, HTTPResponse: rsp, } diff --git a/scripts/database.yml b/scripts/database.yml index 230231a3..42b2e3bb 100644 --- a/scripts/database.yml +++ b/scripts/database.yml @@ -9,7 +9,7 @@ vars: tasks: generate: cmds: - - task db:mig:run + - task db:mig - sqlc generate --file sqlc.yml lint: cmds: @@ -22,7 +22,7 @@ tasks: read -p "Migration Name: " name test -z "$name" && echo "Error: Name is required." && exit 1 migrate create -ext sql -dir {{.MIGRATIONS}} $name - mig:run: + mig: cmds: - task compose:up:bg - sleep 2 diff --git a/serviceAPIs/queryService.yaml b/serviceAPIs/queryService.yaml index e51f05fa..74351adb 100644 --- a/serviceAPIs/queryService.yaml +++ b/serviceAPIs/queryService.yaml @@ -8,14 +8,91 @@ servers: - url: https://api.example.com/v1 description: Production server tags: - - name: QueryService - description: Operations related to queries + - name: ClientService + description: Operations related to clients - name: JobCollectorService description: Operations related to job collectors + - name: QueryService + description: Operations related to queries - name: ExportService description: Operations related to exports paths: + /clients: + post: + operationId: createClient + tags: + - ClientService + summary: Create a new client + description: Creates a new client with the provided details. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ClientCreate' + responses: + '201': + description: Client created successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/IdMessage' + '400': + description: Invalid request body. + + /clients/{id}: + get: + operationId: getClient + tags: + - ClientService + summary: Get a client by ID + description: Retrieves a specific client by its ID. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the client to retrieve. + responses: + '200': + description: Client details. + content: + application/json: + schema: + $ref: '#/components/schemas/JobClient' + '400': + description: Invalid request parameters. + '404': + description: Client not found. + patch: + operationId: updateClient + tags: + - ClientService + summary: Update a client + description: Updates an existing client with new details. + parameters: + - in: path + name: id + required: true + schema: + type: string + description: The ID of the client to update. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ClientUpdate' + responses: + '200': + description: Client updated successfully. + '400': + description: Invalid request body. + '404': + description: Client not found + /queries: get: operationId: listQueries @@ -58,7 +135,7 @@ paths: /queries/{id}: get: - operationId: getQueryById + operationId: getQuery tags: - QueryService summary: Get a query by ID @@ -105,24 +182,6 @@ paths: description: Query updated successfully. '400': description: Invalid request body. - delete: - operationId: deprecateQuery - tags: - - QueryService - summary: Deprecate a query - description: Deprecates a specific query by its ID. - parameters: - - in: path - name: id - required: true - schema: - type: string - description: The ID of the query to deprecate. - responses: - '200': - description: Query deprecated successfully. - '400': - description: Invalid request body. '404': description: Query not found. @@ -363,6 +422,42 @@ components: required: - value + JobClient: + type: object + properties: + id: + type: string + description: The client id + name: + type: string + description: The client name + can_sync: + type: boolean + description: If the client is allowing active syncs + required: + - id + - name + - can_sync + + ClientCreate: + type: object + properties: + name: + type: string + description: The client name + required: + - name + + ClientUpdate: + type: object + properties: + name: + type: string + description: The client name + can_sync: + type: boolean + description: If the client is allowing active syncs + IdMessage: type: object properties: diff --git a/test/queryService/client_test.go b/test/queryService/client_test.go new file mode 100644 index 00000000..d4577b01 --- /dev/null +++ b/test/queryService/client_test.go @@ -0,0 +1,50 @@ +package integration_test + +import ( + "context" + "queryorchestration/internal/test" + queryservice "queryorchestration/pkg/queryService" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestClient(t *testing.T) { + ctx := context.Background() + + address, cleanup := test.CreateAPIWithDependencies(t, ctx, "queryService") + defer cleanup() + + client, err := queryservice.NewClientWithResponses(address) + assert.Nil(t, err) + + idRes, err := client.CreateClientWithResponse(ctx, queryservice.ClientCreate{ + Name: "example_name", + }) + assert.Nil(t, err) + assert.NotNil(t, idRes) + assert.NotNil(t, idRes.JSON201) + assert.NotNil(t, idRes.JSON201.Id) + id := idRes.JSON201.Id + + clientRes, err := client.GetClientWithResponse(ctx, id) + assert.Nil(t, err) + assert.Equal(t, id, clientRes.JSON200.Id) + assert.Equal(t, "example_name", clientRes.JSON200.Name) + assert.False(t, clientRes.JSON200.CanSync) + + updateName := "update_name" + updateCanSync := true + updateRes, err := client.UpdateClientWithResponse(ctx, id, queryservice.ClientUpdate{ + Name: &updateName, + CanSync: &updateCanSync, + }) + assert.Nil(t, err) + assert.NotNil(t, updateRes) + + clientRes, err = client.GetClientWithResponse(ctx, id) + assert.Nil(t, err) + assert.Equal(t, id, clientRes.JSON200.Id) + assert.Equal(t, updateName, clientRes.JSON200.Name) + assert.True(t, clientRes.JSON200.CanSync) +} diff --git a/test/queryService/queryservice_test.go b/test/queryService/queryservice_test.go index d4a27e40..28d44a4d 100644 --- a/test/queryService/queryservice_test.go +++ b/test/queryService/queryservice_test.go @@ -33,7 +33,7 @@ func TestQueryService(t *testing.T) { assert.Nil(t, err) jsonID := idRes.JSON201.Id - queryRes, err := client.GetQueryByIdWithResponse(ctx, jsonID) + queryRes, err := client.GetQueryWithResponse(ctx, jsonID) assert.Nil(t, err) assert.Equal(t, jsonID, queryRes.JSON200.Id) assert.Equal(t, queryservice.JSONEXTRACTOR, queryRes.JSON200.Type) @@ -46,10 +46,8 @@ func TestQueryService(t *testing.T) { assert.Nil(t, err) assert.Len(t, queriesRes.JSON200.Queries, 2) - cfg := "{}" aV := int32(2) res, err := client.UpdateQueryWithResponse(ctx, jsonID, queryservice.QueryUpdate{ - Config: &cfg, ActiveVersion: &aV, RequiredQueries: &[]string{ contextID, @@ -58,7 +56,7 @@ func TestQueryService(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, res) - queryRes, err = client.GetQueryByIdWithResponse(ctx, jsonID) + queryRes, err = client.GetQueryWithResponse(ctx, jsonID) assert.Nil(t, err) assert.Equal(t, jsonID, queryRes.JSON200.Id) assert.Equal(t, queryservice.JSONEXTRACTOR, queryRes.JSON200.Type)