commit be29af436f5687a86e5fbff7b36e2912bf034bb6 Author: Bennett Wetters Date: Sat Nov 4 20:10:08 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e1f8da --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# JetBrains IDEs +.idea/ + +# macOS stuff +.DS_Store + +# go build output +spaceapid diff --git a/ccchh-template.json b/ccchh-template.json new file mode 100644 index 0000000..d6a2bcb --- /dev/null +++ b/ccchh-template.json @@ -0,0 +1,48 @@ +{ + "api_compatibility": [ + "14" + ], + "space": "CCCHH", + "logo": "https://next.hamburg.ccc.de/images/logo.svg", + "ext_ccc": "erfa", + "url": "https://hamburg.ccc.de/", + "location": { + "address": "Zeiseweg 9, 22765 Hamburg, Germany", + "lon": 9.9445899999999998, + "lat": 53.55836 + }, + "state": { + "icon": { + "open": "https://hamburg.ccc.de/dooris/open.png", + "closed": "https://hamburg.ccc.de/dooris/close.png" + } + }, + "contact": { + "phone": "", + "irc": "ircs://irc.hackint.org:9999/ccchh", + "mastodon": "@ccchh@chaos.social", + "email": "mail@hamburg.ccc.de", + "ml": "talk@hamburg.ccc.de", + "matrix": "#ccchh:hamburg.ccc.de" + }, + "feeds": { + "blog": { + "type": "application/atom+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.ccchh.net" + }, + { + "name": "GitLab", + "url": "https://gitlab.hamburg.ccc.de" + } + ] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b0eee8a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitlab.hamburg.ccc.de/ccchh/spaceapid + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..9d91479 --- /dev/null +++ b/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "encoding/json" + "io" + "log" + "net/http" + "os" + "slices" + "strconv" + "time" + + "gitlab.hamburg.ccc.de/ccchh/spaceapid/types" +) + +func main() { + validUsername, success := os.LookupEnv("DOORIS_USERNAME") + if !success || validUsername == "" { + log.Fatalln("Could not retrieve DOORIS_API_KEY env variable or variable is empty") + } + + validPassword, success := os.LookupEnv("DOORIS_PASSWORD") + if !success || validPassword == "" { + log.Fatalln("Could not retrieve DOORIS_API_KEY env variable or variable is empty") + } + + initialJson, success := os.LookupEnv("SPACE_API_JSON") + if !success || initialJson == "" { + log.Fatalln("Could not retrieve SPACE_API_JSON env variable or variable is empty") + } + + spaceApiResponse := new(types.SpaceAPIResponseV14) + err := json.Unmarshal([]byte(initialJson), spaceApiResponse) + if err != nil { + log.Fatalln("Could not parse provided JSON") + } + if !slices.Contains(spaceApiResponse.APICompatibility, "14") { + log.Fatalln("Provided JSON doesn't specify compatibility with API version 14") + } + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + log.Println("Wrong METHOD from", r.RemoteAddr) + w.WriteHeader(http.StatusMethodNotAllowed) + w.Header().Set("allow", http.MethodGet) + return + } + + response, err := json.Marshal(spaceApiResponse) + if err != nil { + log.Println("Failed to serialize JSON response") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write(response) + }) + + http.HandleFunc("/state/open", func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || username != validUsername || password != validPassword { + log.Println("Unauthorized request from", r.RemoteAddr) + w.WriteHeader(http.StatusUnauthorized) + w.Header().Set("www-authentication", "Basic realm=\"space-api\"") + return + } + + if r.Method != http.MethodPut { + log.Println("Wrong METHOD from", r.RemoteAddr) + w.WriteHeader(http.StatusMethodNotAllowed) + w.Header().Set("allow", http.MethodPut) + return + } + + body, err := io.ReadAll(r.Body) + if err != nil { + log.Println("Failed to read body status update request from", r.RemoteAddr) + w.WriteHeader(http.StatusInternalServerError) + _, _ = io.WriteString(w, "Failed reading HTTP request body") + return + } + + newState, err := strconv.ParseBool(string(body)) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString(w, "HTTP request body should either be true or false") + return + } + + spaceApiResponse.State.Open = newState + spaceApiResponse.State.LastChange = time.Now().Unix() + + w.WriteHeader(http.StatusOK) + _, _ = io.WriteString(w, "Update Successful") + }) + + log.Println("Starting HTTP server...") + log.Fatalln(http.ListenAndServe(":8080", nil)) +} diff --git a/types/v14.go b/types/v14.go new file mode 100644 index 0000000..10ef93d --- /dev/null +++ b/types/v14.go @@ -0,0 +1,44 @@ +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 struct { + Open bool `json:"open"` + LastChange int64 `json:"lastchange"` + Icon struct { + Closed string `json:"closed"` + Open string `json:"open"` + } `json:"icon"` + } `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"` + 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"` +}