package routes import ( "bytes" "embed" "fmt" "html/template" "log" "net/http" "strconv" "strings" "sunshine.industries/some-automoderation/metrics" "sunshine.industries/some-automoderation/rooms" "sunshine.industries/some-automoderation/sessions" ) const roomPath = "/room/" const raiseHandPath = "/rooms/raise/" const subscribeRoomPath = "/rooms/subscribe" // registering all routes for page and logic of /room/:roomName func registerPageRoutes( templateFs *embed.FS, sessionSM sessions.SessionManagement, roomsM rooms.RoomManager, metrics *metrics.MetricsContainer, ) { http.Handle(roomPath, // ending in / captures all following path sections, i.e room name http.StripPrefix(roomPath, roomPageRoute(templateFs, roomsM, sessionSM))) http.Handle(raiseHandPath, // ending in / captures all following path sections, i.e gesture num authedPageMiddleware( sessionSM, http.StripPrefix(raiseHandPath, raiseGestureHandRoute(roomsM, metrics)))) http.Handle("/rooms/releaseHand", authedPageMiddleware(sessionSM, releaseHandRoute(roomsM, metrics))) http.Handle(subscribeRoomPath, authedPageMiddleware( sessionSM, http.StripPrefix(subscribeRoomPath, streamingRoomStates(templateFs, roomsM, metrics)))) http.HandleFunc("/rooms/preview-templates", roomTemplatesPreview(templateFs)) http.HandleFunc("/rooms/speakerControls", speakerControlsRoute(templateFs)) } func streamingRoomStates( templateFs *embed.FS, roomsM rooms.RoomManager, metrics *metrics.MetricsContainer, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { metrics.LiveConnectionsGauge.Inc() defer metrics.LiveConnectionsGauge.Dec() r.ParseForm() roomName := r.FormValue("roomName") defer log.Printf("/rooms/subscribe/%s stream ended\n", roomName) session, found := getContextSession(r.Context()) if !found { log.Printf("/rooms/raiseGesture session not found, should be impossible") // TODO return error i guess return } if session.RoomId != roomName { // not authorized log.Printf("/rooms/streamingRoom got unauth with session.RoomId (%s) != roomName (%s)", session.RoomId, roomName) w.WriteHeader(http.StatusUnauthorized) return } log.Printf("Starting stream for room %s for %d\n", roomName, session.PersonId) w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("X-Accel-Buffering", "no") templFile := "templates/room.gohtml" tableTemplates := "templates/tableTemplates.gohtml" tmpl := template.Must( template.New("").ParseFS(templateFs, tableTemplates, templFile)) roomStream := roomsM.Subscribe(r.Context(), roomName) for room := range roomStream { // log.Printf("/rooms/streamingRoom iterating with %+v", room) fmt.Fprint(w, "event: roomTableUpdate\ndata: ") var buffer bytes.Buffer roomTemplateData := roomTableData{ Room: &room, currentPerson: session.PersonId, } err := tmpl.ExecuteTemplate(&buffer, "simpleRoomShow", &roomTemplateData) if err != nil { log.Printf("/rooms/subscribe/%s got error on template %s", roomName, err) } templateStr := buffer.String() templateLine := strings.ReplaceAll(templateStr, "\n", "") fmt.Fprint(w, templateLine) fmt.Fprint(w, "\n\n") w.(http.Flusher).Flush() if session.PersonId == room.CurrentSpeaker { log.Printf("/rooms/subscribe sending 'become-speaker' to %d", session.PersonId) fmt.Fprint(w, "event: become-speaker\ndata:yo\n\n") w.(http.Flusher).Flush() } } } } // if currently speaking? i guess first lower the hand and then raise new // TODO should return control for raised state func raiseGestureHandRoute( roomsM rooms.RoomManager, metrics *metrics.MetricsContainer, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { gestureInd, err := strconv.Atoi(r.URL.Path) selectedGesture, found := rooms.GestureFromInt(gestureInd) if err != nil || !found { log.Printf("/rooms/raiseGesture error %s gettin hand symbol index from path %s\n", err, r.URL.Path) return } metrics.RaiseGestureCounter.WithLabelValues(selectedGesture.String()).Inc() log.Printf("/rooms/raiseGesture successfully got gesture %d : %s", selectedGesture, selectedGesture.String()) session, found := getContextSession(r.Context()) if !found { log.Printf("/rooms/raiseGesture session not found, should be impossible") // TODO return error i guess return } var room rooms.Room err = roomsM.Update(r.Context(), session.RoomId, func(fromRoom rooms.Room) (toRoom rooms.Room) { if fromRoom.CurrentSpeaker == rooms.PersonId(0) { // room had no speaker, need count new speaker turn metrics.SpeakerCounter.WithLabelValues(selectedGesture.String()).Inc() } toRoom = fromRoom.RaiseHand(session.PersonId, selectedGesture) room = toRoom return toRoom }) if err != nil { log.Printf("/rooms/raiseGesture error saving hand: %s\n", err) return // TODO return error i guess } tableTemplates := "templates/room.gohtml" tmpl := template.Must( template.New("").ParseFS(templateFs, tableTemplates)) if session.PersonId == room.CurrentSpeaker { tmpl.ExecuteTemplate(w, "speakerControls", nil) return } else { var gesturesData []GestureData for _, gesture := range rooms.GesturesHighToLow { gesturesData = append(gesturesData, GestureData{ Url: fmt.Sprintf("%s%d", raiseHandPath, gesture), Gesture: gesture, IsSelected: selectedGesture == gesture, }) } err = tmpl.ExecuteTemplate(w, "controls", &gesturesData) if err != nil { log.Printf("/rooms/releaseHand error saving hand: %s\n", err) return // TODO return error i guess } } } } // TODO should return lowered control for passed hand gesture, i guess optional func releaseHandRoute( roomsM rooms.RoomManager, metrics *metrics.MetricsContainer, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { session, found := getContextSession(r.Context()) if !found { log.Printf("/rooms/releaseHand session not found, should be impossible") // TODO return error i guess return } err := roomsM.Update(r.Context(), session.RoomId, func(fromRoom rooms.Room) (toRoom rooms.Room) { toRoom = fromRoom toRoom.ReleaseHand(session.PersonId) if fromRoom.CurrentSpeaker != toRoom.CurrentSpeaker && toRoom.CurrentSpeaker != rooms.PersonId(0) { gesture := toRoom.ParticipantHands[toRoom.CurrentSpeaker] metrics.SpeakerCounter.WithLabelValues(gesture.String()).Inc() } return toRoom }) if err != nil { log.Printf("/rooms/releaseHand error saving hand: %s\n", err) return // TODO return error i guess } tableTemplates := "templates/room.gohtml" tmpl := template.Must( template.New("").ParseFS(templateFs, tableTemplates)) var gesturesData []GestureData for _, gesture := range rooms.GesturesHighToLow { gesturesData = append(gesturesData, GestureData{ Url: fmt.Sprintf("%s%d", raiseHandPath, gesture), Gesture: gesture, IsSelected: false, }) } err = tmpl.ExecuteTemplate(w, "controls", &gesturesData) if err != nil { log.Printf("/rooms/releaseHand error saving hand: %s\n", err) return // TODO return error i guess } } } func roomPageRoute( templateFs *embed.FS, roomsM rooms.RoomManager, sessionSM sessions.SessionManagement, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { roomName := r.URL.Path if roomName == "" { log.Printf("access to empty room") // TODO return error i suppose w.Header().Add("HX-Redirect", "/") return } // check session, session, err := getRequestSession(r, sessionSM) room, found, err := roomsM.Get(r.Context(), roomName) if err != nil || session.RoomId != roomName { log.Printf("not authed with session %+v | error %s, but for wrong room, trying to access %s", session, err, roomName) renderLoginPage(w, roomName, found) return } if err != nil || !found { log.Printf("/room room for name %s not found or err: %s / found %t", roomName, err, found) // TODO here should be append to error place w.Header().Add("HX-Redirect", "/") return } // now we should have a session for this specific room fmt.Printf("all checks for room %s passed with %+v", roomName, session) templFile := "templates/room.gohtml" baseFile := "templates/base.gohtml" tableTemplates := "templates/tableTemplates.gohtml" tmpl := template.Must( template.New("").ParseFS(templateFs, tableTemplates, templFile, baseFile)) var gesturesData []GestureData selectedGesture, isSelected := room.ParticipantHands[session.PersonId] for _, gesture := range rooms.GesturesHighToLow { gesturesData = append(gesturesData, GestureData{ Url: fmt.Sprintf("%s%d", raiseHandPath, gesture), Gesture: gesture, IsSelected: isSelected && selectedGesture == gesture, }) } contentData := struct { Room *roomTableData Gestures []GestureData }{ Room: &roomTableData{ Room: &room, currentPerson: session.PersonId, }, Gestures: gesturesData, } data := pageData{ Base: baseData{ Title: fmt.Sprintf("Some Automoderation: discussion in '%s'", room.Name), LoggedIn: true, }, Content: contentData, Header: headerData{ Title: room.Name, }, } err = tmpl.ExecuteTemplate(w, "full-page", data) if err != nil { log.Printf("/room/%s my error in executing template, huh\n %s", roomName, err) } } } func speakerControlsRoute( templateFs *embed.FS, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { templateFile := "templates/room.gohtml" tmpl := template.Must(template.ParseFS(templateFs, templateFile)) tmpl.ExecuteTemplate(w, "speakerControls", nil) } } type GestureData struct { Url string Gesture rooms.HandGesture IsSelected bool }