Compare commits
	
		
			1 commit
		
	
	
		
			
				main
			
			...
			
				renovate/c
			
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f20b0fbfa9 | 
					 9 changed files with 55 additions and 98 deletions
				
			
		
							
								
								
									
										58
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										58
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
`spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080:
 | 
					`spaceapid` serves a [SpaceAPI](https://spaceapi.io)-compatible JSON on port 8080:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
$ curl http://localhost:8080 | jq
 | 
					$ curl http://[::1]:8080 | jq
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "api_compatibility": [
 | 
					  "api_compatibility": [
 | 
				
			||||||
    "14"
 | 
					    "14"
 | 
				
			||||||
| 
						 | 
					@ -28,50 +28,38 @@ The config consists of three parts:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See [Running](#Running) for details.
 | 
					See [Running](#Running) for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Updating values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The state of the boolean `state->open` property can be modified via `/state/open`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					curl -X PUT -u user:password -d true http://[::1]:8080/state/open
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The same is true for the endpoints for sensors configured under `"dynamic"`.
 | 
				
			||||||
 | 
					Currently only the sensors with the `value/unit/location/name/description` schema are implemented.
 | 
				
			||||||
 | 
					At the time of writing this includes `temperature`, `barometer`, `humidity`, `beverage_supply`, `power_consumption`,
 | 
				
			||||||
 | 
					and `account_balance`.
 | 
				
			||||||
 | 
					Out-of-spec sensors may also be used as long as they share the same schema.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```shell
 | 
				
			||||||
 | 
					curl -X PUT -u user:password -d 23.42 http://[::1]:8080/sensors/{temperature,humidity,...}[/location[/name]]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As can be seen in the example, the http urls are generated from sensor type and optionally `location` and `name`.
 | 
				
			||||||
 | 
					Depending on sensor type `location` might be required for your sensors, see the schema for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Building
 | 
					## Building
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See the `go.mod` file for minimum required Go version.
 | 
					See the `go.mod` file for minimum required Go version.
 | 
				
			||||||
There are currently no dependencies apart from the Go standard library.
 | 
					There are currently no dependencies apart from the Go standard library.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``` shell
 | 
					 | 
				
			||||||
go build -ldflags '-X main.version=v420.69-rc23' .
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Running
 | 
					## Running
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Set the environment variable to a comma-separated list of config files or pass the `-c` flag.
 | 
					Set the environment variable to a comma-separated list of config files or pass the `-c` flag.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```shell
 | 
					```shell
 | 
				
			||||||
env SPACEAPID_CONFIG_PATH=config-template.json go run .
 | 
					env CONFIG_PATH=config-template.json go run .
 | 
				
			||||||
# OR
 | 
					# OR
 | 
				
			||||||
go run . -c config-credentials.json,config-dynamic.json,config-response.json
 | 
					go run . -c config-credentials.json,config-dynamic.json,config-response.json
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Updating values
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The state of the boolean `state->open` and `state->message` property can be modified via `/state/{open,message}`:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```shell
 | 
					 | 
				
			||||||
curl -X PUT -u user:password -d true http://localhost:8080/state/open
 | 
					 | 
				
			||||||
curl -X PUT -u user:password -d "Nur mit Passierschein A38 :3" http://localhost:8080/state/message
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
As `state->message` is optional, its value can be deleted by using the `PUT` method with an empty payload, or by using `DELETE`:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```shell
 | 
					 | 
				
			||||||
curl -X PUT -u user:password -d "" http://localhost:8080/state/message
 | 
					 | 
				
			||||||
# OR
 | 
					 | 
				
			||||||
curl -X DELETE -u user:password http://localhost:8080/state/message
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The same updating procedure applies for the endpoints for sensors configured under `"dynamic"`.
 | 
					 | 
				
			||||||
Currently only the sensors with the `value/unit/location/name/description` schema are implemented.
 | 
					 | 
				
			||||||
At the time of writing this includes `temperature`, `barometer`, `humidity`, `beverage_supply`, `power_consumption`, and `account_balance`.
 | 
					 | 
				
			||||||
Out-of-spec sensors may be used as well, as long as they share the same schema.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```shell
 | 
					 | 
				
			||||||
curl -X PUT -u user:password -d 23.42 http://localhost:8080/sensors/{temperature,humidity,...}[/location[/name]]
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
As can be seen in the example, the http urls are generated from sensor type and optionally `location` and `name`.
 | 
					 | 
				
			||||||
