{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, FlexibleContexts, AllowAmbiguousTypes, FlexibleInstances, IncoherentInstances, UndecidableInstances, PolyKinds, BlockArguments, DataKinds #-}
module IHP.Controller.FileUpload where
import IHP.Prelude
import Network.Wai.Parse (FileInfo, fileContent)
import qualified IHP.ModelSupport as ModelSupport
import qualified Data.ByteString.Lazy as LBS
import IHP.Controller.RequestContext
import IHP.Controller.Context
import qualified System.Process as Process
fileOrNothing :: (?context :: ControllerContext) => ByteString -> Maybe (FileInfo LBS.ByteString)
fileOrNothing :: (?context::ControllerContext) =>
ByteString -> Maybe (FileInfo ByteString)
fileOrNothing !ByteString
name =
case ?context::ControllerContext
ControllerContext
?context.requestContext.requestBody of
FormBody { [File ByteString]
files :: [File ByteString]
files :: RequestBody -> [File ByteString]
files } ->
case ByteString -> [File ByteString] -> Maybe (FileInfo ByteString)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup ByteString
name [File ByteString]
files of
Just FileInfo ByteString
fileInfo | Bool -> Bool
not (ByteString -> Bool
LBS.null (FileInfo ByteString
fileInfo.fileContent)) -> FileInfo ByteString -> Maybe (FileInfo ByteString)
forall a. a -> Maybe a
Just FileInfo ByteString
fileInfo
Maybe (FileInfo ByteString)
_ -> Maybe (FileInfo ByteString)
forall a. Maybe a
Nothing
RequestBody
_ -> Maybe (FileInfo ByteString)
forall a. Maybe a
Nothing
filesByName :: (?context :: ControllerContext) => ByteString -> [FileInfo LBS.ByteString]
filesByName :: (?context::ControllerContext) =>
ByteString -> [FileInfo ByteString]
filesByName !ByteString
name =
case ?context::ControllerContext
ControllerContext
?context.requestContext.requestBody of
FormBody { [File ByteString]
files :: RequestBody -> [File ByteString]
files :: [File ByteString]
files } -> [File ByteString]
files
[File ByteString]
-> ([File ByteString] -> [File ByteString]) -> [File ByteString]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (File ByteString -> Bool) -> [File ByteString] -> [File ByteString]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(ByteString
filename, FileInfo ByteString
_) -> ByteString
filename ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
name)
[File ByteString]
-> ([File ByteString] -> [FileInfo ByteString])
-> [FileInfo ByteString]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (File ByteString -> FileInfo ByteString)
-> [File ByteString] -> [FileInfo ByteString]
forall a b. (a -> b) -> [a] -> [b]
map File ByteString -> FileInfo ByteString
forall a b. (a, b) -> b
snd
RequestBody
_ -> []
data ImageUploadOptions = ImageUploadOptions {
ImageUploadOptions -> Text
convertTo :: Text
, ImageUploadOptions -> Text
imageMagickOptions :: Text
}
uploadImageWithOptions :: forall (fieldName :: Symbol) record (tableName :: Symbol). (
?context :: ControllerContext
, SetField fieldName record (Maybe Text)
, KnownSymbol fieldName
, HasField "id" record (ModelSupport.Id (ModelSupport.NormalizeModel record))
, Show (ModelSupport.PrimaryKey (ModelSupport.GetTableName (ModelSupport.NormalizeModel record)))
, tableName ~ ModelSupport.GetTableName record
, KnownSymbol tableName
) => ImageUploadOptions -> Proxy fieldName -> record -> IO record
uploadImageWithOptions :: forall (fieldName :: Symbol) record (tableName :: Symbol).
(?context::ControllerContext,
SetField fieldName record (Maybe Text), KnownSymbol fieldName,
HasField "id" record (Id (NormalizeModel record)),
Show (PrimaryKey (GetTableName (NormalizeModel record))),
tableName ~ GetTableName record, KnownSymbol tableName) =>
ImageUploadOptions -> Proxy fieldName -> record -> IO record
uploadImageWithOptions ImageUploadOptions
options Proxy fieldName
_ record
user =
let
ByteString
fieldName :: ByteString = String -> ByteString
forall a b. ConvertibleStrings a b => a -> b
cs (Proxy fieldName -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @fieldName))
Text
tableName :: Text = String -> Text
forall a b. ConvertibleStrings a b => a -> b
cs (Proxy tableName -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @tableName))
Text
uploadDir :: Text = Text
"static"
Text
baseImagePath :: Text = Text
"/uploads/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Id' (GetTableName (GetModelByTableName tableName)) -> Text
forall a. Show a => a -> Text
tshow record
user.id Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/picture."
Text
imagePath :: Text = Text
baseImagePath Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"jpg"
uploadFilePath :: Text
uploadFilePath = Text
baseImagePath Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"upload"
in case (?context::ControllerContext) =>
ByteString -> Maybe (FileInfo ByteString)
ByteString -> Maybe (FileInfo ByteString)
fileOrNothing ByteString
fieldName of
Just FileInfo ByteString
file -> IO record -> IO record
forall a. IO a -> IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
ExitCode
_ <- String -> IO ExitCode
Process.system (String
"mkdir -p `dirname " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
uploadFilePath) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"`")
let fullImagePath :: Text
fullImagePath = Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
imagePath
FileInfo ByteString -> ByteString
forall c. FileInfo c -> c
fileContent FileInfo ByteString
file ByteString -> (ByteString -> IO ()) -> IO ()
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> String -> ByteString -> IO ()
LBS.writeFile (Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
uploadFilePath))
String -> IO ProcessHandle
Process.runCommand (Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text
"convert " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
forall a b. ConvertibleStrings a b => a -> b
cs Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
uploadFilePath Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (forall {k} (x :: k) r a. HasField x r a => r -> a
forall (x :: Symbol) r a. HasField x r a => r -> a
getField @"imageMagickOptions" ImageUploadOptions
options) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
forall a b. ConvertibleStrings a b => a -> b
cs Text
fullImagePath))
record
user
record -> (record -> record) -> record
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> forall (field :: Symbol) model value.
SetField field model value =>
value -> model -> model
setField @fieldName (Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Text
forall a b. ConvertibleStrings a b => a -> b
cs Text
imagePath :: Text))
record -> (record -> IO record) -> IO record
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> record -> IO record
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return
Maybe (FileInfo ByteString)
_ -> record -> IO record
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure record
user
uploadImageFile :: forall (fieldName :: Symbol) record (tableName :: Symbol). (
?context :: ControllerContext
, SetField fieldName record (Maybe Text)
, KnownSymbol fieldName
, HasField "id" record (ModelSupport.Id (ModelSupport.NormalizeModel record))
, Show (ModelSupport.PrimaryKey (ModelSupport.GetTableName (ModelSupport.NormalizeModel record)))
, tableName ~ ModelSupport.GetTableName record
, KnownSymbol tableName
) => Text -> Proxy fieldName -> record -> IO record
uploadImageFile :: forall (fieldName :: Symbol) record (tableName :: Symbol).
(?context::ControllerContext,
SetField fieldName record (Maybe Text), KnownSymbol fieldName,
HasField "id" record (Id (NormalizeModel record)),
Show (PrimaryKey (GetTableName (NormalizeModel record))),
tableName ~ GetTableName record, KnownSymbol tableName) =>
Text -> Proxy fieldName -> record -> IO record
uploadImageFile Text
ext Proxy fieldName
_ record
user =
let
ByteString
fieldName :: ByteString = String -> ByteString
forall a b. ConvertibleStrings a b => a -> b
cs (Proxy fieldName -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @fieldName))
Text
tableName :: Text = String -> Text
forall a b. ConvertibleStrings a b => a -> b
cs (Proxy tableName -> String
forall (n :: Symbol) (proxy :: Symbol -> *).
KnownSymbol n =>
proxy n -> String
symbolVal (forall {k} (t :: k). Proxy t
forall (t :: Symbol). Proxy t
Proxy @tableName))
Text
uploadDir :: Text = Text
"static"
Text
imagePath :: Text = Text
"/uploads/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Id' (GetTableName (GetModelByTableName tableName)) -> Text
forall a. Show a => a -> Text
tshow record
user.id Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/picture." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
ext
in case (?context::ControllerContext) =>
ByteString -> Maybe (FileInfo ByteString)
ByteString -> Maybe (FileInfo ByteString)
fileOrNothing ByteString
fieldName of
Just FileInfo ByteString
file -> IO record -> IO record
forall a. IO a -> IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
ExitCode
_ <- String -> IO ExitCode
Process.system (String
"mkdir -p `dirname " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
imagePath) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"`")
FileInfo ByteString -> ByteString
forall c. FileInfo c -> c
fileContent FileInfo ByteString
file ByteString -> (ByteString -> IO ()) -> IO ()
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> String -> ByteString -> IO ()
LBS.writeFile (Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Text
uploadDir Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
imagePath)
record
user
record -> (record -> record) -> record
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> forall (field :: Symbol) model value.
SetField field model value =>
value -> model -> model
setField @fieldName (Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Text
forall a b. ConvertibleStrings a b => a -> b
cs Text
imagePath :: Text))
record -> (record -> IO record) -> IO record
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> record -> IO record
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
Maybe (FileInfo ByteString)
_ -> record -> IO record
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure record
user
uploadPng ::
( ?context :: ControllerContext
, SetField fieldName record (Maybe Text)
, HasField "id" record (ModelSupport.Id' (GetTableName (ModelSupport.GetModelByTableName (GetTableName record))))
, Show (ModelSupport.PrimaryKey (GetTableName (ModelSupport.GetModelByTableName (GetTableName record))))
, KnownSymbol fieldName
, KnownSymbol (GetTableName record)
) => Proxy fieldName -> record -> IO record
uploadPng :: forall (fieldName :: Symbol) record.
(?context::ControllerContext,
SetField fieldName record (Maybe Text),
HasField
"id"
record
(Id' (GetTableName (GetModelByTableName (GetTableName record)))),
Show
(PrimaryKey
(GetTableName (GetModelByTableName (GetTableName record)))),
KnownSymbol fieldName, KnownSymbol (GetTableName record)) =>
Proxy fieldName -> record -> IO record
uploadPng Proxy fieldName
field record
record = Text -> Proxy fieldName -> record -> IO record
forall (fieldName :: Symbol) record (tableName :: Symbol).
(?context::ControllerContext,
SetField fieldName record (Maybe Text), KnownSymbol fieldName,
HasField "id" record (Id (NormalizeModel record)),
Show (PrimaryKey (GetTableName (NormalizeModel record))),
tableName ~ GetTableName record, KnownSymbol tableName) =>
Text -> Proxy fieldName -> record -> IO record
uploadImageFile Text
"png" Proxy fieldName
field record
record
uploadSVG ::
( ?context :: ControllerContext
, SetField fieldName record (Maybe Text)
, HasField "id" record (ModelSupport.Id' (GetTableName (ModelSupport.GetModelByTableName (GetTableName record))))
, Show (ModelSupport.PrimaryKey (GetTableName (ModelSupport.GetModelByTableName (GetTableName record))))
, KnownSymbol fieldName
, KnownSymbol (GetTableName record)
) => Proxy fieldName -> record -> IO record
uploadSVG :: forall (fieldName :: Symbol) record.
(?context::ControllerContext,
SetField fieldName record (Maybe Text),
HasField
"id"
record
(Id' (GetTableName (GetModelByTableName (GetTableName record)))),
Show
(PrimaryKey
(GetTableName (GetModelByTableName (GetTableName record)))),
KnownSymbol fieldName, KnownSymbol (GetTableName record)) =>
Proxy fieldName -> record -> IO record
uploadSVG = Text -> Proxy fieldName -> record -> IO record
forall (fieldName :: Symbol) record (tableName :: Symbol).
(?context::ControllerContext,
SetField fieldName record (Maybe Text), KnownSymbol fieldName,
HasField "id" record (Id (NormalizeModel record)),
Show (PrimaryKey (GetTableName (NormalizeModel record))),
tableName ~ GetTableName record, KnownSymbol tableName) =>
Text -> Proxy fieldName -> record -> IO record
uploadImageFile Text
"svg"