diff --git a/.env.template b/.env.template index f5f6a17..cb7a2f8 100644 --- a/.env.template +++ b/.env.template @@ -8,6 +8,4 @@ MQTT_USERNAME= MQTT_PASSWORD= REST_USERNAME= REST_PASSWORD= -REST_URL= -REST_PAGE= CYPRESS_TEST_URL= diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3bd7e77..dc132c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ stages: - gateway-build + - web-app-build - web-app-tests diff --git a/README.md b/README.md index 9521da3..2d34b9b 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,23 @@ Credentials are available [on request](mailto:remi@heredero.ch). Details in [pico-sensor/README.md](pico-sensor/README.md). +## Test environment +[(Back to top)](#table-of-contents) + +A partial test environment is deployed on the server.
+Web app has a second container with the same code but made a GET request on `/test` instead of `/raclette` for the +development environment. The gateway include a route to `/test` that return the 100 last measurement instead of the +last 24h. Has everything else is still a development environment, the database is the same for the test and +development environment. + # Authors [(Back to top)](#table-of-contents) -* [Sylvan Arnold](https://github.com/Sylvan22) -* [Rémi Heredero](https://github.com/Klagarge) -* [Yann Sierro](https://github.com/Fastium) +* [Sylvan Arnold](https://github.com/Sylvan22) - Mainly pico-sensor part +* [Rémi Heredero](https://github.com/Klagarge) - Mainly gateway and docker part +* [Yann Sierro](https://github.com/Fastium) - Mainly web-app part diff --git a/docker-compose.yml b/docker-compose.yml index 3547b33..b8a23e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,8 +22,6 @@ services: image: registry.forge.hefr.ch/team-raclette/project-softweng/gateway:latest container_name: gateway restart: unless-stopped - ports: - - "8080:8080" environment: - INFLUXDB_TOKEN=$INFLUXDB_TOKEN - INFLUXDB_ORG=$INFLUXDB_ORG @@ -105,15 +103,11 @@ services: image: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest container_name: web-app restart: unless-stopped - ports: - - "8080:8080" - environment: - VUE_APP_REST_USER=$REST_USERNAME - VUE_APP_REST_PASSWORD=$REST_PASSWORD - - VUE_APP_REST_URL=$REST_URL - - VUE_APP_REST_PAGE=$REST_PAGE - + - VUE_APP_REST_URL=rest.mse.kb28.ch + - VUE_APP_REST_PAGE=raclette labels: - "traefik.enable=true" - "traefik.http.routers.web-app-http.entrypoints=http" @@ -125,3 +119,24 @@ services: - "traefik.http.services.web-app-https.loadbalancer.server.port=8080" - "com.centurylinklabs.watchtower.enable=true" + + web-app-test: + image: registry.forge.hefr.ch/team-raclette/project-softweng/web-app:latest + container_name: web-app-test + restart: unless-stopped + environment: + - VUE_APP_REST_USER=$REST_USERNAME + - VUE_APP_REST_PASSWORD=$REST_PASSWORD + - VUE_APP_REST_URL=rest.mse.kb28.ch + - VUE_APP_REST_PAGE=test + labels: + - "traefik.enable=true" + - "traefik.http.routers.web-app-test-http.entrypoints=http" + - "traefik.http.routers.web-app-test-http.rule=Host(`app-test.mse.kb28.ch`)" + - "traefik.http.middlewares.web-app-test-redirect.redirectscheme.scheme=https" + - "traefik.http.routers.web-app-test-https.entrypoints=https" + - "traefik.http.routers.web-app-test-https.rule=Host(`app-test.mse.kb28.ch`)" + - "traefik.http.routers.web-app-test-https.tls.certResolver=letsencrypt" + - "traefik.http.services.web-app-test-https.loadbalancer.server.port=8080" + + - "com.centurylinklabs.watchtower.enable=true" diff --git a/gateway/src/Connection.go b/gateway/src/Connection.go index 63b9322..34b78cb 100644 --- a/gateway/src/Connection.go +++ b/gateway/src/Connection.go @@ -22,6 +22,7 @@ type Gateway struct { mqtt paho.Client rest *gin.Engine restGroup *gin.RouterGroup + testGroup *gin.RouterGroup accounts gin.Accounts influx influxdb2.Client influxApi api.WriteAPIBlocking @@ -100,6 +101,7 @@ func (gh *Gateway) createRestGateway() { // Create a new router group with basic authentication for /raclette gh.restGroup = gh.rest.Group("/raclette", gin.BasicAuth(gh.accounts)) + gh.testGroup = gh.rest.Group("/test", gin.BasicAuth(gh.accounts)) // Define the route for the publish command gh.restGroup.POST("", func(c *gin.Context) { @@ -129,6 +131,14 @@ func (gh *Gateway) createRestGateway() { c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()}) } }) + + // Define the route for the publish command (GET) + gh.testGroup.GET("", func(c *gin.Context) { + ret := gh.requestInfluxTest(c) + if ret != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": ret.Error()}) + } + }) } // createMQTTGateway initializes the MQTT client and sets up subscriptions. diff --git a/gateway/src/RequestInflux.go b/gateway/src/RequestInflux.go index 356ef1a..4dfa91f 100644 --- a/gateway/src/RequestInflux.go +++ b/gateway/src/RequestInflux.go @@ -68,6 +68,7 @@ func (gh *Gateway) requestInflux(c *gin.Context) error { |> filter(fn: (r) => r["user"] == %q) |> filter(fn: (r) => r["room"] == %q) |> filter(fn: (r) => r["device"] == %q) + |> aggregateWindow(every: 5m, fn: mean, createEmpty: false) |> sort(columns: ["_time"], desc: true) `, INFLUXDB_BUCKET, MEASUREMENT_NAME, user, room, device) results, err := queryAPI.Query(context.Background(), query) @@ -101,3 +102,83 @@ func (gh *Gateway) requestInflux(c *gin.Context) error { c.JSON(http.StatusOK, values) return nil } + +func (gh *Gateway) requestInfluxTest(c *gin.Context) error { + // Get the user from the authenticated context + //userId := c.MustGet(gin.AuthUserKey).(string) + + // Get room and device from the query parameters + user, ret := c.GetQuery("user") + if !ret { + return errors.New("no user found") + } + room, ret := c.GetQuery("room") + if !ret { + return errors.New(`no room found`) + } + device, ret := c.GetQuery("device") + if !ret { + return errors.New(`no device found`) + } + + // Get env variables and set default values if not set + INFLUXDB_ORG, ok := os.LookupEnv("INFLUXDB_ORG") + if !ok { + log.Error("INFLUXDB_ORG not set, using default value: raclette") + INFLUXDB_ORG = "raclette" + } + + INFLUXDB_BUCKET, ok := os.LookupEnv("INFLUXDB_BUCKET") + if !ok { + log.Error("INFLUXDB_BUCKET not set, using default value: raclette") + INFLUXDB_BUCKET = "raclette" + } + + queryAPI := gh.influx.QueryAPI(INFLUXDB_ORG) + MEASUREMENT_NAME, ok := os.LookupEnv("MEASUREMENT_NAME") + if !ok { + log.Error("MEASUREMENT_NAME not set, using default value: softweng") + MEASUREMENT_NAME = "THC" + } + // The Flux query uses a large range (-1000d) and aggregates the latest values. + // This ensures we always get the most recent data, even if the database contains old entries. + query := fmt.Sprintf(`from(bucket: %q) + |> range(start: -1000d) + |> filter(fn: (r) => r["_measurement"] == %q) + |> filter(fn: (r) => r["user"] == %q) + |> filter(fn: (r) => r["room"] == %q) + |> filter(fn: (r) => r["device"] == %q) + |> sort(columns: ["_time"], desc: true) + |> limit(n: 100) + `, INFLUXDB_BUCKET, MEASUREMENT_NAME, user, room, device) + results, err := queryAPI.Query(context.Background(), query) + if err != nil { + log.Fatal(err) + } + + type measurement = map[string]interface{} + var values []measurement + + for results.Next() { + m := measurement{} + record := results.Record() + field := record.Field() + value := record.Value() + m["time"] = record.Time() + m["value"] = value.(float64) + if field == "temperature" { + m["type"] = "temperature" + } + if field == "humidity" { + m["type"] = "humidity" + } + values = append(values, m) + fmt.Println(results.Record()) + } + if err := results.Err(); err != nil { + log.Fatal(err) + } + + c.JSON(http.StatusOK, values) + return nil +} diff --git a/pico-sensor/dockerfile b/pico-sensor/Dockerfile similarity index 96% rename from pico-sensor/dockerfile rename to pico-sensor/Dockerfile index 1163773..47ddeae 100644 --- a/pico-sensor/dockerfile +++ b/pico-sensor/Dockerfile @@ -70,7 +70,7 @@ RUN \ ENV PICO_SDK_PATH=/apps/pico-sdk/ # Patch the SDK for semihosting file I/O (needed for gcov) -COPY newlib_interface.c.patched ${PICO_SDK_PATH}/src/rp2_common/pico_clib_interface/newlib_interface.c +COPY ./pico-sensor/newlib_interface.c.patched ${PICO_SDK_PATH}/src/rp2_common/pico_clib_interface/newlib_interface.c # Build picotool so we don't have to build it for the project RUN \