Compare commits

...

1 commit

Author SHA1 Message Date
7a8b904852 --wip-- [skip ci] 2026-07-21 21:32:49 +02:00
10 changed files with 282 additions and 191 deletions

View file

@ -68,7 +68,8 @@
}, },
"response": { "response": {
"api_compatibility": [ "api_compatibility": [
"14" "14",
"15"
], ],
"space": "CCCHH", "space": "CCCHH",
"logo": "https://hamburg.ccc.de/images/logo.svg", "logo": "https://hamburg.ccc.de/images/logo.svg",

View file

@ -34,5 +34,5 @@ type SpaceapidConfig struct {
// The dynamic part of the spaceapi JSON // The dynamic part of the spaceapi JSON
Dynamic DynamicStateConfig `json:"dynamic"` Dynamic DynamicStateConfig `json:"dynamic"`
// The static part of the spaceapi JSON // The static part of the spaceapi JSON
Response types.SpaceAPIResponseV14 `json:"response"` Response types.SpaceAPIResponse `json:"response"`
} }

View file

@ -8,7 +8,7 @@ import (
"git.hamburg.ccc.de/ccchh/spaceapid/types" "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) { return func(w http.ResponseWriter, r *http.Request) {
// Check if GET method // Check if GET method
if r.Method != http.MethodGet { if r.Method != http.MethodGet {

View file

@ -12,7 +12,7 @@ import (
func StateOpen( func StateOpen(
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
resp *types.SpaceState, resp *types.SpaceAPIState,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body, err := updateEndpointValidator(authDB, validCredentials, w, r) body, err := updateEndpointValidator(authDB, validCredentials, w, r)
@ -37,7 +37,7 @@ func StateOpen(
func StateMessagePUT( func StateMessagePUT(
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
resp *types.SpaceState, resp *types.SpaceAPIState,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body, err := updateEndpointValidator(authDB, validCredentials, w, r) body, err := updateEndpointValidator(authDB, validCredentials, w, r)
@ -54,7 +54,7 @@ func StateMessagePUT(
func StateMessageDELETE( func StateMessageDELETE(
authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID, authDB config.HTTPBACredentials, validCredentials []config.HTTPBACredentialID,
resp *types.SpaceState, resp *types.SpaceAPIState,
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
_, err := updateEndpointValidator(authDB, validCredentials, w, r) _, err := updateEndpointValidator(authDB, validCredentials, w, r)

View file

@ -18,7 +18,7 @@ func updateEndpointValidator(
// Check BasicAuth credentials // Check BasicAuth credentials
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok || !util.CheckCredentials(authDB, validCredentials, username, password) { if !ok || !util.CheckCredentials(authDB, validCredentials, username, password) {
w.Header()["WWW-Authenticate"] = []string{"Basic realm=\"spaceapid\""} w.Header().Set("WWW-Authenticate", "Basic realm=\"spaceapid\"")
http.Error(w, "", http.StatusUnauthorized) http.Error(w, "", http.StatusUnauthorized)
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))
} }

View file

@ -43,7 +43,7 @@ func main() {
// 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(resp *types.SpaceAPIResponseV14) { go func(resp *types.SpaceAPIResponse) {
<-sc <-sc
log.Println("Saving state and shutting down...") log.Println("Saving state and shutting down...")
persistence.SaveCurrentState(*resp) persistence.SaveCurrentState(*resp)

View file

@ -16,11 +16,11 @@ const (
// 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.
func MergeOldState(response *types.SpaceAPIResponseV14) { func MergeOldState(response *types.SpaceAPIResponse) {
var ( var (
err error err error
oldState []byte oldState []byte
persistedState types.PersistentStateV14 persistedState types.PersistentSpaceState
) )
log.Println("Merging old state from", persistentStatePath, "...") log.Println("Merging old state from", persistentStatePath, "...")
@ -48,7 +48,7 @@ func MergeOldState(response *types.SpaceAPIResponseV14) {
} }
// Merge state // Merge state
response.State = persistedState.State response.State.FromPersistentSpaceState(persistedState)
// Merge sensors // Merge sensors
for key, environmentSensors := range persistedState.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 // Create state directory if not present
err := os.MkdirAll(persistentStateDir, 0750) err := os.MkdirAll(persistentStateDir, 0750)
if err != nil { if err != nil {
@ -87,7 +87,7 @@ func SaveCurrentState(response types.SpaceAPIResponseV14) {
defer file.Close() defer file.Close()
// Create persistent state // Create persistent state
persistentStateV14 := types.PersistentStateV14{ persistentStateV14 := types.PersistentSpaceState{
State: struct { State: struct {
Open bool `json:"open"` Open bool `json:"open"`
LastChange int64 `json:"lastchange"` LastChange int64 `json:"lastchange"`

View file

@ -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"`
}

159
types/v15.go Normal file
View file

@ -0,0 +1,159 @@
package types
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(state PersistentSpaceState) {
s.Open = state.State.Open
s.LastChange = state.State.LastChange
s.Message = state.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"`
}