diff --git a/Makefile b/Makefile index 9b44683a..1765fead 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ ROOT=$(realpath $(dir $(lastword $(MAKEFILE_LIST)))) +OS := $(shell uname -s) + lint: which golangci-lint || (go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.0) golangci-lint run --config=$(ROOT)/.golangci.yml $(ROOT)/... @@ -15,6 +17,9 @@ docker-test-up: docker-test-down: docker compose -f $(ROOT)/deployment/test/docker-compose.yml down +docker-local-up: + sh -c "$(ROOT)/deployment/local/docker-compose.bash up -d" + logs: docker compose -f $(ROOT)/deployment/test/docker-compose.yml logs diff --git a/cmd/manager/main.go b/cmd/manager/main.go index da98d231..2856160f 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -7,17 +7,22 @@ import ( "github.com/ormushq/ormus/config" "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager" "github.com/ormushq/ormus/manager/delivery/httpserver" "github.com/ormushq/ormus/manager/delivery/httpserver/projecthandler" + "github.com/ormushq/ormus/manager/delivery/httpserver/sourcehandler" "github.com/ormushq/ormus/manager/delivery/httpserver/userhandler" "github.com/ormushq/ormus/manager/managerparam" "github.com/ormushq/ormus/manager/repository/scyllarepo" "github.com/ormushq/ormus/manager/repository/scyllarepo/scyllaproject" + "github.com/ormushq/ormus/manager/repository/scyllarepo/scyllasource" "github.com/ormushq/ormus/manager/repository/scyllarepo/scyllauser" "github.com/ormushq/ormus/manager/service/authservice" "github.com/ormushq/ormus/manager/service/projectservice" + "github.com/ormushq/ormus/manager/service/sourceservice" "github.com/ormushq/ormus/manager/service/userservice" "github.com/ormushq/ormus/manager/validator/projectvalidator" + "github.com/ormushq/ormus/manager/validator/sourcevalidator" "github.com/ormushq/ormus/manager/validator/uservalidator" "github.com/ormushq/ormus/manager/workers" "github.com/ormushq/ormus/pkg/channel" @@ -42,11 +47,19 @@ func main() { logger.L().Debug("start manger") cfg := config.C().Manager done := make(chan bool) - wg := sync.WaitGroup{} + wg := &sync.WaitGroup{} logger.L().Debug(fmt.Sprintf("%+v", cfg)) logger.L().Debug(fmt.Sprintf("%+v", cfg.ScyllaDBConfig)) - internalBroker := simple.New(done, &wg) + svcs := setupServices(wg, done, cfg) + + server := httpserver.New(cfg, svcs) + + server.Server() +} + +func setupServices(wg *sync.WaitGroup, done <-chan bool, cfg manager.Config) httpserver.SetupServices { + internalBroker := simple.New(done, wg) err := internalBroker.NewChannel(managerparam.CreateDefaultProject, channel.BothMode, cfg.InternalBrokerConfig.ChannelSize, cfg.InternalBrokerConfig.NumberInstant, cfg.InternalBrokerConfig.MaxRetryPolicy) if err != nil { @@ -64,17 +77,21 @@ func main() { projectSvc := projectservice.New(projectRepo, internalBroker, projectValidator) projectHandler := projecthandler.New(authSvc, projectSvc) + sourceRepo := scyllasource.New(scylla) + sourceValidator := sourcevalidator.New(sourceRepo) + sourceSvc := sourceservice.New(sourceRepo, sourceValidator, projectSvc) + sourceHandler := sourcehandler.New(authSvc, sourceSvc) + userRepo := scyllauser.New(scylla) userValidator := uservalidator.New(userRepo) userSvc := userservice.New(authSvc, userRepo, internalBroker, userValidator) userHand := userhandler.New(userSvc, projectSvc) - workers.New(projectSvc, internalBroker).Run(done, &wg) + workers.New(projectSvc, internalBroker).Run(done, wg) - server := httpserver.New(cfg, httpserver.SetupServicesResponse{ + return httpserver.SetupServices{ UserHandler: userHand, ProjectHandler: projectHandler, - }) - - server.Server() + SourceHandler: sourceHandler, + } } diff --git a/contract/protobuf/source/source.proto b/contract/protobuf/source/source.proto index 297962ee..bc4fd3aa 100644 --- a/contract/protobuf/source/source.proto +++ b/contract/protobuf/source/source.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package source; -option go_package = "github.com/ormushq/ormus/contract/go/manager"; +option go_package = "github.com/ormushq/ormus/contract/go/source"; import "google/protobuf/timestamp.proto"; diff --git a/doc/swagger/manager_docs.go b/doc/swagger/manager_docs.go index 8b7e5f62..b9f4afb0 100644 --- a/doc/swagger/manager_docs.go +++ b/doc/swagger/manager_docs.go @@ -272,6 +272,238 @@ const docTemplatemanager = `{ } } }, + "/sources": { + "get": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "List sources", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "List sources", + "parameters": [ + { + "type": "string", + "description": "Last token fetched", + "name": "last_token_id", + "in": "query" + }, + { + "type": "integer", + "description": "Per page count", + "name": "per_page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/sourceparam.ListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + }, + "post": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Create source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Create source", + "parameters": [ + { + "description": "Create source request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/sourceparam.CreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/sourceparam.CreateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/sources/{source_id}": { + "post": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Update source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Update source", + "parameters": [ + { + "type": "string", + "description": "Source identifier", + "name": "source_id", + "in": "path", + "required": true + }, + { + "description": "Update source request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/sourceparam.UpdateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/sourceparam.UpdateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + }, + "delete": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Delete source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Delete source", + "parameters": [ + { + "type": "string", + "description": "Source identifier", + "name": "source_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/sourceparam.DeleteResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, "/users/login": { "post": { "security": [ @@ -388,8 +620,11 @@ const docTemplatemanager = `{ "created_at": { "type": "string" }, + "deleted": { + "type": "boolean" + }, "deleted_at": { - "$ref": "#/definitions/sql.NullTime" + "type": "string" }, "description": { "type": "string" @@ -406,11 +641,83 @@ const docTemplatemanager = `{ "updated_at": { "type": "string" }, - "user": { + "user_id": { "type": "string" } } }, + "entity.Source": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted": { + "type": "boolean" + }, + "deleted_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/entity.SourceMetadata" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/entity.Status" + }, + "token_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "write_key": { + "type": "string" + } + } + }, + "entity.SourceMetadata": { + "type": "object", + "properties": { + "category": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + }, + "entity.Status": { + "type": "string", + "enum": [ + "active", + "not active" + ], + "x-enum-varnames": [ + "SourceStatusActive", + "SourceStatusNotActive" + ] + }, "httputil.HTTPError": { "type": "object", "properties": { @@ -541,7 +848,7 @@ const docTemplatemanager = `{ "type": "boolean" }, "last_token": { - "type": "integer" + "type": "string" }, "per_page": { "type": "integer" @@ -575,15 +882,80 @@ const docTemplatemanager = `{ } } }, - "sql.NullTime": { + "sourceparam.CreateRequest": { "type": "object", "properties": { - "time": { - "type": "string" + "description": { + "type": "string", + "example": "test description" }, - "valid": { - "description": "Valid is true if Time is not NULL", + "name": { + "type": "string", + "example": "test name" + }, + "project_id": { + "type": "string" + } + } + }, + "sourceparam.CreateResponse": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/entity.Source" + } + } + }, + "sourceparam.DeleteResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "sourceparam.ListResponse": { + "type": "object", + "properties": { + "has_more": { "type": "boolean" + }, + "last_token": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.Source" + } + } + } + }, + "sourceparam.UpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "updated description" + }, + "name": { + "type": "string", + "example": "updated name" + }, + "status": { + "type": "string", + "example": "active" + } + } + }, + "sourceparam.UpdateResponse": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/entity.Source" } } } diff --git a/doc/swagger/manager_swagger.json b/doc/swagger/manager_swagger.json index dc00241a..d6628ebe 100644 --- a/doc/swagger/manager_swagger.json +++ b/doc/swagger/manager_swagger.json @@ -261,6 +261,238 @@ } } }, + "/sources": { + "get": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "List sources", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "List sources", + "parameters": [ + { + "type": "string", + "description": "Last token fetched", + "name": "last_token_id", + "in": "query" + }, + { + "type": "integer", + "description": "Per page count", + "name": "per_page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/sourceparam.ListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + }, + "post": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Create source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Create source", + "parameters": [ + { + "description": "Create source request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/sourceparam.CreateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/sourceparam.CreateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, + "/sources/{source_id}": { + "post": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Update source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Update source", + "parameters": [ + { + "type": "string", + "description": "Source identifier", + "name": "source_id", + "in": "path", + "required": true + }, + { + "description": "Update source request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/sourceparam.UpdateRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/sourceparam.UpdateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + }, + "delete": { + "security": [ + { + "JWTToken": [] + } + ], + "description": "Delete source", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Source" + ], + "summary": "Delete source", + "parameters": [ + { + "type": "string", + "description": "Source identifier", + "name": "source_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/sourceparam.DeleteResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httputil.HTTPError" + } + } + } + } + }, "/users/login": { "post": { "security": [ @@ -377,8 +609,11 @@ "created_at": { "type": "string" }, + "deleted": { + "type": "boolean" + }, "deleted_at": { - "$ref": "#/definitions/sql.NullTime" + "type": "string" }, "description": { "type": "string" @@ -395,11 +630,83 @@ "updated_at": { "type": "string" }, - "user": { + "user_id": { "type": "string" } } }, + "entity.Source": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted": { + "type": "boolean" + }, + "deleted_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "metadata": { + "$ref": "#/definitions/entity.SourceMetadata" + }, + "name": { + "type": "string" + }, + "owner_id": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/entity.Status" + }, + "token_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "write_key": { + "type": "string" + } + } + }, + "entity.SourceMetadata": { + "type": "object", + "properties": { + "category": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + } + } + }, + "entity.Status": { + "type": "string", + "enum": [ + "active", + "not active" + ], + "x-enum-varnames": [ + "SourceStatusActive", + "SourceStatusNotActive" + ] + }, "httputil.HTTPError": { "type": "object", "properties": { @@ -530,7 +837,7 @@ "type": "boolean" }, "last_token": { - "type": "integer" + "type": "string" }, "per_page": { "type": "integer" @@ -564,15 +871,80 @@ } } }, - "sql.NullTime": { + "sourceparam.CreateRequest": { "type": "object", "properties": { - "time": { - "type": "string" + "description": { + "type": "string", + "example": "test description" }, - "valid": { - "description": "Valid is true if Time is not NULL", + "name": { + "type": "string", + "example": "test name" + }, + "project_id": { + "type": "string" + } + } + }, + "sourceparam.CreateResponse": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/entity.Source" + } + } + }, + "sourceparam.DeleteResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "sourceparam.ListResponse": { + "type": "object", + "properties": { + "has_more": { "type": "boolean" + }, + "last_token": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/definitions/entity.Source" + } + } + } + }, + "sourceparam.UpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "updated description" + }, + "name": { + "type": "string", + "example": "updated name" + }, + "status": { + "type": "string", + "example": "active" + } + } + }, + "sourceparam.UpdateResponse": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/entity.Source" } } } diff --git a/doc/swagger/manager_swagger.yaml b/doc/swagger/manager_swagger.yaml index a034cdfc..0d77ade0 100644 --- a/doc/swagger/manager_swagger.yaml +++ b/doc/swagger/manager_swagger.yaml @@ -3,8 +3,10 @@ definitions: properties: created_at: type: string + deleted: + type: boolean deleted_at: - $ref: '#/definitions/sql.NullTime' + type: string description: type: string id: @@ -15,9 +17,57 @@ definitions: type: string updated_at: type: string - user: + user_id: + type: string + type: object + entity.Source: + properties: + created_at: + type: string + deleted: + type: boolean + deleted_at: + type: string + description: + type: string + id: + type: string + metadata: + $ref: '#/definitions/entity.SourceMetadata' + name: + type: string + owner_id: + type: string + project_id: + type: string + status: + $ref: '#/definitions/entity.Status' + token_id: + type: string + updated_at: + type: string + write_key: type: string type: object + entity.SourceMetadata: + properties: + category: + type: string + id: + type: string + name: + type: string + slug: + type: string + type: object + entity.Status: + enum: + - active + - not active + type: string + x-enum-varnames: + - SourceStatusActive + - SourceStatusNotActive httputil.HTTPError: properties: message: @@ -105,7 +155,7 @@ definitions: has_more: type: boolean last_token: - type: integer + type: string per_page: type: integer projects: @@ -127,13 +177,56 @@ definitions: project: $ref: '#/definitions/entity.Project' type: object - sql.NullTime: + sourceparam.CreateRequest: properties: - time: + description: + example: test description type: string - valid: - description: Valid is true if Time is not NULL + name: + example: test name + type: string + project_id: + type: string + type: object + sourceparam.CreateResponse: + properties: + source: + $ref: '#/definitions/entity.Source' + type: object + sourceparam.DeleteResponse: + properties: + message: + type: string + type: object + sourceparam.ListResponse: + properties: + has_more: type: boolean + last_token: + type: integer + per_page: + type: integer + sources: + items: + $ref: '#/definitions/entity.Source' + type: array + type: object + sourceparam.UpdateRequest: + properties: + description: + example: updated description + type: string + name: + example: updated name + type: string + status: + example: active + type: string + type: object + sourceparam.UpdateResponse: + properties: + source: + $ref: '#/definitions/entity.Source' type: object info: contact: @@ -304,6 +397,154 @@ paths: summary: Update project tags: - Project + /sources: + get: + consumes: + - application/json + description: List sources + parameters: + - description: Last token fetched + in: query + name: last_token_id + type: string + - description: Per page count + in: query + name: per_page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/sourceparam.ListResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httputil.HTTPError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httputil.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httputil.HTTPError' + security: + - JWTToken: [] + summary: List sources + tags: + - Source + post: + consumes: + - application/json + description: Create source + parameters: + - description: Create source request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/sourceparam.CreateRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/sourceparam.CreateResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httputil.HTTPError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httputil.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httputil.HTTPError' + security: + - JWTToken: [] + summary: Create source + tags: + - Source + /sources/{source_id}: + delete: + consumes: + - application/json + description: Delete source + parameters: + - description: Source identifier + in: path + name: source_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/sourceparam.DeleteResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httputil.HTTPError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httputil.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httputil.HTTPError' + security: + - JWTToken: [] + summary: Delete source + tags: + - Source + post: + consumes: + - application/json + description: Update source + parameters: + - description: Source identifier + in: path + name: source_id + required: true + type: string + - description: Update source request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/sourceparam.UpdateRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/sourceparam.UpdateResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httputil.HTTPError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httputil.HTTPError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httputil.HTTPError' + security: + - JWTToken: [] + summary: Update source + tags: + - Source /users/login: post: consumes: diff --git a/manager/delivery/httpserver/server.go b/manager/delivery/httpserver/server.go index 37414da8..08f24a6c 100644 --- a/manager/delivery/httpserver/server.go +++ b/manager/delivery/httpserver/server.go @@ -14,9 +14,10 @@ import ( "github.com/ormushq/ormus/manager/delivery/httpserver/userhandler" ) -type SetupServicesResponse struct { +type SetupServices struct { UserHandler userhandler.Handler ProjectHandler projecthandler.Handler + SourceHandler sourcehandler.Handler } type Server struct { @@ -27,11 +28,12 @@ type Server struct { Router *echo.Echo } -func New(cfg manager.Config, setupSvc SetupServicesResponse) *Server { +func New(cfg manager.Config, setupSvc SetupServices) *Server { return &Server{ config: cfg, userHandler: setupSvc.UserHandler, projectHandler: setupSvc.ProjectHandler, + sourceHandler: setupSvc.SourceHandler, Router: echo.New(), } } @@ -84,7 +86,7 @@ func (s *Server) Server() { })) s.userHandler.SetUserRoute(e) - s.sourceHandler.SetSourceRoute(e) + s.sourceHandler.SetRoutes(e) s.projectHandler.SetRoutes(e) e.GET("/health-check", s.healthCheck) diff --git a/manager/delivery/httpserver/sourcehandler/create.go b/manager/delivery/httpserver/sourcehandler/create.go index 46d6a898..0cc9daa7 100644 --- a/manager/delivery/httpserver/sourcehandler/create.go +++ b/manager/delivery/httpserver/sourcehandler/create.go @@ -1,39 +1,64 @@ package sourcehandler import ( + "errors" "net/http" "github.com/labstack/echo/v4" - "github.com/ormushq/ormus/manager/managerparam" + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/service/authservice" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/httpmsg" + "github.com/ormushq/ormus/pkg/httputil" ) -// ? Handler or *Handler. -func (h Handler) CreateSource(ctx echo.Context) error { +// Create godoc +// +// @Summary Create source +// @Description Create source +// @Tags Source +// @Accept json +// @Produce json +// @Param request body sourceparam.CreateRequest true "Create source request body" +// @Success 201 {object} sourceparam.CreateResponse +// @Failure 400 {object} httputil.HTTPError +// @Failure 401 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Security JWTToken +// @Router /sources [post] +func (h Handler) Create(ctx echo.Context) error { // get user id from context - u := ctx.Get("userID") - userID, ok := u.(string) + claim, ok := ctx.Get(h.authSvc.GetConfig().ContextKey).(*authservice.Claims) if !ok { - return echo.NewHTTPError(http.StatusInternalServerError, EchoErrorMessage("can not get userID")) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": "Invalid auth token", + }) } - // TODO get project id if get from param dont forget add to route ? - - // binding addsource request form - AddSourceReq := new(managerparam.AddSourceRequest) - if err := ctx.Bind(AddSourceReq); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + var req sourceparam.CreateRequest + if err := ctx.Bind(&req); err != nil { + return httputil.NewError(ctx, http.StatusBadRequest, errmsg.ErrBadRequest) } - // validate form also check existen - if err := h.validateSvc.ValidateCreateSourceForm(*AddSourceReq); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + req.UserID = claim.UserID + + resp, err := h.sourceSvc.CreateSource(req) + logger.LogError(err) + var vErr *validator.Error + if errors.As(err, &vErr) { + msg, code := httpmsg.Error(vErr.Err) + + return ctx.JSON(code, echo.Map{ + "message": msg, + "errors": vErr.Fields, + }) } - // call save method in service - sourceResp, err := h.sourceSvc.CreateSource(AddSourceReq, userID) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, EchoErrorMessage(err.Error())) + return httputil.NewErrorWithError(ctx, err) } - return ctx.JSON(http.StatusCreated, sourceResp) + return ctx.JSON(http.StatusCreated, resp) } diff --git a/manager/delivery/httpserver/sourcehandler/delete.go b/manager/delivery/httpserver/sourcehandler/delete.go index c0e30102..2a513e3f 100644 --- a/manager/delivery/httpserver/sourcehandler/delete.go +++ b/manager/delivery/httpserver/sourcehandler/delete.go @@ -1,30 +1,64 @@ package sourcehandler import ( + "errors" "net/http" "github.com/labstack/echo/v4" + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/service/authservice" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/httpmsg" + "github.com/ormushq/ormus/pkg/httputil" ) -func (h Handler) DeleteSource(ctx echo.Context) error { - // get user id from context - u := ctx.Get("userID") - userID, ok := u.(string) +// Delete godoc +// +// @Summary Delete source +// @Description Delete source +// @Tags Source +// @Accept json +// @Produce json +// @Param source_id path string true "Source identifier" +// @Success 200 {object} sourceparam.DeleteResponse +// @Failure 400 {object} httputil.HTTPError +// @Failure 401 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Security JWTToken +// @Router /sources/{source_id} [delete] +func (h Handler) Delete(ctx echo.Context) error { + claim, ok := ctx.Get(h.authSvc.GetConfig().ContextKey).(*authservice.Claims) if !ok { - return echo.NewHTTPError(http.StatusInternalServerError, EchoErrorMessage("can not get userID")) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": "Invalid auth token", + }) } - // get id from param - sourceID := ctx.Param("sourceID") - // validate form also check existen - if err := h.validateSvc.ValidateIDToDelete(sourceID); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + var req sourceparam.DeleteRequest + if err := ctx.Bind(&req); err != nil { + return httputil.NewError(ctx, http.StatusBadRequest, errmsg.ErrBadRequest) } - // call delete service method - if err := h.sourceSvc.DeleteSource(sourceID, userID); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + req.UserID = claim.UserID + + resp, err := h.sourceSvc.Delete(req) + + logger.LogError(err) + var vErr *validator.Error + if errors.As(err, &vErr) { + msg, code := httpmsg.Error(vErr.Err) + + return ctx.JSON(code, echo.Map{ + "message": msg, + "errors": vErr.Fields, + }) + } + + if err != nil { + return httputil.NewErrorWithError(ctx, err) } - return ctx.JSON(http.StatusNoContent, nil) + return ctx.JSON(http.StatusOK, resp) } diff --git a/manager/delivery/httpserver/sourcehandler/handler.go b/manager/delivery/httpserver/sourcehandler/handler.go index 74fb7a2a..84c2c6c8 100644 --- a/manager/delivery/httpserver/sourcehandler/handler.go +++ b/manager/delivery/httpserver/sourcehandler/handler.go @@ -4,27 +4,17 @@ import ( "github.com/labstack/echo/v4" "github.com/ormushq/ormus/manager/service/authservice" "github.com/ormushq/ormus/manager/service/sourceservice" - "github.com/ormushq/ormus/manager/service/userservice" - "github.com/ormushq/ormus/manager/validator/sourcevalidator" ) type Handler struct { - sourceSvc sourceservice.Service - userSvc userservice.Service - validateSvc sourcevalidator.Validator - authSvc authservice.Service + sourceSvc sourceservice.Service + authSvc authservice.Service } -func New(sourceSvc sourceservice.Service, - userSvc userservice.Service, - validateSvc sourcevalidator.Validator, - authSvc authservice.Service, -) *Handler { - return &Handler{ - sourceSvc: sourceSvc, - userSvc: userSvc, - validateSvc: validateSvc, - authSvc: authSvc, +func New(authSvc authservice.Service, sourceSvc sourceservice.Service) Handler { + return Handler{ + sourceSvc: sourceSvc, + authSvc: authSvc, } } diff --git a/manager/delivery/httpserver/sourcehandler/list.go b/manager/delivery/httpserver/sourcehandler/list.go new file mode 100644 index 00000000..89614081 --- /dev/null +++ b/manager/delivery/httpserver/sourcehandler/list.go @@ -0,0 +1,75 @@ +package sourcehandler + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/service/authservice" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/httpmsg" + "github.com/ormushq/ormus/pkg/httputil" +) + +// List godoc +// +// @Summary List sources +// @Description List sources +// @Tags Source +// @Accept json +// @Produce json +// @Param last_token_id query string false "Last token fetched" +// @Param per_page query int false "Per page count" +// @Success 200 {object} sourceparam.ListResponse +// @Failure 400 {object} httputil.HTTPError +// @Failure 401 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Security JWTToken +// @Router /sources [get] +func (h Handler) List(ctx echo.Context) error { + // get user id from context + claim, ok := ctx.Get(h.authSvc.GetConfig().ContextKey).(*authservice.Claims) + if !ok { + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": "Invalid auth token", + }) + } + + var req sourceparam.ListRequest + if err := ctx.Bind(&req); err != nil { + return httputil.NewError(ctx, http.StatusBadRequest, errmsg.ErrBadRequest) + } + + req.UserID = claim.UserID + + if req.PerPage == 0 { + req.PerPage = 10 + } + var lastTokenID int64 = -9223372036854775808 + if req.LastTokenID == 0 { + req.LastTokenID = lastTokenID + } + + // call save method in service + resp, err := h.sourceSvc.List(req) + + logger.LogError(err) + var vErr *validator.Error + if errors.As(err, &vErr) { + msg, code := httpmsg.Error(vErr.Err) + + return ctx.JSON(code, echo.Map{ + "message": msg, + "errors": vErr.Fields, + }) + } + + if err != nil { + return httputil.NewErrorWithError(ctx, err) + } + + return ctx.JSON(http.StatusOK, resp) +} diff --git a/manager/delivery/httpserver/sourcehandler/route.go b/manager/delivery/httpserver/sourcehandler/route.go index 54f41a80..dcdcb565 100644 --- a/manager/delivery/httpserver/sourcehandler/route.go +++ b/manager/delivery/httpserver/sourcehandler/route.go @@ -5,9 +5,10 @@ import ( "github.com/ormushq/ormus/manager/delivery/httpserver/middleware" ) -func (h Handler) SetSourceRoute(e *echo.Echo) { - sourceGroups := e.Group("/sources") - sourceGroups.POST("/", h.CreateSource, middleware.GetTokenFromCookie(h.authSvc)) - sourceGroups.POST("/:sourceID", h.UpdateSource, middleware.GetTokenFromCookie(h.authSvc)) - sourceGroups.DELETE("/:sourceID", h.DeleteSource) +func (h Handler) SetRoutes(e *echo.Echo) { + sourceGroups := e.Group("/sources", middleware.GetTokenFromHeader(h.authSvc)) + sourceGroups.GET("", h.List) + sourceGroups.POST("", h.Create) + sourceGroups.POST("/:sourceID", h.Update) + sourceGroups.DELETE("/:sourceID", h.Delete) } diff --git a/manager/delivery/httpserver/sourcehandler/update.go b/manager/delivery/httpserver/sourcehandler/update.go index e1573d0e..e8762337 100644 --- a/manager/delivery/httpserver/sourcehandler/update.go +++ b/manager/delivery/httpserver/sourcehandler/update.go @@ -1,40 +1,65 @@ package sourcehandler import ( + "errors" "net/http" "github.com/labstack/echo/v4" - "github.com/ormushq/ormus/manager/managerparam" + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/service/authservice" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/httpmsg" + "github.com/ormushq/ormus/pkg/httputil" ) -func (h Handler) UpdateSource(ctx echo.Context) error { +// Update godoc +// +// @Summary Update source +// @Description Update source +// @Tags Source +// @Accept json +// @Produce json +// @Param source_id path string true "Source identifier" +// @Param request body sourceparam.UpdateRequest true "Update source request body" +// @Success 201 {object} sourceparam.UpdateResponse +// @Failure 400 {object} httputil.HTTPError +// @Failure 401 {object} httputil.HTTPError +// @Failure 500 {object} httputil.HTTPError +// @Security JWTToken +// @Router /sources/{source_id} [post] +func (h Handler) Update(ctx echo.Context) error { // get user id from context - u := ctx.Get("userID") - userID, ok := u.(string) + claim, ok := ctx.Get(h.authSvc.GetConfig().ContextKey).(*authservice.Claims) if !ok { - return echo.NewHTTPError(http.StatusInternalServerError, EchoErrorMessage("can not get userID")) + return ctx.JSON(http.StatusBadRequest, echo.Map{ + "message": "Invalid auth token", + }) } - // TODO get project id ? + var req sourceparam.UpdateRequest + if err := ctx.Bind(&req); err != nil { + return httputil.NewError(ctx, http.StatusBadRequest, errmsg.ErrBadRequest) + } - // get source id - sourceID := ctx.Param("sourceID") + req.UserID = claim.UserID - // binding addsource request form - updateSourceReq := new(managerparam.UpdateSourceRequest) - if err := ctx.Bind(updateSourceReq); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) - } + resp, err := h.sourceSvc.Update(req) + logger.LogError(err) + var vErr *validator.Error + if errors.As(err, &vErr) { + msg, code := httpmsg.Error(vErr.Err) - if err := h.validateSvc.ValidateUpdateSourceForm(*updateSourceReq); err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + return ctx.JSON(code, echo.Map{ + "message": msg, + "errors": vErr.Fields, + }) } - // call save method in service - sourceResp, err := h.sourceSvc.UpdateSource(userID, sourceID, updateSourceReq) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, EchoErrorMessage(err.Error())) + return httputil.NewErrorWithError(ctx, err) } - return ctx.JSON(http.StatusCreated, sourceResp) + return ctx.JSON(http.StatusOK, resp) } diff --git a/manager/entity/source.go b/manager/entity/source.go index 46c8cdde..207b58c5 100644 --- a/manager/entity/source.go +++ b/manager/entity/source.go @@ -11,28 +11,30 @@ type SourceCategory string type Status string const ( - StatusActive Status = "active" - StatusNotActive Status = "not active" + SourceStatusActive Status = "active" + SourceStatusNotActive Status = "not active" ) // TODO: need change feilds. type Source struct { - ID string - WriteKey WriteKey - Name string - Description string - ProjectID string - OwnerID string - Status Status - Metadata SourceMetadata - CreateAt time.Time - UpdateAt time.Time - DeleteAt *time.Time + ID string `json:"id"` + TokenID string `json:"token_id"` + WriteKey WriteKey `json:"write_key"` + Name string `json:"name"` + Description string `json:"description"` + ProjectID string `json:"project_id"` + OwnerID string `json:"owner_id"` + Status Status `json:"status"` + Metadata SourceMetadata `json:"metadata"` + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at"` } type SourceMetadata struct { - ID string - Name string - Slug string - Category SourceCategory + ID string `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Category SourceCategory `json:"category"` } diff --git a/manager/managerparam/projectparam/get.go b/manager/managerparam/projectparam/get.go new file mode 100644 index 00000000..f90353c8 --- /dev/null +++ b/manager/managerparam/projectparam/get.go @@ -0,0 +1,12 @@ +package projectparam + +import "github.com/ormushq/ormus/manager/entity" + +type GetRequest struct { + UserID string `json:"-"` + ProjectID string `json:"-" param:"projectID"` +} + +type GetResponse struct { + Project entity.Project `json:"project"` +} diff --git a/manager/managerparam/sourceparam/create.go b/manager/managerparam/sourceparam/create.go new file mode 100644 index 00000000..194d4e52 --- /dev/null +++ b/manager/managerparam/sourceparam/create.go @@ -0,0 +1,14 @@ +package sourceparam + +import "github.com/ormushq/ormus/manager/entity" + +type CreateRequest struct { + UserID string `json:"-"` + ProjectID string `json:"project_id"` + Name string `json:"name" example:"test name"` + Description string `json:"description" example:"test description"` +} + +type CreateResponse struct { + Source entity.Source `json:"source"` +} diff --git a/manager/managerparam/sourceparam/delete.go b/manager/managerparam/sourceparam/delete.go new file mode 100644 index 00000000..63d9831d --- /dev/null +++ b/manager/managerparam/sourceparam/delete.go @@ -0,0 +1,10 @@ +package sourceparam + +type DeleteRequest struct { + UserID string `json:"-"` + SourceID string `json:"-" param:"sourceID"` +} + +type DeleteResponse struct { + Message string `json:"message"` +} diff --git a/manager/managerparam/sourceparam/list.go b/manager/managerparam/sourceparam/list.go new file mode 100644 index 00000000..316555e1 --- /dev/null +++ b/manager/managerparam/sourceparam/list.go @@ -0,0 +1,16 @@ +package sourceparam + +import "github.com/ormushq/ormus/manager/entity" + +type ListRequest struct { + UserID string + LastTokenID int64 `query:"last_token_id"` + PerPage int `query:"per_page"` +} + +type ListResponse struct { + Sources []entity.Source `json:"sources"` + LastTokenID int64 `json:"last_token"` + PerPage int `json:"per_page"` + HasMore bool `json:"has_more"` +} diff --git a/manager/managerparam/sourceparam/update.go b/manager/managerparam/sourceparam/update.go new file mode 100644 index 00000000..7035c9c2 --- /dev/null +++ b/manager/managerparam/sourceparam/update.go @@ -0,0 +1,15 @@ +package sourceparam + +import "github.com/ormushq/ormus/manager/entity" + +type UpdateRequest struct { + UserID string `json:"-"` + SourceID string `json:"-" param:"SourceID"` + Name string `json:"name" example:"updated name"` + Description string `json:"description" example:"updated description"` + Status string `json:"status" example:"active"` +} + +type UpdateResponse struct { + Source entity.Source `json:"source"` +} diff --git a/manager/mockRepo/sourcemock/source_repo_mock.go b/manager/mockRepo/sourcemock/source_repo_mock.go index abd47b0a..60097936 100644 --- a/manager/mockRepo/sourcemock/source_repo_mock.go +++ b/manager/mockRepo/sourcemock/source_repo_mock.go @@ -70,9 +70,9 @@ func (m *MockRepo) InsertSource(source *entity.Source) (*managerparam.AddSourceR ProjectID: source.ProjectID, OwnerID: source.OwnerID, Status: source.Status, - CreateAt: source.CreateAt, - UpdateAt: source.UpdateAt, - DeleteAt: source.DeleteAt, + CreateAt: source.CreatedAt, + UpdateAt: source.UpdatedAt, + DeleteAt: source.DeletedAt, }, nil } @@ -93,9 +93,9 @@ func (m *MockRepo) UpdateSource(id string, source *entity.Source) (*managerparam ProjectID: source.ProjectID, OwnerID: source.OwnerID, Status: source.Status, - CreateAt: source.CreateAt, - UpdateAt: source.UpdateAt, - DeleteAt: source.DeleteAt, + CreateAt: source.CreatedAt, + UpdateAt: source.UpdatedAt, + DeleteAt: source.DeletedAt, }, nil } } diff --git a/manager/repository/scyllarepo/migrations/10_add_user_id_index.up.sql b/manager/repository/scyllarepo/migrations/10_add_user_id_index.up.sql deleted file mode 100644 index 40300a4c..00000000 --- a/manager/repository/scyllarepo/migrations/10_add_user_id_index.up.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX IF NOT EXISTS user_id_idx ON projects (user_id); \ No newline at end of file diff --git a/manager/repository/scyllarepo/migrations/11_add_deleted_index.down.sql b/manager/repository/scyllarepo/migrations/11_add_deleted_index.down.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/manager/repository/scyllarepo/migrations/11_add_deleted_index.up.sql b/manager/repository/scyllarepo/migrations/11_add_deleted_index.up.sql deleted file mode 100644 index 59631c77..00000000 --- a/manager/repository/scyllarepo/migrations/11_add_deleted_index.up.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX IF NOT EXISTS deleted_idx ON projects (deleted); diff --git a/manager/repository/scyllarepo/migrations/10_add_user_id_index.down.sql b/manager/repository/scyllarepo/migrations/12_add_deleted_col_to_sources.down.sql similarity index 100% rename from manager/repository/scyllarepo/migrations/10_add_user_id_index.down.sql rename to manager/repository/scyllarepo/migrations/12_add_deleted_col_to_sources.down.sql diff --git a/manager/repository/scyllarepo/migrations/12_add_deleted_col_to_sources.up.sql b/manager/repository/scyllarepo/migrations/12_add_deleted_col_to_sources.up.sql new file mode 100644 index 00000000..633cd25f --- /dev/null +++ b/manager/repository/scyllarepo/migrations/12_add_deleted_col_to_sources.up.sql @@ -0,0 +1 @@ +ALTER TABLE sources ADD deleted boolean; diff --git a/manager/repository/scyllarepo/migrations/3_create_source_table.up.sql b/manager/repository/scyllarepo/migrations/3_create_source_table.up.sql index a8a599b7..2069edd3 100644 --- a/manager/repository/scyllarepo/migrations/3_create_source_table.up.sql +++ b/manager/repository/scyllarepo/migrations/3_create_source_table.up.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS sources ( project_id TEXT, owner_id TEXT, status TEXT, - create_at TIMESTAMP, - update_at TIMESTAMP, - delete_at TIMESTAMP + created_at TIMESTAMP, + updated_at TIMESTAMP, + deleted_at TIMESTAMP ); \ No newline at end of file diff --git a/manager/repository/scyllarepo/scyllaproject/exist.go b/manager/repository/scyllarepo/scyllaproject/exist.go index 2ec23464..a0d975ac 100644 --- a/manager/repository/scyllarepo/scyllaproject/exist.go +++ b/manager/repository/scyllarepo/scyllaproject/exist.go @@ -13,7 +13,7 @@ func init() { } } -func (r Repository) IsProjectExist(projectID string) (bool, error) { +func (r Repository) Exist(projectID string) (bool, error) { var id string query, err := r.db.GetStatement(statements["IsProjectExist"]) if err != nil { diff --git a/manager/repository/scyllarepo/scyllaproject/hasmore.go b/manager/repository/scyllarepo/scyllaproject/hasmore.go index 9212ba23..7e3c242f 100644 --- a/manager/repository/scyllarepo/scyllaproject/hasmore.go +++ b/manager/repository/scyllarepo/scyllaproject/hasmore.go @@ -7,7 +7,7 @@ import ( func init() { statements["HaseMore"] = scyllarepo.Statement{ - Query: "SELECT COUNT(id) as total FROM projects where user_id = ? and token(id) > ?;", + Query: "SELECT COUNT(id) as total FROM projects where user_id = ? and token(id) > ? ALLOW FILTERING;", Values: []string{"user_id", "last_token"}, } } diff --git a/manager/repository/scyllarepo/scyllasource/create.go b/manager/repository/scyllarepo/scyllasource/create.go new file mode 100644 index 00000000..9ad4452c --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/create.go @@ -0,0 +1,46 @@ +package scyllasource + +import ( + "time" + + "github.com/google/uuid" + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["Create"] = scyllarepo.Statement{ + Query: "Insert into sources(id, write_key, name, description, project_id, owner_id, status, created_at,updated_at, deleted)values(?,?,?,?,?,?,?,?,?,?)", + Values: []string{"id", "write_key", "name", "description", "project_id", "owner_id", "status", "created_at", "updated_at", "deleted"}, + } +} + +func (r Repository) Create(source entity.Source) (entity.Source, error) { + query, err := r.db.GetStatement(statements["Create"]) + if err != nil { + return entity.Source{}, err + } + source.ID = uuid.New().String() + source.CreatedAt = time.Now() + source.UpdatedAt = source.CreatedAt + source.DeletedAt = nil + query.BindMap(qb.M{ + "id": source.ID, + "write_key": source.WriteKey, + "name": source.Name, + "description": source.Description, + "project_id": source.ProjectID, + "owner_id": source.OwnerID, + "status": entity.SourceStatusNotActive, + "created_at": source.CreatedAt, + "updated_at": source.UpdatedAt, + "deleted": false, + }) + + if err := query.Exec(); err != nil { + return entity.Source{}, err + } + + return source, nil +} diff --git a/manager/repository/scyllarepo/scyllasource/delete.go b/manager/repository/scyllarepo/scyllasource/delete.go new file mode 100644 index 00000000..44e43157 --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/delete.go @@ -0,0 +1,36 @@ +package scyllasource + +import ( + "time" + + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["Delete"] = scyllarepo.Statement{ + Query: "update sources set deleted_at = ?,deleted = ?,status = ? where id = ?", + Values: []string{"deleted_at", "deleted", "status", "id"}, + } +} + +func (r Repository) Delete(source entity.Source) error { + query, err := r.db.GetStatement(statements["Delete"]) + if err != nil { + return err + } + t := time.Now() + source.DeletedAt = &t + + query.BindMap(qb.M{ + "id": source.ID, + "deleted": true, + "status": entity.SourceStatusNotActive, + "deleted_at": source.DeletedAt, + }) + + err = query.Exec() + + return err +} diff --git a/manager/repository/scyllarepo/scyllasource/exist.go b/manager/repository/scyllarepo/scyllasource/exist.go new file mode 100644 index 00000000..4f4a0504 --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/exist.go @@ -0,0 +1,36 @@ +package scyllasource + +import ( + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["IsSourceExist"] = scyllarepo.Statement{ + Query: "SELECT id FROM sources WHERE id = ? LIMIT 1", + Values: []string{"id"}, + } +} + +func (r Repository) Exist(sourceID string) (bool, error) { + var id string + query, err := r.db.GetStatement(statements["IsSourceExist"]) + if err != nil { + return false, err + } + query.BindMap(qb.M{ + "id": sourceID, + }) + + found := query.Iter().Scan(&id) + if err = query.Iter().Close(); err != nil { + logger.L().Debug("Error closing iterator", "err msg:", err) + + return false, err + } + + logger.L().Debug("Query executed successfully", "is found:", found) + + return found, nil +} diff --git a/manager/repository/scyllarepo/scyllasource/get.go b/manager/repository/scyllarepo/scyllasource/get.go new file mode 100644 index 00000000..d166bd30 --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/get.go @@ -0,0 +1,32 @@ +package scyllasource + +import ( + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["GetWithId"] = scyllarepo.Statement{ + Query: "SELECT id,token(id) as token_id, write_key, name, description, project_id, owner_id, status, created_at, updated_at, deleted_at FROM sources where id = ? and deleted = false ALLOW FILTERING;", + Values: []string{"id"}, + } +} + +func (r Repository) GetWithID(id string) (entity.Source, error) { + query, err := r.db.GetStatement(statements["GetWithId"]) + if err != nil { + return entity.Source{}, err + } + + query.BindMap(qb.M{ + "id": id, + }) + + var source entity.Source + if err := query.Get(&source); err != nil { + return entity.Source{}, err + } + + return source, nil +} diff --git a/manager/repository/scyllarepo/scyllasource/hasmore.go b/manager/repository/scyllarepo/scyllasource/hasmore.go new file mode 100644 index 00000000..4e1c2393 --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/hasmore.go @@ -0,0 +1,32 @@ +package scyllasource + +import ( + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["HaseMore"] = scyllarepo.Statement{ + Query: "SELECT COUNT(id) as total FROM sources where owner_id = ? and token(id) > ? ALLOW FILTERING;", + Values: []string{"owner_id", "last_token"}, + } +} + +func (r Repository) HaseMore(ownerID string, lastToken int64, perPage int) (bool, error) { + query, err := r.db.GetStatement(statements["HaseMore"]) + if err != nil { + return false, err + } + + query.BindMap(qb.M{ + "owner_id": ownerID, + "last_token": lastToken, + }) + + var total int + if err := query.Scan(&total); err != nil { + return false, err + } + + return (total - perPage) > 0, nil +} diff --git a/manager/repository/scyllarepo/scyllasource/list.go b/manager/repository/scyllarepo/scyllasource/list.go new file mode 100644 index 00000000..d02a878b --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/list.go @@ -0,0 +1,39 @@ +package scyllasource + +import ( + "log/slog" + + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["List"] = scyllarepo.Statement{ + Query: "SELECT id,token(id) as token_id, write_key, name, description, project_id, owner_id, status, created_at, updated_at, deleted_at FROM sources where owner_id = ? AND token(id) > ? AND deleted = false LIMIT ? ALLOW FILTERING;", + Values: []string{"owner_id", "last_token", "limit"}, + } +} + +func (r Repository) List(ownerID string, lastToken int64, limit int) ([]entity.Source, error) { + query, err := r.db.GetStatement(statements["List"]) + if err != nil { + return nil, err + } + + query.BindMap(qb.M{ + "owner_id": ownerID, + "limit": limit, + "last_token": lastToken, + }) + + var sources []entity.Source + if err := query.Select(&sources); err != nil { + logger.L().Error(err.Error(), slog.String("query", query.String())) + + return nil, err + } + + return sources, nil +} diff --git a/manager/repository/scyllarepo/scyllasource/repo.go b/manager/repository/scyllarepo/scyllasource/repo.go new file mode 100644 index 00000000..b6bfb83a --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/repo.go @@ -0,0 +1,19 @@ +package scyllasource + +import ( + "github.com/ormushq/ormus/manager/repository/scyllarepo" +) + +type Repository struct { + db *scyllarepo.DB +} + +var statements = map[string]scyllarepo.Statement{} + +func New(db *scyllarepo.DB) *Repository { + db.RegisterStatements(statements) + + return &Repository{ + db: db, + } +} diff --git a/manager/repository/scyllarepo/scyllasource/update.go b/manager/repository/scyllarepo/scyllasource/update.go new file mode 100644 index 00000000..d655bf6a --- /dev/null +++ b/manager/repository/scyllarepo/scyllasource/update.go @@ -0,0 +1,38 @@ +package scyllasource + +import ( + "time" + + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/repository/scyllarepo" + "github.com/scylladb/gocqlx/v2/qb" +) + +func init() { + statements["Update"] = scyllarepo.Statement{ + Query: "update sources set name = ?,description = ?, status = ?, updated_at = ? where id = ?", + Values: []string{"name", "description", "status", "updated_at", "id"}, + } +} + +func (r Repository) Update(source entity.Source) (entity.Source, error) { + query, err := r.db.GetStatement(statements["Update"]) + if err != nil { + return entity.Source{}, err + } + source.UpdatedAt = time.Now() + + query.BindMap(qb.M{ + "id": source.ID, + "name": source.Name, + "description": source.Description, + "status": source.Status, + "updated_at": source.UpdatedAt, + }) + + if err := query.Exec(); err != nil { + return entity.Source{}, err + } + + return source, nil +} diff --git a/manager/repository/scyllarepo/source.go b/manager/repository/scyllarepo/source.go deleted file mode 100644 index 37980912..00000000 --- a/manager/repository/scyllarepo/source.go +++ /dev/null @@ -1,21 +0,0 @@ -package scyllarepo - -// func (a StorageAdapter) InsertSource(source *entity.Source) error { -// panic("implement me") -// } - -// func (a StorageAdapter) UpdateSource(id string, source *entity.Source) error { -// panic("implement me") -// } - -// func (a StorageAdapter) DeleteSource(id string) error { -// panic("implement me") -// } - -// func (a StorageAdapter) GetUserSourceByID(ownerID, id string) (*entity.Source, error) { -// panic("implement me") -// } - -// func (a StorageAdapter) IsSourceAlreadyCreatedByName(name string) (bool, error) { -// panic("implement me") -// } diff --git a/manager/service/projectservice/delete.go b/manager/service/projectservice/delete.go index e218d213..ef5041cb 100644 --- a/manager/service/projectservice/delete.go +++ b/manager/service/projectservice/delete.go @@ -7,7 +7,7 @@ import ( ) func (s Service) Delete(req projectparam.DeleteRequest) (projectparam.DeleteResponse, error) { - const op = "projectService.Update" + const op = "projectService.Delete" vErr := s.validator.ValidateDeleteRequest(req) if vErr != nil { diff --git a/manager/service/projectservice/get.go b/manager/service/projectservice/get.go new file mode 100644 index 00000000..cd230942 --- /dev/null +++ b/manager/service/projectservice/get.go @@ -0,0 +1,28 @@ +package projectservice + +import ( + "github.com/ormushq/ormus/manager/managerparam/projectparam" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (s Service) Get(req projectparam.GetRequest) (projectparam.GetResponse, error) { + const op = "projectService.Get" + + vErr := s.validator.ValidateGetRequest(req) + if vErr != nil { + return projectparam.GetResponse{}, vErr + } + + project, err := s.repo.GetWithID(req.ProjectID) + if err != nil { + return projectparam.GetResponse{}, richerror.New(op).WithWrappedError(err).WithMessage(errmsg.ErrSomeThingWentWrong) + } + if project.UserID != req.UserID { + return projectparam.GetResponse{}, richerror.New(op).WithKind(richerror.KindForbidden).WithMessage(errmsg.ErrAccessDenied) + } + + return projectparam.GetResponse{ + Project: project, + }, nil +} diff --git a/manager/service/sourceservice/create.go b/manager/service/sourceservice/create.go index 2f303c16..1745f3dc 100644 --- a/manager/service/sourceservice/create.go +++ b/manager/service/sourceservice/create.go @@ -1,30 +1,51 @@ package sourceservice import ( + "github.com/ormushq/ormus/logger" "github.com/ormushq/ormus/manager/entity" - "github.com/ormushq/ormus/manager/managerparam" + "github.com/ormushq/ormus/manager/managerparam/projectparam" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" writekey "github.com/ormushq/ormus/pkg/write_key" ) -func (s Service) CreateSource(req *managerparam.AddSourceRequest, ownerID string) (*managerparam.AddSourceResponse, error) { +func (s Service) CreateSource(req sourceparam.CreateRequest) (sourceparam.CreateResponse, error) { + const op = "sourceService.Create" + + vErr := s.validator.ValidateCreateRequest(req) + if vErr != nil { + return sourceparam.CreateResponse{}, vErr + } + w, err := writekey.GenerateNewWriteKey() if err != nil { - return nil, err + return sourceparam.CreateResponse{}, err + } + + getProjectReq := projectparam.GetRequest{ + UserID: req.UserID, + ProjectID: req.ProjectID, + } + _, err = s.projectSvc.Get(getProjectReq) + if err != nil { + return sourceparam.CreateResponse{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage(errmsg.ErrProjectNotFound) } - source := &entity.Source{ - ID: "", // TODO uuid ulid ? + source := entity.Source{ WriteKey: entity.WriteKey(w), Name: req.Name, Description: req.Description, - OwnerID: ownerID, + OwnerID: req.UserID, ProjectID: req.ProjectID, } - response, err := s.repo.InsertSource(source) + source, err = s.repo.Create(source) if err != nil { - return nil, err + logger.L().Error(err.Error()) + + return sourceparam.CreateResponse{}, richerror.New(op).WithWrappedError(err).WithMessage(errmsg.ErrSomeThingWentWrong) } - return response, nil + return sourceparam.CreateResponse{Source: source}, nil } diff --git a/manager/service/sourceservice/delete.go b/manager/service/sourceservice/delete.go index 58173e4c..6e2b17cf 100644 --- a/manager/service/sourceservice/delete.go +++ b/manager/service/sourceservice/delete.go @@ -1,5 +1,33 @@ package sourceservice -func (s Service) DeleteSource(id, userID string) error { - return s.repo.DeleteSource(id, userID) +import ( + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (s Service) Delete(req sourceparam.DeleteRequest) (sourceparam.DeleteResponse, error) { + const op = "sourceservice.Update" + + vErr := s.validator.ValidateDeleteRequest(req) + if vErr != nil { + return sourceparam.DeleteResponse{}, vErr + } + + source, err := s.repo.GetWithID(req.SourceID) + if err != nil { + return sourceparam.DeleteResponse{}, richerror.New(op).WithWrappedError(err).WithMessage(errmsg.ErrSomeThingWentWrong) + } + if source.OwnerID != req.UserID { + return sourceparam.DeleteResponse{}, richerror.New(op).WithKind(richerror.KindForbidden).WithMessage(errmsg.ErrAccessDenied) + } + + err = s.repo.Delete(source) + if err != nil { + return sourceparam.DeleteResponse{}, richerror.New(op).WithWrappedError(err).WithMessage(errmsg.ErrSomeThingWentWrong) + } + + return sourceparam.DeleteResponse{ + Message: "source deleted successfully", + }, nil } diff --git a/manager/service/sourceservice/list.go b/manager/service/sourceservice/list.go new file mode 100644 index 00000000..fe038eb1 --- /dev/null +++ b/manager/service/sourceservice/list.go @@ -0,0 +1,30 @@ +package sourceservice + +import ( + "github.com/ormushq/ormus/logger" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (s Service) List(req sourceparam.ListRequest) (sourceparam.ListResponse, error) { + const op = "projectService.List" + + sources, err := s.repo.List(req.UserID, req.LastTokenID, req.PerPage) + logger.LogError(err) + if err != nil { + return sourceparam.ListResponse{}, richerror.New(op).WithWrappedError(err) + } + + haseMore, err := s.repo.HaseMore(req.UserID, req.LastTokenID, req.PerPage) + logger.LogError(err) + if err != nil { + return sourceparam.ListResponse{}, richerror.New(op).WithWrappedError(err) + } + + return sourceparam.ListResponse{ + Sources: sources, + HasMore: haseMore, + LastTokenID: req.LastTokenID, + PerPage: req.PerPage, + }, nil +} diff --git a/manager/service/sourceservice/service.go b/manager/service/sourceservice/service.go index 5836ee34..b12e3a24 100644 --- a/manager/service/sourceservice/service.go +++ b/manager/service/sourceservice/service.go @@ -2,23 +2,32 @@ package sourceservice import ( "github.com/ormushq/ormus/manager/entity" - "github.com/ormushq/ormus/manager/managerparam" + "github.com/ormushq/ormus/manager/managerparam/projectparam" + "github.com/ormushq/ormus/manager/validator/sourcevalidator" ) type SourceRepo interface { - InsertSource(source *entity.Source) (*managerparam.AddSourceResponse, error) - UpdateSource(id string, source *entity.Source) (*managerparam.UpdateSourceResponse, error) - DeleteSource(id, userID string) error - GetUserSourceByID(ownerID, id string) (*entity.Source, error) - IsSourceAlreadyCreatedByName(name string) (bool, error) + Create(source entity.Source) (entity.Source, error) + GetWithID(id string) (entity.Source, error) + Update(source entity.Source) (entity.Source, error) + Delete(source entity.Source) error + List(ownerID string, lastToken int64, limit int) ([]entity.Source, error) + HaseMore(ownerID string, lastToken int64, perPage int) (bool, error) } +type ProjectSvc interface { + Get(projectparam.GetRequest) (projectparam.GetResponse, error) +} type Service struct { - repo SourceRepo + repo SourceRepo + validator sourcevalidator.Validator + projectSvc ProjectSvc } -func New(repo SourceRepo) *Service { - return &Service{ - repo: repo, +func New(repo SourceRepo, validator sourcevalidator.Validator, projectSvc ProjectSvc) Service { + return Service{ + repo: repo, + validator: validator, + projectSvc: projectSvc, } } diff --git a/manager/service/sourceservice/source_test.go b/manager/service/sourceservice/source_test.go deleted file mode 100644 index 9bd06059..00000000 --- a/manager/service/sourceservice/source_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package sourceservice_test - -import ( - "fmt" - "testing" - - "github.com/ormushq/ormus/manager/managerparam" - "github.com/ormushq/ormus/manager/mockRepo/sourcemock" - "github.com/ormushq/ormus/manager/mockRepo/usermock" - "github.com/ormushq/ormus/manager/service/sourceservice" - "github.com/ormushq/ormus/pkg/errmsg" - "github.com/ormushq/ormus/pkg/richerror" - "github.com/stretchr/testify/assert" -) - -func TestDeleteSource(t *testing.T) { - testCases := []struct { - name string - repoErr bool - expectedErr error - req string - }{ - { - name: "repo fails", - repoErr: true, - expectedErr: richerror.New("MockRepo.DeleteSource").WithWrappedError(fmt.Errorf(usermock.RepoErr)), - req: "source_id", - }, - { - name: "ordinary", - req: "source_id", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 1. setup - mockRepo := sourcemock.NewMockRepository(tc.repoErr) - service := sourceservice.New(mockRepo) - - // 2. execution - err := service.DeleteSource(tc.req, "userID") - - // 3. assertion - if tc.expectedErr != nil { - assert.Equal(t, tc.expectedErr, err) - - return - } - - assert.NoError(t, err) - }) - } -} - -func TestUpdateSource(t *testing.T) { - testCases := []struct { - name string - repoErr bool - expectedErr error - sourceID string - ownerID string - req1 managerparam.UpdateSourceRequest - }{ - { - name: "repo fails", - repoErr: true, - expectedErr: richerror.New("MockRepo.GetUserSourceById").WithWrappedError(fmt.Errorf(usermock.RepoErr)), - sourceID: "source_id", - ownerID: "owner_id", - req1: managerparam.UpdateSourceRequest{ - Name: "new name", - Description: "new description", - ProjectID: "new project id", - }, - }, - { - name: "ordinary", - repoErr: false, - sourceID: "source_id", - ownerID: "owner_id", - req1: managerparam.UpdateSourceRequest{ - Name: "new name", - Description: "new description", - ProjectID: "new project id", - }, - }, - { - name: "user not found", - repoErr: false, - expectedErr: richerror.New("MockRepo.GetUserSourceById").WithMessage(errmsg.ErrUserNotFound), - sourceID: "invalide source_id", - ownerID: "owner_id", - req1: managerparam.UpdateSourceRequest{ - Name: "new name", - Description: "new description", - ProjectID: "new project id", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 1. setup - mockRepo := sourcemock.NewMockRepository(tc.repoErr) - service := sourceservice.New(mockRepo) - - // 2. execution - response, err := service.UpdateSource(tc.ownerID, tc.sourceID, &tc.req1) - - // 3. assertion - if tc.expectedErr != nil { - assert.Equal(t, tc.expectedErr, err) - assert.Empty(t, response) - return - } - - assert.NoError(t, err) - assert.NotEmpty(t, response) - }) - } -} - -func TestCreateSource(t *testing.T) { - testCases := []struct { - name string - repoErr bool - expectedErr error - ownerID string - req managerparam.AddSourceRequest - }{ - { - name: "repo fails", - repoErr: true, - expectedErr: richerror.New("MockRepo.InsertSource").WithWrappedError(fmt.Errorf(usermock.RepoErr)), - ownerID: "owner_id", - req: managerparam.AddSourceRequest{ - Name: "name", - Description: "description", - ProjectID: "project id", - }, - }, - { - name: "ordinary", - repoErr: false, - ownerID: "owner_id", - req: managerparam.AddSourceRequest{ - Name: "un existed name", - Description: "description", - ProjectID: "project id", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 1. setup - mockRepo := sourcemock.NewMockRepository(tc.repoErr) - service := sourceservice.New(mockRepo) - - // 2. execution - response, err := service.CreateSource(&tc.req, tc.ownerID) - - // 3. assertion - if tc.expectedErr != nil { - assert.Equal(t, tc.expectedErr, err) - assert.Empty(t, response) - return - } - - assert.NoError(t, err) - assert.NotEmpty(t, response) - }) - } -} diff --git a/manager/service/sourceservice/update.go b/manager/service/sourceservice/update.go index 9fcf158e..f85c26fc 100644 --- a/manager/service/sourceservice/update.go +++ b/manager/service/sourceservice/update.go @@ -1,24 +1,42 @@ package sourceservice import ( - "github.com/ormushq/ormus/manager/managerparam" + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" ) -func (s Service) UpdateSource(ownerID, sourceID string, req *managerparam.UpdateSourceRequest) (*managerparam.UpdateSourceResponse, error) { - source, err := s.repo.GetUserSourceByID(ownerID, sourceID) +func (s Service) Update(req sourceparam.UpdateRequest) (sourceparam.UpdateResponse, error) { + const op = "sourceService.Update" + + vErr := s.validator.ValidateUpdateRequest(req) + if vErr != nil { + return sourceparam.UpdateResponse{}, vErr + } + source, err := s.repo.GetWithID(req.SourceID) if err != nil { - return nil, err + return sourceparam.UpdateResponse{}, richerror.New(op).WithWrappedError(err) } + if source.OwnerID != req.UserID { + return sourceparam.UpdateResponse{}, richerror.New(op).WithKind(richerror.KindForbidden).WithMessage(errmsg.ErrAccessDenied) + } source.Name = req.Name source.Description = req.Description - source.ProjectID = req.ProjectID - source.Status = req.Status + switch req.Status { + case string(entity.SourceStatusActive): + source.Status = entity.SourceStatusActive + case string(entity.SourceStatusNotActive): + source.Status = entity.SourceStatusNotActive + } - response, err := s.repo.UpdateSource(sourceID, source) + source, err = s.repo.Update(source) if err != nil { - return nil, err + return sourceparam.UpdateResponse{}, richerror.New(op).WithWrappedError(err).WithMessage(errmsg.ErrSomeThingWentWrong) } - return response, nil + return sourceparam.UpdateResponse{ + Source: source, + }, nil } diff --git a/manager/validator/projectvalidator/get.go b/manager/validator/projectvalidator/get.go new file mode 100644 index 00000000..842e2fa3 --- /dev/null +++ b/manager/validator/projectvalidator/get.go @@ -0,0 +1,41 @@ +package projectvalidator + +import ( + "errors" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/ormushq/ormus/manager/managerparam/projectparam" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (v Validator) ValidateGetRequest(req projectparam.GetRequest) *validator.Error { + const op = "projectvalidator.ValidateGetRequest" + + if err := validation.ValidateStruct(&req, + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.ProjectID, validation.Required, validation.By(v.isProjectExist)), + ); err != nil { + fieldErr := make(map[string]string) + + var errV validation.Errors + ok := errors.As(err, &errV) + + if ok { + for key, value := range errV { + if value != nil { + fieldErr[key] = value.Error() + } + } + } + + return &validator.Error{ + Fields: fieldErr, + Err: richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). + WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), + } + } + + return nil +} diff --git a/manager/validator/sourcevalidator/create.go b/manager/validator/sourcevalidator/create.go new file mode 100644 index 00000000..e2eef5c7 --- /dev/null +++ b/manager/validator/sourcevalidator/create.go @@ -0,0 +1,46 @@ +package sourcevalidator + +import ( + "errors" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (v Validator) ValidateCreateRequest(req sourceparam.CreateRequest) *validator.Error { + minNameLength := 5 + maxNameLength := 30 + + minDescriptionLength := 5 + maxDescriptionLength := 100 + if err := validation.ValidateStruct(&req, + validation.Field(&req.Name, validation.Required, validation.Length(minNameLength, maxNameLength)), + validation.Field(&req.Description, validation.Required, validation.Length(minDescriptionLength, maxDescriptionLength)), + validation.Field(&req.UserID, validation.Required), + ); err != nil { + + fieldErr := make(map[string]string) + + var errV validation.Errors + ok := errors.As(err, &errV) + + if ok { + for key, value := range errV { + if value != nil { + fieldErr[key] = value.Error() + } + } + } + + return &validator.Error{ + Fields: fieldErr, + Err: richerror.New("sourcevalidator.ValidateCreateSourceForm").WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). + WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), + } + } + + return nil +} diff --git a/manager/validator/sourcevalidator/delete.go b/manager/validator/sourcevalidator/delete.go new file mode 100644 index 00000000..48f63e5d --- /dev/null +++ b/manager/validator/sourcevalidator/delete.go @@ -0,0 +1,41 @@ +package sourcevalidator + +import ( + "errors" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (v Validator) ValidateDeleteRequest(req sourceparam.DeleteRequest) *validator.Error { + const op = "sourcevalidator.ValidateDeleteRequest" + + if err := validation.ValidateStruct(&req, + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.SourceID, validation.Required, validation.By(v.isSourceExist)), + ); err != nil { + fieldErr := make(map[string]string) + + var errV validation.Errors + ok := errors.As(err, &errV) + + if ok { + for key, value := range errV { + if value != nil { + fieldErr[key] = value.Error() + } + } + } + + return &validator.Error{ + Fields: fieldErr, + Err: richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). + WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), + } + } + + return nil +} diff --git a/manager/validator/sourcevalidator/source.go b/manager/validator/sourcevalidator/source.go deleted file mode 100644 index 00999117..00000000 --- a/manager/validator/sourcevalidator/source.go +++ /dev/null @@ -1,161 +0,0 @@ -package sourcevalidator - -import ( - "errors" - - validation "github.com/go-ozzo/ozzo-validation/v4" - "github.com/oklog/ulid/v2" - "github.com/ormushq/ormus/manager/entity" - "github.com/ormushq/ormus/manager/managerparam" - "github.com/ormushq/ormus/pkg/errmsg" - "github.com/ormushq/ormus/pkg/richerror" -) - -func (v Validator) ValidateCreateSourceForm(req managerparam.AddSourceRequest) *ValidatorError { - minNameLength := 5 - maxNameLength := 30 - - minDescriptionLength := 5 - maxDescriptionLength := 100 - - if err := validation.ValidateStruct(&req, - validation.Field(&req.Name, validation.Required, validation.Length(minNameLength, maxNameLength), validation.By(v.isSourceAlreadyCreated)), - validation.Field(&req.Description, validation.Required, validation.Length(minDescriptionLength, maxDescriptionLength)), - validation.Field(&req.ProjectID, validation.Required, validation.By(v.validateULID)), - ); err != nil { - - fieldErr := make(map[string]string) - - var errV validation.Errors - ok := errors.As(err, &errV) - - if ok { - for key, value := range errV { - if value != nil { - fieldErr[key] = value.Error() - } - } - } - - return &ValidatorError{ - Fields: fieldErr, - Err: richerror.New("sourcevalidator.ValidateCreateSourceForm").WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). - WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), - } - } - - return nil -} - -func (v Validator) ValidateUpdateSourceForm(req managerparam.UpdateSourceRequest) *ValidatorError { - minNameLength := 5 - maxNameLength := 30 - - minDescriptionLength := 5 - maxDescriptionLength := 100 - - if err := validation.ValidateStruct(&req, - validation.Field(&req.Name, validation.Required, validation.Length(minNameLength, maxNameLength)), - validation.Field(&req.Description, validation.Required, validation.Length(minDescriptionLength, maxDescriptionLength)), - validation.Field(&req.ProjectID, validation.Required, validation.By(v.validateULID)), - validation.Field(&req.Status, validation.Required, validation.By(v.validateStatus)), - ); err != nil { - - fieldErr := make(map[string]string) - - var errV validation.Errors - ok := errors.As(err, &errV) - - if ok { - for key, value := range errV { - if value != nil { - fieldErr[key] = value.Error() - } - } - } - - return &ValidatorError{ - Fields: fieldErr, - Err: richerror.New("sourcevalidator.ValidateUpdateSourceForm").WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). - WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), - } - } - - return nil -} - -func (v Validator) ValidateIDToDelete(id string) *ValidatorError { - if err := validation.Validate(id, - validation.By(v.isSourceAlreadyCreated), - ); err != nil { - - fieldErr := make(map[string]string) - - var errV validation.Errors - ok := errors.As(err, &errV) - - if ok { - for key, value := range errV { - if value != nil { - fieldErr[key] = value.Error() - } - } - } - - return &ValidatorError{ - Fields: fieldErr, - Err: richerror.New("sourcevalidator.ValidateIDToDelete").WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). - WhitMeta(map[string]interface{}{"request:": id}).WithWrappedError(err), - } - } - - return nil -} - -func (v Validator) validateULID(value interface{}) error { - s, ok := value.(string) - if !ok { - return errors.New("error while reflection interface") - } - - _, err := ulid.Parse(s) - if err != nil { - return errors.New("invalid id") - } - - return nil -} - -func (v Validator) isSourceAlreadyCreated(value interface{}) error { - s, ok := value.(string) - if !ok { - return errors.New("error while reflection interface") - } - - exist, err := v.repo.IsSourceAlreadyCreatedByName(s) - if err != nil { - return err - } - - if exist { - return errors.New("this name is already usesd") - } - - return nil -} - -func (v Validator) validateStatus(value interface{}) error { - s, ok := value.(entity.Status) - if !ok { - return errors.New("error while reflection interface") - } - - switch s { - case "active": - return nil - case "not active": - return nil - } - - return errors.New("invalide status of source") -} diff --git a/manager/validator/sourcevalidator/update.go b/manager/validator/sourcevalidator/update.go new file mode 100644 index 00000000..16dfb548 --- /dev/null +++ b/manager/validator/sourcevalidator/update.go @@ -0,0 +1,66 @@ +package sourcevalidator + +import ( + "errors" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/ormushq/ormus/manager/entity" + "github.com/ormushq/ormus/manager/managerparam/sourceparam" + "github.com/ormushq/ormus/manager/validator" + "github.com/ormushq/ormus/pkg/errmsg" + "github.com/ormushq/ormus/pkg/richerror" +) + +func (v Validator) ValidateUpdateRequest(req sourceparam.UpdateRequest) *validator.Error { + const op = "sourcevalidator.ValidateUpdateRequest" + + minNameLength := 5 + maxNameLength := 30 + + minDescriptionLength := 5 + maxDescriptionLength := 100 + + if err := validation.ValidateStruct(&req, + validation.Field(&req.Name, validation.Required, validation.Length(minNameLength, maxNameLength)), + validation.Field(&req.Description, validation.Required, validation.Length(minDescriptionLength, maxDescriptionLength)), + validation.Field(&req.UserID, validation.Required), + validation.Field(&req.Status, validation.Required, validation.By(v.validateStatus)), + ); err != nil { + + fieldErr := make(map[string]string) + + var errV validation.Errors + ok := errors.As(err, &errV) + + if ok { + for key, value := range errV { + if value != nil { + fieldErr[key] = value.Error() + } + } + } + + return &validator.Error{ + Fields: fieldErr, + Err: richerror.New(op).WithMessage(errmsg.ErrorMsgInvalidInput).WhitKind(richerror.KindInvalid). + WhitMeta(map[string]interface{}{"request:": req}).WithWrappedError(err), + } + } + + return nil +} + +func (v Validator) validateStatus(value interface{}) error { + s, ok := value.(string) + if !ok { + return errors.New("error while reflection interface") + } + switch s { + case string(entity.SourceStatusActive): + return nil + case string(entity.SourceStatusNotActive): + return nil + } + + return errors.New("invalide status of source") +} diff --git a/manager/validator/sourcevalidator/validation_test.go b/manager/validator/sourcevalidator/validation_test.go deleted file mode 100644 index 2114475a..00000000 --- a/manager/validator/sourcevalidator/validation_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package sourcevalidator_test - -import ( - "fmt" - "testing" - - "github.com/ormushq/ormus/manager/entity" - "github.com/ormushq/ormus/manager/managerparam" - "github.com/ormushq/ormus/manager/mockRepo/sourcemock" - "github.com/ormushq/ormus/manager/validator/sourcevalidator" - "github.com/stretchr/testify/assert" -) - -func TestValidateUpdateSourceForm(t *testing.T) { - testCases := []struct { - name string - params managerparam.UpdateSourceRequest - repoErr bool - error error - }{ - { - name: "less than min name len", - error: fmt.Errorf("Name: the length must be between 5 and 30\n"), - params: managerparam.UpdateSourceRequest{ - Name: "le", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - Status: entity.StatusNotActive, - }, - }, - { - name: "more than max name len", - error: fmt.Errorf("Name: the length must be between 5 and 30\n"), - params: managerparam.UpdateSourceRequest{ - Name: "more than max name len la la la la la la la la la la la la la la la la", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - Status: entity.StatusNotActive, - }, - }, - { - name: "less than min description len", - error: fmt.Errorf("Description: the length must be between 5 and 100\n"), - params: managerparam.UpdateSourceRequest{ - Name: "normal new name", - Description: "de", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - Status: entity.StatusNotActive, - }, - }, - { - name: "more than max description len", - error: fmt.Errorf("Description: the length must be between 5 and 100\n"), - params: managerparam.UpdateSourceRequest{ - Name: "normal new name", - Description: "more then max description len la la la la la la la la la la la la la lal la la lal al al lal ala lal al lala l l", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - Status: entity.StatusNotActive, - }, - }, - { - name: "invalide project id", - error: fmt.Errorf("ProjectID: invalid id\n"), - params: managerparam.UpdateSourceRequest{ - Name: "normal new name", - Description: "new normal description", - ProjectID: "invalide project id", - Status: entity.StatusNotActive, - }, - }, - { - name: "ordinary", - error: nil, - params: managerparam.UpdateSourceRequest{ - Name: "normal new name", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - Status: entity.StatusNotActive, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 1. setup - mockRepo := sourcemock.NewMockRepository(tc.repoErr) - valid := sourcevalidator.New(mockRepo) - - // 2. execution - res := valid.ValidateUpdateSourceForm(tc.params) - - // 3. assertion - if tc.error == nil { - assert.Nil(t, res) - return - } - assert.Equal(t, tc.error.Error(), res.Error()) - }) - } -} - -func TestValidateCreateSourceForm(t *testing.T) { - defaulte := sourcemock.DefaultSource() - - testCases := []struct { - name string - params managerparam.AddSourceRequest - repoErr bool - error error - }{ - { - name: "less than min name len", - error: fmt.Errorf("Name: the length must be between 5 and 30\n"), - params: managerparam.AddSourceRequest{ - Name: "le", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - { - name: "more than max name len", - error: fmt.Errorf("Name: the length must be between 5 and 30\n"), - params: managerparam.AddSourceRequest{ - Name: "more than max name len la la la la la la la la la la la la la la la la", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - { - name: "less than min description len", - error: fmt.Errorf("Description: the length must be between 5 and 100\n"), - params: managerparam.AddSourceRequest{ - Name: "normal new name", - Description: "de", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - { - name: "more than max description len", - error: fmt.Errorf("Description: the length must be between 5 and 100\n"), - params: managerparam.AddSourceRequest{ - Name: "normal new name", - Description: "more then max description len la la la la la la la la la la la la la lal la la lal al al lal ala lal al lala l l", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - { - name: "invalide project id", - error: fmt.Errorf("ProjectID: invalid id\n"), - params: managerparam.AddSourceRequest{ - Name: "normal new name", - Description: "new normal description", - ProjectID: "invalide project id", - }, - }, - { - name: "exist source", - error: fmt.Errorf("Name: this name is already usesd\n"), - params: managerparam.AddSourceRequest{ - Name: defaulte.Name, - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - { - name: "ordinary", - error: nil, - params: managerparam.AddSourceRequest{ - Name: "normal new name", - Description: "new normal description", - ProjectID: "01HJDQ386MW8EM6WMC8B6J5HAN", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 1. setup - mockRepo := sourcemock.NewMockRepository(tc.repoErr) - valid := sourcevalidator.New(mockRepo) - - // 2. execution - res := valid.ValidateCreateSourceForm(tc.params) - - // 3. assertion - if tc.error == nil { - assert.Nil(t, res) - return - } - assert.Equal(t, tc.error.Error(), res.Error()) - }) - } -} diff --git a/manager/validator/sourcevalidator/validator.go b/manager/validator/sourcevalidator/validator.go index 35794433..e62aa0a7 100644 --- a/manager/validator/sourcevalidator/validator.go +++ b/manager/validator/sourcevalidator/validator.go @@ -1,9 +1,8 @@ package sourcevalidator import ( + "errors" "fmt" - - "github.com/ormushq/ormus/manager/service/sourceservice" ) type ValidatorError struct { @@ -21,10 +20,34 @@ func (v ValidatorError) Error() string { return err } +type Repository interface { + Exist(id string) (bool, error) +} + type Validator struct { - repo sourceservice.SourceRepo + sourceRepo Repository +} + +func New(sourceRepo Repository) Validator { + return Validator{ + sourceRepo: sourceRepo, + } } -func New(repo sourceservice.SourceRepo) Validator { - return Validator{repo: repo} +func (v Validator) isSourceExist(value interface{}) error { + s, ok := value.(string) + if !ok { + return errors.New("error while reflection interface") + } + + exist, err := v.sourceRepo.Exist(s) + if err != nil { + return err + } + + if !exist { + return errors.New("source does not exist") + } + + return nil }