Skip to content

Commit

Permalink
feat: add get online API + test
Browse files Browse the repository at this point in the history
Signed-off-by: Stefano Cappa <[email protected]>
  • Loading branch information
Ks89 committed Dec 25, 2024
1 parent a40d67b commit 7208f3c
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 12 deletions.
5 changes: 3 additions & 2 deletions api/devices_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,15 +285,16 @@ func (handler *DevicesValues) sendViaGrpc(device *models.Device, value *models.D
}

func (handler *DevicesValues) getDevice(deviceID primitive.ObjectID) (models.Device, error) {
handler.logger.Info("gRPC - getDevice - searching device with objectId: ", deviceID)
handler.logger.Debug("getDevice - searching device with objectId: ", deviceID)
var device models.Device
err := handler.collDevices.FindOne(handler.ctx, bson.M{
"_id": deviceID,
}).Decode(&device)
handler.logger.Info("Device found: ", device)
handler.logger.Debug("Device found: ", device)
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)
Expand Down
133 changes: 133 additions & 0 deletions api/online.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package api

import (
"api-server/customerrors"
"api-server/db"
"api-server/models"
"api-server/utils"
"encoding/json"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
"golang.org/x/net/context"
"io"
"net/http"
"os"
"time"
)

type onlineResponse struct {
UUID string `json:"uuid"`
APIToken string `json:"apiToken"`
CreatedAt int64 `json:"createdAt"`
ModifiedAt int64 `json:"modifiedAt"`
}

// Online struct
type Online struct {
client *mongo.Client
collDevices *mongo.Collection
collProfiles *mongo.Collection
ctx context.Context
logger *zap.SugaredLogger
onlineByUUIDURL string
}

// NewOnline function
func NewOnline(ctx context.Context, logger *zap.SugaredLogger, client *mongo.Client) *Online {
onlineServerURL := os.Getenv("HTTP_ONLINE_SERVER") + ":" + os.Getenv("HTTP_ONLINE_PORT")
onlineByUUIDURL := onlineServerURL + os.Getenv("HTTP_ONLINE_API")

return &Online{
client: client,
collDevices: db.GetCollections(client).Devices,
collProfiles: db.GetCollections(client).Profiles,
ctx: ctx,
logger: logger,
onlineByUUIDURL: onlineByUUIDURL,
}
}

