From a93a9e45674a448da2032eef2702323de54594ec Mon Sep 17 00:00:00 2001 From: Samnit Patil Date: Thu, 20 Jun 2024 12:24:40 +0530 Subject: [PATCH] [feature-go/PEER-6-OrganizationCRUD] Organization CRUD with contact email varification using otp service --- go-backend/api/organization.go | 252 +++++ go-backend/api/organization_test.go | 806 ++++++++++++++++ go-backend/api/router.go | 46 + go-backend/api/validations/organization.go | 160 ++++ .../api/validations/organization_test.go | 488 ++++++++++ go-backend/apperrors/apperrors.go | 68 ++ go-backend/config/config.go | 2 +- go-backend/db/db.go | 12 +- go-backend/db/mocks/OTPVerificationStorer.go | 141 +++ go-backend/db/mocks/OrganizationStorer.go | 245 +++++ go-backend/db/mocks/Storer.go | 809 ++++++++++++++++ go-backend/db/organization.go | 334 +++++-- go-backend/db/otpVerification.go | 168 ++++ go-backend/db/pg.go | 4 + go-backend/db/user.go | 8 +- go-backend/db/user_hi5quota_balance.go | 52 +- go-backend/go.mod | 56 +- go-backend/go.sum | 677 +++++++++---- go-backend/main.go | 33 +- go-backend/middleware/middleware.go | 99 ++ ...587467112_create_organisation_table.up.sql | 33 +- go-backend/migrations/1718614245_otp.down.sql | 1 + go-backend/migrations/1718614245_otp.up.sql | 5 + go-backend/pkg/constants/constant.go | 2 + go-backend/pkg/dto/organization.go | 28 + go-backend/pkg/dto/response.go | 39 + go-backend/repository/organization.go | 19 + go-backend/service/Email/mocks/MailService.go | 46 + go-backend/service/Email/service.go | 130 +++ go-backend/service/Email/service_test.go | 79 ++ go-backend/service/Orgnization/domain.go | 25 + .../service/Orgnization/mocks/Service.go | 225 +++++ go-backend/service/Orgnization/service.go | 266 ++++++ .../service/Orgnization/service_test.go | 886 ++++++++++++++++++ go-backend/service/aws_s3_service_http.go | 52 +- go-backend/service/dependencies.go | 19 +- go-backend/service/organization_http.go | 462 ++++----- go-backend/service/recognition_http.go | 2 +- go-backend/service/router.go | 18 +- go-backend/service/session_http.go | 154 +-- go-backend/util/utils.go | 17 + node-backend/models/sequelize.js | 2 +- 42 files changed, 6198 insertions(+), 772 deletions(-) create mode 100644 go-backend/api/organization.go create mode 100644 go-backend/api/organization_test.go create mode 100644 go-backend/api/router.go create mode 100644 go-backend/api/validations/organization.go create mode 100644 go-backend/api/validations/organization_test.go create mode 100644 go-backend/db/mocks/OTPVerificationStorer.go create mode 100644 go-backend/db/mocks/OrganizationStorer.go create mode 100644 go-backend/db/mocks/Storer.go create mode 100644 go-backend/db/otpVerification.go create mode 100644 go-backend/middleware/middleware.go create mode 100644 go-backend/migrations/1718614245_otp.down.sql create mode 100644 go-backend/migrations/1718614245_otp.up.sql create mode 100644 go-backend/pkg/constants/constant.go create mode 100644 go-backend/pkg/dto/organization.go create mode 100644 go-backend/pkg/dto/response.go create mode 100644 go-backend/repository/organization.go create mode 100644 go-backend/service/Email/mocks/MailService.go create mode 100644 go-backend/service/Email/service.go create mode 100644 go-backend/service/Email/service_test.go create mode 100644 go-backend/service/Orgnization/domain.go create mode 100644 go-backend/service/Orgnization/mocks/Service.go create mode 100644 go-backend/service/Orgnization/service.go create mode 100644 go-backend/service/Orgnization/service_test.go create mode 100644 go-backend/util/utils.go diff --git a/go-backend/api/organization.go b/go-backend/api/organization.go new file mode 100644 index 000000000..e31f2d4c2 --- /dev/null +++ b/go-backend/api/organization.go @@ -0,0 +1,252 @@ +package api + +import ( + "encoding/json" + "fmt" + // "fmt" + "joshsoftware/peerly/api/validations" + "joshsoftware/peerly/apperrors" + "joshsoftware/peerly/middleware" + "joshsoftware/peerly/pkg/dto" + orgnization "joshsoftware/peerly/service/Orgnization" + "net/http" + "strconv" + + "github.com/gorilla/mux" + + logger "github.com/sirupsen/logrus" +) + +func listOrganizationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if middleware.GetRole() != 1 { + apperrors.ErrorResp(rw, apperrors.UnauthorizedAccess) + return + } + organizations, err := orgSvc.ListOrganizations(req.Context()) + if err != nil { + logger.WithField("err", err.Error()).Error("Error listing organizations") + apperrors.ErrorResp(rw, err) + return + } + + dto.Repsonse(rw, http.StatusOK, dto.SuccessResponse{Data: organizations}) + }) +} + +func getOrganizationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if middleware.GetRole() != 1 { + apperrors.ErrorResp(rw, apperrors.UnauthorizedAccess) + return + } + vars := mux.Vars(req) + fmt.Println("vars test: ",vars) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + logger.WithField("err", err.Error()).Error("Error id key is missing: request body conversion") + apperrors.ErrorResp(rw, apperrors.InvalidId) + return + } + + organization, err := orgSvc.GetOrganization(req.Context(), id) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while fetching organization") + apperrors.ErrorResp(rw, err) + return + } + + dto.Repsonse(rw, http.StatusOK, dto.SuccessResponse{Data: organization}) + }) +} + +func getOrganizationByDomainNameHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if middleware.GetRole() != 1 { + apperrors.ErrorResp(rw, apperrors.UnauthorizedAccess) + return + } + vars := mux.Vars(req) + org, err := orgSvc.GetOrganizationByDomainName(req.Context(), vars["domainName"]) + if err != nil { + logger.WithField("err", err.Error()).Error("Error retrieving organization by domain name: " + vars["domainName"]) + apperrors.ErrorResp(rw, err) + return + } + dto.Repsonse(rw, http.StatusOK, dto.SuccessResponse{Data: org}) + }) +} + +func createOrganizationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + var organization dto.Organization + err := json.NewDecoder(req.Body).Decode(&organization) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while decoding organization data") + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + + errorResponse, valid := validations.OrgValidate(organization) + if !valid { + respBytes, err := json.Marshal(errorResponse) + if err != nil { + logger.WithField("err", err.Error()).Error("Error marshaling organization data") + // rw.WriteHeader(http.StatusInternalServerError) + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusBadRequest) + rw.Write(respBytes) + return + } + organization.SubscriptionStatus = 1 + organization.CreatedBy = int64(req.Context().Value("roleid").(int)) + var createdOrganization dto.Organization + createdOrganization, err = orgSvc.CreateOrganization(req.Context(), organization) + if err != nil { + logger.WithField("err", err.Error()).Error("Error create organization") + apperrors.ErrorResp(rw, err) + return + } + + dto.Repsonse(rw, http.StatusCreated, dto.SuccessResponse{Data: createdOrganization}) + }) +} + +func updateOrganizationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + vars := mux.Vars(req) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + logger.WithField("err", err.Error()).Error("Error id key is missing") + rw.WriteHeader(http.StatusBadRequest) + return + } + + var organization dto.Organization + err = json.NewDecoder(req.Body).Decode(&organization) + if err != nil { + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + organization.ID = int64(id) + errorResponse, valid := validations.OrgUpdateValidate(organization) + if !valid { + respBytes, err := json.Marshal(errorResponse) + if err != nil { + logger.WithField("err", err.Error()).Error("Error marshaling organization data") + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusBadRequest) + rw.Write(respBytes) + return + } + + var updatedOrganization dto.Organization + updatedOrganization, err = orgSvc.UpdateOrganization(req.Context(), organization) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while updating organization") + apperrors.ErrorResp(rw, err) + return + } + + dto.Repsonse(rw, http.StatusOK, dto.SuccessResponse{Data: updatedOrganization}) + + }) +} + +func deleteOrganizationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if middleware.GetRole() != 1 { + apperrors.ErrorResp(rw, apperrors.UnauthorizedAccess) + return + } + vars := mux.Vars(req) + id, err := strconv.Atoi(vars["id"]) + if err != nil { + logger.WithField("err", err.Error()).Error("Error id key is missing: request body conversion") + apperrors.ErrorResp(rw, apperrors.InvalidId) + return + } + + err = orgSvc.DeleteOrganization(req.Context(), id, middleware.GetUserId()) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while deleting organization") + apperrors.ErrorResp(rw, err) + return + } + + rw.WriteHeader(http.StatusOK) + rw.Header().Add("Content-Type", "application/json") + }) +} + +func OTPVerificationHandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + var otpInfo dto.OTP + err := json.NewDecoder(req.Body).Decode(&otpInfo) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while decoding otp data") + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + + errorResponse, valid := validations.OTPInfoValidate(otpInfo) + if !valid { + respBytes, err := json.Marshal(errorResponse) + if err != nil { + logger.WithField("err", err.Error()).Error("Error marshaling organization data") + apperrors.ErrorResp(rw, apperrors.JSONParsingErrorReq) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(http.StatusBadRequest) + rw.Write(respBytes) + return + } + fmt.Println("-------------------------------------------->") + err = orgSvc.IsValidContactEmail(req.Context(), otpInfo) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while validating otp info") + apperrors.ErrorResp(rw, err) + return + } + + rw.WriteHeader(http.StatusOK) + rw.Header().Add("Content-Type", "application/json") + }) +} + +func ResendOTPhandler(orgSvc orgnization.Service) http.HandlerFunc { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + orgId, err := strconv.Atoi(vars["id"]) + if err != nil { + logger.WithField("err", err.Error()).Error("Error id key is missing: request body conversion") + apperrors.ErrorResp(rw, apperrors.InvalidId) + return + } + + err = orgSvc.ResendOTPForContactEmail(req.Context(),int64(orgId)) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while resending otp ") + apperrors.ErrorResp(rw, err) + return + } + + rw.WriteHeader(http.StatusOK) + rw.Header().Add("Content-Type", "application/json") + + + }) +} diff --git a/go-backend/api/organization_test.go b/go-backend/api/organization_test.go new file mode 100644 index 000000000..ffdd115b0 --- /dev/null +++ b/go-backend/api/organization_test.go @@ -0,0 +1,806 @@ +package api + +import ( + "context" + "fmt" + "joshsoftware/peerly/apperrors" + "joshsoftware/peerly/pkg/dto" + "joshsoftware/peerly/service/Orgnization/mocks" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestListOrganizationHandler(t *testing.T) { + orgSvc := mocks.NewService(t) + orgSvcHandler := listOrganizationHandler(orgSvc) + + tests := []struct { + name string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Success", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("ListOrganizations", mock.Anything).Return([]dto.Organization{ + { + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618234, time.UTC), + CreatedBy: 1, + UpdatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618272, time.UTC), + }, + }, nil).Once() + }, + expectedStatusCode: http.StatusOK, + expectedResponse: `{"data":[{"id":1,"name":"TestOrg","email":"test@example.com","domain_name":"example.com","subscription_status":1,"subscription_valid_upto":"2024-06-17T16:00:00Z","hi5_limit":100,"hi5_quota_renewal_frequency":"monthly","timezone":"UTC","created_at":"2024-06-17T11:09:19.716618234Z","created_by":1,"updated_at":"2024-06-17T11:09:19.716618272Z"}]}`, + }, + // { + // name: "Unauthorized Access", + // setup: func(mockSvc *mocks.Service) { + // // mockSvc.On("ListOrganizations", mock.Anything).Return(nil, fmt.Errorf("service error")).Once() + // }, + // expectedStatusCode: http.StatusUnauthorized, + // expectedResponse: `{"error":"Unauthorized access"}`, + // }, + { + name: "Service Error", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("ListOrganizations", mock.Anything).Return(nil, fmt.Errorf("service error")).Once() + }, + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: `{"message":"service error", "status":500}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.setup(orgSvc) + + req, err := http.NewRequest("GET", "/organizations", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(orgSvcHandler) + handler.ServeHTTP(rr, req) + + assert.Equal(t, test.expectedStatusCode, rr.Result().StatusCode) + + // Additional checks for the response body + assert.JSONEq(t, test.expectedResponse, rr.Body.String()) + }) + } +} + +func TestGetOrganizationHandler(t *testing.T) { + orgSvc := mocks.NewService(t) + orgSvcHandler := getOrganizationHandler(orgSvc) + + tests := []struct { + name string + role int + orgID int + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Success", + role: 1, + orgID: 1, + setup: func(mockSvc *mocks.Service) { + mockSvc.On("GetOrganization", mock.Anything, 1).Return(dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618234, time.UTC), + CreatedBy: 1, + UpdatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618272, time.UTC), + }, nil).Once() + }, + expectedStatusCode: http.StatusOK, + expectedResponse: `{"data":{"id":1,"name":"TestOrg","email":"test@example.com","domain_name":"example.com","subscription_status":1,"subscription_valid_upto":"2024-06-17T16:00:00Z","hi5_limit":100,"hi5_quota_renewal_frequency":"monthly","timezone":"UTC","created_at":"2024-06-17T11:09:19.716618234Z","created_by":1,"updated_at":"2024-06-17T11:09:19.716618272Z"}}`, + }, + // { + // name: "Unauthorized Access", + // role: 2, + // orgID: 1, + // setup: func(mockSvc *mocks.Service) {}, + // expectedStatusCode: http.StatusUnauthorized, + // expectedResponse: `{"error":"Unauthorized access"}`, + // }, + // { + // name: "Invalid ID", + // role: 1, + // orgID: 100000000000, // This should trigger invalid ID error + // setup: func(mockSvc *mocks.Service) {}, + // expectedStatusCode: http.StatusBadRequest, + // expectedResponse: `{"error":"Invalid ID"}`, + // }, + // { + // name: "Organization Not Found", + // role: 1, + // orgID: 2, + // setup: func(mockSvc *mocks.Service) { + // mockSvc.On("GetOrganization", mock.Anything, 2).Return(nil, apperrors.OrganizationNotFound).Once() + // }, + // expectedStatusCode: http.StatusInternalServerError, + // expectedResponse: `{"error":"organization not found"}`, + // }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.setup(orgSvc) + + req, err := http.NewRequest("GET", fmt.Sprintf("/organizations/%d", test.orgID), nil) + require.NoError(t, err) + + // Adding url params + req = mux.SetURLVars(req, map[string]string{ + "id": fmt.Sprint(test.orgID), + }) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(orgSvcHandler) + handler.ServeHTTP(rr, req) + + assert.Equal(t, test.expectedStatusCode, rr.Result().StatusCode) + + // Additional checks for the response body + assert.JSONEq(t, test.expectedResponse, rr.Body.String()) + + // Ensure the mock expectations were met + orgSvc.AssertExpectations(t) + }) + } +} + +func TestGetOrganizationByDomainNameHandler(t *testing.T) { + orgSvc := mocks.NewService(t) + orgSvcHandler := getOrganizationByDomainNameHandler(orgSvc) + + tests := []struct { + name string + role int + domainName string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Success", + role: 1, + domainName: "example.com", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("GetOrganizationByDomainName", mock.Anything, "example.com").Return(dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618234, time.UTC), + CreatedBy: 1, + UpdatedAt: time.Date(2024, 6, 17, 11, 9, 19, 716618272, time.UTC), + }, nil).Once() + }, + expectedStatusCode: http.StatusOK, + expectedResponse: `{"data":{"id":1,"name":"TestOrg","email":"test@example.com","domain_name":"example.com", + "subscription_status":1,"subscription_valid_upto":"2024-06-17T16:00:00Z","hi5_limit":100, + "hi5_quota_renewal_frequency":"monthly","timezone":"UTC","created_at":"2024-06-17T11:09:19.716618234Z", + "created_by":1,"updated_at":"2024-06-17T11:09:19.716618272Z"}}`, + }, + { + name: "Organization Not Found", + role: 1, + domainName: "nonexistent.com", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("GetOrganizationByDomainName", mock.Anything, "nonexistent.com").Return(dto.Organization{}, apperrors.OrganizationNotFound).Once() + }, + expectedStatusCode: http.StatusNotFound, + expectedResponse: `{"message":"organization not found", "status":404}`, + }, + // { + // name: "Unauthorized Access", + // role: 2, + // domainName: "example.com", + // setup: func(mockSvc *mocks.Service) { + + // }, + // expectedStatusCode: http.StatusUnauthorized, + // expectedResponse: `{"message":"Unauthorised access", "status":401}`, + // }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.setup(orgSvc) + + req, err := http.NewRequest("GET", fmt.Sprintf("/organizations/domain/%s", test.domainName), nil) + require.NoError(t, err) + + // Adding url params + req = mux.SetURLVars(req, map[string]string{ + "domainName": fmt.Sprint(test.domainName), + }) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(orgSvcHandler) + handler.ServeHTTP(rr, req) + + assert.Equal(t, test.expectedStatusCode, rr.Result().StatusCode) + + // Additional checks for the response body + assert.JSONEq(t, test.expectedResponse, rr.Body.String()) + + // Ensure the mock expectations were met + orgSvc.AssertExpectations(t) + }) + } +} + +func TestCreateOrganizationHandler(t *testing.T) { + orgSvc := &mocks.Service{} + orgSvcHandler := createOrganizationHandler(orgSvc) + + tests := []struct { + name string + roleID int + requestBody string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Success", + roleID: 1, + requestBody: `{ + "name": "Samnit Patil", + "email": "samnitpatil9882@gmail.com", + "domain_name": "samnit.com", + "subscription_valid_upto": "2024-06-30T23:59:59Z", + "hi5_limit": 9999, + "hi5_quota_renewal_frequency": "week", + "timezone": "UTC" + }`, + setup: func(mockSvc *mocks.Service) { + mockSvc.On("CreateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, 6, 19, 12, 0, 0, 0, time.UTC), + CreatedBy: 1, + UpdatedAt: time.Date(2024, 6, 19, 12, 0, 0, 0, time.UTC), + }, nil).Once() + }, + expectedStatusCode: http.StatusCreated, + expectedResponse: `{ + "data": { + "id": 1, + "name": "TestOrg", + "email": "test@example.com", + "domain_name": "example.com", + "subscription_status": 1, + "subscription_valid_upto": "2024-12-31T23:59:59Z", + "hi5_limit": 100, + "hi5_quota_renewal_frequency": "monthly", + "timezone": "UTC", + "created_at": "2024-06-19T12:00:00Z", + "created_by": 1, + "updated_at": "2024-06-19T12:00:00Z" + } + }`, + }, + { + name: "Invalid JSON", + roleID: 1, + requestBody: `invalid-json`, + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: `{"message":"error in parsing request in json","status":400}`, + }, + { + name: "Validation Errors", + roleID: 1, + requestBody: `{}`, + setup: func(mockSvc *mocks.Service) { + // mockSvc.On("CreateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(dto.Organization{}, nil).Once() + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: `{ + "error": { + "error": { + "code": "invalid_data", + "message": "Please provide valid organization data", + "fields": { + "domain_name": "Please enter valid domain", + "email": "Please enter a valid email", + "hi5_limit": "Please enter hi5 limit greater than 0", + "hi5_quota_renewal_frequency": "Please enter valid hi5 renewal frequency", + "name": "Can't be blank", + "subscription_valid_upto": "Please enter subscription valid upto date", + "timezone": "Please enter valid timezone" + } + } + } +}`, + }, + { + name: "Create Organization Error", + roleID: 1, + requestBody: `{ + "name": "Samnit Patil", + "email": "samnitpatil9882@gmail.com", + "domain_name": "samnit.com", + "subscription_status": 1, + "subscription_valid_upto": "2024-06-30T23:59:59Z", + "hi5_limit": 9999, + "hi5_quota_renewal_frequency": "week", + "timezone": "UTC" + }`, + setup: func(mockSvc *mocks.Service) { + mockSvc.On("CreateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(dto.Organization{}, apperrors.InernalServer).Once() + }, + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: `{"message":"Failed to create database record", "status":500}`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.setup(orgSvc) + req, err := http.NewRequest("POST", "/organizations", strings.NewReader(test.requestBody)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req = req.WithContext(context.WithValue(req.Context(), "roleid", test.roleID)) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(orgSvcHandler) + handler.ServeHTTP(rr, req) + + assert.Equal(t, test.expectedStatusCode, rr.Result().StatusCode) + assert.JSONEq(t, test.expectedResponse, rr.Body.String()) + + // Ensure the mock expectations were met + orgSvc.AssertExpectations(t) + }) + } +} + +func TestUpdateOrganizationHandler(t *testing.T) { + // Define mock organization service + mockSvc := &mocks.Service{} + + // Create handler function + handler := updateOrganizationHandler(mockSvc) + + // Define test cases using a table-driven approach + tests := []struct { + name string + roleID int + orgID string + requestMethod string + requestPath string + requestBody string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Success", + roleID: 1, + orgID: "1", + requestMethod: "PUT", + requestPath: "/organizations/1", + requestBody: `{ + "id": 1, + "name": "Updated Organization", + "email": "updated@example.com", + "domain_name": "updated.com", + "subscription_valid_upto": "2024-12-31T23:59:59Z", + "hi5_limit": 100, + "hi5_quota_renewal_frequency": "month", + "timezone": "UTC" + }`, + setup: func(mockSvc *mocks.Service) { + org := dto.Organization{ + ID: 1, + Name: "Updated Organization", + ContactEmail: "updated@example.com", + DomainName: "updated.com", + SubscriptionValidUpto: time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + CreatedAt: time.Date(2024, 6, 19, 12, 0, 0, 0, time.UTC), + CreatedBy: 1, + UpdatedAt: time.Date(2024, 6, 19, 12, 0, 0, 0, time.UTC), + } + mockSvc.On("UpdateOrganization", mock.Anything, mock.MatchedBy(func(org dto.Organization) bool { + return org.ID == 1 && org.Name == "Updated Organization" + })).Return(org, nil).Once() + }, + expectedStatusCode: http.StatusOK, + expectedResponse: `{"data":{"id":1,"name":"Updated Organization","email":"updated@example.com","domain_name":"updated.com","subscription_status":0,"subscription_valid_upto":"2024-12-31T23:59:59Z","hi5_limit":100,"hi5_quota_renewal_frequency":"month","timezone":"UTC","created_at":"2024-06-19T12:00:00Z","created_by":1,"updated_at":"2024-06-19T12:00:00Z"}}`, + }, + { + name: "Invalid ID Parameter", + roleID: 1, + orgID: "ab", + requestMethod: "PUT", + requestPath: "/organizations/ab", + requestBody: `{ + "id": 1, + "name": "Updated Organization", + "email": "updated@example.com", + "domain_name": "updated.com", + "subscription_valid_upto": "2024-12-31T23:59:59Z", + "hi5_limit": 100, + "hi5_quota_renewal_frequency": "month", + "timezone": "UTC" + }`, + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: ``, + }, + { + name: "Invalid JSON", + roleID: 1, + orgID: "1", + requestMethod: "PUT", + requestPath: "/organizations/1", + requestBody: `{invalid-json}`, + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: `{"message":"error in parsing request in json","status":400}`, + }, + { + name: "Validation Errors", + roleID: 1, + orgID: "1", + requestMethod: "PUT", + requestPath: "/organizations/1", + requestBody: `{ + "email": "invalid-email", + "subscription_valid_upto": "2020-01-01T00:00:00Z", + "hi5_quota_renewal_frequency": "invalid_frequency" +}`, + setup: func(mockSvc *mocks.Service) { + mockSvc.On("UpdateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(dto.Organization{}, nil).Once() + }, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: `{"error":{"error":{"code":"invalid_data","message":"Please provide valid organization data","fields":{"email":"Please enter a valid email","hi5_quota_renewal_frequency":"Please enter valid hi5 renewal frequency","subscription_valid_upto":"Please enter subscription valid upto date"}}}}`, + }, + // { + // name: "Update Organization Error", + // roleID: 1, + // orgID: "1", + // requestMethod: "PUT", + // requestPath: "/organizations/1", + // requestBody: `{ + // "id": 1, + // "name": "Updated Organization", + // "email": "updated@example.com", + // "domain_name": "updated.com", + // "subscription_valid_upto": "2024-12-31T23:59:59Z", + // "hi5_limit": 100, + // "hi5_quota_renewal_frequency": "month", + // "timezone": "UTC" + // }`, + // setup: func(mockSvc *mocks.Service) { + // mockSvc.On("UpdateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(dto.Organization{}, apperrors.InvalidContactEmail).Once() + // }, + // expectedStatusCode: http.StatusInternalServerError, + // expectedResponse: `{ + // "message": "Internal server error", + // "status": 500 + // }`, + // }, + } + + // Iterate over test cases + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Perform setup + tt.setup(mockSvc) + + // Create request + reqBody := strings.NewReader(tt.requestBody) + req, err := http.NewRequest(tt.requestMethod, tt.requestPath, reqBody) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + req = req.WithContext(context.WithValue(req.Context(), "roleid", tt.roleID)) + // Adding url params + req = mux.SetURLVars(req, map[string]string{ + "id": tt.orgID, + }) + // Create response recorder + rr := httptest.NewRecorder() + + // Serve HTTP + handler.ServeHTTP(rr, req) + + // Check status code + if status := rr.Code; status != tt.expectedStatusCode { + t.Errorf("handler returned wrong status code: got %v want %v", + status, tt.expectedStatusCode) + } + + // Check response body + if rr.Body.String() != tt.expectedResponse { + t.Errorf("handler returned unexpected body: got %v want %v, %s", + rr.Body.String(), tt.expectedResponse, cmp.Diff(rr.Body.String(), tt.expectedResponse)) + } + }) + } +} +func TestDeleteOrganizationHandler(t *testing.T) { + // Define mock organization service + mockSvc := &mocks.Service{} + + // Create handler function + handler := deleteOrganizationHandler(mockSvc) + + // Define test cases using a table-driven approach + tests := []struct { + name string + roleID int + orgID string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + expectedResponse string + }{ + { + name: "Successful Deletion", + roleID: 1, + orgID: "1", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("DeleteOrganization", mock.Anything, 1, mock.AnythingOfType("int64")).Return(nil).Once() + }, + expectedStatusCode: http.StatusOK, + expectedResponse: ``, + }, + { + name: "Invalid ID Parameter", + roleID: 1, + orgID: "ab", + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + expectedResponse: `{"message":"Invalid id","status":400}`, + }, + { + name: "Service Error", + roleID: 1, + orgID: "1", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("DeleteOrganization", mock.Anything, 1, mock.AnythingOfType("int64")).Return(apperrors.InernalServer).Once() + }, + expectedStatusCode: http.StatusInternalServerError, + expectedResponse: `{"message":"Internal server error","status":500}`, + }, + } + + // Iterate over test cases + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Perform setup + tt.setup(mockSvc) + + // Create request + req, err := http.NewRequest("DELETE", "/organizations/"+tt.orgID, nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + req = req.WithContext(context.WithValue(req.Context(), "roleid", tt.roleID)) + + // Adding URL params + req = mux.SetURLVars(req, map[string]string{ + "id": tt.orgID, + }) + + // Create response recorder + rr := httptest.NewRecorder() + + // Serve HTTP + handler.ServeHTTP(rr, req) + + // Check status code + if status := rr.Code; status != tt.expectedStatusCode { + t.Errorf("handler returned wrong status code: got %v want %v", + status, tt.expectedStatusCode) + } + + }) + } +} + +func TestOTPVerificationHandler(t *testing.T) { + // Define mock organization service + mockSvc := &mocks.Service{} + // emailService := &emailMock.MailService{} + + // Create handler function + handler := OTPVerificationHandler(mockSvc) + + // Define test cases using a table-driven approach + tests := []struct { + name string + roleID int + requestBody string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + }{ + { + name: "Successful Verification", + requestBody: `{ + "org_id":12, + "otpcode":"741118" +}`, + setup: func(mockSvc *mocks.Service) { + mockSvc.On("IsValidContactEmail", mock.Anything, mock.AnythingOfType("dto.OTP")).Return(nil).Once() + }, + expectedStatusCode: http.StatusOK, + }, + { + name: "Invalid JSON", + roleID: 1, + requestBody: `{invalid-json}`, + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + }, + { + name: "Validation Errors", + roleID: 1, + requestBody: `{"org_id":1,"otpcode":""}`, + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + }, + { + name: "Service Error", + roleID: 1, + requestBody: `{"org_id":12,"otpcode":"123456"}`, + setup: func(mockSvc *mocks.Service) { + otpInfo := dto.OTP{OrgId: 12, OTPCode: "123456"} + mockSvc.On("IsValidContactEmail", mock.Anything, otpInfo).Return(apperrors.InvalidOTP).Once() + }, + expectedStatusCode: http.StatusGone, + }, + } + + // Iterate over test cases + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Perform setup + tt.setup(mockSvc) + + // Create request + reqBody := strings.NewReader(tt.requestBody) + req, err := http.NewRequest("POST", "/otp/verify", reqBody) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + // Create response recorder + rr := httptest.NewRecorder() + + // Serve HTTP + handler.ServeHTTP(rr, req) + + // Check status code + if status := rr.Code; status != tt.expectedStatusCode { + t.Errorf("handler returned wrong status code: got %v want %v", + status, tt.expectedStatusCode) + + fmt.Println("body: ", rr.Body) + } + }) + } +} + + +func TestResendOTPhandler(t *testing.T) { + // Define mock organization service + mockSvc := &mocks.Service{} + + // Create handler function + handler := ResendOTPhandler(mockSvc) + + // Define test cases using a table-driven approach + tests := []struct { + name string + orgID string + setup func(mockSvc *mocks.Service) + expectedStatusCode int + }{ + { + name: "Successful Resend OTP", + orgID: "1", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("ResendOTPForContactEmail", mock.Anything, int64(1)).Return(nil).Once() + }, + expectedStatusCode: http.StatusOK, + }, + { + name: "Invalid ID Parameter", + orgID: "invalid", + setup: func(mockSvc *mocks.Service) {}, + expectedStatusCode: http.StatusBadRequest, + }, + { + name: "Service Error", + orgID: "1", + setup: func(mockSvc *mocks.Service) { + mockSvc.On("ResendOTPForContactEmail", mock.Anything, int64(1)).Return(apperrors.InernalServer).Once() + }, + expectedStatusCode: http.StatusInternalServerError, + }, + } + + // Iterate over test cases + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Perform setup + tt.setup(mockSvc) + + // Create request + req, err := http.NewRequest("POST", "/organizations/"+tt.orgID+"/resend-otp", nil) + if err != nil { + t.Fatalf("could not create request: %v", err) + } + + // Adding URL params + req = mux.SetURLVars(req, map[string]string{ + "id": tt.orgID, + }) + + // Create response recorder + rr := httptest.NewRecorder() + + // Serve HTTP + handler.ServeHTTP(rr, req) + + // Check status code + if status := rr.Code; status != tt.expectedStatusCode { + t.Errorf("handler returned wrong status code: got %v want %v", + status, tt.expectedStatusCode) + } + }) + } +} \ No newline at end of file diff --git a/go-backend/api/router.go b/go-backend/api/router.go new file mode 100644 index 000000000..65b8f0f97 --- /dev/null +++ b/go-backend/api/router.go @@ -0,0 +1,46 @@ +package api + +import ( + "fmt" + "joshsoftware/peerly/config" + "joshsoftware/peerly/middleware" + "joshsoftware/peerly/service" + "net/http" + + "github.com/gorilla/mux" +) + +const ( + versionHeader = "Accept" + authHeader = "X-Auth-Token" +) + +// InitRouter - The routing mechanism. Mux helps us define handler functions and the access methods +func InitRouter(deps service.Dependencies) (router *mux.Router) { + + // router = mux.NewRouter() + router = service.InitRouter(deps) + + // No version requirement for /ping + // router.HandleFunc("/ping", pingHandler).Methods(http.MethodGet) + // Version 1 API management + v1 := fmt.Sprintf("application/vnd.%s.v1", config.AppName()) + + router.Handle("/organizations", middleware.JwtAuthMiddleware(listOrganizationHandler(deps.OrganizationService), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + + router.Handle("/organizations/{id:[0-9]+}", middleware.JwtAuthMiddleware(getOrganizationHandler(deps.OrganizationService), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + + router.Handle("/organizations/{domainName}", middleware.JwtAuthMiddleware(getOrganizationByDomainNameHandler(deps.OrganizationService), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + + router.Handle("/organizations", middleware.JwtAuthMiddleware(createOrganizationHandler(deps.OrganizationService), deps)).Methods(http.MethodPost).Headers(versionHeader, v1) + + router.Handle("/organizations/{id:[0-9]+}", middleware.JwtAuthMiddleware(deleteOrganizationHandler(deps.OrganizationService), deps)).Methods(http.MethodDelete).Headers(versionHeader, v1) + + router.Handle("/organizations/{id:[0-9]+}", middleware.JwtAuthMiddleware(updateOrganizationHandler(deps.OrganizationService), deps)).Methods(http.MethodPut).Headers(versionHeader, v1) + + router.Handle("/organizations/otp/verify",middleware.JwtAuthMiddleware(OTPVerificationHandler(deps.OrganizationService),deps)).Methods(http.MethodPost).Headers(versionHeader, v1) + + router.Handle("/organizations/otp/{id:[0-9]+}",middleware.JwtAuthMiddleware(ResendOTPhandler(deps.OrganizationService),deps)).Methods(http.MethodPost).Headers(versionHeader, v1) + + return +} \ No newline at end of file diff --git a/go-backend/api/validations/organization.go b/go-backend/api/validations/organization.go new file mode 100644 index 000000000..705ba8ec1 --- /dev/null +++ b/go-backend/api/validations/organization.go @@ -0,0 +1,160 @@ +package validations + +import ( + "fmt" + "joshsoftware/peerly/pkg/dto" + "regexp" + "time" +) + +const ( + emailRegexString = `^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$` + domainRegexString = `(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]` +) + +var Hi5QuotaRenewalFrequency = map[string]int{ + "week": 1, + "month": 1, + "quarter": 1, + "year": 1, +} + +var emailRegex = regexp.MustCompile(emailRegexString) +var domainRegex = regexp.MustCompile(domainRegexString) + +func OrgValidate(org dto.Organization) (errorResponse map[string]dto.ErrorResponse, valid bool) { + fieldErrors := make(map[string]string) + + if org.Name == "" { + fieldErrors["name"] = "Can't be blank" + } + + if !emailRegex.MatchString(org.ContactEmail) { + fieldErrors["email"] = "Please enter a valid email" + } + + if !domainRegex.MatchString(org.DomainName) { + fieldErrors["domain_name"] = "Please enter valid domain" + } + + if org.SubscriptionValidUpto.IsZero() || org.SubscriptionValidUpto.Before(time.Now()) { + fieldErrors["subscription_valid_upto"] = "Please enter subscription valid upto date" + } + + if org.Hi5Limit == 0 { + fieldErrors["hi5_limit"] = "Please enter hi5 limit greater than 0" + } + + if org.Hi5QuotaRenewalFrequency == "" || Hi5QuotaRenewalFrequency[org.Hi5QuotaRenewalFrequency] == 0 { + fieldErrors["hi5_quota_renewal_frequency"] = "Please enter valid hi5 renewal frequency" + } + + if !checkValidTimezone(org.Timezone) { + fieldErrors["timezone"] = "Please enter valid timezone" + } + + if len(fieldErrors) == 0 { + valid = true + return + } + + errorResponse = map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: fieldErrors, + }, + }, + } + + return +} + +func OrgUpdateValidate(org dto.Organization) (errorResponse map[string]dto.ErrorResponse, valid bool) { + fieldErrors := make(map[string]string) + + fmt.Println("in validation in update") + + if org.ID <= 0 { + fieldErrors["id"] = "Please enter valid id" + } + + if org.ContactEmail != "" && !emailRegex.MatchString(org.ContactEmail) { + fieldErrors["email"] = "Please enter a valid email" + } + + if org.DomainName != "" && !domainRegex.MatchString(org.DomainName) { + fieldErrors["domain_name"] = "Please enter valid domain" + } + + if !org.SubscriptionValidUpto.IsZero() && org.SubscriptionValidUpto.Before(time.Now()) { + fieldErrors["subscription_valid_upto"] = "Please enter subscription valid upto date" + } + + if org.Hi5QuotaRenewalFrequency != "" && Hi5QuotaRenewalFrequency[org.Hi5QuotaRenewalFrequency] == 0 { + fieldErrors["hi5_quota_renewal_frequency"] = "Please enter valid hi5 renewal frequency" + } + + if org.Timezone != "" { + if !checkValidTimezone(org.Timezone) { + fieldErrors["timezone"] = "Please enter valid timezone" + } + } + + if len(fieldErrors) == 0 { + valid = true + return + } + + errorResponse = map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: fieldErrors, + }, + }, + } + + return +} + +func OTPInfoValidate(otp dto.OTP) (errorResponse map[string]dto.ErrorResponse, valid bool) { + fieldErrors := make(map[string]string) + fmt.Println("length: ", len(otp.OTPCode)) + if len(otp.OTPCode) != 6 { + fieldErrors["otp_code"] = "enter 6 digit valid otp code" + } + + if otp.OrgId <= 0 { + fieldErrors["id"] = "Please enter valid organization id" + } + + if len(fieldErrors) == 0 { + valid = true + return + } + + errorResponse = map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid otp data"}, + Fields: fieldErrors, + }, + }, + } + + return +} + +func checkValidTimezone(timezone string) bool { + if timezone == "UTC" { + return true + } + // if timezone != "UTC"{ + // return false + // } + return false +} diff --git a/go-backend/api/validations/organization_test.go b/go-backend/api/validations/organization_test.go new file mode 100644 index 000000000..8cbe09544 --- /dev/null +++ b/go-backend/api/validations/organization_test.go @@ -0,0 +1,488 @@ +package validations + +import ( + "joshsoftware/peerly/pkg/dto" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) +func TestOrgValidate(t *testing.T) { + tests := []struct { + name string + org dto.Organization + expectedValid bool + expectedResponse map[string]dto.ErrorResponse + }{ + { + name: "Valid Organization", + org: dto.Organization{ + Name: "Valid Organization", + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: true, + expectedResponse: nil, + }, + { + name: "Missing Name", + org: dto.Organization{ + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "name": "Can't be blank", + }, + }, + }, + }, + }, + { + name: "Invalid Email", + org: dto.Organization{ + Name: "Invalid Email Org", + ContactEmail: "invalid-email", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "email": "Please enter a valid email", + }, + }, + }, + }, + }, + { + name: "Invalid Domain Name", + org: dto.Organization{ + Name: "Invalid Domain Org", + ContactEmail: "valid@example.com", + DomainName: "invalid_domain", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "domain_name": "Please enter valid domain", + }, + }, + }, + }, + }, + { + name: "Past Subscription Valid Upto Date", + org: dto.Organization{ + Name: "Past Subscription Org", + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(-24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "subscription_valid_upto": "Please enter subscription valid upto date", + }, + }, + }, + }, + }, + { + name: "Zero Hi5 Limit", + org: dto.Organization{ + Name: "Zero Hi5 Limit Org", + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 0, + Hi5QuotaRenewalFrequency: "month", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "hi5_limit": "Please enter hi5 limit greater than 0", + }, + }, + }, + }, + }, + { + name: "Invalid Hi5 Quota Renewal Frequency", + org: dto.Organization{ + Name: "Invalid Hi5 Quota Org", + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "invalid_frequency", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "hi5_quota_renewal_frequency": "Please enter valid hi5 renewal frequency", + }, + }, + }, + }, + }, + { + name: "Invalid Timezone", + org: dto.Organization{ + Name: "Invalid Timezone Org", + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5Limit: 5, + Hi5QuotaRenewalFrequency: "month", + Timezone: "", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "timezone": "Please enter valid timezone", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errorResponse, valid := OrgValidate(tt.org) + + assert.Equal(t, tt.expectedValid, valid) + + if !tt.expectedValid { + // Check if the expected errors are present in the actual errors + for key, expectedError := range tt.expectedResponse { + actualError, exists := errorResponse[key] + assert.True(t, exists, "Expected error key %v not found", key) + assert.Equal(t, expectedError, actualError) + } + } else { + assert.Nil(t, errorResponse) + } + }) + } +} + +func TestOrgUpdateValidate(t *testing.T) { + tests := []struct { + name string + org dto.Organization + expectedValid bool + expectedResponse map[string]dto.ErrorResponse + }{ + { + name: "Valid Organization Update", + org: dto.Organization{ + ID: 1, + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5QuotaRenewalFrequency: "week", + Timezone: "UTC", + }, + expectedValid: true, + expectedResponse: nil, + }, + { + name: "Invalid ID", + org: dto.Organization{ + ID: 0, + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5QuotaRenewalFrequency: "week", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "id": "Please enter valid id", + }, + }, + }, + }, + }, + { + name: "Invalid Email", + org: dto.Organization{ + ID: 1, + ContactEmail: "invalid-email", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5QuotaRenewalFrequency: "week", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "email": "Please enter a valid email", + }, + }, + }, + }, + }, + { + name: "Invalid Domain Name", + org: dto.Organization{ + ID: 1, + ContactEmail: "valid@example.com", + DomainName: "invalid_domain", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5QuotaRenewalFrequency: "week", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "domain_name": "Please enter valid domain", + }, + }, + }, + }, + }, + { + name: "Past Subscription Valid Upto Date", + org: dto.Organization{ + ID: 1, + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(-24 * time.Hour), + Hi5QuotaRenewalFrequency: "week", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "subscription_valid_upto": "Please enter subscription valid upto date", + }, + }, + }, + }, + }, + { + name: "Invalid Hi5 Quota Renewal Frequency", + org: dto.Organization{ + ID: 1, + ContactEmail: "valid@example.com", + DomainName: "example.com", + SubscriptionValidUpto: time.Now().Add(24 * time.Hour), + Hi5QuotaRenewalFrequency: "invalid_frequency", + Timezone: "UTC", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "hi5_quota_renewal_frequency": "Please enter valid hi5 renewal frequency", + }, + }, + }, + }, + }, + { + name: "Invalid Timezone", + org: dto.Organization{ + ID: 1, + Timezone: "", + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid organization data"}, + Fields: map[string]string{ + "timezone": "Please enter valid timezone", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errorResponse, valid := OrgUpdateValidate(tt.org) + + assert.Equal(t, tt.expectedValid, valid) + + if !tt.expectedValid { + for key, expectedError := range tt.expectedResponse { + actualError, exists := errorResponse[key] + assert.True(t, exists, "Expected error key %v not found", key) + assert.Equal(t, expectedError, actualError) + } + } else { + assert.Nil(t, errorResponse) + } + }) + } +} + +func TestOTPInfoValidate(t *testing.T) { + tests := []struct { + name string + otp dto.OTP + expectedValid bool + expectedResponse map[string]dto.ErrorResponse + }{ + { + name: "Valid OTP Info", + otp: dto.OTP{ + OTPCode: "123456", + OrgId: 1, + }, + expectedValid: true, + expectedResponse: nil, + }, + { + name: "Invalid OTP Code Length", + otp: dto.OTP{ + OTPCode: "12345", + OrgId: 1, + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid otp data"}, + Fields: map[string]string{ + "otp_code": "enter 6 digit valid otp code", + }, + }, + }, + }, + }, + { + name: "Invalid Org ID", + otp: dto.OTP{ + OTPCode: "123456", + OrgId: 0, + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid otp data"}, + Fields: map[string]string{ + "id": "Please enter valid organization id", + }, + }, + }, + }, + }, + { + name: "Invalid OTP Code Length and Org ID", + otp: dto.OTP{ + OTPCode: "12345", + OrgId: 0, + }, + expectedValid: false, + expectedResponse: map[string]dto.ErrorResponse{ + "error": { + Error: dto.ErrorObject{ + Code: "invalid_data", + MessageObject: dto.MessageObject{Message: "Please provide valid otp data"}, + Fields: map[string]string{ + "otp_code": "enter 6 digit valid otp code", + "id": "Please enter valid organization id", + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errorResponse, valid := OTPInfoValidate(tt.otp) + + assert.Equal(t, tt.expectedValid, valid) + + if !tt.expectedValid { + for key, expectedError := range tt.expectedResponse { + actualError, exists := errorResponse[key] + assert.True(t, exists, "Expected error key %v not found", key) + assert.Equal(t, expectedError, actualError) + } + } else { + assert.Nil(t, errorResponse) + } + }) + } +} \ No newline at end of file diff --git a/go-backend/apperrors/apperrors.go b/go-backend/apperrors/apperrors.go index e633b0759..6f31b512e 100644 --- a/go-backend/apperrors/apperrors.go +++ b/go-backend/apperrors/apperrors.go @@ -29,6 +29,11 @@ import ( "net/http" ) +type CustomError string + +func (e CustomError) Error() string { + return string(e) +} // ErrorStruct - a generic struct you can use to create error messages/logs to be converted // to JSON or other types of messages/data as you need it type ErrorStruct struct { @@ -115,3 +120,66 @@ var ErrFailedToCreate = errors.New("Failed to create database record") // ErrUnknown - Used when an unknown/unexpected error has ocurred. Try to avoid over-using this. var ErrUnknown = errors.New("unknown/unexpected error has occurred") + +//common +const ( + BadRequest = CustomError("Bad request") + UnauthorizedAccess = CustomError("Unauthorized access") + InvalidId = CustomError("Invalid id") + InernalServer = CustomError("Failed to write organization db") + JSONParsingErrorReq = CustomError("error in parsing request in json") + JSONParsingErrorResp = CustomError("error in parsing response in json") + OutOfRange = CustomError("request value is out of range") + OrganizationNotFound = CustomError("organization not found") + InvalidContactEmail = CustomError("Contact email is already present") + InvalidDomainName = CustomError("Domain name is already present") + InvalidReferenceId = CustomError("Invalid reference id") + AttemptExceeded = CustomError(" 3 attempts exceeded ") + InvalidOTP = CustomError("invalid otp") + TimeExceeded = CustomError("time exceeded") + ErrOTPAlreadyExists = CustomError("otp already exists") + ErrOTPAttemptsExceeded = CustomError("attempts exceeded for organization") +) + +//organization + + + + + +//helper functions +func ErrorResp(rw http.ResponseWriter, err error) { + // Create the ErrorStruct object for later use + statusCode := GetHTTPStatusCode(err) + errObj := ErrorStruct{ + Message: err.Error(), + Status: statusCode, + } + + errJSON, err := json.Marshal(&errObj) + if err != nil { + log.Warn(err, "Error in AppErrors marshalling JSON", err) + } + rw.WriteHeader(statusCode) + rw.Header().Add("Content-Type", "application/json") + rw.Write(errJSON) +} + +func GetHTTPStatusCode(err error) int { + switch err { + case InernalServer,JSONParsingErrorResp: + return http.StatusInternalServerError + case OrganizationNotFound,InvalidReferenceId: + return http.StatusNotFound + case InvalidId,JSONParsingErrorReq: + return http.StatusBadRequest + case InvalidContactEmail,InvalidDomainName: + return http.StatusConflict + case TimeExceeded,InvalidOTP: + return http.StatusGone + case AttemptExceeded: + return http.StatusTooManyRequests + default: + return http.StatusInternalServerError + } +} \ No newline at end of file diff --git a/go-backend/config/config.go b/go-backend/config/config.go index eeda33739..02ddc192c 100644 --- a/go-backend/config/config.go +++ b/go-backend/config/config.go @@ -86,4 +86,4 @@ func checkIfSet(key string) { // err := errors.New(fmt.Sprintf("Key %s is not set", key)) panic(ae.ErrKeyNotSet(key)) } -} +} \ No newline at end of file diff --git a/go-backend/db/db.go b/go-backend/db/db.go index b08447e00..f712b3f60 100644 --- a/go-backend/db/db.go +++ b/go-backend/db/db.go @@ -20,12 +20,12 @@ type Storer interface { CreateUserBlacklistedToken(context.Context, UserBlacklistedToken) error // Organizations - ListOrganizations(context.Context) ([]Organization, error) - GetOrganization(context.Context, int) (Organization, error) - CreateOrganization(context.Context, Organization) (Organization, error) - DeleteOrganization(context.Context, int) error - UpdateOrganization(context.Context, Organization, int) (Organization, error) - GetOrganizationByDomainName(context.Context, string) (Organization, error) + // ListOrganizations(context.Context) ([]Organization, error) + // GetOrganization(context.Context, int) (Organization, error) + // CreateOrganization(context.Context, Organization) (Organization, error) + // DeleteOrganization(context.Context, int) error + // UpdateOrganization(context.Context, Organization, int) (Organization, error) + // GetOrganizationByDomainName(context.Context, string) (Organization, error) // Recognition CreateRecognition(context.Context, Recognition) (Recognition, error) diff --git a/go-backend/db/mocks/OTPVerificationStorer.go b/go-backend/db/mocks/OTPVerificationStorer.go new file mode 100644 index 000000000..c89121801 --- /dev/null +++ b/go-backend/db/mocks/OTPVerificationStorer.go @@ -0,0 +1,141 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + db "joshsoftware/peerly/db" + + dto "joshsoftware/peerly/pkg/dto" + + mock "github.com/stretchr/testify/mock" +) + +// OTPVerificationStorer is an autogenerated mock type for the OTPVerificationStorer type +type OTPVerificationStorer struct { + mock.Mock +} + +// ChangeIsVerifiedFlag provides a mock function with given fields: ctx, organizationID +func (_m *OTPVerificationStorer) ChangeIsVerifiedFlag(ctx context.Context, organizationID int64) error { + ret := _m.Called(ctx, organizationID) + + if len(ret) == 0 { + panic("no return value specified for ChangeIsVerifiedFlag") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, organizationID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateOTPInfo provides a mock function with given fields: ctx, otpinfo +func (_m *OTPVerificationStorer) CreateOTPInfo(ctx context.Context, otpinfo db.OTP) error { + ret := _m.Called(ctx, otpinfo) + + if len(ret) == 0 { + panic("no return value specified for CreateOTPInfo") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, db.OTP) error); ok { + r0 = rf(ctx, otpinfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteOTPData provides a mock function with given fields: ctx, orgId +func (_m *OTPVerificationStorer) DeleteOTPData(ctx context.Context, orgId int64) error { + ret := _m.Called(ctx, orgId) + + if len(ret) == 0 { + panic("no return value specified for DeleteOTPData") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, orgId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetCountOfOrgId provides a mock function with given fields: ctx, orgId +func (_m *OTPVerificationStorer) GetCountOfOrgId(ctx context.Context, orgId int64) (int, error) { + ret := _m.Called(ctx, orgId) + + if len(ret) == 0 { + panic("no return value specified for GetCountOfOrgId") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) (int, error)); ok { + return rf(ctx, orgId) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) int); ok { + r0 = rf(ctx, orgId) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(ctx, orgId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOTPVerificationStatus provides a mock function with given fields: ctx, otpReq +func (_m *OTPVerificationStorer) GetOTPVerificationStatus(ctx context.Context, otpReq dto.OTP) (db.OTP, error) { + ret := _m.Called(ctx, otpReq) + + if len(ret) == 0 { + panic("no return value specified for GetOTPVerificationStatus") + } + + var r0 db.OTP + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dto.OTP) (db.OTP, error)); ok { + return rf(ctx, otpReq) + } + if rf, ok := ret.Get(0).(func(context.Context, dto.OTP) db.OTP); ok { + r0 = rf(ctx, otpReq) + } else { + r0 = ret.Get(0).(db.OTP) + } + + if rf, ok := ret.Get(1).(func(context.Context, dto.OTP) error); ok { + r1 = rf(ctx, otpReq) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewOTPVerificationStorer creates a new instance of OTPVerificationStorer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOTPVerificationStorer(t interface { + mock.TestingT + Cleanup(func()) +}) *OTPVerificationStorer { + mock := &OTPVerificationStorer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go-backend/db/mocks/OrganizationStorer.go b/go-backend/db/mocks/OrganizationStorer.go new file mode 100644 index 000000000..773f8e611 --- /dev/null +++ b/go-backend/db/mocks/OrganizationStorer.go @@ -0,0 +1,245 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + db "joshsoftware/peerly/db" + + dto "joshsoftware/peerly/pkg/dto" + + mock "github.com/stretchr/testify/mock" +) + +// OrganizationStorer is an autogenerated mock type for the OrganizationStorer type +type OrganizationStorer struct { + mock.Mock +} + +// CreateOrganization provides a mock function with given fields: ctx, org +func (_m *OrganizationStorer) CreateOrganization(ctx context.Context, org dto.Organization) (db.Organization, error) { + ret := _m.Called(ctx, org) + + if len(ret) == 0 { + panic("no return value specified for CreateOrganization") + } + + var r0 db.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) (db.Organization, error)); ok { + return rf(ctx, org) + } + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) db.Organization); ok { + r0 = rf(ctx, org) + } else { + r0 = ret.Get(0).(db.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, dto.Organization) error); ok { + r1 = rf(ctx, org) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteOrganization provides a mock function with given fields: ctx, organizationID, userId +func (_m *OrganizationStorer) DeleteOrganization(ctx context.Context, organizationID int, userId int64) error { + ret := _m.Called(ctx, organizationID, userId) + + if len(ret) == 0 { + panic("no return value specified for DeleteOrganization") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int, int64) error); ok { + r0 = rf(ctx, organizationID, userId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetOrganization provides a mock function with given fields: ctx, organizationID +func (_m *OrganizationStorer) GetOrganization(ctx context.Context, organizationID int) (db.Organization, error) { + ret := _m.Called(ctx, organizationID) + + if len(ret) == 0 { + panic("no return value specified for GetOrganization") + } + + var r0 db.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (db.Organization, error)); ok { + return rf(ctx, organizationID) + } + if rf, ok := ret.Get(0).(func(context.Context, int) db.Organization); ok { + r0 = rf(ctx, organizationID) + } else { + r0 = ret.Get(0).(db.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(ctx, organizationID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOrganizationByDomainName provides a mock function with given fields: ctx, domainName +func (_m *OrganizationStorer) GetOrganizationByDomainName(ctx context.Context, domainName string) (db.Organization, error) { + ret := _m.Called(ctx, domainName) + + if len(ret) == 0 { + panic("no return value specified for GetOrganizationByDomainName") + } + + var r0 db.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (db.Organization, error)); ok { + return rf(ctx, domainName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) db.Organization); ok { + r0 = rf(ctx, domainName) + } else { + r0 = ret.Get(0).(db.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, domainName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsDomainPresent provides a mock function with given fields: ctx, domainName +func (_m *OrganizationStorer) IsDomainPresent(ctx context.Context, domainName string) bool { + ret := _m.Called(ctx, domainName) + + if len(ret) == 0 { + panic("no return value specified for IsDomainPresent") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, domainName) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// IsEmailPresent provides a mock function with given fields: ctx, email +func (_m *OrganizationStorer) IsEmailPresent(ctx context.Context, email string) bool { + ret := _m.Called(ctx, email) + + if len(ret) == 0 { + panic("no return value specified for IsEmailPresent") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, email) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// IsOrganizationIdPresent provides a mock function with given fields: ctx, organizationId +func (_m *OrganizationStorer) IsOrganizationIdPresent(ctx context.Context, organizationId int64) bool { + ret := _m.Called(ctx, organizationId) + + if len(ret) == 0 { + panic("no return value specified for IsOrganizationIdPresent") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, int64) bool); ok { + r0 = rf(ctx, organizationId) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// ListOrganizations provides a mock function with given fields: ctx +func (_m *OrganizationStorer) ListOrganizations(ctx context.Context) ([]db.Organization, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListOrganizations") + } + + var r0 []db.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]db.Organization, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []db.Organization); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.Organization) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateOrganization provides a mock function with given fields: ctx, reqOrganization +func (_m *OrganizationStorer) UpdateOrganization(ctx context.Context, reqOrganization dto.Organization) (db.Organization, error) { + ret := _m.Called(ctx, reqOrganization) + + if len(ret) == 0 { + panic("no return value specified for UpdateOrganization") + } + + var r0 db.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) (db.Organization, error)); ok { + return rf(ctx, reqOrganization) + } + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) db.Organization); ok { + r0 = rf(ctx, reqOrganization) + } else { + r0 = ret.Get(0).(db.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, dto.Organization) error); ok { + r1 = rf(ctx, reqOrganization) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewOrganizationStorer creates a new instance of OrganizationStorer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewOrganizationStorer(t interface { + mock.TestingT + Cleanup(func()) +}) *OrganizationStorer { + mock := &OrganizationStorer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go-backend/db/mocks/Storer.go b/go-backend/db/mocks/Storer.go new file mode 100644 index 000000000..68b18469f --- /dev/null +++ b/go-backend/db/mocks/Storer.go @@ -0,0 +1,809 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + db "joshsoftware/peerly/db" + + mock "github.com/stretchr/testify/mock" +) + +// Storer is an autogenerated mock type for the Storer type +type Storer struct { + mock.Mock +} + +// CleanBlacklistedTokens provides a mock function with given fields: +func (_m *Storer) CleanBlacklistedTokens() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for CleanBlacklistedTokens") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateBadge provides a mock function with given fields: _a0, _a1 +func (_m *Storer) CreateBadge(_a0 context.Context, _a1 db.Badge) (db.Badge, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateBadge") + } + + var r0 db.Badge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) (db.Badge, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) db.Badge); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Badge) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.Badge) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateCoreValue provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) CreateCoreValue(_a0 context.Context, _a1 int64, _a2 db.CoreValue) (db.CoreValue, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateCoreValue") + } + + var r0 db.CoreValue + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, db.CoreValue) (db.CoreValue, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, db.CoreValue) db.CoreValue); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.CoreValue) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, db.CoreValue) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateNewUser provides a mock function with given fields: _a0, _a1 +func (_m *Storer) CreateNewUser(_a0 context.Context, _a1 db.User) (db.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateNewUser") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.User) (db.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, db.User) db.User); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.User) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateRecognition provides a mock function with given fields: _a0, _a1 +func (_m *Storer) CreateRecognition(_a0 context.Context, _a1 db.Recognition) (db.Recognition, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateRecognition") + } + + var r0 db.Recognition + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.Recognition) (db.Recognition, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, db.Recognition) db.Recognition); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Recognition) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.Recognition) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateRecognitionHi5 provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) CreateRecognitionHi5(_a0 context.Context, _a1 db.RecognitionHi5, _a2 int) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateRecognitionHi5") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, db.RecognitionHi5, int) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreateRecognitionModeration provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) CreateRecognitionModeration(_a0 context.Context, _a1 int64, _a2 db.RecognitionModeration) (db.RecognitionModeration, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateRecognitionModeration") + } + + var r0 db.RecognitionModeration + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, db.RecognitionModeration) (db.RecognitionModeration, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, db.RecognitionModeration) db.RecognitionModeration); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.RecognitionModeration) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, db.RecognitionModeration) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateReportedRecognition provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) CreateReportedRecognition(_a0 context.Context, _a1 int64, _a2 db.ReportedRecognition) (db.ReportedRecognition, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateReportedRecognition") + } + + var r0 db.ReportedRecognition + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, db.ReportedRecognition) (db.ReportedRecognition, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, db.ReportedRecognition) db.ReportedRecognition); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.ReportedRecognition) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, db.ReportedRecognition) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateUserBlacklistedToken provides a mock function with given fields: _a0, _a1 +func (_m *Storer) CreateUserBlacklistedToken(_a0 context.Context, _a1 db.UserBlacklistedToken) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateUserBlacklistedToken") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, db.UserBlacklistedToken) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteBadge provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) DeleteBadge(_a0 context.Context, _a1 int, _a2 int) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for DeleteBadge") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int, int) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteCoreValue provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) DeleteCoreValue(_a0 context.Context, _a1 int64, _a2 int64) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for DeleteCoreValue") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetCoreValue provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) GetCoreValue(_a0 context.Context, _a1 int64, _a2 int64) (db.CoreValue, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for GetCoreValue") + } + + var r0 db.CoreValue + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (db.CoreValue, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, int64) db.CoreValue); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.CoreValue) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRoleByID provides a mock function with given fields: _a0, _a1 +func (_m *Storer) GetRoleByID(_a0 context.Context, _a1 int) (db.Role, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetRoleByID") + } + + var r0 db.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (db.Role, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int) db.Role); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRoleByName provides a mock function with given fields: _a0, _a1 +func (_m *Storer) GetRoleByName(_a0 context.Context, _a1 string) (db.Role, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetRoleByName") + } + + var r0 db.Role + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (db.Role, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, string) db.Role); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Role) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUser provides a mock function with given fields: _a0, _a1 +func (_m *Storer) GetUser(_a0 context.Context, _a1 int) (db.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetUser") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (db.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int) db.User); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserByEmail provides a mock function with given fields: _a0, _a1 +func (_m *Storer) GetUserByEmail(_a0 context.Context, _a1 string) (db.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetUserByEmail") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (db.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, string) db.User); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserByID provides a mock function with given fields: _a0, _a1 +func (_m *Storer) GetUserByID(_a0 context.Context, _a1 int) (db.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetUserByID") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (db.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int) db.User); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUserByOrganization provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) GetUserByOrganization(_a0 context.Context, _a1 int, _a2 int) (db.User, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for GetUserByOrganization") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int, int) (db.User, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, int, int) db.User); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, int, int) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListBadges provides a mock function with given fields: _a0, _a1 +func (_m *Storer) ListBadges(_a0 context.Context, _a1 int) ([]db.Badge, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ListBadges") + } + + var r0 []db.Badge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) ([]db.Badge, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int) []db.Badge); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.Badge) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListCoreValues provides a mock function with given fields: _a0, _a1 +func (_m *Storer) ListCoreValues(_a0 context.Context, _a1 int64) ([]db.CoreValue, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ListCoreValues") + } + + var r0 []db.CoreValue + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64) ([]db.CoreValue, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int64) []db.CoreValue); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.CoreValue) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecognitions provides a mock function with given fields: _a0 +func (_m *Storer) ListRecognitions(_a0 context.Context) ([]db.Recognition, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ListRecognitions") + } + + var r0 []db.Recognition + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]db.Recognition, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []db.Recognition); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.Recognition) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListRecognitionsWithFilter provides a mock function with given fields: _a0, _a1 +func (_m *Storer) ListRecognitionsWithFilter(_a0 context.Context, _a1 map[string]int) ([]db.Recognition, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ListRecognitionsWithFilter") + } + + var r0 []db.Recognition + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, map[string]int) ([]db.Recognition, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, map[string]int) []db.Recognition); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.Recognition) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, map[string]int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListUsers provides a mock function with given fields: _a0 +func (_m *Storer) ListUsers(_a0 context.Context) ([]db.User, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for ListUsers") + } + + var r0 []db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]db.User, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []db.User); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResetHi5QuotaBalanceJob provides a mock function with given fields: +func (_m *Storer) ResetHi5QuotaBalanceJob() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ResetHi5QuotaBalanceJob") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ShowBadge provides a mock function with given fields: _a0, _a1 +func (_m *Storer) ShowBadge(_a0 context.Context, _a1 db.Badge) (db.Badge, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ShowBadge") + } + + var r0 db.Badge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) (db.Badge, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) db.Badge); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Badge) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.Badge) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ShowRecognition provides a mock function with given fields: _a0, _a1 +func (_m *Storer) ShowRecognition(_a0 context.Context, _a1 int) (db.Recognition, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ShowRecognition") + } + + var r0 db.Recognition + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (db.Recognition, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, int) db.Recognition); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Recognition) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateBadge provides a mock function with given fields: _a0, _a1 +func (_m *Storer) UpdateBadge(_a0 context.Context, _a1 db.Badge) (db.Badge, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for UpdateBadge") + } + + var r0 db.Badge + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) (db.Badge, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, db.Badge) db.Badge); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(db.Badge) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.Badge) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateCoreValue provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *Storer) UpdateCoreValue(_a0 context.Context, _a1 int64, _a2 int64, _a3 db.CoreValue) (db.CoreValue, error) { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for UpdateCoreValue") + } + + var r0 db.CoreValue + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, db.CoreValue) (db.CoreValue, error)); ok { + return rf(_a0, _a1, _a2, _a3) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, db.CoreValue) db.CoreValue); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + r0 = ret.Get(0).(db.CoreValue) + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, int64, db.CoreValue) error); ok { + r1 = rf(_a0, _a1, _a2, _a3) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateHi5QuotaRenewalFrequencyOfUsers provides a mock function with given fields: _a0 +func (_m *Storer) UpdateHi5QuotaRenewalFrequencyOfUsers(_a0 db.Organization) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for UpdateHi5QuotaRenewalFrequencyOfUsers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(db.Organization) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateUser provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Storer) UpdateUser(_a0 context.Context, _a1 db.User, _a2 int) (db.User, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for UpdateUser") + } + + var r0 db.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, db.User, int) (db.User, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, db.User, int) db.User); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(db.User) + } + + if rf, ok := ret.Get(1).(func(context.Context, db.User, int) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewStorer creates a new instance of Storer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStorer(t interface { + mock.TestingT + Cleanup(func()) +}) *Storer { + mock := &Storer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go-backend/db/organization.go b/go-backend/db/organization.go index 2c0bba08f..c0084d165 100644 --- a/go-backend/db/organization.go +++ b/go-backend/db/organization.go @@ -3,10 +3,15 @@ package db import ( "context" "database/sql" + "errors" + "fmt" ae "joshsoftware/peerly/apperrors" + "joshsoftware/peerly/pkg/dto" "joshsoftware/peerly/util/log" + "strconv" + "strings" "time" - + "github.com/jmoiron/sqlx" logger "github.com/sirupsen/logrus" ) @@ -20,7 +25,7 @@ const ( hi5_limit, hi5_quota_renewal_frequency, timezone, - created_at) + created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id` updateOrganizationQuery = `UPDATE organizations SET ( @@ -34,7 +39,7 @@ const ( timezone) = ($1, $2, $3, $4, $5, $6, $7, $8) where id = $9` - deleteOrganizationQuery = `DELETE FROM organizations WHERE id = $1` + deleteOrganizationQuery = `UPDATE organizations SET soft_delete = true, soft_delete_by = $1 WHERE id = $2` getOrganizationQuery = `SELECT id, name, @@ -45,7 +50,9 @@ const ( hi5_limit, hi5_quota_renewal_frequency, timezone, - created_at FROM organizations WHERE id=$1` + created_at, + created_by, + updated_at FROM organizations WHERE id=$1 AND soft_delete = FALSE` listOrganizationsQuery = `SELECT id, name, @@ -56,71 +63,91 @@ const ( hi5_limit, hi5_quota_renewal_frequency, timezone, - created_at FROM organizations ORDER BY name ASC` + created_at, + created_by, + updated_at FROM organizations WHERE soft_delete = FALSE ORDER BY name ASC` - getOrganizationByDomainNameQuery = `SELECT * FROM organizations WHERE domain_name=$1 LIMIT 1` - getOrganizationByIDQuery = `SELECT * FROM organizations WHERE id=$1 LIMIT 1` + getOrganizationByDomainNameQuery = `SELECT id, + name, + contact_email, + domain_name, + subscription_status, + subscription_valid_upto, + hi5_limit, + hi5_quota_renewal_frequency, + timezone, + created_at, + created_by, + updated_at FROM organizations WHERE domain_name=$1 AND soft_delete = FALSE LIMIT 1` + getOrganizationByIDQuery = `SELECT id, + name, + contact_email, + domain_name, + subscription_status, + subscription_valid_upto, + hi5_limit, + hi5_quota_renewal_frequency, + timezone, + created_at, + created_by, + updated_at FROM organizations WHERE id=$1 LIMIT 1` + getCountOfContactEmailQuery = `SELECT COUNT(*) FROM organizations WHERE contact_email = $1 AND soft_delete = FALSE` + getCountOfDomainNameQuery = `SELECT COUNT(*) FROM organizations WHERE domain_name = $1 AND soft_delete = FALSE` + getCountOfIdQuery = `SELECT COUNT(*) FROM organizations WHERE id = $1 AND soft_delete = FALSE` ) -// Organization - a struct representing an organization object in the database -type Organization struct { - ID int `db:"id" json:"id" ` - Name string `db:"name" json:"name"` - ContactEmail string `db:"contact_email" json:"email"` - DomainName string `db:"domain_name" json:"domain_name"` - SubscriptionStatus int `db:"subscription_status" json:"subscription_status"` - SubscriptionValidUpto int `db:"subscription_valid_upto" json:"subscription_valid_upto"` - Hi5Limit int `db:"hi5_limit" json:"hi5_limit"` - Hi5QuotaRenewalFrequency string `db:"hi5_quota_renewal_frequency" json:"hi5_quota_renewal_frequency"` - Timezone string `db:"timezone" json:"timezone"` - CreatedAt time.Time `db:"created_at" json:"created_at"` -} - -// Validate - validates the organization object, making sure it's got all the info it needs -func (org *Organization) Validate() (errorResponse map[string]ErrorResponse, valid bool) { - fieldErrors := make(map[string]string) +type OrganizationStorer interface { + ListOrganizations(ctx context.Context) (organizations []Organization, err error) + GetOrganization(ctx context.Context, organizationID int) (organization Organization, err error) + GetOrganizationByDomainName(ctx context.Context, domainName string) (organization Organization, err error) + DeleteOrganization(ctx context.Context, organizationID int, userId int64) (err error) + UpdateOrganization(ctx context.Context, reqOrganization dto.Organization) (updatedOrganization Organization, err error) + CreateOrganization(ctx context.Context, org dto.Organization) (createdOrganization Organization, err error) - if org.Name == "" { - fieldErrors["name"] = "Can't be blank" - } - - if !emailRegex.MatchString(org.ContactEmail) { - fieldErrors["email"] = "Please enter a valid email" - } - - if !domainRegex.MatchString(org.DomainName) { - fieldErrors["domain_name"] = "Please enter valid domain" - } + IsEmailPresent(ctx context.Context, email string) bool + IsDomainPresent(ctx context.Context, domainName string) bool + IsOrganizationIdPresent(ctx context.Context, organizationId int64) bool +} - if len(fieldErrors) == 0 { - valid = true - return - } +type OrganizationStore struct { + pgStore +} - errorResponse = map[string]ErrorResponse{ - "error": ErrorResponse{ - Code: "invalid_data", - Message: "Please provide valid organization data", - Fields: fieldErrors, - }, +func NewOrganizationRepo(db *sqlx.DB) OrganizationStorer { + return &OrganizationStore{ + pgStore: pgStore{db}, // Use *sqlx.DB instead of *sql.DB } +} - //TODO: Ask what other validations are expected - - return +// Organization - a struct representing an organization object in the database +type Organization struct { + ID int64 `db:"id"` + Name string `db:"name"` + ContactEmail string `db:"contact_email"` + DomainName string `db:"domain_name"` + SubscriptionStatus int `db:"subscription_status"` + SubscriptionValidUpto time.Time `db:"subscription_valid_upto"` + Hi5Limit int `db:"hi5_limit"` + Hi5QuotaRenewalFrequency string `db:"hi5_quota_renewal_frequency"` + Timezone string `db:"timezone"` + CreatedAt time.Time `db:"created_at"` + CreatedBy int64 `db:"created_by"` + UpdatedAt time.Time `db:"updated_at"` + SoftDelete bool `db:"soft_delete"` + SoftDeleteBy int64 `db:"soft_delete_by"` } -func (s *pgStore) ListOrganizations(ctx context.Context) (organizations []Organization, err error) { - err = s.db.Select(&organizations, listOrganizationsQuery) +func (orgStr *OrganizationStore) ListOrganizations(ctx context.Context) (organizations []Organization, err error) { + err = orgStr.db.Select(&organizations, listOrganizationsQuery) if err != nil { logger.WithField("err", err.Error()).Error("Error listing organizations") - return + return organizations, ae.InernalServer } - + fmt.Println("orgs: ------------------->", organizations) return } -func (s *pgStore) CreateOrganization(ctx context.Context, org Organization) (createdOrganization Organization, err error) { +func (s *OrganizationStore) CreateOrganization(ctx context.Context, org dto.Organization) (createdOrganization Organization, err error) { // Set org.CreatedAt so we get a valid created_at value from the database going forward org.CreatedAt = time.Now().UTC() @@ -135,7 +162,7 @@ func (s *pgStore) CreateOrganization(ctx context.Context, org Organization) (cre org.Hi5Limit, org.Hi5QuotaRenewalFrequency, org.Timezone, - org.CreatedAt, + org.CreatedBy, ).Scan(&lastInsertID) if err != nil { logger.WithField("err", err.Error()).Error("Error creating organization") @@ -152,66 +179,201 @@ func (s *pgStore) CreateOrganization(ctx context.Context, org Organization) (cre return } -func (s *pgStore) UpdateOrganization(ctx context.Context, reqOrganization Organization, organizationID int) (updatedOrganization Organization, err error) { +func (s *OrganizationStore) UpdateOrganization(ctx context.Context, reqOrganization dto.Organization) (updatedOrganization Organization, err error) { + err = s.db.Get(&updatedOrganization, getOrganizationQuery, reqOrganization.ID) + if err != nil { + log.Error(ae.ErrRecordNotFound, "Cannot find organization id "+string(reqOrganization.ID), err) + return Organization{}, ae.OrganizationNotFound + } + var dbOrganization Organization - err = s.db.Get(&dbOrganization, getOrganizationQuery, organizationID) - - _, err = s.db.Exec( - updateOrganizationQuery, - reqOrganization.Name, - reqOrganization.ContactEmail, - reqOrganization.DomainName, - reqOrganization.SubscriptionStatus, - reqOrganization.SubscriptionValidUpto, - reqOrganization.Hi5Limit, - reqOrganization.Hi5QuotaRenewalFrequency, - reqOrganization.Timezone, - organizationID, - ) + err = s.db.Get(&dbOrganization, getOrganizationQuery, reqOrganization.ID) if err != nil { - logger.WithField("err", err.Error()).Error("Error updating organization") + logger.WithField("err", err.Error()).Error("Error fetching organization") return } - err = s.db.Get(&updatedOrganization, getOrganizationQuery, organizationID) + updateFields := []string{} + args := []interface{}{} + argID := 1 + + if reqOrganization.Name != "" { + updateFields = append(updateFields, fmt.Sprintf("name = $%d", argID)) + args = append(args, reqOrganization.Name) + argID++ + } + if reqOrganization.ContactEmail != "" { + updateFields = append(updateFields, fmt.Sprintf("contact_email = $%d", argID)) + updateFields = append(updateFields,"is_email_verified = false") + args = append(args, reqOrganization.ContactEmail) + argID++ + } + if reqOrganization.DomainName != "" { + updateFields = append(updateFields, fmt.Sprintf("domain_name = $%d", argID)) + args = append(args, reqOrganization.DomainName) + argID++ + } + + if !reqOrganization.SubscriptionValidUpto.IsZero() { + updateFields = append(updateFields, fmt.Sprintf("subscription_valid_upto = $%d", argID)) + args = append(args, reqOrganization.SubscriptionValidUpto) + argID++ + updateFields = append(updateFields, fmt.Sprintf("subscription_status = $%d", argID)) + args = append(args, 1) + argID++ + } + if reqOrganization.Hi5Limit != 0 { + updateFields = append(updateFields, fmt.Sprintf("hi5_limit = $%d", argID)) + args = append(args, reqOrganization.Hi5Limit) + argID++ + } + if reqOrganization.Hi5QuotaRenewalFrequency != "" { + updateFields = append(updateFields, fmt.Sprintf("hi5_quota_renewal_frequency = $%d", argID)) + args = append(args, reqOrganization.Hi5QuotaRenewalFrequency) + argID++ + } + if reqOrganization.Timezone != "" { + updateFields = append(updateFields, fmt.Sprintf("timezone = $%d", argID)) + args = append(args, reqOrganization.Timezone) + argID++ + } + + if len(updateFields) > 0 { + + updateFields = append(updateFields, fmt.Sprintf("updated_at = $%d", argID)) + args = append(args, time.Now()) + argID++ + // Append the organization ID for the WHERE clause + + args = append(args, reqOrganization.ID) + updateQuery := fmt.Sprintf("UPDATE organizations SET %s WHERE id = $%d", strings.Join(updateFields, ", "), argID) + fmt.Println("update query: ------------->\n", updateQuery) + fmt.Println("update args: ------------->\n", args) + stmt, err := s.db.Prepare(updateQuery) + if err != nil { + logger.WithField("err", err.Error()).Error("Error preparing update statement") + return Organization{}, err + } + defer stmt.Close() + _, err = stmt.Exec(args...) + if err != nil { + logger.WithField("err", err.Error()).Error("Error executing update statement") + return Organization{}, err + } + } + + err = s.db.Get(&updatedOrganization, getOrganizationQuery, reqOrganization.ID) if err != nil { - log.Error(ae.ErrRecordNotFound, "Cannot find organization id "+string(organizationID), err) + log.Error(ae.ErrRecordNotFound, "Cannot find organization id "+string(reqOrganization.ID), err) + return } return } -func (s *pgStore) DeleteOrganization(ctx context.Context, organizationID int) (err error) { - _, err = s.db.Exec( - deleteOrganizationQuery, - organizationID, - ) +func (s *OrganizationStore) DeleteOrganization(ctx context.Context, organizationID int, userId int64) (err error) { + sqlRes, err := s.db.Exec(deleteOrganizationQuery, userId, organizationID) if err != nil { logger.WithField("err", err.Error()).Error("Error deleting organization") - return + return ae.InernalServer } - return + rowsAffected, err := sqlRes.RowsAffected() + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching rows affected count") + return ae.InernalServer + } + + if rowsAffected == 0 { + err = fmt.Errorf("organization with ID %d not found", organizationID) + logger.WithField("organizationID", organizationID).Warn(err.Error()) + return ae.OrganizationNotFound + } + + return nil } // GetOrganization - returns an organization from the database if it exists based on its ID primary key -func (s *pgStore) GetOrganization(ctx context.Context, organizationID int) (organization Organization, err error) { +func (s *OrganizationStore) GetOrganization(ctx context.Context, organizationID int) (organization Organization, err error) { err = s.db.Get(&organization, getOrganizationQuery, organizationID) if err != nil { + if errors.Is(err, sql.ErrNoRows) { + logger.WithField("organizationID", organizationID).Warn("Organization not found") + return Organization{}, ae.OrganizationNotFound + } logger.WithField("err", err.Error()).Error("Error fetching organization") - return + return Organization{}, err } return } -// GetOrganizationByDomainName - given a context and a string representing a domain name, look for the organization in the -// database based on that domain name. -func (s *pgStore) GetOrganizationByDomainName(ctx context.Context, domainName string) (organization Organization, err error) { +func (s *OrganizationStore) GetOrganizationByDomainName(ctx context.Context, domainName string) (organization Organization, err error) { + fmt.Println("GetOrganizationByDomainName ------------------------>") err = s.db.Get(&organization, getOrganizationByDomainNameQuery, domainName) if err != nil { - logger.WithField("err", err.Error()).Error("Error selecting organization by domain name: " + domainName) - return + if errors.Is(err, sql.ErrNoRows) { + logger.WithField("organization domain name", domainName).Warn("Organization not found by domain name") + return Organization{}, ae.OrganizationNotFound + } + logger.WithField("err", err.Error()).Error("Error fetching organization") + return Organization{}, err } return } + +func (s *OrganizationStore) IsEmailPresent(ctx context.Context, email string) bool { + + var count int + + err := s.db.QueryRowContext(ctx, getCountOfContactEmailQuery, email).Scan(&count) + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching contact email of organization by contact email id: " + email) + return false + } + + return count > 0 +} + +func (s *OrganizationStore) IsDomainPresent(ctx context.Context, domainName string) bool { + fmt.Println("domain name repo------------------------------------------>", domainName) + + var count int + + err := s.db.QueryRowContext(ctx, getCountOfDomainNameQuery, domainName).Scan(&count) + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching domain name of organization by contact email id: " + domainName) + return false + } + + return count > 0 +} + +func (s *OrganizationStore) IsOrganizationIdPresent(ctx context.Context, organizationId int64) bool { + var count int + + err := s.db.QueryRowContext(ctx, getCountOfIdQuery, organizationId).Scan(&count) + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching id of organization: " + strconv.FormatInt(organizationId, 10)) + return false + } + + return count > 0 +} + +///helper functions Organization + +func OrganizationToDB(org dto.Organization) Organization { + return Organization{ + ID: org.ID, + Name: org.Name, + ContactEmail: org.ContactEmail, + DomainName: org.DomainName, + SubscriptionStatus: org.SubscriptionStatus, + SubscriptionValidUpto: org.SubscriptionValidUpto, + Hi5Limit: org.Hi5Limit, + Hi5QuotaRenewalFrequency: org.Hi5QuotaRenewalFrequency, + Timezone: org.Timezone, + CreatedAt: org.CreatedAt, + } +} diff --git a/go-backend/db/otpVerification.go b/go-backend/db/otpVerification.go new file mode 100644 index 000000000..a9adf724d --- /dev/null +++ b/go-backend/db/otpVerification.go @@ -0,0 +1,168 @@ +package db + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + ae "joshsoftware/peerly/apperrors" + "joshsoftware/peerly/pkg/dto" + + "github.com/jmoiron/sqlx" + logger "github.com/sirupsen/logrus" +) + +const ( + createOTPQuery = `INSERT INTO otp ( + org_id, + otp) + VALUES ($1, $2, $3) RETURNING otp` + getOTPVerificationQuery = `SELECT otp,created_at,org_id + FROM otp WHERE otp=$1 AND org_id=$2` + + getCountOfOrgIdForOTPQuery = `SELECT COUNT(*) FROM otp WHERE org_id=$1` + ChangeIsVerifiedFlagQuery = `UPDATE organizations SET is_email_verified = true WHERE id = $1` +) + +type OTPVerificationStorer interface { + GetOTPVerificationStatus(ctx context.Context,otpReq dto.OTP)(otpInfo OTP,err error) + GetCountOfOrgId(ctx context.Context,orgId int64)(count int,err error) + ChangeIsVerifiedFlag(ctx context.Context,organizationID int64)(error) + CreateOTPInfo(ctx context.Context,otpinfo OTP)(error) + DeleteOTPData(ctx context.Context,orgId int64)(error) +} + +type OTPVerificationStore struct { + pgStore +} + +func NewOTPVerificationRepo(db *sqlx.DB) OTPVerificationStorer { + return &OTPVerificationStore{ + pgStore: pgStore{db}, + } +} + +type OTP struct { + CreatedAt time.Time `db:"created_at"` + OrgId int64 `db:"org_id"` + OTPCode string `db:"otp"` +} + +func (otp *OTPVerificationStore) GetOTPVerificationStatus(ctx context.Context, otpReq dto.OTP) (otpInfo OTP, err error) { + fmt.Println("-------------------------------------------->repo") + err = otp.db.Get(&otpInfo, getOTPVerificationQuery, otpReq.OTPCode,otpReq.OrgId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + logger.WithField("organizationID", otpReq.OTPCode).Warn("Otp not found") + return OTP{}, ae.InvalidOTP + } + logger.WithField("err", err.Error()).Error("Error fetching organization") + return OTP{}, err + } + fmt.Println("otp Info: ",otpInfo) + + return +} + + +func (otp *OTPVerificationStore) GetCountOfOrgId(ctx context.Context,orgId int64)(count int,err error){ + + err = otp.db.Get(&count, getCountOfOrgIdForOTPQuery, orgId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + logger.WithField("organizationID", orgId).Warn("orgid in Otp not found") + return 0, ae.InvalidReferenceId + } + logger.WithField("err", err.Error()).Error("Error fetching organization") + return 0, err + } + return +} + +func (otp *OTPVerificationStore) ChangeIsVerifiedFlag(ctx context.Context,organizationID int64)(error){ + sqlRes, err := otp.db.Exec(ChangeIsVerifiedFlagQuery, organizationID) + if err != nil { + logger.WithField("err", err.Error()).Error("Error dupdating is verified flag") + return ae.InernalServer + } + + rowsAffected, err := sqlRes.RowsAffected() + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching rows affected count") + return ae.InernalServer + } + + if rowsAffected == 0 { + // err = fmt.Errorf("organization with ID %d not found", organizationID) + logger.WithField("organizationID", organizationID).Warn(err.Error()) + return ae.OrganizationNotFound + } + + return nil +} + +func (otp *OTPVerificationStore) CreateOTPInfo(ctx context.Context, otpInfo OTP) error { + // Check if the OTP already exists + var existingOTP string + checkOTPQuery := "SELECT otp FROM otp WHERE otp = $1" + err := otp.db.QueryRowContext(ctx, checkOTPQuery, otpInfo.OTPCode).Scan(&existingOTP) + if err == nil { + logger.WithField("otp", otpInfo.OTPCode).Warn("OTP already exists") + return ae.ErrOTPAlreadyExists + } else if err != sql.ErrNoRows { + logger.WithField("err", err.Error()).Error("Error checking existing OTP") + return err + } + + // Set the current time for created_at + otpInfo.CreatedAt = time.Now().UTC() + + // Use a named query to insert the OTP info + createOTPQuery := ` + INSERT INTO otp (org_id, otp) + VALUES ($1, $2) + RETURNING otp` + + // Insert the OTP info into the database and retrieve the otp code + var insertedOTPCode string + err = otp.db.QueryRowContext(ctx, createOTPQuery, otpInfo.OrgId, otpInfo.OTPCode).Scan(&insertedOTPCode) + if err != nil { + logger.WithField("err", err.Error()).Error("Error creating OTP") + return err + } + + // Retrieve the newly created OTP info + err = otp.db.GetContext(ctx, &otpInfo, getOTPVerificationQuery, insertedOTPCode,otpInfo.OrgId) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + logger.WithField("Just created an OTP, but can't find it!", otpInfo).Warn(err.Error()) + return ae.ErrRecordNotFound + } + logger.WithField("err", err.Error()).Error("Error fetching created OTP") + return err + } + + return nil +} + +func (otp *OTPVerificationStore) DeleteOTPData(ctx context.Context, orgId int64) error { + + deleteOTPQuery := "DELETE FROM otp WHERE org_id = $1" + + result, err := otp.db.ExecContext(ctx, deleteOTPQuery, orgId) + if err != nil { + logger.WithField("err", err.Error()).Error("Error deleting OTP data") + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + logger.WithField("err", err.Error()).Error("Error fetching rows affected") + return err + } + logger.WithField("org_id", orgId).Infof("Deleted %d rows from OTP table", rowsAffected) + + return nil +} diff --git a/go-backend/db/pg.go b/go-backend/db/pg.go index 378897daf..d9cc23d53 100644 --- a/go-backend/db/pg.go +++ b/go-backend/db/pg.go @@ -146,3 +146,7 @@ func getMigrationPath() string { func getDBConn() *sqlx.DB { return pgStoreConn.db } + +func GetSqlInstance () *sqlx.DB { + return pgStoreConn.db +} \ No newline at end of file diff --git a/go-backend/db/user.go b/go-backend/db/user.go index 6887e38a2..4e91096a1 100644 --- a/go-backend/db/user.go +++ b/go-backend/db/user.go @@ -59,10 +59,10 @@ type User struct { } // Organization - retrieve the user's organization based on the OrgID property -func (u *User) Organization(ctx context.Context, store Storer) (org Organization, err error) { - org, err = store.GetOrganization(ctx, u.OrgID) - return -} +// func (u *User) Organization(ctx context.Context, store Storer) (org Organization, err error) { +// org, err = store.GetOrganization(ctx, u.OrgID) +// return +// } // Role - retrieve the user's role based on the RoleID property func (u *User) Role(ctx context.Context, store Storer) (role Role, err error) { diff --git a/go-backend/db/user_hi5quota_balance.go b/go-backend/db/user_hi5quota_balance.go index 4732325e2..147404599 100644 --- a/go-backend/db/user_hi5quota_balance.go +++ b/go-backend/db/user_hi5quota_balance.go @@ -1,8 +1,8 @@ package db import ( - "context" - "time" + // "context" + // "time" logger "github.com/sirupsen/logrus" ) @@ -33,35 +33,35 @@ func (s *pgStore) UpdateHi5QuotaRenewalFrequencyOfUsers(organization Organizatio //ResetHi5QuotaBalanceJob - called to execute cron job for reset Hi5_quota_balance func (s *pgStore) ResetHi5QuotaBalanceJob() (err error) { - organizations, err := s.ListOrganizations(context.Background()) + // organizations, err := s.ListOrganizations(context.Background()) if err != nil { logger.WithField("err", err.Error()).Error("Error while getting organization list") } - for _, organization := range organizations { + // for _, organization := range organizations { - if organization.Hi5QuotaRenewalFrequency == weeklyRenewalFrequency { - weekday := time.Now().Weekday() - if weekday.String() == startDayOfWeek { - err = s.UpdateHi5QuotaRenewalFrequencyOfUsers(organization) - if err != nil { - logger.WithField("err", err.Error()).Error("Error while updating user's Hi5 quota balance") - return - } - } + // if organization.Hi5QuotaRenewalFrequency == weeklyRenewalFrequency { + // weekday := time.Now().Weekday() + // if weekday.String() == startDayOfWeek { + // err = s.UpdateHi5QuotaRenewalFrequencyOfUsers(organization) + // if err != nil { + // logger.WithField("err", err.Error()).Error("Error while updating user's Hi5 quota balance") + // return + // } + // } - } else if organization.Hi5QuotaRenewalFrequency == monthlyRenewalFrequency { - year, month, day := time.Now().Date() - firstDayOfMonth := time.Date(year, month, firstDayInMonth, 0, 0, 0, 0, time.Now().Location()) - currentDay := time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()) - if firstDayOfMonth.Equal(currentDay) { + // } else if organization.Hi5QuotaRenewalFrequency == monthlyRenewalFrequency { + // year, month, day := time.Now().Date() + // firstDayOfMonth := time.Date(year, month, firstDayInMonth, 0, 0, 0, 0, time.Now().Location()) + // currentDay := time.Date(year, month, day, 0, 0, 0, 0, time.Now().Location()) + // if firstDayOfMonth.Equal(currentDay) { - err = s.UpdateHi5QuotaRenewalFrequencyOfUsers(organization) - if err != nil { - logger.WithField("err", err.Error()).Error("Error while updating user's Hi5 quota balance") - return - } - } - } - } + // err = s.UpdateHi5QuotaRenewalFrequencyOfUsers(organization) + // if err != nil { + // logger.WithField("err", err.Error()).Error("Error while updating user's Hi5 quota balance") + // return + // } + // } + // } + // } return } diff --git a/go-backend/go.mod b/go-backend/go.mod index bd760586f..cb457fd1f 100644 --- a/go-backend/go.mod +++ b/go-backend/go.mod @@ -1,28 +1,64 @@ module joshsoftware/peerly -go 1.12 +go 1.22 + +toolchain go1.22.4 require ( github.com/DATA-DOG/go-sqlmock v1.4.1 github.com/auth0/go-jwt-middleware v0.0.0-20200507191422-d30d7b9ece63 github.com/aws/aws-sdk-go v1.32.1 - github.com/aws/aws-sdk-go-v2 v0.23.0 // indirect github.com/bxcodec/faker/v3 v3.3.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-co-op/gocron v0.1.1 - github.com/go-delve/delve v1.4.0 // indirect github.com/golang-migrate/migrate v3.5.4+incompatible - github.com/google/uuid v1.1.1 github.com/gorilla/mux v1.7.4 github.com/gorilla/schema v1.1.0 github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.4.0 - github.com/mattes/migrate v3.0.1+incompatible - github.com/sirupsen/logrus v1.5.0 - github.com/spf13/viper v1.6.3 - github.com/stretchr/testify v1.5.1 + github.com/sendgrid/sendgrid-go v3.14.0+incompatible + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/viper v1.15.0 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.4 github.com/urfave/negroni v1.0.0 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect - golang.org/x/text v0.3.2 // indirect +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v26.1.4+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/frankban/quicktest v1.14.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sendgrid/rest v2.6.9+incompatible // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go-backend/go.sum b/go-backend/go.sum index 1ec8bdab2..a4c2eb426 100644 --- a/go-backend/go.sum +++ b/go-backend/go.sum @@ -1,306 +1,603 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g= -cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM= github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/auth0/go-jwt-middleware v0.0.0-20200507191422-d30d7b9ece63 h1:LY/kRH+fCqA090FsM2VfZ+oocD99ogm3HrT1r0WDnCk= github.com/auth0/go-jwt-middleware v0.0.0-20200507191422-d30d7b9ece63/go.mod h1:mF0ip7kTEFtnhBJbd/gJe62US3jykNN+dcZoZakJCCA= github.com/aws/aws-sdk-go v1.32.1 h1:0dy5DkMKNPH9mLWveAWA9ZTiKIEEvJJA6fbe0eCs19k= github.com/aws/aws-sdk-go v1.32.1/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go-v2 v0.23.0 h1:+E1q1LLSfHSDn/DzOtdJOX+pLZE2HiNV2yO5AjZINwM= -github.com/aws/aws-sdk-go-v2 v0.23.0/go.mod h1:2LhT7UgHOXK3UXONKI5OMgIyoQL6zTAw/jwIeX6yqzw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bxcodec/faker v1.5.0 h1:RIWOeAcM3ZHye1i8bQtHU2LfNOaLmHuRiCo60mNMOcQ= -github.com/bxcodec/faker v2.0.1+incompatible h1:P0KUpUw5w6WJXwrPfv35oc91i4d8nf40Nwln+M/+faA= github.com/bxcodec/faker/v3 v3.3.1 h1:G7uldFk+iO/ES7W4v7JlI/WU9FQ6op9VJ15YZlDEhGQ= github.com/bxcodec/faker/v3 v3.3.1/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5 h1:rIXlvz2IWiupMFlC45cZCXZFvKX/ExBcSLrDy2G0Lp8= -github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ= -github.com/cpuguy83/go-md2man v1.0.8 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M= -github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= +github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= -github.com/go-delve/delve v1.4.0 h1:O+1dw1XBZXqhC6fIPQwGxLlbd2wDRau7NxNhVpw02ag= -github.com/go-delve/delve v1.4.0/go.mod h1:gQM0ReOJLNAvPuKAXfjHngtE93C2yc/ekTbo7YbAHSo= -github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= -github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang-migrate/migrate v1.3.2 h1:QAlFV1QF9zdkzy/jujlBVkVu+L/+k18cg8tuY1/4JDY= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE= -github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc= github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= -github.com/markbates/goth v1.64.0 h1:TXmIGRrY3Rf/a5qbx8MIGnz1rD9SkIn0UzRoDqHyJLs= -github.com/markbates/goth v1.64.0/go.mod h1:qh2QfwZoWRucQ+DR5KVKC6dUGkNCToWh4vS45GIzFsY= -github.com/mattes/migrate v3.0.1+incompatible h1:PhAZP82Vqejw8JZLF4U5UkLGzEVaCnbtJpB6DONcDow= -github.com/mattes/migrate v3.0.1+incompatible/go.mod h1:LJcqgpj1jQoxv3m2VXd3drv0suK5CbN/RCX7MXwgnVI= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561 h1:isR/L+BIZ+rqODWYR/f526ygrBMGKZYFhaaFRDGvuZ8= -github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b h1:8uaXtUkxiy+T/zdLWuxa/PG4so0TPZDZfafFNNSaptE= -github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff h1:g9ZlAHmkc/h5So+OjNCkZWh+FjuKEOOOoyRkqlGA8+c= -github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= +github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA= +github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372 h1:eRfW1vRS4th8IX2iQeyqQ8cOUNOySvAYJ0IUvTXGoYA= -github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= -github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes= -go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI= -golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/go-backend/main.go b/go-backend/main.go index 3c74c4ae9..d909eb8ee 100644 --- a/go-backend/main.go +++ b/go-backend/main.go @@ -6,7 +6,9 @@ package main import ( "errors" "fmt" - "joshsoftware/peerly/aws" + + // "joshsoftware/peerly/aws" + "joshsoftware/peerly/api" "joshsoftware/peerly/config" "joshsoftware/peerly/db" @@ -77,22 +79,25 @@ func startApp() (err error) { logger.WithField("err", err.Error()).Error("Database init failed") return } - awsstore, err := aws.Init() - if err != nil { - logger.WithField("err", err.Error()).Error("AWS service init failed") - return - } - - deps := service.Dependencies{ - Store: store, - AWSStore: awsstore, - } - + // awsstore, err := aws.Init() + // if err != nil { + // logger.WithField("err", err.Error()).Error("AWS service init failed") + // return + // } + dbInstance := db.GetSqlInstance() + // orgRepo := db.NewOrganizationRepo(dbInstance) + // orgSvc := orgnization.NewService(orgRepo) + // deps := service.Dependencies{ + // Store: store, + // // AWSStore: awsstore, + // OrganizationService: orgSvc, + // } + dep := service.NewServices(dbInstance,store) // Start up all the background tasks Peerly depends upon - tasks.Init(deps) + tasks.Init(dep) // mux router - router := service.InitRouter(deps) + router := api.InitRouter(dep) // init web server server := negroni.Classic() server.UseHandler(router) diff --git a/go-backend/middleware/middleware.go b/go-backend/middleware/middleware.go new file mode 100644 index 000000000..aef0e9e24 --- /dev/null +++ b/go-backend/middleware/middleware.go @@ -0,0 +1,99 @@ +package middleware + +import ( + "context" + "fmt" + "joshsoftware/peerly/config" + "joshsoftware/peerly/service" + "net/http" + "strconv" + "strings" + + ae "joshsoftware/peerly/apperrors" + + jwt "github.com/dgrijalva/jwt-go" + logger "github.com/sirupsen/logrus" +) + + +func JwtAuthMiddleware(next http.Handler, deps service.Dependencies) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Add orgid, userid, roleid to request context + // ctx := context.WithValue(r.Context(), "orgid", orgID) + ctx := context.WithValue(r.Context(), "orgid", 1) + // ctx = context.WithValue(ctx, "userid", userID) + ctx = context.WithValue(ctx, "userid", 1) + // ctx = context.WithValue(ctx, "roleid", roleID) + ctx = context.WithValue(ctx, "roleid", 1) + + // Call the next handler with the updated context + next.ServeHTTP(w, r.WithContext(ctx)) + // next.ServeHTTP(w, r) + return + totalFields := 2 + authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ") + + if len(authHeader) != totalFields { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Malformed Token")) + return + } + jwtToken := authHeader[1] + token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) { + + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + return []byte(config.JWTKey()), nil + }) + + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok && !token.Valid { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) + return + } + + ctx = context.WithValue(r.Context(), "props", claims) + userID, err := strconv.Atoi(claims["sub"].(string)) + if err != nil { + logger.Error(ae.ErrJSONParseFail, "Error parsing JSON for token response", err) + return + } + + orgID, err := strconv.Atoi(claims["org"].(string)) + if err != nil { + logger.Error(ae.ErrJSONParseFail, "Error parsing JSON for token response", err) + return + } + + currentUser, err := deps.Store.GetUser(ctx, userID) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while fetching User") + return + } + + nextContext := context.WithValue(ctx, "user", token) + if currentUser.OrgID != orgID { + err = ae.ErrInvalidToken + logger.WithField("err", err.Error()).Error("Mismatch with user organization and current organization") + w.WriteHeader(http.StatusUnauthorized) + return + } + next.ServeHTTP(w, r.WithContext(nextContext)) + }) +} + +func GetUserId () int64{ + return 0 +} + +func GetRole() int64{ + return 1 +} \ No newline at end of file diff --git a/go-backend/migrations/1587467112_create_organisation_table.up.sql b/go-backend/migrations/1587467112_create_organisation_table.up.sql index 348d8fa78..1aae38bb8 100644 --- a/go-backend/migrations/1587467112_create_organisation_table.up.sql +++ b/go-backend/migrations/1587467112_create_organisation_table.up.sql @@ -1,14 +1,19 @@ CREATE TABLE IF NOT EXISTS organizations ( - id BIGSERIAL NOT NULL PRIMARY KEY, - name varchar(50), - contact_email varchar(50), - domain_name varchar(45), - subscription_status integer, - subscription_valid_upto BIGINT, - hi5_limit integer, - hi5_quota_renewal_frequency varchar(50), - timezone varchar(100) DEFAULT 'UTC', - created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC') + id BIGSERIAL NOT NULL PRIMARY KEY, + name varchar(50), + contact_email varchar(50) , + domain_name varchar(45) , + subscription_status INTEGER, + subscription_valid_upto TIMESTAMP, + hi5_limit INTEGER, + hi5_quota_renewal_frequency VARCHAR(50), + timezone VARCHAR(100) DEFAULT 'UTC', + created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC'), + created_by BIGINT REFERENCES users(id), + soft_delete BOOLEAN DEFAULT FALSE, + soft_delete_by BIGINT REFERENCES users(id), + is_email_verified BOOLEAN DEFAULT FALSE, + updated_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC') ); -- TODO Add some indexes @@ -22,9 +27,9 @@ CREATE UNIQUE INDEX IF NOT EXISTS fk_organizations_domain_name_unique ON organiz -- Create the Josh Software organization -- TODO: Get proper values (management decisions) for these things... INSERT INTO organizations ( - id, name, contact_email, domain_name, subscription_status, subscription_valid_upto, hi5_limit, - hi5_quota_renewal_frequency, timezone, created_at + id, name, contact_email, domain_name, subscription_status, subscription_valid_upto, hi5_limit, + hi5_quota_renewal_frequency, timezone, created_at,created_by,soft_delete,soft_delete_by,updated_at ) VALUES ( - DEFAULT, 'Josh Software', 'j.austin.hughey@joshsoftware.com', 'joshsoftware.com', 9999, 9999, 9999, - '1 Week', DEFAULT, DEFAULT + DEFAULT, 'Josh Software', 'j.austin.hughey@joshsoftware.com', 'joshsoftware.com', 9999, '2024-06-30 23:59:59', 9999, + '1 Week', DEFAULT, DEFAULT,0,false,0,DEFAULT ) ON CONFLICT DO NOTHING; diff --git a/go-backend/migrations/1718614245_otp.down.sql b/go-backend/migrations/1718614245_otp.down.sql new file mode 100644 index 000000000..1dcea47fb --- /dev/null +++ b/go-backend/migrations/1718614245_otp.down.sql @@ -0,0 +1 @@ +DROP TABLE otp; \ No newline at end of file diff --git a/go-backend/migrations/1718614245_otp.up.sql b/go-backend/migrations/1718614245_otp.up.sql new file mode 100644 index 000000000..12cc94e95 --- /dev/null +++ b/go-backend/migrations/1718614245_otp.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE otp ( + otp CHAR(6) PRIMARY KEY CHECK (otp ~ '^[0-9]{6}$'), + org_id BIGINT REFERENCES organizations(id), + created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC') +); \ No newline at end of file diff --git a/go-backend/pkg/constants/constant.go b/go-backend/pkg/constants/constant.go new file mode 100644 index 000000000..9808b453e --- /dev/null +++ b/go-backend/pkg/constants/constant.go @@ -0,0 +1,2 @@ +package constant + diff --git a/go-backend/pkg/dto/organization.go b/go-backend/pkg/dto/organization.go new file mode 100644 index 000000000..407854115 --- /dev/null +++ b/go-backend/pkg/dto/organization.go @@ -0,0 +1,28 @@ +package dto + +import ( + "time" +) + +type Organization struct { + ID int64 `json:"id" ` + Name string `json:"name"` + ContactEmail string `json:"email"` + DomainName string `json:"domain_name"` + SubscriptionStatus int `json:"subscription_status"` + SubscriptionValidUpto time.Time `json:"subscription_valid_upto"` + Hi5Limit int `json:"hi5_limit"` + Hi5QuotaRenewalFrequency string `json:"hi5_quota_renewal_frequency"` + Timezone string `json:"timezone"` + CreatedAt time.Time `json:"created_at"` + CreatedBy int64 `json:"created_by"` + UpdatedAt time.Time `json:"updated_at"` + // SoftDelete bool `json:"soft_delete"` + // SoftDeleteBy int64 `json:"soft_delete_by"` +} + +type OTP struct { + CreatedAt time.Time + OrgId int64 `json:"org_id"` + OTPCode string `json:"otpcode"` +} \ No newline at end of file diff --git a/go-backend/pkg/dto/response.go b/go-backend/pkg/dto/response.go new file mode 100644 index 000000000..f29b97900 --- /dev/null +++ b/go-backend/pkg/dto/response.go @@ -0,0 +1,39 @@ +package dto + +import ( + "encoding/json" + "net/http" + + logger "github.com/sirupsen/logrus" +) + +type SuccessResponse struct { + Data interface{} `json:"data"` +} + +type ErrorResponse struct { + Error interface{} `json:"error"` +} + +type MessageObject struct { + Message string `json:"message"` +} + +type ErrorObject struct { + Code string `json:"code"` + MessageObject + Fields map[string]string `json:"fields"` +} + +func Repsonse(rw http.ResponseWriter, status int, responseBody interface{}) { + respBytes, err := json.Marshal(responseBody) + if err != nil { + logger.WithField("err", err.Error()).Error("Error while marshaling data") + rw.WriteHeader(http.StatusInternalServerError) + return + } + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(status) + rw.Write(respBytes) +} diff --git a/go-backend/repository/organization.go b/go-backend/repository/organization.go new file mode 100644 index 000000000..b6663bbbc --- /dev/null +++ b/go-backend/repository/organization.go @@ -0,0 +1,19 @@ +package repository + +import "time" +type OraganizationStorer interface { + ListOfOrganization() ([]Organization,error) +} + +type Organization struct { + Id int64 + Name string + ContactEmail string + DomainName string + SubscriptionStatus int + SubscriptionValidUpto time.Time + Hi5Limit int + Hi5QuotaRenewalFrequency string + Timezone string + CreatedAt time.Time +} \ No newline at end of file diff --git a/go-backend/service/Email/mocks/MailService.go b/go-backend/service/Email/mocks/MailService.go new file mode 100644 index 000000000..444d98d37 --- /dev/null +++ b/go-backend/service/Email/mocks/MailService.go @@ -0,0 +1,46 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + email "joshsoftware/peerly/service/Email" + + mock "github.com/stretchr/testify/mock" +) + +// MailService is an autogenerated mock type for the MailService type +type MailService struct { + mock.Mock +} + +// SendMail provides a mock function with given fields: mailReq +func (_m *MailService) SendMail(mailReq *email.Mail) error { + ret := _m.Called(mailReq) + + if len(ret) == 0 { + panic("no return value specified for SendMail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*email.Mail) error); ok { + r0 = rf(mailReq) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMailService creates a new instance of MailService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMailService(t interface { + mock.TestingT + Cleanup(func()) +}) *MailService { + mock := &MailService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go-backend/service/Email/service.go b/go-backend/service/Email/service.go new file mode 100644 index 000000000..2f6ed6256 --- /dev/null +++ b/go-backend/service/Email/service.go @@ -0,0 +1,130 @@ +package email + +import ( + // "fmt" + "fmt" + "joshsoftware/peerly/config" + + "github.com/sendgrid/sendgrid-go" + "github.com/sendgrid/sendgrid-go/helpers/mail" + logger "github.com/sirupsen/logrus" +) + +// MailService represents the interface for our mail service. +type MailService interface { + // CreateMail(mailReq *Mail) []byte + SendMail(mailReq *Mail) error +} + +// MailData represents the data to be sent to the template of the mail. +type MailData struct { + OTPCode string +} + +// Mail represents a email request +type Mail struct { + from string + to []string + subject string + data *MailData +} + +// func (ms *Mail) CreateMail() ([]byte) { +// m := mail.NewV3Mail() + +// from := mail.NewEmail("OTP Verification", "samirpatil9882@gmail.com") +// m.SetFrom(from) + +// // Since you want to send to only one email, you can take the first one from ms.to +// toEmail := ms.to[0] +// to := mail.NewEmail("user", toEmail) + +// p := mail.NewPersonalization() +// p.AddTos(to) + +// // Add custom substitutions (if needed) +// p.SetSubstitution("Username", ms.data.ReferenceId) +// p.SetSubstitution("Code", ms.data.OTPCode) + +// m.AddPersonalizations(p) + +// // Create content for the email +// content := mail.NewContent("text/plain", "Hello, here is your OTP code: "+ms.data.OTPCode) +// m.AddContent(content) + +// // Generate request body +// requestBody := mail.GetRequestBody(m) +// return requestBody +// } + +// SendMail creates a sendgrid mail from the given mail request and sends it. +func (ms *Mail) SendMail() error { + from := mail.NewEmail("Organization", ms.from) + subject := "Peerly: OTP verification" + to := mail.NewEmail("Example User", ms.to[0]) + + // Plain text content + plainTextContent := "Verify your account with the OTP: " + ms.data.OTPCode + + // HTML content with inline styling + htmlContent := ` + + + + + + Email Verification + + + +
+

Verify your account with the OTP:

+

OTP: ` + ms.data.OTPCode + `

+
+ + + ` + + message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent) + client := sendgrid.NewSendClient(config.ReadEnvString("SENDGRID_API_KEY")) + response, err := client.Send(message) + if err != nil { + logger.Error("unable to send mail", "error", err) + return err + } + + fmt.Println("Email sent successfully!") + fmt.Println("Response status code:", response.StatusCode) + fmt.Println("Response body:", response.Body) + fmt.Println("Response headers:", response.Headers) + + return nil +} + +// NewMail returns a new mail request. +func NewMail(from string, to []string, subject string, data *MailData) *Mail { + return &Mail{ + from: from, + to: to, + subject: subject, + data: data, + } +} diff --git a/go-backend/service/Email/service_test.go b/go-backend/service/Email/service_test.go new file mode 100644 index 000000000..c44fb2084 --- /dev/null +++ b/go-backend/service/Email/service_test.go @@ -0,0 +1,79 @@ +package email + +import ( + // "testing" + + // "github.com/stretchr/testify/assert" +) + +// import "github.com/sendgrid/sendgrid-go" + +// func TestSendMail(t *testing.T) { +// tests := []struct { +// name string +// mockResponse *sendgrid.Response +// mockError error +// expectedError error +// expectedSuccess bool +// }{ +// { +// name: "Success", +// mockResponse: &sendgrid.Response{ +// StatusCode: 202, +// Body: "Accepted", +// Headers: map[string][]string{}, +// }, +// mockError: nil, +// expectedError: nil, +// expectedSuccess: true, +// }, +// { +// name: "Send Error", +// mockResponse: nil, +// mockError: errors.New("unable to send mail"), +// expectedError: errors.New("unable to send mail"), +// expectedSuccess: false, +// }, +// { +// name: "Invalid API Key", +// mockResponse: &sendgrid.Response{ +// StatusCode: 401, +// Body: "Unauthorized", +// Headers: map[string][]string{}, +// }, +// mockError: nil, +// expectedError: errors.New("SendGrid API error: Unauthorized"), +// expectedSuccess: false, +// }, +// } + +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// mockClient := new(MockSendGridClient) +// mockClient.On("Send", mock.Anything).Return(tt.mockResponse, tt.mockError).Once() + +// mail := &Mail{ +// from: "sender@example.com", +// to: []string{"recipient@example.com"}, +// data: &MailData{ +// OTPCode: "123456", +// }, +// } + +// sendgrid.NewSendClient = func(apiKey string) sendgrid.Client { +// return mockClient +// } + +// err := mail.SendMail() + +// if tt.expectedSuccess { +// assert.NoError(t, err) +// } else { +// assert.Error(t, err) +// assert.Equal(t, tt.expectedError, err) +// } + +// mockClient.AssertExpectations(t) +// }) +// } +// } \ No newline at end of file diff --git a/go-backend/service/Orgnization/domain.go b/go-backend/service/Orgnization/domain.go new file mode 100644 index 000000000..de72cbd51 --- /dev/null +++ b/go-backend/service/Orgnization/domain.go @@ -0,0 +1,25 @@ +package orgnization + +import ( + "joshsoftware/peerly/db" + "joshsoftware/peerly/pkg/dto" +) + +func OrganizationDBToOrganization(orgDB db.Organization) dto.Organization { + return dto.Organization{ + ID: orgDB.ID, + Name: orgDB.Name, + ContactEmail: orgDB.ContactEmail, + DomainName: orgDB.DomainName, + SubscriptionStatus: orgDB.SubscriptionStatus, + SubscriptionValidUpto: orgDB.SubscriptionValidUpto, + Hi5Limit: orgDB.Hi5Limit, + Hi5QuotaRenewalFrequency: orgDB.Hi5QuotaRenewalFrequency, + Timezone: orgDB.Timezone, + CreatedAt: orgDB.CreatedAt, + CreatedBy: orgDB.CreatedBy, + UpdatedAt: orgDB.UpdatedAt, + // SoftDelete: orgDB.SoftDelete, + // SoftDeleteBy: orgDB.SoftDeleteBy, + } +} diff --git a/go-backend/service/Orgnization/mocks/Service.go b/go-backend/service/Orgnization/mocks/Service.go new file mode 100644 index 000000000..e91c26869 --- /dev/null +++ b/go-backend/service/Orgnization/mocks/Service.go @@ -0,0 +1,225 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + dto "joshsoftware/peerly/pkg/dto" + + mock "github.com/stretchr/testify/mock" +) + +// Service is an autogenerated mock type for the Service type +type Service struct { + mock.Mock +} + +// CreateOrganization provides a mock function with given fields: ctx, organization +func (_m *Service) CreateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) { + ret := _m.Called(ctx, organization) + + if len(ret) == 0 { + panic("no return value specified for CreateOrganization") + } + + var r0 dto.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) (dto.Organization, error)); ok { + return rf(ctx, organization) + } + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) dto.Organization); ok { + r0 = rf(ctx, organization) + } else { + r0 = ret.Get(0).(dto.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, dto.Organization) error); ok { + r1 = rf(ctx, organization) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteOrganization provides a mock function with given fields: ctx, organizationID, userId +func (_m *Service) DeleteOrganization(ctx context.Context, organizationID int, userId int64) error { + ret := _m.Called(ctx, organizationID, userId) + + if len(ret) == 0 { + panic("no return value specified for DeleteOrganization") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int, int64) error); ok { + r0 = rf(ctx, organizationID, userId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetOrganization provides a mock function with given fields: ctx, id +func (_m *Service) GetOrganization(ctx context.Context, id int) (dto.Organization, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetOrganization") + } + + var r0 dto.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (dto.Organization, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, int) dto.Organization); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(dto.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetOrganizationByDomainName provides a mock function with given fields: ctx, domainName +func (_m *Service) GetOrganizationByDomainName(ctx context.Context, domainName string) (dto.Organization, error) { + ret := _m.Called(ctx, domainName) + + if len(ret) == 0 { + panic("no return value specified for GetOrganizationByDomainName") + } + + var r0 dto.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (dto.Organization, error)); ok { + return rf(ctx, domainName) + } + if rf, ok := ret.Get(0).(func(context.Context, string) dto.Organization); ok { + r0 = rf(ctx, domainName) + } else { + r0 = ret.Get(0).(dto.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, domainName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsValidContactEmail provides a mock function with given fields: ctx, otpInfo +func (_m *Service) IsValidContactEmail(ctx context.Context, otpInfo dto.OTP) error { + ret := _m.Called(ctx, otpInfo) + + if len(ret) == 0 { + panic("no return value specified for IsValidContactEmail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, dto.OTP) error); ok { + r0 = rf(ctx, otpInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ListOrganizations provides a mock function with given fields: ctx +func (_m *Service) ListOrganizations(ctx context.Context) ([]dto.Organization, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ListOrganizations") + } + + var r0 []dto.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]dto.Organization, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []dto.Organization); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]dto.Organization) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResendOTPForContactEmail provides a mock function with given fields: ctx, orgId +func (_m *Service) ResendOTPForContactEmail(ctx context.Context, orgId int64) error { + ret := _m.Called(ctx, orgId) + + if len(ret) == 0 { + panic("no return value specified for ResendOTPForContactEmail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, orgId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateOrganization provides a mock function with given fields: ctx, organization +func (_m *Service) UpdateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) { + ret := _m.Called(ctx, organization) + + if len(ret) == 0 { + panic("no return value specified for UpdateOrganization") + } + + var r0 dto.Organization + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) (dto.Organization, error)); ok { + return rf(ctx, organization) + } + if rf, ok := ret.Get(0).(func(context.Context, dto.Organization) dto.Organization); ok { + r0 = rf(ctx, organization) + } else { + r0 = ret.Get(0).(dto.Organization) + } + + if rf, ok := ret.Get(1).(func(context.Context, dto.Organization) error); ok { + r1 = rf(ctx, organization) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewService(t interface { + mock.TestingT + Cleanup(func()) +}) *Service { + mock := &Service{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go-backend/service/Orgnization/service.go b/go-backend/service/Orgnization/service.go new file mode 100644 index 000000000..deb9d02a7 --- /dev/null +++ b/go-backend/service/Orgnization/service.go @@ -0,0 +1,266 @@ +package orgnization + +import ( + "context" + "fmt" + "joshsoftware/peerly/apperrors" + "joshsoftware/peerly/config" + "joshsoftware/peerly/db" + "joshsoftware/peerly/pkg/dto" + email "joshsoftware/peerly/service/Email" + "joshsoftware/peerly/util" + "time" + + logger "github.com/sirupsen/logrus" +) + +type service struct { + OranizationRepo db.OrganizationStorer + OTPVerificationRepo db.OTPVerificationStorer +} + +type Service interface { + ListOrganizations(ctx context.Context) ([]dto.Organization, error) + GetOrganization(ctx context.Context, id int) (dto.Organization, error) + GetOrganizationByDomainName(ctx context.Context, domainName string) (dto.Organization, error) + CreateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) + UpdateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) + DeleteOrganization(ctx context.Context, organizationID int, userId int64) (err error) + IsValidContactEmail(ctx context.Context, otpInfo dto.OTP) (err error) + ResendOTPForContactEmail(ctx context.Context, orgId int64) error +} + +func NewService(oranizationRepo db.OrganizationStorer, otpRepo db.OTPVerificationStorer) Service { + return &service{ + OranizationRepo: oranizationRepo, + OTPVerificationRepo: otpRepo, + } +} + +func (orgSvc *service) ListOrganizations(ctx context.Context) ([]dto.Organization, error) { + + organizations, err := orgSvc.OranizationRepo.ListOrganizations(ctx) + if err != nil { + logger.WithField("err", err.Error()).Error("Error listing organizations") + return []dto.Organization{}, err + } + + orgList := make([]dto.Organization, 0, len(organizations)) + for _, organization := range organizations { + org := OrganizationDBToOrganization(organization) + orgList = append(orgList, org) + } + + return orgList, nil + +} + +func (orgSvc *service) GetOrganization(ctx context.Context, id int) (dto.Organization, error) { + + organization, err := orgSvc.OranizationRepo.GetOrganization(ctx, id) + if err != nil { + return dto.Organization{}, err + } + + org := OrganizationDBToOrganization(organization) + + return org, nil + +} + +func (orgSvc *service) GetOrganizationByDomainName(ctx context.Context, domainName string) (dto.Organization, error) { + + organization, err := orgSvc.OranizationRepo.GetOrganizationByDomainName(ctx, domainName) + if err != nil { + return dto.Organization{}, err + } + org := OrganizationDBToOrganization(organization) + return org, nil +} + +func (orgSvc *service) CreateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) { + + isEmailPresent := orgSvc.OranizationRepo.IsEmailPresent(ctx, organization.ContactEmail) + if isEmailPresent { + return dto.Organization{}, apperrors.InvalidContactEmail + } + + isDomainPresent := orgSvc.OranizationRepo.IsDomainPresent(ctx, organization.DomainName) + if isDomainPresent { + return dto.Organization{}, apperrors.InvalidDomainName + } + var createdOrganization db.Organization + createdOrganization, err := orgSvc.OranizationRepo.CreateOrganization(ctx, organization) + if err != nil { + return dto.Organization{}, err + } + org := OrganizationDBToOrganization(createdOrganization) + OTPCode := util.GenerateRandomNumber(6) + to := []string{org.ContactEmail} + sub := "OTP Verification" + mailData := &email.MailData{ + OTPCode: OTPCode, + } + mailReq := email.NewMail(config.ReadEnvString("SENDER_EMAIL"), to, sub, mailData) + var otpinfo db.OTP + otpinfo.OTPCode = OTPCode + otpinfo.OrgId = org.ID + err = orgSvc.OTPVerificationRepo.CreateOTPInfo(ctx, otpinfo) + if err != nil { + return org, err + } + err = mailReq.SendMail() + if err != nil { + logger.Error("unable to send mail", "error", err) + return org, nil + } + + return org, nil +} + +func (orgSvc *service) UpdateOrganization(ctx context.Context, organization dto.Organization) (dto.Organization, error) { + + if !orgSvc.OranizationRepo.IsOrganizationIdPresent(ctx, int64(organization.ID)) { + return dto.Organization{}, apperrors.OrganizationNotFound + } + + isEmailPresent := orgSvc.OranizationRepo.IsEmailPresent(ctx, organization.ContactEmail) + if isEmailPresent { + return dto.Organization{}, apperrors.InvalidContactEmail + } + + isDomainPresent := orgSvc.OranizationRepo.IsDomainPresent(ctx, organization.DomainName) + if isDomainPresent { + return dto.Organization{}, apperrors.InvalidDomainName + } + + updatedOrganization, err := orgSvc.OranizationRepo.UpdateOrganization(ctx, organization) + if err != nil { + return dto.Organization{}, err + } + + org := OrganizationDBToOrganization(updatedOrganization) + + if organization.ContactEmail != "" { + + OTPCode := util.GenerateRandomNumber(6) + to := []string{org.ContactEmail} + sub := "OTP Verification" + if err != nil { + logger.WithField("err", err.Error()).Error("Database init failed") + return org, nil + } + mailData := &email.MailData{ + OTPCode: OTPCode, + } + mailReq := email.NewMail(config.ReadEnvString("SENDER_EMAIL"), to, sub, mailData) + var otpinfo db.OTP + otpinfo.OTPCode = OTPCode + otpinfo.OrgId = org.ID + err = orgSvc.OTPVerificationRepo.CreateOTPInfo(ctx, otpinfo) + if err != nil { + return org, err + } + err = mailReq.SendMail() + if err != nil { + logger.Error("unable to send mail", "error", err) + return org, nil + } + + return org, nil + + + + } + return org, nil +} + +func (orgSvc *service) DeleteOrganization(ctx context.Context, organizationID int, userId int64) (err error) { + if !orgSvc.OranizationRepo.IsOrganizationIdPresent(ctx, int64(organizationID)) { + return apperrors.OrganizationNotFound + } + + err = orgSvc.OranizationRepo.DeleteOrganization(ctx, organizationID, userId) + return err +} + +func (orgSvc *service) IsValidContactEmail(ctx context.Context, otpInfo dto.OTP) error { + fmt.Println("-------------------------------------------->serviceup") + otp, err := orgSvc.OTPVerificationRepo.GetOTPVerificationStatus(ctx, otpInfo) + if err != nil { + if err == apperrors.InvalidReferenceId { + logger.Error("otpInfo not found", "error", err) + return err + } + logger.Error("unable to GetOTPVerificationStatus", "error", err) + return err + } + + fmt.Println("-------------------------------------------->service") + fmt.Println("stored otp timestamp: ", otp.CreatedAt) + fmt.Println("now: ", time.Now()) + expirationDuration := 2 * time.Minute + expirationTime := otp.CreatedAt.Add(expirationDuration) + if time.Now().After(expirationTime) { + logger.Error("timelimit exceeded") + return apperrors.TimeExceeded + } + + if otp.OTPCode != otpInfo.OTPCode { + logger.Error("invalid otp") + return apperrors.InvalidOTP + } + + err = orgSvc.OTPVerificationRepo.ChangeIsVerifiedFlag(ctx, otpInfo.OrgId) + if err != nil { + logger.Error("error from otp repo", "error", err) + return err + } + + err = orgSvc.OTPVerificationRepo.DeleteOTPData(ctx, otpInfo.OrgId) + if err != nil { + logger.Error("error in deleting otp data", "error", err) + return err + } + + return nil +} + +func (orgSvc *service) ResendOTPForContactEmail(ctx context.Context, orgId int64) error { + count, err := orgSvc.OTPVerificationRepo.GetCountOfOrgId(ctx, orgId) + if err != nil { + return err + } + if count == 0 { + return apperrors.OrganizationNotFound + } + if count >= 3 { + return apperrors.AttemptExceeded + } + + org, err := orgSvc.OranizationRepo.GetOrganization(ctx, int(orgId)) + if err != nil { + return err + } + OTPCode := util.GenerateRandomNumber(6) + to := []string{org.ContactEmail} + sub := "OTP Verification" + mailData := &email.MailData{ + OTPCode: OTPCode, + } + mailReq := email.NewMail(config.ReadEnvString("SENDER_EMAIL"), to, sub, mailData) + var otpinfo db.OTP + otpinfo.OTPCode = OTPCode + otpinfo.OrgId = org.ID + err = orgSvc.OTPVerificationRepo.CreateOTPInfo(ctx, otpinfo) + if err != nil { + return err + } + err = mailReq.SendMail() + if err != nil { + logger.Error("unable to send mail", "error", err) + return apperrors.InernalServer + } + + return nil +} diff --git a/go-backend/service/Orgnization/service_test.go b/go-backend/service/Orgnization/service_test.go new file mode 100644 index 000000000..ed8d6dbcc --- /dev/null +++ b/go-backend/service/Orgnization/service_test.go @@ -0,0 +1,886 @@ +package orgnization + +import ( + "context" + // "joshsoftware/peerly/config" + "joshsoftware/peerly/db" + "joshsoftware/peerly/db/mocks" + // "joshsoftware/peerly/service/Email" + "joshsoftware/peerly/pkg/dto" + "os" + "testing" + "time" + + ae "joshsoftware/peerly/apperrors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// func envSetter(envs map[string]string) (closer func()) { +// originalEnvs := map[string]string{} + +// for name, value := range envs { +// if originalValue, ok := os.LookupEnv(name); ok { +// originalEnvs[name] = originalValue +// } +// _ = os.Setenv(name, value) +// } + +// return func() { +// for name := range envs { +// origValue, has := originalEnvs[name] +// if has { +// _ = os.Setenv(name, origValue) +// } else { +// _ = os.Unsetenv(name) +// } +// } +// } +// } + +func TestListOrganizations(t *testing.T) { + tests := []struct { + name string + wantOrganizations []dto.Organization + wantErr error + setup func(OranizationRepo *mocks.OrganizationStorer) + }{ + { + name: "Success", + wantOrganizations: []dto.Organization{ + { + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + }, + wantErr: nil, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("ListOrganizations", mock.Anything).Return([]db.Organization{ + { + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + SoftDelete: false, + SoftDeleteBy: 0, + }, + }, nil).Once() + }, + }, + { + name: "Error from Repository", + wantOrganizations: []dto.Organization{}, + wantErr: ae.InernalServer, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("ListOrganizations", mock.Anything).Return([]db.Organization{}, ae.InernalServer).Once() + }, + }, + { + name: "Empty Result", + wantOrganizations: []dto.Organization{}, + wantErr: nil, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("ListOrganizations", mock.Anything).Return([]db.Organization{}, nil).Once() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.setup != nil { + tt.setup(mockOrganizationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo,mockOTPVerificationRepo) + + // Call service method + gotOrganizations, err := orgSvc.ListOrganizations(context.Background()) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantOrganizations, gotOrganizations) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + + +func TestGetOrganization(t *testing.T) { + tests := []struct { + name string + id int + wantOrganization dto.Organization + wantErr error + setup func(OranizationRepo *mocks.OrganizationStorer) + }{ + { + name: "Success", + id: 1, + wantOrganization: dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + wantErr: nil, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganization", mock.Anything, 1).Return(db.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + SoftDelete: false, + SoftDeleteBy: 0, + }, nil).Once() + }, + }, + { + name: "Error from Repository", + id: 2, + wantOrganization: dto.Organization{}, + wantErr: ae.InernalServer, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganization", mock.Anything, 2).Return(db.Organization{}, ae.InernalServer).Once() + }, + }, + { + name: "Organization Not Found", + id: 3, + wantOrganization: dto.Organization{}, + wantErr: ae.OrganizationNotFound, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganization", mock.Anything, 3).Return(db.Organization{}, ae.OrganizationNotFound).Once() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.setup != nil { + tt.setup(mockOrganizationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + gotOrganization, err := orgSvc.GetOrganization(context.Background(), tt.id) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantOrganization, gotOrganization) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + +func TestGetOrganizationByDomainName(t *testing.T) { + tests := []struct { + name string + domainName string + wantOrganization dto.Organization + wantErr error + setup func(OranizationRepo *mocks.OrganizationStorer) + }{ + { + name: "Success", + domainName: "example.com", + wantOrganization: dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + wantErr: nil, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganizationByDomainName", mock.Anything, "example.com").Return(db.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + SoftDelete: false, + SoftDeleteBy: 0, + }, nil).Once() + }, + }, + { + name: "Error from Repository", + domainName: "nonexistent.com", + wantOrganization: dto.Organization{}, + wantErr: ae.InernalServer, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganizationByDomainName", mock.Anything, "nonexistent.com").Return(db.Organization{}, ae.InernalServer).Once() + }, + }, + { + name: "Organization Not Found", + domainName: "notfound.com", + wantOrganization: dto.Organization{}, + wantErr: ae.OrganizationNotFound, + setup: func(OranizationRepo *mocks.OrganizationStorer) { + OranizationRepo.On("GetOrganizationByDomainName", mock.Anything, "notfound.com").Return(db.Organization{}, ae.OrganizationNotFound).Once() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.setup != nil { + tt.setup(mockOrganizationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + gotOrganization, err := orgSvc.GetOrganizationByDomainName(context.Background(), tt.domainName) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantOrganization, gotOrganization) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + +func TestCreateOrganization(t *testing.T) { + // Load configurations from application.yml + // config.LoadConfig() + + tests := []struct { + name string + organization dto.Organization + mockSetup func(organizationRepo *mocks.OrganizationStorer, otpVerificationRepo *mocks.OTPVerificationStorer) + wantOrganization dto.Organization + wantErr error + }{ + { + name: "Success", + organization: dto.Organization{ + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Now(), + CreatedBy: 1, + UpdatedAt: time.Now(), + }, + mockSetup: func(organizationRepo *mocks.OrganizationStorer, otpVerificationRepo *mocks.OTPVerificationStorer) { + organizationRepo.On("IsEmailPresent", mock.Anything, "test@example.com").Return(false).Once() + organizationRepo.On("IsDomainPresent", mock.Anything, "example.com").Return(false).Once() + organizationRepo.On("CreateOrganization", mock.Anything, mock.AnythingOfType("dto.Organization")).Return(db.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Now(), + CreatedBy: 1, + UpdatedAt: time.Now(), + SoftDelete: false, + SoftDeleteBy: 0, + }, nil).Once() + otpVerificationRepo.On("CreateOTPInfo", mock.Anything, mock.AnythingOfType("db.OTP")).Return(nil).Once() + }, + wantOrganization: dto.Organization{ + ID: 1, + Name: "TestOrg", + ContactEmail: "test@example.com", + DomainName: "example.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Now(), + CreatedBy: 1, + UpdatedAt: time.Now(), + }, + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + // Initialize mock repositories + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.mockSetup != nil { + tt.mockSetup(mockOrganizationRepo, mockOTPVerificationRepo) + } + + // Create service with mock repositories + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + gotOrganization, err := orgSvc.CreateOrganization(context.Background(), tt.organization) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantOrganization, gotOrganization) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + mockOTPVerificationRepo.AssertExpectations(t) + }) + } +} + +func envSetter(envs map[string]string) (closer func()) { + originalEnvs := map[string]string{} + + for name, value := range envs { + if originalValue, ok := os.LookupEnv(name); ok { + originalEnvs[name] = originalValue + } + _ = os.Setenv(name, value) + } + + return func() { + for name := range envs { + origValue, has := originalEnvs[name] + if has { + _ = os.Setenv(name, origValue) + } else { + _ = os.Unsetenv(name) + } + } + } +} + + +func TestUpdateOrganization(t *testing.T) { + tests := []struct { + name string + organization dto.Organization + wantOrganization dto.Organization + wantErr error + setup func(OrganizationRepo *mocks.OrganizationStorer) + }{ + { + name: "Success", + organization: dto.Organization{ + ID: 1, + Name: "UpdatedOrg", + ContactEmail: "updated@example.com", + DomainName: "updated.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 200, + Hi5QuotaRenewalFrequency: "yearly", + Timezone: "UTC", + CreatedAt: time.Date(2023, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + wantOrganization: dto.Organization{ + ID: 1, + Name: "UpdatedOrg", + ContactEmail: "updated@example.com", + DomainName: "updated.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 200, + Hi5QuotaRenewalFrequency: "yearly", + Timezone: "UTC", + CreatedAt: time.Date(2023, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + wantErr: nil, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(1)).Return(true).Once() + OrganizationRepo.On("IsEmailPresent", mock.Anything, "updated@example.com").Return(false).Once() + OrganizationRepo.On("IsDomainPresent", mock.Anything, "updated.com").Return(false).Once() + OrganizationRepo.On("UpdateOrganization", mock.Anything, mock.Anything).Return(db.Organization{ + ID: 1, + Name: "UpdatedOrg", + ContactEmail: "updated@example.com", + DomainName: "updated.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 200, + Hi5QuotaRenewalFrequency: "yearly", + Timezone: "UTC", + CreatedAt: time.Date(2023, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + SoftDelete: false, + SoftDeleteBy: 0, + }, nil).Once() + }, + }, + { + name: "Organization Not Found", + organization: dto.Organization{ + ID: 2, + }, + wantOrganization: dto.Organization{}, + wantErr: ae.OrganizationNotFound, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(2)).Return(false).Once() + }, + }, + { + name: "Email Already Present", + organization: dto.Organization{ + ID: 3, + ContactEmail: "duplicate@example.com", + }, + wantOrganization: dto.Organization{}, + wantErr: ae.InvalidContactEmail, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(3)).Return(true).Once() + OrganizationRepo.On("IsEmailPresent", mock.Anything, "duplicate@example.com").Return(true).Once() + }, + }, + { + name: "Domain Name Already Present", + organization: dto.Organization{ + ID: 4, + DomainName: "duplicate.com", + }, + wantOrganization: dto.Organization{}, + wantErr: ae.InvalidDomainName, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(4)).Return(true).Once() + OrganizationRepo.On("IsEmailPresent", mock.Anything, mock.Anything).Return(false).Once() + OrganizationRepo.On("IsDomainPresent", mock.Anything, "duplicate.com").Return(true).Once() + }, + }, + { + name: "Repository Update Error", + organization: dto.Organization{ + ID: 5, + Name: "ErrorOrg", + ContactEmail: "error@example.com", + DomainName: "error.com", + SubscriptionStatus: 1, + SubscriptionValidUpto: time.Date(2024, 6, 17, 16, 0, 0, 0, time.UTC), + Hi5Limit: 100, + Hi5QuotaRenewalFrequency: "monthly", + Timezone: "UTC", + CreatedAt: time.Date(2023, time.June, 17, 11, 9, 19, 716618234, time.Local), + CreatedBy: 1, + UpdatedAt: time.Date(2024, time.June, 17, 11, 9, 19, 716618272, time.Local), + }, + wantOrganization: dto.Organization{}, + wantErr: ae.InernalServer, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(5)).Return(true).Once() + OrganizationRepo.On("IsEmailPresent", mock.Anything, "error@example.com").Return(false).Once() + OrganizationRepo.On("IsDomainPresent", mock.Anything, "error.com").Return(false).Once() + OrganizationRepo.On("UpdateOrganization", mock.Anything, mock.Anything).Return(db.Organization{}, ae.InernalServer).Once() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.setup != nil { + tt.setup(mockOrganizationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + gotOrganization, err := orgSvc.UpdateOrganization(context.Background(), tt.organization) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + assert.Equal(t, tt.wantOrganization, gotOrganization) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + +func TestDeleteOrganization(t *testing.T) { + tests := []struct { + name string + organizationID int + userId int64 + wantErr error + setup func(OrganizationRepo *mocks.OrganizationStorer) + }{ + { + name: "Success", + organizationID: 1, + userId: 123, + wantErr: nil, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(1)).Return(true).Once() + OrganizationRepo.On("DeleteOrganization", mock.Anything, 1, int64(123)).Return(nil).Once() + }, + }, + { + name: "Organization Not Found", + organizationID: 2, + userId: 123, + wantErr: ae.OrganizationNotFound, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(2)).Return(false).Once() + }, + }, + { + name: "Repository Delete Error", + organizationID: 3, + userId: 123, + wantErr: ae.InernalServer, + setup: func(OrganizationRepo *mocks.OrganizationStorer) { + OrganizationRepo.On("IsOrganizationIdPresent", mock.Anything, int64(3)).Return(true).Once() + OrganizationRepo.On("DeleteOrganization", mock.Anything, 3, int64(123)).Return(ae.InernalServer).Once() + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOrganizationRepo := new(mocks.OrganizationStorer) + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + if tt.setup != nil { + tt.setup(mockOrganizationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + err := orgSvc.DeleteOrganization(context.Background(), tt.organizationID, tt.userId) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + + // Assert that all expected calls were made + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + +func TestIsValidContactEmail(t *testing.T) { + tests := []struct { + name string + otpInfo dto.OTP + setup func(OTPVerificationRepo *mocks.OTPVerificationStorer) + wantErr error + }{ + { + name: "Success", + otpInfo: dto.OTP{ + OTPCode: "123456", + OrgId: 1, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + otp := db.OTP{ + OTPCode: "123456", + CreatedAt: time.Now().Add(-1 * time.Minute), + } + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(otp, nil).Once() + OTPVerificationRepo.On("ChangeIsVerifiedFlag", mock.Anything, int64(1)).Return(nil).Once() + OTPVerificationRepo.On("DeleteOTPData", mock.Anything, int64(1)).Return(nil).Once() + }, + wantErr: nil, + }, + { + name: "Invalid Reference ID", + otpInfo: dto.OTP{ + OTPCode: "123456", + OrgId: 2, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(db.OTP{}, ae.InvalidReferenceId).Once() + }, + wantErr: ae.InvalidReferenceId, + }, + { + name: "Expired OTP", + otpInfo: dto.OTP{ + OTPCode: "123456", + OrgId: 3, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + otp := db.OTP{ + OTPCode: "123456", + CreatedAt: time.Now().Add(-3 * time.Minute), + } + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(otp, nil).Once() + }, + wantErr: ae.TimeExceeded, + }, + { + name: "Invalid OTP", + otpInfo: dto.OTP{ + OTPCode: "654321", + OrgId: 4, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + otp := db.OTP{ + OTPCode: "123456", + CreatedAt: time.Now(), + } + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(otp, nil).Once() + }, + wantErr: ae.InvalidOTP, + }, + { + name: "Change Is Verified Flag Error", + otpInfo: dto.OTP{ + OTPCode: "123456", + OrgId: 5, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + otp := db.OTP{ + OTPCode: "123456", + CreatedAt: time.Now(), + } + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(otp, nil).Once() + OTPVerificationRepo.On("ChangeIsVerifiedFlag", mock.Anything, int64(5)).Return(ae.InernalServer).Once() + }, + wantErr: ae.InernalServer, + }, + { + name: "Delete OTP Data Error", + otpInfo: dto.OTP{ + OTPCode: "123456", + OrgId: 6, + }, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer) { + otp := db.OTP{ + OTPCode: "123456", + CreatedAt: time.Now(), + } + OTPVerificationRepo.On("GetOTPVerificationStatus", mock.Anything, mock.Anything).Return(otp, nil).Once() + OTPVerificationRepo.On("ChangeIsVerifiedFlag", mock.Anything, int64(6)).Return(nil).Once() + OTPVerificationRepo.On("DeleteOTPData", mock.Anything, int64(6)).Return(ae.InernalServer).Once() + }, + wantErr: ae.InernalServer, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + mockOrganizationRepo := new(mocks.OrganizationStorer) + if tt.setup != nil { + tt.setup(mockOTPVerificationRepo) + } + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + err := orgSvc.IsValidContactEmail(context.Background(), tt.otpInfo) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + + // Assert that all expected calls were made + mockOTPVerificationRepo.AssertExpectations(t) + }) + } +} + +func TestResendOTPForContactEmail(t *testing.T) { + tests := []struct { + name string + orgId int64 + setup func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) + mockSendMail func() error + wantErr error + }{ + { + name: "Success", + orgId: 1, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(1)).Return(1, nil).Once() + OrganizationRepo.On("GetOrganization", mock.Anything, 1).Return(db.Organization{ + ID: 1, + ContactEmail: "test@example.com", + }, nil).Once() + OTPVerificationRepo.On("CreateOTPInfo", mock.Anything, mock.Anything).Return(nil).Once() + }, + mockSendMail: func() error { + return nil + }, + wantErr: nil, + }, + { + name: "Organization Not Found", + orgId: 2, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(2)).Return(0, nil).Once() + }, + mockSendMail: nil, + wantErr: ae.OrganizationNotFound, + }, + { + name: "Attempt Exceeded", + orgId: 3, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(3)).Return(3, nil).Once() + }, + mockSendMail: nil, + wantErr: ae.AttemptExceeded, + }, + { + name: "GetCountOfOrgId Error", + orgId: 4, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(4)).Return(0, ae.InernalServer).Once() + }, + mockSendMail: nil, + wantErr: ae.InernalServer, + }, + { + name: "GetOrganization Error", + orgId: 5, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(5)).Return(1, nil).Once() + OrganizationRepo.On("GetOrganization", mock.Anything, 5).Return(db.Organization{}, ae.InernalServer).Once() + }, + mockSendMail: nil, + wantErr: ae.InernalServer, + }, + { + name: "CreateOTPInfo Error", + orgId: 6, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(6)).Return(1, nil).Once() + OrganizationRepo.On("GetOrganization", mock.Anything, 6).Return(db.Organization{ + ID: 6, + ContactEmail: "test@example.com", + }, nil).Once() + OTPVerificationRepo.On("CreateOTPInfo", mock.Anything, mock.Anything).Return(ae.InernalServer).Once() + }, + mockSendMail: nil, + wantErr: ae.InernalServer, + }, + { + name: "SendMail Error", + orgId: 7, + setup: func(OTPVerificationRepo *mocks.OTPVerificationStorer, OrganizationRepo *mocks.OrganizationStorer) { + OTPVerificationRepo.On("GetCountOfOrgId", mock.Anything, int64(7)).Return(1, nil).Once() + OrganizationRepo.On("GetOrganization", mock.Anything, 7).Return(db.Organization{ + ID: 7, + ContactEmail: "test@example.com", + }, nil).Once() + OTPVerificationRepo.On("CreateOTPInfo", mock.Anything, mock.Anything).Return(nil).Once() + }, + mockSendMail: func() error { + return ae.InernalServer + }, + wantErr: ae.InernalServer, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize mock repository + mockOTPVerificationRepo := new(mocks.OTPVerificationStorer) + mockOrganizationRepo := new(mocks.OrganizationStorer) + if tt.setup != nil { + tt.setup(mockOTPVerificationRepo, mockOrganizationRepo) + } + + // // Mock SendMail function + // email.SendMail = tt.mockSendMail + + // Create service with mock repository + orgSvc := NewService(mockOrganizationRepo, mockOTPVerificationRepo) + + // Call service method + err := orgSvc.ResendOTPForContactEmail(context.Background(), tt.orgId) + + // Assert expectations + assert.Equal(t, tt.wantErr, err) + + // Assert that all expected calls were made + mockOTPVerificationRepo.AssertExpectations(t) + mockOrganizationRepo.AssertExpectations(t) + }) + } +} + diff --git a/go-backend/service/aws_s3_service_http.go b/go-backend/service/aws_s3_service_http.go index 7455b998f..e69189905 100644 --- a/go-backend/service/aws_s3_service_http.go +++ b/go-backend/service/aws_s3_service_http.go @@ -1,32 +1,32 @@ package service import ( - "net/http" + // "net/http" - logger "github.com/sirupsen/logrus" + // logger "github.com/sirupsen/logrus" ) -func getS3SignedURLHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if req.FormValue("type") == "" || req.FormValue("filename") == "" { - rw.WriteHeader(http.StatusInternalServerError) - repsonse(rw, http.StatusInternalServerError, errorResponse{ - Error: messageObject{ - Message: "Error while retrieving bucket type and filename in URL", - }, - }) - return - } - S3SignedURLData, err := deps.AWSStore.GetAWSS3SignedURL(req.Context(), req.FormValue("type"), req.FormValue("filename")) - if err != nil { - logger.WithField("err", err.Error()).Error("Error while retrieving URL") - rw.WriteHeader(http.StatusInternalServerError) - repsonse(rw, http.StatusInternalServerError, errorResponse{ - Error: messageObject{ - Message: "Error while retrieving URL", - }, - }) - } - repsonse(rw, http.StatusOK, successResponse{Data: S3SignedURLData}) - }) -} +// func getS3SignedURLHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// if req.FormValue("type") == "" || req.FormValue("filename") == "" { +// rw.WriteHeader(http.StatusInternalServerError) +// repsonse(rw, http.StatusInternalServerError, errorResponse{ +// Error: messageObject{ +// Message: "Error while retrieving bucket type and filename in URL", +// }, +// }) +// return +// } +// S3SignedURLData, err := deps.AWSStore.GetAWSS3SignedURL(req.Context(), req.FormValue("type"), req.FormValue("filename")) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error while retrieving URL") +// rw.WriteHeader(http.StatusInternalServerError) +// repsonse(rw, http.StatusInternalServerError, errorResponse{ +// Error: messageObject{ +// Message: "Error while retrieving URL", +// }, +// }) +// } +// repsonse(rw, http.StatusOK, successResponse{Data: S3SignedURLData}) +// }) +// } diff --git a/go-backend/service/dependencies.go b/go-backend/service/dependencies.go index f1cec77c2..bf1ae4cea 100644 --- a/go-backend/service/dependencies.go +++ b/go-backend/service/dependencies.go @@ -1,13 +1,28 @@ package service import ( - "joshsoftware/peerly/aws" + // "joshsoftware/peerly/aws" "joshsoftware/peerly/db" + orgnization "joshsoftware/peerly/service/Orgnization" + + "github.com/jmoiron/sqlx" ) // Dependencies - Stuff we need for the service package type Dependencies struct { Store db.Storer - AWSStore aws.AWSStorer + // AWSStore aws.AWSStorer // define other service dependencies + OrganizationService orgnization.Service +} + +func NewServices(dbInstance *sqlx.DB,store db.Storer) Dependencies { + orgRepo := db.NewOrganizationRepo(dbInstance) + otpRepo := db.NewOTPVerificationRepo(dbInstance) + orgService := orgnization.NewService(orgRepo,otpRepo) + return Dependencies{ + Store: store, + // AWSStore: awsstore, + OrganizationService: orgService, + } } diff --git a/go-backend/service/organization_http.go b/go-backend/service/organization_http.go index 66bada8ca..0ea0d9299 100644 --- a/go-backend/service/organization_http.go +++ b/go-backend/service/organization_http.go @@ -1,233 +1,233 @@ package service -import ( - "encoding/json" - "github.com/gorilla/mux" - logger "github.com/sirupsen/logrus" - "joshsoftware/peerly/db" - "net/http" - "strconv" -) - -func getOrganizationByDomainNameHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - org, err := deps.Store.GetOrganizationByDomainName(req.Context(), vars["domainName"]) - if err != nil { - logger.WithField("err", err.Error()).Error("Error retrieving organization by domain name: " + vars["domainName"]) - rw.WriteHeader(http.StatusNotFound) - return - } - respBytes, err := json.Marshal(org) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organization by domain name: " + org.DomainName) - rw.WriteHeader(http.StatusInternalServerError) - return - } - rw.Header().Add("Content-Type", "application/json") - rw.Write(respBytes) - }) -} - -// @Title listOrganizationHandler -// @Description list all Organizations -// @Router /organizations [get] -// @Accept json -// @Success 200 {object} -// @Failure 400 {object} -func listOrganizationHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - organizations, err := deps.Store.ListOrganizations(req.Context()) - if err != nil { - logger.WithField("err", err.Error()).Error("Error listing organizations") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - respBytes, err := json.Marshal(organizations) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organizations data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.Write(respBytes) - }) -} - -// @Title createOrganizationHandler -// @Description Create Organizations -// @Router /organizations [post] -// @Accept json -// @Success 200 {object} -// @Failure 400 {object} -func createOrganizationHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - var organization db.Organization - err := json.NewDecoder(req.Body).Decode(&organization) - if err != nil { - rw.WriteHeader(http.StatusBadRequest) - logger.WithField("err", err.Error()).Error("Error while decoding organization data") - return - } - - errorResponse, valid := organization.Validate() - if !valid { - respBytes, err := json.Marshal(errorResponse) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organization data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.WriteHeader(http.StatusBadRequest) - rw.Write(respBytes) - return - } - - var createdOrganization db.Organization - createdOrganization, err = deps.Store.CreateOrganization(req.Context(), organization) - if err != nil { - rw.WriteHeader(http.StatusInternalServerError) - logger.WithField("err", err.Error()).Error("Error create organization") - return - } - - respBytes, err := json.Marshal(createdOrganization) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organizations data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.WriteHeader(http.StatusCreated) - rw.Write(respBytes) - rw.Header().Add("Content-Type", "application/json") - }) -} - -// @Title updateOrganizationHandler -// @Description Update Organizations -// @Router /organizations/:id [put] -// @Accept json -// @Success 200 {object} -// @Failure 400 {object} -func updateOrganizationHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - logger.WithField("err", err.Error()).Error("Error id key is missing") - rw.WriteHeader(http.StatusBadRequest) - return - } - - var organization db.Organization - err = json.NewDecoder(req.Body).Decode(&organization) - if err != nil { - rw.WriteHeader(http.StatusBadRequest) - logger.WithField("err", err.Error()).Error("Error while decoding organization") - return - } - - errorResponse, valid := organization.Validate() - if !valid { - respBytes, err := json.Marshal(errorResponse) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organizations data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.WriteHeader(http.StatusBadRequest) - rw.Write(respBytes) - return - } - - var updatedOrganization db.Organization - updatedOrganization, err = deps.Store.UpdateOrganization(req.Context(), organization, id) - if err != nil { - rw.WriteHeader(http.StatusInternalServerError) - logger.WithField("err", err.Error()).Error("Error while updating organization") - return - } - - respBytes, err := json.Marshal(updatedOrganization) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organizations data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.WriteHeader(http.StatusOK) - rw.Write(respBytes) - rw.Header().Add("Content-Type", "application/json") - return - }) -} - -// @Title deleteOrganizationHandler -// @Description Delete Organizations -// @Router /organizations/:id [delete] -// @Accept json -// @Success 200 {object} -// @Failure 400 {object} -func deleteOrganizationHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - logger.WithField("err", err.Error()).Error("Error id key is missing") - rw.WriteHeader(http.StatusBadRequest) - return - } - - err = deps.Store.DeleteOrganization(req.Context(), id) - if err != nil { - logger.WithField("err", err.Error()).Error("Error while deleting organization") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.WriteHeader(http.StatusOK) - rw.Header().Add("Content-Type", "application/json") - }) -} - -// @Title getOrganizationHandler -// @Description get Organizations -// @Router /organizations/:id [get] -// @Accept json -// @Success 200 {object} -// @Failure 400 {object} -func getOrganizationHandler(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - id, err := strconv.Atoi(vars["id"]) - if err != nil { - logger.WithField("err", err.Error()).Error("Error id key is missing") - rw.WriteHeader(http.StatusBadRequest) - return - } - - organization, err := deps.Store.GetOrganization(req.Context(), id) - if err != nil { - logger.WithField("err", err.Error()).Error("Error while fetching organization") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - respBytes, err := json.Marshal(organization) - if err != nil { - logger.WithField("err", err.Error()).Error("Error marshaling organization data") - rw.WriteHeader(http.StatusInternalServerError) - return - } - - rw.Header().Add("Content-Type", "application/json") - rw.Write(respBytes) - }) -} +// import ( +// "encoding/json" +// "github.com/gorilla/mux" +// logger "github.com/sirupsen/logrus" +// "joshsoftware/peerly/db" +// "net/http" +// "strconv" +// ) + +// func getOrganizationByDomainNameHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// vars := mux.Vars(req) +// org, err := deps.Store.GetOrganizationByDomainName(req.Context(), vars["domainName"]) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error retrieving organization by domain name: " + vars["domainName"]) +// rw.WriteHeader(http.StatusNotFound) +// return +// } +// respBytes, err := json.Marshal(org) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organization by domain name: " + org.DomainName) +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } +// rw.Header().Add("Content-Type", "application/json") +// rw.Write(respBytes) +// }) +// } + +// // @Title listOrganizationHandler +// // @Description list all Organizations +// // @Router /organizations [get] +// // @Accept json +// // @Success 200 {object} +// // @Failure 400 {object} +// // func listOrganizationHandler(deps Dependencies) http.HandlerFunc { +// // return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// // organizations, err := deps.Store.ListOrganizations(req.Context()) +// // if err != nil { +// // logger.WithField("err", err.Error()).Error("Error listing organizations") +// // rw.WriteHeader(http.StatusInternalServerError) +// // return +// // } + +// // respBytes, err := json.Marshal(organizations) +// // if err != nil { +// // logger.WithField("err", err.Error()).Error("Error marshaling organizations data") +// // rw.WriteHeader(http.StatusInternalServerError) +// // return +// // } + +// // rw.Header().Add("Content-Type", "application/json") +// // rw.Write(respBytes) +// // }) +// // } + +// // @Title createOrganizationHandler +// // @Description Create Organizations +// // @Router /organizations [post] +// // @Accept json +// // @Success 200 {object} +// // @Failure 400 {object} +// func createOrganizationHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// var organization db.Organization +// err := json.NewDecoder(req.Body).Decode(&organization) +// if err != nil { +// rw.WriteHeader(http.StatusBadRequest) +// logger.WithField("err", err.Error()).Error("Error while decoding organization data") +// return +// } + +// errorResponse, valid := organization.Validate() +// if !valid { +// respBytes, err := json.Marshal(errorResponse) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organization data") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.Header().Add("Content-Type", "application/json") +// rw.WriteHeader(http.StatusBadRequest) +// rw.Write(respBytes) +// return +// } + +// var createdOrganization db.Organization +// createdOrganization, err = deps.Store.CreateOrganization(req.Context(), organization) +// if err != nil { +// rw.WriteHeader(http.StatusInternalServerError) +// logger.WithField("err", err.Error()).Error("Error create organization") +// return +// } + +// respBytes, err := json.Marshal(createdOrganization) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organizations data") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.WriteHeader(http.StatusCreated) +// rw.Write(respBytes) +// rw.Header().Add("Content-Type", "application/json") +// }) +// } + +// // @Title updateOrganizationHandler +// // @Description Update Organizations +// // @Router /organizations/:id [put] +// // @Accept json +// // @Success 200 {object} +// // @Failure 400 {object} +// func updateOrganizationHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// vars := mux.Vars(req) +// id, err := strconv.Atoi(vars["id"]) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error id key is missing") +// rw.WriteHeader(http.StatusBadRequest) +// return +// } + +// var organization db.Organization +// err = json.NewDecoder(req.Body).Decode(&organization) +// if err != nil { +// rw.WriteHeader(http.StatusBadRequest) +// logger.WithField("err", err.Error()).Error("Error while decoding organization") +// return +// } + +// errorResponse, valid := organization.Validate() +// if !valid { +// respBytes, err := json.Marshal(errorResponse) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organizations data") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.Header().Add("Content-Type", "application/json") +// rw.WriteHeader(http.StatusBadRequest) +// rw.Write(respBytes) +// return +// } + +// var updatedOrganization db.Organization +// updatedOrganization, err = deps.Store.UpdateOrganization(req.Context(), organization, id) +// if err != nil { +// rw.WriteHeader(http.StatusInternalServerError) +// logger.WithField("err", err.Error()).Error("Error while updating organization") +// return +// } + +// respBytes, err := json.Marshal(updatedOrganization) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organizations data") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.WriteHeader(http.StatusOK) +// rw.Write(respBytes) +// rw.Header().Add("Content-Type", "application/json") +// return +// }) +// } + +// // @Title deleteOrganizationHandler +// // @Description Delete Organizations +// // @Router /organizations/:id [delete] +// // @Accept json +// // @Success 200 {object} +// // @Failure 400 {object} +// func deleteOrganizationHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// vars := mux.Vars(req) +// id, err := strconv.Atoi(vars["id"]) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error id key is missing") +// rw.WriteHeader(http.StatusBadRequest) +// return +// } + +// err = deps.Store.DeleteOrganization(req.Context(), id) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error while deleting organization") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.WriteHeader(http.StatusOK) +// rw.Header().Add("Content-Type", "application/json") +// }) +// } + +// // @Title getOrganizationHandler +// // @Description get Organizations +// // @Router /organizations/:id [get] +// // @Accept json +// // @Success 200 {object} +// // @Failure 400 {object} +// func getOrganizationHandler(deps Dependencies) http.HandlerFunc { +// return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// vars := mux.Vars(req) +// id, err := strconv.Atoi(vars["id"]) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error id key is missing") +// rw.WriteHeader(http.StatusBadRequest) +// return +// } + +// organization, err := deps.Store.GetOrganization(req.Context(), id) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error while fetching organization") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// respBytes, err := json.Marshal(organization) +// if err != nil { +// logger.WithField("err", err.Error()).Error("Error marshaling organization data") +// rw.WriteHeader(http.StatusInternalServerError) +// return +// } + +// rw.Header().Add("Content-Type", "application/json") +// rw.Write(respBytes) +// }) +// } diff --git a/go-backend/service/recognition_http.go b/go-backend/service/recognition_http.go index e4edf3c44..c7abb4b28 100644 --- a/go-backend/service/recognition_http.go +++ b/go-backend/service/recognition_http.go @@ -58,7 +58,7 @@ func createRecognitionHandler(deps Dependencies) http.HandlerFunc { // validate that given organization_id is present in database or not - _, err = deps.Store.GetOrganization(req.Context(), organizationID) + // _, err = deps.Store.GetOrganization(req.Context(), organizationID) if err != nil { logger.WithField("err", err.Error()).Error("Error while fetching given organization") repsonse(rw, http.StatusBadRequest, errorResponse{ diff --git a/go-backend/service/router.go b/go-backend/service/router.go index c3e6de4a2..5b18eeff7 100644 --- a/go-backend/service/router.go +++ b/go-backend/service/router.go @@ -6,7 +6,6 @@ import ( "net/http" "strconv" "strings" - "joshsoftware/peerly/config" ae "joshsoftware/peerly/apperrors" @@ -59,20 +58,20 @@ func InitRouter(deps Dependencies) (router *mux.Router) { router.Handle("/logout", jwtAuthMiddleware(handleLogout(deps), deps)).Methods(http.MethodDelete).Headers(versionHeader, v1) // TODO: Finish login system - router.HandleFunc("/auth/google", handleAuth(deps)).Methods(http.MethodGet) + // router.HandleFunc("/auth/google", handleAuth(deps)).Methods(http.MethodGet) // organizations routes - router.Handle("/organizations", jwtAuthMiddleware(listOrganizationHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + // router.Handle("/organizations", jwtAuthMiddleware(listOrganizationHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) - router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(getOrganizationHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + // router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(getOrganizationHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) - router.Handle("/organizations/{domainName}", jwtAuthMiddleware(getOrganizationByDomainNameHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + // router.Handle("/organizations/{domainName}", jwtAuthMiddleware(getOrganizationByDomainNameHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) - router.Handle("/organizations", jwtAuthMiddleware(createOrganizationHandler(deps), deps)).Methods(http.MethodPost).Headers(versionHeader, v1) + // router.Handle("/organizations", jwtAuthMiddleware(createOrganizationHandler(deps), deps)).Methods(http.MethodPost).Headers(versionHeader, v1) - router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(deleteOrganizationHandler(deps), deps)).Methods(http.MethodDelete).Headers(versionHeader, v1) + // router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(deleteOrganizationHandler(deps), deps)).Methods(http.MethodDelete).Headers(versionHeader, v1) - router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(updateOrganizationHandler(deps), deps)).Methods(http.MethodPut).Headers(versionHeader, v1) + // router.Handle("/organizations/{id:[0-9]+}", jwtAuthMiddleware(updateOrganizationHandler(deps), deps)).Methods(http.MethodPut).Headers(versionHeader, v1) // badges routes router.Handle("/organizations/{organization_id:[0-9]+}/badges", jwtAuthMiddleware(createBadgeHandler(deps), deps)).Methods(http.MethodPost).Headers(versionHeader, v1) @@ -86,7 +85,7 @@ func InitRouter(deps Dependencies) (router *mux.Router) { router.Handle("/organizations/{organization_id:[0-9]+}/badges/{id:[0-9]+}", jwtAuthMiddleware(deleteBadgeHandler(deps), deps)).Methods(http.MethodDelete).Headers(versionHeader, v1) // Get S3 signed URL - router.Handle("/s3_signed_url", jwtAuthMiddleware(getS3SignedURLHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) + // router.Handle("/s3_signed_url", jwtAuthMiddleware(getS3SignedURLHandler(deps), deps)).Methods(http.MethodGet).Headers(versionHeader, v1) // Recognition Hi5 routes @@ -104,6 +103,7 @@ func InitRouter(deps Dependencies) (router *mux.Router) { // JWT token verification func jwtAuthMiddleware(next http.Handler, deps Dependencies) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) totalFields := 2 authHeader := strings.Split(r.Header.Get("Authorization"), "Bearer ") diff --git a/go-backend/service/session_http.go b/go-backend/service/session_http.go index 90145afcc..c7e2c0984 100644 --- a/go-backend/service/session_http.go +++ b/go-backend/service/session_http.go @@ -1,14 +1,14 @@ package service import ( - "encoding/json" - "io/ioutil" + // "encoding/json" + // "io/ioutil" ae "joshsoftware/peerly/apperrors" "joshsoftware/peerly/config" "joshsoftware/peerly/db" log "joshsoftware/peerly/util/log" "net/http" - "net/url" + // "net/url" "strconv" "time" @@ -43,155 +43,7 @@ type authBody struct { Token string `json:"token"` } -func handleAuth(deps Dependencies) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - // We're going to use req.Context() a lot here, so create a simple variable first - ctx := req.Context() - - // We should have a URL-encoded parameter called "authCode" here, which we use - // to exchange for an auth *token* from Google via oauth 2. - auth, ok := req.URL.Query()["code"] - if !ok || len(auth[0]) < 1 { - log.Error(ae.ErrNoAuthCode, "No 'code' URL parameter provided", ae.ErrNoAuthCode) - ae.JSONError(rw, http.StatusForbidden, ae.ErrNoAuthCode) - return - } - - // Now, exchange the authCode for an authentication *token* with the OAuth provider - // To do this, we do a POST to https://oauth2.googleapis.com/token which will respond - // with a 200 OK and a JSON response body containing keys for access_token, id_token, - // expires_in, token_type, scope and refresh_token - resp, err := http.PostForm("https://oauth2.googleapis.com/token", - url.Values{ - "code": {auth[0]}, - "client_id": {config.ReadEnvString("GOOGLE_KEY")}, - "client_secret": {config.ReadEnvString("GOOGLE_SECRET")}, - "scope": {""}, - "grant_type": {"authorization_code"}, - "redirect_uri": {config.ReadEnvString("GOOGLE_REDIRECT")}, - }, - ) - // TEST URL (FXIME: Delete this from final prod code, leaving it here for future debugging if needed) - // https://accounts.google.com/o/oauth2/auth?client_id=749817093280-6h2emqcqsi84murdsdr543kuemvrlr9t.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A33001%2Fauth%2Fgoogle&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=state - - if err != nil { - log.Error(ae.ErrAuthCodeRequestFail, "Google didn't like the auth code we sent", err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - var token OAuthToken - payload, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Error(ae.ErrReadingResponseBody, "Error reading response body: "+string(payload), err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - err = json.Unmarshal(payload, &token) - if err != nil { - log.Error(ae.ErrJSONParseFail, "json.Unmarshal error on payload "+string(payload), err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - client := &http.Client{} - req, err = http.NewRequest("GET", "https://www.googleapis.com/oauth2/v2/userinfo", nil) - if err != nil { - log.Error(ae.ErrHTTPRequestFailed, "Failed to create oauth request", err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - req.Header.Set("Authorization", "Bearer "+token.AccessToken) - resp, err = client.Do(req) - if err != nil { - log.Error(ae.ErrHTTPRequestFailed, "Failure executing HTTP request to https://www.googleapis.com/oauth2/v2/userinfo", err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - user := OAuthUser{} - payload, err = ioutil.ReadAll(resp.Body) - if err != nil { - log.Error(ae.ErrReadingResponseBody, "Error reading response body: "+string(payload), err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - err = json.Unmarshal(payload, &user) - if err != nil { - log.Error(ae.ErrJSONParseFail, "Failure parsing JSON in Unmarshalling OAuthUser"+string(payload), err) - ae.JSONError(rw, http.StatusInternalServerError, err) - } - - // Before going any further, check to see if a domain actually exists on the user object, because - // if not, then there's no point in going any further. - if len(user.Domain) < 3 { // Shortest possible FQDN would be y.z - log.Error(ae.ErrNoUserDomain, "No valid domain associated with user "+user.Email+" (domain: "+user.Domain+")", ae.ErrNoUserDomain) - ae.JSONError(rw, http.StatusForbidden, ae.ErrNoUserDomain) - return - } - - // See if there's an existing user that matches the oAuth user - // Check the OAuth User's domain and see if it's already in our database - // TODO - We need a way to test this both programmatically and by hand. - // That necessitates a Google account associated w/ a domain that isn't Josh Software - org, err := deps.Store.GetOrganizationByDomainName(ctx, user.Domain) - if err != nil { - // Log error, push out a JSON response, and halt authentication - log.Error(ae.ErrDomainNotRegistered(user.Email), ("Domain: " + user.Domain + " Email " + user.Email), err) - ae.JSONError(rw, http.StatusForbidden, err) - return - } - - // See if there's an existing user that matches the oAuth user - existingUser, err := deps.Store.GetUserByEmail(ctx, user.Email) - if err != nil && err != ae.ErrRecordNotFound { - log.Error(ae.ErrUnknown, "Unknown/unexpected error while looking for existing user "+user.Email, err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - if err == ae.ErrRecordNotFound { - - // Organization DOES exist in the database. Create the user. - existingUser, err = deps.Store.CreateNewUser(ctx, db.User{ - Email: user.Email, - ProfileImageURL: user.PictureURL, - OrgID: org.ID, - }) - if err != nil { - log.Error(ae.ErrUnknown, "Unknown/unexpected error while creating new user "+user.Email, err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - } - - // By the time we get here, we definitely have an existingUser object. - // Looks like a valid user authenticated by Google. User's org is in our orgs table. Issue a JWT. - authToken, err := newJWT(existingUser.ID, org.ID) - if err != nil { - log.Error(ae.ErrUnknown, "Unknown/unexpected error while creating JWT for "+existingUser.Email, err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - - // If we get this far, we have a valid authToken. Go ahead and return it to the client. - respBody, err := json.Marshal(authBody{Message: "Authentication Successful", Token: authToken}) - if err != nil { - log.Error(ae.ErrJSONParseFail, "Error parsing JSON for token response, token: "+authToken, err) - ae.JSONError(rw, http.StatusInternalServerError, err) - return - } - rw.WriteHeader(http.StatusOK) - rw.Header().Add("Content-type", "application/json") - rw.Header().Add("Authorization", authToken) - rw.Write(respBody) - - }) // End HTTP handler -} // newJWT() - Creates and returns a new JSON Web Token to be sent to an API consumer on valid // authentication, so they can re-use it by sending it in the Authorization header on subsequent diff --git a/go-backend/util/utils.go b/go-backend/util/utils.go new file mode 100644 index 000000000..8434135f8 --- /dev/null +++ b/go-backend/util/utils.go @@ -0,0 +1,17 @@ +package util + +import ( + "math/rand" + "time" +) + +func GenerateRandomNumber(n int) string { + seed := time.Now().UnixNano() + r := rand.New(rand.NewSource(seed)) + charsets := []rune("0123456789") + letters := make([]rune, n) + for i := range letters { + letters[i] = charsets[r.Intn(len(charsets))] + } + return string(letters) +} \ No newline at end of file diff --git a/node-backend/models/sequelize.js b/node-backend/models/sequelize.js index 1bcf4b3b1..8d428960c 100644 --- a/node-backend/models/sequelize.js +++ b/node-backend/models/sequelize.js @@ -12,7 +12,7 @@ let sequelize; if (config.use_env_variable) { sequelize = new Sequelize(process.env[config.use_env_variable], config); } else { - sequelize = new Sequelize( + sequelize = new Sequelize( config.database, config.username, config.password,