2015-01-18 19:44:41 +00:00
|
|
|
-- eidolon -- A simple gallery in Haskell and Yesod
|
|
|
|
-- Copyright (C) 2015 Amedeo Molnár
|
|
|
|
--
|
|
|
|
-- This program is free software: you can redistribute it and/or modify
|
|
|
|
-- it under the terms of the GNU Affero General Public License as published
|
|
|
|
-- by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
-- (at your option) any later version.
|
|
|
|
--
|
|
|
|
-- This program is distributed in the hope that it will be useful,
|
|
|
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
-- GNU Affero General Public License for more details.
|
|
|
|
--
|
|
|
|
-- You should have received a copy of the GNU Affero General Public License
|
2015-01-21 09:00:18 +00:00
|
|
|
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2015-01-18 19:44:41 +00:00
|
|
|
|
2014-12-27 23:30:53 +00:00
|
|
|
module Helper where
|
2014-08-13 22:52:57 +00:00
|
|
|
|
|
|
|
import Prelude
|
2014-12-19 20:59:55 +00:00
|
|
|
import Yesod.Static
|
2014-08-15 11:24:14 +00:00
|
|
|
import Model
|
|
|
|
import Data.Maybe
|
2015-01-11 07:06:48 +00:00
|
|
|
import Data.List as L
|
2014-08-24 19:42:42 +00:00
|
|
|
import qualified Data.ByteString as B
|
|
|
|
import qualified Data.ByteString.Lazy as BL
|
|
|
|
import qualified Data.Text as T
|
2016-08-23 18:06:35 +00:00
|
|
|
|
2014-12-14 19:21:23 +00:00
|
|
|
import Data.Time
|
|
|
|
import Data.Char
|
2016-08-23 18:06:35 +00:00
|
|
|
|
2014-08-15 11:24:14 +00:00
|
|
|
import Database.Persist
|
2014-08-24 19:42:42 +00:00
|
|
|
import System.Random
|
2014-08-30 18:23:57 +00:00
|
|
|
import Yesod
|
2014-08-24 19:42:42 +00:00
|
|
|
import Numeric (readHex, showHex)
|
2014-09-03 23:15:27 +00:00
|
|
|
import Network.Mail.Mime
|
|
|
|
import Text.Blaze.Html.Renderer.Utf8
|
2014-08-13 22:52:57 +00:00
|
|
|
|
2016-08-23 16:20:30 +00:00
|
|
|
import Codec.Picture
|
|
|
|
|
2014-08-24 19:42:42 +00:00
|
|
|
getUserIdFromText :: T.Text -> UserId
|
2014-08-13 22:52:57 +00:00
|
|
|
getUserIdFromText tempUserId =
|
2014-12-23 04:01:53 +00:00
|
|
|
case key of
|
|
|
|
Left a ->
|
|
|
|
error $ T.unpack a
|
|
|
|
Right k ->
|
|
|
|
k
|
|
|
|
where
|
|
|
|
key = keyFromValues $ pInt64 : []
|
2015-09-26 03:27:54 +00:00
|
|
|
pInt64 = PersistInt64 $ fromIntegral (read $ T.unpack tempUserId :: Integer)
|
2014-08-14 00:25:18 +00:00
|
|
|
|
2014-12-23 04:01:53 +00:00
|
|
|
extractKey :: PersistEntity record => Key record -> T.Text
|
|
|
|
extractKey = extractKey' . keyToValues
|
2014-08-14 00:25:18 +00:00
|
|
|
where
|
2014-12-23 04:01:53 +00:00
|
|
|
extractKey' [PersistInt64 k] = T.pack $ show k
|
2014-08-14 00:25:18 +00:00
|
|
|
extractKey' _ = ""
|
2014-08-15 11:24:14 +00:00
|
|
|
|
2015-10-16 19:57:18 +00:00
|
|
|
packKey :: PersistEntity record => T.Text -> Key record
|
|
|
|
packKey = keyFromValues' . readText
|
|
|
|
where
|
2016-08-23 18:06:35 +00:00
|
|
|
readText t = PersistInt64 (fromIntegral $ read $ T.unpack t)
|
2015-10-16 19:57:18 +00:00
|
|
|
keyFromValues' v = case keyFromValues [v] of
|
|
|
|
Left err -> error $ T.unpack err
|
|
|
|
Right k -> k
|
|
|
|
|
2014-12-23 04:01:53 +00:00
|
|
|
fromHex :: String -> BL.ByteString
|
2014-08-24 19:42:42 +00:00
|
|
|
fromHex = BL.pack . hexToWords
|
|
|
|
where hexToWords (c:c':text) =
|
|
|
|
let hex = [c, c']
|
2014-08-26 02:37:56 +00:00
|
|
|
(word, _):_ = readHex hex
|
2014-08-24 19:42:42 +00:00
|
|
|
in word : hexToWords text
|
|
|
|
hexToWords _ = []
|
|
|
|
|
|
|
|
-- strict variant
|
|
|
|
|
|
|
|
fromHex' :: String -> B.ByteString
|
|
|
|
fromHex' = B.concat . BL.toChunks . fromHex
|
|
|
|
|
|
|
|
toHex :: B.ByteString -> T.Text
|
|
|
|
toHex = T.pack . concatMap mapByte . B.unpack
|
|
|
|
where mapByte = pad 2 '0' . flip showHex ""
|
|
|
|
pad len padding s
|
|
|
|
| length s < len = pad len padding $ padding:s
|
|
|
|
| otherwise = s
|
|
|
|
|
|
|
|
makeRandomToken :: IO T.Text
|
|
|
|
makeRandomToken = (T.pack . take 16 . randoms) `fmap` newStdGen
|
|
|
|
|
|
|
|
generateSalt :: IO B.ByteString
|
|
|
|
generateSalt = (B.pack . take 8 . randoms) <$> getStdGen
|
2014-08-30 18:23:57 +00:00
|
|
|
|
2014-12-06 03:39:24 +00:00
|
|
|
tagField :: Monad m => Field m [T.Text]
|
2014-08-30 18:23:57 +00:00
|
|
|
tagField = Field
|
|
|
|
{ fieldParse = \rawVals _ -> do
|
|
|
|
case rawVals of
|
2015-01-11 07:06:48 +00:00
|
|
|
[x] -> case L.null [x] of
|
2014-09-21 17:42:18 +00:00
|
|
|
False -> return $ Right $ Just $ removeItem "" $ T.splitOn " " x
|
2014-09-14 03:04:13 +00:00
|
|
|
True -> return $ Right $ Nothing
|
2014-08-30 18:23:57 +00:00
|
|
|
_ -> return $ Left $ error "unexpected tag list"
|
2015-10-19 06:32:47 +00:00
|
|
|
, fieldView = \idAttr nameAttr attrs eResult _ ->
|
|
|
|
[whamlet|<input id=#{idAttr} type="text" name=#{nameAttr} *{attrs} value=#{either id (T.intercalate " ") eResult}>|]
|
2014-08-30 18:23:57 +00:00
|
|
|
, fieldEnctype = UrlEncoded
|
|
|
|
}
|
2014-09-03 23:15:27 +00:00
|
|
|
|
2014-12-06 03:39:24 +00:00
|
|
|
userField :: Monad m => [(T.Text, UserId)] -> Field m [UserId]
|
|
|
|
userField users = Field
|
|
|
|
{ fieldParse = \rawVals _ -> do
|
|
|
|
case rawVals of
|
|
|
|
[x] -> case x == "" of
|
|
|
|
False ->
|
|
|
|
-- clean = removeItem "" $ T.splitOn " " x
|
|
|
|
let ids = map (\u -> lookup u users) (removeItem "" $ T.splitOn " " x)
|
|
|
|
in case Nothing `elem` ids of
|
2014-12-06 13:03:13 +00:00
|
|
|
False -> return $ Right $ Just $ nub $ map fromJust ids
|
2014-12-06 03:39:24 +00:00
|
|
|
True -> return $ Left $ error "Invalid username list"
|
|
|
|
True -> return $ Right $ Just $ []
|
|
|
|
_ -> return $ Left $ error "unexpected username list"
|
2015-10-19 06:32:47 +00:00
|
|
|
, fieldView = \idAttr nameAttr attrs eResult _ ->
|
|
|
|
[whamlet|<input id=#{idAttr} type="text" name=#{nameAttr} *{attrs} value=#{either id (getUsersFromResult users) eResult}>|]
|
2014-12-06 03:39:24 +00:00
|
|
|
, fieldEnctype = UrlEncoded
|
|
|
|
}
|
|
|
|
|
2014-12-23 04:01:53 +00:00
|
|
|
getUsersFromResult :: Eq b => [(T.Text, b)] -> [b] -> T.Text
|
2014-12-06 03:39:24 +00:00
|
|
|
getUsersFromResult users res = T.intercalate " " $ map (\x -> fromMaybe "" $ reverseLookup x users) res
|
|
|
|
|
2014-09-03 23:15:27 +00:00
|
|
|
sendMail :: MonadIO m => T.Text -> T.Text -> Html -> m ()
|
|
|
|
sendMail toEmail subject body =
|
|
|
|
liftIO $ renderSendMail
|
|
|
|
Mail
|
|
|
|
{ mailFrom = Address Nothing "noreply" -- TODO: set sender Address
|
|
|
|
, mailTo = [Address Nothing toEmail]
|
|
|
|
, mailCc = []
|
|
|
|
, mailBcc = []
|
|
|
|
, mailHeaders = [("Subject", subject)]
|
|
|
|
, mailParts = [[Part
|
|
|
|
{ partType = "text/html; charset=utf-8"
|
|
|
|
, partEncoding = None
|
|
|
|
, partFilename = Nothing
|
|
|
|
, partHeaders = []
|
|
|
|
, partContent = renderHtml body
|
|
|
|
}]]
|
|
|
|
}
|
|
|
|
|
|
|
|
generateString :: IO T.Text
|
|
|
|
generateString = (toHex . B.pack . take 16 . randoms) <$> newStdGen
|
2014-09-07 04:57:53 +00:00
|
|
|
|
|
|
|
removeItem :: Eq a => a -> [a] -> [a]
|
|
|
|
removeItem _ [] = []
|
|
|
|
removeItem x (y:ys)
|
|
|
|
| x == y = removeItem x ys
|
|
|
|
| otherwise = y : removeItem x ys
|
2014-12-03 21:01:57 +00:00
|
|
|
|
2014-12-06 03:39:24 +00:00
|
|
|
reverseLookup :: Eq b => b -> [(a, b)] -> Maybe a
|
|
|
|
reverseLookup s ((x, y):zs)
|
|
|
|
| s == y = Just x
|
|
|
|
| s /= y = reverseLookup s zs
|
2016-08-23 18:06:35 +00:00
|
|
|
reverseLookup _ [] = Nothing
|
|
|
|
reverseLookup _ _ = Nothing
|
2014-12-06 03:39:24 +00:00
|
|
|
|
2014-12-03 21:01:57 +00:00
|
|
|
acceptedTypes :: [T.Text]
|
2016-10-21 21:20:22 +00:00
|
|
|
acceptedTypes =
|
|
|
|
-- PNG
|
|
|
|
[ "image/png"
|
|
|
|
-- BMP
|
|
|
|
, "image/x-ms-bmp"
|
|
|
|
, "image/x-bmp"
|
|
|
|
, "image/bmp"
|
|
|
|
-- JPG
|
|
|
|
, "image/jpeg"
|
|
|
|
, "image/jpg"
|
|
|
|
-- GIF
|
|
|
|
, "image/gif"
|
|
|
|
-- Radiance
|
|
|
|
, "image/vnd.radiance"
|
|
|
|
-- Targa
|
|
|
|
, "image/x-targa"
|
|
|
|
, "image/x-tga"
|
|
|
|
-- Tiff
|
|
|
|
, "image/tiff"
|
|
|
|
, "image/tiff-fx"
|
|
|
|
-- SVG
|
|
|
|
, "image/svg+xml"
|
2017-10-31 08:56:54 +00:00
|
|
|
-- PDF
|
|
|
|
, "application/pdf"
|
2016-10-21 21:20:22 +00:00
|
|
|
]
|
2014-12-14 19:21:23 +00:00
|
|
|
|
|
|
|
iso8601 :: FormatTime t => t -> String
|
|
|
|
iso8601 time =
|
|
|
|
formatTime defaultTimeLocale (iso8601DateFormat $ Just "%H:%M:%S") time ++
|
|
|
|
zone
|
|
|
|
where zone = case formatTime defaultTimeLocale "%z" time of
|
|
|
|
(sig:digits@(h1:h2:m1:m2))
|
2015-09-25 21:31:37 +00:00
|
|
|
| sig `elem` ("+-" :: String) &&
|
2014-12-14 19:21:23 +00:00
|
|
|
all isDigit digits ->
|
|
|
|
sig:h1:h2:':':m1:m2
|
|
|
|
_ ->
|
|
|
|
"Z"
|
|
|
|
|
|
|
|
localTimeToZonedTime :: TimeZone -> LocalTime -> ZonedTime
|
|
|
|
localTimeToZonedTime tz =
|
|
|
|
utcToZonedTime tz . localTimeToUTC tz
|
|
|
|
|
2014-12-23 04:01:53 +00:00
|
|
|
rfc822 :: FormatTime t => t -> String
|
2014-12-14 19:21:23 +00:00
|
|
|
rfc822 = formatTime defaultTimeLocale rfc822DateFormat
|
2014-12-19 20:59:55 +00:00
|
|
|
|
|
|
|
mediumStaticImageRoute :: Medium -> Route Static
|
|
|
|
mediumStaticImageRoute medium =
|
|
|
|
StaticRoute (drop 2 $ T.splitOn "/" $ T.pack $ mediumPath medium) []
|
|
|
|
|
|
|
|
mediumStaticThumbRoute :: Medium -> Route Static
|
|
|
|
mediumStaticThumbRoute medium =
|
|
|
|
StaticRoute (drop 2 $ T.splitOn "/" $ T.pack $ mediumThumb medium) []
|
2015-01-11 07:06:48 +00:00
|
|
|
|
2015-09-26 03:27:54 +00:00
|
|
|
getThumbWidth :: MonadIO m => Maybe String -> m Int
|
2015-01-11 07:06:48 +00:00
|
|
|
getThumbWidth path
|
|
|
|
| path == Nothing = pure 230
|
2016-08-23 16:20:30 +00:00
|
|
|
| otherwise = liftIO $ do
|
|
|
|
eorig <- readImage $ fromJust path
|
|
|
|
case eorig of
|
|
|
|
Right orig -> do
|
|
|
|
let img = convertRGBA8 orig
|
|
|
|
return $ imageWidth img
|
|
|
|
Left e ->
|
|
|
|
error e
|
2015-01-16 00:23:02 +00:00
|
|
|
|
2016-08-23 18:06:35 +00:00
|
|
|
multiFileField :: (Monad m) => Field m [FileInfo]
|
2015-01-16 00:23:02 +00:00
|
|
|
multiFileField = Field
|
|
|
|
{ fieldParse = \_ files -> return $
|
|
|
|
case files of
|
|
|
|
[] -> Right Nothing
|
2016-08-23 18:06:35 +00:00
|
|
|
_:_ -> Right $ Just files
|
2015-01-16 00:23:02 +00:00
|
|
|
, fieldView = \id' name attrs _ isReq -> toWidget [hamlet|
|
|
|
|
<input id=#{id'} name=#{name} *{attrs} type=file :isReq:required multiple="" enctype="multipart/form-data">
|
|
|
|
|]
|
|
|
|
, fieldEnctype = Multipart
|
|
|
|
}
|