packages feed

wikimusic-ssr-1.0.1.6: src/WikiMusic/SSR/Servant/ApiSetup.hs

{-# LANGUAGE OverloadedLabels #-}

module WikiMusic.SSR.Servant.ApiSetup (mkApp) where

import Network.HTTP.Client (newManager)
import Network.HTTP.Client.TLS (tlsManagerSettings)
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 Principium
import Servant
import Servant.Client
import WikiMusic.SSR.Backend.Rest ()
import WikiMusic.SSR.Servant.ApiSpec
import WikiMusic.SSR.Servant.ArtistRoutes
import WikiMusic.SSR.Servant.GenreRoutes
import WikiMusic.SSR.Servant.LoginRoutes
import WikiMusic.SSR.Servant.PreferenceRoutes
import WikiMusic.SSR.Servant.SongRoutes
import WikiMusic.SSR.Servant.Utilities
import WikiMusic.SSR.View.Html ()

newClientEnv :: (MonadIO m) => AppConfig -> m ClientEnv
newClientEnv cfg = do
  manager <- liftIO $ newManager tlsManagerSettings
  pure $ clientEnv manager Nothing
  where
    baseUrl' =
      BaseUrl
        { baseUrlScheme = if (cfg ^. #api % #protocol) == "https" then Https else Http,
          baseUrlHost = unpackText $ cfg ^. #api % #host,
          baseUrlPort = cfg ^. #api % #port,
          baseUrlPath = ""
        }
    clientEnv manager cookieJar =
      ClientEnv
        { manager = manager,
          baseUrl = baseUrl',
          cookieJar = cookieJar,
          makeClientRequest = defaultMakeClientRequest,
          middleware = id
        }

mkApp :: ApacheLogger -> AppConfig -> IO Application
mkApp logger' cfg = do
  let apiCfg = EmptyContext

  now <- getZonedTime
  clientEnv <- newClientEnv cfg

  let env =
        Env
          { logger = logger',
            cfg = cfg,
            processStartedAt = now,
            reportedVersion = cfg ^. #dev % #reportedVersion,
            clientEnv = clientEnv
          }
  pure
    . (if (cfg ^. #dev % #reportedVersion) == "dev" then logStdoutDev else logStdout)
    . myCors (cfg ^. #cors)
    . P.prometheus P.def
    $ serveWithContext wikimusicSSRServant apiCfg (server env)

artistBaseEntityRoutes :: Env -> Server BaseEntityRoutes
artistBaseEntityRoutes env =
  artistsRoute env
    :<|> artistRoute env
    :<|> artistCreateRoute env
    :<|> artistCreateFormRoute env
    :<|> artistLikeRoute env
    :<|> artistDislikeRoute env
    :<|> artistEditRoute env
    :<|> artistEditFormRoute env
    :<|> searchArtistRoute env
    :<|> (createArtistArtworkRoute env :<|> artistArtworkDeleteRoute env :<|> updateArtistArtworkOrderRoute env)
    :<|> artistDeleteRoute env

genreBaseEntityRoutes :: Env -> Server BaseEntityRoutes
genreBaseEntityRoutes env =
  genresRoute env
    :<|> genreRoute env
    :<|> genreCreateRoute env
    :<|> genreCreateFormRoute env
    :<|> genreLikeRoute env
    :<|> genreDislikeRoute env
    :<|> genreEditRoute env
    :<|> genreEditFormRoute env
    :<|> searchGenreRoute env
    :<|> (createGenreArtworkRoute env :<|> genreArtworkDeleteRoute env :<|> updateGenreArtworkOrderRoute env)
    :<|> genreDeleteRoute env

songBaseEntityRoutes :: Env -> Server BaseEntityRoutes
songBaseEntityRoutes env =
  songsRoute env
    :<|> songRoute env
    :<|> songCreateRoute env
    :<|> songCreateFormRoute env
    :<|> songLikeRoute env
    :<|> songDislikeRoute env
    :<|> songEditRoute env
    :<|> songEditFormRoute env
    :<|> searchSongRoute env
    :<|> (createSongArtworkRoute env :<|> songArtworkDeleteRoute env :<|> updateSongArtworkOrderRoute env)
    :<|> songDeleteRoute env

preferenceRoutes :: Env -> Server PreferenceRoutes
preferenceRoutes env =
  setLanguageRoute env
    :<|> setArtistSortingRoute env
    :<|> setGenreSortingRoute env
    :<|> setSongSortingRoute env
    :<|> setDarkModeRoute env
    :<|> setSongAsciiSizeRoute env
    :<|> setPaletteRoute env

loginRoutes :: Env -> Server LoginRoutes
loginRoutes env =
  loginFormRoute env
    :<|> submitLoginRoute env

songContentsRoutes :: Env -> Server SongContentsRoutes
songContentsRoutes env =
  songContentCreateFormRoute env
    :<|> songContentEditFormRoute env
    :<|> songContentDeleteRoute env

passwordRoutes :: Env -> Server PasswordRoutes
passwordRoutes env =
  requestPasswordResetRoute env
    :<|> doRequestPasswordResetRoute env
    :<|> doPasswordResetFormRoute env
    :<|> doPasswordResetRoute env

userRoutes :: Env -> Server UserRoutes
userRoutes env =
  inviteUsersRoute env
    :<|> inviteUsersFormRoute env

staticFilesRoute :: Env -> Server StaticFilesRoute
staticFilesRoute env =
  serveDirectoryFileServer
    . unpackText
    $ (env ^. #cfg % #webFrontend % #staticFileDir)

server :: Env -> Server WikiMusicSSRServant
server env =
  fallbackRoute
    :<|> artistBaseEntityRoutes env
    :<|> genreBaseEntityRoutes env
    :<|> ( songBaseEntityRoutes env
             :<|> songContentsRoutes env
         )
    :<|> preferenceRoutes env
    :<|> loginRoutes env
    :<|> errorRoute env
    :<|> passwordRoutes env
    :<|> userRoutes env
    :<|> staticFilesRoute env

fallbackRoute :: Handler a
fallbackRoute =
  respondWithHttp
    httpFound
      { cause = Just "Fallback page!",
        headers =
          [ withLocation "/songs"
          ]
      }

wikimusicSSRServant :: Proxy WikiMusicSSRServant
wikimusicSSRServant = Proxy

myCors :: CorsConfig -> Middleware
myCors cfg = cors (const $ Just policy)
  where
    policy =
      CorsResourcePolicy
        { corsOrigins = Just (map encodeUtf8 (cfg ^. #origins), True),
          corsMethods = map encodeUtf8 (cfg ^. #methods),
          corsRequestHeaders = map (fromString . unpackText) (cfg ^. #requestHeaders),
          corsExposedHeaders =
            Just
              [ "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
        }