packages feed

wikimusic-api-1.2.0.8: src/WikiMusic/Servant/ApiSetup.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoFieldSelectors #-}

module WikiMusic.Servant.ApiSetup
  ( mkApp,
    PrivateAPI,
    PublicAPI,
    WikiMusicAPIServer,
    wiredUpPrivateServer,
    wiredUpPublicServer,
  )
where

import Data.OpenApi qualified
import Data.Proxy
import Data.Text (unpack)
import Data.Text qualified as T
import Database.Beam
import Database.SQLite.Simple qualified as Sqlite
import Network.Wai
import Network.Wai.Logger (ApacheLogger)
import Network.Wai.Middleware.Cors
import Network.Wai.Middleware.Prometheus qualified as P
import Network.Wai.Middleware.RequestLogger
import Prometheus qualified as P
import Prometheus.Metric.GHC qualified as P
import Relude
import Servant
import Servant.OpenApi
import WikiMusic.Model.Config
import WikiMusic.Model.Env
import WikiMusic.Protolude
import WikiMusic.Servant.ApiSpec
import WikiMusic.Servant.ArtistRoutes
import WikiMusic.Servant.AuthRoutes
import WikiMusic.Servant.GenreRoutes
import WikiMusic.Servant.SongRoutes
import WikiMusic.Servant.UserRoutes
import WikiMusic.Servant.Utilities

swagger :: Servant.Handler Data.OpenApi.OpenApi
swagger = pure $ toOpenApi docsProxy

apiProxy :: Proxy WikiMusicAPIServer
apiProxy = Proxy

docsProxy :: Proxy APIDocsServer
docsProxy = Proxy

myCors :: CorsConfig -> Middleware
myCors cfg = cors (const $ Just policy)
  where
    policy =
      CorsResourcePolicy
        { corsOrigins = Just (map (fromString . unpack) (cfg ^. #origins), True),
          corsMethods = map (fromString . unpack) (cfg ^. #methods),
          corsRequestHeaders = map (fromString . unpack) (cfg ^. #requestHeaders),
          corsExposedHeaders =
            Just
              [ "x-wikimusic-auth",
                "content-type",
                "date",
                "content-length",
                "access-control-allow-origin",
                "access-control-allow-methods",
                "access-control-allow-headers",
                "access-control-request-method",
                "access-control-request-headers"
              ],
          corsMaxAge = Nothing,
          corsVaryOrigin = False,
          corsRequireOrigin = False,
          corsIgnoreFailures = False
        }

mkApp :: (MonadIO m) => ApacheLogger -> AppConfig -> m Application
mkApp logger' cfg = do
  now <- liftIO getCurrentTime
  conn <- liftIO $ Sqlite.open (T.unpack $ cfg ^. #sqlite % #path)
  mailCss <- liftIO $ readFileBS "resources/css/mail.css"

  let env =
        Env
          { cfg = cfg,
            processStartedAt = now,
            logger = logger',
            conn = conn,
            mailCss = T.filter (\x -> x /= '\n' && x /= '\t') . decodeUtf8 $ mailCss
          }
      authCfg = authCheckIO env
      apiCfg = authCfg :. EmptyContext
      apiItself =
        wiredUpPrivateServer env
          :<|> ( swagger
                   :<|> wiredUpPublicServer env
               )

  pure
    . (if (cfg ^. #dev % #reportedVersion) == "dev" then logStdoutDev else logStdout)
    . myCors (cfg ^. #cors)
    . P.prometheus P.def
    $ serveWithContext apiProxy apiCfg apiItself

wiredUpPrivateServer :: Env -> Server PrivateAPI
wiredUpPrivateServer env =
  artistHandlers env :<|> genreHandlers env :<|> songHandlers env :<|> authHandlers env

artistBase :: Env -> Server WithBaseEntityRoutes
artistBase env =
  deleteArtistsByIdentifierRoute env
    :<|> deleteArtistCommentsByIdentifierRoute env
    :<|> deleteArtistOpinionsByIdentifierRoute env
    :<|> deleteArtistArtworksByIdentifierRoute env

artistHandlers :: Env -> Server ArtistsAPI
artistHandlers env =
  artistBase env
    :<|> ( fetchArtistsRoute env
             :<|> searchArtistsRoute env
             :<|> fetchArtistRoute env
             :<|> insertArtistsRoute env
             :<|> insertArtistCommentsRoute env
             :<|> upsertArtistOpinionsRoute env
             :<|> insertArtistArtworksRoute env
             :<|> updateArtistArtworksOrderRoute env
             :<|> updateArtistRoute env
         )

genreBase :: Env -> Server WithBaseEntityRoutes
genreBase env =
  deleteGenresByIdentifierRoute env
    :<|> deleteGenreCommentsByIdentifierRoute env
    :<|> deleteGenreOpinionsByIdentifierRoute env
    :<|> deleteGenreArtworksByIdentifierRoute env

genreHandlers :: Env -> Server GenresAPI
genreHandlers env =
  genreBase env
    :<|> ( fetchGenresRoute env
             :<|> searchGenresRoute env
             :<|> fetchGenreRoute env
             :<|> insertGenresRoute env
             :<|> insertGenreCommentsRoute env
             :<|> upsertGenreOpinionsRoute env
             :<|> insertGenreArtworksRoute env
             :<|> updateGenreArtworksOrderRoute env
             :<|> updateGenreRoute env
         )

songBase :: Env -> Server WithBaseEntityRoutes
songBase env =
  deleteSongsByIdentifierRoute env
    :<|> deleteSongCommentsByIdentifierRoute env
    :<|> deleteSongOpinionsByIdentifierRoute env
    :<|> deleteSongArtworksByIdentifierRoute env

songHandlers :: Env -> Server SongsAPI
songHandlers env =
  songBase env
    :<|> fetchSongsRoute env
    :<|> searchSongsRoute env
    :<|> fetchSongRoute env
    :<|> insertSongsRoute env
    :<|> insertSongCommentsRoute env
    :<|> upsertSongOpinionsRoute env
    :<|> insertSongArtworksRoute env
    :<|> insertArtistOfSongRoute env
    :<|> deleteArtistOfSongRoute env
    :<|> updateSongArtworksOrderRoute env
    :<|> updateSongRoute env
    :<|> insertSongContentsRoute env
    :<|> deleteSongContentsByIdentifierRoute env
    :<|> updateSongContentsRoute env

authHandlers :: Env -> Server AuthAPI
authHandlers env =
  fetchMeRoute env
    :<|> inviteUserRoute env
    :<|> deleteUserRoute env

wiredUpPublicServer :: Env -> Server PublicAPI
wiredUpPublicServer env =
  loginRoute env
    :<|> ( makeResetPasswordLinkRoute env
             :<|> doPasswordResetRoute env
         )
    :<|> systemInformationRoute env