Keep scs for cookies and session API while sessions DDL and queries live in the same sqlc stack as the rest of Postgres.
115 lines
2.6 KiB
Go
115 lines
2.6 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/alexedwards/scs/v2"
|
|
|
|
"plumber/internal/store/sqlc"
|
|
)
|
|
|
|
// SessionStore persists scs sessions in Postgres via sqlc and optionally
|
|
// deletes expired rows on an interval.
|
|
type SessionStore struct {
|
|
db *sql.DB
|
|
q *sqlc.Queries
|
|
stop chan struct{}
|
|
stopped chan struct{}
|
|
once sync.Once
|
|
}
|
|
|
|
// NewSessionStore creates a store backed by db. cleanupInterval > 0 starts a
|
|
// background goroutine that deletes expired sessions; 0 disables cleanup.
|
|
func NewSessionStore(db *sql.DB, cleanupInterval time.Duration) *SessionStore {
|
|
s := &SessionStore{
|
|
db: db,
|
|
q: sqlc.New(db),
|
|
}
|
|
if cleanupInterval > 0 {
|
|
s.stop = make(chan struct{})
|
|
s.stopped = make(chan struct{})
|
|
go s.cleanupLoop(cleanupInterval)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Store returns the scs.Store implementation (s itself).
|
|
func (s *SessionStore) Store() scs.Store {
|
|
return s
|
|
}
|
|
|
|
// Find implements scs.Store.
|
|
func (s *SessionStore) Find(token string) ([]byte, bool, error) {
|
|
return s.FindCtx(context.Background(), token)
|
|
}
|
|
|
|
// Commit implements scs.Store.
|
|
func (s *SessionStore) Commit(token string, data []byte, expiry time.Time) error {
|
|
return s.CommitCtx(context.Background(), token, data, expiry)
|
|
}
|
|
|
|
// Delete implements scs.Store.
|
|
func (s *SessionStore) Delete(token string) error {
|
|
return s.DeleteCtx(context.Background(), token)
|
|
}
|
|
|
|
// FindCtx implements scs.CtxStore.
|
|
func (s *SessionStore) FindCtx(ctx context.Context, token string) ([]byte, bool, error) {
|
|
data, err := s.q.GetSession(ctx, token)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, false, nil
|
|
}
|
|
return nil, false, err
|
|
}
|
|
return data, true, nil
|
|
}
|
|
|
|
// CommitCtx implements scs.CtxStore.
|
|
func (s *SessionStore) CommitCtx(ctx context.Context, token string, data []byte, expiry time.Time) error {
|
|
return s.q.UpsertSession(ctx, sqlc.UpsertSessionParams{
|
|
Token: token,
|
|
Data: data,
|
|
Expiry: expiry,
|
|
})
|
|
}
|
|
|
|
// DeleteCtx implements scs.CtxStore.
|
|
func (s *SessionStore) DeleteCtx(ctx context.Context, token string) error {
|
|
return s.q.DeleteSession(ctx, token)
|
|
}
|
|
|
|
// StopCleanup stops the background expiry deleter. Safe to call multiple times.
|
|
func (s *SessionStore) StopCleanup() {
|
|
if s == nil || s.stop == nil {
|
|
return
|
|
}
|
|
s.once.Do(func() {
|
|
close(s.stop)
|
|
<-s.stopped
|
|
})
|
|
}
|
|
|
|
// Close stops background session cleanup.
|
|
func (s *SessionStore) Close() {
|
|
s.StopCleanup()
|
|
}
|
|
|
|
func (s *SessionStore) cleanupLoop(interval time.Duration) {
|
|
defer close(s.stopped)
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
_ = s.q.DeleteExpiredSessions(context.Background())
|
|
case <-s.stop:
|
|
return
|
|
}
|
|
}
|
|
}
|