2023-08-10 00:55:07 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2023-08-10 01:56:19 +02:00
|
|
|
{- TODO: refactor code into multiple modules -}
|
2023-08-10 00:55:07 +02:00
|
|
|
module ICAFocus where
|
|
|
|
|
|
|
|
import qualified Network.HTTP.Client as H
|
|
|
|
import qualified Network.HTTP.Client.TLS as H
|
|
|
|
import Network.HTTP.Types.Status
|
|
|
|
import Data.Aeson
|
|
|
|
import Data.Aeson.Types
|
|
|
|
import Data.Aeson.KeyMap
|
|
|
|
import Data.Text
|
|
|
|
import Text.Read
|
|
|
|
import Data.ByteString.Lazy
|
2023-08-10 01:56:19 +02:00
|
|
|
import Control.Monad.Except
|
2023-08-10 00:55:07 +02:00
|
|
|
|
|
|
|
newtype ImageLink = ImageLink { getLink :: String }
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
newtype ImageData = ImageData { getData :: ByteString }
|
|
|
|
instance Show ImageData where
|
|
|
|
show _ = "ImageData { ... }"
|
|
|
|
|
|
|
|
data ICAFocusItem a = ICAFocusItem
|
|
|
|
{ name :: Text
|
|
|
|
, image :: a
|
|
|
|
, pricePerUnit :: Float
|
|
|
|
, pricePerKg :: Float
|
|
|
|
}
|
|
|
|
deriving (Show, Functor)
|
|
|
|
|
|
|
|
type ICAFocusData = ICAFocusItem ImageLink
|
|
|
|
type ICAFocusProduct = ICAFocusItem ImageData
|
|
|
|
|
2023-08-10 01:56:19 +02:00
|
|
|
unpackEither :: MonadFail m => Either String a -> m a
|
|
|
|
unpackEither (Left s) = fail s
|
|
|
|
unpackEither (Right a) = pure a
|
|
|
|
|
|
|
|
apiEndpoint :: Int -> String
|
|
|
|
apiEndpoint i = "https://handlaprivatkund.ica.se/stores/1004247/api/v4/products/bop?retailerProductId=" ++ show i
|
|
|
|
|
|
|
|
(.::) :: FromJSON a => Parser Object -> Key -> Parser a
|
|
|
|
v .:: k = v >>= (.: k)
|
|
|
|
|
2023-08-10 00:55:07 +02:00
|
|
|
instance FromJSON ICAFocusData where
|
|
|
|
parseJSON (Object v) = do
|
2023-08-10 01:56:19 +02:00
|
|
|
productMap <- v .: "entities" .:: "product"
|
2023-08-10 00:55:07 +02:00
|
|
|
product <- case elems productMap of
|
|
|
|
[Object x] -> pure x
|
|
|
|
[] -> fail "No products"
|
|
|
|
_ -> fail "Too many products"
|
|
|
|
|
2023-08-10 01:56:19 +02:00
|
|
|
ICAFocusItem
|
|
|
|
<$> product .: "name"
|
|
|
|
<*> (ImageLink <$> product .: "image" .:: "src")
|
|
|
|
<*> (unpackEither . readEither =<< product .: "price" .:: "unit" .:: "current" .:: "amount")
|
|
|
|
<*> (unpackEither . readEither =<< product .: "price" .:: "current" .:: "amount")
|
2023-08-10 00:55:07 +02:00
|
|
|
|
2023-08-10 01:56:19 +02:00
|
|
|
loadProductData :: Int -> ExceptT String IO ICAFocusData
|
2023-08-10 00:55:07 +02:00
|
|
|
loadProductData product_id = do
|
2023-08-10 01:56:19 +02:00
|
|
|
req <- H.parseRequest $ apiEndpoint product_id
|
|
|
|
resp <- liftIO $ H.httpLbs req =<< H.newManager H.tlsManagerSettings
|
|
|
|
|
|
|
|
unless (H.responseStatus resp == status200) (throwError "Error status code")
|
2023-08-10 00:55:07 +02:00
|
|
|
|
2023-08-10 01:56:19 +02:00
|
|
|
liftEither . eitherDecode $ H.responseBody resp
|
2023-08-10 00:55:07 +02:00
|
|
|
|
2023-08-10 01:56:19 +02:00
|
|
|
loadProductImage :: ICAFocusData -> ExceptT String IO ICAFocusProduct
|
2023-08-10 00:55:07 +02:00
|
|
|
loadProductImage x = do
|
|
|
|
req <- H.parseRequest $ (getLink . image) x
|
2023-08-10 01:56:19 +02:00
|
|
|
resp <- liftIO $ H.httpLbs req =<< H.newManager H.tlsManagerSettings
|
|
|
|
|
|
|
|
unless (H.responseStatus resp == status200) (throwError "Error status code")
|
|
|
|
pure $ const (ImageData (H.responseBody resp)) <$> x
|
|
|
|
|
|
|
|
loadProduct :: Int -> ExceptT String IO ICAFocusProduct
|
|
|
|
loadProduct = loadProductData >=> loadProductImage
|