1
0
mirror of https://github.com/alaudidaelark/coupon-servant.git synced 2026-03-07 22:12:34 +00:00

initial version with basic model - todo implement logic

added a swagger-generator

deriving coupontype automatically

implemented cors for swagger to work

fixed product model
This commit is contained in:
2017-05-05 00:16:45 +05:30
committed by Alaudidae
commit 1d44db32f0
18 changed files with 536 additions and 0 deletions

24
src/Api.hs Normal file
View File

@@ -0,0 +1,24 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Api (module Api,module Models) where
import Data.Proxy
import Data.Text
import Models
import Servant.API
type CouponApi =
"coupon" :> "add" :> ReqBody '[JSON] Coupon :> Post '[JSON] (Maybe Coupon)
:<|> "coupon" :> "get" :> Capture "name" Text :> Get '[JSON] (Maybe Coupon)
:<|> "coupon" :> "del" :> Capture "name" Text :> Get '[JSON] (Maybe Coupon)
type BillCouponApi =
"billcoupon" :> ReqBody '[JSON] BillCoupon :> Post '[JSON] CouponResult
type ServerApi = CouponApi :<|> BillCouponApi
api :: Proxy ServerApi
api = Proxy

80
src/App.hs Normal file
View File

@@ -0,0 +1,80 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module App where
import Api
import Control.Monad.IO.Class
import Control.Monad.Logger (runStderrLoggingT)
import Data.String.Conversions
import Data.Text
import Database.Persist
import Database.Persist.Postgresql
import Database.Persist.Sqlite
import Network.Wai
import Network.Wai.Handler.Warp as Warp
import Network.Wai.Middleware.Cors
import Servant
-- import Network.Wai.Middleware.RequestLogger (logStdoutDev)
couponServer :: ConnectionPool -> Server CouponApi
couponServer pool =
couponAddH :<|> couponGetH :<|> couponDelH
where
couponAddH newCoupon = liftIO $ couponAdd newCoupon
couponGetH code = liftIO $ couponGet code
couponDelH code = liftIO $ couponDel code
couponAdd :: Coupon -> IO (Maybe Coupon)
couponAdd newCoupon = flip runSqlPersistMPool pool $ do
exists <- selectFirst [CouponCode ==. couponCode newCoupon] []
case exists of
Nothing -> Just <$> insert newCoupon
Just _ -> return Nothing
return Nothing
couponGet :: Text -> IO (Maybe Coupon)
couponGet code = flip runSqlPersistMPool pool $ do
mUser <- selectFirst [CouponCode ==. code] []
return $ entityVal <$> mUser
couponDel :: Text -> IO (Maybe Coupon)
couponDel code = flip runSqlPersistMPool pool $ do
deleteWhere [CouponCode ==. code]
return Nothing
billCouponServer :: ConnectionPool -> Server BillCouponApi
billCouponServer pool = billCouponComputeH
where billCouponComputeH bill = liftIO $ billCouponCompute bill
-- return $ Applied 100
billCouponCompute :: BillCoupon -> IO CouponResult
billCouponCompute bill = do putStrLn $ show bill
return $ Applied 100
server :: ConnectionPool -> Server ServerApi
server pool = couponServer pool :<|> billCouponServer pool
app :: ConnectionPool -> Application
app pool = cors (const $ Just policy) $ serve api $ server pool
where
policy = simpleCorsResourcePolicy { corsRequestHeaders = ["Content-Type"] }
mkPgApp :: String -> IO Application
mkPgApp sqliteFile = do
pool <- runStderrLoggingT $ createPostgresqlPool (cs sqliteFile) 5
runSqlPool (runMigration migrateAll) pool
return $ app pool
mkApp :: String -> IO Application
mkApp sqliteFile = do
pool <- runStderrLoggingT $ createSqlitePool (cs sqliteFile) 5
runSqlPool (runMigration migrateAll) pool
return $ app pool
run :: String -> IO ()
run dbConnStr =
Warp.run 3000 =<< mkPgApp dbConnStr

