Switch jsonb support from persistent-postgresql to a custom module
The custom module provides a parametric wrapper, allowing any specific FromJSON/ToJSON instance to be used. It's a standalone module though, and not a wrapper of persistent-postgresql, because persistent-postgresql uses aeson Value and it prevents using toEncoding to get from the value directly to a string.
This commit is contained in:
parent
0032456925
commit
85c6354291
9 changed files with 84 additions and 9 deletions
|
@ -13,7 +13,7 @@
|
||||||
-- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
-- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
|
||||||
RemoteRawObject
|
RemoteRawObject
|
||||||
content Value
|
content PersistJSONObject
|
||||||
received UTCTime
|
received UTCTime
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
RemoteRawObject
|
RemoteRawObject
|
||||||
content Value
|
content PersistJSONObject
|
||||||
received UTCTime
|
received UTCTime
|
||||||
|
|
||||||
RemoteDiscussion
|
RemoteDiscussion
|
||||||
|
|
|
@ -60,9 +60,12 @@ _ .=? Nothing = mempty
|
||||||
k .=? (Just v) = k .= v
|
k .=? (Just v) = k .= v
|
||||||
|
|
||||||
data WithValue a = WithValue
|
data WithValue a = WithValue
|
||||||
{ wvRaw :: Value
|
{ wvRaw :: Object
|
||||||
, wvParsed :: a
|
, wvParsed :: a
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON a => FromJSON (WithValue a) where
|
instance FromJSON a => FromJSON (WithValue a) where
|
||||||
parseJSON v = WithValue v <$> parseJSON v
|
parseJSON v =
|
||||||
|
flip WithValue
|
||||||
|
<$> parseJSON v
|
||||||
|
<*> withObject "WithValue" pure v
|
||||||
|
|
69
src/Database/Persist/JSON.hs
Normal file
69
src/Database/Persist/JSON.hs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Persistent field type for efficient storage of JSON values, and storage of
|
||||||
|
-- Haskell values in general using their JSON representation. Requires
|
||||||
|
-- PostgreSQL, and directly uses PostgreSQL's @jsonb@ type.
|
||||||
|
--
|
||||||
|
-- The module "Database.Persist.PostgreSQL.JSON" from @persistent-postgresql@
|
||||||
|
-- provides similar functionality, but it uses aeson's 'Value' type, which
|
||||||
|
-- means all encoding has to go through 'Value' and we can't benefit from
|
||||||
|
-- 'toEncoding'.
|
||||||
|
module Database.Persist.JSON
|
||||||
|
( PersistJSON (..)
|
||||||
|
, PersistJSONValue
|
||||||
|
, PersistJSONObject
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Aeson.Text
|
||||||
|
import Data.Text.Lazy.Encoding
|
||||||
|
import Database.Persist
|
||||||
|
import Database.Persist.Sql
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
newtype PersistJSON a = PersistJSON
|
||||||
|
{ persistJSONValue :: a
|
||||||
|
}
|
||||||
|
|
||||||
|
type PersistJSONValue = PersistJSON Value
|
||||||
|
|
||||||
|
type PersistJSONObject = PersistJSON Object
|
||||||
|
|
||||||
|
-- persistent-postgresql turns jsonb values into PersistByteString, but it
|
||||||
|
-- encodes PersistByteString in bytea encoding. So, we encode to PersistText
|
||||||
|
-- (to create text encoding, not bytea) and decode from PersistByteString
|
||||||
|
-- (because that's what persistent-postgresql sends, which is convenient
|
||||||
|
-- because we can directly decode the ByteString using aeson).
|
||||||
|
instance (FromJSON a, ToJSON a) => PersistField (PersistJSON a) where
|
||||||
|
toPersistValue = toPersistValue . encodeToLazyText . persistJSONValue
|
||||||
|
fromPersistValue (PersistByteString b) =
|
||||||
|
case eitherDecodeStrict b of
|
||||||
|
Left s -> Left $ T.concat
|
||||||
|
[ "Decoding jsonb value ", T.pack (show b), " failed: "
|
||||||
|
, T.pack s
|
||||||
|
]
|
||||||
|
Right x -> Right $ PersistJSON x
|
||||||
|
fromPersistValue v =
|
||||||
|
Left $
|
||||||
|
"Expected jsonb field to be decoded by persistent-postgresql as \
|
||||||
|
\a PersistByteString, instead got " <> T.pack (show v)
|
||||||
|
|
||||||
|
instance (FromJSON a, ToJSON a) => PersistFieldSql (PersistJSON a) where
|
||||||
|
sqlType _ = SqlOther "jsonb"
|
|
@ -24,7 +24,7 @@ import Control.Monad
|
||||||
import Control.Monad.Logger.CallStack
|
import Control.Monad.Logger.CallStack
|
||||||
import Control.Monad.Trans.Except
|
import Control.Monad.Trans.Except
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
import Data.Aeson (Value)
|
import Data.Aeson (Object)
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text.Encoding
|
import Data.Text.Encoding
|
||||||
|
@ -39,6 +39,7 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
import Database.Persist.JSON
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Web.ActivityPub
|
import Web.ActivityPub
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ import Vervis.Settings
|
||||||
-- | Handle an activity that came to our inbox. Return a description of what we
|
-- | Handle an activity that came to our inbox. Return a description of what we
|
||||||
-- did, and whether we stored the activity or not (so that we can decide
|
-- did, and whether we stored the activity or not (so that we can decide
|
||||||
-- whether to log it for debugging).
|
-- whether to log it for debugging).
|
||||||
handleActivity :: Value -> Text -> InstanceId -> RemoteSharerId -> Activity -> Handler (Text, Bool)
|
handleActivity :: Object -> Text -> InstanceId -> RemoteSharerId -> Activity -> Handler (Text, Bool)
|
||||||
handleActivity raw hActor iidActor rsidActor (Activity _id _luActor audience specific) =
|
handleActivity raw hActor iidActor rsidActor (Activity _id _luActor audience specific) =
|
||||||
case specific of
|
case specific of
|
||||||
CreateActivity (Create note) -> do
|
CreateActivity (Create note) -> do
|
||||||
|
@ -199,7 +200,7 @@ handleActivity raw hActor iidActor rsidActor (Activity _id _luActor audience spe
|
||||||
done "Got Create Note replying to remote message which belongs to a different discussion"
|
done "Got Create Note replying to remote message which belongs to a different discussion"
|
||||||
return $ Just $ Left mid
|
return $ Just $ Left mid
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
rroid <- lift $ insert $ RemoteRawObject raw now
|
rroid <- lift $ insert $ RemoteRawObject (PersistJSON raw) now
|
||||||
mid <- lift $ insert Message
|
mid <- lift $ insert Message
|
||||||
{ messageCreated = published
|
{ messageCreated = published
|
||||||
, messageContent = content
|
, messageContent = content
|
||||||
|
|
|
@ -171,7 +171,7 @@ postInboxR = do
|
||||||
(h, luActor) <- f2l . actorDetailId <$> liftE result
|
(h, luActor) <- f2l . actorDetailId <$> liftE result
|
||||||
ActorDetail uActor iid rsid <- liftE result
|
ActorDetail uActor iid rsid <- liftE result
|
||||||
let (h, luActor) = f2l uActor
|
let (h, luActor) = f2l uActor
|
||||||
wv@(WithValue v (Doc h' a)) <- requireJsonBody
|
wv@(WithValue _ (Doc h' a)) <- requireJsonBody
|
||||||
unless (h == h') $
|
unless (h == h') $
|
||||||
throwE "Activity host doesn't match signature key host"
|
throwE "Activity host doesn't match signature key host"
|
||||||
unless (activityActor a == luActor) $
|
unless (activityActor a == luActor) $
|
||||||
|
|
|
@ -40,6 +40,7 @@ import Data.ByteString (ByteString)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Time (UTCTime)
|
import Data.Time (UTCTime)
|
||||||
import Database.Persist.Class (EntityField)
|
import Database.Persist.Class (EntityField)
|
||||||
|
import Database.Persist.JSON (PersistJSONObject)
|
||||||
import Database.Persist.Schema.Types (Entity)
|
import Database.Persist.Schema.Types (Entity)
|
||||||
import Database.Persist.Schema.SQL ()
|
import Database.Persist.Schema.SQL ()
|
||||||
import Database.Persist.Sql (SqlBackend)
|
import Database.Persist.Sql (SqlBackend)
|
||||||
|
|
|
@ -28,7 +28,7 @@ import Yesod.Auth.Account (PersistUserCredentials (..))
|
||||||
import Crypto.PublicVerifKey
|
import Crypto.PublicVerifKey
|
||||||
import Database.Persist.EmailAddress
|
import Database.Persist.EmailAddress
|
||||||
import Database.Persist.Graph.Class
|
import Database.Persist.Graph.Class
|
||||||
import Database.Persist.Postgresql.JSON ()
|
import Database.Persist.JSON
|
||||||
import Network.FedURI (FedURI, LocalURI)
|
import Network.FedURI (FedURI, LocalURI)
|
||||||
|
|
||||||
import Vervis.Model.Group
|
import Vervis.Model.Group
|
||||||
|
|
|
@ -73,6 +73,7 @@ library
|
||||||
Data.Tree.Local
|
Data.Tree.Local
|
||||||
Database.Esqueleto.Local
|
Database.Esqueleto.Local
|
||||||
Database.Persist.Class.Local
|
Database.Persist.Class.Local
|
||||||
|
Database.Persist.JSON
|
||||||
Database.Persist.Sql.Local
|
Database.Persist.Sql.Local
|
||||||
Database.Persist.Local
|
Database.Persist.Local
|
||||||
Database.Persist.Local.Class.PersistEntityHierarchy
|
Database.Persist.Local.Class.PersistEntityHierarchy
|
||||||
|
|
Loading…
Reference in a new issue