feat: impl release hand & next speaker selection
This commit is contained in:
parent
aa048efbd3
commit
480d007e6c
4
main.go
4
main.go
|
@ -31,10 +31,10 @@ func main() {
|
||||||
|
|
||||||
fmt.Printf("Server will start on port %d\n", port)
|
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, }
|
sessions := sessions.RedisSM{ Rdb: rdb, }
|
||||||
|
|
||||||
routes.RegisterRoutes(sessions, rooms)
|
routes.RegisterRoutes(sessions, roomsM)
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
|
@ -5,25 +5,28 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Person struct {
|
type PersonId int
|
||||||
PersonId int
|
|
||||||
Name string
|
// TODO move to rooms i guess
|
||||||
PasswordHash string
|
func RandomPersonId() PersonId {
|
||||||
|
randInt := rand.Int()
|
||||||
|
if randInt == 0 {
|
||||||
|
randInt = 1
|
||||||
|
}
|
||||||
|
return PersonId(randInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
|
||||||
Name string // will be unique ID
|
type Person struct {
|
||||||
AdminIds []int
|
Id PersonId
|
||||||
|
Name string
|
||||||
PasswordHash 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
|
// well, it seems that i'd better do marshalling into bytes then
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -31,6 +30,7 @@ func registerLoginRoutes(
|
||||||
|
|
||||||
const authCookieName = "auth"
|
const authCookieName = "auth"
|
||||||
const loginPath = "/login"
|
const loginPath = "/login"
|
||||||
|
|
||||||
type contextKey string
|
type contextKey string
|
||||||
|
|
||||||
func getContextSession(ctx context.Context) (sessions.SessionData, bool) {
|
func getContextSession(ctx context.Context) (sessions.SessionData, bool) {
|
||||||
|
@ -49,14 +49,14 @@ func authedPageMiddleware(
|
||||||
sessionsM sessions.SessionManagement, next http.Handler,
|
sessionsM sessions.SessionManagement, next http.Handler,
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
returnNoAccess := func(w http.ResponseWriter, r *http.Request) {
|
returnNoAccess := func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("auth middle > restricting access to %s", r.URL.Path)
|
log.Printf("auth middle > restricting access to %s", r.URL.Path)
|
||||||
w.Header().Add("HX-Replace-Url", loginPath)
|
w.Header().Add("HX-Replace-Url", loginPath)
|
||||||
renderLoginPage(w)
|
renderLoginPage(w)
|
||||||
}
|
}
|
||||||
rerturnSuccess := func(w http.ResponseWriter, r *http.Request, session sessions.SessionData) {
|
rerturnSuccess := func(w http.ResponseWriter, r *http.Request, session sessions.SessionData) {
|
||||||
ctx := context.WithValue(r.Context(), contextKey("session"), session)
|
ctx := context.WithValue(r.Context(), contextKey("session"), session)
|
||||||
log.Printf("auth middle > allowing access to %s for %+v", r.URL.Path, session)
|
log.Printf("auth middle > allowing access to %s for %+v", r.URL.Path, session)
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
}
|
}
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionCookie, err := r.Cookie(authCookieName)
|
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,
|
sessionSM sessions.SessionManagement,
|
||||||
roomsM rooms.RoomManager,
|
roomsM rooms.RoomManager,
|
||||||
) http.HandlerFunc {
|
) http.HandlerFunc {
|
||||||
|
@ -108,32 +108,32 @@ func createRoomHandler( templateFs *embed.FS,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
person := rooms.Person{
|
person := rooms.Person{
|
||||||
PersonId: rand.Int(),
|
Id: rooms.RandomPersonId(),
|
||||||
Name: r.PostFormValue("personalName"),
|
Name: r.PostFormValue("personalName"),
|
||||||
PasswordHash: r.PostFormValue("personalPassword"), // TODO hash the password, not to store
|
PasswordHash: r.PostFormValue("personalPassword"), // TODO hash the password, not to store
|
||||||
}
|
}
|
||||||
newRoom := rooms.Room{
|
newRoom := rooms.Room{
|
||||||
Name: roomName,
|
Name: roomName,
|
||||||
PasswordHash: r.PostFormValue("roomPassword"), // TODO hash the password, not to store
|
PasswordHash: r.PostFormValue("roomPassword"), // TODO hash the password, not to store
|
||||||
AdminIds: []int{person.PersonId},
|
AdminIds: []rooms.PersonId{person.Id},
|
||||||
Paricipants: []rooms.Person{person},
|
Paricipants: []rooms.Person{person},
|
||||||
}
|
}
|
||||||
err = roomsM.Save(newRoom)
|
err = roomsM.Save(newRoom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("what am i to do? error saving room %s", err)
|
log.Printf("what am i to do? error saving room %s", err)
|
||||||
// todo return error notice somehow
|
// todo return error notice somehow
|
||||||
}
|
}
|
||||||
newSessionId, err := sessionSM.Save(newRoom.Name, person.PersonId)
|
newSessionId, err := sessionSM.Save(newRoom.Name, person.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("what am i to do? error saving session %s", err)
|
log.Printf("what am i to do? error saving session %s", err)
|
||||||
// todo return error notice somehow
|
// todo return error notice somehow
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: authCookieName,
|
Name: authCookieName,
|
||||||
Value: fmt.Sprint(newSessionId),
|
Value: fmt.Sprint(newSessionId),
|
||||||
Secure: true,
|
Secure: true,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
})
|
})
|
||||||
var templFile = "templates/index.gohtml"
|
var templFile = "templates/index.gohtml"
|
||||||
tmpl := template.Must(template.ParseFS(templateFs, templFile))
|
tmpl := template.Must(template.ParseFS(templateFs, templFile))
|
||||||
|
@ -148,7 +148,7 @@ func createRoomHandler( templateFs *embed.FS,
|
||||||
|
|
||||||
// checking whether the room name already exists
|
// checking whether the room name already exists
|
||||||
// toggle button between Join or Create
|
// toggle button between Join or Create
|
||||||
func checkRoomName( templateFs *embed.FS,
|
func checkRoomName(templateFs *embed.FS,
|
||||||
roomsM rooms.RoomManager,
|
roomsM rooms.RoomManager,
|
||||||
) http.HandlerFunc {
|
) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
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,
|
sessionSM sessions.SessionManagement,
|
||||||
roomsM rooms.RoomManager,
|
roomsM rooms.RoomManager,
|
||||||
) http.HandlerFunc {
|
) http.HandlerFunc {
|
||||||
|
@ -221,9 +221,9 @@ func joinRoomHandler( templateFs *embed.FS,
|
||||||
log.Printf("/login/join room pass correct, new person joins")
|
log.Printf("/login/join room pass correct, new person joins")
|
||||||
// creating a new person with provided password hash
|
// creating a new person with provided password hash
|
||||||
person = rooms.Person{
|
person = rooms.Person{
|
||||||
Name: personName,
|
Name: personName,
|
||||||
PasswordHash: personPass,
|
PasswordHash: personPass,
|
||||||
PersonId: rand.Int(),
|
Id: rooms.RandomPersonId(),
|
||||||
}
|
}
|
||||||
err := roomsM.Update(context.TODO(), room.Name, func(fromRoom rooms.Room) (toRoom rooms.Room) {
|
err := roomsM.Update(context.TODO(), room.Name, func(fromRoom rooms.Room) (toRoom rooms.Room) {
|
||||||
toRoom = fromRoom
|
toRoom = fromRoom
|
||||||
|
@ -234,7 +234,7 @@ func joinRoomHandler( templateFs *embed.FS,
|
||||||
log.Printf("/login/join problem adding person to room", person.Name)
|
log.Printf("/login/join problem adding person to room", person.Name)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
// TODO render error to be put in error place
|
// TODO render error to be put in error place
|
||||||
// with message try again
|
// with message try again
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,16 +242,16 @@ func joinRoomHandler( templateFs *embed.FS,
|
||||||
// now we have room and person, can create a session
|
// now we have room and person, can create a session
|
||||||
// and we've checked password
|
// and we've checked password
|
||||||
|
|
||||||
newSessionId, err := sessionSM.Save(room.Name, person.PersonId)
|
newSessionId, err := sessionSM.Save(room.Name, person.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("/login/submit > error saving session %s", err)
|
log.Printf("/login/submit > error saving session %s", err)
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: authCookieName,
|
Name: authCookieName,
|
||||||
Value: fmt.Sprint(newSessionId),
|
Value: fmt.Sprint(newSessionId),
|
||||||
Secure: true,
|
Secure: true,
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
})
|
})
|
||||||
log.Printf("is is %d. room things %s & %s, personal things %s and %s. \n found room %+v",
|
log.Printf("is is %d. room things %s & %s, personal things %s and %s. \n found room %+v",
|
||||||
newSessionId, roomName, roomPass, personName, personPass, room,
|
newSessionId, roomName, roomPass, personName, personPass, room,
|
||||||
|
@ -261,4 +261,3 @@ func joinRoomHandler( templateFs *embed.FS,
|
||||||
w.Header().Add("HX-Redirect", "/")
|
w.Header().Add("HX-Redirect", "/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,20 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"sunshine.industries/some-automoderation/rooms"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionData struct {
|
type SessionData struct {
|
||||||
SessionId int `redis:"session_id"`
|
SessionId int `redis:"session_id"`
|
||||||
RoomId string `redis:"room_id"`
|
RoomId string `redis:"room_id"`
|
||||||
PersonId int `redis:"person_id"`
|
PersonId rooms.PersonId `redis:"person_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionManagement interface {
|
type SessionManagement interface {
|
||||||
Get(sessionId int) SessionData
|
Get(sessionId int) SessionData
|
||||||
Save(roomName string, personId int) (int, error)
|
Save(roomName string, personId rooms.PersonId) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
|
@ -43,7 +45,7 @@ func (redisSM RedisSM) Get(sessionId int) SessionData {
|
||||||
log.Printf("> successfully found %d %+v", sessionId, foundSession)
|
log.Printf("> successfully found %d %+v", sessionId, foundSession)
|
||||||
return 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()
|
randId := rand.Int()
|
||||||
newSession := SessionData{
|
newSession := SessionData{
|
||||||
SessionId: randId,
|
SessionId: randId,
|
||||||
|
@ -64,7 +66,7 @@ func (d DummySM) Get(sessionId int) SessionData {
|
||||||
log.Printf("get dummy session by %d", sessionId)
|
log.Printf("get dummy session by %d", sessionId)
|
||||||
return SessionData{}
|
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)
|
log.Printf("save dummy session with %s %d", roomName, personId)
|
||||||
return 1, nil
|
return 1, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue