on master help sign in
WIP
commit31a32dc3eb064c7c393a971d7e82423682464440 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 25 Nov 2018, 11:17 (8 years ago) parentc18c96d
8 files changed+186−49
default.nix+3−13 lib/backend/src/Obelisk/Postgres/LogicalDecoding/Plugins/TestDecoding.hs+1 lib/backend/src/Obelisk/Postgres/Replication.hs−1 skeleton/backend/backend.cabal+3 skeleton/backend/src/Backend.hs+78−26 skeleton/backend/src/Backend/Schema.hs+50 skeleton/common/src/Common/Api.hs+43−2 skeleton/frontend/src/Frontend.hs+8−7
default.nix+3−13
@@ -16,19 +16,9 @@ let
1616 openssh
1717 ];
1818
19− beam-src = pkgs.fetchFromGitHub {
20− owner = "obsidiansystems";
21− repo = "beam";
22− rev = "6c9b08d57951083c1f7e4bd49d941619603127f4";
23− sha256 = "1k4sdz0dxjafwkwlpfcdxkgvvv591nfwc97g1fq23wrs271jr2sm";
24− };
19+ beam-src = hackGet ./dep/beam;
2520
26− gargoyle-src = pkgs.fetchFromGitHub {
27− owner = "obsidiansystems";
28− repo = "gargoyle";
29− rev = "2c19c569325ad76694526e9b688ccdbf148df980";
30− sha256 = "0257p0qd8xx900ngghkjbmjnvn7pjv05g0jm5kkrm4p6alrlhfyl";
31− };
21+ gargoyle-src = hackGet ./dep/gargoyle;
3222
3323 getReflexPlatform = getReflexPlatform' __useLegacyCompilers;
3424 getReflexPlatform' = __useLegacyCompilers: sys: import ./dep/reflex-platform {
@@ -79,7 +69,7 @@ let
7969 sha256 = "1v337d810d88jzfriw07pr16d34nibm6q2zkw787lc3sfn07glp5";
8070 }) {};
8171
82− beam-core = self.callCabal2nix "beam-core" (beam-src + /beam-core) {};
72+ beam-core = dontCheck (self.callCabal2nix "beam-core" (beam-src + /beam-core) {});
8373 beam-postgres = dontCheck (self.callCabal2nix "beam-postgres" (beam-src + /beam-postgres) {});
8474 beam-migrate = self.callCabal2nix "beam-migrate" (beam-src + /beam-migrate) {};
8575
lib/backend/src/Obelisk/Postgres/LogicalDecoding/Plugins/TestDecoding.hs+1
@@ -3,6 +3,7 @@
33module Obelisk.Postgres.LogicalDecoding.Plugins.TestDecoding
44 ( line
55 , Transaction
6+ , Change (..)
67 , LineError (..)
78 , linesToTransactions
89 ) where
lib/backend/src/Obelisk/Postgres/Replication.hs−1
@@ -64,7 +64,6 @@ withLogicalDecoding dbUri pluginName opts go = bracket (PQ.connectdb $ dbUri <>
6464 <*> newMVar mempty
6565 <*> newIORef 0
6666 backendPid <- PQ.backendPID unwrappedConn
67− putStrLn $ "withLogicalDecoding.d" <> show backendPid
6867 let slotName = PG.Identifier $ slotNamePrefix <> T.pack (show backendPid)
6968 [(_, _, _, _) :: (Text, Text, Maybe Text, Text)] <- uncurry (PG.query pgConn) [sqlQ| CREATE_REPLICATION_SLOT ?slotName TEMPORARY LOGICAL ?pluginName NOEXPORT_SNAPSHOT |]
7069 withConn $ \conn -> do
skeleton/backend/backend.cabal+3
@@ -13,6 +13,9 @@ library
1313 , obelisk-backend
1414 , obelisk-request
1515 , obelisk-route
16+ , beam-core
17+ , beam-migrate
18+ , beam-postgres
1619 exposed-modules:
1720 Backend
1821 ghc-options: -Wall
skeleton/backend/src/Backend.hs+78−26
@@ -1,4 +1,6 @@
11{-# LANGUAGE EmptyCase #-}
2+{-# LANGUAGE MultiParamTypeClasses #-}
3+{-# LANGUAGE AllowAmbiguousTypes #-}
24{-# LANGUAGE TemplateHaskell #-}
35{-# LANGUAGE GADTs #-}
46{-# LANGUAGE TupleSections #-}
@@ -16,58 +18,108 @@
1618{-# LANGUAGE TypeFamilies #-}
1719module Backend where
1820
21+import Common.Api
22+import Backend.Schema
1923import Common.Route
20−import Data.Aeson
2124import Data.Text (Text)
22−import Data.Functor.Identity
2325import Data.Map.Monoidal (MonoidalMap (..))
24−import qualified Data.Map.Monoidal as Map
25−import Data.Semigroup
26+import Data.Pool
2627import Obelisk.Backend
2728import Obelisk.Route
2829import Obelisk.Api
29−import Obelisk.Api.Pipeline --TODO: Move Request to Obelisk.Api
3030import Obelisk.Db
3131import Obelisk.Postgres.LogicalDecoding.Plugins.TestDecoding
32−import Obelisk.Request.TH
33−import Reflex.Class
34−import Reflex.Query.Class
35−
36−import Debug.Trace
37−
38−newtype VS k v a = VS (MonoidalMap k a)
39− deriving (Semigroup, Monoid, Functor, Foldable, Traversable, Group, ToJSON, FromJSON, Eq)
32+import qualified Data.Map as Map
33+import Data.Sequence (Seq)
4034
41−newtype V k v a = V (MonoidalMap k (a, First (Maybe v)))
42− deriving (Semigroup, Monoid, Functor, Foldable, Traversable, ToJSON, FromJSON, Eq)
35+import Database.Beam
36+import Database.Beam.Postgres
37+import Database.Beam.Postgres.Migrate
38+import Database.Beam.Migrate.Simple
39+import Database.Beam.Schema.Tables
40+import Database.PostgreSQL.Simple.Types
41+import Control.Monad.Reader
4342
44−instance FunctorMaybe (V k v) where
45− fmapMaybe f (V m) = V $ Map.mapMaybe (\(a, v) -> (,v) <$> f a) m
43+import Data.Proxy
4644
47−instance (Ord k, Semigroup a) => Query (VS k v a) where
48− type QueryResult (VS k v a) = V k v a
49− crop _ = id
50−
51−data MyRequest a where
52− MyRequest_Echo :: Text -> MyRequest Text
45+import Data.Functor.Identity
46+import Data.Functor.Const
47+import Control.Lens
5348
54−makeRequestForData ''MyRequest
49+import Debug.Trace
5550
5651backend :: Backend BackendRoute FrontendRoute
5752backend = Backend
5853 { _backend_run = \serve -> do
5954 withDbUri "db" $ \dbUri -> do
60− print dbUri
55+ withConnectionPool dbUri $ \pool -> do
56+ withResource pool $ \conn -> do
57+ runBeamPostgres conn $ autoMigrate migrationBackend checkedDb
6158 let onNotify :: forall a. VS Int Text a -> Transaction -> ReadDb (V Int Text a)
6259 onNotify (VS _) txn = do
6360 traceM $ show txn
61+ traceM $ show $ decodeTransaction txn
62+ tasks <- runSelectReturningList' $ select $ all_ $ _db_tasks db
63+ traceM $ show tasks
6464 pure $ V $ MonoidalMap mempty
6565 onSubscribe :: forall a. VS Int Text a -> ReadDb (V Int Text a)
6666 onSubscribe (VS _) = do
67+ tasks <- runSelectReturningList' $ select $ all_ $ _db_tasks db
68+ traceM $ show tasks
6769 pure $ V $ MonoidalMap mempty
68− withApiHandler dbUri (\(MyRequest_Echo a) -> pure a) onNotify onSubscribe $ \handler -> do
70+ onRequest :: forall a. MyRequest a -> WriteDb a
71+ onRequest = \case
72+ MyRequest_Echo a -> do
73+ runInsert' $ insert (_db_tasks db) $ insertExpressions $ (:[]) $ Task
74+ { _task_id = default_
75+ , _task_description = val_ a
76+ , _task_completed = val_ False
77+ }
78+ pure a
79+ withApiHandler dbUri onRequest onNotify onSubscribe $ \handler -> do
6980 serve $ \case
7081 BackendRoute_Api :/ () -> handler
7182 _ -> return ()
7283 , _backend_routeEncoder = backendRouteEncoder
7384 }
85+
86+class MonadBeamRead m where
87+ runSelectReturningList' :: FromBackendRow Postgres a => SqlSelect Postgres a -> m [a]
88+ runSelectReturningOne' :: FromBackendRow Postgres a => SqlSelect Postgres a -> m (Maybe a)
89+
90+unsafeRunPgReadDb :: Pg a -> ReadDb a
91+unsafeRunPgReadDb a = ReadDb $ do
92+ conn <- ask
93+ lift $ runBeamPostgres conn a
94+
95+unsafeRunPgWriteDb :: Pg a -> WriteDb a
96+unsafeRunPgWriteDb a = WriteDb $ do
97+ conn <- ask
98+ lift $ runBeamPostgres conn a
99+
100+instance MonadBeamRead WriteDb where
101+ runSelectReturningList' = unsafeRunPgWriteDb . runSelectReturningList
102+ runSelectReturningOne' = unsafeRunPgWriteDb . runSelectReturningOne
103+
104+instance MonadBeamRead ReadDb where
105+ runSelectReturningList' = unsafeRunPgReadDb . runSelectReturningList
106+ runSelectReturningOne' = unsafeRunPgReadDb . runSelectReturningOne
107+
108+class MonadBeamWrite m where
109+ runInsert' :: SqlInsert Postgres table -> m ()
110+ runUpdate' :: SqlUpdate Postgres table -> m ()
111+ runDelete' :: SqlDelete Postgres table -> m ()
112+
113+instance MonadBeamWrite WriteDb where
114+ runInsert' = unsafeRunPgWriteDb . runInsert
115+ runUpdate' = unsafeRunPgWriteDb . runUpdate
116+ runDelete' = unsafeRunPgWriteDb . runDelete
117+
118+decodeTransaction :: Transaction -> Db (Const (Seq Change))
119+decodeTransaction (_, tableChanges) = runIdentity $ zipTables (Proxy :: Proxy Postgres) f db db
120+ where
121+ f :: IsDatabaseEntity Postgres entityType => DatabaseEntity Postgres Db entityType -> DatabaseEntity Postgres Db entityType -> Identity (Const (Seq Change) entityType)
122+ f (DatabaseEntity descriptor) _ = pure $ Const $
123+ let n = view dbEntityName descriptor
124+ s = Just "public" -- view dbEntitySchema descriptor --TODO: Beam has 'Nothing' while the replication connection reports 'Just "public"'
125+ in trace (show (s, n)) $ Map.findWithDefault mempty (QualifiedIdentifier s n) tableChanges
skeleton/backend/src/Backend/Schema.hsnew file+50
@@ -0,0 +1,50 @@
1+{-# LANGUAGE StandaloneDeriving #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE DeriveGeneric #-}
8+{-# LANGUAGE TypeSynonymInstances #-}
9+module Backend.Schema where
10+
11+import Data.Int
12+import Data.Text (Text)
13+import Database.Beam
14+import Database.Beam.Migrate.Simple
15+import Database.Beam.Migrate
16+import Database.Beam.Postgres
17+
18+data TaskT f = Task
19+ { _task_id :: Columnar f Int64
20+ , _task_description :: Columnar f Text
21+ , _task_completed :: Columnar f Bool
22+ }
23+ deriving (Generic)
24+type Task = TaskT Identity
25+
26+deriving instance Show Task
27+deriving instance Eq Task
28+
29+instance Beamable TaskT
30+
31+instance Table TaskT where
32+ data PrimaryKey TaskT f = TaskId (Columnar f Int64) deriving (Generic)
33+ primaryKey = TaskId . _task_id
34+
35+instance Beamable (PrimaryKey TaskT)
36+
37+data Db f = Db
38+ { _db_tasks :: f (TableEntity TaskT)
39+ }
40+ deriving (Generic)
41+
42+deriving instance Show (f (TableEntity TaskT)) => Show (Db f)
43+
44+instance Database Postgres Db
45+
46+checkedDb :: CheckedDatabaseSettings Postgres Db
47+checkedDb = defaultMigratableDbSettings
48+
49+db :: DatabaseSettings Postgres Db
50+db = unCheckDatabase checkedDb
skeleton/common/src/Common/Api.hs+43−2
@@ -1,4 +1,45 @@
1+{-# LANGUAGE EmptyCase #-}
2+{-# LANGUAGE TemplateHaskell #-}
3+{-# LANGUAGE GADTs #-}
4+{-# LANGUAGE TupleSections #-}
5+{-# LANGUAGE DeriveFunctor #-}
6+{-# LANGUAGE DeriveFoldable #-}
7+{-# LANGUAGE DeriveTraversable #-}
8+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9+{-# LANGUAGE TypeApplications #-}
10+{-# LANGUAGE ScopedTypeVariables #-}
11+{-# LANGUAGE FlexibleContexts #-}
12+{-# LANGUAGE FlexibleInstances #-}
13+{-# LANGUAGE LambdaCase #-}
14+{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE RankNTypes #-}
16+{-# LANGUAGE TypeFamilies #-}
117module Common.Api where
218
3−commonStuff :: String
4−commonStuff = "Here is a string defined in code common to the frontend and backend."
19+import Data.Aeson
20+import Data.AppendMap ()
21+import Data.Map.Monoidal (MonoidalMap (..))
22+import qualified Data.Map.Monoidal as Map
23+import Data.Semigroup
24+import Data.Text (Text)
25+import Obelisk.Request.TH
26+import Reflex.Class
27+import Reflex.Query.Class
28+
29+newtype VS k v a = VS (MonoidalMap k a)
30+ deriving (Semigroup, Monoid, Functor, Foldable, Traversable, Group, ToJSON, FromJSON, Eq)
31+
32+newtype V k v a = V (MonoidalMap k (a, First (Maybe v)))
33+ deriving (Semigroup, Monoid, Functor, Foldable, Traversable, ToJSON, FromJSON, Eq)
34+
35+instance FunctorMaybe (V k v) where
36+ fmapMaybe f (V m) = V $ Map.mapMaybe (\(a, v) -> (,v) <$> f a) m
37+
38+instance (Ord k, Semigroup a) => Query (VS k v a) where
39+ type QueryResult (VS k v a) = V k v a
40+ crop _ = id
41+
42+data MyRequest a where
43+ MyRequest_Echo :: Text -> MyRequest Text
44+
45+makeRequestForData ''MyRequest
skeleton/frontend/src/Frontend.hs+8−7
@@ -1,23 +1,24 @@
11{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE TypeFamilies #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE RankNTypes #-}
7+{-# LANGUAGE RecursiveDo #-}
28{-# LANGUAGE OverloadedStrings #-}
39{-# LANGUAGE TypeApplications #-}
410module Frontend where
511
6−import qualified Data.Text as T
12+import Common.Route
713import Obelisk.Frontend
14+import Obelisk.Generated.Static
815import Obelisk.Route
916import Reflex.Dom.Core
1017
11−import Common.Api
12−import Common.Route
13−import Obelisk.Generated.Static
14−
15−
1618frontend :: Frontend (R FrontendRoute)
1719frontend = Frontend
1820 { _frontend_head = el "title" $ text "Obelisk Minimal Example"
1921 , _frontend_body = do
2022 text "Welcome to Obelisk!"
21− el "p" $ text $ T.pack commonStuff
2223 elAttr "img" ("src" =: static @"obelisk.jpg") blank
2324 }