Skip to content

Commit

Permalink
Matcher: Get Package Metadata from TMDb
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthi-chaud committed Apr 14, 2024
1 parent 2770cfa commit dfea430
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 50 deletions.
3 changes: 3 additions & 0 deletions matcher/matcher.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ library
Matcher
Matcher.API.Client
Matcher.API.Dto
Matcher.API.Dto.Artist
Matcher.API.Dto.BaseExternalId
Matcher.API.Dto.Package
Matcher.API.Event
Matcher.Network
Matcher.TMDB.Client
Expand Down
47 changes: 36 additions & 11 deletions matcher/src/Matcher.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import Matcher.API.Dto
import Matcher.API.Event
import Matcher.TMDB.Client
import Matcher.TMDB.Models (
ArtistDetails (ArtistDetails),
ArtistDetails (..),
ArtistSearchResult (..),
MovieDetails (..),
MovieSearchResult (..),
)
import Prelude hiding (id)

Expand All @@ -18,21 +20,44 @@ handleAPIEvent client tmdb (APIEvent "artist" Insert uuid name) = runExceptT $ d
ExceptT $ getArtistDetails tmdb (identifier artist)
let artistDto =
ArtistExternalId
{ artistId = uuid,
externalId =
BaseExternalId
{ url = "https://www.themoviedb.org/person/" ++ show (identifier artist),
value = show $ identifier artist,
description = artistDescription,
rating = Nothing,
providerName = "TMDB"
}
}
uuid
BaseExternalId
{ url = "https://www.themoviedb.org/person/" ++ show (identifier artist),
value = show $ identifier artist,
description = artistDescription,
rating = Nothing,
providerName = "TMDB"
}
ExceptT $
pushArtistExternalId client artistDto
>> case profilePath artist of
Nothing -> return $ Right ()
Just posterUrl -> runExceptT $ do
posterBytes <- ExceptT $ getPoster tmdb posterUrl
ExceptT $ pushArtistPoster client uuid posterBytes
handleAPIEvent client tmdb (APIEvent "package" Insert uuid name) = runExceptT $ do
package <- ExceptT $ getPackage client uuid
let packageSearchToken = case artist_name package of
Nothing -> name
Just artistName -> artistName ++ " " ++ name
movie <- ExceptT $ searchMovie tmdb packageSearchToken
MovieDetails packageDescription <-
ExceptT $ getMovieDetails tmdb (i movie)
let packageDto =
PackageExternalId
uuid
BaseExternalId
{ url = "https://www.themoviedb.org/movie/" ++ show (i movie),
value = show $ i movie,
description = packageDescription,
rating = (\r -> round (10 * r) :: Int) <$> (vote_average movie),

Check warning on line 53 in matcher/src/Matcher.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in handleAPIEvent in module Matcher: Redundant bracket ▫︎ Found: "(\\ r -> round (10 * r) :: Int) <$> (vote_average movie)" ▫︎ Perhaps: "(\\ r -> round (10 * r) :: Int) <$> vote_average movie"
providerName = "TMDB"
}
ExceptT $
pushPackageExternalId client packageDto
>> case posterPath movie of
Nothing -> return $ Right ()
Just posterUrl -> runExceptT $ do
posterBytes <- ExceptT $ getPoster tmdb posterUrl
ExceptT $ pushPackagePoster client uuid posterBytes
handleAPIEvent _ _ _ = return $ Left "No handler for this event"
28 changes: 25 additions & 3 deletions matcher/src/Matcher/API/Client.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
module Matcher.API.Client (APIClient (..), ping, pushArtistExternalId, pushArtistPoster) where
module Matcher.API.Client (
APIClient (..),
ping,
pushArtistExternalId,
pushPackageExternalId,
pushArtistPoster,
pushPackagePoster,
getArtist,
getPackage,
) where

