feat: SpaceAPI v15 support
Delete types/v14.go, replace with v15.go.
Rename internal types to not include message version.
I don't currently see the need to support multiple message
versions that have incompatible layouts.
v15 is backwards compatible with v14, so the config example now
specifies both.
Rename the "response" field in the config file to "static" as
dynamic/static makes more sense from the user's perspective.
(Internally it's still SpaceapidConfig.{Dynamic,Response}
to align with type names and usage.)
This commit is contained in:
parent
b5a286a237
commit
fd3cae1866
10 changed files with 293 additions and 196 deletions
|
|
@ -6,7 +6,8 @@
|
|||
$ curl http://localhost:8080 | jq
|
||||
{
|
||||
"api_compatibility": [
|
||||
"14"
|
||||
"14",
|
||||
"15"
|
||||
],
|
||||
"space": "CCCHH",
|
||||
...
|
||||
|
|
@ -22,9 +23,9 @@ The config consists of three parts:
|
|||
- `"credentials"`
|
||||
- List of Username/Password credentials for HTTP BasicAuth
|
||||
- `"dynamic"`
|
||||
- The configuration for the dynamic parts of the message
|
||||
- `"response"`
|
||||
- The static (pre-filled) parts of the response
|
||||
- The dynamic parts of the message
|
||||
- `"static"`
|
||||
- The static (pre-filled) parts of the message
|
||||
|
||||
See [Running](#Running) for details.
|
||||
|
||||
|
|
|
|||
|
|
@ -66,9 +66,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"static": {
|
||||
"api_compatibility": [
|
||||
"14"
|
||||
"14",
|
||||
"15"
|
||||
],
|
||||
"space": "CCCHH",
|
||||
"logo": "https://hamburg.ccc.de/images/logo.svg",
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ func ParseConfiguration() (conf SpaceapidConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if compatible with v14
|
||||
if !slices.Contains(conf.Response.APICompatibility, "14") {
|
||||
log.Fatalln("Provided file doesn't specify compatibility with API version 14")
|
||||
// Sanity check: Config should declare compatibility with current SpaceAPI message version
|
||||
if !slices.Contains(conf.Response.APICompatibility, types.CurrentMessageVersion) {
|
||||
log.Fatalln("Provided config doesn't specify compatibility with current API version", types.CurrentMessageVersion)
|
||||
}
|
||||
|
||||
// Initialize fields for environment sensors
|
||||
|
|
|
|||
|
|
@ -34,5 +34,5 @@ type SpaceapidConfig struct {
|
|||
// The dynamic part of the spaceapi JSON
|
||||
Dynamic DynamicStateConfig `json:"dynamic"`
|
||||
// The static part of the spaceapi JSON
|
||||
Response types.SpaceAPIResponseV14 `json:"response"`
|
||||
Response types.SpaceAPIResponse `json:"static"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"git.hamburg.ccc.de/ccchh/spaceapid/types"
|
||||
)
|
||||
|
||||
func Root(resp *types.SpaceAPIResponseV14) func(http.ResponseWriter, *http.Request) {
|
||||
func Root(resp *types.SpaceAPIResponse) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if GET method
|
||||
if r.Method != http.MethodGet {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
func StateOpen(
|
||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
||||
resp *types.SpaceState,
|
||||
resp *types.SpaceAPIState,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := updateEndpointValidator(authDB, validCredentials, w, r)
|
||||
|
|
@ -37,7 +37,7 @@ func StateOpen(
|
|||
|
||||
func StateMessagePUT(
|
||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
||||
resp *types.SpaceState,
|
||||
resp *types.SpaceAPIState,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := updateEndpointValidator(authDB, validCredentials, w, r)
|
||||
|
|
@ -54,7 +54,7 @@ func StateMessagePUT(
|
|||
|
||||
func StateMessageDELETE(
|
||||
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
|
||||
resp *types.SpaceState,
|
||||
resp *types.SpaceAPIState,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := updateEndpointValidator(authDB, validCredentials, w, r)
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -46,7 +46,7 @@ func main() {
|
|||
// Register signal handler
|
||||
sc := make(chan os.Signal, 1)
|
||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func(resp *types.SpaceAPIResponseV14) {
|
||||
go func(resp *types.SpaceAPIResponse) {
|
||||
<-sc
|
||||
log.Println("Saving state and shutting down...")
|
||||
persistence.SaveCurrentState(*resp)
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ const (
|
|||
|
||||
// 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.
|
||||
func MergeOldState(response *types.SpaceAPIResponseV14) {
|
||||
func MergeOldState(response *types.SpaceAPIResponse) {
|
||||
var (
|
||||
err error
|
||||
oldState []byte
|
||||
persistedState types.PersistentStateV14
|
||||
persistedState types.PersistentSpaceState
|
||||
)
|
||||
|
||||
log.Println("Merging old state from", persistentStatePath, "...")
|
||||
|
|
@ -48,7 +48,7 @@ func MergeOldState(response *types.SpaceAPIResponseV14) {
|
|||
}
|
||||
|
||||
// Merge state
|
||||
response.State = persistedState.State
|
||||
response.State.FromPersistentSpaceState(persistedState)
|
||||
|
||||
// Merge sensors
|
||||
for key, environmentSensors := range persistedState.Sensors {
|
||||
|
|
@ -72,7 +72,7 @@ removeOld:
|
|||
}
|
||||
}
|
||||
|
||||
func SaveCurrentState(response types.SpaceAPIResponseV14) {
|
||||
func SaveCurrentState(response types.SpaceAPIResponse) {
|
||||
// Create state directory if not present
|
||||
err := os.MkdirAll(persistentStateDir, 0750)
|
||||
if err != nil {
|
||||
|
|
@ -87,7 +87,7 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
|
|||
defer file.Close()
|
||||
|
||||
// Create persistent state
|
||||
persistentStateV14 := types.PersistentStateV14{
|
||||
persistentStateV14 := types.PersistentSpaceState{
|
||||
State: struct {
|
||||
Open bool `json:"open"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
|
|
|
|||
69
types/v14.go
69
types/v14.go
|
|
@ -1,69 +0,0 @@
|
|||
package types
|
||||
|
||||
type SpaceAPIResponseV14 struct {
|
||||
APICompatibility []string `json:"api_compatibility"`
|
||||
Space string `json:"space"`
|
||||
Logo string `json:"logo"`
|
||||
ExtCCC string `json:"ext_ccc"`
|
||||
URL string `json:"url"`
|
||||
Location struct {
|
||||
Address string `json:"address"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
} `json:"location"`
|
||||
State SpaceState `json:"state"`
|
||||
Contact struct {
|
||||
Phone string `json:"phone"`
|
||||
IRC string `json:"irc"`
|
||||
Mastodon string `json:"mastodon"`
|
||||
Email string `json:"email"`
|
||||
ML string `json:"ml"`
|
||||
Matrix string `json:"matrix"`
|
||||
} `json:"contact"`
|
||||
Sensors map[string][]EnvironmentSensor `json:"sensors"`
|
||||
Feeds struct {
|
||||
Blog struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
} `json:"blog"`
|
||||
Calendar struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
} `json:"calendar"`
|
||||
} `json:"feeds"`
|
||||
Links []struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
} `json:"links"`
|
||||
}
|
||||
|
||||
type SpaceState struct {
|
||||
Open bool `json:"open"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type EnvironmentSensor struct {
|
||||
Value float64 `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
LastChange int64 `json:"lastchange,omitempty"`
|
||||
}
|
||||
|
||||
type PersistentStateV14 struct {
|
||||
State struct {
|
||||
Open bool `json:"open"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
Message string `json:"message,omitempty"`
|
||||
} `json:"state"`
|
||||
Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
|
||||
}
|
||||
|
||||
type PersistentEnvironmentSensor struct {
|
||||
Value float64 `json:"value"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
}
|
||||
164
types/v15.go
Normal file
164
types/v15.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
package types
|
||||
|
||||
const CurrentMessageVersion = "15"
|
||||
|
||||
// SpaceAPIResponse represents the contents of the SpaceAPI message
|
||||
//
|
||||
// Currently implemented version: 15
|
||||
type SpaceAPIResponse struct {
|
||||
APICompatibility []string `json:"api_compatibility"` // REQUIRED
|
||||
Space string `json:"space"` // REQUIRED
|
||||
Logo string `json:"logo"` // REQUIRED
|
||||
URL string `json:"url"` // REQUIRED
|
||||
|
||||
// Minimum number of properties: 1
|
||||
Location struct {
|
||||
Address string `json:"address,omitzero"`
|
||||
Lat float64 `json:"lat,omitzero"`
|
||||
Lon float64 `json:"lon,omitzero"`
|
||||
Timezone string `json:"timezone,omitzero"`
|
||||
CountryCode string `json:"country_code,omitzero"`
|
||||
Hint string `json:"hint,omitzero"`
|
||||
// Minimum number of items: 1
|
||||
Areas []SpaceAPILocationAreas `json:"areas,omitempty"`
|
||||
} `json:"location,omitzero"`
|
||||
|
||||
SpaceFed struct {
|
||||
SpaceNet bool `json:"spacenet,omitzero"`
|
||||
SpaceSAML bool `json:"spacesaml,omitzero"`
|
||||
} `json:"spacefed,omitzero"`
|
||||
|
||||
Cam []string `json:"cam,omitempty"`
|
||||
|
||||
State SpaceAPIState `json:"state,omitzero"`
|
||||
|
||||
Events []SpaceAPIEvents `json:"events,omitempty"`
|
||||
|
||||
Contact struct {
|
||||
Phone string `json:"phone,omitzero"`
|
||||
Sip string `json:"sip,omitzero"`
|
||||
// keymasters
|
||||
Keymasters []struct {
|
||||
Name string `json:"name,omitzero"`
|
||||
IRCNick string `json:"irc_nick,omitzero"`
|
||||
Phone string `json:"phone,omitzero"`
|
||||
Email string `json:"email,omitzero"`
|
||||
Twitter string `json:"twitter,omitzero"`
|
||||
Xmpp string `json:"xmpp,omitzero"`
|
||||
Mastodon string `json:"mastodon,omitzero"`
|
||||
Matrix string `json:"matrix,omitzero"`
|
||||
} `json:"keymasters,omitempty"`
|
||||
IRC string `json:"irc,omitzero"`
|
||||
Twitter string `json:"twitter,omitzero"`
|
||||
Mastodon string `json:"mastodon,omitzero"`
|
||||
Facebook string `json:"facebook,omitzero"`
|
||||
Identica string `json:"identica,omitzero"`
|
||||
Foursquare string `json:"foursquare,omitzero"`
|
||||
Email string `json:"email,omitzero"`
|
||||
ML string `json:"ml,omitzero"`
|
||||
XMPP string `json:"xmpp,omitzero"`
|
||||
IssueMail string `json:"issue_mail,omitzero"`
|
||||
Gopher string `json:"gopher,omitzero"`
|
||||
Matrix string `json:"matrix,omitzero"`
|
||||
Mumble string `json:"mumble,omitzero"`
|
||||
} `json:"contact"`
|
||||
|
||||
Sensors map[string][]EnvironmentSensor `json:"sensors,omitempty"`
|
||||
|
||||
Feeds struct {
|
||||
Blog struct {
|
||||
Type string `json:"type,omitzero"`
|
||||
URL string `json:"url"` // REQUIRED
|
||||
} `json:"blog,omitzero"`
|
||||
Wiki struct {
|
||||
Type string `json:"type,omitzero"`
|
||||
URL string `json:"url"` // REQUIRED
|
||||
} `json:"wiki,omitzero"`
|
||||
Calendar struct {
|
||||
Type string `json:"type,omitzero"`
|
||||
URL string `json:"url"` // REQUIRED
|
||||
} `json:"calendar,omitzero"`
|
||||
Flickr struct {
|
||||
Type string `json:"type,omitzero"`
|
||||
URL string `json:"url"` // REQUIRED
|
||||
} `json:"flickr,omitzero"`
|
||||
} `json:"feeds,omitzero"`
|
||||
|
||||
Projects []string `json:"projects,omitempty"`
|
||||
|
||||
Links []struct {
|
||||
Name string `json:"name"` // REQUIRED
|
||||
URL string `json:"url"` // REQUIRED
|
||||
Description string `json:"description,omitzero"`
|
||||
} `json:"links,omitempty"`
|
||||
|
||||
MembershipPlans []struct {
|
||||
Name string `json:"name"` // REQUIRED
|
||||
Value int64 `json:"value"` // REQUIRED
|
||||
Currency string `json:"currency"` // REQUIRED
|
||||
BillingInterval string `json:"billing_interval"` // REQUIRED
|
||||
Description string `json:"description,omitzero"`
|
||||
} `json:"membership_plans,omitempty"`
|
||||
|
||||
LinkedSpaces []struct {
|
||||
Endpoint string `json:"endpoint,omitzero"`
|
||||
Website string `json:"website,omitzero"`
|
||||
} `json:"linked_spaces,omitempty"`
|
||||
|
||||
ExtCCC string `json:"ext_ccc,omitzero"`
|
||||
}
|
||||
|
||||
type SpaceAPILocationAreas struct {
|
||||
Name string `json:"name,omitzero"`
|
||||
Description string `json:"description,omitzero"`
|
||||
SquareMeters float64 `json:"square_meters"` // REQUIRED
|
||||
}
|
||||
|
||||
type SpaceAPIState struct {
|
||||
Open bool `json:"open,omitempty"`
|
||||
LastChange int64 `json:"lastchange,omitempty"`
|
||||
TriggerPerson string `json:"trigger_person,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Icon struct {
|
||||
Open string `json:"open,omitempty"` // REQUIRED
|
||||
Closed string `json:"closed,omitempty"` // REQUIRED
|
||||
} `json:"icon,omitzero"`
|
||||
}
|
||||
|
||||
func (s *SpaceAPIState) FromPersistentSpaceState(ps PersistentSpaceState) {
|
||||
s.Open = ps.State.Open
|
||||
s.LastChange = ps.State.LastChange
|
||||
s.Message = ps.State.Message
|
||||
}
|
||||
|
||||
type SpaceAPIEvents struct {
|
||||
Name string `json:"name"` // REQUIRED
|
||||
Type string `json:"type"` // REQUIRED
|
||||
Timestamp int64 `json:"timestamp"` // REQUIRED
|
||||
Extra string `json:"extra,omitzero"`
|
||||
}
|
||||
|
||||
type EnvironmentSensor struct {
|
||||
Value float64 `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
LastChange int64 `json:"lastchange,omitempty"`
|
||||
}
|
||||
|
||||
type PersistentSpaceState struct {
|
||||
State struct {
|
||||
Open bool `json:"open"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
Message string `json:"message,omitempty"`
|
||||
} `json:"state"`
|
||||
Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
|
||||
}
|
||||
|
||||
type PersistentEnvironmentSensor struct {
|
||||
Value float64 `json:"value"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
LastChange int64 `json:"lastchange"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue