summaryrefslogtreecommitdiff
path: root/fig-web/src/Fig/Web/Module/Exchange.hs
blob: 11af071308aafba51f1b11b522221777074291dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
module Fig.Web.Module.Exchange
  ( public
  , secure
  ) where

import Fig.Prelude

import Control.Error.Util (hush)

import qualified Database.Redis as Redis

import Data.Maybe (mapMaybe)
import qualified Data.Aeson as Aeson
import qualified Data.Text.Read as Text.R
import qualified Data.ByteString.Lazy as BS.Lazy
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID

import Fig.Web.Utils
import Fig.Web.Types
import Fig.Web.Auth

public :: PublicModule
public a = do
  onGet "/api/exchange" do
    listings <- getOrders a.db.conn
    respondJSON listings

secure :: SecureModule
secure a = do
  onPost "/api/exchange" $ authed a \creds -> do
    haveCur <- formParam "haveCur"
    haveAmount <- formParam "haveAmount"
    wantCur <- formParam "wantCur"
    wantAmount <- formParam "wantAmount"
    key <- createOrder a.db.conn $ Order
      { creator = creds.twitchId
      , haveCur = haveCur
      , haveAmount = haveAmount
      , wantCur = wantCur
      , wantAmount = wantAmount
      }
    respondText $ decodeUtf8 key
  onPost "/api/exchange/:key" $ authed a \creds -> do
    key <- pathParam "key"
    satisfyOrder a.db.conn key creds.twitchId
  onDelete "/api/exchange/:key" $ authed a \_creds -> do
    key <- pathParam "key"
    cancelOrder a.db.conn key

adjustUserCurrency :: Text -> Text -> Integer -> Redis.Redis ()
adjustUserCurrency user cur amt = do
  let key = "currency:" <> encodeUtf8 user
  let ecur = encodeUtf8 cur
  mold <- hush <$> Redis.hget key ecur
  let old = case mold of
        Just (Just o) -> case Text.R.decimal $ decodeUtf8 o of
          Right (num, _) -> num
          _else -> 0
        _else -> 0
  void . Redis.hset key ecur . encodeUtf8 . tshow $ old + amt

data Order = Order
  { creator :: !Text
  , wantCur :: !Text
  , wantAmount :: !Integer
  , haveCur :: !Text
  , haveAmount :: !Integer
  } deriving Generic
instance Aeson.ToJSON Order
instance Aeson.FromJSON Order

createOrder :: MonadIO m => Redis.Connection -> Order -> m ByteString
createOrder c o = liftIO $ Redis.runRedis c do
  let bs = Aeson.encode o
  uuid <- liftIO UUID.nextRandom
  let key = BS.Lazy.toStrict $ UUID.toByteString uuid
  void $ Redis.hset "orders" key (BS.Lazy.toStrict bs)
  pure key

getOrders :: MonadIO m => Redis.Connection -> m [Order]
getOrders c = liftIO $ Redis.runRedis c do
  Redis.hvals "orders" >>= \case
    Left _ -> pure []
    Right orders -> pure $ mapMaybe (Aeson.decode' . BS.Lazy.fromStrict) orders

cancelOrder :: MonadIO m => Redis.Connection -> ByteString -> m ()
cancelOrder c key = liftIO $ Redis.runRedis c do
  void $ Redis.hdel "orders" [key]

satisfyOrder :: MonadIO m => Redis.Connection -> ByteString -> Text -> m ()
satisfyOrder c key buyer = liftIO $ Redis.runRedis c do
  Redis.hget "orders" key >>= \case
    Right (Just bs) -> case Aeson.decode' $ BS.Lazy.fromStrict bs of
      Nothing -> pure ()
      Just (order :: Order) -> do
        adjustUserCurrency buyer order.wantCur (-order.wantAmount)
        adjustUserCurrency order.creator order.wantCur order.wantAmount
        adjustUserCurrency buyer order.haveCur order.haveAmount
        adjustUserCurrency order.creator order.haveCur (-order.haveAmount)
    _else -> pure ()