Merge branch 'feat/test-env'

add test environment

See merge request team-raclette/project-softweng!23
This commit is contained in:
2025-06-24 22:07:33 +02:00
7 changed files with 128 additions and 14 deletions

View File

@@ -8,6 +8,4 @@ MQTT_USERNAME=
MQTT_PASSWORD=
REST_USERNAME=
REST_PASSWORD=
REST_URL=
REST_PAGE=
CYPRESS_TEST_URL=

View File

@@ -1,5 +1,6 @@
stages:
- gateway-build
- web-app-build
- web-app-tests

View File

@@ -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. <br>
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

View File

@@ -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"

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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 \