Compare commits

..

7 commits

9 changed files with 98 additions and 55 deletions

View file

@ -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://[::1]:8080 | jq $ curl http://localhost:8080 | jq
{ {
"api_compatibility": [ "api_compatibility": [
"14" "14"
@ -28,38 +28,50 @@ 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 CONFIG_PATH=config-template.json go run . env SPACEAPID_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.

View file

@ -14,7 +14,7 @@ import (
) )
const ( const (
envConfigPath = "CONFIG_PATH" envConfigPath = "SPACEAPID_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("Neither the env variable nor cli flag was specified, we are missing a config file.") log.Fatalln("Error: Neither env: ", envConfigPath, "nor cli flag was specified, unable to start without config.")
} }
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")
} }
// Initialise fields for environment sensors // Initialize 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
View file

@ -1,3 +1,3 @@
module git.hamburg.ccc.de/ccchh/spaceapid module git.hamburg.ccc.de/ccchh/spaceapid
go 1.21 go 1.22

View file

@ -34,3 +34,37 @@ 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()
}
}

View file

@ -10,8 +10,7 @@ import (
"git.hamburg.ccc.de/ccchh/spaceapid/util" "git.hamburg.ccc.de/ccchh/spaceapid/util"
) )
// updateEndpointValidator checks BasicAuth credentials, // updateEndpointValidator checks BasicAuth credentials and then returns the request body
// 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,
@ -24,13 +23,6 @@ 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
View file

@ -50,21 +50,29 @@ func main() {
os.Exit(0) os.Exit(0)
}(&conf.Response) }(&conf.Response)
// Register HTTP handlers // Root handler
http.HandleFunc("/", http.HandleFunc("GET /{$}",
handlers.Root(&conf.Response), handlers.Root(&conf.Response),
) )
http.HandleFunc("/state/open", // 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),
) )
// Register handlers for Environmental Sensors // state->message
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 {
urlPath := util.GetSensorURLPath( urlPattern := "PUT " + util.GetSensorURLPath(
sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name, sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
) )
http.HandleFunc( http.HandleFunc(
urlPath, urlPattern,
handlers.EnvironmentSensor( handlers.EnvironmentSensor(
conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i], conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
), ),

View file

@ -89,9 +89,10 @@ 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"`
}{Open: response.State.Open, LastChange: response.State.LastChange}, Message string `json:"message,omitempty"`
}{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)

View file

@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}

View file

@ -38,8 +38,9 @@ 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 {
@ -53,8 +54,9 @@ 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"`
} }