// really stupid test door server package main import ( "context" "encoding/json" "errors" "fmt" "io" "log" "net/http" "os" "os/signal" "strings" "sync" "time" ) type DoorBoyServer struct { longPollers map[string]chan string } type card struct { UidHash string `json:"uid_hash"` } type cardToken struct { Token card `json:"token"` } type cardList struct { AllowedUids []cardToken `json:"allowed_uids"` KeepOpenUntil *time.Time `json:"keep_open_until,omitempty"` } var keys []cardToken = []cardToken{ //{card{UidHash: "d72c87d0f077c7766f2985dfab30e8955c373a13a1e93d315203939f542ff86e73ee37c31f4c4b571f4719fa8e3589f12db8dcb57ea9f56764bb7d58f64cf705"}}, {card{UidHash: "873636abbcf597a4835afc1c9b16a72eb6175b7ab278a8f18ab13c50172c90ea97cc3e258efd9cc1d885d7ea32d87fd006907892793e7cd6c468417bd8b8421a"}}, {card{UidHash: "a08094343b4057777af4935b79df586d7eb999117883a53393e8b46f1ab19577b12039a5d2e0b9d0364bbed5c82d83a507492fea47ace633acf23da2dcf1560e"}}, {card{UidHash: "d78271547cde009726b159dca09e53bee72feebe90b3eb7cb6e394caafc30bdb1f1567efc2f19bbdf3c6922e0bebed910ee4fa4f5b13bd379651da4f620f3559"}}, } var keyLock sync.RWMutex var keepOpenUntil *time.Time func getRoot(w http.ResponseWriter, r *http.Request) { fmt.Printf("got / request\n") io.WriteString(w, "This is my website!\n") } func getAllowed(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") keyLock.RLock() keys := cardList{ AllowedUids: keys, KeepOpenUntil: keepOpenUntil, } respJson, _ := json.Marshal(keys) keyLock.RUnlock() _, err := w.Write(respJson) if err != nil { return } } func writeAndFlush(writer http.ResponseWriter, buffer []byte) error { _, err := writer.Write(buffer) if err != nil { return err } if f, ok := writer.(http.Flusher); ok { f.Flush() } return nil } func (doorboyserver *DoorBoyServer) getLongPoll(w http.ResponseWriter, r *http.Request) { err := writeAndFlush(w, []byte("data: response-generator-started\n\n")) if err != nil { return } events := make(chan string) doorboyserver.longPollers[r.RemoteAddr] = events defer delete(doorboyserver.longPollers, r.RemoteAddr) err = writeAndFlush(w, []byte("data: watch-stream-opened\n\n")) if err != nil { return } d := <-events // get door open event _, err = w.Write([]byte("data: ")) if err != nil { return } _, err = w.Write([]byte(d)) if err != nil { return } err = writeAndFlush(w, []byte("\n\n")) if err != nil { return } } func (doorboyserver *DoorBoyServer) getLongPollJson(w http.ResponseWriter, r *http.Request) { flusher, ok := w.(http.Flusher) if !ok { fmt.Println("Failed to get flusher for http response") } w.WriteHeader(http.StatusOK) flusher.Flush() respJson, _ := json.Marshal([]string{"123456789", "aabbccddeeff", "aabbcc"}) for i := 1; i <= 10; i++ { w.Write(respJson) w.Write([]byte("\n")) fmt.Println("send") flusher.Flush() // Trigger "chunked" encoding and send a chunk... time.Sleep(500 * time.Millisecond) } } func (doobserver *DoorBoyServer) postOpenDoor(w http.ResponseWriter, r *http.Request) { fmt.Println("longPollers:") for key, value := range doobserver.longPollers { fmt.Println(" Key:", key, "Value:", value) value <- "workshop" } w.Write([]byte("OK")) } func postCardSwipe(w http.ResponseWriter, r *http.Request) { data, err := io.ReadAll(r.Body) if err != nil { log.Println("Got swipe request without data") return } log.Println("Swipe Request with key:", string(data)) w.Write([]byte("OK")) } func (doobserver *DoorBoyServer) postAddCard(w http.ResponseWriter, r *http.Request) { data, err := io.ReadAll(r.Body) if err != nil { log.Println("Got swipe request without data") return } cardHex := string(data) if len(cardHex) != 128 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Key invalid")) return } keyLock.RLock() found := false for _, key := range keys { if key.Token.UidHash == cardHex { // key is already in there! found = true } } keyLock.RUnlock() if found { return } keyLock.Lock() keys = append(keys, cardToken{Token: card{UidHash: cardHex}}) keyLock.Unlock() for _, datastream := range doobserver.longPollers { datastream <- "update" } log.Println("Added new key:", string(data)) } func (doobserver *DoorBoyServer) postRemoveCard(w http.ResponseWriter, r *http.Request) { data, err := io.ReadAll(r.Body) if err != nil { log.Println("Got swipe request without data") return } cardHex := string(data) if len(cardHex) != 128 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Key invalid")) return } keyLock.Lock() foundIndex := -1 for i, key := range keys { if key.Token.UidHash == cardHex { // key is already in there! foundIndex = i } } if foundIndex == -1 { keyLock.Unlock() return } if foundIndex == len(keys)-1 { keys = keys[:foundIndex] } else { keys = append(keys[:foundIndex], keys[foundIndex+1:]...) } keyLock.Unlock() for _, datastream := range doobserver.longPollers { datastream <- "update" } log.Println("Remove key:", string(data)) } func (doobserver *DoorBoyServer) postKeepDoorOpen(w http.ResponseWriter, r *http.Request) { rs, _ := io.ReadAll(r.Body) parsedTime, err := time.Parse(time.RFC3339, strings.TrimSpace(string(rs))) if err != nil { fmt.Println("Error with parsing time: %v", err) w.WriteHeader(http.StatusBadRequest) } keepOpenUntil = &parsedTime doobserver.postOpenDoor(w, r) } func main() { fmt.Println("Running...") var dumpKeys []cardToken savedKeys, err := os.ReadFile("keys.json") err = json.Unmarshal(savedKeys, &dumpKeys) if err == nil { for _, k := range dumpKeys { keys = append(keys, k) } log.Println("Loaded keys successfully!") } ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() doorboyserver := DoorBoyServer{longPollers: map[string]chan string{}} http.HandleFunc("/", getRoot) http.HandleFunc("/allowed", getAllowed) http.HandleFunc("/longpoll", doorboyserver.getLongPoll) http.HandleFunc("/jspoll", doorboyserver.getLongPollJson) http.HandleFunc("/open", doorboyserver.postOpenDoor) http.HandleFunc("/cardswipe", postCardSwipe) http.HandleFunc("/keepdooropen", doorboyserver.postKeepDoorOpen) http.HandleFunc("/addcard", doorboyserver.postAddCard) http.HandleFunc("/removecard", doorboyserver.postRemoveCard) go func() { err := http.ListenAndServe(":3333", nil) if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server closed\n") } else if err != nil { fmt.Printf("error starting server: %s\n", err) os.Exit(1) } }() <-ctx.Done() log.Println("Shutting down!") keyLock.Lock() data, err := json.Marshal(keys) if err == nil { os.WriteFile("keys.json", data, 0600) log.Println("Saved keys successfully!") } keyLock.Unlock() }