Depending on sensor type, `location` might be required for your sensors, see the schema for details.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	envConfigPath = "SPACEAPID_CONFIG_PATH"
 | 
						envConfigPath = "CONFIG_PATH"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type pathsT []string
 | 
					type pathsT []string
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,7 @@ func getConfigPaths() (paths pathsT) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If paths is still empty we are missing something
 | 
						// If paths is still empty we are missing something
 | 
				
			||||||
	if len(paths) == 0 {
 | 
						if len(paths) == 0 {
 | 
				
			||||||
		log.Fatalln("Error: Neither env: ", envConfigPath, "nor cli flag was specified, unable to start without config.")
 | 
							log.Fatalln("Neither the env variable nor cli flag was specified, we are missing a config file.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -91,7 +91,7 @@ func ParseConfiguration() (conf SpaceapidConfig) {
 | 
				
			||||||
		log.Fatalln("Provided file doesn't specify compatibility with API version 14")
 | 
							log.Fatalln("Provided file doesn't specify compatibility with API version 14")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Initialize fields for environment sensors
 | 
						// Initialise fields for environment sensors
 | 
				
			||||||
	conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
 | 
						conf.Response.Sensors = make(map[string][]types.EnvironmentSensor)
 | 
				
			||||||
	for key, sensorConfigs := range conf.Dynamic.Sensors {
 | 
						for key, sensorConfigs := range conf.Dynamic.Sensors {
 | 
				
			||||||
		conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
 | 
							conf.Response.Sensors[key] = make([]types.EnvironmentSensor, len(sensorConfigs))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							| 
						 | 
					@ -1,3 +1,3 @@
 | 
				
			||||||
module git.hamburg.ccc.de/ccchh/spaceapid
 | 
					module git.hamburg.ccc.de/ccchh/spaceapid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.22
 | 
					go 1.21
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,37 +34,3 @@ func StateOpen(
 | 
				
			||||||
		resp.LastChange = time.Now().Unix()
 | 
							resp.LastChange = time.Now().Unix()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func StateMessagePUT(
 | 
					 | 
				
			||||||
	authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
					 | 
				
			||||||
	resp *types.SpaceState,
 | 
					 | 
				
			||||||
) func(http.ResponseWriter, *http.Request) {
 | 
					 | 
				
			||||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
		body, err := updateEndpointValidator(authDB, validCredentials, w, r)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Set SpaceAPI response values
 | 
					 | 
				
			||||||
		resp.Message = string(body)
 | 
					 | 
				
			||||||
		resp.LastChange = time.Now().Unix()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func StateMessageDELETE(
 | 
					 | 
				
			||||||
	authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
					 | 
				
			||||||
	resp *types.SpaceState,
 | 
					 | 
				
			||||||
) func(http.ResponseWriter, *http.Request) {
 | 
					 | 
				
			||||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
		_, err := updateEndpointValidator(authDB, validCredentials, w, r)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			log.Println(err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Set SpaceAPI response values
 | 
					 | 
				
			||||||
		resp.Message = ""
 | 
					 | 
				
			||||||
		resp.LastChange = time.Now().Unix()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,8 @@ import (
 | 
				
			||||||
	"git.hamburg.ccc.de/ccchh/spaceapid/util"
 | 
						"git.hamburg.ccc.de/ccchh/spaceapid/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// updateEndpointValidator checks BasicAuth credentials and then returns the request body
 | 
					// updateEndpointValidator checks BasicAuth credentials,
 | 
				
			||||||
 | 
					// checks for correct HTTP method and then returns the request body
 | 
				
			||||||
func updateEndpointValidator(
 | 
					func updateEndpointValidator(
 | 
				
			||||||
	authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
						authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
 | 
				
			||||||
	w http.ResponseWriter, r *http.Request,
 | 
						w http.ResponseWriter, r *http.Request,
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,13 @@ func updateEndpointValidator(
 | 
				
			||||||
		return []byte{}, errors.New(fmt.Sprintf("Unauthorized request from %s Username: %s Password: %s", r.RemoteAddr, username, password))
 | 
							return []byte{}, errors.New(fmt.Sprintf("Unauthorized request from %s Username: %s Password: %s", r.RemoteAddr, username, password))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if PUT method
 | 
				
			||||||
 | 
						if r.Method != http.MethodPut {
 | 
				
			||||||
 | 
							w.Header().Set("Allow", http.MethodPut)
 | 
				
			||||||
 | 
							http.Error(w, "", http.StatusMethodNotAllowed)
 | 
				
			||||||
 | 
							return []byte{}, errors.New(fmt.Sprintf("Wrong Method: %s from %s at %s", r.Method, r.RemoteAddr, r.RequestURI))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Read request body
 | 
						// Read request body
 | 
				
			||||||
	body, err := io.ReadAll(r.Body)
 | 
						body, err := io.ReadAll(r.Body)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								main.go
									
										
									
									
									
								
							| 
						 | 
					@ -50,29 +50,21 @@ func main() {
 | 
				
			||||||
		os.Exit(0)
 | 
							os.Exit(0)
 | 
				
			||||||
	}(&conf.Response)
 | 
						}(&conf.Response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Root handler
 | 
						// Register HTTP handlers
 | 
				
			||||||
	http.HandleFunc("GET /{$}",
 | 
						http.HandleFunc("/",
 | 
				
			||||||
		handlers.Root(&conf.Response),
 | 
							handlers.Root(&conf.Response),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	// state->open
 | 
						http.HandleFunc("/state/open",
 | 
				
			||||||
	http.HandleFunc("PUT /state/open",
 | 
					 | 
				
			||||||
		handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
 | 
							handlers.StateOpen(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	// state->message
 | 
						// Register handlers for Environmental Sensors
 | 
				
			||||||
	http.HandleFunc("PUT /state/message",
 | 
					 | 
				
			||||||
		handlers.StateMessagePUT(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	http.HandleFunc("DELETE /state/message",
 | 
					 | 
				
			||||||
		handlers.StateMessageDELETE(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	// Register handlers for environmental sensors
 | 
					 | 
				
			||||||
	for sensorType, envSensorConfigs := range conf.Dynamic.Sensors {
 | 
						for sensorType, envSensorConfigs := range conf.Dynamic.Sensors {
 | 
				
			||||||
		for i, envSensorConfig := range envSensorConfigs {
 | 
							for i, envSensorConfig := range envSensorConfigs {
 | 
				
			||||||
			urlPattern := "PUT " + util.GetSensorURLPath(
 | 
								urlPath := util.GetSensorURLPath(
 | 
				
			||||||
				sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
 | 
									sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
			http.HandleFunc(
 | 
								http.HandleFunc(
 | 
				
			||||||
				urlPattern,
 | 
									urlPath,
 | 
				
			||||||
				handlers.EnvironmentSensor(
 | 
									handlers.EnvironmentSensor(
 | 
				
			||||||
					conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
 | 
										conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
 | 
				
			||||||
				),
 | 
									),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,10 +89,9 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
 | 
				
			||||||
	// Create persistent state
 | 
						// Create persistent state
 | 
				
			||||||
	persistentStateV14 := types.PersistentStateV14{
 | 
						persistentStateV14 := types.PersistentStateV14{
 | 
				
			||||||
		State: struct {
 | 
							State: struct {
 | 
				
			||||||
			Open       bool   `json:"open"`
 | 
								Open       bool  `json:"open"`
 | 
				
			||||||
			LastChange int64  `json:"lastchange"`
 | 
								LastChange int64 `json:"lastchange"`
 | 
				
			||||||
			Message    string `json:"message,omitempty"`
 | 
							}{Open: response.State.Open, LastChange: response.State.LastChange},
 | 
				
			||||||
		}{Open: response.State.Open, LastChange: response.State.LastChange, Message: response.State.Message},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Save sensor state
 | 
						// Save sensor state
 | 
				
			||||||
	persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
 | 
						persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								renovate.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								renovate.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
 | 
				
			||||||
 | 
					  "extends": [
 | 
				
			||||||
 | 
					    "config:recommended"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								types/v14.go
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								types/v14.go
									
										
									
									
									
								
							| 
						 | 
					@ -38,9 +38,8 @@ type SpaceAPIResponseV14 struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SpaceState struct {
 | 
					type SpaceState struct {
 | 
				
			||||||
	Open       bool   `json:"open"`
 | 
						Open       bool  `json:"open"`
 | 
				
			||||||
	LastChange int64  `json:"lastchange"`
 | 
						LastChange int64 `json:"lastchange"`
 | 
				
			||||||
	Message    string `json:"message,omitempty"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type EnvironmentSensor struct {
 | 
					type EnvironmentSensor struct {
 | 
				
			||||||
| 
						 | 
					@ -54,9 +53,8 @@ type EnvironmentSensor struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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"`
 | 
				
			||||||
		Message    string `json:"message,omitempty"`
 | 
					 | 
				
			||||||
	} `json:"state"`
 | 
						} `json:"state"`
 | 
				
			||||||
	Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
 | 
						Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue