From 2be14d573a3c017e4364c2fa9ecd78c7003c5167 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:40:25 -0700 Subject: [PATCH 1/2] Added update multiple accounts token API --- .../Switcher GitOps.postman_collection.json | 36 ++++++++ resources/swagger.yaml | 64 ++++++++++++++ src/controller/account.go | 50 +++++++++++ src/controller/account_test.go | 86 +++++++++++++++++++ 4 files changed, 236 insertions(+) diff --git a/resources/postman/Switcher GitOps.postman_collection.json b/resources/postman/Switcher GitOps.postman_collection.json index 964c7b8..c236d70 100644 --- a/resources/postman/Switcher GitOps.postman_collection.json +++ b/resources/postman/Switcher GitOps.postman_collection.json @@ -117,6 +117,42 @@ }, "response": [] }, + { + "name": "Update Tokens", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{gitopsToken}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n\t\"domainId\": \"{{domain_id}}\",\r\n\t\"token\": \"{{github_pat}}\",\r\n \"environments\": [\r\n \"default\",\r\n \"staging\"\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{url}}/account", + "host": [ + "{{url}}" + ], + "path": [ + "account" + ] + } + }, + "response": [] + }, { "name": "Update (force sync)", "request": { diff --git a/resources/swagger.yaml b/resources/swagger.yaml index 0a3a804..24205b2 100644 --- a/resources/swagger.yaml +++ b/resources/swagger.yaml @@ -171,6 +171,46 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + put: + tags: + - Account API + summary: Update multiple accounts token + description: Update multiple accounts token by domain ID and environment + security: + - bearerAuth: [] + parameters: + - name: domainId + in: path + required: true + description: Domain ID + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AccountTokensRequest' + responses: + '200': + description: Account tokens updated + content: + application/json: + schema: + $ref: '#/components/schemas/AccountTokensResponse' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Account not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' /account/{domainId}/{environment}: get: tags: @@ -361,6 +401,30 @@ components: forceprune: type: boolean description: Force delete elements from the API when true + AccountTokensRequest: + type: object + properties: + token: + type: string + description: Git token + domainId: + type: string + format: uuid + description: Domain ID + environments: + type: array + items: + type: string + description: Environment names + AccountTokensResponse: + type: object + properties: + result: + type: boolean + description: Result status + message: + type: string + description: Result message ErrorResponse: type: object properties: diff --git a/src/controller/account.go b/src/controller/account.go index 40a96ee..f885bc5 100644 --- a/src/controller/account.go +++ b/src/controller/account.go @@ -18,6 +18,17 @@ type AccountController struct { routeAccountPath string } +type AccountTokensRequest struct { + Token string `json:"token"` + DomainId string `json:"domainId"` + Environments []string `json:"environments"` +} + +type AccountTokensResponse struct { + Result bool `json:"result"` + Message string `json:"message"` +} + type ErrorResponse struct { Error string `json:"error"` } @@ -35,6 +46,8 @@ func (controller *AccountController) RegisterRoutes(r *mux.Router) http.Handler ValidateToken(http.HandlerFunc(controller.CreateAccountHandler))).Methods(http.MethodPost) r.NewRoute().Path(controller.routeAccountPath).Name("UpdateAccount").Handler( ValidateToken(http.HandlerFunc(controller.UpdateAccountHandler))).Methods(http.MethodPut) + r.NewRoute().Path(controller.routeAccountPath + "/{domainId}").Name("UpdateAccountTokens").Handler( + ValidateToken(http.HandlerFunc(controller.UpdateAccountTokensHandler))).Methods(http.MethodPut) r.NewRoute().Path(controller.routeAccountPath + "/{domainId}").Name("GelAllAccountsByDomainId").Handler( ValidateToken(http.HandlerFunc(controller.FetchAllAccountsByDomainIdHandler))).Methods(http.MethodGet) r.NewRoute().Path(controller.routeAccountPath + "/{domainId}/{enviroment}").Name("GetAccount").Handler( @@ -132,6 +145,43 @@ func (controller *AccountController) UpdateAccountHandler(w http.ResponseWriter, utils.ResponseJSON(w, accountUpdated, http.StatusOK) } +func (controller *AccountController) UpdateAccountTokensHandler(w http.ResponseWriter, r *http.Request) { + var accountTokensRequest AccountTokensRequest + err := json.NewDecoder(r.Body).Decode(&accountTokensRequest) + if err != nil { + utils.LogError("Error updating account tokens: %s", err.Error()) + utils.ResponseJSON(w, ErrorResponse{Error: "Invalid request"}, http.StatusBadRequest) + return + } + + if accountTokensRequest.Token == "" { + utils.LogError("Error updating account tokens: Token is required") + utils.ResponseJSON(w, ErrorResponse{Error: "Token is required"}, http.StatusBadRequest) + return + } + + // Encrypt token before saving + accountTokensRequest.Token = utils.Encrypt(accountTokensRequest.Token, config.GetEnv("GIT_TOKEN_PRIVATE_KEY")) + + // Update account tokens + for _, environment := range accountTokensRequest.Environments { + account, err := controller.accountRepository.FetchByDomainIdEnvironment(accountTokensRequest.DomainId, environment) + if err != nil { + utils.LogError("Error fetching account: %s", err.Error()) + utils.ResponseJSON(w, ErrorResponse{Error: "Error fetching account"}, http.StatusNotFound) + return + } + + account.Token = accountTokensRequest.Token + controller.accountRepository.Update(account) + } + + utils.ResponseJSON(w, AccountTokensResponse{ + Result: true, + Message: "Account tokens updated successfully", + }, http.StatusOK) +} + func (controller *AccountController) DeleteAccountHandler(w http.ResponseWriter, r *http.Request) { domainId := mux.Vars(r)["domainId"] enviroment := mux.Vars(r)["enviroment"] diff --git a/src/controller/account_test.go b/src/controller/account_test.go index 210f5c8..5479422 100644 --- a/src/controller/account_test.go +++ b/src/controller/account_test.go @@ -227,6 +227,92 @@ func TestUpdateAccountHandler(t *testing.T) { }) } +func TestUpdateAccountTokensHandler(t *testing.T) { + token := generateToken("test", time.Minute) + + t.Run("Should update account tokens", func(t *testing.T) { + // Create accounts + account1 := accountV1 + account1.Domain.ID = "123-controller-update-account-tokens" + account1.Environment = "default" + accountController.CreateAccountHandler(givenAccountRequest(account1)) + + account2 := accountV1 + account2.Domain.ID = "123-controller-update-account-tokens" + account2.Environment = "staging" + accountController.CreateAccountHandler(givenAccountRequest(account2)) + + // Test + payload, _ := json.Marshal(AccountTokensRequest{ + DomainId: account1.Domain.ID, + Environments: []string{account1.Environment, account2.Environment}, + Token: "new-token", + }) + + req, _ := http.NewRequest(http.MethodPut, accountController.routeAccountPath+"/"+account1.Domain.ID, bytes.NewBuffer(payload)) + response := executeRequest(req, r, token) + + // Assert + var accountTokensResponse AccountTokensResponse + err := json.NewDecoder(response.Body).Decode(&accountTokensResponse) + + assert.Equal(t, http.StatusOK, response.Code) + assert.Nil(t, err) + + accountRepository := repository.NewAccountRepositoryMongo(mongoDb) + accountFromDb1 := accountRepository.FetchAllByDomainId(account1.Domain.ID) + + decryptedToken1, _ := utils.Decrypt(accountFromDb1[0].Token, config.GetEnv("GIT_TOKEN_PRIVATE_KEY")) + decryptedToken2, _ := utils.Decrypt(accountFromDb1[1].Token, config.GetEnv("GIT_TOKEN_PRIVATE_KEY")) + assert.Equal(t, "new-token", decryptedToken1) + assert.Equal(t, "new-token", decryptedToken2) + }) + + t.Run("Should not update account tokens - token is required", func(t *testing.T) { + // Test + payload, _ := json.Marshal(AccountTokensRequest{ + DomainId: "123-controller-update-account-tokens", + Environments: []string{"default"}, + Token: "", + }) + + req, _ := http.NewRequest(http.MethodPut, accountController.routeAccountPath+"/123-controller-update-account-tokens", bytes.NewBuffer(payload)) + response := executeRequest(req, r, token) + + // Assert + assert.Equal(t, http.StatusBadRequest, response.Code) + assert.Equal(t, "{\"error\":\"Token is required\"}", response.Body.String()) + }) + + t.Run("Should not update account tokens - invalid request", func(t *testing.T) { + // Test + payload := []byte("") + req, _ := http.NewRequest(http.MethodPut, accountController.routeAccountPath+"/invalid", bytes.NewBuffer(payload)) + response := executeRequest(req, r, token) + + // Assert + assert.Equal(t, http.StatusBadRequest, response.Code) + assert.Equal(t, "{\"error\":\"Invalid request\"}", response.Body.String()) + }) + + t.Run("Should not update account tokens - not found", func(t *testing.T) { + // Test + payload, _ := json.Marshal(AccountTokensRequest{ + DomainId: "not-found", + Environments: []string{"default"}, + Token: "new-token", + }) + + req, _ := http.NewRequest(http.MethodPut, accountController.routeAccountPath+"/not-found", bytes.NewBuffer(payload)) + response := executeRequest(req, r, token) + + // Assert + assert.Equal(t, http.StatusNotFound, response.Code) + assert.Equal(t, "{\"error\":\"Error fetching account\"}", response.Body.String()) + }) + +} + func TestDeleteAccountHandler(t *testing.T) { token := generateToken("test", time.Minute) From 7697f83e2ec00e819a39fe4ecc1668aa37f92fa4 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:46:57 -0700 Subject: [PATCH 2/2] Fixes code smells --- src/controller/account.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/controller/account.go b/src/controller/account.go index f885bc5..d150a6e 100644 --- a/src/controller/account.go +++ b/src/controller/account.go @@ -12,6 +12,10 @@ import ( "github.com/switcherapi/switcher-gitops/src/utils" ) +const ( + InvalidRequestDesc = "Invalid request" +) + type AccountController struct { coreHandler *core.CoreHandler accountRepository repository.AccountRepository @@ -62,7 +66,7 @@ func (controller *AccountController) CreateAccountHandler(w http.ResponseWriter, var accountRequest model.Account err := json.NewDecoder(r.Body).Decode(&accountRequest) if err != nil { - utils.ResponseJSON(w, ErrorResponse{Error: "Invalid request"}, http.StatusBadRequest) + utils.ResponseJSON(w, ErrorResponse{Error: InvalidRequestDesc}, http.StatusBadRequest) return } @@ -125,7 +129,7 @@ func (controller *AccountController) UpdateAccountHandler(w http.ResponseWriter, err := json.NewDecoder(r.Body).Decode(&accountRequest) if err != nil { utils.LogError("Error updating account: %s", err.Error()) - utils.ResponseJSON(w, ErrorResponse{Error: "Invalid request"}, http.StatusBadRequest) + utils.ResponseJSON(w, ErrorResponse{Error: InvalidRequestDesc}, http.StatusBadRequest) return } @@ -150,7 +154,7 @@ func (controller *AccountController) UpdateAccountTokensHandler(w http.ResponseW err := json.NewDecoder(r.Body).Decode(&accountTokensRequest) if err != nil { utils.LogError("Error updating account tokens: %s", err.Error()) - utils.ResponseJSON(w, ErrorResponse{Error: "Invalid request"}, http.StatusBadRequest) + utils.ResponseJSON(w, ErrorResponse{Error: InvalidRequestDesc}, http.StatusBadRequest) return }