packages feed

darcsden-1.1: src/DarcsDen/State/Session.hs

module DarcsDen.State.Session where

import Control.Monad.Trans
import Database.Redis.Monad hiding (expireAt)
import Data.Time
import Data.Time.Clock.POSIX
import System.UUID.V4
import qualified Data.ByteString as BS
import qualified Control.Monad.Trans as MTL

import DarcsDen.State.Util
import DarcsDen.State.User (OAuthID)
import DarcsDen.Util (fromBS, toBS)


data Notification
    = Success { nMessage :: String }
    | Message { nMessage :: String }
    | Warning { nMessage :: String }
    deriving (Eq, Show)

data Session =
    Session
        { sID :: BS.ByteString
        , sExpire :: UTCTime
        , sUser :: Maybe String
        , sNotifications :: [Notification]
        , sOAuthReg :: Maybe (Either String OAuthID) -- Nothing -> registration not in process
                                                     -- Just (Left xs) -> secret key to prevent csrf
                                                     -- Just (Right xid) -> oauth identity of the user
        }
    deriving (Eq, Show)


expireAt :: WithRedis m => String -> Int -> m (Reply Int)
expireAt k t = do
    now <- MTL.liftIO getCurrentTime

    let relative = diffUTCTime (posixSecondsToUTCTime (fromIntegral t)) now

    expire k (ceiling relative)

getSession :: MonadIO m => BS.ByteString -> m (Maybe Session)
getSession sid = withRedis $ do
    RBulk mexpire <- get (sessionKey' sid "expire")

    case mexpire of
        Just e -> do
            RBulk user <- get (sessionKey' sid "user")
            RBulk gr <- get (sessionKey' sid "oauthreg")
            ws <- fmap (map Warning) $ getNotifications "warning"
            ms <- fmap (map Message) $ getNotifications "message"
            ss <- fmap (map Success) $ getNotifications "success"
            return . Just $ Session
                { sID = sid
                , sExpire = posixSecondsToUTCTime (fromIntegral (e :: Int))
                , sUser = user
                , sNotifications = concat [ws, ms, ss]
                , sOAuthReg = fmap read gr
                }
        Nothing -> return Nothing
  where
    getNotifications t = do
        RMulti res <- smembers (sessionKey' sid t)
        case res of
            Just mns ->
                return $ map (\(RBulk (Just n)) -> n) mns
            Nothing -> return []

newSession :: MonadIO m => m (Maybe Session)
newSession = do
    ui <- liftIO uuid

    now <- liftIO (getCurrentTime)
    let s = Session
                { sID = toBS . filter (/= '-') . show $ ui
                , sExpire = addUTCTime (60 * 60 * 24 * 30) now
                , sUser = Nothing
                , sNotifications = []
                , sOAuthReg = Nothing
                }

    withRedis $ do
        incrBy (sessionKey s "expire") (sessionExpire s)
        expireAt (sessionKey s "expire") (sessionExpire s)

    return (Just s)

deleteSession :: MonadIO m => Session -> m ()
deleteSession s = withRedis $ do
    del (sessionKey s "expire")
    del (sessionKey s "user")
    del (sessionKey s "warning")
    del (sessionKey s "message")
    del (sessionKey s "success")
    del (sessionKey s "oauthreg")
    return ()

setUser :: MonadIO m => Maybe String -> Session -> m Session
setUser mn s = withRedis $ do
    case mn of
        Just n -> do
            set key n
            expireAt key (sessionExpire s)
        Nothing -> del key
    return (s { sUser = mn })
  where key = sessionKey s "user"

setOAuthReg :: MonadIO m => Maybe (Either String OAuthID) -> Session -> m ()
setOAuthReg v s = withRedis $ do
   case v of
      Just n -> do
         set key (show n)
         expire key (24 * 60 * 60)
         return ()
      Nothing -> do
         del key
         return ()
  where key = sessionKey s "oauthreg"

clearNotifications :: MonadIO m => Session -> m ()
clearNotifications s = withRedis $
    mapM_ (del . sessionKey s)
        ["warning", "message", "success"]

notice :: MonadIO m => Notification -> Session -> m ()
notice n s = withRedis $ do
    sadd (key n) (nMessage n)
    expireAt (key n) (sessionExpire s)
    return ()
  where
    key (Warning _) = sessionKey s "warning"
    key (Message _) = sessionKey s "message"
    key (Success _) = sessionKey s "success"

warn :: MonadIO m => String -> Session -> m ()
warn = notice . Warning

success :: MonadIO m => String -> Session -> m ()
success = notice . Success

message :: MonadIO m => String -> Session -> m ()
message = notice . Message

sessionKey :: Session -> String -> String
sessionKey = sessionKey' . sID

sessionKey' :: BS.ByteString -> String -> String
sessionKey' sid n = "session:" ++ fromBS sid ++ ":" ++ n

sessionExpire :: Session -> Int
sessionExpire = ceiling . utcTimeToPOSIXSeconds . sExpire