{-|
Module: IHP.FileStorage.Config
Description: Helpers for Config.hs to set up the File Storage
Copyright: (c) digitally induced GmbH, 2021
-}
module IHP.FileStorage.Config
( initS3Storage
, initStaticDirStorage
, initMinioStorage
, initFilebaseStorage
) where

import IHP.Prelude
import IHP.FileStorage.Types
import IHP.FrameworkConfig

import Network.Minio

import qualified Control.Monad.Trans.State.Strict as State
import qualified Data.TMap as TMap
import Control.Monad.Trans.Maybe
import qualified IHP.EnvVar as EnvVar

-- | The AWS access key and secret key have to be provided using the @AWS_ACCESS_KEY_ID@ and @AWS_SECRET_ACCESS_KEY@ env vars.
--
-- __Example:__ Set up a s3 storage in @Config.hs@
--
-- > module Config where
-- >
-- > import IHP.Prelude
-- > import IHP.Environment
-- > import IHP.FrameworkConfig
-- > import IHP.FileStorage.Config
-- >
-- > config :: ConfigBuilder
-- > config = do
-- >     option Development
-- >     option (AppHostname "localhost")
-- >     initS3Storage "eu-central-1" "my-bucket-name"
--
initS3Storage :: HasCallStack => Text -> Text -> State.StateT TMap.TMap IO ()
initS3Storage :: HasCallStack => Region -> Region -> StateT TMap IO ()
initS3Storage Region
region Region
bucket = do
    ConnectInfo
connectInfo <- ConnectInfo
awsCI
        ConnectInfo -> (ConnectInfo -> ConnectInfo) -> ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Region -> ConnectInfo -> ConnectInfo
setRegion Region
region
        ConnectInfo -> (ConnectInfo -> IO ConnectInfo) -> IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [CredentialLoader] -> ConnectInfo -> IO ConnectInfo
setCredsFrom [CredentialLoader
fromAWSEnv]
        IO ConnectInfo
-> (IO ConnectInfo -> StateT TMap IO ConnectInfo)
-> StateT TMap IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> IO ConnectInfo -> StateT TMap IO ConnectInfo
forall (monad :: * -> *) result.
(MonadIO monad, HasCallStack) =>
IO result -> monad result
configIO

    let baseUrl :: Region
baseUrl = Region
"https://" Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
bucket Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
".s3." Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
region Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
".amazonaws.com/"
    FileStorage -> StateT TMap IO ()
forall option. Typeable option => option -> StateT TMap IO ()
option S3Storage { ConnectInfo
connectInfo :: ConnectInfo
$sel:connectInfo:StaticDirStorage :: ConnectInfo
connectInfo, Region
bucket :: Region
$sel:bucket:StaticDirStorage :: Region
bucket, Region
baseUrl :: Region
$sel:baseUrl:StaticDirStorage :: Region
baseUrl }

-- | The Minio access key and secret key have to be provided using the @MINIO_ACCESS_KEY@ and @MINIO_SECRET_KEY@ env vars.
--
-- __Example:__ Set up a minio storage in @Config.hs@
--
-- > module Config where
-- >
-- > import IHP.Prelude
-- > import IHP.Environment
-- > import IHP.FrameworkConfig
-- > import IHP.FileStorage.Config
-- >
-- > config :: ConfigBuilder
-- > config = do
-- >     option Development
-- >     option (AppHostname "localhost")
-- >     initMinioStorage "https://minio.example.com" "my-bucket-name"
--
initMinioStorage :: HasCallStack => Text -> Text -> State.StateT TMap.TMap IO ()
initMinioStorage :: HasCallStack => Region -> Region -> StateT TMap IO ()
initMinioStorage Region
server Region
bucket = do
    ConnectInfo
connectInfo <- Region
server
        Region -> (Region -> String) -> String
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Region -> String
forall a b. ConvertibleStrings a b => a -> b
cs
        String -> (String -> ConnectInfo) -> ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> String -> ConnectInfo
forall a. IsString a => String -> a
fromString
        ConnectInfo -> (ConnectInfo -> IO ConnectInfo) -> IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [CredentialLoader] -> ConnectInfo -> IO ConnectInfo
setCredsFrom [CredentialLoader
fromMinioEnv]
        IO ConnectInfo
-> (IO ConnectInfo -> StateT TMap IO ConnectInfo)
-> StateT TMap IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> IO ConnectInfo -> StateT TMap IO ConnectInfo
forall (monad :: * -> *) result.
(MonadIO monad, HasCallStack) =>
IO result -> monad result
configIO

    let baseUrl :: Region
baseUrl = Region
server Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
"/" Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
bucket Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
"/"
    FileStorage -> StateT TMap IO ()
forall option. Typeable option => option -> StateT TMap IO ()
option S3Storage { ConnectInfo
$sel:connectInfo:StaticDirStorage :: ConnectInfo
connectInfo :: ConnectInfo
connectInfo, Region
$sel:bucket:StaticDirStorage :: Region
bucket :: Region
bucket, Region
$sel:baseUrl:StaticDirStorage :: Region
baseUrl :: Region
baseUrl }

