wai-session-postgresql (empty) → 0.1.0.0
raw patch · 5 files changed
+175/−0 lines, 5 filesdep +basedep +bytestringdep +cerealsetup-changed
Dependencies added: base, bytestring, cereal, postgresql-session, postgresql-simple, time, transformers, wai-session
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- src/Network/Wai/Session/PostgreSQL.hs +99/−0
- test/Spec.hs +2/−0
- wai-session-postgresql.cabal +42/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Hans-Christian Esperer <hc@hcesperer.org> (c) 2015++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Network/Wai/Session/PostgreSQL.hs view
@@ -0,0 +1,99 @@+module Network.Wai.Session.PostgreSQL+ ( dbStore+ , WithPostgreSQLConn (..)+ , StoreSettings (..)+ ) where++import Control.Exception.Base+import Control.Exception+import Control.Monad+import Control.Monad.IO.Class+import Data.Int (Int64)+import Data.Serialize (encode, decode, Serialize)+import Data.Time.Clock.POSIX (getPOSIXTime)+import Database.PostgreSQL.Simple+import Network.Wai.Session++import qualified Data.ByteString as B++data StoreSettings = StoreSettings+ { storeSettingsSessionTimeout :: Int64+ , storeSettingsKeyGen :: IO B.ByteString+ }++class WithPostgreSQLConn a where+ withPostgreSQLConn :: a -> (Connection -> IO b) -> IO b++instance WithPostgreSQLConn Connection where+ withPostgreSQLConn conn = bracket (return conn) (\_ -> return ())++qryCreateTable = "CREATE TABLE session (id bigserial NOT NULL, session_key character varying NOT NULL, session_created_at bigint NOT NULL, session_last_access bigint NOT NULL, session_value bytea NOT NULL, CONSTRAINT session_pkey PRIMARY KEY (id), CONSTRAINT session_session_key_key UNIQUE (session_key)) WITH (OIDS=FALSE);"+qryCreateSession = "INSERT INTO session (session_key, session_created_at, session_last_access, session_value) VALUES (?,?,?,?)"+qryUpdateSession = "UPDATE session SET session_value=?,session_last_access=? WHERE session_key=?"+qryLookupSession = "SELECT session_value FROM session WHERE session_key=? AND session_last_access>=?"+qryLookupSession' = "UPDATE session SET session_last_access=? WHERE session_key=?"+qryLookupSession'' = "SELECT session_value FROM session WHERE session_key=?"++dbStore :: (WithPostgreSQLConn a, Serialize k, Eq k, Serialize v, MonadIO m) => a -> StoreSettings -> IO (SessionStore m k v)+dbStore pool stos = do+ withPostgreSQLConn pool $ \ conn ->+ unerror $ execute_ conn qryCreateTable+ return $ dbStore' pool stos++dbStore' :: (WithPostgreSQLConn a, Serialize k, Eq k, Serialize v, MonadIO m) => a -> StoreSettings -> SessionStore m k v+dbStore' pool stos Nothing = do+ newKey <- storeSettingsKeyGen stos+ let map = [] :: [(k, v)]+ map' = "" -- encode map+ curtime <- round <$> liftIO getPOSIXTime+ withPostgreSQLConn pool $ \ conn ->+ void $ execute conn qryCreateSession (newKey, curtime :: Int64, curtime, map' :: B.ByteString)+ backend pool newKey map+dbStore' pool stos (Just key) = do+ let map = [] :: [(k, v)]+ map' = "\"\"" -- encode map+ curtime <- round <$> liftIO getPOSIXTime+ res <- withPostgreSQLConn pool $ \ conn ->+ query conn qryLookupSession (key, curtime - storeSettingsSessionTimeout stos) :: IO [Only B.ByteString]+ case res of+ [Only _] -> backend pool key map+ _ -> dbStore' pool stos Nothing++backend :: (WithPostgreSQLConn a, Serialize k, Eq k, Serialize v, MonadIO m) => a -> B.ByteString -> [(k, v)] -> IO (Session m k v, IO B.ByteString)+backend pool key mappe =+ return ( (+ (reader pool key mappe)+ , (writer pool key mappe) )+ , return key )++reader :: (WithPostgreSQLConn a, Serialize k, Eq k, Serialize v, MonadIO m) => a -> B.ByteString -> [(k, v)] -> k -> m (Maybe v)+reader pool key mappe k = do+ curtime <- round <$> liftIO getPOSIXTime+ res <- liftIO $ withPostgreSQLConn pool $ \conn -> do+ void $ execute conn qryLookupSession' (curtime :: Int64, key)+ query conn qryLookupSession'' (Only key)+ case res of+ [Only store'] -> case decode (fromBinary store') of+ Right store -> return $ k `lookup` store+ Left error -> return Nothing+ [] -> return Nothing++writer :: (WithPostgreSQLConn a, Serialize k, Eq k, Serialize v, MonadIO m) => a -> B.ByteString -> [(k, v)] -> k -> v -> m ()+writer pool key mappe k v = do+ curtime <- round <$> liftIO getPOSIXTime+ [Only store] <- liftIO $ withPostgreSQLConn pool $ \conn ->+ query conn qryLookupSession'' (Only key)+ let store' = case decode (fromBinary store) of+ Right s -> s+ _ -> []+ store'' = ((k,v):) . filter ((/=k) . fst) $ store'+ store''' = encode store''+ liftIO $ withPostgreSQLConn pool $ \conn ->+ void $ execute conn qryUpdateSession (Binary store''', curtime :: Int64, key)+++ignoreSqlError :: SqlError -> IO ()+ignoreSqlError _ = pure ()++unerror :: IO a -> IO ()+unerror action = void action `catch` ignoreSqlError
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"
+ wai-session-postgresql.cabal view
@@ -0,0 +1,42 @@+name: wai-session-postgresql+version: 0.1.0.0+synopsis: PostgreSQL backed Wai session store+description: Please see README.md+homepage: http://github.com/githubuser/postgresql-session#readme+license: BSD3+license-file: LICENSE+author: Hans-Christian Esperer hc@hcesperer.org+maintainer: Hans-Christian Esperer hc@hcesperer.org+homepage: https://github.com/hce/postgresql-session+copyright: 2015 Hans-Christian Esperer+stability: experimental+tested-with: GHC == 7.10.2+category: Web+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Network.Wai.Session.PostgreSQL+ build-depends: base >= 4.7 && < 5+ , bytestring+ , cereal+ , postgresql-simple+ , time+ , transformers+ , wai-session+ default-language: Haskell2010+ default-extensions: OverloadedStrings++test-suite postgresql-session-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base+ , postgresql-session+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/githubuser/postgresql-session