@@ -1,5 +1,12 @@
1{-# LANGUAGE DeriveGeneric #-}
1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeApplications #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
4module Foo.Data
11module Foo.Data
@@ -9,7 +16,6 @@ module Foo.Data
9 createApp,
16 createApp,
10 createConfig,
17 createConfig,
@@ -17,12 +23,16 @@ module Foo.Data
21 addConnection,
28 addConnection,
22 removeConnection,
29 removeConnection,
23 TokenizedResponse(..),
30 TokenizedResponse(..),
24 Update(..),
31 Update(..),
25− getToken
32+ getToken,
28import GHC.Conc
38import GHC.Conc
@@ -50,6 +60,14 @@ import qualified Data.CyclicBuffer as CB
50import Data.Set ((\\), union)
60import Data.Set ((\\), union)
51import Data.Monoid
61import Data.Monoid
52import Debug.Trace
62import Debug.Trace
64+import qualified Database.Beam.Postgres as P
65+import Database.Beam.Query
66+import Database.PostgreSQL.Simple (withTransaction)
67+import Database.Beam.Backend.SQL.BeamExtensions
68+import System.Random.Shuffle
69+import Network.SocketIO
70+import Network.EngineIO (SocketId)
54type App = ReaderT Config Handler
72type App = ReaderT Config Handler
@@ -57,35 +75,35 @@ class HasConfig c where
57 getCfg :: c -> Config
75 getCfg :: c -> Config
59allUsers :: App [User]
77allUsers :: App [User]
60−allUsers = ask >>= liftIO . fmap M.elems . readTVarIO . users
78+allUsers = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . users $ socialDb
62post :: PostId -> App Post
80post :: PostId -> App Post
63post pId = do
81post pId = do
64− cfg <- ask
82+ conn <- asks dbConn
65− u <- liftIO . atomically . fmap (M.lookup pId) . readTVar . posts $ cfg
83+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (PostId pId) . posts $ socialDb
67 Just u -> return u
85 Just u -> return u
68 Nothing -> throwError err404
86 Nothing -> throwError err404
70user :: UserId -> App User
88user :: UserId -> App User
71user uId = do
89user uId = do
72− cfg <- ask
90+ conn <- asks dbConn
73− u <- liftIO . atomically . fmap (M.lookup uId) . readTVar . users $ cfg
91+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (UserId uId) . users $ socialDb
75 Just u -> return u
93 Just u -> return u
76 Nothing -> throwError err404
94 Nothing -> throwError err404
78−allPosts :: App (STM [Post])
96+allPosts :: App [Post]
79−allPosts = asks $ fmap M.elems . readTVar . posts
97+allPosts = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . posts $ socialDb
81getToken :: App (STM Token)
99getToken :: App (STM Token)
82getToken = asks $ fmap CB.lastToken . readTVar . updates
100getToken = asks $ fmap CB.lastToken . readTVar . updates
84−createConfig :: IO Config
102+createConfig :: P.Connection -> IO Config
85−createConfig = do
103+createConfig conn = do
86 mng <- newManager tlsManagerSettings
104 mng <- newManager tlsManagerSettings
87− cfg <- atomically $ Config <$> newTVar 0 <*> newTVar 0 <*> newTVar M.empty <*> newTVar M.empty <*> return mng <*> newTVar M.empty <*> newTVar 0 <*> newTVar (CB.empty 20)
105+ cfg <- atomically $ Config <$> return conn <*> return mng <*> newTVar M.empty <*> newTVar 0 <*> newTVar (CB.empty 20)
88− forkIO . forever $ threadDelay 2000000 >> (runHandler . createApp cfg $ startAddingPosts 20 cfg >>= uncurry sendUpdate)
106+ forkIO . forever $ threadDelay 7000000 >> (runHandler . createApp cfg $ startAddingPosts 25 cfg >>= uncurry sendUpdate)
89 return cfg
107 return cfg
91type Token = Int
109type Token = Int
@@ -116,18 +134,32 @@ instance Semigroup Update where
116instance Monoid Update where
134instance Monoid Update where
117 mempty = Update S.empty S.empty
135 mempty = Update S.empty S.empty
119−addConnection :: HasConfig c => c -> Connection -> STM ConnectionId
137+addConnection :: HasConfig c => c -> Socket -> STM SocketId
120addConnection c conn = do
138addConnection c conn = do
121 let cfg = getCfg c
139 let cfg = getCfg c
140+ let sId = socketId conn
122− lastID <- fmap (+1) . readTVar . lastConnection $ cfg
141+ -- lastID <- fmap (+1) . readTVar . lastConnection $ cfg
123− flip writeTVar lastID . lastConnection $ cfg
142+ -- flip writeTVar lastID . lastConnection $ cfg
124− modifyTVar (connections cfg) $ M.insert lastID conn
143+ modifyTVar (connections cfg) $ M.insert sId conn
125− return lastID
144+ return sId
127−removeConnection :: HasConfig c => c -> ConnectionId -> STM ()
146+removeConnection :: HasConfig c => c -> Socket -> STM ()
128−removeConnection c cId = do
147+removeConnection c conn = do
129 let cfg = getCfg c
148 let cfg = getCfg c
149+ let sId = socketId conn
130− modifyTVar (connections cfg) $ M.delete cId
150+ modifyTVar (connections cfg) $ M.delete sId
153+-- subscribe :: MonadIO m => Config -> Socket -> m ()
154+-- subscribe cfg conn = do
155+-- cId <- liftSTM $ addConnection cfg conn
156+-- -- liftIO $ flip finally (atomically $ removeConnection cfg cId) $ forever $ do
157+-- -- _ <- receiveDataMessage conn
161+-- unsubscribe :: MonadIO m => Config -> Socket -> m ()
162+-- unsubscribe cfg conn = liftIO . atomically $ removeConnection cfg cId
@@ -139,12 +171,9 @@ type PostId = Int
139type ConnectionId = Int
171type ConnectionId = Int
141data Config = Config {
173data Config = Config {
142− lastUserID :: TVar UserId,
174+ dbConn :: P.Connection,
143− lastPostID :: TVar PostId,
144− users :: TVar (M.Map Int User),
145− posts :: TVar (M.Map Int Post),
146 manager :: Manager,
175 manager :: Manager,
147− connections :: TVar (M.Map ConnectionId Connection),
176+ connections :: TVar (M.Map SocketId Socket),
148 lastConnection :: TVar ConnectionId,
177 lastConnection :: TVar ConnectionId,
149 updates :: TVar (CB.CyclicBuffer Update)
178 updates :: TVar (CB.CyclicBuffer Update)
@@ -152,94 +181,149 @@ data Config = Config {
152instance HasConfig Config where
181instance HasConfig Config where
153 getCfg = id
182 getCfg = id
155−-- startAddingUsers :: Config -> App ()
156−-- startAddingUsers cfg = do
157−-- a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just 2) (Just False)
158−-- let t = fromMaybe ["None", "None"] a
159−-- liftIO . atomically $ newUser cfg (t !! 0) (t !! 1)
162startAddingPosts :: Int -> Config -> App (Token, Update)
184startAddingPosts :: Int -> Config -> App (Token, Update)
163startAddingPosts lim cfg = do
185startAddingPosts lim cfg = do
164 cfg <- ask
186 cfg <- ask
187+ let conn = dbConn cfg
165− let us = users cfg
188+ -- let us = users socialDb
166− let ps = posts cfg
189+ -- let ps = posts socialDb
168 a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just lim) (Just 0)
191 a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just lim) (Just 0)
169 g <- liftIO newStdGen
192 g <- liftIO newStdGen
170 let rns = randoms g
193 let rns = randoms g
172 let t = fromMaybe (repeat "None") a
195 let t = fromMaybe (repeat "None") a
173− liftIO . atomically $ do
196+
174− uu <- readTVar us
197+ let deleteCount = 3
175− let uSize = M.size uu
198+
176− if uSize >= 1 then do
199+ (deletedIds, addedIds) <- join . liftIO . withTransaction conn . return $ do
177− pp <- readTVar ps
200+ userIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
178− let toDeleteIndex = head rns `mod` M.size pp
201+ uc <- all_ . users $ socialDb
179− let deletedKey = fst $ M.elemAt toDeleteIndex pp
202+ pure (userId uc)
180− dd <- if M.size pp > 0 then writeTVar ps (M.deleteAt toDeleteIndex pp) >> return [deletedKey]
203+
181− else return []
204+ if length userIds <= 0 then return ([],[])
206+ postIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
207+ uc <- all_ . posts $ socialDb
211+ let postCount = length postIds
212+ let toDeleteIndecies = if postCount == 0 then [] else take deleteCount $ shuffle' postIds postCount g
182− let toAdd = max 0 $ lim - (M.size pp - 1)
214+ let toAdd = max 0 $ lim - length postIds + length toDeleteIndecies
183− add <- fmap (map postId) $ forM (zip rns $ take toAdd t) $ \(rnd, t) -> newPost cfg (randomFrom rnd uu) t
215+
216+ -- Just deletedPost <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . select . limit_ 1 . offset_ (fromIntegral $ toDeleteIndex - 1) . all_ . posts $ socialDb
217+ -- let deletedId = postId deletedPost
219+ unless (length toDeleteIndecies <= 0) $
220+ liftIO . P.runBeamPostgres conn . runDelete $
221+ delete (posts socialDb) ((`in_` map val_ toDeleteIndecies) . postId)
223+ nIds <- liftIO . P.runBeamPostgres conn . runInsertReturningList (posts socialDb) $ insertExpressions $ take toAdd . flip map (zip rns t) $ \(r, t) -> Post default_ (val_ (userIds %! r)) (val_ t)
225+ return (toDeleteIndecies, map postId nIds)
227+ liftIO . atomically $ do
184− let upd = Update (S.fromList add) (S.fromList dd)
228+ let upd = Update (S.fromList addedIds) (S.fromList deletedIds)
185− let uppT = updates cfg
229+ let uppT = updates cfg
186− upp <- readTVar uppT
230+ upp <- readTVar uppT
187− let (token, upp') = CB.insert upp upd
231+ let (token, upp') = CB.insert upp upd
188− writeTVar uppT upp'
232+ writeTVar uppT upp'
189− return (token, upd)
233+ return (token, upd)
190− else return (0, mempty)
192sendUpdate :: Token -> Update -> App ()
235sendUpdate :: Token -> Update -> App ()
193sendUpdate t u = do
236sendUpdate t u = do
194 cfg <- ask
237 cfg <- ask
195 conns <- fmap M.elems . liftIO . readTVarIO $ connections cfg
238 conns <- fmap M.elems . liftIO . readTVarIO $ connections cfg
239+ forM conns $ \ conn -> emitTo conn postUpdates $ TokenizedResponse t u
196− liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
240+ -- liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
197 return ()
241 return ()
243+modIndex :: [a] -> Int -> a
244+modIndex aa i = aa !! (i `mod` length aa)
199randomFrom :: Int -> M.Map k v -> k
250randomFrom :: Int -> M.Map k v -> k
200randomFrom g as = M.keys as !! (abs g `mod` M.size as)
251randomFrom g as = M.keys as !! (abs g `mod` M.size as)
202−newUser :: Config -> Text -> Text -> STM User
253+newUser :: Config -> Text -> Text -> IO [User]
203newUser cfg first second = do
254newUser cfg first second = do
204− let lastID = lastUserID cfg
255+ let conn = dbConn cfg
205− let us = users cfg
256+ P.runBeamPostgres conn . runInsertReturningList (users socialDb) $ insertExpressions [User default_ (val_ first) (val_ second)]
207− id <- readTVar lastID
258+
208− writeTVar lastID (id+1)
209− let user = User (UserName first second) id
210− oldUsers <- readTVar us
211− writeTVar us $ M.insert id user oldUsers
214−newPost :: Config -> UserId -> Text -> STM Post
259+-- newPost :: Config -> UserId -> Text -> STM Post
215−newPost cfg user body = do
260+-- newPost cfg user body = do
216− let lastId = lastPostID cfg
261+-- let lastId = lastPostID cfg
217− let ps = posts cfg
262+-- let ps = posts cfg
219− id <- readTVar lastId
264+-- id <- readTVar lastId
220− writeTVar lastId (id+1)
265+-- writeTVar lastId (id+1)
221− let p = Post id user body
266+-- let p = Post id user body
222− modifyTVar ps $ M.insert id p
267+-- modifyTVar ps $ M.insert id p
223− return p
268+-- return p
225−data User = User {
270+data UserT f = User {
226− userName :: UserName,
227− userId :: UserId
271+ userId :: Columnar f UserId,
272+ firstName :: Columnar f Text,
273+ secondName :: Columnar f Text
228} deriving Generic
274} deriving Generic
230−data Post = Post {
276+instance Beamable UserT
278+type User = UserT Identity
280+instance Table UserT where
281+ data PrimaryKey UserT f = UserId (Columnar f UserId)
284+ primaryKey = UserId . userId
286+instance Beamable (PrimaryKey UserT)
288+data PostT f = Post {
231− postId :: PostId,
289+ postId :: Columnar f PostId,
232− postAuthor :: UserId,
290+ postAuthor :: Columnar f UserId,
233− postBody :: Text
291+ postBody :: Columnar f Text
234} deriving Generic
292} deriving Generic
294+instance Beamable PostT
296+type Post = PostT Identity
236instance ToJSON User
298instance ToJSON User
237instance ToJSON Post
299instance ToJSON Post
239−data UserName = UserName {
301+instance Table PostT where
240− firstName :: Text,
302+ data PrimaryKey PostT f = PostId (Columnar f PostId)
241− secondName :: Text
303+ deriving Generic
242−} deriving (Generic)
304+
305+ primaryKey = PostId . postId
244−instance ToJSON UserName
307+instance Beamable (PrimaryKey PostT)
309+data SocialDb f = SocialDb {
310+ users :: f (TableEntity UserT),
311+ posts :: f (TableEntity PostT)
314+instance Database be SocialDb
316+socialDb :: DatabaseSettings be SocialDb
317+socialDb = defaultDbSettings `withDbModification`
319+ users = modifyTable id $ tableModification {
320+ firstName = "firstname",
321+ secondName = "secondname"
325+subscribeWithToken :: Text
326+subscribeWithToken = "subscribeWithToken"
328+postUpdates = "postUpdates"