From 82a3314089dcab6e32db6c6fb9dfa30875987c31 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:44:27 -1000 Subject: [PATCH] feat: add SQLite session store support Add NewSQLiteSessionManager helper that creates an SCS session manager backed by SQLite, allowing sessions to persist across server restarts. The function handles table creation automatically. --- go.mod | 1 + go.sum | 3 +++ internal/examples/session/main.go | 22 +++++++++++++++++++++- session.go | 23 +++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 15c10cd..7668c1a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require maragu.dev/gomponents v1.2.0 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de github.com/alexedwards/scs/v2 v2.9.0 github.com/mattn/go-sqlite3 v1.14.32 github.com/starfederation/datastar-go v1.0.3 diff --git a/go.sum b/go.sum index b37ac05..470891f 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWG github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de h1:c72K9HLu6K442et0j3BUL/9HEYaUJouLkkVANdmqTOo= +github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss= github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90= github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -17,6 +19,7 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= diff --git a/internal/examples/session/main.go b/internal/examples/session/main.go index cecbd17..0674076 100644 --- a/internal/examples/session/main.go +++ b/internal/examples/session/main.go @@ -1,13 +1,33 @@ package main import ( + "database/sql" + "log" + + _ "github.com/mattn/go-sqlite3" "github.com/ryanhamamura/via" "github.com/ryanhamamura/via/h" ) func main() { + // Open SQLite database for persistent sessions + db, err := sql.Open("sqlite3", "sessions.db") + if err != nil { + log.Fatalf("failed to open database: %v", err) + } + defer db.Close() + + // Create session manager with SQLite store + sm, err := via.NewSQLiteSessionManager(db) + if err != nil { + log.Fatalf("failed to create session manager: %v", err) + } + v := via.New() - v.Config(via.Options{ServerAddress: ":7331"}) + v.Config(via.Options{ + ServerAddress: ":7331", + SessionManager: sm, + }) // Login page v.Page("/login", func(c *via.Context) { diff --git a/session.go b/session.go index 2fa1608..268fd23 100644 --- a/session.go +++ b/session.go @@ -2,11 +2,34 @@ package via import ( "context" + "database/sql" "time" + "github.com/alexedwards/scs/sqlite3store" "github.com/alexedwards/scs/v2" ) +// NewSQLiteSessionManager creates a session manager using SQLite for persistence. +// Creates the sessions table if it doesn't exist. +// The returned manager can be configured further (Lifetime, Cookie settings, etc.) +// before passing to Options.SessionManager. +func NewSQLiteSessionManager(db *sql.DB) (*scs.SessionManager, error) { + _, err := db.Exec(` + CREATE TABLE IF NOT EXISTS sessions ( + token TEXT PRIMARY KEY, + data BLOB NOT NULL, + expiry REAL NOT NULL + ); + CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON sessions(expiry); + `) + if err != nil { + return nil, err + } + sm := scs.New() + sm.Store = sqlite3store.New(db) + return sm, nil +} + // Session provides access to the user's session data. // Session data persists across page views for the same browser. type Session struct {