diff --git a/config-template.json b/config-template.json index 31cb500..8b2b039 100644 --- a/config-template.json +++ b/config-template.json @@ -1,111 +1,112 @@ { - "credentials": { - "club-assistant": { - "username": "club-assistant", - "password": "clubmiau" - }, - "dooris-hauptraum": { - "username": "dooris-hauptraum", - "password": "doorimiau" - } - }, - "dynamic": { - "sensors": { - "temperature": [ - { - "sensor_data": { - "unit": "°C", - "location": "Hauptraum", - "name": "Kueche", - "description": "Sensor im Ofen" - }, - "allowed_credentials": [ - "club-assistant" - ] - }, - { - "sensor_data": { - "unit": "°C", - "location": "Hauptraum", - "description": "Sensor im Hauptraum" - }, - "allowed_credentials": [ - "club-assistant" - ] - } - ], - "humidity": [ - { - "sensor_data": { - "unit": "%", - "location": "Hauptraum", - "name": "Kueche", - "description": "Sensor im Wasserhahn" - }, - "allowed_credentials": [ - "club-assistant" - ] - } - ], - "beverage_supply": [ - { - "sensor_data": { - "unit": "btl" - }, - "allowed_credentials": [ - "club-assistant" - ] - } - ] - }, - "state": { - "open": { - "allowed_credentials": [ - "dooris-hauptraum" - ] - } - } - }, - "response": { - "api_compatibility": [ - "14" - ], - "space": "CCCHH", - "logo": "https://hamburg.ccc.de/images/logo.svg", - "ext_ccc": "erfa", - "url": "https://hamburg.ccc.de/", - "location": { - "address": "Zeiseweg 9, 22765 Hamburg, Germany", - "lon": 9.94444, - "lat": 53.5584 - }, - "contact": { - "phone": "+49 40 23830150", - "irc": "ircs://irc.hackint.org:6697/#ccchh", - "mastodon": "@ccchh@chaos.social", - "email": "mail@hamburg.ccc.de", - "ml": "talk@hamburg.ccc.de", - "matrix": "#ccchh:hamburg.ccc.de" - }, - "feeds": { - "blog": { - "type": "application/rss+xml", - "url": "https://hamburg.ccc.de/feed.xml" - }, - "calendar": { - "type": "ical", - "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export" - } - }, - "links": [ - { - "name": "Wiki", - "url": "https://wiki.hamburg.ccc.de" - }, - { - "name": "Git (Forgejo)", - "url": "https://git.hamburg.ccc.de" - } - ] - } + "credentials": { + "club-assistant": { + "username": "club-assistant", + "password": "clubmiau" + }, + "dooris-hauptraum": { + "username": "dooris-hauptraum", + "password": "doorimiau" + } + }, + "dynamic": { + "sensors": { + "temperature": [ + { + "sensor_data": { + "unit": "°C", + "location": "Hauptraum", + "name": "Kueche", + "description": "Sensor im Ofen" + }, + "allowed_credentials": [ + "club-assistant" + ] + }, + { + "sensor_data": { + "unit": "°C", + "location": "Hauptraum", + "description": "Sensor im Hauptraum" + }, + "allowed_credentials": [ + "club-assistant" + ] + } + ], + "humidity": [ + { + "sensor_data": { + "unit": "%", + "location": "Hauptraum", + "name": "Kueche", + "description": "Sensor im Wasserhahn" + }, + "allowed_credentials": [ + "club-assistant" + ] + } + ], + "beverage_supply": [ + { + "sensor_data": { + "unit": "btl" + }, + "allowed_credentials": [ + "club-assistant" + ] + } + ] + }, + "state": { + "open": { + "allowed_credentials": [ + "dooris-hauptraum" + ] + } + } + }, + "response": { + "api_compatibility": [ + "14", + "15" + ], + "space": "CCCHH", + "logo": "https://hamburg.ccc.de/images/logo.svg", + "ext_ccc": "erfa", + "url": "https://hamburg.ccc.de/", + "location": { + "address": "Zeiseweg 9, 22765 Hamburg, Germany", + "lon": 9.94444, + "lat": 53.5584 + }, + "contact": { + "phone": "+49 40 23830150", + "irc": "ircs://irc.hackint.org:6697/#ccchh", + "mastodon": "@ccchh@chaos.social", + "email": "mail@hamburg.ccc.de", + "ml": "talk@hamburg.ccc.de", + "matrix": "#ccchh:hamburg.ccc.de" + }, + "feeds": { + "blog": { + "type": "application/rss+xml", + "url": "https://hamburg.ccc.de/feed.xml" + }, + "calendar": { + "type": "ical", + "url": "webcal://cloud.hamburg.ccc.de/remote.php/dav/public-calendars/QJAdExziSnNJEz5g/?export" + } + }, + "links": [ + { + "name": "Wiki", + "url": "https://wiki.hamburg.ccc.de" + }, + { + "name": "Git (Forgejo)", + "url": "https://git.hamburg.ccc.de" + } + ] + } } diff --git a/config/config.go b/config/config.go index 444fd45..b57d615 100644 --- a/config/config.go +++ b/config/config.go @@ -59,7 +59,7 @@ func getConfigPaths() (paths pathsT) { // If paths is still empty we are missing something if len(paths) == 0 { - log.Fatalln("Error: Neither env: ", envConfigPath, "nor cli flag was specified, unable to start without config.") + log.Fatalln("Error: Neither env:", envConfigPath, "nor cli flag was specified, unable to start without config.") } return } diff --git a/config/types.go b/config/types.go index 4a305ce..9361579 100644 --- a/config/types.go +++ b/config/types.go @@ -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:"response"` } diff --git a/handlers/root.go b/handlers/root.go index 47091d5..88cdc55 100644 --- a/handlers/root.go +++ b/handlers/root.go @@ -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 { diff --git a/handlers/state.go b/handlers/state.go index d1ab9d9..d4962c1 100644 --- a/handlers/state.go +++ b/handlers/state.go @@ -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) diff --git a/handlers/util.go b/handlers/util.go index 27c2d9f..d7e6dd5 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -18,7 +18,7 @@ func updateEndpointValidator( // Check BasicAuth credentials username, password, ok := r.BasicAuth() 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) return []byte{}, errors.New(fmt.Sprintf("Unauthorized request from %s Username: %s Password: %s", r.RemoteAddr, username, password)) } diff --git a/main.go b/main.go index 35b564d..b67c43f 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,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) diff --git a/persistence/persistentState.go b/persistence/persistentState.go index 74fbdb2..87c3121 100644 --- a/persistence/persistentState.go +++ b/persistence/persistentState.go @@ -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"` diff --git a/types/v14.go b/types/v14.go deleted file mode 100644 index 3763fe4..0000000 --- a/types/v14.go +++ /dev/null @@ -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"` -} diff --git a/types/v15.go b/types/v15.go new file mode 100644 index 0000000..0362fab --- /dev/null +++ b/types/v15.go @@ -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"` +}