on c050296 help sign in
17 files changed+399−269
since splitsnapshots
unifiedsplit
LICENSE Procfile Setup.hs app.json app/Main.hs+11−4 docs.md docs/Docs.hs+23−23 package.yaml+7 readme.md+87−101 src/Data/CyclicBuffer.hs src/Data/Error.hs src/Foo/Data.hs+173−89 src/Lib.hs+90−49 src/Network/Bacon.hs stack.yaml+7−2 swagger/Swagger.hs+1−1 test/Spec.hs
LICENSEmode 100755 → 100644

Only the mode changed.

Procfilemode 100755 → 100644

Only the mode changed.

Setup.hsmode 100755 → 100644

Only the mode changed.

app.jsonmode 100755 → 100644

Only the mode changed.

app/Main.hsmode 100755 → 100644+11−4
@@ -1,3 +1,5 @@
1+-- {-# LANGUAGE OverloadedStrings #-}
2+
13module Main where
24
35import Network.Wai.Handler.Warp (run)
@@ -5,12 +7,17 @@ import Servant.Server (serve)
57import System.Environment
68import Lib
79import Network.Wai.Middleware.Cors
10+import Database.Beam.Postgres (connectPostgreSQL)
11+import Data.Maybe
12+import qualified Data.ByteString.Char8 as B
813
914main :: IO ()
1015main = do
11− env <- lookupEnv "PORT"
12− let port = maybe 2000 read env :: Int
16+ mPort <- lookupEnv "PORT"
17+ let port = maybe 2000 read mPort :: Int
1318 putStrLn $ "Starting on port: " ++ show port
14− print env
15− srv <- createServer
19+ mDatabaseURL <- lookupEnv "DATABASE_URL"
20+ let databaseURL = fromMaybe "host=localhost port=5432 dbname=postgres connect_timeout=10" mDatabaseURL
21+ conn <- connectPostgreSQL $ B.pack databaseURL
22+ srv <- createServer conn
1623 run port $ simpleCors $ serve server srv
docs.mdmode 100755 → 100644

Only the mode changed.

docs/Docs.hsmode 100755 → 100644+23−23
@@ -19,34 +19,34 @@ import Data.Foldable (fold)
1919import Servant.API.WebSocket
2020import Control.Lens
2121
22−main = writeFile "docs.md" . markdown . docs . pretty $ server
22+main = return () -- writeFile "docs.md" . markdown . docs . pretty $ server
2323
24−-- instance ToSample User
25−instance ToCapture (Capture "first" T.Text) where
26− toCapture _ = DocCapture "first" "The first name of the user."
24+-- -- instance ToSample User
25+-- instance ToCapture (Capture "first" T.Text) where
26+-- toCapture _ = DocCapture "first" "The first name of the user."
2727
28−instance ToSample Foo.Data.Post where
29− toSamples _ = samples $ Post `fmap` [1, 4, 7, 194] <*> [4, 7, 92] <*> ["This is the post body", "This is another post body"]
28+-- instance ToSample Foo.Data.Post where
29+-- toSamples _ = samples $ Post `fmap` [1, 4, 7, 194] <*> [4, 7, 92] <*> ["This is the post body", "This is another post body"]
3030
31−instance ToSample User where
32− toSamples _ = samples [
33− User (UserName "Seva" "Leonidovich") 1,
34− User (UserName "Foo" "Barovich") 42,
35− User (UserName "Bar" "Fooovich") 69,
36− User (UserName "Qux" "Quixovich") 8]
31+-- instance ToSample User where
32+-- toSamples _ = samples [
33+-- User (UserName "Seva" "Leonidovich") 1,
34+-- User (UserName "Foo" "Barovich") 42,
35+-- User (UserName "Bar" "Fooovich") 69,
36+-- User (UserName "Qux" "Quixovich") 8]
3737
38−instance ToCapture (Capture "second" T.Text) where
39− toCapture _ = DocCapture "second" "The second name of the user."
38+-- instance ToCapture (Capture "second" T.Text) where
39+-- toCapture _ = DocCapture "second" "The second name of the user."
4040
41−instance ToSample (TokenizedResponse [Foo.Data.Post]) where
42− toSamples _ = samples $ TokenizedResponse `fmap` [1, 46, 193, 7] <*> [map snd $ toSamples (Proxy :: Proxy Foo.Data.Post)]
41+-- instance ToSample (TokenizedResponse [Foo.Data.Post]) where
42+-- toSamples _ = samples $ TokenizedResponse `fmap` [1, 46, 193, 7] <*> [map snd $ toSamples (Proxy :: Proxy Foo.Data.Post)]
4343
44−instance ToCapture (Capture "id" Int) where
45− toCapture _ = DocCapture "id" "The id of the entity you want to query."
44+-- instance ToCapture (Capture "id" Int) where
45+-- toCapture _ = DocCapture "id" "The id of the entity you want to query."
4646
47−instance ToCapture (Capture "token" Int) where
48− toCapture _ = DocCapture "token" "The token, recieved wit the previuos request"
47+-- instance ToCapture (Capture "token" Int) where
48+-- toCapture _ = DocCapture "token" "The token, recieved wit the previuos request"
4949
50−instance HasDocs WebSocketPending where
51− docsFor Proxy (endpoint, action) =
52− docsFor (Proxy :: Proxy Raw) (endpoint, action & notes <>~ [DocNote "title" ["not title"]])
50+-- instance HasDocs WebSocketPending where
51+-- docsFor Proxy (endpoint, action) =
52+-- docsFor (Proxy :: Proxy Raw) (endpoint, action & notes <>~ [DocNote "title" ["not title"]])
package.yamlmode 100755 → 100644+7
@@ -37,6 +37,13 @@ dependencies:
3737- websockets
3838- lens
3939- wai-cors
40+- beam-core
41+- beam-postgres
42+- postgresql-simple
43+- random-shuffle
44+- socket-io
45+- engine-io
46+- engine-io-wai
4047
4148library:
4249 source-dirs: src
readme.mdmode 100755 → 100644+87−101
@@ -6,64 +6,9 @@ _url: [`https://websuck1t.herokuapp.com`](https://websuck1t.herokuapp.com)_
66
77## GET /posts/all
88
9−### Response:
10−
11−- Status code 200
12−- Headers: []
13−
14−- Supported content types are:
15−
16− - `application/json`
17−
18−- Example (`application/json`):
19−
20−```javascript
21−{
22− "token": 1,
23− "response": [
24− {
25− "postBody": "This is the post body",
26− "postAuthor": 4,
27− "postId": 1
28− },
29− {
30− "postBody": "This is another post body",
31− "postAuthor": 4,
32− "postId": 2
33− },
34− {
35− "postBody": "This is the post body",
36− "postAuthor": 7,
37− "postId": 3
38− },
39− {
40− "postBody": "This is another post body",
41− "postAuthor": 7,
42− "postId": 4
43− },
44− {
45− "postBody": "This is the post body",
46− "postAuthor": 92,
47− "postId": 5
48− },
49− {
50− "postBody": "This is another post body",
51− "postAuthor": 92,
52− "postId": 6
53− }
54− ]
55−}
56−```
57−
58−## GET /users/:id
59−
60−Retrieves a user with the given `id`.
61−
62−#### If there is no user with the given `id`, the method returns error `404`.
9+_start with: [`https://websuck1t.herokuapp.com/posts/all`](https://websuck1t.herokuapp.com/posts/all)_
6310
64−### Captures:
65−
66−- *id*: The id of the entity you want to query.
11+Use this _url_ to get the token every time when application begins and previous session was closed
6712
6813### Response:
6914
@@ -78,64 +23,41 @@ Retrieves a user with the given `id`.
7823
7924```javascript
8025{
81− "userName": {
82− "secondName": "Leonidovich",
83− "firstName": "Seva"
84− },
85− "userId": 1
26+ "token": 1828,
27+ "response": {
28+ "deleted": [],
29+ "added": [
30+ 1844,
31+ 1845,
32+ 1846
33+ ]
34+ }
8635}
87−```
88−
89−- Example (`application/json`):
9036
91−```javascript
92−{
93− "userName": {
94− "secondName": "Barovich",
95− "firstName": "Foo"
96− },
97− "userId": 42
98−}
9937```
10038
101−- Example (`application/json`):
39+### Response fields
10240
103−```javascript
104−{
105− "userName": {
106− "secondName": "Fooovich",
107− "firstName": "Bar"
108− },
109− "userId": 69
110−}
111−```
41+- "token" to further use it in subscription to web socket
42+- "response" to start with the given number of posts in table
11243
113−- Example (`application/json`):
44+## socket.io /posts/subscribe/
11445
115−```javascript
116−{
117− "userName": {
118− "secondName": "Quixovich",
119− "firstName": "Qux"
120− },
121− "userId": 8
122−}
123−```
46+_NOTE: subscribe to the `"postUpdates"` event before submitting the token to `"subscribeWithToken"`._
12447
125−## WebSocket /posts/subscribe/:token
48+You access the web socket with the socket io protocol. You will most likely need to install a library.
12649
127−You access the web socket with the web socket protocol. I. e. `https://` -> `ws://`.
50+### Connecting
12851
129−Open a web socket from the given `token` for receiving events about new and deleted posts.
52+To start recieving events you send a `"subscribeWithToken"` event with a single `Int` as the messages body. The `Int` should be the last _token_ you recieved from any API.
13053
131−#### Returns error `498` if the supplied token has expired or is invalid.
54+#### Response
13255
56+After sending the request, _you_ will receive a `"subscribeWithToken"` event with a single `Bool` in the message of the event. The `Bool` indicates whether you have supplied a valid token. If the response is `true`, you are now subscribed to recieve updates. If thge response was `false`, nothing happens, you will not be sent any updates.
13357
134−### Captures:
135−
136−- *token*: The token, received with the previous request.
58+### Receiving updates
13759
138−### Response:
60+Updates will be sent to you on the `"postUpdates"` event. The event message will contain the following JSON Object (same as `/posts/all`):
13961
14062```javascript
14163{
@@ -157,6 +79,8 @@ Open a web socket from the given `token` for receiving events about new and dele
15779
15880## GET /posts/:id
15981
82+_url: [`https://websuck1t.herokuapp.com/posts/{id}`](https://websuck1t.herokuapp.com/posts/1)_
83+
16084#### If there is no post with the given `id`, the method returns error `404`.
16185
16286### Captures:
@@ -221,3 +145,65 @@ Open a web socket from the given `token` for receiving events about new and dele
221145 "postId": 1
222146}
223147```
148+
149+
150+## GET /users/:id
151+
152+_url: [`https://websuck1t.herokuapp.com/users/{id}`](https://websuck1t.herokuapp.com/users/1)_
153+
154+Retrieves a user with the given `id`.
155+
156+#### If there is no user with the given `id`, the method returns error `404`.
157+
158+### Captures:
159+
160+- *id*: The id of the entity you want to query.
161+
162+### Response:
163+
164+- Status code 200
165+- Headers: []
166+
167+- Supported content types are:
168+
169+ - `application/json`
170+
171+- Example (`application/json`):
172+
173+```javascript
174+{
175+ "secondName": "Leonidovich",
176+ "firstName": "Seva",
177+ "userId": 1
178+}
179+```
180+
181+- Example (`application/json`):
182+
183+```javascript
184+{
185+ "secondName": "Barovich",
186+ "firstName": "Foo",
187+ "userId": 42
188+}
189+```
190+
191+- Example (`application/json`):
192+
193+```javascript
194+{
195+ "secondName": "Fooovich",
196+ "firstName": "Bar",
197+ "userId": 69
198+}
199+```
200+
201+- Example (`application/json`):
202+
203+```javascript
204+{
205+ "secondName": "Quixovich",
206+ "firstName": "Qux",
207+ "userId": 8
208+}
209+```
src/Data/CyclicBuffer.hsmode 100755 → 100644

Only the mode changed.

src/Data/Error.hsmode 100755 → 100644

Only the mode changed.

src/Foo/Data.hsmode 100755 → 100644+173−89
@@ -1,5 +1,12 @@
11{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE GADTs #-}
23{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeApplications #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
9+
310
411module Foo.Data
512 (
@@ -9,7 +16,6 @@ module Foo.Data
916 createApp,
1017 createConfig,
1118 User(..),
12− UserName(..),
1319 Config,
1420 updates,
1521 allUsers,
@@ -17,12 +23,16 @@ module Foo.Data
1723 UserId,
1824 PostId,
1925 Post(..),
26+ postId,
2027 allPosts,
2128 addConnection,
2229 removeConnection,
2330 TokenizedResponse(..),
2431 Update(..),
25− getToken
32+ getToken,
33+ Token,
34+ subscribeWithToken,
35+ postUpdates
2636 ) where
2737
2838import GHC.Conc
@@ -50,6 +60,14 @@ import qualified Data.CyclicBuffer as CB
5060import Data.Set ((\\), union)
5161import Data.Monoid
5262import Debug.Trace
63+import Database.Beam
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)
5371
5472type App = ReaderT Config Handler
5573
@@ -57,35 +75,35 @@ class HasConfig c where
5775 getCfg :: c -> Config
5876
5977allUsers :: 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
6179
6280post :: PostId -> App Post
6381post pId = do
64− cfg <- ask
65− u <- liftIO . atomically . fmap (M.lookup pId) . readTVar . posts $ cfg
82+ conn <- asks dbConn
83+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (PostId pId) . posts $ socialDb
6684 case u of
6785 Just u -> return u
6886 Nothing -> throwError err404
6987
7088user :: UserId -> App User
7189user uId = do
72− cfg <- ask
73− u <- liftIO . atomically . fmap (M.lookup uId) . readTVar . users $ cfg
90+ conn <- asks dbConn
91+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (UserId uId) . users $ socialDb
7492 case u of
7593 Just u -> return u
7694 Nothing -> throwError err404
7795
78−allPosts :: App (STM [Post])
79−allPosts = asks $ fmap M.elems . readTVar . posts
96+allPosts :: App [Post]
97+allPosts = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . posts $ socialDb
8098
8199getToken :: App (STM Token)
82100getToken = asks $ fmap CB.lastToken . readTVar . updates
83101
84−createConfig :: IO Config
85−createConfig = do
102+createConfig :: P.Connection -> IO Config
103+createConfig conn = do
86104 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)
88− forkIO . forever $ threadDelay 2000000 >> (runHandler . createApp cfg $ startAddingPosts 20 cfg >>= uncurry sendUpdate)
105+ cfg <- atomically $ Config <$> return conn <*> return mng <*> newTVar M.empty <*> newTVar 0 <*> newTVar (CB.empty 20)
106+ forkIO . forever $ threadDelay 7000000 >> (runHandler . createApp cfg $ startAddingPosts 25 cfg >>= uncurry sendUpdate)
89107 return cfg
90108
91109type Token = Int
@@ -116,18 +134,32 @@ instance Semigroup Update where
116134instance Monoid Update where
117135 mempty = Update S.empty S.empty
118136
119−addConnection :: HasConfig c => c -> Connection -> STM ConnectionId
137+addConnection :: HasConfig c => c -> Socket -> STM SocketId
120138addConnection c conn = do
121139 let cfg = getCfg c
122− lastID <- fmap (+1) . readTVar . lastConnection $ cfg
123− flip writeTVar lastID . lastConnection $ cfg
124− modifyTVar (connections cfg) $ M.insert lastID conn
125− return lastID
140+ let sId = socketId conn
141+ -- lastID <- fmap (+1) . readTVar . lastConnection $ cfg
142+ -- flip writeTVar lastID . lastConnection $ cfg
143+ modifyTVar (connections cfg) $ M.insert sId conn
144+ return sId
126145
127−removeConnection :: HasConfig c => c -> ConnectionId -> STM ()
128−removeConnection c cId = do
146+removeConnection :: HasConfig c => c -> Socket -> STM ()
147+removeConnection c conn = do
129148 let cfg = getCfg c
130− modifyTVar (connections cfg) $ M.delete cId
149+ let sId = socketId conn
150+ modifyTVar (connections cfg) $ M.delete sId
151+
152+
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
158+-- -- return ()
159+-- return ()
160+
161+-- unsubscribe :: MonadIO m => Config -> Socket -> m ()
162+-- unsubscribe cfg conn = liftIO . atomically $ removeConnection cfg cId
131163
132164
133165
@@ -139,12 +171,9 @@ type PostId = Int
139171type ConnectionId = Int
140172
141173data Config = Config {
142− lastUserID :: TVar UserId,
143− lastPostID :: TVar PostId,
144− users :: TVar (M.Map Int User),
145− posts :: TVar (M.Map Int Post),
174+ dbConn :: P.Connection,
146175 manager :: Manager,
147− connections :: TVar (M.Map ConnectionId Connection),
176+ connections :: TVar (M.Map SocketId Socket),
148177 lastConnection :: TVar ConnectionId,
149178 updates :: TVar (CB.CyclicBuffer Update)
150179}
@@ -152,94 +181,149 @@ data Config = Config {
152181instance HasConfig Config where
153182 getCfg = id
154183
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)
160−-- return ()
161−
162184startAddingPosts :: Int -> Config -> App (Token, Update)
163185startAddingPosts lim cfg = do
164186 cfg <- ask
165− let us = users cfg
166− let ps = posts cfg
187+ let conn = dbConn cfg
188+ -- let us = users socialDb
189+ -- let ps = posts socialDb
167190
168191 a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just lim) (Just 0)
169192 g <- liftIO newStdGen
170193 let rns = randoms g
171−
194+
172195 let t = fromMaybe (repeat "None") a
173− liftIO . atomically $ do
174− uu <- readTVar us
175− let uSize = M.size uu
176− if uSize >= 1 then do
177− pp <- readTVar ps
178− let toDeleteIndex = head rns `mod` M.size pp
179− let deletedKey = fst $ M.elemAt toDeleteIndex pp
180− dd <- if M.size pp > 0 then writeTVar ps (M.deleteAt toDeleteIndex pp) >> return [deletedKey]
181− else return []
182− let toAdd = max 0 $ lim - (M.size pp - 1)
183− add <- fmap (map postId) $ forM (zip rns $ take toAdd t) $ \(rnd, t) -> newPost cfg (randomFrom rnd uu) t
184− let upd = Update (S.fromList add) (S.fromList dd)
185− let uppT = updates cfg
186− upp <- readTVar uppT
187− let (token, upp') = CB.insert upp upd
188− writeTVar uppT upp'
189− return (token, upd)
190− else return (0, mempty)
196+
197+ let deleteCount = 3
198+
199+ (deletedIds, addedIds) <- join . liftIO . withTransaction conn . return $ do
200+ userIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
201+ uc <- all_ . users $ socialDb
202+ pure (userId uc)
203+
204+ if length userIds <= 0 then return ([],[])
205+ else do
206+ postIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
207+ uc <- all_ . posts $ socialDb
208+ pure (postId uc)
209+
210+
211+ let postCount = length postIds
212+ let toDeleteIndecies = if postCount == 0 then [] else take deleteCount $ shuffle' postIds postCount g
213+
214+ let toAdd = max 0 $ lim - length postIds + length toDeleteIndecies
215+
216+ -- Just deletedPost <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . select . limit_ 1 . offset_ (fromIntegral $ toDeleteIndex - 1) . all_ . posts $ socialDb
217+ -- let deletedId = postId deletedPost
218+
219+ unless (length toDeleteIndecies <= 0) $
220+ liftIO . P.runBeamPostgres conn . runDelete $
221+ delete (posts socialDb) ((`in_` map val_ toDeleteIndecies) . postId)
222+
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)
224+
225+ return (toDeleteIndecies, map postId nIds)
226+
227+ liftIO . atomically $ do
228+ let upd = Update (S.fromList addedIds) (S.fromList deletedIds)
229+ let uppT = updates cfg
230+ upp <- readTVar uppT
231+ let (token, upp') = CB.insert upp upd
232+ writeTVar uppT upp'
233+ return (token, upd)
191234
192235sendUpdate :: Token -> Update -> App ()
193236sendUpdate t u = do
194237 cfg <- ask
195238 conns <- fmap M.elems . liftIO . readTVarIO $ connections cfg
196− liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
239+ forM conns $ \ conn -> emitTo conn postUpdates $ TokenizedResponse t u
240+ -- liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
197241 return ()
198242
243+modIndex :: [a] -> Int -> a
244+modIndex aa i = aa !! (i `mod` length aa)
245+
246+infixl 5 %!
247+(%!) = modIndex
248+
249+
199250randomFrom :: Int -> M.Map k v -> k
200251randomFrom g as = M.keys as !! (abs g `mod` M.size as)
201252
202−newUser :: Config -> Text -> Text -> STM User
253+newUser :: Config -> Text -> Text -> IO [User]
203254newUser cfg first second = do
204− let lastID = lastUserID cfg
205− let us = users cfg
206− do
207− id <- readTVar lastID
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
212− return user
213−
214−newPost :: Config -> UserId -> Text -> STM Post
215−newPost cfg user body = do
216− let lastId = lastPostID cfg
217− let ps = posts cfg
218− do
219− id <- readTVar lastId
220− writeTVar lastId (id+1)
221− let p = Post id user body
222− modifyTVar ps $ M.insert id p
223− return p
224−
225−data User = User {
226− userName :: UserName,
227− userId :: UserId
255+ let conn = dbConn cfg
256+ P.runBeamPostgres conn . runInsertReturningList (users socialDb) $ insertExpressions [User default_ (val_ first) (val_ second)]
257+
258+
259+-- newPost :: Config -> UserId -> Text -> STM Post
260+-- newPost cfg user body = do
261+-- let lastId = lastPostID cfg
262+-- let ps = posts cfg
263+-- do
264+-- id <- readTVar lastId
265+-- writeTVar lastId (id+1)
266+-- let p = Post id user body
267+-- modifyTVar ps $ M.insert id p
268+-- return p
269+
270+data UserT f = User {
271+ userId :: Columnar f UserId,
272+ firstName :: Columnar f Text,
273+ secondName :: Columnar f Text
228274} deriving Generic
229275
230−data Post = Post {
231− postId :: PostId,
232− postAuthor :: UserId,
233− postBody :: Text
276+instance Beamable UserT
277+
278+type User = UserT Identity
279+
280+instance Table UserT where
281+ data PrimaryKey UserT f = UserId (Columnar f UserId)
282+ deriving Generic
283+
284+ primaryKey = UserId . userId
285+
286+instance Beamable (PrimaryKey UserT)
287+
288+data PostT f = Post {
289+ postId :: Columnar f PostId,
290+ postAuthor :: Columnar f UserId,
291+ postBody :: Columnar f Text
234292} deriving Generic
235293
294+instance Beamable PostT
295+
296+type Post = PostT Identity
297+
236298instance ToJSON User
237299instance ToJSON Post
238300
239−data UserName = UserName {
240− firstName :: Text,
241− secondName :: Text
242−} deriving (Generic)
301+instance Table PostT where
302+ data PrimaryKey PostT f = PostId (Columnar f PostId)
303+ deriving Generic
304+
305+ primaryKey = PostId . postId
243306
244−instance ToJSON UserName
307+instance Beamable (PrimaryKey PostT)
308+
309+data SocialDb f = SocialDb {
310+ users :: f (TableEntity UserT),
311+ posts :: f (TableEntity PostT)
312+} deriving Generic
245313
314+instance Database be SocialDb
315+
316+socialDb :: DatabaseSettings be SocialDb
317+socialDb = defaultDbSettings `withDbModification`
318+ dbModification {
319+ users = modifyTable id $ tableModification {
320+ firstName = "firstname",
321+ secondName = "secondname"
322+ }
323+ }
324+
325+subscribeWithToken :: Text
326+subscribeWithToken = "subscribeWithToken"
327+postUpdates :: Text
328+postUpdates = "postUpdates"
329+
src/Lib.hsmode 100755 → 100644+90−49
@@ -1,6 +1,9 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE TypeOperators #-}
33{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+
47
58module Lib
69 ( server,
@@ -26,76 +29,114 @@ import Data.Aeson
2629import qualified Data.Set as S
2730import Data.CyclicBuffer
2831import Data.Foldable (fold)
29−
32+import qualified Database.Beam.Postgres as P
33+-- import Network.Wai
34+import Network.EngineIO.Wai (toWaiApplication, waiAPI)
35+import Network.SocketIO
36+import Control.Monad.State
37+import Debug.Trace
3038
3139type API = "users" :> Capture "id" UserId :> Get '[JSON] User
3240 :<|> "users" :> "add" :> Capture "first" Text :> Capture "second" Text :> Get '[JSON] [User]
3341 :<|> "users" :> "all" :> Get '[JSON] [User]
3442 :<|> "posts" :> Capture "id" PostId :> Get '[JSON] D.Post
35− :<|> "posts" :> "all" :> Get '[JSON] (TokenizedResponse [D.Post])
36− :<|> "posts" :> "subscribe" :> Capture "token" Int :> WebSocketPending
43+ :<|> "posts" :> "all" :> Get '[JSON] (TokenizedResponse Update)
44+ :<|> "posts" :> "subscribe" :> Raw --Capture "token" Int :> WebSocketPending
3745
38−createServer :: IO (Server API)
39−createServer = do
40− cfg <- createConfig
41− atomically $ newUser cfg "Kostya" "Designerovich"
42− atomically $ newUser cfg "Anna" "Androidovna"
43− atomically $ newUser cfg "Darya" "Aiphonovna"
44− atomically $ newUser cfg "Ilya" "Serverovich"
45− return $ hoistServer server (createApp cfg) serverAPI
46+createServer :: P.Connection -> IO (Server API)
47+createServer pConn = do
48+ cfg <- createConfig pConn
49+ -- newUser cfg "Kostya" "Designerovich"
50+ -- newUser cfg "Anna" "Androidovna"
51+ -- newUser cfg "Darya" "Aiphonovna"
52+ -- newUser cfg "Ilya" "Serverovich"
53+ -- newUser cfg "Константин" "Дудосерович"
54+ subscribePostsEndpoint <- subscribePosts cfg
55+ let app = toWaiApplication subscribePostsEndpoint :: Application
56+ let sAPI = serverAPI app
57+ return $ hoistServer server (createApp cfg) sAPI
4658
4759server :: Proxy API
4860server = Proxy
4961
50−serverAPI :: ServerT API App
51−serverAPI =
52− user
53− :<|> createUserPath
54− :<|> allUsers
55− :<|> post
56− :<|> returnPosts
57− :<|> validateConnection
62+serverAPI :: Application -> ServerT API App
63+serverAPI app = user
64+ :<|> createUserPath
65+ :<|> allUsers
66+ :<|> post
67+ :<|> returnPosts
68+ :<|> pure app
69+ -- :<|> validateConnection
5870
5971createUserPath :: Text -> Text -> App [User]
6072createUserPath first second = do
6173 cfg <- ask
62− liftIO . atomically $ newUser cfg first second
74+ liftIO $ newUser cfg first second
6375 allUsers
6476
65−returnPosts :: App (TokenizedResponse [D.Post])
77+returnPosts :: App (TokenizedResponse Update)
6678returnPosts = do
6779 posts <- allPosts
6880 t <- getToken
69− liftIO $ atomically $ TokenizedResponse <$> t <*> posts
81+ liftIO $ atomically $ TokenizedResponse <$> t <*> (pure $ flip Update (S.empty) $ S.fromList $ Prelude.map D.postId posts)
7082
71−invalidTokenError :: RejectRequest
72−invalidTokenError = defaultRejectRequest {
73− rejectCode = 498,
74− rejectMessage = "The supplied token is not valid" }
83+-- invalidTokenError :: RejectRequest
84+-- invalidTokenError = defaultRejectRequest {
85+-- rejectCode = 498,
86+-- rejectMessage = "The supplied token is not valid" }
7587
76−validateConnection :: Int -> PendingConnection -> App ()
77−validateConnection t pc = do
78− cfg <- ask
79− (cyc, token) <- liftIO $ atomically $ do
80− uu <- readTVar $ updates cfg
81− return (getFrom uu t, lastToken uu)
82− case cyc of
83− Nothing -> liftIO $ rejectRequestWith pc invalidTokenError
84− Just uu -> do
85− conn <- liftIO $ acceptRequest pc
86− liftIO $ sendTextData conn $ encode $ TokenizedResponse token $ fold uu
87− subscribe conn
88− return ()
89−
90−subscribe :: Connection -> App ()
91−subscribe conn = do
92− cfg <- ask
93− cId <- liftSTM $ addConnection cfg conn
94− -- liftIO $ sendDataMessage conn . Binary . encode $ D.TokenizedResponse 1 (Update (S.fromList [1, 2, 3]) (S.fromList [4, 2,6]))
95− liftIO $ flip finally (atomically $ removeConnection cfg cId) $ forever $ do
96− _ <- receiveDataMessage conn
97− return ()
98− return ()
88+-- validToken :: Token -> App Bool
89+-- validToken t pc = do
90+-- cfg <- ask
91+-- (cyc, token) <- liftIO $ atomically $ do
92+-- uu <- readTVar $ updates cfg
93+-- return (getFrom uu t, lastToken uu)
94+-- return $ case cyc of
95+-- Nothing -> False
96+-- Just uu -> True
97+
98+
99+
100+-- subscribePosts :: Config -> IO (m())
101+subscribePosts cfg = do
102+ handle <- initialize waiAPI $ subscriptionHandler cfg
103+ return handle
104+
105+-- subscriptionHandler :: MonadIO m => Config -> StateT RoutingTable (ReaderT Socket m) a
106+subscriptionHandler :: MonadState RoutingTable m => Config -> m ()
107+subscriptionHandler cfg = do
108+ on "test" $ \ (i::Int) -> do
109+ emit "test" $ Update (S.fromList [1,2,3]) (S.fromList [4,5,6])
110+
111+ appendDisconnectHandler $ do
112+ conn <- ask
113+ liftSTM $ removeConnection cfg conn
114+
115+
116+ on D.subscribeWithToken $ \ (t :: Token) -> do
117+ -- emit "subscribeWithToken" $ do
118+ conn <- ask
119+ (cyc, token) <- liftIO $ atomically $ do
120+ uu <- readTVar $ updates cfg
121+ return (getFrom uu t, lastToken uu)
122+ case cyc of
123+ Nothing -> emit D.subscribeWithToken False
124+ Just uu -> do
125+ emit D.postUpdates $ TokenizedResponse token $ fold uu
126+ sId <- liftSTM $ addConnection cfg conn
127+ emit subscribeWithToken True
128+
129+ -- if validToken t then do
130+ -- conn <- ask
131+ -- liftSTM $ do
132+ -- subscribe cfg conn
133+ -- uu <- readTVar $ updates cfg
134+ -- return (lastToken uu)
135+
136+ -- emit "postUpdates"
137+ -- else return false
99138
139+
140+
100141liftSTM :: MonadIO m => STM a -> m a
101142liftSTM = liftIO . atomically
src/Network/Bacon.hsmode 100755 → 100644

Only the mode changed.

stack.yamlmode 100755 → 100644+7−2
@@ -17,7 +17,7 @@
1717#
1818# resolver: ./custom-snapshot.yaml
1919# resolver: https://example.com/snapshots/2018-01-01.yaml
20−resolver: lts-13.1
20+resolver: lts-12.25
2121
2222# User packages to be built.
2323# Various formats can be used as shown in the example below.
@@ -37,7 +37,12 @@ packages:
3737# Dependency packages to be pulled from upstream that are not in the resolver
3838# using the same syntax as the packages field.
3939# (e.g., acme-missiles-0.3)
40−# extra-deps: []
40+extra-deps:
41+- beam-core-0.7.2.2
42+- beam-postgres-0.3.2.2
43+- socket-io-1.3.11
44+- engine-io-1.2.22
45+- engine-io-wai-1.0.9
4146
4247# Override default flag values for local packages and extra-deps
4348# flags: {}
swagger/Swagger.hsmode 100755 → 100644+1−1
@@ -19,6 +19,6 @@ import Data.Aeson
1919import qualified Data.ByteString.Lazy as BS
2020
2121instance ToSchema User
22−instance ToSchema UserName
22+-- instance ToSchema UserName
2323
2424main = return () -- BS.writeFile "swag.txt" . encode . toSwagger $ server
test/Spec.hsmode 100755 → 100644

Only the mode changed.