spaceapid/main.go
Bennett Wetters 11046cb501 feat: Allow setting state->open to unknown via DELETE
The schema defines the absence of state->open to represent the unknown
state. Previously it was not possible to explicitly set this state via
the HTTP endpoints.

types.SpaceAPIState.Open is now an enum which is either unknown, closed,
or open. Custom IsZero() and MarshalJSON() methods on the type ensure
marshaling the value yields a boolean for open/closed and an empty byte
slice for unknown (resulting in the field being absent from the final
response).

The handlers.StateOpenDELETE() function has been added to expose this
behavior.

Switching to encoding/json/v2 required raising the minimum Go version in
go.mod to 1.27.

Closes: CCCHH/spaceapid!37
Fixes:  CCCHH/spaceapid#38
2026-10-09 00:30:26 +02:00

98 lines
2.5 KiB
Go

package main
import (
"flag"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"git.hamburg.ccc.de/ccchh/spaceapid/config"
"git.hamburg.ccc.de/ccchh/spaceapid/handlers"
"git.hamburg.ccc.de/ccchh/spaceapid/persistence"
"git.hamburg.ccc.de/ccchh/spaceapid/types"
"git.hamburg.ccc.de/ccchh/spaceapid/util"
)
var (
flagHelp bool
version string
)
func init() {
flag.BoolVar(&flagHelp, "h", false, "Print help output")
}
func main() {
if version == "" {
version = "unknown"
}
log.Println("SpaceAPId version", version)
flag.Parse()
if flagHelp {
flag.PrintDefaults()
os.Exit(0)
}
// Get spaceapid configuration
conf := config.ParseConfiguration()
// Merge old state if present
persistence.MergeOldState(&conf.Response)
// Register signal handler
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
go func(resp *types.SpaceAPIResponse) {
<-sc
log.Println("Saving state and shutting down...")
persistence.SaveCurrentState(*resp)
os.Exit(0)
}(&conf.Response)
// Root handler
http.HandleFunc("GET /{$}",
handlers.Root(&conf.Response),
)
// state->open
http.HandleFunc("PUT /state/open",
handlers.StateOpenPUT(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
)
http.HandleFunc("DELETE /state/open",
handlers.StateOpenDELETE(conf.Credentials, conf.Dynamic.State.Open.AllowedCredentials, &conf.Response.State),
)
// 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 i, envSensorConfig := range envSensorConfigs {
urlPattern := util.GetSensorURLPath(
sensorType, envSensorConfig.SensorData.Location, envSensorConfig.SensorData.Name,
)
http.HandleFunc(
"PUT "+urlPattern,
handlers.EnvironmentSensorPUT(
conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
),
)
http.HandleFunc(
"DELETE "+urlPattern,
handlers.EnvironmentSensorDELETE(
conf.Credentials, envSensorConfig.AllowedCredentials, &conf.Response.Sensors[sensorType][i],
),
)
}
}
// Start webserver
log.Println("Starting HTTP server...")
log.Fatalln(http.ListenAndServe(":8080", nil))
}