packages feed

servant-seo-0.1.0: example/example.hs

{-# LANGUAGE AllowAmbiguousTypes        #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveAnyClass             #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE PolyKinds                  #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeOperators              #-}
module Main where

import           Data.Aeson
import           Data.Text                (Text)
import qualified Data.Text                as Text
import           GHC.Generics             (Generic)
import qualified Network.Wai.Handler.Warp as Warp
import           Servant
import           Servant.HTML.Blaze       (HTML)
import           Text.Blaze               (ToMarkup)

import           Servant.Seo.Combinators
import           Servant.Seo.Sitemap
import           Servant.Seo.UI

newtype NewsPage = NewsPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

newtype NewsUrl = NewsUrl Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, ToHttpApiData, FromHttpApiData, ToMarkup)

newtype SearchResultPage = SearchResultPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

newtype AboutPage = AboutPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

newtype HomePage = HomePage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

newtype SearchPattern = SearchPattern Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, ToHttpApiData, FromHttpApiData)

newtype SearchResultsPage = SearchResultsPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, ToHttpApiData, FromHttpApiData, ToMarkup)

newtype BlogUrl = BlogUrl Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, ToHttpApiData, FromHttpApiData, ToMarkup)

newtype BlogPage = BlogPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

newtype BlogComment = BlogComment Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, FromJSON)

data Login = Login
    { username :: !Text
    , password :: !Text
    }
    deriving (Eq, Show, Generic, FromJSON)

newtype AdminPage = AdminPage Text
  deriving stock (Eq, Show, Generic)
  deriving newtype (ToJSON, FromHttpApiData, ToMarkup)

-- *** User instances

instance ToSitemapPathPiece BlogUrl where
  getPathPiecesForIndexing _ _ = pure $ toUrl <$> ([0 .. 100000] :: [Int])
    where
      toUrl = BlogUrl . Text.pack . show

instance ToSitemapPathPiece NewsUrl where
  getPathPiecesForIndexing _ _ = pure $ toUrl <$> ([0 .. 100] :: [Int])
    where
      toUrl = NewsUrl . Text.pack . show

instance ToSitemapParamPart SearchPattern where
  getParamsForIndexing _ _ = pure $ SearchPattern . Text.pack <$> samples
    where
      samples = [ [ c1, c2 ] |  c1 <- ['a' .. 'z'], c2 <- ['0' .. '9'] ]

-- ** Example API

type PublicAPI
  =    Get '[HTML] HomePage
  :<|> ("blog" :> Frequency 'Always :> BlogAPI)
  :<|> ("news" :> Capture ":newsurl" NewsUrl :> Get '[HTML] NewsPage)
  :<|> ("search" :> QueryParam "q" SearchPattern :> Get '[HTML] SearchResultsPage)
  :<|> ("about" :> Priority '(1,0) :> Get '[HTML] AboutPage)
  :<|> "auth" :> ReqBody '[JSON] Login :> Post '[JSON] NoContent

type BlogAPI
  =    Capture ":blogurl" BlogUrl :> Get '[HTML] BlogPage
  :<|> Capture ":blogurl" BlogUrl
    :> ReqBody '[JSON] BlogComment
    :> Post '[JSON, HTML] BlogPage

type ProtectedAPI = Disallow "admin" :> Get '[HTML] AdminPage

type StaticAPI = "cdn" :> Disallow "static" :> Raw

type API = StaticAPI :<|> ProtectedAPI :<|> PublicAPI

-- ** Example app

api :: Proxy API
api = Proxy

server :: Server API
server
     = serveStatic
  :<|> serveProtected
  :<|> servePublic

serveProtected :: Handler AdminPage
serveProtected = throwError err401

servePublic :: Server PublicAPI
servePublic
     = serveHome
  :<|> (serveBlog :<|> servePostBlogComment)
  :<|> serveNews
  :<|> serveSearch
  :<|> serveAbout
  :<|> serveAuth

serveHome :: Handler HomePage
serveHome = pure (HomePage "")

serveBlog :: BlogUrl -> Handler BlogPage
serveBlog _ = pure (BlogPage "")

servePostBlogComment :: BlogUrl -> BlogComment -> Handler BlogPage
servePostBlogComment _ _ = pure (BlogPage "")

serveSearch :: Maybe SearchPattern -> Handler SearchResultsPage
serveSearch _ = pure (SearchResultsPage "")

serveAbout :: Handler AboutPage
serveAbout = pure (AboutPage "")

serveAuth :: Login -> Handler NoContent
serveAuth _ = pure NoContent

serveNews :: NewsUrl -> Handler NewsPage
serveNews _ = pure (NewsPage "")

serveStatic :: Server StaticAPI
serveStatic = serveDirectoryWebApp "."

startServer :: IO ()
startServer = do
  Warp.runSettings Warp.defaultSettings
    $ serveWithSeo website api server
  where
    website = "https://example.com"

main :: IO ()
main = startServer