feat: state is now scoped by both user id and chat id

This commit is contained in:
abelith committed 2026-10-07 15:01:18 +03:00
1 parent 4b94fcb920
commit ec12f29fdb
3 files changed
+31 -23

No files matched your search

+5 -5
View File
@@ -27,9 +27,9 @@ type EventIsolation interface {
}
type FSMStorage interface {
Get(ctx context.Context, userID int) (state.Snapshot, error)
Get(ctx context.Context, scope isolation.Scope) (state.Snapshot, error)
Put(ctx context.Context, state state.Snapshot) error
Delete(ctx context.Context, userID int) error
Delete(ctx context.Context, scope isolation.Scope) error
}
type UpdateHandler interface {
@@ -157,13 +157,13 @@ func (d *Dispatcher) handleUpdates(ch <-chan model.Update) {
func (d *Dispatcher) handleUpdate(ctx context.Context, upd model.Update) {
var st *state.Context
snapshot, err := d.fsmStorage.Get(ctx, int(upd.UserID))
snapshot, err := d.fsmStorage.Get(ctx, isolation.Scope{UserID: upd.UserID, ChatID: upd.ChatID})
if err != nil && !errors.Is(err, state.ErrStateNotFound) {
log.Error().Err(err).Send()
return
}
if errors.Is(err, state.ErrStateNotFound) {
st = state.New(int(upd.UserID))
st = state.New(isolation.Scope{UserID: upd.UserID, ChatID: upd.ChatID})
} else {
st = state.Restore(snapshot)
}
@@ -177,7 +177,7 @@ func (d *Dispatcher) handleUpdate(ctx context.Context, upd model.Update) {
st = c.State()
if st.State() == state.ClearState {
if err := d.fsmStorage.Delete(ctx, int(upd.UserID)); err != nil {
if err := d.fsmStorage.Delete(ctx, isolation.Scope{UserID: upd.UserID, ChatID: upd.ChatID}); err != nil {
log.Error().Err(err).Send()
return
}
+19 -5
View File
@@ -19,6 +19,11 @@ type EventIsolation struct {
ttl time.Duration
}
type fsmKey struct {
userID int64
chatID int64
}
type eventIsolationKey struct {
userID int
chatID int
@@ -34,8 +39,11 @@ func NewFSMStorage() *FSMStorage {
return &FSMStorage{}
}
func (f *FSMStorage) Get(_ context.Context, userID int) (state.Snapshot, error) {
st, ok := f.m.Load(userID)
func (f *FSMStorage) Get(_ context.Context, scope isolation.Scope) (state.Snapshot, error) {
st, ok := f.m.Load(fsmKey{
userID: scope.UserID,
chatID: scope.ChatID,
})
if !ok {
return state.Snapshot{}, state.ErrStateNotFound
}
@@ -44,12 +52,18 @@ func (f *FSMStorage) Get(_ context.Context, userID int) (state.Snapshot, error)
}
func (f *FSMStorage) Put(_ context.Context, snapshot state.Snapshot) error {
f.m.Store(snapshot.ID, snapshot)
f.m.Store(fsmKey{
userID: snapshot.Scope.UserID,
chatID: snapshot.Scope.ChatID,
}, snapshot)
return nil
}
func (f *FSMStorage) Delete(_ context.Context, userID int) error {
f.m.Delete(userID)
func (f *FSMStorage) Delete(_ context.Context, scope isolation.Scope) error {
f.m.Delete(fsmKey{
userID: scope.UserID,
chatID: scope.ChatID,
})
return nil
}
+7 -13
View File
@@ -3,8 +3,7 @@ package state
import (
"encoding/json"
"fmt"
"github.com/rs/zerolog/log"
"github.com/abelith/gomax/v1/isolation"
)
var (
@@ -32,13 +31,13 @@ func (JSONCodec) Unmarshal(d []byte, v any) error {
}
type Snapshot struct {
ID int `json:"id"`
Scope isolation.Scope `json:"id"`
State State `json:"state"`
Data map[string][]byte `json:"data"`
}
type Context struct {
id int
scope isolation.Scope
state State
data map[string][]byte
codec Codec
@@ -48,9 +47,9 @@ type Option func(*Context)
func WithCodec(c Codec) Option { return func(sc *Context) { sc.codec = c } }
func New(id int, opts ...Option) *Context {
func New(scope isolation.Scope, opts ...Option) *Context {
sc := &Context{
id: id,
scope: scope,
data: make(map[string][]byte),
codec: JSONCodec{},
}
@@ -61,7 +60,7 @@ func New(id int, opts ...Option) *Context {
}
func Restore(s Snapshot, opts ...Option) *Context {
sc := New(s.ID, opts...)
sc := New(s.Scope, opts...)
sc.state = s.State
for k, v := range s.Data {
sc.data[k] = v
@@ -74,15 +73,10 @@ func (sc *Context) Snapshot() Snapshot {
for k, v := range sc.data {
data[k] = v
}
return Snapshot{ID: sc.id, State: sc.state, Data: data}
return Snapshot{Scope: sc.scope, State: sc.state, Data: data}
}
func (sc *Context) Set(state State) {
log.Debug().
Int("user_id", sc.id).
Str("from", string(sc.state)).
Str("to", string(state)).
Msg("fsm state transition")
sc.state = state
}