Skip to content

Commit

Permalink
feat: add api to set FCM token to the profile
Browse files Browse the repository at this point in the history
Signed-off-by: Stefano Cappa <[email protected]>
  • Loading branch information
Ks89 committed Dec 27, 2024
1 parent 7208f3c commit 2a4c831
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 83 deletions.
24 changes: 4 additions & 20 deletions api/devices_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 2 additions & 25 deletions api/fcm_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import (
"api-server/db"
"api-server/models"
"api-server/utils"
"bytes"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"golang.org/x/net/context"
"io"
"net/http"
"os"
)
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion api/online.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 75 additions & 8 deletions api/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"`
Expand All @@ -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,
}
}

Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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"})
}
27 changes: 2 additions & 25 deletions api/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -19,7 +18,6 @@ import (
"go.uber.org/zap"
"golang.org/x/net/context"
"google.golang.org/grpc"
"io"
"net/http"
"os"
"time"
Expand Down Expand Up @@ -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")
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions initialization/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 2a4c831

Please sign in to comment.