wikimusic-api-1.1.0.1: src/WikiMusic/PostgreSQL/ArtistQuery.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module WikiMusic.PostgreSQL.ArtistQuery () where
import Control.Concurrent.Async
import Data.Map (elems, keys)
import Data.Map qualified as Map
import Data.Text (pack)
import Hasql.Decoders as D
import Hasql.Encoders as E
import Hasql.Session qualified as Session
import Hasql.Statement (Statement (..))
import WikiMusic.Free.ArtistQuery
import WikiMusic.Model.Artist
import WikiMusic.Model.Artwork
import WikiMusic.Model.Comment
import WikiMusic.Model.Opinion
import WikiMusic.Model.Other
import WikiMusic.Model.Thread as CommentThread
import WikiMusic.PostgreSQL.Model.Artist
import WikiMusic.PostgreSQL.ReadAbstraction qualified as ReadAbstraction
import WikiMusic.Protolude
instance Exec ArtistQuery where
execAlgebra (FetchArtists env sortOrder limit offset next) =
next =<< fetchArtists' env sortOrder limit offset
execAlgebra (FetchArtistsByUUID env sortOrder identifiers next) =
next =<< fetchArtistsByUUID' env sortOrder identifiers
execAlgebra (EnrichedArtistResponse env artistMap enrichArtistParams next) =
next =<< enrichedArtistResponse' env artistMap enrichArtistParams
execAlgebra (FetchArtistComments env identifiers next) =
next =<< fetchArtistComments' env identifiers
execAlgebra (FetchArtistOpinions env identifiers next) =
next =<< fetchArtistOpinions' env identifiers
execAlgebra (SearchArtists env searchInput sortOrder limit offset next) =
next =<< searchArtists' env searchInput sortOrder limit offset
execAlgebra (FetchArtistArtworks env identifiers next) =
next =<< fetchArtistArtworks' env identifiers
-- | PostgreSQL artist row Hasql decoder for reading from `artists` table
artistRowDecoder :: Result [ArtistRow]
artistRowDecoder =
D.rowList $
(,,,,,,,,,,,,)
<$> D.column (D.nonNullable D.uuid)
<*> D.column (D.nonNullable D.text)
<*> D.column (D.nonNullable D.uuid)
<*> D.column (D.nonNullable D.int8)
<*> D.column (D.nullable D.uuid)
<*> D.column (D.nonNullable D.timestamptz)
<*> D.column (D.nullable D.timestamptz)
<*> D.column (D.nullable D.text)
<*> D.column (D.nullable D.text)
<*> D.column (D.nullable D.text)
<*> D.column (D.nullable D.text)
<*> D.column (D.nonNullable D.int8)
<*> D.column (D.nullable D.text)
-- | Parse a PostgreSQL Hasql row to a domain representation of artist
parseArtistRows :: [ArtistRow] -> [(UUID, Artist)]
parseArtistRows = map parser
where
parser row = let artist = artistFromRow row in (artist ^. #identifier, artist)
-- | Fetch artists from storage, according to a sort order, limit and offset
fetchArtists' :: (MonadIO m) => Env -> ArtistSortOrder -> Limit -> Offset -> m (Map UUID Artist, [UUID])
fetchArtists' env sortOrder (Limit limit) (Offset offset) =
ReadAbstraction.persistenceReadCall
(env ^. #pool)
(Session.statement (fromIntegral limit, fromIntegral offset) stmt)
(\xs -> (fromList xs, map fst xs))
parseArtistRows
where
stmt = Statement query encoder artistRowDecoder True
sortOrder' = fromString . WikiMusic.Model.Artist.show $ sortOrder
query =
encodeUtf8
[untrimming|
SELECT artists.identifier, artists.display_name, artists.created_by,
artists.visibility_status, artists.approved_by, artists.created_at, artists.last_edited_at,
spotify_url, youtube_url, soundcloud_url, wikipedia_url, artists.views, artists.description
FROM artists
LEFT JOIN artist_external_sources ON artist_external_sources.artist_identifier = artists.identifier
ORDER BY $sortOrder'
LIMIT ($$1) OFFSET $$2
|]
encoder =
contrazip2
(E.param . E.nonNullable $ E.int8)
(E.param . E.nonNullable $ E.int8)
-- | Fetch artists by UUID from storage, according to a sort order
fetchArtistsByUUID' :: (MonadIO m) => Env -> ArtistSortOrder -> [UUID] -> m (Map UUID Artist, [UUID])
fetchArtistsByUUID' env sortOrder identifiers =
ReadAbstraction.persistenceReadCall
(env ^. #pool)
(Session.statement identifiers stmt)
(\xs -> (fromList xs, map fst xs))
parseArtistRows
where
stmt = Statement query encoder artistRowDecoder True
sortOrder' = fromString . WikiMusic.Model.Artist.show $ sortOrder
query =
encodeUtf8
[untrimming|
SELECT artists.identifier, artists.display_name, artists.created_by,
artists.visibility_status, artists.approved_by, artists.created_at, artists.last_edited_at,
spotify_url, youtube_url, soundcloud_url, wikipedia_url, artists.views, artists.description
FROM artists
LEFT JOIN artist_external_sources ON artist_external_sources.artist_identifier = artists.identifier
WHERE artists.identifier = ANY($$1) ORDER BY $sortOrder'
|]
encoder = E.param . E.nonNullable $ E.foldableArray . E.nonNullable $ E.uuid
-- | Fetch artist artworks from storage
fetchArtistArtworks' :: (MonadIO m) => Env -> [UUID] -> m (Map UUID ArtistArtwork)
fetchArtistArtworks' env identifiers =
ReadAbstraction.fetchArtworks
(env ^. #pool)
(parseArtworkRows fromRow)
"artist_artworks"
"artist_identifier"
(vectorFromList identifiers)
where
fromRow
( identifier,
artistIdentifier,
createdBy,
visibilityStatus,
approvedBy,
contentUrl,
contentCaption,
createdAt,
lastEditedAt,
orderValue
) =
let artwork =
Artwork
{ identifier = identifier,
createdBy = createdBy,
visibilityStatus = fromIntegral visibilityStatus,
approvedBy = approvedBy,
contentUrl = contentUrl,
contentCaption = contentCaption,
createdAt = createdAt,
lastEditedAt = lastEditedAt,
orderValue = fromIntegral orderValue
}
in ArtistArtwork {..}
-- | Enrich artists with related information, according to enrichment parameters
enrichedArtistResponse' :: (MonadIO m) => Env -> Map UUID Artist -> EnrichArtistParams -> m (Map UUID Artist)
enrichedArtistResponse' env artistMap enrichArtistParams = do
(artworkMap, (opinionMap, commentMap)) <- liftIO $ concurrently getArtwork (concurrently getOpinion getComment)
let enrichedArtists =
mapMap
( \artist -> do
let rawCommentMap = Map.filter (matchesArtistIdentifier artist) commentMap
allComments = elems rawCommentMap
commentThreads = map renderThread $ mkThreads allComments isChildOf' (^. #comment % #parentIdentifier)
artist
{ comments = commentThreads,
artworks = filterMap (matchesArtistIdentifier artist) artworkMap,
opinions = filterMap (matchesArtistIdentifier artist) opinionMap
}
)
artistMap
pure enrichedArtists
where
matchesArtistIdentifier artist = (== artist ^. #identifier) . (^. #artistIdentifier)
isChildOf' p x = Just (p ^. #comment % #identifier) == x ^. #comment % #parentIdentifier
artistIds = keys artistMap
getComment =
if enrichArtistParams ^. #includeComments
then exec @ArtistQuery $ fetchArtistComments env artistIds
else pure $ fromList []
getArtwork =
if enrichArtistParams ^. #includeArtworks
then exec @ArtistQuery $ fetchArtistArtworks env artistIds
else pure $ fromList []
getOpinion =
if enrichArtistParams ^. #includeOpinions
then exec @ArtistQuery $ fetchArtistOpinions env artistIds
else pure $ fromList []
-- | Fetch artist comments from storage
fetchArtistComments' :: (MonadIO m) => Env -> [UUID] -> m (Map UUID ArtistComment)
fetchArtistComments' env identifiers =
ReadAbstraction.fetchComments
(env ^. #pool)
(parseCommentRows fromRow)
"artist_comments"
"artist_identifier"
(vectorFromList identifiers)
where
fromRow
( identifier,
artistIdentifier,
parentIdentifier,
createdBy,
visibilityStatus,
contents,
approvedBy,
createdAt,
lastEditedAt
) =
let comment =
Comment
{ identifier = identifier,
parentIdentifier = parentIdentifier,
createdBy = createdBy,
visibilityStatus = fromIntegral visibilityStatus,
contents = contents,
approvedBy = approvedBy,
createdAt = createdAt,
lastEditedAt = lastEditedAt
}
in ArtistComment {..}
-- | Search artists by keywords from storage, according to a sort order, limit and offset
searchArtists' :: (MonadIO m) => Env -> SearchInput -> ArtistSortOrder -> Limit -> Offset -> m (Map UUID Artist, [UUID])
searchArtists' env searchInput sortOrder (Limit limit) (Offset offset) =
ReadAbstraction.persistenceReadCall
(env ^. #pool)
(Session.statement (fromIntegral limit, fromIntegral offset) stmt)
(\xs -> (fromList xs, map fst xs))
parseArtistRows
where
sortOrder' = pack . WikiMusic.Model.Artist.show $ sortOrder
searchConstraints = ReadAbstraction.mkSearchConstraints (searchInput ^. #value)
query =
encodeUtf8
[untrimming|
SELECT artists.identifier, artists.display_name, artists.created_by,
artists.visibility_status, artists.approved_by, artists.created_at, artists.last_edited_at,
spotify_url, youtube_url, soundcloud_url, wikipedia_url, artists.views, artists.description
FROM artists
LEFT JOIN artist_external_sources ON artist_external_sources.artist_identifier = artists.identifier
WHERE $searchConstraints
ORDER BY $sortOrder'
LIMIT ($$1) OFFSET $$2
|]
encoder =
contrazip2
(E.param . E.nonNullable $ E.int8)
(E.param . E.nonNullable $ E.int8)
stmt = Statement query encoder artistRowDecoder True
-- | Fetch artist opinions from storage
fetchArtistOpinions' :: (MonadIO m) => Env -> [UUID] -> m (Map UUID ArtistOpinion)
fetchArtistOpinions' env identifiers =
ReadAbstraction.fetchOpinions
(env ^. #pool)
(parseOpinionRows fromRow)
"artist_opinions"
"artist_identifier"
(vectorFromList identifiers)
where
fromRow
( identifier,
artistIdentifier,
createdBy,
isLike,
isDislike,
createdAt,
lastEditedAt
) =
let opinion = Opinion {..}
in ArtistOpinion {..}