import Data.Aeson (encode)
import Data.Aeson (eitherDecodeStrict', encode)
import Data.ByteString
import Matcher.API.Dto (ArtistExternalId)
import Matcher.API.Dto (Artist, ArtistExternalId, Package, PackageExternalId)
import Matcher.Network

data APIClient = APIClient
Expand Down Expand Up @@ -47,3 +56,16 @@ pushArtistExternalId client dto = (() <$) <$> apiPost client "/external_ids" (to
pushArtistPoster :: APIClient -> String -> ByteString -> IO (Either String ())
pushArtistPoster client uuid posterBytes =
(() <$) <$> apiPostBinary client ("/artists/" ++ uuid ++ "/poster") posterBytes

getArtist :: APIClient -> String -> IO (Either String Artist)
getArtist client uuid = (eitherDecodeStrict' =<<) <$> apiRequest client ("/artists/" ++ uuid) []

getPackage :: APIClient -> String -> IO (Either String Package)
getPackage client uuid = (eitherDecodeStrict' =<<) <$> apiRequest client ("/packages/" ++ uuid) []

pushPackageExternalId :: APIClient -> PackageExternalId -> IO (Either String ())
pushPackageExternalId client dto = (() <$) <$> apiPost client "/external_ids" (toStrict $ encode dto)

pushPackagePoster :: APIClient -> String -> ByteString -> IO (Either String ())
pushPackagePoster client uuid posterBytes =
(() <$) <$> apiPostBinary client ("/packages/" ++ uuid ++ "/poster") posterBytes
39 changes: 11 additions & 28 deletions matcher/src/Matcher/API/Dto.hs
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
module Matcher.API.Dto (ArtistExternalId (..), BaseExternalId (..)) where

import Data.Aeson.Types

data BaseExternalId = BaseExternalId
{ url :: String,
value :: String,
description :: Maybe String,
rating :: Maybe Int,
providerName :: String
}

jsonPairs :: BaseExternalId -> [Pair]
jsonPairs i =
[ "url" .= url i,
"value" .= value i,
"description" .= description i,
"rating" .= rating i,
"provider_name" .= providerName i
]

data ArtistExternalId = ArtistExternalId
{ artistId :: String,
externalId :: BaseExternalId
}

instance ToJSON ArtistExternalId where
toJSON (ArtistExternalId i d) = object $ ("artist_id" .= i) : jsonPairs d
module Matcher.API.Dto (
ArtistExternalId (..),
BaseExternalId (..),
Artist (..),
Package (..),
PackageExternalId (..),
) where

import Matcher.API.Dto.Artist
import Matcher.API.Dto.BaseExternalId
import Matcher.API.Dto.Package
20 changes: 20 additions & 0 deletions matcher/src/Matcher/API/Dto/Artist.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Matcher.API.Dto.Artist (ArtistExternalId (..), Artist (..)) where

import Data.Aeson.Types
import GHC.Generics (Generic)
import Matcher.API.Dto.BaseExternalId

data ArtistExternalId = ArtistExternalId
{ artistId :: String,
artistExternalId :: BaseExternalId
}

instance ToJSON ArtistExternalId where
toJSON (ArtistExternalId i d) = object $ ("artist_id" .= i) : jsonPairs d

data Artist = Artist

Check warning on line 15 in matcher/src/Matcher/API/Dto/Artist.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in Artist in module Matcher.API.Dto.Artist: Use newtype instead of data ▫︎ Found: "data Artist\n = Artist {id :: String}\n deriving (Generic, Show)" ▫︎ Perhaps: "newtype Artist\n = Artist {id :: String}\n deriving (Generic, Show)" ▫︎ Note: decreases laziness
{ id :: String
}
deriving (Generic, Show)

instance FromJSON Artist
20 changes: 20 additions & 0 deletions matcher/src/Matcher/API/Dto/BaseExternalId.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Matcher.API.Dto.BaseExternalId (BaseExternalId (..), jsonPairs) where

import Data.Aeson.Types

data BaseExternalId = BaseExternalId
{ url :: String,
value :: String,
description :: Maybe String,
rating :: Maybe Int,
providerName :: String
}

jsonPairs :: BaseExternalId -> [Pair]
jsonPairs i =
[ "url" .= url i,
"value" .= value i,
"description" .= description i,
"rating" .= rating i,
"provider_name" .= providerName i
]
20 changes: 20 additions & 0 deletions matcher/src/Matcher/API/Dto/Package.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Matcher.API.Dto.Package (Package (..), PackageExternalId (..)) where

import Data.Aeson.Types
import GHC.Generics (Generic)
import Matcher.API.Dto.BaseExternalId

data Package = Package

Check warning on line 7 in matcher/src/Matcher/API/Dto/Package.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in Package in module Matcher.API.Dto.Package: Use newtype instead of data ▫︎ Found: "data Package\n = Package {artist_name :: Maybe String}\n deriving (Generic, Show)" ▫︎ Perhaps: "newtype Package\n = Package {artist_name :: Maybe String}\n deriving (Generic, Show)" ▫︎ Note: decreases laziness
{ artist_name :: Maybe String
}
deriving (Generic, Show)

instance FromJSON Package

data PackageExternalId = PackageExternalId
{ packageId :: String,
packageExternalId :: BaseExternalId
}

instance ToJSON PackageExternalId where
toJSON (PackageExternalId i d) = object $ ("package_id" .= i) : jsonPairs d
44 changes: 43 additions & 1 deletion matcher/src/Matcher/TMDB/Client.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module Matcher.TMDB.Client (TMDBClient (..), searchArtist, getArtistDetails, getPoster) where
module Matcher.TMDB.Client (
TMDBClient (..),
searchArtist,
getArtistDetails,
getPoster,
searchMovie,
getMovieDetails,
) where

import Data.Aeson (eitherDecodeStrict')
import Data.ByteString (ByteString)
Expand Down Expand Up @@ -52,6 +59,30 @@ searchArtist client token = do
}
)

searchMovie :: TMDBClient -> String -> IO (Either String MovieSearchResult)
searchMovie client token = do
searchRawRes <-
tmdbRequest
client
"/search/movie"
[ ("query", token),
("page", "1"),
("include_adult", "false"),
("language", "en-US")
]
return $
searchRawRes
>>= ( \s -> do
page <- eitherDecodeStrict' s :: Either String (Page MovieSearchResult)
case results page of
[] -> Left "Empty Page"
(a : _) ->
return $
a
{ posterPath = ("https://image.tmdb.org/t/p/original" ++) <$> posterPath a
}
)

getArtistDetails :: TMDBClient -> Integer -> IO (Either String ArtistDetails)
getArtistDetails client artistId = do
rawRes <-
Expand All @@ -63,6 +94,17 @@ getArtistDetails client artistId = do
rawRes
>>= eitherDecodeStrict'

getMovieDetails :: TMDBClient -> Integer -> IO (Either String MovieDetails)
getMovieDetails client movieId = do
rawRes <-
tmdbRequest
client
("/movie/" <> show movieId)
[]
return $
rawRes
>>= eitherDecodeStrict'

getPoster :: TMDBClient -> String -> IO (Either String ByteString)
getPoster client posterurl =
request
Expand Down
44 changes: 37 additions & 7 deletions matcher/src/Matcher/TMDB/Models.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
module Matcher.TMDB.Models (Page (..), ArtistSearchResult (..), ArtistDetails (..)) where
module Matcher.TMDB.Models (
Page (..),
ArtistSearchResult (..),
ArtistDetails (..),
MovieSearchResult (..),
MovieDetails (..),
) where

import Data.Aeson

Expand All @@ -9,6 +15,14 @@ data ArtistSearchResult = ArtistSearchResult
profilePath :: Maybe String
}

instance FromJSON ArtistSearchResult where
parseJSON = withObject "TMDB Artist" $ \v ->
ArtistSearchResult
<$> v .: "id"
<*> v .: "name"
<*> v .: "original_name"
<*> v .: "profile_path"

data ArtistDetails = ArtistDetails

Check warning on line 26 in matcher/src/Matcher/TMDB/Models.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in ArtistDetails in module Matcher.TMDB.Models: Use newtype instead of data ▫︎ Found: "data ArtistDetails = ArtistDetails {biography :: Maybe String}" ▫︎ Perhaps: "newtype ArtistDetails = ArtistDetails {biography :: Maybe String}" ▫︎ Note: decreases laziness
{ biography :: Maybe String
}
Expand All @@ -25,10 +39,26 @@ newtype Page a = Page
instance (FromJSON a) => FromJSON (Page a) where
parseJSON = withObject "Page" $ \v -> Page <$> v .: "results"

instance FromJSON ArtistSearchResult where
parseJSON = withObject "TMDB Artist" $ \v ->
ArtistSearchResult
data MovieSearchResult = MovieSearchResult
{ i :: Integer,
title :: String,
vote_average :: Maybe Double,
posterPath :: Maybe String
}

data MovieDetails = MovieDetails

Check warning on line 49 in matcher/src/Matcher/TMDB/Models.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in MovieDetails in module Matcher.TMDB.Models: Use newtype instead of data ▫︎ Found: "data MovieDetails = MovieDetails {overview :: Maybe String}" ▫︎ Perhaps: "newtype MovieDetails = MovieDetails {overview :: Maybe String}" ▫︎ Note: decreases laziness
{ overview :: Maybe String
}

instance FromJSON MovieSearchResult where
parseJSON = withObject "TMDB Movie" $ \v ->
MovieSearchResult
<$> v .: "id"
<*> v .: "name"
<*> v .: "original_name"
<*> v .: "profile_path"
<*> v .: "title"
<*> v .: "vote_average"
<*> v .: "poster_path"

instance FromJSON MovieDetails where
parseJSON = withObject "TMDB Movie Details" $ \v ->
MovieDetails
<$> v .: "overview"
22 changes: 22 additions & 0 deletions matcher/test/Matcher/TestTMDB.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,25 @@ specs = describe "TMDB" $ do
take 10 description `shouldBe` "Madonna (b"
reverse (take 10 $ reverse description) `shouldBe` " industry."
)
describe "Search Package" $ do
it "Should Get Package" $ do
searchMovie tmdbClient "The Corrs - Live at Lansdowne Road"
>>= ( \case
Left e -> expectationFailure e
Right res -> do
i res `shouldBe` 2188
title res `shouldBe` "The Corrs: Live at Lansdowne Road"
vote_average res `shouldBe` (Just 7.7)

Check warning on line 51 in matcher/test/Matcher/TestTMDB.hs

View workflow job for this annotation

GitHub Actions / Lint

Suggestion in specs in module Matcher.TestTMDB: Redundant bracket ▫︎ Found: "vote_average res `shouldBe` (Just 7.7)" ▫︎ Perhaps: "vote_average res `shouldBe` Just 7.7"
posterPath res
`shouldBe` Just "https://image.tmdb.org/t/p/original/ApXQQS8peDN9wzXhpU30xzFH5TN.jpg"
)
describe "Get Artist Details" $ do
it "Should Get Package Details" $ do
getMovieDetails tmdbClient 2188
>>= ( \case
Left e -> expectationFailure e
Right (MovieDetails Nothing) -> expectationFailure "No description found"
Right (MovieDetails (Just description)) -> do
take 10 description `shouldBe` "Irish-Celt"
reverse (take 10 $ reverse description) `shouldBe` "by fields."
)

0 comments on commit dfea430

Please sign in to comment.