Cross-Origin Resource Sharing now allow all *.e.kb28.ch Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Klagarge <remi@heredero.ch>
187 lines
5.0 KiB
Go
187 lines
5.0 KiB
Go
package rest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"gateway/influx"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
_ "gateway/docs"
|
|
|
|
"github.com/gin-contrib/cors"
|
|
"github.com/gin-gonic/gin"
|
|
swaggerFiles "github.com/swaggo/files"
|
|
ginSwagger "github.com/swaggo/gin-swagger"
|
|
)
|
|
|
|
type RestGateway struct {
|
|
influxGateway *influx.InfluxGateway
|
|
engine *gin.Engine
|
|
measurementName string
|
|
username string
|
|
password string
|
|
}
|
|
|
|
func NewRestGateway(influxGateway *influx.InfluxGateway, measurementName string, username, password string) *RestGateway {
|
|
g := &RestGateway{
|
|
influxGateway: influxGateway,
|
|
engine: gin.Default(),
|
|
measurementName: measurementName,
|
|
username: username,
|
|
password: password,
|
|
}
|
|
|
|
g.setupRoutes()
|
|
return g
|
|
}
|
|
|
|
func (g *RestGateway) setupRoutes() {
|
|
// Setup CORS middleware to allow all *.e.kb28.ch origins
|
|
corsConfig := cors.Config{
|
|
AllowOriginFunc: func(origin string) bool {
|
|
// Match any origin like *.e.kb28.ch
|
|
pattern := regexp.MustCompile(`^https://.*\.e\.kb28\.ch$`)
|
|
return pattern.MatchString(origin)
|
|
},
|
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
|
|
ExposeHeaders: []string{"Content-Length"},
|
|
AllowCredentials: true,
|
|
}
|
|
g.engine.Use(cors.New(corsConfig))
|
|
|
|
v1 := g.engine.Group("/api/v1")
|
|
if g.username != "" && g.password != "" {
|
|
v1.Use(gin.BasicAuth(gin.Accounts{
|
|
g.username: g.password,
|
|
}))
|
|
}
|
|
|
|
{
|
|
v1.GET("/rooms", g.getRooms)
|
|
v1.GET("/rooms/:room-id/current", g.getRoomCurrent)
|
|
v1.GET("/rooms/:room-id/history", g.getRoomHistory)
|
|
}
|
|
|
|
g.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
|
}
|
|
|
|
func (g *RestGateway) Run(addr string) error {
|
|
return g.engine.Run(addr)
|
|
}
|
|
|
|
// GET /api/v1/rooms
|
|
// getRooms godoc
|
|
// @Summary Get all unique rooms
|
|
// @Description Get a list of all unique rooms from the measurement
|
|
// @Tags rooms
|
|
// @Produce json
|
|
// @Success 200 {array} string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Security BasicAuth
|
|
// @Router /rooms [get]
|
|
func (g *RestGateway) getRooms(c *gin.Context) {
|
|
// Query unique rooms from the measurement
|
|
query := fmt.Sprintf(`SELECT DISTINCT("room") FROM "%s"`, g.measurementName)
|
|
|
|
// Using context.Background() as seen in working snippet
|
|
it, err := g.influxGateway.Query(context.Background(), query)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var rooms []string
|
|
for it.Next() {
|
|
val := it.Value()
|
|
if room, ok := val["room"].(string); ok {
|
|
rooms = append(rooms, room)
|
|
}
|
|
}
|
|
|
|
if err := it.Err(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, rooms)
|
|
}
|
|
|
|
// GET /api/v1/rooms/{room-id}/current
|
|
// getRoomCurrent godoc
|
|
// @Summary Get current data for a room
|
|
// @Description Get the latest record for a specific room
|
|
// @Tags rooms
|
|
// @Produce json
|
|
// @Param room-id path string true "Room ID"
|
|
// @Success 200 {object} map[string]any
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Security BasicAuth
|
|
// @Router /rooms/{room-id}/current [get]
|
|
func (g *RestGateway) getRoomCurrent(c *gin.Context) {
|
|
roomID := c.Param("room-id")
|
|
|
|
// Get the last record for the specific room
|
|
query := fmt.Sprintf(`SELECT * FROM "%s" WHERE "room" = '%s' ORDER BY time DESC LIMIT 1`, g.measurementName, roomID)
|
|
|
|
// Using context.Background() as seen in working snippet
|
|
it, err := g.influxGateway.Query(context.Background(), query)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if it.Next() {
|
|
c.JSON(http.StatusOK, it.Value())
|
|
return
|
|
}
|
|
|
|
if err := it.Err(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Room not found or no data available"})
|
|
}
|
|
|
|
// GET /api/v1/rooms/{room-id}/history
|
|
// getRoomHistory godoc
|
|
// @Summary Get history for a room
|
|
// @Description Get history for a specific room
|
|
// @Tags rooms
|
|
// @Produce json
|
|
// @Param room-id path string true "Room ID"
|
|
// @Param window query string false "Time window (e.g., 1 day, 1 hour, 30 min)" default(1 day)
|
|
// @Success 200 {array} map[string]any
|
|
// @Failure 500 {object} map[string]string
|
|
// @Security BasicAuth
|
|
// @Router /rooms/{room-id}/history [get]
|
|
func (g *RestGateway) getRoomHistory(c *gin.Context) {
|
|
roomID := c.Param("room-id")
|
|
window := c.DefaultQuery("window", "1 day")
|
|
|
|
query := fmt.Sprintf(` SELECT * FROM "%s" WHERE "room" = '%s' AND time > now() - INTERVAL '%s' ORDER BY time ASC
|
|
`, g.measurementName, roomID, window)
|
|
|
|
// Using context.Background() as seen in working snippet
|
|
it, err := g.influxGateway.Query(context.Background(), query)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var history []map[string]any
|
|
for it.Next() {
|
|
history = append(history, it.Value())
|
|
}
|
|
|
|
if err := it.Err(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, history)
|
|
}
|