{-# LANGUAGE AllowAmbiguousTypes #-}

module IHP.LoginSupport.Helper.Controller
( currentUser
, currentUserOrNothing
, currentUserId
, ensureIsUser
, HasNewSessionUrl
, currentAdmin
, currentAdminOrNothing
, currentAdminId
, ensureIsAdmin
, login
, sessionKey
, logout
, CurrentUserRecord
, CurrentAdminRecord
, module IHP.AuthSupport.Authorization
, module IHP.AuthSupport.Authentication
) where

import IHP.Prelude
import IHP.Controller.Context
import IHP.Controller.Redirect
import IHP.Controller.Session
import IHP.LoginSupport.Types
import IHP.Controller.RequestContext
import qualified IHP.Controller.Session as Session
import IHP.FlashMessages.ControllerFunctions
import qualified IHP.ModelSupport as ModelSupport
import IHP.ControllerSupport
import IHP.FrameworkConfig
import System.IO.Unsafe (unsafePerformIO)
import Control.Monad.Fail
import IHP.AuthSupport.Authorization
import IHP.AuthSupport.Authentication
import IHP.Controller.Context

{-# INLINABLE currentUser #-}
currentUser :: forall user. (?context :: ControllerContext, HasNewSessionUrl user, Typeable user, user ~ CurrentUserRecord) => user
currentUser :: user
currentUser = user -> Maybe user -> user
forall a. a -> Maybe a -> a
fromMaybe (Text -> user
forall a. (?context::ControllerContext) => Text -> a
redirectToLogin (Proxy user -> Text
forall user. HasNewSessionUrl user => Proxy user -> Text
newSessionUrl (Proxy user
forall k (t :: k). Proxy t
Proxy @user))) Maybe user
forall user.
(?context::ControllerContext, HasNewSessionUrl user, Typeable user,
 user ~ CurrentUserRecord) =>
Maybe user
currentUserOrNothing

{-# INLINABLE currentUserOrNothing #-}
currentUserOrNothing :: forall user. (?context :: ControllerContext, HasNewSessionUrl user, Typeable user, user ~ CurrentUserRecord) => (Maybe user)
currentUserOrNothing :: Maybe user
currentUserOrNothing = case IO (Maybe (Maybe user)) -> Maybe (Maybe user)
forall a. IO a -> a
unsafePerformIO ((?context::ControllerContext, Typeable (Maybe user)) =>
IO (Maybe (Maybe user))
forall value.
(?context::ControllerContext, Typeable value) =>
IO (Maybe value)
maybeFromContext @(Maybe user)) of
    Just Maybe user
user -> Maybe user
user
    Maybe (Maybe user)
Nothing -> Text -> Maybe user
forall a. Text -> a
error Text
"currentUserOrNothing: initAuthentication @User has not been called in initContext inside FrontController of this application"

{-# INLINABLE currentUserId #-}
currentUserId :: forall user userId. (?context :: ControllerContext, HasNewSessionUrl user, HasField "id" user userId, Typeable user, user ~ CurrentUserRecord) => userId
currentUserId :: userId
currentUserId = (?context::ControllerContext, HasNewSessionUrl user, Typeable user,
 user ~ CurrentUserRecord) =>
user
forall user.
(?context::ControllerContext, HasNewSessionUrl user, Typeable user,
 user ~ CurrentUserRecord) =>
user
currentUser @user user -> (user -> userId) -> userId
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "id" -> user -> userId
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "id" (Proxy "id")
Proxy "id"
#id

{-# INLINABLE ensureIsUser #-}
ensureIsUser :: forall user userId. (?context :: ControllerContext, HasNewSessionUrl user, HasField "id" user userId, Typeable user, user ~ CurrentUserRecord) => IO ()
ensureIsUser :: IO ()
ensureIsUser =
    case (?context::ControllerContext, HasNewSessionUrl user, Typeable user,
 user ~ CurrentUserRecord) =>
Maybe user
forall user.
(?context::ControllerContext, HasNewSessionUrl user, Typeable user,
 user ~ CurrentUserRecord) =>
Maybe user
currentUserOrNothing @user of
        Just user
_ -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
        Maybe user
Nothing -> (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
redirectToLoginWithMessage (Proxy user -> Text
forall user. HasNewSessionUrl user => Proxy user -> Text
newSessionUrl (Proxy user
forall k (t :: k). Proxy t
Proxy :: Proxy user))

{-# INLINABLE currentAdmin #-}
currentAdmin :: forall admin. (?context :: ControllerContext, HasNewSessionUrl admin, Typeable admin) => admin
currentAdmin :: admin
currentAdmin = admin -> Maybe admin -> admin
forall a. a -> Maybe a -> a
fromMaybe (Text -> admin
forall a. (?context::ControllerContext) => Text -> a
redirectToLogin (Proxy admin -> Text
forall user. HasNewSessionUrl user => Proxy user -> Text
newSessionUrl (Proxy admin
forall k (t :: k). Proxy t
Proxy @admin))) Maybe admin
forall admin.
(?context::ControllerContext, HasNewSessionUrl admin,
 Typeable admin) =>
Maybe admin
currentAdminOrNothing

{-# INLINABLE currentAdminOrNothing #-}
currentAdminOrNothing :: forall admin. (?context :: ControllerContext, HasNewSessionUrl admin, Typeable admin) => (Maybe admin)
currentAdminOrNothing :: Maybe admin
currentAdminOrNothing = case IO (Maybe (Maybe admin)) -> Maybe (Maybe admin)
forall a. IO a -> a
unsafePerformIO ((?context::ControllerContext, Typeable (Maybe admin)) =>
IO (Maybe (Maybe admin))
forall value.
(?context::ControllerContext, Typeable value) =>
IO (Maybe value)
maybeFromContext @(Maybe admin)) of
    Just Maybe admin
admin -> Maybe admin
admin
    Maybe (Maybe admin)
Nothing -> Text -> Maybe admin
forall a. Text -> a
error Text
"currentAdminOrNothing: initAuthentication @Admin has not been called in initContext inside FrontController of this application"

{-# INLINABLE currentAdminId #-}
currentAdminId :: forall admin adminId. (?context :: ControllerContext, HasNewSessionUrl admin, HasField "id" admin adminId, Typeable admin) => adminId
currentAdminId :: adminId
currentAdminId = (?context::ControllerContext, HasNewSessionUrl admin,
 Typeable admin) =>
admin
forall admin.
(?context::ControllerContext, HasNewSessionUrl admin,
 Typeable admin) =>
admin
currentAdmin @admin admin -> (admin -> adminId) -> adminId
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> Proxy "id" -> admin -> adminId
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "id" (Proxy "id")
Proxy "id"
#id

{-# INLINABLE ensureIsAdmin #-}
ensureIsAdmin :: forall admin adminId. (?context :: ControllerContext, HasNewSessionUrl admin, Typeable admin) => IO ()
ensureIsAdmin :: IO ()
ensureIsAdmin =
    case (?context::ControllerContext, HasNewSessionUrl admin,
 Typeable admin) =>
Maybe admin
forall admin.
(?context::ControllerContext, HasNewSessionUrl admin,
 Typeable admin) =>
Maybe admin
currentAdminOrNothing @admin of
        Just admin
_ -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
        Maybe admin
Nothing -> (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
redirectToLoginWithMessage (Proxy admin -> Text
forall user. HasNewSessionUrl user => Proxy user -> Text
newSessionUrl (Proxy admin
forall k (t :: k). Proxy t
Proxy :: Proxy admin))

-- | Log's in an entity
-- Examples:
-- ```
-- let user :: User = ... in login user
-- let admin :: Admin = ... in login admin
-- ```
{-# INLINABLE login #-}
login :: forall user id. (?context :: ControllerContext, KnownSymbol (ModelSupport.GetModelName user), HasField "id" user id, Show id) => user -> IO ()
login :: user -> IO ()
login user
user = (?context::ControllerContext) => Text -> Text -> IO ()
Text -> Text -> IO ()
Session.setSession (KnownSymbol (GetModelName user) => Text
forall user. KnownSymbol (GetModelName user) => Text
sessionKey @user) (id -> Text
forall a. Show a => a -> Text
tshow (Proxy "id" -> user -> id
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "id" (Proxy "id")
Proxy "id"
#id user
user))

-- Log's out an entity
{-# INLINABLE logout #-}
logout :: forall user id. (?context :: ControllerContext, KnownSymbol (ModelSupport.GetModelName user), HasField "id" user id, Show id) => user -> IO ()
logout :: user -> IO ()
logout user
user = (?context::ControllerContext) => Text -> Text -> IO ()
Text -> Text -> IO ()
Session.setSession (KnownSymbol (GetModelName user) => Text
forall user. KnownSymbol (GetModelName user) => Text
sessionKey @user) Text
""

{-# INLINABLE sessionKey #-}
sessionKey :: forall user. (KnownSymbol (ModelSupport.GetModelName user)) => Text
sessionKey :: Text
sessionKey = Text
"login." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> KnownSymbol (GetModelName user) => Text
forall user. KnownSymbol (GetModelName user) => Text
ModelSupport.getModelName @user

redirectToLoginWithMessage :: (?context :: ControllerContext) => Text -> IO ()
redirectToLoginWithMessage :: Text -> IO ()
redirectToLoginWithMessage Text
newSessionPath = do 
    (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
setSuccessMessage Text
"Please log in to access this page"
    (?context::ControllerContext) => Text -> Text -> IO ()
Text -> Text -> IO ()
setSession Text
"IHP.LoginSupport.redirectAfterLogin" (ByteString -> Text
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
(?context::ControllerContext) => ByteString
getRequestPathAndQuery)
    (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
redirectToPath Text
newSessionPath
    Text -> IO ()
forall a. Text -> a
error Text
"Unreachable"


redirectToLogin :: (?context :: ControllerContext) => Text -> a
redirectToLogin :: Text -> a
redirectToLogin Text
newSessionPath = IO a -> a
forall a. IO a -> a
unsafePerformIO (IO a -> a) -> IO a -> a
forall a b. (a -> b) -> a -> b
$ do
    (?context::ControllerContext) => Text -> IO ()
Text -> IO ()
redirectToPath Text
newSessionPath
    Text -> IO a
forall a. Text -> a
error Text
"Unreachable"