-- | Stores files publicly visible inside the @static@ directory
--
-- __Example:__ Store uploaded files in the @static/@ directory
--
-- > module Config where
-- >
-- > import IHP.Prelude
-- > import IHP.Environment
-- > import IHP.FrameworkConfig
-- > import IHP.FileStorage.Config
-- >
-- > config :: ConfigBuilder
-- > config = do
-- >     option Development
-- >     option (AppHostname "localhost")
-- >     initStaticDirStorage
--
initStaticDirStorage :: State.StateT TMap.TMap IO ()
initStaticDirStorage :: StateT TMap IO ()
initStaticDirStorage = FileStorage -> StateT TMap IO ()
forall option. Typeable option => option -> StateT TMap IO ()
option FileStorage
StaticDirStorage

-- | The Filebase access key and secret key have to be provided using the @FILEBASE_KEY@ and @FILEBASE_SECRET@ env vars.
--
-- __Example:__ Set up a Filebase storage in @Config.hs@
--
-- > module Config where
-- >
-- > import IHP.Prelude
-- > import IHP.Environment
-- > import IHP.FrameworkConfig
-- > import IHP.FileStorage.Config
-- >
-- > config :: ConfigBuilder
-- > config = do
-- >     option Development
-- >     option (AppHostname "localhost")
-- >     initFilebaseStorage "my-bucket-name"
--
initFilebaseStorage :: HasCallStack => Text -> State.StateT TMap.TMap IO ()
initFilebaseStorage :: HasCallStack => Region -> StateT TMap IO ()
initFilebaseStorage Region
bucket = do
    ConnectInfo
connectInfo <- ConnectInfo
filebaseCI
        ConnectInfo -> (ConnectInfo -> IO ConnectInfo) -> IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [CredentialLoader] -> ConnectInfo -> IO ConnectInfo
setCredsFrom [CredentialLoader
fromFilebaseEnv]
        IO ConnectInfo
-> (IO ConnectInfo -> StateT TMap IO ConnectInfo)
-> StateT TMap IO ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> IO ConnectInfo -> StateT TMap IO ConnectInfo
forall (monad :: * -> *) result.
(MonadIO monad, HasCallStack) =>
IO result -> monad result
configIO

    let baseUrl :: Region
baseUrl = Region
"https://" Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
bucket Region -> Region -> Region
forall a. Semigroup a => a -> a -> a
<> Region
".s3.filebase.com/"
    FileStorage -> StateT TMap IO ()
forall option. Typeable option => option -> StateT TMap IO ()
option S3Storage { ConnectInfo
$sel:connectInfo:StaticDirStorage :: ConnectInfo
connectInfo :: ConnectInfo
connectInfo, Region
$sel:bucket:StaticDirStorage :: Region
bucket :: Region
bucket, Region
$sel:baseUrl:StaticDirStorage :: Region
baseUrl :: Region
baseUrl }

filebaseCI :: ConnectInfo
filebaseCI :: ConnectInfo
filebaseCI = ConnectInfo
"https://s3.filebase.com" ConnectInfo -> (ConnectInfo -> ConnectInfo) -> ConnectInfo
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Region -> ConnectInfo -> ConnectInfo
setRegion Region
"us-east-1"

fromFilebaseEnv :: CredentialLoader
fromFilebaseEnv :: CredentialLoader
fromFilebaseEnv = MaybeT IO CredentialValue -> CredentialLoader
forall (m :: * -> *) a. MaybeT m a -> m (Maybe a)
runMaybeT (MaybeT IO CredentialValue -> CredentialLoader)
-> MaybeT IO CredentialValue -> CredentialLoader
forall a b. (a -> b) -> a -> b
$ do
    String
filebaseKey <- IO (Maybe String) -> MaybeT IO String
forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT (IO (Maybe String) -> MaybeT IO String)
-> IO (Maybe String) -> MaybeT IO String
forall a b. (a -> b) -> a -> b
$ ByteString -> IO (Maybe String)
forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad (Maybe result)
EnvVar.envOrNothing ByteString
"FILEBASE_KEY"
    String
filebaseSecret <- IO (Maybe String) -> MaybeT IO String
forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT (IO (Maybe String) -> MaybeT IO String)
-> IO (Maybe String) -> MaybeT IO String
forall a b. (a -> b) -> a -> b
$ ByteString -> IO (Maybe String)
forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad (Maybe result)
EnvVar.envOrNothing ByteString
"FILEBASE_SECRET"
    CredentialValue -> MaybeT IO CredentialValue
forall a. a -> MaybeT IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure CredentialValue { cvAccessKey :: AccessKey
cvAccessKey = String -> AccessKey
forall a. IsString a => String -> a
fromString String
filebaseKey, cvSecretKey :: SecretKey
cvSecretKey = String -> SecretKey
forall a. IsString a => String -> a
fromString String
filebaseSecret, cvSessionToken :: Maybe SessionToken
cvSessionToken = Maybe SessionToken
forall a. Maybe a
Nothing }