content: {- mimic server side session store -}
  
  module Session where
  
  import Control.Concurrent.MVar
  import Control.Monad.IO.Class
  import Control.Monad.Trans.Except
  import Data.Default
  import Data.Map.Strict qualified as Map
  import Data.Text.Lazy qualified as TL
  import Network.OAuth2.Experiment
  import Network.OAuth2.Provider
  import Types
  
  -- For the sake of simplicity for this demo App,
  -- I store user data in MVar in server side.
  -- It means user session shared across browsers.
  -- which simplify my testing cross browsers.
  -- I am sure you don't want to this for your production services.
  initUserStore ::
    [IdpName] ->
    IO AuthorizationGrantUserStore
  initUserStore = do
    fmap AuthorizationGrantUserStore
      . newMVar
      . Map.fromList
      . fmap (\idpName -> (idpName, def {idpName = idpName}))
  
  insertCodeVerifier ::
    AuthorizationGrantUserStore ->
    IdpName ->
    Maybe CodeVerifier ->
    ExceptT TL.Text IO ()
  insertCodeVerifier store idpName val = do
    sdata <- lookupAppSessionData store idpName
    let newData = sdata {authorizePkceCodeVerifier = val}
    liftIO $ upsertAppSessionData store idpName newData
  
  upsertAppSessionData ::
    AuthorizationGrantUserStore ->
    IdpName ->
    IdpAuthorizationCodeAppSessionData ->
    IO ()
  upsertAppSessionData (AuthorizationGrantUserStore store) idpName val = do
    m1 <- takeMVar store
    let m2 =
          if Map.member idpName m1
            then Map.adjust (const val) idpName m1
            else Map.insert idpName val m1
    putMVar store m2
  
  allAppSessionData :: AuthorizationGrantUserStore -> IO [IdpAuthorizationCodeAppSessionData]
  allAppSessionData (AuthorizationGrantUserStore store) = do
    m1 <- tryReadMVar store
    return $ maybe [] Map.elems m1
  
  removeAppSessionData ::
    AuthorizationGrantUserStore ->
    IdpName ->
    IO ()
  removeAppSessionData store idpName = do
    upsertAppSessionData store idpName (def {idpName = idpName})
  
  lookupAppSessionData ::
    AuthorizationGrantUserStore ->
    IdpName ->
    ExceptT TL.Text IO IdpAuthorizationCodeAppSessionData
  lookupAppSessionData (AuthorizationGrantUserStore store) idpName = do
    mm <- liftIO $ tryReadMVar store
    m1 <-
      except $
        maybe
          (Left "[lookupAppSessionData] store (mvar) is empty")
          Right
          mm
    except $
      maybe
        (Left $ "[lookupAppSessionData] unable to find cache data for idp " <> toText idpName)
        Right
        (Map.lookup idpName m1)
  