on master help sign in
rollback
commitb7b88842744a53b92d67ab04de299a76745b0d8a authoriko <ilyakooo0@gmail.com> dateThu 28 Feb 2019, 13:16 (8 years ago) parent9096b23
17 files changed+269−399
unifiedsplit
LICENSE Procfile Setup.hs app.json app/Main.hs+4−11 docs.md docs/Docs.hs+23−23 package.yaml−7 readme.md+101−87 src/Data/CyclicBuffer.hs src/Data/Error.hs src/Foo/Data.hs+89−173 src/Lib.hs+49−90 src/Network/Bacon.hs stack.yaml+2−7 swagger/Swagger.hs+1−1 test/Spec.hs
LICENSEmode 100644 → 100755

Only the mode changed.

Procfilemode 100644 → 100755

Only the mode changed.

Setup.hsmode 100644 → 100755

Only the mode changed.

app.jsonmode 100644 → 100755

Only the mode changed.

app/Main.hsmode 100644 → 100755+4−11
@@ -1,5 +1,3 @@
1−-- {-# LANGUAGE OverloadedStrings #-}
2−
31module Main where
42
53import Network.Wai.Handler.Warp (run)
@@ -7,17 +5,12 @@ import Servant.Server (serve)
75import System.Environment
86import Lib
97import Network.Wai.Middleware.Cors
10−import Database.Beam.Postgres (connectPostgreSQL)
11−import Data.Maybe
12−import qualified Data.ByteString.Char8 as B
138
149main :: IO ()
1510main = do
16− mPort <- lookupEnv "PORT"
17− let port = maybe 2000 read mPort :: Int
11+ env <- lookupEnv "PORT"
12+ let port = maybe 2000 read env :: Int
1813 putStrLn $ "Starting on port: " ++ show port
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
14+ print env
15+ srv <- createServer
2316 run port $ simpleCors $ serve server srv
docs.mdmode 100644 → 100755

Only the mode changed.

docs/Docs.hsmode 100644 → 100755+23−23
@@ -19,34 +19,34 @@ import Data.Foldable (fold)
1919import Servant.API.WebSocket
2020import Control.Lens
2121
22−main = return () -- writeFile "docs.md" . markdown . docs . pretty $ server
22+main = 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 100644 → 100755−7
@@ -37,13 +37,6 @@ 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
4740
4841library:
4942 source-dirs: src
readme.mdmode 100644 → 100755+101−87
@@ -6,9 +6,64 @@ _url: [`https://websuck1t.herokuapp.com`](https://websuck1t.herokuapp.com)_
66
77## GET /posts/all
88
9−_start with: [`https://websuck1t.herokuapp.com/posts/all`](https://websuck1t.herokuapp.com/posts/all)_
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`.
1063
11−Use this _url_ to get the token every time when application begins and previous session was closed
64+### Captures:
65+
66+- *id*: The id of the entity you want to query.
1267
1368### Response:
1469
@@ -23,41 +78,64 @@ Use this _url_ to get the token every time when application begins and previous
2378
2479```javascript
2580{
26− "token": 1828,
27− "response": {
28− "deleted": [],
29− "added": [
30− 1844,
31− 1845,
32− 1846
33− ]
34− }
81+ "userName": {
82+ "secondName": "Leonidovich",
83+ "firstName": "Seva"
84+ },
85+ "userId": 1
3586}
87+```
88+
89+- Example (`application/json`):
3690
91+```javascript
92+{
93+ "userName": {
94+ "secondName": "Barovich",
95+ "firstName": "Foo"
96+ },
97+ "userId": 42
98+}
3799```
38100
39−### Response fields
101+- Example (`application/json`):
40102
41−- "token" to further use it in subscription to web socket
42−- "response" to start with the given number of posts in table
103+```javascript
104+{
105+ "userName": {
106+ "secondName": "Fooovich",
107+ "firstName": "Bar"
108+ },
109+ "userId": 69
110+}
111+```
43112
44−## socket.io /posts/subscribe/
113+- Example (`application/json`):
45114
46−_NOTE: subscribe to the `"postUpdates"` event before submitting the token to `"subscribeWithToken"`._
115+```javascript
116+{
117+ "userName": {
118+ "secondName": "Quixovich",
119+ "firstName": "Qux"
120+ },
121+ "userId": 8
122+}
123+```
47124
48−You access the web socket with the socket io protocol. You will most likely need to install a library.
125+## WebSocket /posts/subscribe/:token
49126
50−### Connecting
127+You access the web socket with the web socket protocol. I. e. `https://` -> `ws://`.
51128
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.
129+Open a web socket from the given `token` for receiving events about new and deleted posts.
53130
54−#### Response
131+#### Returns error `498` if the supplied token has expired or is invalid.
55132
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.
57133
58−### Receiving updates
134+### Captures:
135+
136+- *token*: The token, received with the previous request.
59137
60−Updates will be sent to you on the `"postUpdates"` event. The event message will contain the following JSON Object (same as `/posts/all`):
138+### Response:
61139
62140```javascript
63141{
@@ -79,8 +157,6 @@ Updates will be sent to you on the `"postUpdates"` event. The event message will
79157
80158## GET /posts/:id
81159
82−_url: [`https://websuck1t.herokuapp.com/posts/{id}`](https://websuck1t.herokuapp.com/posts/1)_
83−
84160#### If there is no post with the given `id`, the method returns error `404`.
85161
86162### Captures:
@@ -145,65 +221,3 @@ _url: [`https://websuck1t.herokuapp.com/posts/{id}`](https://websuck1t.herokuapp
145221 "postId": 1
146222}
147223```
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 100644 → 100755

Only the mode changed.

src/Data/Error.hsmode 100644 → 100755

Only the mode changed.

src/Foo/Data.hsmode 100644 → 100755+89−173
@@ -1,12 +1,5 @@
11{-# LANGUAGE DeriveGeneric #-}
2−{-# LANGUAGE GADTs #-}
32{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE TypeFamilies #-}
7−{-# LANGUAGE TypeApplications #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−
103
114module Foo.Data
125 (
@@ -16,6 +9,7 @@ module Foo.Data
169 createApp,
1710 createConfig,
1811 User(..),
12+ UserName(..),
1913 Config,
2014 updates,
2115 allUsers,
@@ -23,16 +17,12 @@ module Foo.Data
2317 UserId,
2418 PostId,
2519 Post(..),
26− postId,
2720 allPosts,
2821 addConnection,
2922 removeConnection,
3023 TokenizedResponse(..),
3124 Update(..),
32− getToken,
33− Token,
34− subscribeWithToken,
35− postUpdates
25+ getToken
3626 ) where
3727
3828import GHC.Conc
@@ -60,14 +50,6 @@ import qualified Data.CyclicBuffer as CB
6050import Data.Set ((\\), union)
6151import Data.Monoid
6252import Debug.Trace
63−import Database.Beam
64−import qualified Database.Beam.Postgres as P
65−import Database.Beam.Query
66−import Database.PostgreSQL.Simple (withSavepoint)
67−import Database.Beam.Backend.SQL.BeamExtensions
68−import System.Random.Shuffle
69−import Network.SocketIO
70−import Network.EngineIO (SocketId)
7153
7254type App = ReaderT Config Handler
7355
@@ -75,35 +57,35 @@ class HasConfig c where
7557 getCfg :: c -> Config
7658
7759allUsers :: App [User]
78−allUsers = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . users $ socialDb
60+allUsers = ask >>= liftIO . fmap M.elems . readTVarIO . users
7961
8062post :: PostId -> App Post
8163post pId = do
82− conn <- asks dbConn
83− u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (PostId pId) . posts $ socialDb
64+ cfg <- ask
65+ u <- liftIO . atomically . fmap (M.lookup pId) . readTVar . posts $ cfg
8466 case u of
8567 Just u -> return u
8668 Nothing -> throwError err404
8769
8870user :: UserId -> App User
8971user uId = do
90− conn <- asks dbConn
91− u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (UserId uId) . users $ socialDb
72+ cfg <- ask
73+ u <- liftIO . atomically . fmap (M.lookup uId) . readTVar . users $ cfg
9274 case u of
9375 Just u -> return u
9476 Nothing -> throwError err404
9577
96−allPosts :: App [Post]
97−allPosts = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . posts $ socialDb
78+allPosts :: App (STM [Post])
79+allPosts = asks $ fmap M.elems . readTVar . posts
9880
9981getToken :: App (STM Token)
10082getToken = asks $ fmap CB.lastToken . readTVar . updates
10183
102−createConfig :: P.Connection -> IO Config
103−createConfig conn = do
84+createConfig :: IO Config
85+createConfig = do
10486 mng <- newManager tlsManagerSettings
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)
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)
10789 return cfg
10890
10991type Token = Int
@@ -134,32 +116,18 @@ instance Semigroup Update where
134116instance Monoid Update where
135117 mempty = Update S.empty S.empty
136118
137−addConnection :: HasConfig c => c -> Socket -> STM SocketId
119+addConnection :: HasConfig c => c -> Connection -> STM ConnectionId
138120addConnection c conn = do
139121 let cfg = getCfg c
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
122+ lastID <- fmap (+1) . readTVar . lastConnection $ cfg
123+ flip writeTVar lastID . lastConnection $ cfg
124+ modifyTVar (connections cfg) $ M.insert lastID conn
125+ return lastID
145126
146−removeConnection :: HasConfig c => c -> Socket -> STM ()
147−removeConnection c conn = do
127+removeConnection :: HasConfig c => c -> ConnectionId -> STM ()
128+removeConnection c cId = do
148129 let cfg = getCfg c
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
130+ modifyTVar (connections cfg) $ M.delete cId
163131
164132
165133
@@ -171,9 +139,12 @@ type PostId = Int
171139type ConnectionId = Int
172140
173141data Config = Config {
174− dbConn :: P.Connection,
142+ lastUserID :: TVar UserId,
143+ lastPostID :: TVar PostId,
144+ users :: TVar (M.Map Int User),
145+ posts :: TVar (M.Map Int Post),
175146 manager :: Manager,
176− connections :: TVar (M.Map SocketId Socket),
147+ connections :: TVar (M.Map ConnectionId Connection),
177148 lastConnection :: TVar ConnectionId,
178149 updates :: TVar (CB.CyclicBuffer Update)
179150}
@@ -181,149 +152,94 @@ data Config = Config {
181152instance HasConfig Config where
182153 getCfg = id
183154
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+
184162startAddingPosts :: Int -> Config -> App (Token, Update)
185163startAddingPosts lim cfg = do
186164 cfg <- ask
187− let conn = dbConn cfg
188− -- let us = users socialDb
189− -- let ps = posts socialDb
165+ let us = users cfg
166+ let ps = posts cfg
190167
191168 a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just lim) (Just 0)
192169 g <- liftIO newStdGen
193170 let rns = randoms g
194−
195− let t = fromMaybe (repeat "None") a
196−
197− let deleteCount = 3
198171
199− (deletedIds, addedIds) <- join . liftIO . withSavepoint 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)
172+ 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)
234191
235192sendUpdate :: Token -> Update -> App ()
236193sendUpdate t u = do
237194 cfg <- ask
238195 conns <- fmap M.elems . liftIO . readTVarIO $ connections cfg
239− forM conns $ \ conn -> emitTo conn postUpdates $ TokenizedResponse t u
240− -- liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
196+ liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
241197 return ()
242198
243−modIndex :: [a] -> Int -> a
244−modIndex aa i = aa !! (i `mod` length aa)
245−
246−infixl 5 %!
247−(%!) = modIndex
248−
249−
250199randomFrom :: Int -> M.Map k v -> k
251200randomFrom g as = M.keys as !! (abs g `mod` M.size as)
252201
253−newUser :: Config -> Text -> Text -> IO [User]
202+newUser :: Config -> Text -> Text -> STM User
254203newUser cfg first second = do
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
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
274228} deriving Generic
275229
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
230+data Post = Post {
231+ postId :: PostId,
232+ postAuthor :: UserId,
233+ postBody :: Text
292234} deriving Generic
293235
294−instance Beamable PostT
295−
296−type Post = PostT Identity
297−
298236instance ToJSON User
299237instance ToJSON Post
300238
301−instance Table PostT where
302− data PrimaryKey PostT f = PostId (Columnar f PostId)
303− deriving Generic
304−
305− primaryKey = PostId . postId
239+data UserName = UserName {
240+ firstName :: Text,
241+ secondName :: Text
242+} deriving (Generic)
306243
307−instance Beamable (PrimaryKey PostT)
308−
309−data SocialDb f = SocialDb {
310− users :: f (TableEntity UserT),
311− posts :: f (TableEntity PostT)
312−} deriving Generic
244+instance ToJSON UserName
313245
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 100644 → 100755+49−90
@@ -1,9 +1,6 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE TypeOperators #-}
33{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE FlexibleContexts #-}
6−
74
85module Lib
96 ( server,
@@ -29,114 +26,76 @@ import Data.Aeson
2926import qualified Data.Set as S
3027import Data.CyclicBuffer
3128import Data.Foldable (fold)
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
29+
3830
3931type API = "users" :> Capture "id" UserId :> Get '[JSON] User
4032 :<|> "users" :> "add" :> Capture "first" Text :> Capture "second" Text :> Get '[JSON] [User]
4133 :<|> "users" :> "all" :> Get '[JSON] [User]
4234 :<|> "posts" :> Capture "id" PostId :> Get '[JSON] D.Post
43− :<|> "posts" :> "all" :> Get '[JSON] (TokenizedResponse Update)
44− :<|> "posts" :> "subscribe" :> Raw --Capture "token" Int :> WebSocketPending
35+ :<|> "posts" :> "all" :> Get '[JSON] (TokenizedResponse [D.Post])
36+ :<|> "posts" :> "subscribe" :> Capture "token" Int :> WebSocketPending
4537
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
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
5846
5947server :: Proxy API
6048server = Proxy
6149
62−serverAPI :: Application -> ServerT API App
63−serverAPI app = user
64− :<|> createUserPath
65− :<|> allUsers
66− :<|> post
67− :<|> returnPosts
68− :<|> pure app
69− -- :<|> validateConnection
50+serverAPI :: ServerT API App
51+serverAPI =
52+ user
53+ :<|> createUserPath
54+ :<|> allUsers
55+ :<|> post
56+ :<|> returnPosts
57+ :<|> validateConnection
7058
7159createUserPath :: Text -> Text -> App [User]
7260createUserPath first second = do
7361 cfg <- ask
74− liftIO $ newUser cfg first second
62+ liftIO . atomically $ newUser cfg first second
7563 allUsers
7664
77−returnPosts :: App (TokenizedResponse Update)
65+returnPosts :: App (TokenizedResponse [D.Post])
7866returnPosts = do
7967 posts <- allPosts
8068 t <- getToken
81− liftIO $ atomically $ TokenizedResponse <$> t <*> (pure $ flip Update (S.empty) $ S.fromList $ Prelude.map D.postId posts)
82−
83−-- invalidTokenError :: RejectRequest
84−-- invalidTokenError = defaultRejectRequest {
85−-- rejectCode = 498,
86−-- rejectMessage = "The supplied token is not valid" }
87−
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−
69+ liftIO $ atomically $ TokenizedResponse <$> t <*> posts
9870
71+invalidTokenError :: RejectRequest
72+invalidTokenError = defaultRejectRequest {
73+ rejectCode = 498,
74+ rejectMessage = "The supplied token is not valid" }
9975
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
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 ()
13899
139−
140−
141100liftSTM :: MonadIO m => STM a -> m a
142101liftSTM = liftIO . atomically
src/Network/Bacon.hsmode 100644 → 100755

Only the mode changed.

stack.yamlmode 100644 → 100755+2−7
@@ -17,7 +17,7 @@
1717#
1818# resolver: ./custom-snapshot.yaml
1919# resolver: https://example.com/snapshots/2018-01-01.yaml
20−resolver: lts-12.25
20+resolver: lts-13.1
2121
2222# User packages to be built.
2323# Various formats can be used as shown in the example below.
@@ -37,12 +37,7 @@ 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:
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
40+# extra-deps: []
4641
4742# Override default flag values for local packages and extra-deps
4843# flags: {}
swagger/Swagger.hsmode 100644 → 100755+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 100644 → 100755

Only the mode changed.