diff --git a/api/devices_values.go b/api/devices_values.go index d237e03..67bde86 100644 --- a/api/devices_values.go +++ b/api/devices_values.go @@ -17,7 +17,6 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "io" "net/http" "os" "reflect" @@ -79,7 +78,8 @@ func (handler *DevicesValues) GetValuesDevice(c *gin.Context) { } // check if device is in profile (device owned by profile) - if !isDeviceInProfile(&profile, objectID) { + + if !utils.Contains(profile.Devices, objectID) { handler.logger.Error("REST - GET - GetValuesDevice - this device is not in your profile") c.JSON(http.StatusBadRequest, gin.H{"error": "this device is not in your profile"}) return @@ -132,7 +132,7 @@ func (handler *DevicesValues) GetValuesDevice(c *gin.Context) { deviceValues := make([]models.SensorValue, 0) for _, feature := range device.Features { path := handler.sensorGetValueURL + device.UUID + "/" + feature.Name - _, result, err := handler.getSensorValue(path) + _, result, err := utils.Get(path) if err != nil { handler.logger.Errorf("REST - GetValuesDevice - cannot get sensor value from remote service = %#v", err) // TODO manage errors @@ -196,7 +196,7 @@ func (handler *DevicesValues) PostValueDevice(c *gin.Context) { } // check if device is in profile (device owned by profile) - if !isDeviceInProfile(&profile, objectID) { + if !utils.Contains(profile.Devices, objectID) { handler.logger.Error("REST - POST - PostValueDevice - this is not your device") c.JSON(http.StatusBadRequest, gin.H{"error": "this device is not in your profile"}) return @@ -221,16 +221,6 @@ func (handler *DevicesValues) PostValueDevice(c *gin.Context) { // ------------------------------ Private methods ------------------------------ -func (handler *DevicesValues) getSensorValue(url string) (int, string, error) { - response, err := http.Get(url) - if err != nil { - return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot get sensor value via HTTP") - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - return response.StatusCode, string(body), nil -} - func (handler *DevicesValues) sendViaGrpc(device *models.Device, value *models.DeviceState, apiToken string) error { handler.logger.Infof("gRPC - sendViaGrpc - Called with value = %#v and apiToken = %s", value, apiToken) @@ -294,12 +284,6 @@ func (handler *DevicesValues) getDevice(deviceID primitive.ObjectID) (models.Dev return device, err } -// TODO this private function is called also by online service. I should move this in a utility package -// check if the profile contains that device -> if profile is the owner of that device -func isDeviceInProfile(profile *models.Profile, deviceID primitive.ObjectID) bool { - return utils.Contains(profile.Devices, deviceID) -} - func getType(value interface{}) string { t := reflect.TypeOf(value) if t.Kind() == reflect.Ptr { diff --git a/api/fcm_token.go b/api/fcm_token.go index f9254e6..6fb86d8 100644 --- a/api/fcm_token.go +++ b/api/fcm_token.go @@ -5,7 +5,6 @@ import ( "api-server/db" "api-server/models" "api-server/utils" - "bytes" "encoding/json" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" @@ -13,7 +12,6 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "golang.org/x/net/context" - "io" "net/http" "os" ) @@ -98,7 +96,7 @@ func (handler *FCMToken) PostFCMToken(c *gin.Context) { func (handler *FCMToken) initFCMTokenViaHTTP(obj *InitFCMTokenReq) error { // check if service is available calling keep-alive // TODO remove this in a production code - _, _, keepAliveErr := handler.keepAliveOnlineService(handler.keepAliveOnlineURL) + _, _, keepAliveErr := utils.Get(handler.keepAliveOnlineURL) if keepAliveErr != nil { return customerrors.Wrap(http.StatusInternalServerError, keepAliveErr, "Cannot call keepAlive of remote online service") } @@ -109,30 +107,9 @@ func (handler *FCMToken) initFCMTokenViaHTTP(obj *InitFCMTokenReq) error { return customerrors.Wrap(http.StatusInternalServerError, err, "Cannot create payload to call fcmToken service") } - _, _, err = handler.initFCMToken(handler.fcmTokenOnlineURL, payloadJSON) + _, _, err = utils.Post(handler.fcmTokenOnlineURL, payloadJSON) if err != nil { return customerrors.Wrap(http.StatusInternalServerError, err, "Cannot init fcmToken") } return nil } - -func (handler *FCMToken) keepAliveOnlineService(urlKeepAlive string) (int, string, error) { - response, err := http.Get(urlKeepAlive) - if err != nil { - return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call keepAlive of the remote online service via HTTP") - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - return response.StatusCode, string(body), nil -} - -func (handler *FCMToken) initFCMToken(urlOnline string, payloadJSON []byte) (int, string, error) { - var payloadBody = bytes.NewBuffer(payloadJSON) - response, err := http.Post(urlOnline, "application/json", payloadBody) - if err != nil { - return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call fcmToken service via HTTP") - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - return response.StatusCode, string(body), nil -} diff --git a/api/online.go b/api/online.go index d191ec1..5794294 100644 --- a/api/online.go +++ b/api/online.go @@ -72,7 +72,7 @@ func (handler *Online) GetOnline(c *gin.Context) { } // check if device is in profile (device owned by profile) - if !isDeviceInProfile(&profile, objectID) { + if !utils.Contains(profile.Devices, objectID) { handler.logger.Error("REST - GET - GetOnline - this device is not in your profile") c.JSON(http.StatusBadRequest, gin.H{"error": "this device is not in your profile"}) return diff --git a/api/profiles.go b/api/profiles.go index cb8d140..702a9b6 100644 --- a/api/profiles.go +++ b/api/profiles.go @@ -6,6 +6,7 @@ import ( "api-server/utils" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" @@ -16,6 +17,11 @@ import ( "time" ) +// ProfileUpdateFCMTokenReq struct +type ProfileUpdateFCMTokenReq struct { + FCMToken string `json:"fcmToken" validate:"required"` +} + // GithubResponse struct type GithubResponse struct { Login string `json:"login"` @@ -30,15 +36,17 @@ type Profiles struct { collProfiles *mongo.Collection ctx context.Context logger *zap.SugaredLogger + validate *validator.Validate } // NewProfiles function -func NewProfiles(ctx context.Context, logger *zap.SugaredLogger, client *mongo.Client) *Profiles { +func NewProfiles(ctx context.Context, logger *zap.SugaredLogger, client *mongo.Client, validate *validator.Validate) *Profiles { return &Profiles{ client: client, collProfiles: db.GetCollections(client).Profiles, ctx: ctx, logger: logger, + validate: validate, } } @@ -63,14 +71,14 @@ func (handler *Profiles) GetProfile(c *gin.Context) { c.JSON(http.StatusOK, &profileRes) } -// PostProfilesToken function -func (handler *Profiles) PostProfilesToken(c *gin.Context) { - handler.logger.Info("REST - POST - PostProfilesToken called") +// PostProfilesAPIToken function to regenerate the API Token +func (handler *Profiles) PostProfilesAPIToken(c *gin.Context) { + handler.logger.Info("REST - POST - PostProfilesAPIToken called") // get profileID from path params profileID, errID := primitive.ObjectIDFromHex(c.Param("id")) if errID != nil { - handler.logger.Error("REST - POST - PostProfilesToken - wrong format of the path param 'id'") + handler.logger.Error("REST - POST - PostProfilesAPIToken - wrong format of the path param 'id'") c.JSON(http.StatusBadRequest, gin.H{"error": "wrong format of the path param 'id'"}) return } @@ -79,14 +87,14 @@ func (handler *Profiles) PostProfilesToken(c *gin.Context) { session := sessions.Default(c) profileSession, err := utils.GetProfileFromSession(&session) if err != nil { - handler.logger.Error("REST - POST - PostProfilesToken - cannot find profile in session") + handler.logger.Error("REST - POST - PostProfilesAPIToken - cannot find profile in session") c.JSON(http.StatusUnauthorized, gin.H{"error": "cannot find profile in session"}) return } // check if the profile you are trying to update (path param) is your profile (session profile) if profileSession.ID != profileID { - handler.logger.Error("REST - POST - ProfilesToken - Current profileID is different than profileID in session") + handler.logger.Error("REST - POST - PostProfilesAPIToken - Current profileID is different than profileID in session") c.JSON(http.StatusBadRequest, gin.H{"error": "cannot re-generate APIToken for a different profile then yours"}) return } @@ -102,9 +110,68 @@ func (handler *Profiles) PostProfilesToken(c *gin.Context) { }, }) if err != nil { - handler.logger.Error("REST - POST - PostProfilesToken - Cannot update profile with the new apiToken") + handler.logger.Error("REST - POST - PostProfilesAPIToken - Cannot update profile with the new apiToken") c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot update apiToken"}) return } c.JSON(http.StatusOK, gin.H{"apiToken": apiToken}) } + +// PostProfilesFCMToken function to store the Firebase Cloud Messaging Token +func (handler *Profiles) PostProfilesFCMToken(c *gin.Context) { + handler.logger.Info("REST - POST - PostProfilesFCMToken called") + + // get profileID from path params + profileID, errID := primitive.ObjectIDFromHex(c.Param("id")) + if errID != nil { + handler.logger.Error("REST - POST - PostProfilesFCMToken - wrong format of the path param 'id'") + c.JSON(http.StatusBadRequest, gin.H{"error": "wrong format of the path param 'id'"}) + return + } + + // retrieve current profile object from database (using session profile as input) + session := sessions.Default(c) + profileSession, err := utils.GetProfileFromSession(&session) + if err != nil { + handler.logger.Error("REST - POST - PostProfilesFCMToken - cannot find profile in session") + c.JSON(http.StatusUnauthorized, gin.H{"error": "cannot find profile in session"}) + return + } + + // check if the profile you are trying to update (path param) is your profile (session profile) + if profileSession.ID != profileID { + handler.logger.Error("REST - POST - PostProfilesFCMToken - Current profileID is different than profileID in session") + c.JSON(http.StatusBadRequest, gin.H{"error": "cannot set FCMToken for a different profile then yours"}) + return + } + + var profileUpdateFCMTokenReq ProfileUpdateFCMTokenReq + if err = c.ShouldBindJSON(&profileUpdateFCMTokenReq); err != nil { + handler.logger.Error("REST - POST - PostProfilesFCMToken - Cannot bind request body", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request payload"}) + return + } + + err = handler.validate.Struct(profileUpdateFCMTokenReq) + if err != nil { + handler.logger.Errorf("REST - POST - PostProfilesFCMToken - request body is not valid, err %#v", err) + var errFields = utils.GetErrorMessage(err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body, these fields are not valid:" + errFields}) + return + } + + _, err = handler.collProfiles.UpdateOne(handler.ctx, bson.M{ + "_id": profileSession.ID, + }, bson.M{ + "$set": bson.M{ + "fcmToken": profileUpdateFCMTokenReq.FCMToken, + "modifiedAt": time.Now(), + }, + }) + if err != nil { + handler.logger.Error("REST - POST - PostProfilesFCMToken - Cannot update profile with fcmToken") + c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot set fcmToken"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Profile update with FCM Token"}) +} diff --git a/api/register.go b/api/register.go index cd3ea3a..ddac37e 100644 --- a/api/register.go +++ b/api/register.go @@ -6,7 +6,6 @@ import ( "api-server/db" "api-server/models" "api-server/utils" - "bytes" "encoding/json" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" @@ -19,7 +18,6 @@ import ( "go.uber.org/zap" "golang.org/x/net/context" "google.golang.org/grpc" - "io" "net/http" "os" "time" @@ -210,7 +208,7 @@ func (handler *Register) PostRegister(c *gin.Context) { func (handler *Register) registerSensorViaHTTP(device *models.Device, profileFound *models.Profile) error { // check if service is available calling keep-alive // TODO remove this in a production code - _, _, keepAliveErr := handler.keepAliveSensorService(handler.keepAliveSensorURL) + _, _, keepAliveErr := utils.Get(handler.keepAliveSensorURL) if keepAliveErr != nil { return customerrors.Wrap(http.StatusInternalServerError, keepAliveErr, "Cannot call keepAlive of remote register service") } @@ -236,7 +234,7 @@ func (handler *Register) registerSensorViaHTTP(device *models.Device, profileFou } for _, feature := range device.Features { - _, _, err := handler.registerSensor(handler.registerSensorURL+feature.Name, payloadJSON) + _, _, err := utils.Post(handler.registerSensorURL+feature.Name, payloadJSON) if err != nil { return customerrors.Wrap(http.StatusInternalServerError, err, "Cannot register sensor device feature "+feature.Name) } @@ -300,27 +298,6 @@ func (handler *Register) registerControllerViaGRPC(device *models.Device, profil return r.GetStatus(), r.GetMessage(), nil } -func (handler *Register) keepAliveSensorService(urlKeepAlive string) (int, string, error) { - response, err := http.Get(urlKeepAlive) - if err != nil { - return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call keepAlive of the remote register service via HTTP") - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - return response.StatusCode, string(body), nil -} - -func (handler *Register) registerSensor(urlRegister string, payloadJSON []byte) (int, string, error) { - var payloadBody = bytes.NewBuffer(payloadJSON) - response, err := http.Post(urlRegister, "application/json", payloadBody) - if err != nil { - return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot register to sensor service via HTTP") - } - defer response.Body.Close() - body, _ := io.ReadAll(response.Body) - return response.StatusCode, string(body), nil -} - func (handler *Register) insertDevice(device *models.Device, profile *models.Profile) error { // start-session dbSession, err := handler.client.StartSession() diff --git a/initialization/server.go b/initialization/server.go index 6352566..1315031 100644 --- a/initialization/server.go +++ b/initialization/server.go @@ -119,7 +119,7 @@ func RegisterRoutes(ctx context.Context, router *gin.Engine, cookieStore *cookie devices = api.NewDevices(ctx, logger, client) assignDevices = api.NewAssignDevice(ctx, logger, client, validate) devicesValues = api.NewDevicesValues(ctx, logger, client, validate) - profiles = api.NewProfiles(ctx, logger, client) + profiles = api.NewProfiles(ctx, logger, client, validate) register = api.NewRegister(ctx, logger, client, validate) // FCM = Firebase Cloud Messaging => identify a smartphone on Firebase to send notifications fcmToken = api.NewFCMToken(ctx, logger, client, validate) @@ -153,7 +153,8 @@ func RegisterRoutes(ctx context.Context, router *gin.Engine, cookieStore *cookie private.DELETE("/homes/:id/rooms/:rid", homes.DeleteRoom) private.GET("/profile", profiles.GetProfile) - private.POST("/profiles/:id/tokens", profiles.PostProfilesToken) + private.POST("/profiles/:id/tokens", profiles.PostProfilesAPIToken) + private.POST("/profiles/:id/fcmTokens", profiles.PostProfilesFCMToken) private.GET("/devices", devices.GetDevices) private.PUT("/devices/:id", assignDevices.PutAssignDeviceToHomeRoom) diff --git a/integration_tests/profiles_test.go b/integration_tests/profiles_test.go index defb5f0..94ecc58 100644 --- a/integration_tests/profiles_test.go +++ b/integration_tests/profiles_test.go @@ -5,6 +5,7 @@ import ( "api-server/db" "api-server/initialization" "api-server/testuutils" + "bytes" "encoding/json" "github.com/gin-gonic/gin" . "github.com/onsi/ginkgo/v2" @@ -22,6 +23,10 @@ type newTokenResponse struct { APIToken string `json:"apiToken"` } +type updateFCMTokenResponse struct { + Message string `json:"message"` +} + var _ = Describe("Profiles", func() { var ctx context.Context var logger *zap.SugaredLogger @@ -47,7 +52,7 @@ var _ = Describe("Profiles", func() { testuutils.DropAllCollections(ctx, collProfiles, collHomes, collDevices) }) - Context("calling profiles api", func() { + Context("calling profiles api GET", func() { It("should return logged profile", func() { jwtToken, cookieSession := testuutils.GetJwt(router) profileRes := testuutils.GetLoggedProfile(router, jwtToken, cookieSession) @@ -56,8 +61,10 @@ var _ = Describe("Profiles", func() { Expect(profileRes.Devices).To(HaveLen(0)) Expect(profileRes.Github).To(Equal(api.DbGithubUserTestmock)) }) + }) - It("should generate a new profile api token", func() { + Context("calling profiles apiToken api POST", func() { + It("should generate a new profile apiToken", func() { jwtToken, cookieSession := testuutils.GetJwt(router) profileRes := testuutils.GetLoggedProfile(router, jwtToken, cookieSession) @@ -104,4 +111,110 @@ var _ = Describe("Profiles", func() { Expect(recorder.Body.String()).To(Equal(`{"error":"cannot re-generate APIToken for a different profile then yours"}`)) }) }) + + Context("calling profiles fcmToken api POST", func() { + It("should update existing profile with FCM Token", func() { + jwtToken, cookieSession := testuutils.GetJwt(router) + profileRes := testuutils.GetLoggedProfile(router, jwtToken, cookieSession) + + profileID := profileRes.ID.Hex() + profileUpdateFCMTokenReq := api.ProfileUpdateFCMTokenReq{ + FCMToken: "MOCKED_FCM_TOKEN", + } + var profileUpdateBuf bytes.Buffer + err := json.NewEncoder(&profileUpdateBuf).Encode(profileUpdateFCMTokenReq) + Expect(err).ShouldNot(HaveOccurred()) + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/profiles/"+profileID+"/fcmTokens", &profileUpdateBuf) + req.Header.Add("Cookie", cookieSession) + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Add("Content-Type", `application/json`) + router.ServeHTTP(recorder, req) + Expect(recorder.Code).To(Equal(http.StatusOK)) + var response updateFCMTokenResponse + err = json.Unmarshal(recorder.Body.Bytes(), &response) + Expect(err).ShouldNot(HaveOccurred()) + Expect(response.Message).To(Equal("Profile update with FCM Token")) + }) + + It("should return an error, if profileId is wrong", func() { + jwtToken, cookieSession := testuutils.GetJwt(router) + profileID := "bad_profile_id" + + profileUpdateFCMTokenReq := api.ProfileUpdateFCMTokenReq{ + FCMToken: "MOCKED_FCM_TOKEN", + } + var profileUpdateBuf bytes.Buffer + err := json.NewEncoder(&profileUpdateBuf).Encode(profileUpdateFCMTokenReq) + Expect(err).ShouldNot(HaveOccurred()) + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/profiles/"+profileID+"/fcmTokens", &profileUpdateBuf) + req.Header.Add("Cookie", cookieSession) + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Add("Content-Type", `application/json`) + router.ServeHTTP(recorder, req) + Expect(recorder.Code).To(Equal(http.StatusBadRequest)) + Expect(recorder.Body.String()).To(Equal(`{"error":"wrong format of the path param 'id'"}`)) + }) + + It("should return an error, if profileId is not the one in session", func() { + jwtToken, cookieSession := testuutils.GetJwt(router) + profileID := primitive.NewObjectID() + + profileUpdateFCMTokenReq := api.ProfileUpdateFCMTokenReq{ + FCMToken: "MOCKED_FCM_TOKEN", + } + var profileUpdateBuf bytes.Buffer + err := json.NewEncoder(&profileUpdateBuf).Encode(profileUpdateFCMTokenReq) + Expect(err).ShouldNot(HaveOccurred()) + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/profiles/"+profileID.Hex()+"/fcmTokens", &profileUpdateBuf) + req.Header.Add("Cookie", cookieSession) + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Add("Content-Type", `application/json`) + router.ServeHTTP(recorder, req) + Expect(recorder.Code).To(Equal(http.StatusBadRequest)) + Expect(recorder.Body.String()).To(Equal(`{"error":"cannot set FCMToken for a different profile then yours"}`)) + }) + + It("should return an error, if request body is not a JSON", func() { + jwtToken, cookieSession := testuutils.GetJwt(router) + profileRes := testuutils.GetLoggedProfile(router, jwtToken, cookieSession) + profileID := profileRes.ID.Hex() + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/profiles/"+profileID+"/fcmTokens", nil) + req.Header.Add("Cookie", cookieSession) + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Add("Content-Type", `application/json`) + router.ServeHTTP(recorder, req) + Expect(recorder.Code).To(Equal(http.StatusBadRequest)) + Expect(recorder.Body.String()).To(Equal(`{"error":"invalid request payload"}`)) + }) + + It("should return an error, if request body is not valid", func() { + jwtToken, cookieSession := testuutils.GetJwt(router) + profileRes := testuutils.GetLoggedProfile(router, jwtToken, cookieSession) + profileID := profileRes.ID.Hex() + + profileUpdateFCMTokenReq := api.ProfileUpdateFCMTokenReq{ + // FCMToken is a required field, but here it's omitted + } + var profileUpdateBuf bytes.Buffer + err := json.NewEncoder(&profileUpdateBuf).Encode(profileUpdateFCMTokenReq) + Expect(err).ShouldNot(HaveOccurred()) + + recorder := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/profiles/"+profileID+"/fcmTokens", &profileUpdateBuf) + req.Header.Add("Cookie", cookieSession) + req.Header.Add("Authorization", "Bearer "+jwtToken) + req.Header.Add("Content-Type", `application/json`) + router.ServeHTTP(recorder, req) + Expect(recorder.Code).To(Equal(http.StatusBadRequest)) + Expect(recorder.Body.String()).To(Equal(`{"error":"invalid request body, these fields are not valid: fcmtoken"}`)) + }) + }) }) diff --git a/models/profile.go b/models/profile.go index 1e9577d..d721ef8 100644 --- a/models/profile.go +++ b/models/profile.go @@ -21,6 +21,7 @@ type Profile struct { ID primitive.ObjectID `json:"id" bson:"_id"` Github GitHub `json:"github" bson:"github"` APIToken string `json:"apiToken" bson:"apiToken"` + FCMToken string `json:"fcmToken" bson:"fcmToken"` Devices []primitive.ObjectID `json:"devices" bson:"devices"` Homes []primitive.ObjectID `json:"homes" bson:"homes"` CreatedAt time.Time `json:"createdAt" bson:"createdAt"` diff --git a/utils/http.go b/utils/http.go new file mode 100644 index 0000000..3c80f2a --- /dev/null +++ b/utils/http.go @@ -0,0 +1,31 @@ +package utils + +import ( + "api-server/customerrors" + "bytes" + "io" + "net/http" +) + +// Get function +func Get(url string) (int, string, error) { + response, err := http.Get(url) + if err != nil { + return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call HTTP GET API of the remote service") + } + defer response.Body.Close() + body, _ := io.ReadAll(response.Body) + return response.StatusCode, string(body), nil +} + +// Post function +func Post(url string, payloadJSON []byte) (int, string, error) { + var payloadBody = bytes.NewBuffer(payloadJSON) + response, err := http.Post(url, "application/json", payloadBody) + if err != nil { + return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call HTTP POST API of the remote service") + } + defer response.Body.Close() + body, _ := io.ReadAll(response.Body) + return response.StatusCode, string(body), nil +}