on master help sign in
Seem to have resolved #2
commit083f5633ad002014538d2ca8c4e669fcf5ed7eb5 authoriko <ilyakooo0@gmail.com> dateSat 2 Feb 2019, 17:04 (8 years ago) parent69eac29
7 files changed+190−119
app/Main.hs+11−4 docs/Docs.hs+23−23 package.yaml+4 src/Foo/Data.hs+135−78 src/Lib.hs+12−11 stack.yaml+4−2 swagger/Swagger.hs+1−1
app/Main.hs+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/Docs.hs+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.yaml+4
@@ -37,6 +37,10 @@ dependencies:
3737- websockets
3838- lens
3939- wai-cors
40+- beam-core
41+- beam-postgres
42+- postgresql-simple
43+- random-shuffle
4044
4145library:
4246 source-dirs: src
src/Foo/Data.hs+135−78
@@ -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,
@@ -50,6 +56,12 @@ import qualified Data.CyclicBuffer as CB
5056import Data.Set ((\\), union)
5157import Data.Monoid
5258import Debug.Trace
59+import Database.Beam
60+import qualified Database.Beam.Postgres as P
61+import Database.Beam.Query
62+import Database.PostgreSQL.Simple (withTransaction)
63+import Database.Beam.Backend.SQL.BeamExtensions
64+import System.Random.Shuffle
5365
5466type App = ReaderT Config Handler
5567
@@ -57,35 +69,35 @@ class HasConfig c where
5769 getCfg :: c -> Config
5870
5971allUsers :: App [User]
60−allUsers = ask >>= liftIO . fmap M.elems . readTVarIO . users
72+allUsers = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . users $ socialDb
6173
6274post :: PostId -> App Post
6375post pId = do
64− cfg <- ask
65− u <- liftIO . atomically . fmap (M.lookup pId) . readTVar . posts $ cfg
76+ conn <- asks dbConn
77+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (PostId pId) . posts $ socialDb
6678 case u of
6779 Just u -> return u
6880 Nothing -> throwError err404
6981
7082user :: UserId -> App User
7183user uId = do
72− cfg <- ask
73− u <- liftIO . atomically . fmap (M.lookup uId) . readTVar . users $ cfg
84+ conn <- asks dbConn
85+ u <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . flip lookup_ (UserId uId) . users $ socialDb
7486 case u of
7587 Just u -> return u
7688 Nothing -> throwError err404
7789
78−allPosts :: App (STM [Post])
79−allPosts = asks $ fmap M.elems . readTVar . posts
90+allPosts :: App [Post]
91+allPosts = asks dbConn >>= \conn -> liftIO . P.runBeamPostgres conn . runSelectReturningList . select . all_ . posts $ socialDb
8092
8193getToken :: App (STM Token)
8294getToken = asks $ fmap CB.lastToken . readTVar . updates
8395
84−createConfig :: IO Config
85−createConfig = do
96+createConfig :: P.Connection -> IO Config
97+createConfig conn = do
8698 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 7000000 >> (runHandler . createApp cfg $ startAddingPosts 20 cfg >>= uncurry sendUpdate)
99+ cfg <- atomically $ Config <$> return conn <*> return mng <*> newTVar M.empty <*> newTVar 0 <*> newTVar (CB.empty 20)
100+ forkIO . forever $ threadDelay 7000000 >> (runHandler . createApp cfg $ startAddingPosts 25 cfg >>= uncurry sendUpdate)
89101 return cfg
90102
91103type Token = Int
@@ -139,10 +151,7 @@ type PostId = Int
139151type ConnectionId = Int
140152
141153data Config = Config {
142− lastUserID :: TVar UserId,
143− lastPostID :: TVar PostId,
144− users :: TVar (M.Map Int User),
145− posts :: TVar (M.Map Int Post),
154+ dbConn :: P.Connection,
146155 manager :: Manager,
147156 connections :: TVar (M.Map ConnectionId Connection),
148157 lastConnection :: TVar ConnectionId,
@@ -152,42 +161,56 @@ data Config = Config {
152161instance HasConfig Config where
153162 getCfg = id
154163
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−
162164startAddingPosts :: Int -> Config -> App (Token, Update)
163165startAddingPosts lim cfg = do
164166 cfg <- ask
165− let us = users cfg
166− let ps = posts cfg
167+ let conn = dbConn cfg
168+ -- let us = users socialDb
169+ -- let ps = posts socialDb
167170
168171 a <- liftIO $ loremBacon (manager cfg) (Just AllMeat) (Just lim) (Just 0)
169172 g <- liftIO newStdGen
170173 let rns = randoms g
171−
174+
172175 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)
176+
177+ let deleteCount = 3
178+
179+ (deletedIds, addedIds) <- join . liftIO . withTransaction conn . return $ do
180+ userIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
181+ uc <- all_ . users $ socialDb
182+ pure (userId uc)
183+
184+ if length userIds <= 0 then return ([],[])
185+ else do
186+ postIds <- liftIO . P.runBeamPostgres conn . runSelectReturningList . select $ do
187+ uc <- all_ . posts $ socialDb
188+ pure (postId uc)
189+
190+
191+ let postCount = length postIds
192+ let toDeleteIndecies = take deleteCount $ shuffle' postIds postCount g
193+
194+ let toAdd = max 0 $ lim - length postIds + length toDeleteIndecies
195+
196+ -- Just deletedPost <- liftIO . P.runBeamPostgres conn . runSelectReturningOne . select . limit_ 1 . offset_ (fromIntegral $ toDeleteIndex - 1) . all_ . posts $ socialDb
197+ -- let deletedId = postId deletedPost
198+
199+ unless (length toDeleteIndecies <= 0) $
200+ liftIO . P.runBeamPostgres conn . runDelete $
201+ delete (posts socialDb) ((`in_` map val_ toDeleteIndecies) . postId)
202+
203+ 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)
204+
205+ return (toDeleteIndecies, map postId nIds)
206+
207+ liftIO . atomically $ do
208+ let upd = Update (S.fromList addedIds) (S.fromList deletedIds)
209+ let uppT = updates cfg
210+ upp <- readTVar uppT
211+ let (token, upp') = CB.insert upp upd
212+ writeTVar uppT upp'
213+ return (token, upd)
191214
192215sendUpdate :: Token -> Update -> App ()
193216sendUpdate t u = do
@@ -196,50 +219,84 @@ sendUpdate t u = do
196219 liftIO $ forM conns $ flip sendTextData $ encode $ TokenizedResponse t u
197220 return ()
198221
222+modIndex :: [a] -> Int -> a
223+modIndex aa i = aa !! (i `mod` length aa)
224+
225+infixl 5 %!
226+(%!) = modIndex
227+
228+
199229randomFrom :: Int -> M.Map k v -> k
200230randomFrom g as = M.keys as !! (abs g `mod` M.size as)
201231
202−newUser :: Config -> Text -> Text -> STM User
232+newUser :: Config -> Text -> Text -> IO [User]
203233newUser 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
234+ let conn = dbConn cfg
235+ P.runBeamPostgres conn . runInsertReturningList (users socialDb) $ insertExpressions [User default_ (val_ first) (val_ second)]
236+
237+
238+-- newPost :: Config -> UserId -> Text -> STM Post
239+-- newPost cfg user body = do
240+-- let lastId = lastPostID cfg
241+-- let ps = posts cfg
242+-- do
243+-- id <- readTVar lastId
244+-- writeTVar lastId (id+1)
245+-- let p = Post id user body
246+-- modifyTVar ps $ M.insert id p
247+-- return p
248+
249+data UserT f = User {
250+ userId :: Columnar f UserId,
251+ firstName :: Columnar f Text,
252+ secondName :: Columnar f Text
228253} deriving Generic
229254
230−data Post = Post {
231− postId :: PostId,
232− postAuthor :: UserId,
233− postBody :: Text
255+instance Beamable UserT
256+
257+type User = UserT Identity
258+
259+instance Table UserT where
260+ data PrimaryKey UserT f = UserId (Columnar f UserId)
261+ deriving Generic
262+
263+ primaryKey = UserId . userId
264+
265+instance Beamable (PrimaryKey UserT)
266+
267+data PostT f = Post {
268+ postId :: Columnar f PostId,
269+ postAuthor :: Columnar f UserId,
270+ postBody :: Columnar f Text
234271} deriving Generic
235272
273+instance Beamable PostT
274+
275+type Post = PostT Identity
276+
236277instance ToJSON User
237278instance ToJSON Post
238279
239−data UserName = UserName {
240− firstName :: Text,
241− secondName :: Text
242−} deriving (Generic)
280+instance Table PostT where
281+ data PrimaryKey PostT f = PostId (Columnar f PostId)
282+ deriving Generic
283+
284+ primaryKey = PostId . postId
285+
286+instance Beamable (PrimaryKey PostT)
287+
288+data SocialDb f = SocialDb {
289+ users :: f (TableEntity UserT),
290+ posts :: f (TableEntity PostT)
291+} deriving Generic
243292
244−instance ToJSON UserName
293+instance Database be SocialDb
245294
295+socialDb :: DatabaseSettings be SocialDb
296+socialDb = defaultDbSettings `withDbModification`
297+ dbModification {
298+ users = modifyTable id $ tableModification {
299+ firstName = "firstname",
300+ secondName = "secondname"
301+ }
302+ }
src/Lib.hs+12−11
@@ -1,6 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE TypeOperators #-}
33{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
45
56module Lib
67 ( server,
@@ -26,7 +27,7 @@ import Data.Aeson
2627import qualified Data.Set as S
2728import Data.CyclicBuffer
2829import Data.Foldable (fold)
29−
30+import qualified Database.Beam.Postgres as P
3031
3132type API = "users" :> Capture "id" UserId :> Get '[JSON] User
3233 :<|> "users" :> "add" :> Capture "first" Text :> Capture "second" Text :> Get '[JSON] [User]
@@ -35,14 +36,14 @@ type API = "users" :> Capture "id" UserId :> Get '[JSON] User
3536 :<|> "posts" :> "all" :> Get '[JSON] (TokenizedResponse [D.Post])
3637 :<|> "posts" :> "subscribe" :> Capture "token" Int :> WebSocketPending
3738
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− atomically $ newUser cfg "Константин" "Дудосерович"
39+createServer :: P.Connection -> IO (Server API)
40+createServer pConn = do
41+ cfg <- createConfig pConn
42+ -- newUser cfg "Kostya" "Designerovich"
43+ -- newUser cfg "Anna" "Androidovna"
44+ -- newUser cfg "Darya" "Aiphonovna"
45+ -- newUser cfg "Ilya" "Serverovich"
46+ -- newUser cfg "Константин" "Дудосерович"
4647 return $ hoistServer server (createApp cfg) serverAPI
4748
4849server :: Proxy API
@@ -60,14 +61,14 @@ serverAPI =
6061createUserPath :: Text -> Text -> App [User]
6162createUserPath first second = do
6263 cfg <- ask
63− liftIO . atomically $ newUser cfg first second
64+ liftIO $ newUser cfg first second
6465 allUsers
6566
6667returnPosts :: App (TokenizedResponse [D.Post])
6768returnPosts = do
6869 posts <- allPosts
6970 t <- getToken
70− liftIO $ atomically $ TokenizedResponse <$> t <*> posts
71+ liftIO $ atomically $ TokenizedResponse <$> t <*> pure posts
7172
7273invalidTokenError :: RejectRequest
7374invalidTokenError = defaultRejectRequest {
stack.yaml+4−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,9 @@ 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
4143
4244# Override default flag values for local packages and extra-deps
4345# flags: {}
swagger/Swagger.hs+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