seperate room state from muxrpc handler
There is a new roomstate package now with a Manager which is shared between muxrpc and the http handler(s). Also adds a list of peers in the room to admin dashboard.
This commit is contained in:
parent
a8ae13063f
commit
beea19f93e
|
@ -204,6 +204,7 @@ func runroomsrv() error {
|
|||
dashboardH, err := handlers.New(
|
||||
nil,
|
||||
repo.New(repoDir),
|
||||
roomsrv.StateManager,
|
||||
db.AuthWithSSB,
|
||||
db.AuthFallback,
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ type connectWithOriginArg struct {
|
|||
Origin refs.FeedRef `json:"origin"` // this should be clear from the shs session already
|
||||
}
|
||||
|
||||
func (rs *roomState) connect(ctx context.Context, req *muxrpc.Request, peerSrc *muxrpc.ByteSource, peerSnk *muxrpc.ByteSink) error {
|
||||
func (h *handler) connect(ctx context.Context, req *muxrpc.Request, peerSrc *muxrpc.ByteSource, peerSnk *muxrpc.ByteSink) error {
|
||||
// unpack arguments
|
||||
|
||||
var args []connectArg
|
||||
|
@ -38,27 +38,26 @@ func (rs *roomState) connect(ctx context.Context, req *muxrpc.Request, peerSrc *
|
|||
arg := args[0]
|
||||
|
||||
// see if we have and endpoint for the target
|
||||
rs.roomsMu.Lock()
|
||||
|
||||
edp, has := rs.rooms["lobby"][arg.Target.Ref()]
|
||||
edp, has := h.state.Has(arg.Target)
|
||||
if !has {
|
||||
rs.roomsMu.Unlock()
|
||||
return fmt.Errorf("no such endpoint")
|
||||
}
|
||||
|
||||
// call connect on them
|
||||
var argWorigin connectWithOriginArg
|
||||
argWorigin.connectArg = arg
|
||||
argWorigin.Origin = rs.self
|
||||
argWorigin.Origin = h.self
|
||||
|
||||
targetSrc, targetSnk, err := edp.Duplex(ctx, muxrpc.TypeBinary, muxrpc.Method{"tunnel", "connect"}, argWorigin)
|
||||
if err != nil {
|
||||
delete(rs.rooms["lobby"], arg.Target.Ref())
|
||||
rs.updater.Update(rs.rooms["lobby"].asList())
|
||||
rs.roomsMu.Unlock()
|
||||
h.state.Remove(arg.Target)
|
||||
// TODO: the call could fail because of an error with the caller, too.
|
||||
// if we remove the wrong one, tho others might get confused
|
||||
// h.state.Remove(caller)
|
||||
|
||||
return fmt.Errorf("failed to init connect call with target: %w", err)
|
||||
}
|
||||
rs.roomsMu.Unlock()
|
||||
|
||||
// pipe data
|
||||
var cpy muxrpcDuplexCopy
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
"go.cryptoscope.co/muxrpc/v2/typemux"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/broadcasts"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
const name = "tunnel"
|
||||
|
@ -40,29 +39,23 @@ func (plugin) Authorize(net.Conn) bool { return true }
|
|||
}
|
||||
*/
|
||||
|
||||
func New(log kitlog.Logger, ctx context.Context, self refs.FeedRef) maybemuxrpc.Plugin {
|
||||
func New(log kitlog.Logger, self refs.FeedRef, m *roomstate.Manager) maybemuxrpc.Plugin {
|
||||
mux := typemux.New(log)
|
||||
|
||||
var rs = new(roomState)
|
||||
rs.self = self
|
||||
rs.logger = log
|
||||
rs.updater, rs.broadcaster = broadcasts.NewRoomChanger()
|
||||
rs.rooms = make(roomsStateMap)
|
||||
var h = new(handler)
|
||||
h.self = self
|
||||
h.logger = log
|
||||
h.state = m
|
||||
|
||||
go rs.stateTicker(ctx)
|
||||
mux.RegisterAsync(append(method, "isRoom"), typemux.AsyncFunc(h.isRoom))
|
||||
mux.RegisterAsync(append(method, "ping"), typemux.AsyncFunc(h.ping))
|
||||
|
||||
// so far just lobby (v1 rooms)
|
||||
rs.rooms["lobby"] = make(roomStateMap)
|
||||
mux.RegisterAsync(append(method, "announce"), typemux.AsyncFunc(h.announce))
|
||||
mux.RegisterAsync(append(method, "leave"), typemux.AsyncFunc(h.leave))
|
||||
|
||||
mux.RegisterAsync(append(method, "isRoom"), typemux.AsyncFunc(rs.isRoom))
|
||||
mux.RegisterAsync(append(method, "ping"), typemux.AsyncFunc(rs.ping))
|
||||
mux.RegisterSource(append(method, "endpoints"), typemux.SourceFunc(h.endpoints))
|
||||
|
||||
mux.RegisterAsync(append(method, "announce"), typemux.AsyncFunc(rs.announce))
|
||||
mux.RegisterAsync(append(method, "leave"), typemux.AsyncFunc(rs.leave))
|
||||
|
||||
mux.RegisterSource(append(method, "endpoints"), typemux.SourceFunc(rs.endpoints))
|
||||
|
||||
mux.RegisterDuplex(append(method, "connect"), typemux.DuplexFunc(rs.connect))
|
||||
mux.RegisterDuplex(append(method, "connect"), typemux.DuplexFunc(h.connect))
|
||||
|
||||
return plugin{
|
||||
h: &mux,
|
||||
|
|
|
@ -5,142 +5,76 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/broadcasts"
|
||||
)
|
||||
|
||||
type roomState struct {
|
||||
self refs.FeedRef
|
||||
|
||||
type handler struct {
|
||||
logger kitlog.Logger
|
||||
self refs.FeedRef
|
||||
|
||||
updater broadcasts.RoomChangeSink
|
||||
broadcaster *broadcasts.RoomChangeBroadcast
|
||||
|
||||
roomsMu sync.Mutex
|
||||
rooms roomsStateMap
|
||||
state *roomstate.Manager
|
||||
}
|
||||
|
||||
func (rs *roomState) stateTicker(ctx context.Context) {
|
||||
tick := time.NewTicker(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
tick.Stop()
|
||||
return
|
||||
|
||||
case <-tick.C:
|
||||
}
|
||||
rs.roomsMu.Lock()
|
||||
for room, members := range rs.rooms {
|
||||
level.Info(rs.logger).Log("room", room, "cnt", len(members))
|
||||
for who := range members {
|
||||
level.Info(rs.logger).Log("room", room, "feed", who[1:5])
|
||||
}
|
||||
}
|
||||
rs.roomsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// layout is map[room-name]map[canonical feedref]client-handle
|
||||
type roomsStateMap map[string]roomStateMap
|
||||
|
||||
// roomStateMap is a single room
|
||||
type roomStateMap map[string]muxrpc.Endpoint
|
||||
|
||||
// copy map entries to list for broadcast update
|
||||
func (rsm roomStateMap) asList() []string {
|
||||
memberList := make([]string, 0, len(rsm))
|
||||
for m := range rsm {
|
||||
memberList = append(memberList, m)
|
||||
}
|
||||
return memberList
|
||||
}
|
||||
|
||||
func (rs *roomState) isRoom(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(rs.logger).Log("called", "isRoom")
|
||||
func (h *handler) isRoom(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(h.logger).Log("called", "isRoom")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (rs *roomState) ping(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
func (h *handler) ping(context.Context, *muxrpc.Request) (interface{}, error) {
|
||||
now := time.Now().UnixNano() / 1000
|
||||
level.Debug(rs.logger).Log("called", "ping")
|
||||
level.Debug(h.logger).Log("called", "ping")
|
||||
return now, nil
|
||||
}
|
||||
|
||||
func (rs *roomState) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(rs.logger).Log("called", "announce")
|
||||
func (h *handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
level.Debug(h.logger).Log("called", "announce")
|
||||
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs.roomsMu.Lock()
|
||||
// add ref to lobby
|
||||
rs.rooms["lobby"][ref.Ref()] = req.Endpoint()
|
||||
// update all the connected tunnel.endpoints calls
|
||||
rs.updater.Update(rs.rooms["lobby"].asList())
|
||||
rs.roomsMu.Unlock()
|
||||
h.state.AddEndpoint(*ref, req.Endpoint())
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (rs *roomState) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
func (h *handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) {
|
||||
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs.roomsMu.Lock()
|
||||
// remove ref from lobby
|
||||
delete(rs.rooms["lobby"], ref.Ref())
|
||||
// update all the connected tunnel.endpoints calls
|
||||
rs.updater.Update(rs.rooms["lobby"].asList())
|
||||
rs.roomsMu.Unlock()
|
||||
h.state.Remove(*ref)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (rs *roomState) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
|
||||
level.Debug(rs.logger).Log("called", "endpoints")
|
||||
func (h *handler) endpoints(_ context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error {
|
||||
level.Debug(h.logger).Log("called", "endpoints")
|
||||
|
||||
toPeer := newForwarder(snk)
|
||||
|
||||
// for future updates
|
||||
rs.broadcaster.Register(toPeer)
|
||||
h.state.Register(toPeer)
|
||||
|
||||
ref, err := network.GetFeedRefFromAddr(req.RemoteAddr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs.roomsMu.Lock()
|
||||
|
||||
lobby := rs.rooms["lobby"]
|
||||
// if the peer didn't call tunnel.announce()
|
||||
if _, has := lobby[ref.Ref()]; has {
|
||||
has := h.state.AlreadyAdded(*ref, req.Endpoint())
|
||||
if !has {
|
||||
// just send the current state to the new peer
|
||||
toPeer.Update(lobby.asList())
|
||||
} else {
|
||||
// register them as if they did
|
||||
lobby[ref.Ref()] = req.Endpoint()
|
||||
rs.rooms["lobby"] = lobby
|
||||
|
||||
// update everyone
|
||||
rs.updater.Update(lobby.asList())
|
||||
toPeer.Update(h.state.List())
|
||||
}
|
||||
|
||||
// send the current state
|
||||
rs.roomsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@ import (
|
|||
"github.com/keks/nocomment"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/handlers/tunnel/server"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/handlers/whoami"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
func (s *Server) initNetwork() error {
|
||||
|
@ -82,7 +82,11 @@ func (s *Server) initNetwork() error {
|
|||
|
||||
// s.master.Register(replicate.NewPlug(s.Users))
|
||||
|
||||
tunnelPlug := server.New(kitlog.With(s.logger, "unit", "tunnel"), s.rootCtx, s.Whoami())
|
||||
tunnelPlug := server.New(
|
||||
kitlog.With(s.logger, "unit", "tunnel"),
|
||||
s.Whoami(),
|
||||
s.StateManager,
|
||||
)
|
||||
s.public.Register(tunnelPlug)
|
||||
|
||||
// tcp+shs
|
||||
|
|
|
@ -12,16 +12,18 @@ import (
|
|||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"go.cryptoscope.co/netwrap"
|
||||
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/multicloser"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemuxrpc"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/network"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
@ -55,6 +57,8 @@ type Server struct {
|
|||
master maybemuxrpc.PluginManager
|
||||
|
||||
authorizer listAuthorizer
|
||||
|
||||
StateManager *roomstate.Manager
|
||||
}
|
||||
|
||||
func (s Server) Whoami() refs.FeedRef {
|
||||
|
@ -119,6 +123,8 @@ func New(opts ...Option) (*Server, error) {
|
|||
}
|
||||
}
|
||||
|
||||
s.StateManager = roomstate.NewManager(s.rootCtx, s.logger)
|
||||
|
||||
if err := s.initNetwork(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package roomstate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"go.cryptoscope.co/muxrpc/v2"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/broadcasts"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
logger kitlog.Logger
|
||||
|
||||
updater broadcasts.RoomChangeSink
|
||||
broadcaster *broadcasts.RoomChangeBroadcast
|
||||
|
||||
roomMu sync.Mutex
|
||||
room roomStateMap
|
||||
}
|
||||
|
||||
func NewManager(ctx context.Context, log kitlog.Logger) *Manager {
|
||||
var m Manager
|
||||
m.updater, m.broadcaster = broadcasts.NewRoomChanger()
|
||||
m.room = make(roomStateMap)
|
||||
|
||||
go m.stateTicker(ctx)
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Manager) stateTicker(ctx context.Context) {
|
||||
tick := time.NewTicker(10 * time.Second)
|
||||
last := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
tick.Stop()
|
||||
return
|
||||
|
||||
case <-tick.C:
|
||||
}
|
||||
m.roomMu.Lock()
|
||||
|
||||
cnt := len(m.room)
|
||||
if cnt == last {
|
||||
continue
|
||||
}
|
||||
last = cnt
|
||||
|
||||
level.Info(m.logger).Log("room-cnt", cnt)
|
||||
for who := range m.room {
|
||||
level.Info(m.logger).Log("feed", who[1:5])
|
||||
}
|
||||
|
||||
m.roomMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// roomStateMap is a single room
|
||||
type roomStateMap map[string]muxrpc.Endpoint
|
||||
|
||||
// copy map entries to list for broadcast update
|
||||
func (rsm roomStateMap) AsList() []string {
|
||||
memberList := make([]string, 0, len(rsm))
|
||||
for m := range rsm {
|
||||
memberList = append(memberList, m)
|
||||
}
|
||||
return memberList
|
||||
}
|
||||
|
||||
// Register listens to changes to the room
|
||||
func (m *Manager) Register(sink broadcasts.RoomChangeSink) {
|
||||
m.broadcaster.Register(sink)
|
||||
}
|
||||
|
||||
// List just returns a list of feed references
|
||||
func (m *Manager) List() []string {
|
||||
m.roomMu.Lock()
|
||||
defer m.roomMu.Unlock()
|
||||
return m.room.AsList()
|
||||
}
|
||||
|
||||
// AddEndpoint adds the endpoint to the room
|
||||
func (m *Manager) AddEndpoint(who refs.FeedRef, edp muxrpc.Endpoint) {
|
||||
m.roomMu.Lock()
|
||||
// add ref to to the room map
|
||||
m.room[who.Ref()] = edp
|
||||
// update all the connected tunnel.endpoints calls
|
||||
m.updater.Update(m.room.AsList())
|
||||
m.roomMu.Unlock()
|
||||
}
|
||||
|
||||
// Remove removes the peer from the room
|
||||
func (m *Manager) Remove(who refs.FeedRef) {
|
||||
m.roomMu.Lock()
|
||||
// remove ref from lobby
|
||||
delete(m.room, who.Ref())
|
||||
// update all the connected tunnel.endpoints calls
|
||||
m.updater.Update(m.room.AsList())
|
||||
m.roomMu.Unlock()
|
||||
}
|
||||
|
||||
// AlreadyAdded returns true if the peer was already added to the room
|
||||
func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool {
|
||||
m.roomMu.Lock()
|
||||
|
||||
// if the peer didn't call tunnel.announce()
|
||||
_, has := m.room[who.Ref()]
|
||||
if !has {
|
||||
// register them as if they didnt
|
||||
m.room[who.Ref()] = edp
|
||||
|
||||
// update everyone
|
||||
m.updater.Update(m.room.AsList())
|
||||
}
|
||||
|
||||
// send the current state
|
||||
m.roomMu.Unlock()
|
||||
return has
|
||||
}
|
||||
|
||||
// Has returns true and the endpoint if the peer is in the room
|
||||
func (m *Manager) Has(who refs.FeedRef) (muxrpc.Endpoint, bool) {
|
||||
m.roomMu.Lock()
|
||||
// add ref to to the room map
|
||||
edp, has := m.room[who.Ref()]
|
||||
m.roomMu.Unlock()
|
||||
return edp, has
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
)
|
||||
|
||||
|
@ -15,18 +16,18 @@ var HTMLTemplates = []string{
|
|||
"/admin/dashboard.tmpl",
|
||||
}
|
||||
|
||||
func Handler(m *mux.Router, r *render.Renderer) http.Handler {
|
||||
func Handler(m *mux.Router, r *render.Renderer, roomState *roomstate.Manager) http.Handler {
|
||||
if m == nil {
|
||||
m = router.Admin(nil)
|
||||
}
|
||||
|
||||
m.Get(router.AdminDashboard).HandlerFunc(r.HTML("/admin/dashboard.tmpl", dashboard))
|
||||
m.Get(router.AdminDashboard).HandlerFunc(r.HTML("/admin/dashboard.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
lst := roomState.List()
|
||||
return struct {
|
||||
Clients []string
|
||||
Count int
|
||||
}{lst, len(lst)}, nil
|
||||
}))
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func dashboard(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
return struct {
|
||||
Name string
|
||||
}{"test"}, nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/admin"
|
||||
roomsAuth "github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/auth"
|
||||
|
@ -26,6 +27,7 @@ import (
|
|||
func New(
|
||||
m *mux.Router,
|
||||
repo repo.Interface,
|
||||
roomState *roomstate.Manager,
|
||||
as admindb.AuthWithSSBService,
|
||||
fs admindb.AuthFallbackService,
|
||||
) (http.Handler, error) {
|
||||
|
@ -53,8 +55,11 @@ func New(
|
|||
admin.HTMLTemplates,
|
||||
)...),
|
||||
render.FuncMap(web.TemplateFuncs(m)),
|
||||
// TODO: add plural and template data variants
|
||||
// TODO: move these to the i18n helper pkg
|
||||
render.InjectTemplateFunc("i18npl", func(r *http.Request) interface{} {
|
||||
loc := localizerFromRequest(locHelper, r)
|
||||
return loc.LocalizePlurals
|
||||
}),
|
||||
render.InjectTemplateFunc("i18n", func(r *http.Request) interface{} {
|
||||
loc := localizerFromRequest(locHelper, r)
|
||||
return loc.LocalizeSimple
|
||||
|
@ -143,7 +148,7 @@ func New(
|
|||
adminRouter.Use(a.Authenticate)
|
||||
|
||||
// we dont strip path here because it somehow fucks with the middleware setup
|
||||
adminRouter.PathPrefix("/").Handler(admin.Handler(adminRouter, r))
|
||||
adminRouter.PathPrefix("/").Handler(admin.Handler(adminRouter, r, roomState))
|
||||
|
||||
m.PathPrefix("/news").Handler(http.StripPrefix("/news", news.Handler(m, r)))
|
||||
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
)
|
||||
|
@ -100,6 +104,86 @@ func TestLoginForm(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestFallbackAuth(t *testing.T) {
|
||||
setup(t)
|
||||
t.Cleanup(teardown)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
|
||||
loginVals := url.Values{
|
||||
"user": []string{"test"},
|
||||
"pass": []string{"test"},
|
||||
}
|
||||
testAuthFallbackDB.CheckReturns(int64(23), nil)
|
||||
|
||||
url, err := testRouter.Get(router.AuthFallbackSignInForm).URL()
|
||||
r.Nil(err)
|
||||
url.Host = "localhost"
|
||||
url.Scheme = "http"
|
||||
|
||||
resp := testClient.PostForm(url.String(), loginVals)
|
||||
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal(1, testAuthFallbackDB.CheckCallCount())
|
||||
|
||||
// very cheap client session
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
|
||||
c := resp.Result().Cookies()
|
||||
jar.SetCookies(url, c)
|
||||
|
||||
var h = http.Header(map[string][]string{})
|
||||
|
||||
dashboardURL, err := testRouter.Get(router.AdminDashboard).URL()
|
||||
r.Nil(err)
|
||||
dashboardURL.Host = "localhost"
|
||||
dashboardURL.Scheme = "http"
|
||||
|
||||
cs := jar.Cookies(dashboardURL)
|
||||
r.Len(cs, 1, "expecting one cookie!")
|
||||
theCookie := cs[0].String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
h.Set("Cookie", theCookie)
|
||||
|
||||
html, resp := testClient.GetHTML(dashboardURL.String(), &h)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
||||
assertLocalized(t, html, []localizedElement{
|
||||
{"#welcome", "AdminDashboardWelcome"},
|
||||
{"title", "AdminDashboardTitle"},
|
||||
})
|
||||
|
||||
testRef := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{0}, 16)}
|
||||
testRoomState.AddEndpoint(testRef, nil)
|
||||
|
||||
html, resp = testClient.GetHTML(dashboardURL.String(), &h)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
||||
assertLocalized(t, html, []localizedElement{
|
||||
{"#welcome", "AdminDashboardWelcome"},
|
||||
{"title", "AdminDashboardTitle"},
|
||||
{"#roomCount", "AdminRoomCountSingular"},
|
||||
})
|
||||
|
||||
testRef2 := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{1}, 16)}
|
||||
testRoomState.AddEndpoint(testRef2, nil)
|
||||
|
||||
html, resp = testClient.GetHTML(dashboardURL.String(), &h)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
t.Log(html.Find("body").Text())
|
||||
assertLocalized(t, html, []localizedElement{
|
||||
{"#welcome", "AdminDashboardWelcome"},
|
||||
{"title", "AdminDashboardTitle"},
|
||||
{"#roomCount", "AdminRoomCountPlural"},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
type localizedElement struct {
|
||||
|
|
|
@ -4,6 +4,7 @@ package handlers
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -11,9 +12,11 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
"go.mindeco.de/http/tester"
|
||||
"go.mindeco.de/logging/logtest"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/admindb/mockdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
|
@ -30,6 +33,8 @@ var (
|
|||
testAuthDB *mockdb.FakeAuthWithSSBService
|
||||
testAuthFallbackDB *mockdb.FakeAuthFallbackService
|
||||
|
||||
testRoomState *roomstate.Manager
|
||||
|
||||
testI18N = justTheKeys()
|
||||
)
|
||||
|
||||
|
@ -48,18 +53,22 @@ func setup(t *testing.T) {
|
|||
|
||||
testAuthDB = new(mockdb.FakeAuthWithSSBService)
|
||||
testAuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
||||
|
||||
log, _ := logtest.KitLogger("complete", t)
|
||||
ctx := context.TODO()
|
||||
testRoomState = roomstate.NewManager(ctx, log)
|
||||
|
||||
h, err := New(
|
||||
testRouter,
|
||||
testRepo,
|
||||
testRoomState,
|
||||
testAuthDB,
|
||||
testAuthFallbackDB,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "setup: handler init failed"))
|
||||
t.Fatal("setup: handler init failed:", err)
|
||||
}
|
||||
|
||||
// log, _ := logtest.KitLogger("complete", t)
|
||||
|
||||
testMux = http.NewServeMux()
|
||||
testMux.Handle("/", h)
|
||||
testClient = tester.New(testMux, t)
|
||||
|
@ -70,9 +79,13 @@ func teardown() {
|
|||
testClient = nil
|
||||
testAuthDB = nil
|
||||
testAuthFallbackDB = nil
|
||||
testRoomState = nil
|
||||
}
|
||||
|
||||
// auto generate from defaults a list of Label = "Label"
|
||||
// must keep order of input intact
|
||||
// (at least all the globals before starting with nested plurals)
|
||||
// also replaces 'one' and 'other' in plurals
|
||||
func justTheKeys() []byte {
|
||||
f, err := i18n.Defaults.Open("/active.en.toml")
|
||||
if err != nil {
|
||||
|
@ -86,8 +99,36 @@ func justTheKeys() []byte {
|
|||
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
for _, key := range md.Keys() {
|
||||
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
||||
// if we don't produce the same order as the input
|
||||
// (in go maps are ALWAYS random access when ranged over)
|
||||
// nested keys (such as plural form) will mess up the global level...
|
||||
for _, k := range md.Keys() {
|
||||
key := k.String()
|
||||
val, has := justAMap[key]
|
||||
if !has {
|
||||
// fmt.Println("i18n test warning:", key, "not unmarshaled")
|
||||
continue
|
||||
}
|
||||
|
||||
switch tv := val.(type) {
|
||||
|
||||
case string:
|
||||
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
||||
|
||||
case map[string]interface{}:
|
||||
// fmt.Println("i18n test warning: custom map for ", key)
|
||||
|
||||
fmt.Fprintf(buf, "\n[%s]\n", key)
|
||||
// replace "one" and "other" keys
|
||||
// with Label and LabelPlural
|
||||
tv["one"] = key + "Singular"
|
||||
tv["other"] = key + "Plural"
|
||||
toml.NewEncoder(buf).Encode(tv)
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled toml structure under %s: %T\n", key, val))
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
LandingTitle = "ohai my room"
|
||||
LandingWelcome = "Landing welcome here"
|
||||
NewsWelcome = "so, what happend (recently)"
|
||||
NewsTitle = "News"
|
||||
NewsOverview = "News - Overview"
|
||||
AuthFallbackWelcome = "You really shouldn't be here.... Let's get you through this."
|
||||
AuthFallbackTitle = "The place of last resort"
|
||||
AuthSignIn = "Sign in"
|
||||
AuthSignOut = "Sign out"
|
||||
NewsWelcome = "so, what happend (recently)"
|
||||
NewsTitle = "News"
|
||||
NewsOverview = "News - Overview"
|
||||
AdminDashboardWelcome = "Welcome to your dashboard"
|
||||
AdminDashboardTitle = "Room Dashboard"
|
||||
|
||||
[AdminRoomCount]
|
||||
description = "The number of people in a room"
|
||||
one = "Ther is one person in the Room"
|
||||
other = "There are {{.Count}} people in the Room"
|
|
@ -21,14 +21,14 @@ var Defaults = func() http.FileSystem {
|
|||
fs := vfsgen۰FS{
|
||||
"/": &vfsgen۰DirInfo{
|
||||
name: "/",
|
||||
modTime: time.Date(2021, 2, 10, 12, 9, 47, 401093107, time.UTC),
|
||||
modTime: time.Date(2021, 2, 10, 16, 34, 42, 221227039, time.UTC),
|
||||
},
|
||||
"/active.en.toml": &vfsgen۰CompressedFileInfo{
|
||||
name: "active.en.toml",
|
||||
modTime: time.Date(2021, 2, 10, 13, 36, 16, 98174331, time.UTC),
|
||||
uncompressedSize: 345,
|
||||
modTime: time.Date(2021, 2, 10, 18, 20, 44, 393607246, time.UTC),
|
||||
uncompressedSize: 591,
|
||||
|
||||
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x54\x8e\xc1\x4e\xc3\x30\x0c\x86\xef\x7b\x8a\x5f\xb9\x0c\x24\xc8\x1b\x70\xe0\x82\x84\x34\xb1\x03\x93\x10\xc7\xac\x35\x75\x84\x1b\x57\x89\xb3\xaa\x6f\x8f\x4a\xbb\x32\x6e\xf6\xf7\x7f\xb6\xfe\x43\x48\x6d\x4c\xdd\x29\x9a\x10\x9e\xe0\x94\x43\x44\x3f\x21\xab\xf6\x6e\xb7\xa6\x1f\x24\x8d\xf6\xbf\xf9\x4a\x30\xae\x88\x29\x93\xdb\x3d\x57\xe3\x97\x20\x72\x0e\xcd\xf7\x8d\xfc\xa9\x15\x99\x82\xc8\x84\xc2\x5a\xa5\x4d\x7b\xc3\x79\x39\xf2\xde\x7b\x1c\xc8\xf6\x05\x1d\x19\x26\xad\x30\xce\x5a\x3b\x86\x71\x2c\xfe\xff\xd3\xad\xdf\x89\x09\x83\x84\x86\xa0\x5f\x90\x50\x0c\x99\x8a\x66\x5b\xf4\xf7\xd8\xa5\xd7\x34\x7b\xf3\x84\x98\xfe\xf0\xb1\xda\xc6\xb5\x9a\xdb\xbd\xd1\x58\x6e\xba\x16\x7d\xc0\xc8\xc1\xc0\x61\x18\x28\xb5\xb8\xcb\xd4\x50\x32\x99\xee\x17\x77\xab\x30\x2f\x0b\x3a\x5e\x28\x5f\x22\x8d\x57\x8a\x47\x5c\x91\xfb\x09\x00\x00\xff\xff\x5e\xfa\x34\x19\x59\x01\x00\x00"),
|
||||
compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\x5c\x91\xcd\x6a\xe3\x30\x14\x85\xf7\x7e\x8a\x83\x37\x99\x81\x19\xbf\x41\x16\xc3\x84\x42\x21\x34\x90\x06\x4a\x29\x5d\xc8\xf6\xad\x25\x2a\xeb\x1a\xe9\x2a\xc6\x84\xbc\x7b\x51\xfc\x13\xb7\x3b\xe9\xd3\x27\xdd\x73\xd0\x5e\xb9\xda\xb8\xe6\x64\xc4\x12\xb6\xc8\x59\x2b\x83\x76\x80\x67\x6e\xf3\x6c\x3a\x7d\x21\x5b\x71\x7b\x3b\x9f\x08\xfa\x09\x69\xf2\x94\x67\x4f\xd4\x87\x95\x14\xf8\x0f\x7a\xad\x04\x5a\x75\x1d\xb9\x1a\xbf\x3c\x55\xe4\xc4\x0e\xbf\x47\x77\x19\x97\x36\x23\x3a\x9c\xc9\x9f\x0d\xf5\x33\xc5\x5f\xcc\x28\xcf\xfe\x45\xd1\x0f\xca\xda\x52\x55\x9f\xab\x39\xaf\x1c\xe1\x49\x59\x3b\x20\x68\x8e\xb6\x76\x1b\x41\x39\x86\x2a\x8a\xa2\xc0\x9e\x64\x13\xd0\x90\x60\xe0\x08\xd1\x9e\x63\xa3\x21\xda\x84\xe2\xfb\xa3\x4b\xa0\x93\x26\x74\x56\x55\x04\xfe\x80\x55\x41\xe0\x29\xb0\x97\x51\x7f\x36\x8d\x7b\x74\xc9\x4b\x2b\x18\x77\xc7\x87\x28\x0b\xe7\x98\xfc\xba\x35\x6e\xa7\x82\x2e\x59\xf9\x7a\x95\x7a\x5e\x0a\xa7\x54\x1e\xf5\xec\xfc\xbc\xb3\x84\x3a\x32\xb7\xd8\xdd\xb5\xec\xed\x26\x26\xfc\x9f\xa3\x93\xf7\xac\xa6\x50\x79\xd3\x89\x61\x37\xb7\x70\xb1\x2d\xc9\xa7\x1a\x1d\x71\x67\x09\xc6\x41\x4d\xff\xca\x6e\x2e\xeb\x61\x02\xd2\xb6\x23\x1f\x38\x55\x82\x68\xc2\x71\xd4\x24\x09\x5b\x8c\x26\x41\x79\xc2\xe5\x52\xdc\x66\x5e\xaf\xab\x77\x97\x2b\x5f\x01\x00\x00\xff\xff\x18\xb8\x47\x4c\x4f\x02\x00\x00"),
|
||||
},
|
||||
}
|
||||
fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
|
||||
|
|
|
@ -119,6 +119,9 @@ func (l Localizer) LocalizePlurals(messageID string, pluralCount int) string {
|
|||
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
PluralCount: pluralCount,
|
||||
TemplateData: map[string]int{
|
||||
"Count": pluralCount,
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
return msg
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{{ define "title" }}{{i18n "AdminDashboard"}}{{ end }}
|
||||
{{ define "title" }}{{i18n "AdminDashboardTitle"}}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="page-header">
|
||||
<h1 id="welcome">{{i18n "AdminWelcome"}}</h1>
|
||||
<h1 id="welcome">{{i18n "AdminDashboardWelcome"}}</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p>super cool dashboard!</p>
|
||||
<p id="roomCount">{{i18npl "AdminRoomCount" .Count}}</p>
|
||||
<ul>
|
||||
{{range .Clients}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
</div> <!-- /row -->
|
||||
{{end}}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue