{-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} module Main where import Control.Exception import Control.Monad (void) import Control.Monad.Base import Control.Monad.Logger import Data.ByteString (ByteString) import Data.Proxy import Data.Time import Database.PostgreSQL.Query import Servant.API.DB import Servant.DB.PostgreSQL type UserId = Int data RegisterUser = RegisterUser { userRegName :: String , userRegPassword :: String , userRegRegTime :: Day } deriving (Eq) deriveToRow ''RegisterUser data User = User { userId :: UserId , userName :: String , userPassword :: String , userRegTime :: Day } deriving (Eq) deriveFromRow ''User deriveToRow ''User type UserAPI = ArgNamed "u" (Composite RegisterUser) :> Procedure "postUser" (Only Int) :<|> ArgPos Int :> Procedure "getUser" (Maybe User) :<|> ArgPos Int :> Procedure "deleteUser" () :<|> Procedure "getUsers" [User] newtype PostgresM a = PostgresM { runPostgresM :: PgMonadT (LoggingT IO) a } deriving (Functor, HasPostgres, MonadLogger, Monad, Applicative, MonadBase IO) postUser :: Composite RegisterUser -> PostgresM (Only Int) getUser :: Int -> PostgresM (Maybe User) deleteUser :: Int -> PostgresM () getUsers :: PostgresM [User] ( postUser :<|> getUser :<|> deleteUser :<|> getUsers) = deriveDB (Proxy :: Proxy UserAPI) (Proxy :: Proxy PostgresM) main :: IO () main = withDB $ do t <- getCurrentTime let user = RegisterUser "Vasya" "123456" (utctDay t) Only i <- runDB $ postUser $ Composite user users <- runDB getUsers let user' = User { userId = i , userName = userRegName user , userPassword = userRegPassword user , userRegTime = userRegRegTime user } assertEqual "wrote != read list" [user'] users muser1 <- runDB $ getUser i assertEqual "wrote != read single" (Just user') muser1 runDB $ deleteUser i muser2 <- runDB $ getUser i assertEqual "deleted cannot be read" Nothing muser2 where assertEqual msg a b | a == b = return () | otherwise = fail msg dbConnection :: ByteString dbConnection = "host=localhost dbname=servant_db_postgresql_test port=5432" runDB :: PostgresM a -> IO a runDB m = do con <- connectPostgreSQL dbConnection runStderrLoggingT . runPgMonadT con . runPostgresM $ m withDB :: IO a -> IO a withDB = bracket_ (runDB userFuncs) (runDB userFuncsDrop) userFuncs :: PostgresM () userFuncs = void $ pgExecute [sqlExp| CREATE TYPE "userRegister" AS( name text, password text, regTime date ); CREATE TABLE IF NOT EXISTS "users"( id serial PRIMARY KEY, name text NOT NULL, password text NOT NULL, regTime date NOT NULL ); CREATE OR REPLACE FUNCTION "postUser"(u "userRegister") RETURNS integer AS $$ INSERT INTO users(name, password, regTime) VALUES (u.name, u.password, u.regTime) RETURNING id; $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION "getUser"(int) RETURNS users AS $$ SELECT * FROM users WHERE id = $1; $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION "deleteUser"(int) RETURNS void AS $$ DELETE FROM users WHERE id = $1; $$ LANGUAGE sql; CREATE OR REPLACE FUNCTION "getUsers"() RETURNS SETOF users AS $$ SELECT * FROM users; $$ LANGUAGE sql; |] userFuncsDrop :: PostgresM () userFuncsDrop = void $ pgExecute [sqlExp| DROP FUNCTION IF EXISTS "postUser"(u "userCreate"); DROP FUNCTION IF EXISTS "getUsers"(); DROP FUNCTION IF EXISTS "getUser"(int); DROP FUNCTION IF EXISTS "deleteUser"(int); DROP TYPE IF EXISTS "userRegister" CASCADE; DROP TABLE IF EXISTS "users"; |]