module Snap.Snaplet.SqliteSimple.JwtAuth.Db where
import Control.Concurrent
import Control.Monad.State
import Data.ByteString (ByteString)
import Data.Maybe (listToMaybe)
import qualified Data.Text as T
import qualified Database.SQLite.Simple as S
import Snap
import Snap.Snaplet.SqliteSimple
import Snap.Snaplet.SqliteSimple.JwtAuth.Types
data DbUser = DbUser {
dbuserId :: Int
, dbuserLogin :: T.Text
, dbuserHashedPass :: ByteString
} deriving (Eq, Show)
instance FromRow DbUser where
fromRow = DbUser <$> field <*> field <*> field
jwtAuthTable :: T.Text
jwtAuthTable = "jwt_auth"
versionTable :: T.Text
versionTable = T.concat [jwtAuthTable, "_version"]
userTable :: T.Text
userTable = T.concat [jwtAuthTable, "_user"]
schemaVersion :: S.Connection -> IO Int
schemaVersion conn = do
versionExists <- tableExists conn versionTable
if not versionExists
then return 0
else do
[Only v] <- S.query_ conn (qconcat ["SELECT version FROM ", versionTable, " LIMIT 1"]) :: IO [Only Int]
return v
tableExists :: S.Connection -> T.Text -> IO Bool
tableExists conn tblName = do
r <- S.query conn "SELECT name FROM sqlite_master WHERE type='table' AND name=?" (Only tblName)
case r of
[Only (_ :: String)] -> return True
_ -> return False
setSchemaVersion :: S.Connection -> Int -> IO ()
setSchemaVersion conn v = do
let q = S.Query $ T.concat ["UPDATE ", versionTable, " SET version = ?"]
S.execute conn q (Only v)
upgradeSchema :: Connection -> Int -> IO ()
upgradeSchema conn fromVersion = do
ver <- schemaVersion conn
when (ver == fromVersion) (upgrade ver >> setSchemaVersion conn (fromVersion+1))
where
upgrade 0 = do
S.execute_ conn (S.Query $ T.concat ["CREATE TABLE ", versionTable, " (version INTEGER)"])
S.execute_ conn (S.Query $ T.concat ["INSERT INTO ", versionTable, " VALUES (1)"])
upgrade _ = error "unknown version"
createInitialSchema :: S.Connection -> IO ()
createInitialSchema conn = do
let q = S.Query $ T.concat
[ "CREATE TABLE ", userTable, " (uid INTEGER PRIMARY KEY,"
, "login text UNIQUE NOT NULL,"
, "password text,"
, "created_on timestamp);"
]
S.execute_ conn q
createTableIfMissing :: MVar S.Connection -> IO ()
createTableIfMissing connMVar =
withMVar connMVar $ \conn -> do
authTblExists <- tableExists conn userTable
unless authTblExists $ createInitialSchema conn
upgradeSchema conn 0
executeSingle :: (ToRow q) => S.Query -> q -> H b ()
executeSingle q ps = do
conn <- gets sqliteJwtConn
liftIO $ withMVar conn $ \c ->
S.execute c q ps
querySingle :: (ToRow q, FromRow a) => S.Query -> q -> H b (Maybe a)
querySingle q ps = do
conn <- gets sqliteJwtConn
liftIO $ withMVar conn $ \c ->
return . listToMaybe =<< S.query c q ps
qconcat :: [T.Text] -> S.Query
qconcat = S.Query . T.concat
fromDbUser :: DbUser -> User
fromDbUser (DbUser i l _) = User i l
queryUser :: T.Text -> Handler b SqliteJwt (Maybe DbUser)
queryUser login = do
querySingle (qconcat ["SELECT uid,login,password FROM ", userTable, " WHERE login=?"])
(Only login)
insertUser :: T.Text -> ByteString -> Handler b SqliteJwt ()
insertUser login hashedPass = do
let insq = qconcat ["INSERT INTO ", userTable, " (login,password) VALUES (?,?)"]
executeSingle insq (login,hashedPass)