Merge branch 'Bendodroid-envSensors' into 'main'
Add Support for Temperature and Humidity Sensors + New Config File Format Closes #6 See merge request ccchh/spaceapid!4
This commit is contained in:
		
				commit
				
					
						9d5f7cf6cb
					
				
			
		
					 15 changed files with 476 additions and 238 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -6,3 +6,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# go build output
 | 
					# go build output
 | 
				
			||||||
spaceapid
 | 
					spaceapid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Saved state
 | 
				
			||||||
 | 
					spaceapid-state.json
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,42 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "api_compatibility": [
 | 
					 | 
				
			||||||
    "14"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "space": "CCCHH",
 | 
					 | 
				
			||||||
  "logo": "https://next.hamburg.ccc.de/images/logo.svg",
 | 
					 | 
				
			||||||
  "ext_ccc": "erfa",
 | 
					 | 
				
			||||||
  "url": "https://hamburg.ccc.de/",
 | 
					 | 
				
			||||||
  "location": {
 | 
					 | 
				
			||||||
    "address": "Zeiseweg 9, 22765 Hamburg, Germany",
 | 
					 | 
				
			||||||
    "lon": 9.9445899999999998,
 | 
					 | 
				
			||||||
    "lat": 53.55836
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "contact": {
 | 
					 | 
				
			||||||
    "phone": "+494023830150",
 | 
					 | 
				
			||||||
    "irc": "ircs://irc.hackint.org:6697/#ccchh",
 | 
					 | 
				
			||||||
    "mastodon": "@ccchh@chaos.social",
 | 
					 | 
				
			||||||
    "email": "mail@hamburg.ccc.de",
 | 
					 | 
				
			||||||
    "ml": "talk@hamburg.ccc.de",
 | 
					 | 
				
			||||||
    "matrix": "#ccchh:hamburg.ccc.de"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "feeds": {
 | 
					 | 
				
			||||||
    "blog": {
 | 
					 | 
				
			||||||
      "type": "application/atom+xml",
 | 
					 | 
				
			||||||
      "url": "https://hamburg.ccc.de/feed.xml"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "calendar": {
 | 
					 | 
				
			||||||
      "type": "ical",
 | 
					 | 
				
			||||||
      "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "links": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "name": "Wiki",
 | 
					 | 
				
			||||||
      "url": "https://wiki.ccchh.net"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "name": "GitLab",
 | 
					 | 
				
			||||||
      "url": "https://gitlab.hamburg.ccc.de"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										101
									
								
								config-template.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								config-template.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,101 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "credentials": {
 | 
				
			||||||
 | 
					    "home-assistant": {
 | 
				
			||||||
 | 
					      "username": "home-assistant",
 | 
				
			||||||
 | 
					      "password": "hamiau"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "dooris-hauptraum": {
 | 
				
			||||||
 | 
					      "username": "dooris-hauptraum",
 | 
				
			||||||
 | 
					      "password": "doorimiau"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dynamic": {
 | 
				
			||||||
 | 
					    "sensors": {
 | 
				
			||||||
 | 
					      "temperature": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "sensor_data": {
 | 
				
			||||||
 | 
					            "unit": "C",
 | 
				
			||||||
 | 
					            "location": "Hauptraum",
 | 
				
			||||||
 | 
					            "name": "Kueche",
 | 
				
			||||||
 | 
					            "description": "Sensor im Ofen"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "allowed_credentials": [
 | 
				
			||||||
 | 
					            "home-assistant"
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "sensor_data": {
 | 
				
			||||||
 | 
					            "unit": "C",
 | 
				
			||||||
 | 
					            "location": "Hauptraum",
 | 
				
			||||||
 | 
					            "description": "Sensor im Hauptraum"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "allowed_credentials": [
 | 
				
			||||||
 | 
					            "home-assistant"
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "humidity": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "sensor_data": {
 | 
				
			||||||
 | 
					            "unit": "hPa",
 | 
				
			||||||
 | 
					            "location": "Hauptraum",
 | 
				
			||||||
 | 
					            "name": "Kueche",
 | 
				
			||||||
 | 
					            "description": "Sensor im Wasserhahn"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "allowed_credentials": [
 | 
				
			||||||
 | 
					            "home-assistant"
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "state": {
 | 
				
			||||||
 | 
					      "open": {
 | 
				
			||||||
 | 
					        "allowed_credentials": [
 | 
				
			||||||
 | 
					          "dooris-hauptraum"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "response": {
 | 
				
			||||||
 | 
					    "api_compatibility": [
 | 
				
			||||||
 | 
					      "14"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "space": "CCCHH",
 | 
				
			||||||
 | 
					    "logo": "https://next.hamburg.ccc.de/images/logo.svg",
 | 
				
			||||||
 | 
					    "ext_ccc": "erfa",
 | 
				
			||||||
 | 
					    "url": "https://hamburg.ccc.de/",
 | 
				
			||||||
 | 
					    "location": {
 | 
				
			||||||
 | 
					      "address": "Zeiseweg 9, 22765 Hamburg, Germany",
 | 
				
			||||||
 | 
					      "lon": 9.9445899999999998,
 | 
				
			||||||
 | 
					      "lat": 53.55836
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "contact": {
 | 
				
			||||||
 | 
					      "phone": "+49 40 23830150",
 | 
				
			||||||
 | 
					      "irc": "ircs://irc.hackint.org:6697/#ccchh",
 | 
				
			||||||
 | 
					      "mastodon": "@ccchh@chaos.social",
 | 
				
			||||||
 | 
					      "email": "mail@hamburg.ccc.de",
 | 
				
			||||||
 | 
					      "ml": "talk@hamburg.ccc.de",
 | 
				
			||||||
 | 
					      "matrix": "#ccchh:hamburg.ccc.de"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "feeds": {
 | 
				
			||||||
 | 
					      "blog": {
 | 
				
			||||||
 | 
					        "type": "application/atom+xml",
 | 
				
			||||||
 | 
					        "url": "https://hamburg.ccc.de/feed.xml"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "calendar": {
 | 
				
			||||||
 | 
					        "type": "ical",
 | 
				
			||||||
 | 
					        "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "links": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "Wiki",
 | 
				
			||||||
 | 
					        "url": "https://wiki.ccchh.net"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "GitLab",
 | 
				
			||||||
 | 
					        "url": "https://gitlab.hamburg.ccc.de"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,65 @@
 | 
				
			||||||
package config
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configuration represents the settings needed to configure spaceapid
 | 
					import (
 | 
				
			||||||
type Configuration struct {
 | 
						"bytes"
 | 
				
			||||||
	// The HTTP BasicAuth username for door status updates
 | 
						"encoding/json"
 | 
				
			||||||
	BAUsername string
 | 
						"log"
 | 
				
			||||||
	// The HTTP BasicAuth password for door status updates
 | 
						"os"
 | 
				
			||||||
	BAPassword string
 | 
						"path/filepath"
 | 
				
			||||||
	// The path to the JSON with initial values
 | 
						"slices"
 | 
				
			||||||
	TemplatePath string
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						envConfigPath = "CONFIG_PATH"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getConfigPath gets the spaceapid configuration from the respective environment variables
 | 
				
			||||||
 | 
					func getConfigPath() string {
 | 
				
			||||||
 | 
						// JSON template path
 | 
				
			||||||
 | 
						configPath, ok := os.LookupEnv(envConfigPath)
 | 
				
			||||||
 | 
						if !ok || configPath == "" {
 | 
				
			||||||
 | 
							log.Fatalln("Could not retrieve", envConfigPath, "env variable or variable is empty")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Save as absolute path
 | 
				
			||||||
 | 
						absConfigPath, err := filepath.Abs(configPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln("Failed converting", configPath, "to absolute path:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return absConfigPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParseConfiguration returns the config from file
 | 
				
			||||||
 | 
					func ParseConfiguration() (conf SpaceapidConfig) {
 | 
				
			||||||
 | 
						log.Println("Parsing configuration file")
 | 
				
			||||||
 | 
						// Read file
 | 
				
			||||||
 | 
						file, err := os.ReadFile(getConfigPath())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln("Failed reading file:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse JSON
 | 
				
			||||||
 | 
						dec := json.NewDecoder(bytes.NewReader(file))
 | 
				
			||||||
 | 
						dec.DisallowUnknownFields()
 | 
				
			||||||
 | 
						err = dec.Decode(&conf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln("Could not parse spaceapid config file:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if compatible with v14
 | 
				
			||||||
 | 
						if !slices.Contains(conf.Response.APICompatibility, "14") {
 | 
				
			||||||
 | 
							log.Fatalln("Provided file doesn't specify compatibility with API version 14")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Initialise fields for environment sensors
 | 
				
			||||||
 | 
						conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
 | 
				
			||||||
 | 
						for key, sensorConfigs := range conf.Dynamic.Sensors {
 | 
				
			||||||
 | 
							conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
 | 
				
			||||||
 | 
							for i, sensorConfig := range sensorConfigs {
 | 
				
			||||||
 | 
								conf.Response.Sensors[key][i] = sensorConfig.SensorData
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										38
									
								
								config/types.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								config/types.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HTTPBACredentialID string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HTTPBACredentials map[HTTPBACredentialID]HTTPBACredential
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HTTPBACredential struct {
 | 
				
			||||||
 | 
						Username string `json:"username"`
 | 
				
			||||||
 | 
						Password string `json:"password"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EnvironmentSensorConfig struct {
 | 
				
			||||||
 | 
						SensorData         types.EnvironmentSensor `json:"sensor_data"`
 | 
				
			||||||
 | 
						AllowedCredentials []HTTPBACredentialID    `json:"allowed_credentials"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DynamicStateConfig struct {
 | 
				
			||||||
 | 
						Sensors map[string][]EnvironmentSensorConfig `json:"sensors"`
 | 
				
			||||||
 | 
						State   struct {
 | 
				
			||||||
 | 
							Open struct {
 | 
				
			||||||
 | 
								AllowedCredentials []HTTPBACredentialID `json:"allowed_credentials"`
 | 
				
			||||||
 | 
							} `json:"open"`
 | 
				
			||||||
 | 
						} `json:"state"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SpaceapidConfig is the representation of the config.json file
 | 
				
			||||||
 | 
					type SpaceapidConfig struct {
 | 
				
			||||||
 | 
						// A list of username/password pairs for BasicAuth endpoints
 | 
				
			||||||
 | 
						Credentials HTTPBACredentials `json:"credentials"`
 | 
				
			||||||
 | 
						// The dynamic part of the spaceapi JSON
 | 
				
			||||||
 | 
						Dynamic DynamicStateConfig `json:"dynamic"`
 | 
				
			||||||
 | 
						// The static part of the spaceapi JSON
 | 
				
			||||||
 | 
						Response types.SpaceAPIResponseV14 `json:"response"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,87 +0,0 @@
 | 
				
			||||||
package handlers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) {
 | 
					 | 
				
			||||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
		// Check if GET method
 | 
					 | 
				
			||||||
		if r.Method != http.MethodGet {
 | 
					 | 
				
			||||||
			log.Println("Wrong METHOD from", r.RemoteAddr)
 | 
					 | 
				
			||||||
			w.Header().Set("Allow", http.MethodGet)
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusMethodNotAllowed)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Serialize response
 | 
					 | 
				
			||||||
		response, err := json.Marshal(resp)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println("Failed to serialize JSON response:", err)
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Respond with OK
 | 
					 | 
				
			||||||
		w.Header().Set("Access-Control-Allow-Origin", "*")
 | 
					 | 
				
			||||||
		w.Header().Set("Content-Type", "application/json")
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusOK)
 | 
					 | 
				
			||||||
		_, _ = w.Write(response)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func StateOpen(
 | 
					 | 
				
			||||||
	validUsername, validPassword string, resp *types.SpaceAPIResponseV14,
 | 
					 | 
				
			||||||
) func(http.ResponseWriter, *http.Request) {
 | 
					 | 
				
			||||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
		// Check BasicAuth credentials
 | 
					 | 
				
			||||||
		username, password, ok := r.BasicAuth()
 | 
					 | 
				
			||||||
		if !ok || username != validUsername || password != validPassword {
 | 
					 | 
				
			||||||
			log.Println("Unauthorized request from", r.RemoteAddr)
 | 
					 | 
				
			||||||
			w.Header().Set("WWW-Authenticate", "Basic realm=\"space-api\"")
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusUnauthorized)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Check if PUT method
 | 
					 | 
				
			||||||
		if r.Method != http.MethodPut {
 | 
					 | 
				
			||||||
			log.Println("Wrong METHOD from", r.RemoteAddr)
 | 
					 | 
				
			||||||
			w.Header().Set("Allow", http.MethodPut)
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusMethodNotAllowed)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Read request body
 | 
					 | 
				
			||||||
		body, err := io.ReadAll(r.Body)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println("Failed to read request body from", r.RemoteAddr)
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
					 | 
				
			||||||
			_, _ = io.WriteString(w, "Failed reading HTTP request body")
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Parse request body
 | 
					 | 
				
			||||||
		newState, err := strconv.ParseBool(string(body))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println("Failed to parse request body from", r.RemoteAddr)
 | 
					 | 
				
			||||||
			w.WriteHeader(http.StatusBadRequest)
 | 
					 | 
				
			||||||
			_, _ = io.WriteString(w, "HTTP request body should either be true or false")
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Set SpaceAPI response values
 | 
					 | 
				
			||||||
		resp.State.Open = newState
 | 
					 | 
				
			||||||
		resp.State.LastChange = time.Now().Unix()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Respond with OK
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusOK)
 | 
					 | 
				
			||||||
		_, _ = io.WriteString(w, "Update Successful")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								handlers/root.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								handlers/root.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) {
 | 
				
			||||||
 | 
						return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							// Check if GET method
 | 
				
			||||||
 | 
							if r.Method != http.MethodGet {
 | 
				
			||||||
 | 
								log.Println("Wrong METHOD from", r.RemoteAddr)
 | 
				
			||||||
 | 
								w.Header().Set("Allow", http.MethodGet)
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusMethodNotAllowed)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Serialize response
 | 
				
			||||||
 | 
							response, err := json.Marshal(resp)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Failed to serialize JSON response:", err)
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Respond with OK
 | 
				
			||||||
 | 
							w.Header().Set("Access-Control-Allow-Origin", "*")
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							_, _ = w.Write(response)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								handlers/sensors.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								handlers/sensors.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EnvironmentSensor(
 | 
				
			||||||
 | 
						authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
				
			||||||
 | 
						resp *types.EnvironmentSensor,
 | 
				
			||||||
 | 
					) func(http.ResponseWriter, *http.Request) {
 | 
				
			||||||
 | 
						return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							body := updateEndpointValidator(authDB, validCredentials, w, r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse request body
 | 
				
			||||||
 | 
							newState, err := strconv.ParseFloat(string(body), 64)
 | 
				
			||||||
 | 
							if err != nil || math.IsInf(newState, 0) {
 | 
				
			||||||
 | 
								log.Println("Failed to parse request body from", r.RemoteAddr)
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								_, _ = io.WriteString(w, "HTTP request body has to be a valid float64 value != +/-Inf")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Set SpaceAPI response values
 | 
				
			||||||
 | 
							resp.Value = newState
 | 
				
			||||||
 | 
							resp.LastChange = time.Now().Unix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Respond with OK
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							_, _ = io.WriteString(w, "Update Successful")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								handlers/state.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								handlers/state.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func StateOpen(
 | 
				
			||||||
 | 
						authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
				
			||||||
 | 
						resp *types.SpaceState,
 | 
				
			||||||
 | 
					) func(http.ResponseWriter, *http.Request) {
 | 
				
			||||||
 | 
						return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							body := updateEndpointValidator(authDB, validCredentials, w, r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse request body
 | 
				
			||||||
 | 
							newState, err := strconv.ParseBool(string(body))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Failed to parse request body from", r.RemoteAddr)
 | 
				
			||||||
 | 
								w.WriteHeader(http.StatusBadRequest)
 | 
				
			||||||
 | 
								_, _ = io.WriteString(w, "HTTP request body should either be true or false")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Set SpaceAPI response values
 | 
				
			||||||
 | 
							resp.Open = newState
 | 
				
			||||||
 | 
							resp.LastChange = time.Now().Unix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Respond with OK
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							_, _ = io.WriteString(w, "Update Successful")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								handlers/util.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								handlers/util.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/util"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// updateEndpointValidator checks BasicAuth credentials,
 | 
				
			||||||
 | 
					// checks for correct HTTP method and then returns the request body
 | 
				
			||||||
 | 
					func updateEndpointValidator(
 | 
				
			||||||
 | 
						authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
				
			||||||
 | 
						w http.ResponseWriter, r *http.Request,
 | 
				
			||||||
 | 
					) (body []byte) {
 | 
				
			||||||
 | 
						// Check BasicAuth credentials
 | 
				
			||||||
 | 
						username, password, ok := r.BasicAuth()
 | 
				
			||||||
 | 
						if !ok || !util.CheckCredentials(authDB, validCredentials, username, password) {
 | 
				
			||||||
 | 
							log.Println("Unauthorized request from", r.RemoteAddr)
 | 
				
			||||||
 | 
							w.Header().Set("WWW-Authenticate", "Basic realm=\"space-api\"")
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if PUT method
 | 
				
			||||||
 | 
						if r.Method != http.MethodPut {
 | 
				
			||||||
 | 
							log.Println("Wrong Method: ", r.Method, "from", r.RemoteAddr, "at", r.RequestURI)
 | 
				
			||||||
 | 
							w.Header().Set("Allow", http.MethodPut)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusMethodNotAllowed)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Read request body
 | 
				
			||||||
 | 
						body, err := io.ReadAll(r.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Failed to read request body from", r.RemoteAddr)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusInternalServerError)
 | 
				
			||||||
 | 
							_, _ = io.WriteString(w, "Failed reading HTTP request body")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										40
									
								
								main.go
									
										
									
									
									
								
							| 
						 | 
					@ -1,40 +1,58 @@
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/signal"
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/handlers"
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/handlers"
 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/util"
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	log.Println("Reading configuration values")
 | 
						// Get spaceapid configuration
 | 
				
			||||||
	config := util.GetConfiguration()
 | 
						conf := config.ParseConfiguration()
 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Println("Reading initial SpaceAPI response from", config.TemplatePath)
 | 
					 | 
				
			||||||
	spaceApiResponse := util.ParseTemplate(config.TemplatePath)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Merge old state if present
 | 
						// Merge old state if present
 | 
				
			||||||
	util.MergeOldState(&spaceApiResponse)
 | 
						util.MergeOldState(&conf.Response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register signal handler
 | 
						// Register signal handler
 | 
				
			||||||
	sc := make(chan os.Signal, 1)
 | 
						sc := make(chan os.Signal, 1)
 | 
				
			||||||
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
 | 
						signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
 | 
				
			||||||
	go func(ch chan os.Signal, resp *types.SpaceAPIResponseV14) {
 | 
						go func(resp *types.SpaceAPIResponseV14) {
 | 
				
			||||||
		<-ch
 | 
							<-sc
 | 
				
			||||||
		log.Println("Saving state and shutting down...")
 | 
							log.Println("Saving state and shutting down...")
 | 
				
			||||||
		util.SaveCurrentState(*resp)
 | 
							util.SaveCurrentState(*resp)
 | 
				
			||||||
		os.Exit(0)
 | 
							os.Exit(0)
 | 
				
			||||||
	}(sc, &spaceApiResponse)
 | 
						}(&conf.Response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register HTTP handlers
 | 
						// Register HTTP handlers
 | 
				
			||||||
	http.HandleFunc("/", handlers.Root(&spaceApiResponse))
 | 
						http.HandleFunc("/",
 | 
				
			||||||
	http.HandleFunc("/state/open", handlers.StateOpen(config.BAUsername, config.BAPassword, &spaceApiResponse))
 | 
							handlers.Root(&conf.Response),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						http.HandleFunc("/state/open",
 | 
				
			||||||
 | 
							handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						// Register handlers for Environmental Sensors
 | 
				
			||||||
 | 
						for key, envSensorConfigs := range conf.Dynamic.Sensors {
 | 
				
			||||||
 | 
							for i, envSensorConfig := range envSensorConfigs {
 | 
				
			||||||
 | 
								pattern := fmt.Sprintf("/sensors/%s/%s", key, envSensorConfig.SensorData.Location)
 | 
				
			||||||
 | 
								if envSensorConfig.SensorData.Name != "" {
 | 
				
			||||||
 | 
									pattern += "/" + envSensorConfig.SensorData.Name
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								http.HandleFunc(strings.ToLower(pattern),
 | 
				
			||||||
 | 
									handlers.EnvironmentSensor(
 | 
				
			||||||
 | 
										conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[key][i],
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start webserver
 | 
						// Start webserver
 | 
				
			||||||
	log.Println("Starting HTTP server...")
 | 
						log.Println("Starting HTTP server...")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								types/v14.go
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								types/v14.go
									
										
									
									
									
								
							| 
						 | 
					@ -11,10 +11,7 @@ type SpaceAPIResponseV14 struct {
 | 
				
			||||||
		Lat     float64 `json:"lat"`
 | 
							Lat     float64 `json:"lat"`
 | 
				
			||||||
		Lon     float64 `json:"lon"`
 | 
							Lon     float64 `json:"lon"`
 | 
				
			||||||
	} `json:"location"`
 | 
						} `json:"location"`
 | 
				
			||||||
	State struct {
 | 
						State   SpaceState `json:"state"`
 | 
				
			||||||
		Open       bool  `json:"open"`
 | 
					 | 
				
			||||||
		LastChange int64 `json:"lastchange"`
 | 
					 | 
				
			||||||
	} `json:"state"`
 | 
					 | 
				
			||||||
	Contact struct {
 | 
						Contact struct {
 | 
				
			||||||
		Phone    string `json:"phone"`
 | 
							Phone    string `json:"phone"`
 | 
				
			||||||
		IRC      string `json:"irc"`
 | 
							IRC      string `json:"irc"`
 | 
				
			||||||
| 
						 | 
					@ -23,6 +20,7 @@ type SpaceAPIResponseV14 struct {
 | 
				
			||||||
		ML       string `json:"ml"`
 | 
							ML       string `json:"ml"`
 | 
				
			||||||
		Matrix   string `json:"matrix"`
 | 
							Matrix   string `json:"matrix"`
 | 
				
			||||||
	} `json:"contact"`
 | 
						} `json:"contact"`
 | 
				
			||||||
 | 
						Sensors map[string][]EnvironmentSensor `json:"sensors"`
 | 
				
			||||||
	Feeds   struct {
 | 
						Feeds   struct {
 | 
				
			||||||
		Blog struct {
 | 
							Blog struct {
 | 
				
			||||||
			Type string `json:"type"`
 | 
								Type string `json:"type"`
 | 
				
			||||||
| 
						 | 
					@ -39,9 +37,31 @@ type SpaceAPIResponseV14 struct {
 | 
				
			||||||
	} `json:"links"`
 | 
						} `json:"links"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SpaceState struct {
 | 
				
			||||||
 | 
						Open       bool  `json:"open"`
 | 
				
			||||||
 | 
						LastChange int64 `json:"lastchange"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EnvironmentSensor struct {
 | 
				
			||||||
 | 
						Value       float64 `json:"value"`
 | 
				
			||||||
 | 
						Unit        string  `json:"unit"`
 | 
				
			||||||
 | 
						Location    string  `json:"location"`
 | 
				
			||||||
 | 
						Name        string  `json:"name,omitempty"`
 | 
				
			||||||
 | 
						Description string  `json:"description,omitempty"`
 | 
				
			||||||
 | 
						LastChange  int64   `json:"lastchange,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type PersistentStateV14 struct {
 | 
					type PersistentStateV14 struct {
 | 
				
			||||||
	State struct {
 | 
						State struct {
 | 
				
			||||||
		Open       bool  `json:"open"`
 | 
							Open       bool  `json:"open"`
 | 
				
			||||||
		LastChange int64 `json:"lastchange"`
 | 
							LastChange int64 `json:"lastchange"`
 | 
				
			||||||
	} `json:"state"`
 | 
						} `json:"state"`
 | 
				
			||||||
 | 
						Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PersistentEnvironmentSensor struct {
 | 
				
			||||||
 | 
						Value      float64 `json:"value"`
 | 
				
			||||||
 | 
						Location   string  `json:"location"`
 | 
				
			||||||
 | 
						Name       string  `json:"name,omitempty"`
 | 
				
			||||||
 | 
						LastChange int64   `json:"lastchange"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,48 +0,0 @@
 | 
				
			||||||
package util
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	envBAUsername       = "BA_USERNAME"
 | 
					 | 
				
			||||||
	envBAPassword       = "BA_PASSWORD"
 | 
					 | 
				
			||||||
	envJSONTemplatePath = "JSON_TEMPLATE_PATH"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetConfiguration gets the spaceapid configuration from the respective environment variables
 | 
					 | 
				
			||||||
func GetConfiguration() (c config.Configuration) {
 | 
					 | 
				
			||||||
	var (
 | 
					 | 
				
			||||||
		success bool
 | 
					 | 
				
			||||||
		err     error
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// HTTP BasicAuth username
 | 
					 | 
				
			||||||
	c.BAUsername, success = os.LookupEnv(envBAUsername)
 | 
					 | 
				
			||||||
	if !success || c.BAUsername == "" {
 | 
					 | 
				
			||||||
		log.Fatalln("Could not retrieve env variable", envBAUsername, "or variable is empty")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// HTTP BasicAuth password
 | 
					 | 
				
			||||||
	c.BAPassword, success = os.LookupEnv(envBAPassword)
 | 
					 | 
				
			||||||
	if !success || c.BAPassword == "" {
 | 
					 | 
				
			||||||
		log.Fatalln("Could not retrieve", envBAPassword, "env variable or variable is empty")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// JSON template path
 | 
					 | 
				
			||||||
	templatePath, success := os.LookupEnv(envJSONTemplatePath)
 | 
					 | 
				
			||||||
	if !success || templatePath == "" {
 | 
					 | 
				
			||||||
		log.Fatalln("Could not retrieve", envJSONTemplatePath, "env variable or variable is empty")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// Save as absolute path
 | 
					 | 
				
			||||||
	c.TemplatePath, err = filepath.Abs(templatePath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatalln("Failed converting", templatePath, "to absolute path:", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										20
									
								
								util/credentials.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								util/credentials.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckCredentials validates whether a given username/password pair matches an
 | 
				
			||||||
 | 
					// entry in the authDB whose id is present in the validCredentials list
 | 
				
			||||||
 | 
					func CheckCredentials(
 | 
				
			||||||
 | 
						authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, username, password string,
 | 
				
			||||||
 | 
					) bool {
 | 
				
			||||||
 | 
						for _, id := range validCredentials {
 | 
				
			||||||
 | 
							if cred, present := authDB[id]; present {
 | 
				
			||||||
 | 
								if cred.Username == username && cred.Password == password {
 | 
				
			||||||
 | 
									return true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										74
									
								
								util/util.go
									
										
									
									
									
								
							
							
						
						
									
										74
									
								
								util/util.go
									
										
									
									
									
								
							| 
						 | 
					@ -1,43 +1,18 @@
 | 
				
			||||||
package util
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"slices"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
						"gitlab.hamburg.ccc.de/ccchh/spaceapid/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json"
 | 
					const (
 | 
				
			||||||
 | 
						savedStateJSONPath = "/var/lib/spaceapid/spaceapid-state.json"
 | 
				
			||||||
// ParseTemplate parses the given file and
 | 
					)
 | 
				
			||||||
func ParseTemplate(file string) (resp types.SpaceAPIResponseV14) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Read template file
 | 
					 | 
				
			||||||
	template, err := os.ReadFile(file)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatalln("Failed reading file:", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Parse JSON
 | 
					 | 
				
			||||||
	dec := json.NewDecoder(bytes.NewReader(template))
 | 
					 | 
				
			||||||
	dec.DisallowUnknownFields()
 | 
					 | 
				
			||||||
	err = dec.Decode(&resp)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatalln("Could not parse SpaceAPI response template:", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Check if compatible with v14
 | 
					 | 
				
			||||||
	if !slices.Contains(resp.APICompatibility, "14") {
 | 
					 | 
				
			||||||
		log.Fatalln("Provided template doesn't specify compatibility with API version 14")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MergeOldState merges a given SpaceAPIResponse with the state saved at the time of last program exit and then deletes
 | 
					// MergeOldState merges a given SpaceAPIResponse with the state saved at the time of last program exit and then deletes
 | 
				
			||||||
// the file containing the old state.
 | 
					// the file containing the old state.
 | 
				
			||||||
| 
						 | 
					@ -45,8 +20,11 @@ func MergeOldState(response *types.SpaceAPIResponseV14) {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		err            error
 | 
							err            error
 | 
				
			||||||
		oldState       []byte
 | 
							oldState       []byte
 | 
				
			||||||
 | 
							persistedState types.PersistentStateV14
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println("Merging old state from", savedStateJSONPath, "...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if state.json is present
 | 
						// Check if state.json is present
 | 
				
			||||||
	_, err = os.Stat(savedStateJSONPath)
 | 
						_, err = os.Stat(savedStateJSONPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -57,18 +35,35 @@ func MergeOldState(response *types.SpaceAPIResponseV14) {
 | 
				
			||||||
		goto removeOld
 | 
							goto removeOld
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Read file and merge
 | 
						// Read file and load persisted state
 | 
				
			||||||
	oldState, err = os.ReadFile(savedStateJSONPath)
 | 
						oldState, err = os.ReadFile(savedStateJSONPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println("Error reading old state from", savedStateJSONPath, ", skipping merge... error:", err)
 | 
							log.Println("Error reading old state from", savedStateJSONPath, ", skipping merge... error:", err)
 | 
				
			||||||
		goto removeOld
 | 
							goto removeOld
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = json.Unmarshal(oldState, response)
 | 
						err = json.Unmarshal(oldState, &persistedState)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Println(savedStateJSONPath, "doesn't seem to contain valid data... error:", err)
 | 
							log.Println(savedStateJSONPath, "doesn't seem to contain valid data... error:", err)
 | 
				
			||||||
		goto removeOld
 | 
							goto removeOld
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Merge state
 | 
				
			||||||
 | 
						response.State = persistedState.State
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Merge sensors
 | 
				
			||||||
 | 
						for key, environmentSensors := range persistedState.Sensors {
 | 
				
			||||||
 | 
							for _, sensor := range environmentSensors {
 | 
				
			||||||
 | 
								// Order or amount of sensors might have changed, so check sensors already present in response
 | 
				
			||||||
 | 
								// and then look for matching values in persisted state
 | 
				
			||||||
 | 
								for i := range response.Sensors[key] {
 | 
				
			||||||
 | 
									if rs := &response.Sensors[key][i]; rs.Location == sensor.Location && rs.Name == sensor.Name {
 | 
				
			||||||
 | 
										rs.Value = sensor.Value
 | 
				
			||||||
 | 
										rs.LastChange = sensor.LastChange
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Delete old state json
 | 
						// Delete old state json
 | 
				
			||||||
removeOld:
 | 
					removeOld:
 | 
				
			||||||
	err = os.RemoveAll(savedStateJSONPath)
 | 
						err = os.RemoveAll(savedStateJSONPath)
 | 
				
			||||||
| 
						 | 
					@ -84,14 +79,12 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
 | 
				
			||||||
		log.Fatalln("Failed creating", savedStateJSONPath, ", aborting... error:", err)
 | 
							log.Fatalln("Failed creating", savedStateJSONPath, ", aborting... error:", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Open persistent state file for reading
 | 
						// Open persistent state file
 | 
				
			||||||
	file, err := os.OpenFile(savedStateJSONPath, os.O_RDWR|os.O_CREATE, 0644)
 | 
						file, err := os.OpenFile(savedStateJSONPath, os.O_WRONLY|os.O_CREATE, 0644)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalln("Failed opening", savedStateJSONPath, "while trying to save current state... error:", err)
 | 
							log.Fatalln("Failed opening", savedStateJSONPath, "while trying to save current state... error:", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer func(file *os.File) {
 | 
						defer file.Close()
 | 
				
			||||||
		_ = file.Close()
 | 
					 | 
				
			||||||
	}(file)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create persistent state
 | 
						// Create persistent state
 | 
				
			||||||
	persistentStateV14 := types.PersistentStateV14{
 | 
						persistentStateV14 := types.PersistentStateV14{
 | 
				
			||||||
| 
						 | 
					@ -100,6 +93,17 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
 | 
				
			||||||
			LastChange int64 `json:"lastchange"`
 | 
								LastChange int64 `json:"lastchange"`
 | 
				
			||||||
		}{Open: response.State.Open, LastChange: response.State.LastChange},
 | 
							}{Open: response.State.Open, LastChange: response.State.LastChange},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						// Save sensor state
 | 
				
			||||||
 | 
						persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
 | 
				
			||||||
 | 
						for key, environmentSensors := range response.Sensors {
 | 
				
			||||||
 | 
							persistentStateV14.Sensors[key] = make([]types.PersistentEnvironmentSensor, len(environmentSensors))
 | 
				
			||||||
 | 
							for i, sensor := range environmentSensors {
 | 
				
			||||||
 | 
								persistentStateV14.Sensors[key][i].Value = sensor.Value
 | 
				
			||||||
 | 
								persistentStateV14.Sensors[key][i].Location = sensor.Location
 | 
				
			||||||
 | 
								persistentStateV14.Sensors[key][i].Name = sensor.Name
 | 
				
			||||||
 | 
								persistentStateV14.Sensors[key][i].LastChange = sensor.LastChange
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Serialize persistent state
 | 
						// Serialize persistent state
 | 
				
			||||||
	marshal, err := json.MarshalIndent(persistentStateV14, "", "\t")
 | 
						marshal, err := json.MarshalIndent(persistentStateV14, "", "\t")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue