packages feed

wikimusic-api-1.2.0.8: src/WikiMusic/Sqlite/SongCommand.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module WikiMusic.Sqlite.SongCommand () where

import Data.Map qualified as Map
import Data.UUID qualified as UUID
import Database.Beam
import Database.Beam.Sqlite
import Relude
import WikiMusic.Beam.Artist
import WikiMusic.Beam.Database
import WikiMusic.Beam.Relations
import WikiMusic.Beam.Song
import WikiMusic.Free.SongCommand
import WikiMusic.Interaction.Model.Song
import WikiMusic.Model.Artwork
import WikiMusic.Model.Comment
import WikiMusic.Model.Opinion
import WikiMusic.Model.Song
import WikiMusic.Protolude

insertArtistsOfSongs' :: (MonadIO m) => Env -> [ArtistOfSong] -> m (Map UUID ArtistOfSong)
insertArtistsOfSongs' env items = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songArtists) wikiMusicDatabase)
    $ insertValues (map mkSongArtistP items)
  pure Map.empty

insertSongs' :: (MonadIO m) => Env -> [Song] -> m (Map UUID Song)
insertSongs' env songs = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songs) wikiMusicDatabase)
    $ insertValues (map toPersistenceSong songs)

  externalContents <-
    mapM
      ( \s -> do
          newIdentifier <- liftIO nextRandom
          pure $ toPersistenceSongExternalContents s newIdentifier
      )
      songs
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songExternalSources) wikiMusicDatabase)
    $ insertValues externalContents
  pure Map.empty

insertSongComments' :: (MonadIO m) => Env -> [SongComment] -> m (Either SongCommandError ())
insertSongComments' env comments = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songComments) wikiMusicDatabase)
    $ insertValues (map toPersistenceSongComment comments)
  pure . Right $ ()

insertSongExternalSources' :: (MonadIO m) => Env -> [SongExternalSources] -> m (Map UUID SongExternalSources)
insertSongExternalSources' env externalSources = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songExternalSources) wikiMusicDatabase)
    $ insertValues (map toPersistenceSongExternalSources externalSources)
  pure Map.empty

insertSongArtworks' :: (MonadIO m) => Env -> [SongArtwork] -> m (Map UUID SongArtwork)
insertSongArtworks' env artworks = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songArtworks) wikiMusicDatabase)
    $ insertValues (map mkSongArtworkP artworks)
  pure Map.empty

