diff --git a/main.go b/main.go index 03685c6..a10fdf8 100644 --- a/main.go +++ b/main.go @@ -31,10 +31,10 @@ func main() { fmt.Printf("Server will start on port %d\n", port) - rooms := rooms.RedisRM { Rdb: rdb, } + roomsM := rooms.RedisRM { Rdb: rdb, } sessions := sessions.RedisSM{ Rdb: rdb, } - routes.RegisterRoutes(sessions, rooms) + routes.RegisterRoutes(sessions, roomsM) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) } diff --git a/rooms/room.go b/rooms/room.go new file mode 100644 index 0000000..e549862 --- /dev/null +++ b/rooms/room.go @@ -0,0 +1,147 @@ +package rooms + +import ( + "log" + "slices" +) + +type Room struct { + Name string // will be unique ID + AdminIds []PersonId + PasswordHash string + CurrentSpeaker PersonId // i guess let's set to zero value when it's noone from the room + Paricipants []Person + // TODO hands, for each type of hand? + // i guess participants order fixed for now? + // and i'll still need 'current' for each hand level + ParticipantHands map[PersonId]HandGesture + Marks map[HandGesture]PersonId +} + +func (r *Room)InitMaps() { + if r.ParticipantHands == nil { + r.ParticipantHands = make(map[PersonId]HandGesture) + } + if r.Marks == nil { + r.Marks = make(map[HandGesture]PersonId) + } +} + +// and how would i organize? +// i should have map[HandGesture]ParticipantId as mark. for 'from where to go clockwise if returning a level lover' +// now i want methods that for some person raise some hand, so i guess it adds what? to map[ParticipantId]HandGesture +// i suppose methods should be on a room, in a session i'd have ParticipantId to pass in, and would load room +// and i want all parts of room in same struct, because i'd want to subscribe to changes to all of them + +// i suppose raising a hand could be just in place adding PersonId->HandGesture, +// but releasing hand should have additional logic +func (r *Room) ReleaseHand(p PersonId) { + // releasing a hand of a current speaker should result in selection of a new speaker + log.Printf("about to release hand of %d in %s", p, r.Name) + + // first remove hand of the requested person + currentSpeakerGesture, currentSpeakerHandFound := r.ParticipantHands[p] + if !currentSpeakerHandFound { + return + } + + delete(r.ParticipantHands, p) + + // if not a current speaker, no complicated logic required to release a hand + if r.CurrentSpeaker != p { + return + } + + // if a current speaker - after removing the hand, we need to find next speaker + // from highest hand gesture to lowest, until one is found + gestures := [...]HandGesture{Meta, ClarifyingQ, Expand, ProbingQ, ChangeTopic} + + var nextSpeakerId PersonId + var nextSpeakerFound bool + +gestureIteration: + for _, gesture := range gestures { + log.Printf("searching for gesture %s", gesture.String()) + startIndex := r.gestureSearchStartIndex(gesture, currentSpeakerGesture) + participantsCount := len(r.Paricipants) + for i := 1; i < participantsCount; i++ { + checkIndex := startIndex + i%participantsCount + checkPerson := r.Paricipants[checkIndex] + checkGesture, isFound := r.ParticipantHands[checkPerson.Id] + if isFound && checkGesture == gesture { + nextSpeakerId, nextSpeakerFound = checkPerson.Id, true + break gestureIteration + } + } + } + if !nextSpeakerFound { + log.Printf("there is not next speaker, that's ok") + } + + nextSpeakerGesture := r.ParticipantHands[nextSpeakerId] + log.Printf("found next speaker %+v", nextSpeakerId) + + if nextSpeakerGesture > currentSpeakerGesture { + // raising the level of the speaker, need to save mark + log.Printf("we do have nextSpeaker of priority %s higher than current %s", nextSpeakerGesture.String(), currentSpeakerGesture.String()) + r.Marks[currentSpeakerGesture] = p + } + + // also we need to remove marks from top to current speaker level + for _, gesture := range gestures { + if gesture < nextSpeakerGesture { + break + } + delete(r.Marks, gesture) + } + + r.CurrentSpeaker = nextSpeakerId +} + +// find place to start - separate function +// current speaker level, searched gesture level, room marks +// if search level >= current speaker start from speaker +// if search level < current speaekr look for mark, start from mark of that level, +// +// if no mark - from speaker +func (r *Room) gestureSearchStartIndex(gesture, curSpeakerGesture HandGesture) int { + if r.CurrentSpeaker == PersonId(0) { + // nobody is actully a speaker + return 0 + } + + var personFromWhichToStartSearch PersonId + + // if searched guesture is higher or same as current speaker, start from speaker + if gesture >= curSpeakerGesture { + personFromWhichToStartSearch = r.CurrentSpeaker + } + // if searched gesture is of lower priority from current speaker, check marks to return to, or use speaker level + gestureMark, found := r.Marks[gesture] + if found { + personFromWhichToStartSearch = gestureMark + } + // if no mark found - count from the speaker + personFromWhichToStartSearch = r.CurrentSpeaker + + indexFromWhichToStart := slices.IndexFunc(r.Paricipants, func(p Person) bool { + return p.Id == personFromWhichToStartSearch + }) + return indexFromWhichToStart +} + +// sooooooo. i need hand types. are there enums in go? +type HandGesture uint8 + +const ( + ChangeTopic HandGesture = iota + ProbingQ + Expand + ClarifyingQ + Meta +) + +// String() method to print the name of the days +func (g HandGesture) String() string { + return [...]string{"Change Topic", "Probing Quesion", "Expand", "Clarifying Quesion", "Meta"}[g] +} diff --git a/rooms/rooms_manager.go b/rooms/rooms_manager.go index 1831e44..32d56ec 100644 --- a/rooms/rooms_manager.go +++ b/rooms/rooms_manager.go @@ -5,25 +5,28 @@ import ( "encoding/json" "errors" "fmt" + "math/rand" "log" "github.com/redis/go-redis/v9" ) -type Person struct { - PersonId int - Name string - PasswordHash string +type PersonId int + +// TODO move to rooms i guess +func RandomPersonId() PersonId { + randInt := rand.Int() + if randInt == 0 { + randInt = 1 + } + return PersonId(randInt) } -type Room struct { - Name string // will be unique ID - AdminIds []int + +type Person struct { + Id PersonId + Name string PasswordHash string - Paricipants []Person - // TODO hands, for each type of hand? - // i guess participants order fixed for now? - // and i'll still need 'current' for each hand level } // well, it seems that i'd better do marshalling into bytes then diff --git a/routes/login_page.go b/routes/login_page.go index 8c9a202..16ef0dd 100644 --- a/routes/login_page.go +++ b/routes/login_page.go @@ -6,7 +6,6 @@ import ( "fmt" "html/template" "log" - "math/rand" "net/http" "strconv" @@ -31,6 +30,7 @@ func registerLoginRoutes( const authCookieName = "auth" const loginPath = "/login" + type contextKey string func getContextSession(ctx context.Context) (sessions.SessionData, bool) { @@ -49,14 +49,14 @@ func authedPageMiddleware( sessionsM sessions.SessionManagement, next http.Handler, ) http.Handler { returnNoAccess := func(w http.ResponseWriter, r *http.Request) { - log.Printf("auth middle > restricting access to %s", r.URL.Path) - w.Header().Add("HX-Replace-Url", loginPath) - renderLoginPage(w) + log.Printf("auth middle > restricting access to %s", r.URL.Path) + w.Header().Add("HX-Replace-Url", loginPath) + renderLoginPage(w) } rerturnSuccess := func(w http.ResponseWriter, r *http.Request, session sessions.SessionData) { - ctx := context.WithValue(r.Context(), contextKey("session"), session) - log.Printf("auth middle > allowing access to %s for %+v", r.URL.Path, session) - next.ServeHTTP(w, r.WithContext(ctx)) + ctx := context.WithValue(r.Context(), contextKey("session"), session) + log.Printf("auth middle > allowing access to %s for %+v", r.URL.Path, session) + next.ServeHTTP(w, r.WithContext(ctx)) } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { sessionCookie, err := r.Cookie(authCookieName) @@ -90,7 +90,7 @@ func renderLoginPage(w http.ResponseWriter) { } } -func createRoomHandler( templateFs *embed.FS, +func createRoomHandler(templateFs *embed.FS, sessionSM sessions.SessionManagement, roomsM rooms.RoomManager, ) http.HandlerFunc { @@ -108,32 +108,32 @@ func createRoomHandler( templateFs *embed.FS, return } person := rooms.Person{ - PersonId: rand.Int(), - Name: r.PostFormValue("personalName"), + Id: rooms.RandomPersonId(), + Name: r.PostFormValue("personalName"), PasswordHash: r.PostFormValue("personalPassword"), // TODO hash the password, not to store } newRoom := rooms.Room{ - Name: roomName, + Name: roomName, PasswordHash: r.PostFormValue("roomPassword"), // TODO hash the password, not to store - AdminIds: []int{person.PersonId}, - Paricipants: []rooms.Person{person}, + AdminIds: []rooms.PersonId{person.Id}, + Paricipants: []rooms.Person{person}, } err = roomsM.Save(newRoom) if err != nil { log.Printf("what am i to do? error saving room %s", err) // todo return error notice somehow } - newSessionId, err := sessionSM.Save(newRoom.Name, person.PersonId) + newSessionId, err := sessionSM.Save(newRoom.Name, person.Id) if err != nil { log.Printf("what am i to do? error saving session %s", err) // todo return error notice somehow } http.SetCookie(w, &http.Cookie{ - Name: authCookieName, - Value: fmt.Sprint(newSessionId), - Secure: true, + Name: authCookieName, + Value: fmt.Sprint(newSessionId), + Secure: true, HttpOnly: true, - Path: "/", + Path: "/", }) var templFile = "templates/index.gohtml" tmpl := template.Must(template.ParseFS(templateFs, templFile)) @@ -148,7 +148,7 @@ func createRoomHandler( templateFs *embed.FS, // checking whether the room name already exists // toggle button between Join or Create -func checkRoomName( templateFs *embed.FS, +func checkRoomName(templateFs *embed.FS, roomsM rooms.RoomManager, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -167,7 +167,7 @@ func checkRoomName( templateFs *embed.FS, } } -func joinRoomHandler( templateFs *embed.FS, +func joinRoomHandler(templateFs *embed.FS, sessionSM sessions.SessionManagement, roomsM rooms.RoomManager, ) http.HandlerFunc { @@ -221,9 +221,9 @@ func joinRoomHandler( templateFs *embed.FS, log.Printf("/login/join room pass correct, new person joins") // creating a new person with provided password hash person = rooms.Person{ - Name: personName, + Name: personName, PasswordHash: personPass, - PersonId: rand.Int(), + Id: rooms.RandomPersonId(), } err := roomsM.Update(context.TODO(), room.Name, func(fromRoom rooms.Room) (toRoom rooms.Room) { toRoom = fromRoom @@ -234,7 +234,7 @@ func joinRoomHandler( templateFs *embed.FS, log.Printf("/login/join problem adding person to room", person.Name) w.WriteHeader(http.StatusInternalServerError) // TODO render error to be put in error place - // with message try again + // with message try again return } } @@ -242,16 +242,16 @@ func joinRoomHandler( templateFs *embed.FS, // now we have room and person, can create a session // and we've checked password - newSessionId, err := sessionSM.Save(room.Name, person.PersonId) + newSessionId, err := sessionSM.Save(room.Name, person.Id) if err != nil { log.Printf("/login/submit > error saving session %s", err) } http.SetCookie(w, &http.Cookie{ - Name: authCookieName, - Value: fmt.Sprint(newSessionId), - Secure: true, + Name: authCookieName, + Value: fmt.Sprint(newSessionId), + Secure: true, HttpOnly: true, - Path: "/", + Path: "/", }) log.Printf("is is %d. room things %s & %s, personal things %s and %s. \n found room %+v", newSessionId, roomName, roomPass, personName, personPass, room, @@ -261,4 +261,3 @@ func joinRoomHandler( templateFs *embed.FS, w.Header().Add("HX-Redirect", "/") } } - diff --git a/sessions/sessions_manager.go b/sessions/sessions_manager.go index 93704f0..6810ab7 100644 --- a/sessions/sessions_manager.go +++ b/sessions/sessions_manager.go @@ -6,18 +6,20 @@ import ( "log" "math/rand" + "sunshine.industries/some-automoderation/rooms" + "github.com/redis/go-redis/v9" ) type SessionData struct { - SessionId int `redis:"session_id"` - RoomId string `redis:"room_id"` - PersonId int `redis:"person_id"` + SessionId int `redis:"session_id"` + RoomId string `redis:"room_id"` + PersonId rooms.PersonId `redis:"person_id"` } type SessionManagement interface { Get(sessionId int) SessionData - Save(roomName string, personId int) (int, error) + Save(roomName string, personId rooms.PersonId) (int, error) } var ctx = context.Background() @@ -43,7 +45,7 @@ func (redisSM RedisSM) Get(sessionId int) SessionData { log.Printf("> successfully found %d %+v", sessionId, foundSession) return foundSession } -func (redisSM RedisSM) Save(roomName string, personId int) (int, error) { +func (redisSM RedisSM) Save(roomName string, personId rooms.PersonId) (int, error) { randId := rand.Int() newSession := SessionData{ SessionId: randId, @@ -64,7 +66,7 @@ func (d DummySM) Get(sessionId int) SessionData { log.Printf("get dummy session by %d", sessionId) return SessionData{} } -func (d DummySM) Save(roomName string, personId int) (int, error) { +func (d DummySM) Save(roomName string, personId rooms.PersonId) (int, error) { log.Printf("save dummy session with %s %d", roomName, personId) return 1, nil }