-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Start of Sprint 2 #49
base: master
Are you sure you want to change the base?
Changes from 42 commits
830d2ec
a43ee57
45c7a2d
19b697b
f6a52f0
46ba90a
e7fef1e
bc4b2df
4e25c7c
f11a655
bb234be
785c7d7
f286b54
4781ca8
e16f7a0
df887ed
8308879
74e8743
a8c94fd
74eff26
290cc61
945ef75
e770e4c
04fc93d
93b6037
3734c41
07304e8
1a298cb
dd2d971
bd209c5
1004ca1
4b32bdd
ea708d7
6f80c26
f940de5
18e2dfe
c6f7d14
03a33cc
7b4ba29
6ebf760
79d5114
f02c93a
a995b2d
4a38268
14e2d8a
4ce8966
4b3fca4
82d2d78
69f172f
aa38a20
95f65e2
27cf3b3
480119f
741ff7d
87b9674
81f6fab
0a8c997
29a8a80
65c5ccc
52c102b
0ce8cbf
cd991a1
e788e65
6ec820b
f56c9c5
caa39c0
117f8db
54dcd2e
39dbab3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE FunctionalDependencies #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE TemplateHaskell #-} | ||
|
||
module ApiBase where | ||
|
||
import Control.Lens | ||
import Control.Monad.IO.Class | ||
import Control.Monad.Reader | ||
import Control.Monad.Writer | ||
import qualified Data.Profunctor.Product.Default as D | ||
import Data.Time (UTCTime, getCurrentTime) | ||
import DataTypes | ||
import Opaleye | ||
import OpaleyeDef | ||
import qualified Data.Text as T | ||
import GHC.Int | ||
import Prelude hiding (id) | ||
import TH | ||
import Data.Aeson (Value(..)) | ||
import JsonInstances () | ||
|
||
makeAudtableLenses ''Role | ||
makeAudtableLenses ''Tenant | ||
makeAudtableLenses ''User | ||
|
||
auditLog :: String -> AppM () | ||
auditLog = tell | ||
|
||
removeRawDbRows :: Table columnsW columnsR -> (columnsR -> Column PGBool) -> AppM GHC.Int.Int64 | ||
removeRawDbRows table matchFunc = do | ||
conn <- getConnection | ||
liftIO $ runDelete conn table matchFunc | ||
|
||
createDbRows :: (Show columnsW, D.Default QueryRunner columnsR haskells) | ||
=> Table columnsW columnsR -> [columnsW] -> AppM [haskells] | ||
createDbRows table pgrows = do | ||
auditLog $ "Create : " ++ (show pgrows) | ||
conn <- getConnection | ||
liftIO $ runInsertManyReturning conn table pgrows (\x -> x) | ||
|
||
updateDbRow :: (Show columnsW, HasId columnsR (Column PGInt4)) => Table columnsW columnsR -> Column PGInt4 -> columnsW -> AppM columnsW | ||
updateDbRow table row_id item = do | ||
auditLog $ "Update :" ++ (show item) | ||
conn <- getConnection | ||
_ <- liftIO $ runUpdate conn table (\_ -> item) (matchFunc row_id) | ||
return item | ||
where | ||
matchFunc :: (HasId cmR (Column PGInt4)) => (Column PGInt4 -> cmR -> Column PGBool) | ||
matchFunc itId item' = (item' ^. id) .== itId | ||
|
||
createRow ::( | ||
Show incoming, | ||
Show columnsW, | ||
HasCreatedat columnsW (Maybe (Column PGTimestamptz)), | ||
HasUpdatedat columnsW (Column PGTimestamptz), | ||
D.Default Constant incoming columnsW, D.Default QueryRunner returned row) | ||
=> Table columnsW returned -> incoming -> AppM row | ||
createRow table item = do | ||
auditLog $ "Create : " ++ (show item) | ||
currentTime <- liftIO $ fmap pgUTCTime getCurrentTime | ||
let itemPg = (constant item) & createdat .~ (Just currentTime) & updatedat .~ (currentTime) | ||
fmap (head) $ createDbRows table [itemPg] | ||
|
||
updateRow :: ( | ||
Show columnsW | ||
, Show haskells | ||
, HasUpdatedat haskells UTCTime | ||
, D.Default Constant haskells columnsW | ||
, D.Default Constant itemId (Column PGInt4) | ||
, HasId haskells itemId | ||
, HasId columnsR (Column PGInt4) | ||
) | ||
=> Table columnsW columnsR -> haskells -> AppM haskells | ||
updateRow table item = do | ||
auditLog $ "Update : " ++ (show item) | ||
let itId = item ^. id | ||
currentTime <- liftIO getCurrentTime | ||
let updatedItem = (putUpdatedTimestamp currentTime) item | ||
_ <- updateDbRow table (constant itId) (constant updatedItem) | ||
return updatedItem | ||
where | ||
putUpdatedTimestamp :: (HasUpdatedat item (UTCTime)) => UTCTime -> item -> item | ||
putUpdatedTimestamp timestamp = updatedat .~ timestamp | ||
|
||
updateAuditableRow :: ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't this be in a DB transaction? Have we solved for DB transaction yets? |
||
Show columnsW | ||
, HasUpdatedat (Auditable haskells) UTCTime | ||
, D.Default Constant haskells columnsW | ||
, D.Default Constant itemId (Column PGInt4) | ||
, HasId (Auditable haskells) itemId | ||
, HasId columnsR (Column PGInt4) | ||
) | ||
=> Table columnsW columnsR -> Auditable haskells -> AppM (Auditable haskells) | ||
updateAuditableRow table audti = do | ||
--auditLog $ "Update : " ++ (show item) | ||
let itId = audti ^. id | ||
currentTime <- liftIO getCurrentTime | ||
let updatedItem = (putUpdatedTimestamp currentTime) audti | ||
let Auditable { _data = item, _log = _log} = updatedItem | ||
_ <- updateDbRow table (constant itId) (constant item) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we really be ignoring the result? Isn't it better to use |
||
insertIntoLog table itId "" _log | ||
return audti | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the in-memory audit log being blanked out after a successful update or insert? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. I completely missed that. Will do. |
||
where | ||
putUpdatedTimestamp :: (HasUpdatedat item (UTCTime)) => UTCTime -> item -> item | ||
putUpdatedTimestamp timestamp = updatedat .~ timestamp | ||
|
||
insertIntoLog :: ( | ||
D.Default Constant item_id (Column PGInt4) | ||
) => Table a b -> item_id -> T.Text -> Value -> AppM () | ||
insertIntoLog table auditable_id summary changes = do | ||
case table of | ||
Table table_name _ -> do | ||
Just tenant <- getCurrentTenant | ||
Just user <- getCurrentUser | ||
conn <- getConnection | ||
let tenant_id = tenant ^. id | ||
let user_id = user ^. id | ||
_ <- liftIO $ runInsertMany conn auditTable [( | ||
(), | ||
constant tenant_id, | ||
Just $ constant user_id, | ||
Just $ pgBool False, | ||
constant auditable_id, | ||
pgStrictText $ T.pack table_name, | ||
pgStrictText $ summary, | ||
constant changes, | ||
Nothing)] | ||
return () | ||
_ -> error "Unsupported Table constructor" | ||
|
||
removeRow :: ( | ||
Show haskells | ||
, D.Default Constant itemId (Column PGInt4) | ||
, HasId haskells itemId | ||
, HasId columnsR (Column PGInt4) | ||
) => Table columnsW columnsR -> haskells -> AppM GHC.Int.Int64 | ||
removeRow table item = do | ||
auditLog $ "Remove : " ++ (show item) | ||
conn <- getConnection | ||
liftIO $ do | ||
runDelete conn table $ matchFunc $ item ^. id | ||
where | ||
matchFunc :: ( | ||
HasId columnsR (Column PGInt4), | ||
D.Default Constant itemId (Column PGInt4) | ||
) => (itemId -> columnsR -> Column PGBool) | ||
matchFunc itId item' = (item' ^. id) .== (constant itId) | ||
|
||
readRow :: (D.Default QueryRunner columnsR haskells) => | ||
Opaleye.Query columnsR -> AppM [haskells] | ||
readRow query' = do | ||
conn <- getConnection | ||
liftIO $ runQuery conn query' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we be using
runUpdateReturning
here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. That would be ideal. But since we already hold the record in its updated state, I thought, why do the additional fetching. If there was a failure with update, we will get an exception anyway, right?
I am not sure, but I think the use case of "update returning" would be if we are doing an operation on existing value, like "update product set quantity = quantity + 1", and in that case, returning the new values can save an additional select.
But here we are not doing anything like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel kts better to use updateReturning because it allows us to represent the latest state of the row in the DB, which might be different than what is in the memory, possibly due to DB triggers.