upsertSongOpinions' :: (MonadIO m) => Env -> [SongOpinion] -> m (Map UUID SongOpinion)
upsertSongOpinions' env opinions = do
  mapM_
    ( \o -> do
        exOpinion <- liftIO $ runBeamSqliteDebug putStrLn (env ^. #conn) $ do
          runSelectReturningOne $ select $ do
            filter_
              ( \s ->
                  (s ^. #songIdentifier)
                    ==. val_ (SongId $ UUID.toText $ o ^. #songIdentifier)
                    &&. (s ^. #createdBy)
                    ==. val_ (UUID.toText $ o ^. #opinion % #createdBy)
              )
              $ all_ ((^. #songOpinions) wikiMusicDatabase)
        case exOpinion of
          Nothing ->
            liftIO
              . runBeamSqliteDebug putStrLn (env ^. #conn)
              . runInsert
              . insert ((^. #songOpinions) wikiMusicDatabase)
              $ insertValues [mkSongOpinionP o]
          Just oo -> do
            let newO =
                  ( oo
                      { isLike = o ^. #opinion % #isLike,
                        isDislike = o ^. #opinion % #isDislike,
                        lastEditedAt = o ^. #opinion % #lastEditedAt
                      }
                  ) ::
                    SongOpinion'
            liftIO
              . runBeamSqliteDebug putStrLn (env ^. #conn)
              . runUpdate
              $ save ((^. #songOpinions) wikiMusicDatabase) newO
        pure ()
    )
    opinions

  pure Map.empty

uberDeleteSongs' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
uberDeleteSongs' env identifiers =
  do
    deleteArtworksOfSongsResult <- liftIO . exec @SongCommand $ deleteArtworksOfSongs env identifiers
    deleteOpinionsOfSongsResult <- liftIO . exec @SongCommand $ deleteOpinionsOfSongs env identifiers
    deleteCommentsOfSongsResult <- liftIO . exec @SongCommand $ deleteCommentsOfSongs env identifiers
    deleteSongExternalSourcesResult <- liftIO . exec @SongCommand $ deleteSongExternalSources env identifiers
    deleteSongsResult <- doDeleteSongs' env identifiers
    deleteArtistsOfSongResult <- liftIO . exec @SongCommand $ deleteArtistsOfSongs env identifiers
    deleteContentsOfSongResult <- liftIO . exec @SongCommand $ deleteContentsOfSongs env identifiers
    pure
      $ deleteArtworksOfSongsResult
      <> deleteOpinionsOfSongsResult
      <> deleteSongExternalSourcesResult
      <> deleteCommentsOfSongsResult
      <> deleteArtistsOfSongResult
      <> deleteContentsOfSongResult
      <> deleteSongsResult

doDeleteSongs' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
doDeleteSongs' env identifiers = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runDelete
    $ delete
      ((^. #songs) wikiMusicDatabase)
      (\c -> (c ^. #identifier) `in_` map (val_ . UUID.toText) identifiers)
  pure . Right $ ()

updateSongArtworkOrder' :: (MonadIO m) => Env -> [SongArtworkOrderUpdate] -> m (Either a ())
updateSongArtworkOrder' env orderUpdates = do
  mapM_
    ( \ou -> do
        art <- liftIO $ runBeamSqliteDebug putStrLn (env ^. #conn) $ do
          runSelectReturningOne $ select $ do
            filter_
              (\s -> (s ^. #identifier) ==. val_ (UUID.toText $ ou ^. #identifier))
              $ all_ ((^. #songArtworks) wikiMusicDatabase)
        case art of
          Nothing -> pure ()
          Just foundArt -> do
            let a = foundArt {orderValue = fromIntegral $ ou ^. #orderValue} :: SongArtwork'
            liftIO . runBeamSqliteDebug putStrLn (env ^. #conn) . runUpdate . save ((^. #songArtworks) wikiMusicDatabase) $ a
    )
    orderUpdates
  pure . Right $ ()

updateSongs' :: (MonadIO m) => Env -> Map UUID (Song, Maybe SongDelta) -> m (Either Text ())
updateSongs' env deltas = do
  now <- liftIO getCurrentTime
  mapM_ (save'' . toPersistenceSong . doDelta now) (Map.elems deltas)
  exUpdate <- liftIO $ exec @SongCommand $ updateSongExternalSources env deltas
  pure $ exUpdate <> Right ()
  where
    save'' x =
      liftIO
        . runBeamSqliteDebug putStrLn (env ^. #conn)
        . runUpdate
        $ save ((^. #songs) wikiMusicDatabase) x
    doDelta :: UTCTime -> (Song, Maybe SongDelta) -> Song
    doDelta now (x, xDelta') =
      case xDelta' of
        Nothing -> x
        Just xDelta ->
          x
            { musicKey = xDelta ^. #musicKey,
              musicTuning = xDelta ^. #musicTuning,
              musicCreationDate = xDelta ^. #musicCreationDate,
              albumName = xDelta ^. #albumName,
              albumInfoLink = xDelta ^. #albumInfoLink,
              displayName = fromMaybe (x ^. #displayName) (xDelta ^. #displayName),
              description = xDelta ^. #description,
              lastEditedAt = Just now
            }

updateSongExternalSources' :: (MonadIO m) => Env -> Map UUID (Song, Maybe SongDelta) -> m (Either Text ())
updateSongExternalSources' env deltas = do
  now <- liftIO getCurrentTime
  mapM_
    ( \(song, xDelta) -> do
        ex <- liftIO $ runBeamSqliteDebug putStrLn (env ^. #conn) $ do
          runSelectReturningOne $ select $ do
            filter_
              (\s -> (s ^. #songIdentifier) ==. val_ (SongId $ UUID.toText $ song ^. #identifier))
              $ all_ ((^. #songExternalSources) wikiMusicDatabase)
        case ex of
          Nothing -> pure ()
          Just foundEx -> do
            let a =
                  foundEx
                    { spotifyUrl = (^. #spotifyUrl) =<< xDelta,
                      wikipediaUrl = (^. #wikipediaUrl) =<< xDelta,
                      soundcloudUrl = (^. #soundcloudUrl) =<< xDelta,
                      youtubeUrl = (^. #youtubeUrl) =<< xDelta,
                      lastEditedAt = Just now
                    } ::
                    SongExternalSources'
            liftIO . runBeamSqliteDebug putStrLn (env ^. #conn) . runUpdate . save ((^. #songExternalSources) wikiMusicDatabase) $ a
    )
    deltas
  pure . Right $ ()

newSongArtworkFromRequest' :: (MonadIO m) => UUID -> InsertSongArtworksRequestItem -> m SongArtwork
newSongArtworkFromRequest' createdBy req = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ SongArtwork
      { songIdentifier = req ^. #songIdentifier,
        artwork =
          Artwork
            { identifier = newUUID,
              createdBy = createdBy,
              contentUrl = req ^. #contentUrl,
              contentCaption = req ^. #contentCaption,
              createdAt = now,
              lastEditedAt = Nothing,
              visibilityStatus = 0,
              approvedBy = Nothing,
              orderValue = req ^. #orderValue
            }
      }

newSongOpinionFromRequest' :: (MonadIO m) => UUID -> UpsertSongOpinionsRequestItem -> m SongOpinion
newSongOpinionFromRequest' createdBy req = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ SongOpinion
      { songIdentifier = req ^. #songIdentifier,
        opinion =
          Opinion
            { identifier = newUUID,
              createdBy = createdBy,
              isLike = req ^. #isLike,
              isDislike = not $ req ^. #isLike,
              createdAt = now,
              lastEditedAt = Nothing
            }
      }

updateSongContents' :: (MonadIO m) => Env -> [SongContentDelta] -> m (Either Text ())
updateSongContents' env songContentDeltas = do
  songContentsP <-
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runSelectReturningList
      . select
      . filter_ (\s -> (s ^. #identifier) `in_` map (val_ . (\z -> UUID.toText $ z ^. #identifier)) songContentDeltas)
      $ all_ ((^. #songContents) wikiMusicDatabase)

  _ <- liftIO . putStrLn . Relude.show $ songContentDeltas
  now <- liftIO getCurrentTime
  let updatedP =
        map
          ( \songContentDelta -> do
              fmap (updatedSongContent songContentDelta now . head)
                $ nonEmpty
                $ filter
                  ( \songContentP -> songContentP ^. #identifier == UUID.toText (songContentDelta ^. #identifier)
                  )
                  songContentsP
          )
          songContentDeltas
  mapM_
    ( liftIO
        . runBeamSqliteDebug putStrLn (env ^. #conn)
        . runUpdate
        . save ((^. #songContents) wikiMusicDatabase)
    )
    (catMaybes updatedP)
  pure . Right $ ()

updatedSongContent :: SongContentDelta -> UTCTime -> SongContents' -> SongContents'
updatedSongContent songContentDelta now x =
  x
    { versionName = songContentDelta ^. #versionName,
      instrumentType = fromMaybe "" (songContentDelta ^. #instrumentType),
      asciiLegend = songContentDelta ^. #asciiLegend,
      asciiContents = songContentDelta ^. #asciiContents,
      pdfContents = songContentDelta ^. #pdfContents,
      guitarProContents = songContentDelta ^. #guitarProContents,
      lastEditedAt = Just now
    } ::
    SongContents'

deleteArtistOfSong' :: (MonadIO m) => Env -> (UUID, UUID) -> m (Either SongCommandError ())
deleteArtistOfSong' env (songId, artistId) = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runDelete
    $ delete
      ((^. #songArtists) wikiMusicDatabase)
      (\c -> (c ^. #songIdentifier) ==. (val_ . SongId . UUID.toText $ songId) &&. (c ^. #artistIdentifier) ==. (val_ . ArtistId . UUID.toText $ artistId))

  pure . Right $ ()

newSongCommentFromRequest' :: (MonadIO m) => UUID -> InsertSongCommentsRequestItem -> m SongComment
newSongCommentFromRequest' createdBy x = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ SongComment
      { songIdentifier = x ^. #songIdentifier,
        comment =
          Comment
            { identifier = newUUID,
              parentIdentifier = x ^. #parentIdentifier,
              createdBy = createdBy,
              visibilityStatus = 0,
              contents = x ^. #contents,
              approvedBy = Nothing,
              createdAt = now,
              lastEditedAt = Nothing
            }
      }

insertSongContents' :: (MonadIO m) => Env -> [SongContent] -> m (Map UUID SongContent)
insertSongContents' env contents = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runInsert
    . insert ((^. #songContents) wikiMusicDatabase)
    $ insertValues (map mkSongContentsP contents)
  pure Map.empty

newSongFromRequest' :: (MonadIO m) => UUID -> InsertSongsRequestItem -> m Song
newSongFromRequest' createdBy song = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ Song
      { identifier = newUUID,
        displayName = song ^. #displayName,
        musicKey = song ^. #musicKey,
        musicTuning = song ^. #musicTuning,
        musicCreationDate = song ^. #musicCreationDate,
        albumName = song ^. #albumName,
        albumInfoLink = song ^. #albumInfoLink,
        createdBy = createdBy,
        visibilityStatus = 0,
        approvedBy = Nothing,
        createdAt = now,
        lastEditedAt = Nothing,
        artworks = Map.empty,
        comments = [],
        opinions = Map.empty,
        contents = Map.empty,
        spotifyUrl = song ^. #spotifyUrl,
        youtubeUrl = song ^. #youtubeUrl,
        soundcloudUrl = song ^. #soundcloudUrl,
        wikipediaUrl = song ^. #wikipediaUrl,
        artists = Map.empty,
        viewCount = 0,
        description = song ^. #description
      }

newArtistOfSongFromRequest' :: (MonadIO m) => UUID -> InsertArtistsOfSongsRequestItem -> m ArtistOfSong
newArtistOfSongFromRequest' createdBy x = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ ArtistOfSong
      { identifier = newUUID,
        songIdentifier = x ^. #songIdentifier,
        artistIdentifier = x ^. #artistIdentifier,
        createdAt = now,
        createdBy = createdBy
      }

newSongContentFromRequest' :: (MonadIO m) => UUID -> InsertSongContentsRequestItem -> m SongContent
newSongContentFromRequest' createdBy x = do
  newUUID <- liftIO nextRandom
  now <- liftIO getCurrentTime
  pure
    $ SongContent
      { identifier = newUUID,
        songIdentifier = x ^. #songIdentifier,
        versionName = x ^. #versionName,
        visibilityStatus = 0,
        approvedBy = Nothing,
        instrumentType = x ^. #instrumentType,
        asciiLegend = x ^. #asciiLegend,
        asciiContents = x ^. #asciiContents,
        pdfContents = x ^. #pdfContents,
        guitarProContents = x ^. #guitarProContents,
        createdAt = now,
        createdBy = createdBy,
        lastEditedAt = Nothing
      }

deleteSongComments' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
deleteSongComments' env identifiers = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runDelete
    $ delete
      ((^. #songComments) wikiMusicDatabase)
      (\c -> (c ^. #identifier) `in_` map (val_ . UUID.toText) identifiers)
  pure . Right $ ()

deleteSongArtworks' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
deleteSongArtworks' env identifiers = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runDelete
    $ delete
      ((^. #songArtworks) wikiMusicDatabase)
      (\c -> (c ^. #identifier) `in_` map (val_ . UUID.toText) identifiers)
  pure . Right $ ()

deleteSongOpinions' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
deleteSongOpinions' env identifiers = do
  liftIO
    . runBeamSqliteDebug putStrLn (env ^. #conn)
    . runDelete
    $ delete
      ((^. #songOpinions) wikiMusicDatabase)
      (\c -> (c ^. #identifier) `in_` map (val_ . UUID.toText) identifiers)
  pure . Right $ ()

incrementViewsByOne' :: (MonadIO m) => Env -> [UUID] -> m (Either SongCommandError ())
incrementViewsByOne' env identifiers = do
  mapM_ doUpdate identifiers
  pure $ Right ()
  where
    doUpdate x = do
      ex <- liftIO $ runBeamSqliteDebug putStrLn (env ^. #conn) $ do
        runSelectReturningOne $ select $ do
          filter_
            (\s -> (s ^. #identifier) ==. (val_ . UUID.toText $ x))
            $ all_ ((^. #songs) wikiMusicDatabase)
      case ex of
        Nothing -> pure ()
        Just foundEx -> do
          let a =
                foundEx
                  { viewCount = (foundEx ^. #viewCount) + 1
                  } ::
                  Song'
          liftIO . runBeamSqliteDebug putStrLn (env ^. #conn) . runUpdate . save ((^. #songs) wikiMusicDatabase) $ a

instance Exec SongCommand where
  execAlgebra (IncrementViewsByOne env identifiers next) =
    next =<< incrementViewsByOne' env identifiers
  execAlgebra (InsertSongs env songs next) =
    next =<< insertSongs' env songs
  execAlgebra (InsertSongComments env comments next) =
    next =<< insertSongComments' env comments
  execAlgebra (InsertSongExternalSources env externalSources next) =
    next =<< insertSongExternalSources' env externalSources
  execAlgebra (InsertSongArtworks env artworks next) =
    next =<< insertSongArtworks' env artworks
  execAlgebra (UpsertSongOpinions env opinions next) =
    next =<< upsertSongOpinions' env opinions
  execAlgebra (DeleteSongs env identifiers next) =
    next =<< uberDeleteSongs' env identifiers
  execAlgebra (DeleteSongComments env identifiers next) = do
    next =<< deleteSongComments' env identifiers
  execAlgebra (DeleteSongArtworks env identifiers next) = do
    next =<< deleteSongArtworks' env identifiers
  execAlgebra (DeleteSongOpinions env identifiers next) = do
    next =<< deleteSongOpinions' env identifiers
  execAlgebra (DeleteCommentsOfSongs env identifiers next) = do
    let ids = map UUID.toText identifiers

    mapM_
      ( \y ->
          runBeamSqliteDebug putStrLn (env ^. #conn)
            . runDelete
            $ delete ((^. #songComments) wikiMusicDatabase) (\c -> c ^. #songIdentifier ==. (val_ . SongId $ y))
      )
      ids

    next $ Right ()
  execAlgebra (DeleteSongExternalSources env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songExternalSources) wikiMusicDatabase)
        (\c -> (c ^. #songIdentifier) `in_` map (val_ . SongId . UUID.toText) identifiers)
    next . Right $ ()
  execAlgebra (DeleteArtworksOfSongs env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songArtworks) wikiMusicDatabase)
        (\c -> (c ^. #songIdentifier) `in_` map (val_ . SongId . UUID.toText) identifiers)
    next . Right $ ()
  execAlgebra (DeleteOpinionsOfSongs env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songOpinions) wikiMusicDatabase)
        (\c -> (c ^. #songIdentifier) `in_` map (val_ . SongId . UUID.toText) identifiers)
    next . Right $ ()
  execAlgebra (UpdateSongArtworkOrder env orderUpdates next) =
    next =<< updateSongArtworkOrder' env orderUpdates
  execAlgebra (UpdateSongs env deltas next) =
    next =<< updateSongs' env deltas
  execAlgebra (UpdateSongContents env deltas next) =
    next =<< updateSongContents' env deltas
  execAlgebra (UpdateSongExternalSources env deltas next) =
    next =<< updateSongExternalSources' env deltas
  execAlgebra (NewSongCommentFromRequest createdBy req next) =
    next =<< newSongCommentFromRequest' createdBy req
  execAlgebra (NewSongOpinionFromRequest createdBy req next) =
    next =<< newSongOpinionFromRequest' createdBy req
  execAlgebra (NewSongArtworkFromRequest createdBy req next) =
    next =<< newSongArtworkFromRequest' createdBy req
  execAlgebra (InsertSongContents env contents next) =
    next =<< insertSongContents' env contents
  execAlgebra (InsertArtistsOfSongs env items next) =
    next =<< insertArtistsOfSongs' env items
  execAlgebra (DeleteArtistsOfSongs env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songArtists) wikiMusicDatabase)
        (\c -> (c ^. #songIdentifier) `in_` map (val_ . SongId . UUID.toText) identifiers)

    next . Right $ ()
  execAlgebra (DeleteArtistOfSong env identifiers next) =
    next =<< deleteArtistOfSong' env identifiers
  execAlgebra (NewSongFromRequest createdBy song next) =
    next =<< newSongFromRequest' createdBy song
  execAlgebra (NewArtistOfSongFromRequest createdBy x next) =
    next =<< newArtistOfSongFromRequest' createdBy x
  execAlgebra (NewSongContentFromRequest createdBy x next) =
    next =<< newSongContentFromRequest' createdBy x
  execAlgebra (DeleteContentsOfSongs env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songContents) wikiMusicDatabase)
        (\c -> (c ^. #songIdentifier) `in_` map (val_ . SongId . UUID.toText) identifiers)
    next . Right $ ()
  execAlgebra (DeleteSongContents env identifiers next) = do
    liftIO
      . runBeamSqliteDebug putStrLn (env ^. #conn)
      . runDelete
      $ delete
        ((^. #songContents) wikiMusicDatabase)
        (\c -> (c ^. #identifier) `in_` map (val_ . UUID.toText) identifiers)
    next . Right $ ()