package sessions import ( "context" "fmt" "log" "math/rand" "time" "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 rooms.PersonId `redis:"person_id"` ExpireIn time.Duration `redis:"-"` } type SessionManagement interface { Get(ctx context.Context, sessionId int) SessionData Save(ctx context.Context, roomName string, personId rooms.PersonId) (SessionData, error) Remove(ctx context.Context, sessionId int) error } const sessionPrefix = "session" const SessionTtl = 3 * time.Hour const SessionProlongationWindow = time.Hour func sessionIdToKey(sessionId int) string { return fmt.Sprintf("%s:%d", sessionPrefix, sessionId) } type RedisSM struct { Rdb *redis.Client } func (redisSM RedisSM) Get(ctx context.Context, sessionId int) SessionData { var foundSession SessionData redisKey := sessionIdToKey(sessionId) err := redisSM.Rdb.HGetAll(ctx, redisKey).Scan(&foundSession) if err != nil { log.Printf("> error reading %s : %s", redisKey, err) return SessionData{} } log.Printf("> successfully found %d %+v", sessionId, foundSession) ttl, err := redisSM.Rdb.TTL(ctx, redisKey).Result() if err != nil { log.Printf("> error getting ttl for session %+v", foundSession) return foundSession } if ttl == -2 { log.Printf("> ttl indicates session doesn't exist for session %+v", foundSession) return SessionData{} } if ttl < SessionProlongationWindow { err = redisSM.Rdb.Expire(ctx, redisKey, SessionTtl).Err() if err != nil { log.Printf("> error updating ttl for session %+v", foundSession) return foundSession } else { ttl = SessionTtl } } foundSession.ExpireIn = ttl return foundSession } func (redisSM RedisSM) Save(ctx context.Context, roomName string, personId rooms.PersonId) (SessionData, error) { randId := rand.Int() newSession := SessionData{ SessionId: randId, RoomId: roomName, PersonId: personId, ExpireIn: SessionTtl, } err := redisSM.Rdb.HSet(ctx, sessionIdToKey(randId), newSession).Err() redisSM.Rdb.Expire(ctx, sessionIdToKey(randId), SessionTtl) if err != nil { log.Printf("> error! saving session %+v %s", newSession, err) return SessionData{}, fmt.Errorf("error saving new session: %+v with %s", newSession, err) } return newSession, nil } func (redisSM RedisSM) Remove(ctx context.Context, sessionId int) error { err := redisSM.Rdb.Del(ctx, sessionIdToKey(sessionId)).Err() return err } type DummySM struct{} func (d DummySM) Get(ctx context.Context, sessionId int) SessionData { log.Printf("get dummy session by %d", sessionId) return SessionData{} } func (d DummySM) Save(ctx context.Context, roomName string, personId rooms.PersonId) (SessionData, error) { log.Printf("save dummy session with %s %d", roomName, personId) return SessionData{}, nil } func (d DummySM) Remove(ctx context.Context, sessionId int) error { log.Printf("deleting session %d", sessionId) return nil }