{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-missing-methods #-} module Main where import Control.Monad.Reader import Data.FileEmbed import Data.Proxy import Data.Text.Encoding import Database.Beam import Database.Beam.Sqlite import Database.SQLite.Simple import Network.Wai import Network.Wai.Handler.Warp import Options.Applicative import Servant.API import Servant.Server import Shpadoinkle import Shpadoinkle.Router.Server import Types import View data Options = Options { assets :: FilePath , port :: Int } newtype App a = App { runApp :: ReaderT Connection IO a } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader Connection) toHandler :: MonadIO m => Connection -> App ~> m toHandler c a = liftIO $ runReaderT (runApp a) c parser :: Parser Options parser = Options <$> strOption (long "assets" <> short 'a' <> metavar "FILEPATH") <*> option auto (long "port" <> short 'p' <> metavar "PORT" <> showDefault <> value 8080) options :: ParserInfo Options options = info (parser <**> helper) $ fullDesc <> progDesc "Servant CRUD Example" <> header "Space craft manager as an example of Shpadoinkle" runSql :: (MonadIO m, MonadReader Connection m) => SqliteM ~> m runSql x = do conn <- ask; liftIO $ runBeamSqlite conn x newtype Noop a = Noop (JSM a) deriving newtype (Functor, Applicative, Monad, MonadIO, MonadJSM) deriving anyclass CRUDSpaceCraft instance CRUDSpaceCraft App where listSpaceCraft = runSql . runSelectReturningList . select . all_ $ _roster db getSpaceCraft i = runSql . runSelectReturningOne . select . filter_ (\s -> val_ (SpaceCraftKey i) ==. primaryKey s) . all_ $ _roster db updateSpaceCraft i SpaceCraftUpdate {..} = runSql . runUpdate $ save (_roster db) $ SpaceCraft i _sku _description _serial _squadron _operable deleteSpaceCraft i = runSql . runDelete $ delete (_roster db) $ \sc -> val_ (SpaceCraftKey i) ==. primaryKey sc createSpaceCraft SpaceCraftUpdate {..} = runSql . fmap (_identity . head) . runInsertReturningList . insert (_roster db) $ insertExpressions [ SpaceCraft default_ (val_ _sku) (val_ _description) (val_ _serial) (val_ _squadron) (val_ _operable) ] app :: Connection -> FilePath -> Application app conn root = serve (Proxy @ (API :<|> SPA)) $ serveApi :<|> serveSpa where serveApi :: Server API serveApi = hoistServer (Proxy @ API) (toHandler conn) $ listSpaceCraft :<|> getSpaceCraft :<|> updateSpaceCraft :<|> createSpaceCraft :<|> deleteSpaceCraft serveSpa :: Server SPA serveSpa = serveUI @ SPA root (\r -> toHandler conn $ do i <- start r; return . template i $ view @ Noop i) routes main :: IO () main = do Options {..} <- execParser options conn <- open "roster.db" execute_ conn . Query $ decodeUtf8 $(embedFile "./servant-crud/migrate.sql") run port $ app conn assets