From 39ef13e9158cfe7880f289829f95c1a9dd5acf35 Mon Sep 17 00:00:00 2001 From: Cmdv Date: Thu, 20 Nov 2025 12:16:44 +0000 Subject: [PATCH] 1966 - offchain image metadata --- cardano-db-sync/cardano-db-sync.cabal | 1 + .../src/Cardano/DbSync/OffChain/Vote/Types.hs | 5 +- .../Cardano/DbSync/OffChain/Vote/TypesTest.hs | 71 +++++++++++++++++++ cardano-db-sync/test/Main.hs | 2 + .../src/Cardano/Db/Statement/OffChain.hs | 4 +- 5 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 cardano-db-sync/test/Cardano/DbSync/OffChain/Vote/TypesTest.hs diff --git a/cardano-db-sync/cardano-db-sync.cabal b/cardano-db-sync/cardano-db-sync.cabal index 4bbc16a24..3f4303042 100644 --- a/cardano-db-sync/cardano-db-sync.cabal +++ b/cardano-db-sync/cardano-db-sync.cabal @@ -347,6 +347,7 @@ test-suite test Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest Cardano.DbSync.Era.Shelley.Generic.ScriptTest Cardano.DbSync.Gen + Cardano.DbSync.OffChain.Vote.TypesTest Cardano.DbSync.Util.AddressTest Cardano.DbSync.Util.Bech32Test Cardano.DbSync.Util.CborTest diff --git a/cardano-db-sync/src/Cardano/DbSync/OffChain/Vote/Types.hs b/cardano-db-sync/src/Cardano/DbSync/OffChain/Vote/Types.hs index 9e531c3da..dd4c33a87 100644 --- a/cardano-db-sync/src/Cardano/DbSync/OffChain/Vote/Types.hs +++ b/cardano-db-sync/src/Cardano/DbSync/OffChain/Vote/Types.hs @@ -284,8 +284,9 @@ instance FromJSON Image where | (_, tb) <- Text.break (== '/') ctb , Text.isPrefixOf "/" tb , (_, b) <- Text.break (== ';') tb - , Just imageData <- Text.stripPrefix ";base64," b -> - pure $ Image (TextValue imageData) Nothing + , Just _ <- Text.stripPrefix ";base64," b -> + -- Store the full data URI including prefix + pure $ Image curl Nothing _ -> fromImageUrl <$> parseJSON v where withObjectV v' s p = withObject s p v' diff --git a/cardano-db-sync/test/Cardano/DbSync/OffChain/Vote/TypesTest.hs b/cardano-db-sync/test/Cardano/DbSync/OffChain/Vote/TypesTest.hs new file mode 100644 index 000000000..5406c8d7f --- /dev/null +++ b/cardano-db-sync/test/Cardano/DbSync/OffChain/Vote/TypesTest.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.DbSync.OffChain.Vote.TypesTest (tests) where + +import Cardano.DbSync.OffChain.Vote.Types +import Cardano.Prelude +import qualified Data.Aeson as Aeson +import qualified Data.ByteString.Lazy as LBS +import qualified Data.Text as Text +import Hedgehog +import qualified Hedgehog as H +import Prelude () + +tests :: IO Bool +tests = + checkParallel $ + Group + "Cardano.DbSync.OffChain.Vote.Types" + [ ("Image preserves data URI prefix", prop_image_preserves_data_uri_prefix) + , ("Image handles URL with hash", prop_image_handles_url_with_hash) + , ("Image preserves JPEG data URI", prop_image_preserves_jpeg_data_uri) + ] + +prop_image_preserves_data_uri_prefix :: Property +prop_image_preserves_data_uri_prefix = property $ do + let jsonWithDataUri = + LBS.fromStrict $ + encodeUtf8 + "{ \"contentUrl\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA\" }" + + case Aeson.eitherDecode jsonWithDataUri of + Left err -> do + H.footnote $ "Parse failed: " <> err + H.failure + Right (img :: Image) -> do + let imgContent = textValue $ content img + H.assert $ "data:" `Text.isPrefixOf` imgContent + H.assert $ "image/png" `Text.isInfixOf` imgContent + H.assert $ "base64" `Text.isInfixOf` imgContent + imgContent === "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA" + +prop_image_handles_url_with_hash :: Property +prop_image_handles_url_with_hash = property $ do + let jsonWithUrl = + LBS.fromStrict $ + encodeUtf8 + "{ \"contentUrl\": \"https://example.com/image.png\", \"sha256\": \"abc123\" }" + + case Aeson.eitherDecode jsonWithUrl of + Left err -> do + H.footnote $ "Parse failed: " <> err + H.failure + Right (img :: Image) -> do + textValue (content img) === "https://example.com/image.png" + (textValue <$> msha256 img) === Just "abc123" + +prop_image_preserves_jpeg_data_uri :: Property +prop_image_preserves_jpeg_data_uri = property $ do + let jsonWithJpeg = + LBS.fromStrict $ + encodeUtf8 + "{ \"contentUrl\": \"data:image/jpeg;base64,/9j/4AAQSkZJRg\" }" + + case Aeson.eitherDecode jsonWithJpeg of + Left err -> do + H.footnote $ "Parse failed: " <> err + H.failure + Right (img :: Image) -> do + let imgContent = textValue $ content img + imgContent === "data:image/jpeg;base64,/9j/4AAQSkZJRg" diff --git a/cardano-db-sync/test/Main.hs b/cardano-db-sync/test/Main.hs index b629870a1..8ce2c42a2 100644 --- a/cardano-db-sync/test/Main.hs +++ b/cardano-db-sync/test/Main.hs @@ -4,6 +4,7 @@ import qualified Cardano.DbSync.ApiTest as Api import qualified Cardano.DbSync.Config.TypesTest as Types import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptDataTest as ScriptData import qualified Cardano.DbSync.Era.Shelley.Generic.ScriptTest as Script +import qualified Cardano.DbSync.OffChain.Vote.TypesTest as VoteTypes import qualified Cardano.DbSync.Util.AddressTest as Address import qualified Cardano.DbSync.Util.Bech32Test as Bech32 import qualified Cardano.DbSync.Util.CborTest as Cbor @@ -23,4 +24,5 @@ main = , DbSync.tests , Types.tests , Api.tests + , VoteTypes.tests ] diff --git a/cardano-db/src/Cardano/Db/Statement/OffChain.hs b/cardano-db/src/Cardano/Db/Statement/OffChain.hs index f274ee0e9..668cfea0f 100644 --- a/cardano-db/src/Cardano/Db/Statement/OffChain.hs +++ b/cardano-db/src/Cardano/Db/Statement/OffChain.hs @@ -507,7 +507,9 @@ insertBulkOffChainVoteData offChainVoteData = do insertBulkOffChainVoteDrepDataStmt :: HsqlStmt.Statement [SO.OffChainVoteDrepData] () insertBulkOffChainVoteDrepDataStmt = - insertBulk + insertBulkWith + (ReplaceWithColumns ["off_chain_vote_data_id"]) + False extractOffChainVoteDrepData SO.offChainVoteDrepDataBulkEncoder NoResultBulk