// GetOnline function
func (handler *Online) GetOnline(c *gin.Context) {
handler.logger.Info("REST - GET - GetOnline called")

objectID, errID := primitive.ObjectIDFromHex(c.Param("id"))
if errID != nil {
handler.logger.Error("REST - GET - GetOnline - 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)
profile, err := utils.GetLoggedProfile(handler.ctx, &session, handler.collProfiles)
if err != nil {
handler.logger.Error("REST - GET - GetOnline - cannot find profile in session")
c.JSON(http.StatusUnauthorized, gin.H{"error": "cannot find profile in session"})
return
}

// check if device is in profile (device owned by profile)
if !isDeviceInProfile(&profile, 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
}
// get device from db
device, err := handler.getDevice(objectID)
if err != nil {
handler.logger.Error("REST - GET - GetOnline - cannot find device")
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot find device"})
return
}

_, result, err := handler.onlineByUUIDService(handler.onlineByUUIDURL + "/" + device.UUID)
if err != nil {
handler.logger.Errorf("REST - GetOnline - cannot get online from remote service = %#v", err)
if re, ok := err.(*customerrors.ErrorWrapper); ok {
handler.logger.Errorf("REST - GetOnline - cannot get online with status = %d, message = %s\n", re.Code, re.Message)
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Cannot get online"})
return
}
handler.logger.Debugf("REST - GetOnline - result = %#v", result)

onlineResp := onlineResponse{}
err = json.Unmarshal([]byte(result), &onlineResp)
if err != nil {
handler.logger.Errorf("REST - GetOnline - cannot unmarshal JSON response from online remote service = %#v", err)
// TODO manage errors
}

response := models.Online{}
response.UUID = device.UUID
response.APIToken = onlineResp.APIToken
response.CreatedAt = time.UnixMilli(onlineResp.CreatedAt)
response.ModifiedAt = time.UnixMilli(onlineResp.ModifiedAt)
c.JSON(http.StatusOK, &response)
}

// TODO this is equals to the method defined in devices_values
func (handler *Online) getDevice(deviceID primitive.ObjectID) (models.Device, error) {
handler.logger.Debug("getDevice - searching device with objectId: ", deviceID)
var device models.Device
err := handler.collDevices.FindOne(handler.ctx, bson.M{
"_id": deviceID,
}).Decode(&device)
handler.logger.Debug("Device found: ", device)
return device, err
}

func (handler *Online) onlineByUUIDService(urlOnline string) (int, string, error) {
response, err := http.Get(urlOnline)
if err != nil {
return -1, "", customerrors.Wrap(http.StatusInternalServerError, err, "Cannot call online service via HTTP")
}
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
return response.StatusCode, string(body), nil
}
25 changes: 15 additions & 10 deletions initialization/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var devicesValues *api.DevicesValues
var profiles *api.Profiles
var register *api.Register
var fcmToken *api.FCMToken
var online *api.Online
var keepAlive *api.KeepAlive

var oauthCallbackURL string
Expand All @@ -38,26 +39,26 @@ func SetupRouter(logger *zap.SugaredLogger) (*gin.Engine, cookie.Store) {
httpServer := os.Getenv("HTTP_SERVER")
oauthCallback := os.Getenv("OAUTH_CALLBACK")

// init oauthCallbackURL based on httpOrigin
// 1. init oauthCallbackURL based on httpOrigin
oauthCallbackURL = oauthCallback
logger.Info("SetupRouter - oauthCallbackURL is = " + oauthCallbackURL)

httpOrigin := httpServer + ":" + port
logger.Info("SetupRouter - httpOrigin is = " + httpOrigin)

// init GIN
// 2. init GIN
router := gin.Default()
// init session
// 3. init session
cookieStore := cookie.NewStore([]byte("secret"))
router.Use(sessions.Sessions("session", cookieStore))
// apply compression
// 4. apply compression
router.Use(gzip.Gzip(gzip.DefaultCompression))

// fix a max POST payload size
// 5. fix a max POST payload size
logger.Info("SetupRouter - set mac POST payload size")
router.Use(limits.RequestSizeLimiter(1024 * 1024))

// 10. Configure CORS
// 6. Configure CORS
// - No origin allowed by default
// - GET,POST, PUT, HEAD methods
// - Credentials share disabled
Expand All @@ -83,7 +84,7 @@ func SetupRouter(logger *zap.SugaredLogger) (*gin.Engine, cookie.Store) {
logger.Info("SetupRouter - CORS disabled")
}

// 11. Configure Gin to serve a SPA for non-production env
// 7. Configure Gin to serve a SPA for non-production env
// In prod we will use nginx, so this will be ignored!
// GIN is terrible with SPA, because you can configure static.serve
// but if you refresh the SPA it will return an error, and you cannot add something like /*
Expand Down Expand Up @@ -120,11 +121,13 @@ func RegisterRoutes(ctx context.Context, router *gin.Engine, cookieStore *cookie
devicesValues = api.NewDevicesValues(ctx, logger, client, validate)
profiles = api.NewProfiles(ctx, logger, client)
register = api.NewRegister(ctx, logger, client, validate)
// FCM = Firebase Cloud Messaging => used to associate smartphone to an APIToken
// FCM = Firebase Cloud Messaging => identify a smartphone on Firebase to send notifications
fcmToken = api.NewFCMToken(ctx, logger, client, validate)
online = api.NewOnline(ctx, logger, client)

// 12. Configure oAuth2 authentication
// 1. Configure oAuth2 authentication
router.Use(sessions.Sessions("session", *cookieStore)) // session called "session"
// 2. Define public APIs
// public API to get Login URL
router.GET("/api/login", oauthGithub.GetLoginURL)
// public APIs
Expand All @@ -136,7 +139,7 @@ func RegisterRoutes(ctx context.Context, router *gin.Engine, cookieStore *cookie
authorized.Use(oauthGithub.OauthAuth())
authorized.GET("", auth.LoginCallback)

// 13. Define /api group protected via JWTMiddleware
// 3. Define private APIs (/api group) protected via JWTMiddleware
private := router.Group("/api")
private.Use(auth.JWTMiddleware())
{
Expand All @@ -158,5 +161,7 @@ func RegisterRoutes(ctx context.Context, router *gin.Engine, cookieStore *cookie

private.GET("/devices/:id/values", devicesValues.GetValuesDevice)
private.POST("/devices/:id/values", devicesValues.PostValueDevice)

private.GET("/online/:id", online.GetOnline)
}
}
Loading

0 comments on commit 7208f3c

Please sign in to comment.