50
src/Coupon.hs Normal file
View File

@@ -0,0 +1,50 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Coupon where
import Data.Aeson
import Data.Aeson.Types
import Database.Persist.TH
import GHC.Generics
import Prelude
data Product = Product {
productName :: String,
productPrice:: Int
} deriving (Eq, Read, Show, Generic, FromJSON, ToJSON)
data BillCoupon = BillCoupon {
customer :: String,
coupon :: String,
productList :: [Product]
} deriving (Eq, Read, Show, Generic, FromJSON, ToJSON)
data CouponResult = Applied Int | Rejected String | Partial String
deriving (Show, Read, Eq, Generic, FromJSON, ToJSON)
data CouponType = ProductFlat Int | CartFlat Int | CartPercent Int
deriving (Show, Read, Eq, Generic)
couponOption = defaultOptions { sumEncoding = ObjectWithSingleField }
instance FromJSON CouponType where
parseJSON = genericParseJSON couponOption
instance ToJSON CouponType where
toJSON = genericToJSON couponOption
derivePersistField "CouponType"
prodListEx = [Product {productName = "Water", productPrice = 15}]
billCouponExample = BillCoupon { customer = "test@email.com", coupon = "FLAT100", productList = prodListEx}

40
src/Lib.hs Normal file
View File

@@ -0,0 +1,40 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
module Lib
( startApp
, app
) where
import Data.Aeson
import Data.Aeson.TH
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
data User = User
{ userId :: Int
, userFirstName :: String
, userLastName :: String
} deriving (Eq, Show)
$(deriveJSON defaultOptions ''User)
type API = "users" :> Get '[JSON] [User]
startApp :: IO ()
startApp = run 8080 app
app :: Application
app = serve api server
api :: Proxy API
api = Proxy
server :: Server API
server = return users
users :: [User]
users = [ User 1 "Isaac" "Newton"
, User 2 "Albert" "Einstein"
]

47
src/Models.hs Normal file
View File

@@ -0,0 +1,47 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Models (module Models,module Coupon) where
import Coupon
import Data.Aeson
import Data.Text
import Data.Time.Clock
import Database.Persist.TH
import GHC.Generics
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Customer
email Text
UniqueEmail email
Primary email
deriving Eq Read Show Generic
-- Product
-- name Text
-- price Int
-- code Text
-- UniqueName name
-- Foreign Coupon fkcoupon code
-- Primary name
-- deriving Eq Read Show Generic
Coupon json
code Text
value CouponType
min_price Int
customer_limit Int
usage_limit Int
valid_from UTCTime default=now()
valid_till UTCTime default=now()
UniqueCode code
Primary code
deriving Eq Read Show Generic
|]

38
src/SwaggerGen.hs Normal file
View File

@@ -0,0 +1,38 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}
module SwaggerGen where
import Api
import Control.Lens
import Data.Aeson
import Data.Aeson.Types (camelTo2)
import qualified Data.ByteString.Lazy.Char8 as BL8
import Data.Swagger
import Servant.Swagger
modifier :: String -> String
modifier = drop 1 . dropWhile (/= '_') . camelTo2 '_'
prefixSchemaOptions :: SchemaOptions
prefixSchemaOptions = defaultSchemaOptions { fieldLabelModifier = modifier }
instance ToSchema BillCoupon where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
instance ToSchema CouponType where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
instance ToSchema Coupon where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
instance ToSchema Product where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
instance ToSchema Customer where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
instance ToSchema CouponResult where declareNamedSchema = genericDeclareNamedSchema prefixSchemaOptions
swaggerDoc :: Swagger
swaggerDoc = toSwagger api
& host ?~ "localhost:3000"
& info.title .~ "Coupon Api"
& info.version .~ "v1"
genSwaggerDoc :: IO ()
genSwaggerDoc = BL8.writeFile "swagger.json" (encode swaggerDoc)