From 8c7e3398c255fa6bbcb13d5b72158f8a4fbe70da Mon Sep 17 00:00:00 2001 From: Sudip Bhattarai Date: Thu, 31 Jul 2025 12:05:54 +0545 Subject: [PATCH 1/2] chore: fix haskell lint and formatting --- govtool/backend/src/VVA/API.hs | 71 +++++---- govtool/backend/src/VVA/API/Types.hs | 42 +++--- govtool/backend/src/VVA/Account.hs | 13 +- govtool/backend/src/VVA/AdaHolder.hs | 6 +- govtool/backend/src/VVA/Config.hs | 28 ++-- govtool/backend/src/VVA/DRep.hs | 85 ++++++----- govtool/backend/src/VVA/Ipfs.hs | 88 ++++++----- govtool/backend/src/VVA/Network.hs | 2 +- govtool/backend/src/VVA/Proposal.hs | 58 ++++---- govtool/backend/src/VVA/Transaction.hs | 4 +- govtool/backend/src/VVA/Types.hs | 198 +++++++++++++------------ 11 files changed, 312 insertions(+), 283 deletions(-) diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index cfbb35960..5185c3882 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -1,10 +1,10 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE DataKinds #-} module VVA.API where @@ -13,9 +13,11 @@ import Control.Exception (throw, throwIO) import Control.Monad.Except (runExceptT, throwError) import Control.Monad.Reader -import Data.Aeson (Value(..), Array, decode, ToJSON, toJSON) +import Data.Aeson (Array, ToJSON, Value (..), decode, toJSON) import Data.Bool (Bool) -import Data.List (sortOn, sort) +import Data.ByteString.Lazy (ByteString) +import qualified Data.ByteString.Lazy as BSL +import Data.List (sort, sortOn) import qualified Data.Map as Map import Data.Maybe (Maybe (Nothing), catMaybes, fromMaybe, mapMaybe) import Data.Ord (Down (..)) @@ -23,38 +25,35 @@ import Data.Text hiding (any, drop, elem, filter, lengt import qualified Data.Text as Text import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL -import qualified Data.Vector as V +import Data.Time (TimeZone, localTimeToUTC) import Data.Time.LocalTime (TimeZone, getCurrentTimeZone) - +import qualified Data.Vector as V import Numeric.Natural (Natural) import Servant.API -import Servant.Server import Servant.Exception (Throws) +import Servant.Server + import System.Random (randomRIO) import Text.Read (readMaybe) +import VVA.Account as Account import qualified VVA.AdaHolder as AdaHolder import VVA.API.Types import VVA.Cache (cacheRequest) import VVA.Config import qualified VVA.DRep as DRep import qualified VVA.Epoch as Epoch +import qualified VVA.Ipfs as Ipfs import VVA.Network as Network -import VVA.Account as Account import qualified VVA.Proposal as Proposal import qualified VVA.Transaction as Transaction import qualified VVA.Types as Types import VVA.Types (App, AppEnv (..), - AppError (CriticalError, InternalError, ValidationError, AppIpfsError), + AppError (AppIpfsError, CriticalError, InternalError, ValidationError), CacheEnv (..)) -import Data.Time (TimeZone, localTimeToUTC) -import qualified VVA.Ipfs as Ipfs -import Data.ByteString.Lazy (ByteString) -import qualified Data.ByteString.Lazy as BSL -import Servant.Exception (Throws) type VVAApi = "ipfs" @@ -127,7 +126,7 @@ upload mFileName fileContentText = do throwError $ ValidationError "The uploaded file is larger than 500Kb" eIpfsHash <- liftIO $ Ipfs.ipfsUpload vvaPinataJwt fileName fileContent case eIpfsHash of - Left err -> throwError $ AppIpfsError err + Left err -> throwError $ AppIpfsError err Right ipfsHash -> return $ UploadResponse ipfsHash mapDRepType :: Types.DRepType -> DRepType @@ -188,9 +187,9 @@ drepList mSearchQuery statuses mSortMode mPage mPageSize = do viewLower = Text.toLower dRepRegistrationView hashLower = Text.toLower dRepRegistrationDRepHash in case dRepRegistrationType of - Types.SoleVoter -> + Types.SoleVoter -> searchLower == viewLower || searchLower == hashLower - Types.DRep -> + Types.DRep -> True @@ -318,13 +317,13 @@ getVotes :: App m => HexText -> [GovernanceActionType] -> Maybe GovernanceAction getVotes (unHexText -> dRepId) selectedTypes sortMode mSearch = do CacheEnv {dRepGetVotesCache} <- asks vvaCache (votes, proposals) <- cacheRequest dRepGetVotesCache dRepId $ DRep.getVotes dRepId [] - + let voteMapById = Map.fromList $ map (\vote -> (Types.voteGovActionId vote, vote)) votes - - processedProposals <- filter (isProposalSearchedFor mSearch) <$> + + processedProposals <- filter (isProposalSearchedFor mSearch) <$> mapSortAndFilterProposals selectedTypes sortMode proposals - + return [ VoteResponse { voteResponseVote = voteToResponse vote @@ -334,7 +333,7 @@ getVotes (unHexText -> dRepId) selectedTypes sortMode mSearch = do , let govActionId = unHexText (proposalResponseTxHash proposalResponse) <> "#" <> pack (show $ proposalResponseIndex proposalResponse) , Just vote <- [Map.lookup govActionId voteMapById] ] - + drepInfo :: App m => HexText -> m DRepInfoResponse drepInfo (unHexText -> dRepId) = do CacheEnv {dRepInfoCache} <- asks vvaCache @@ -365,15 +364,15 @@ drepInfo (unHexText -> dRepId) = do drepVotingPowerList :: App m => [Text] -> m [DRepVotingPowerListResponse] drepVotingPowerList identifiers = do CacheEnv {dRepVotingPowerListCache} <- asks vvaCache - + let cacheKey = Text.intercalate "," (sort identifiers) - - results <- cacheRequest dRepVotingPowerListCache cacheKey $ + + results <- cacheRequest dRepVotingPowerListCache cacheKey $ DRep.getDRepsVotingPowerList identifiers - + return $ map toDRepVotingPowerListResponse results where - toDRepVotingPowerListResponse Types.DRepVotingPowerList{..} = + toDRepVotingPowerListResponse Types.DRepVotingPowerList{..} = DRepVotingPowerListResponse { drepVotingPowerListResponseView = drepView , drepVotingPowerListResponseHashRaw = HexText drepHashRaw @@ -456,9 +455,9 @@ getProposal g@(GovActionId govActionTxHash govActionIndex) mDrepId' = do let mDrepId = unHexText <$> mDrepId' CacheEnv {getProposalCache} <- asks vvaCache proposal@Types.Proposal {proposalUrl, proposalDocHash} <- cacheRequest getProposalCache (unHexText govActionTxHash, govActionIndex) (Proposal.getProposal (unHexText govActionTxHash) govActionIndex) - + timeZone <- liftIO getCurrentTimeZone - + let proposalResponse = proposalToResponse timeZone proposal voteResponse <- case mDrepId of Nothing -> return Nothing @@ -478,20 +477,20 @@ getProposal g@(GovActionId govActionTxHash govActionIndex) mDrepId' = do getEnactedProposalDetails :: App m => Maybe GovernanceActionType -> m (Maybe EnactedProposalDetailsResponse) getEnactedProposalDetails maybeType = do let proposalType = maybe "HardForkInitiation" governanceActionTypeToText maybeType - + mDetails <- Proposal.getPreviousEnactedProposal proposalType - + let response = enactedProposalDetailsToResponse <$> mDetails - + return response where governanceActionTypeToText :: GovernanceActionType -> Text - governanceActionTypeToText actionType = + governanceActionTypeToText actionType = case actionType of HardForkInitiation -> "HardForkInitiation" - ParameterChange -> "ParameterChange" - _ -> "HardForkInitiation" - + ParameterChange -> "ParameterChange" + _ -> "HardForkInitiation" + enactedProposalDetailsToResponse :: Types.EnactedProposalDetails -> EnactedProposalDetailsResponse enactedProposalDetailsToResponse Types.EnactedProposalDetails{..} = EnactedProposalDetailsResponse @@ -513,7 +512,7 @@ getTransactionStatus (unHexText -> transactionId) = do return $ GetTransactionStatusResponse $ case status of Just value -> Just $ toJSON value Nothing -> Nothing - + throw500 :: App m => m () throw500 = throwError $ CriticalError "intentional system break for testing purposes" diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 360f9be64..2b1badf27 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -205,7 +205,14 @@ instance ToParamSchema GovernanceActionType where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data DRepSortMode = Random | VotingPower | RegistrationDate | Status deriving (Bounded, Enum, Eq, Generic, Read, Show) +data DRepSortMode = Random | VotingPower | RegistrationDate | Status deriving + ( Bounded + , Enum + , Eq + , Generic + , Read + , Show + ) instance FromJSON DRepSortMode where parseJSON (Aeson.String dRepSortMode) = pure $ fromJust $ readMaybe (Text.unpack dRepSortMode) @@ -406,7 +413,8 @@ data ProposalResponse } deriving (Generic, Show) -newtype ProposalAuthors = ProposalAuthors { getProposalAuthors :: Value } +newtype ProposalAuthors + = ProposalAuthors { getProposalAuthors :: Value } deriving newtype (Show) instance FromJSON ProposalAuthors where @@ -659,7 +667,7 @@ data DRepInfoResponse , dRepInfoResponseGivenName :: Maybe Text , dRepInfoResponseObjectives :: Maybe Text , dRepInfoResponseMotivations :: Maybe Text - , dRepInfoResponseQualifications :: Maybe Text + , dRepInfoResponseQualifications :: Maybe Text , dRepInfoResponseImageUrl :: Maybe Text , dRepInfoResponseImageHash :: Maybe HexText } @@ -906,7 +914,7 @@ data DRep , dRepGivenName :: Maybe Text , dRepObjectives :: Maybe Text , dRepMotivations :: Maybe Text - , dRepQualifications :: Maybe Text + , dRepQualifications :: Maybe Text , dRepImageUrl :: Maybe Text , dRepImageHash :: Maybe HexText , dRepIdentityReferences :: Maybe DRepReferences @@ -1011,11 +1019,11 @@ instance ToSchema DelegationResponse where data GetNetworkInfoResponse = GetNetworkInfoResponse - { getNetworkInfoResponseCurrentTime :: UTCTime - , getNetworkInfoResponseEpochNo :: Integer - , getNetworkInfoResponseBlockNo :: Integer - , getNetworkInfoResponseNetworkName :: Text - } + { getNetworkInfoResponseCurrentTime :: UTCTime + , getNetworkInfoResponseEpochNo :: Integer + , getNetworkInfoResponseBlockNo :: Integer + , getNetworkInfoResponseNetworkName :: Text + } deriveJSON (jsonOptions "getNetworkInfoResponse") ''GetNetworkInfoResponse @@ -1035,11 +1043,11 @@ instance ToSchema GetNetworkInfoResponse where data GetNetworkTotalStakeResponse = GetNetworkTotalStakeResponse - { getNetworkTotalStakeResponseTotalStakeControlledByDReps :: Integer - , getNetworkTotalStakeResponseTotalStakeControlledBySPOs :: Integer - , getNetworkTotalStakeResponseAlwaysAbstainVotingPower :: Integer - , getNetworkTotalStakeResponseAlwaysNoConfidenceVotingPower :: Integer - } + { getNetworkTotalStakeResponseTotalStakeControlledByDReps :: Integer + , getNetworkTotalStakeResponseTotalStakeControlledBySPOs :: Integer + , getNetworkTotalStakeResponseAlwaysAbstainVotingPower :: Integer + , getNetworkTotalStakeResponseAlwaysNoConfidenceVotingPower :: Integer + } deriveJSON (jsonOptions "getNetworkTotalStakeResponse") ''GetNetworkTotalStakeResponse @@ -1113,10 +1121,8 @@ data GetAccountInfoResponse deriving (Generic, Show) deriveJSON (jsonOptions "getAccountInfoResponse") ''GetAccountInfoResponse -data UploadResponse - = UploadResponse - { uploadResponseIpfsCid :: Text - } +newtype UploadResponse + = UploadResponse { uploadResponseIpfsCid :: Text } deriving (Generic, Show) deriveJSON (jsonOptions "uploadResponse") ''UploadResponse diff --git a/govtool/backend/src/VVA/Account.hs b/govtool/backend/src/VVA/Account.hs index 10aa92e52..5ce98ae78 100644 --- a/govtool/backend/src/VVA/Account.hs +++ b/govtool/backend/src/VVA/Account.hs @@ -1,21 +1,24 @@ -{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TemplateHaskell #-} module VVA.Account where import Control.Monad.Except (MonadError, throwError) import Control.Monad.Reader (MonadIO, MonadReader, liftIO) + import Data.ByteString (ByteString) import Data.FileEmbed (embedFile) +import Data.Has (Has) import Data.String (fromString) -import qualified Database.PostgreSQL.Simple as SQL -import VVA.Types (AppError(..), AccountInfo(..)) import Data.Text (Text, unpack) import qualified Data.Text.Encoding as Text import qualified Data.Text.IO as Text -import Data.Has (Has) + +import qualified Database.PostgreSQL.Simple as SQL + import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AccountInfo (..), AppError (..)) sqlFrom :: ByteString -> SQL.Query sqlFrom = fromString . unpack . Text.decodeUtf8 diff --git a/govtool/backend/src/VVA/AdaHolder.hs b/govtool/backend/src/VVA/AdaHolder.hs index 6232a8310..b708cd26e 100644 --- a/govtool/backend/src/VVA/AdaHolder.hs +++ b/govtool/backend/src/VVA/AdaHolder.hs @@ -1,12 +1,12 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ScopedTypeVariables #-} module VVA.AdaHolder where -import Control.Exception (try, SomeException) +import Control.Exception (SomeException, try) import Control.Monad.Except import Control.Monad.Reader @@ -65,4 +65,4 @@ getStakeKeyVotingPower stakeKey = withPool $ \conn -> do return 0 Right _ -> do Text.putStrLn ("Unexpected result for stake key: " <> stakeKey) - return 0 \ No newline at end of file + return 0 diff --git a/govtool/backend/src/VVA/Config.hs b/govtool/backend/src/VVA/Config.hs index 49151d630..997a3c231 100644 --- a/govtool/backend/src/VVA/Config.hs +++ b/govtool/backend/src/VVA/Config.hs @@ -68,19 +68,19 @@ instance DefaultConfig DBConfig where data VVAConfigInternal = VVAConfigInternal { -- | db-sync database access. - vVAConfigInternalDbsyncconfig :: DBConfig + vVAConfigInternalDbsyncconfig :: DBConfig -- | Server port. - , vVAConfigInternalPort :: Int + , vVAConfigInternalPort :: Int -- | Server host. - , vVAConfigInternalHost :: Text + , vVAConfigInternalHost :: Text -- | Request cache duration - , vVaConfigInternalCacheDurationSeconds :: Int + , vVaConfigInternalCacheDurationSeconds :: Int -- | Sentry DSN - , vVAConfigInternalSentrydsn :: String + , vVAConfigInternalSentrydsn :: String -- | Sentry environment - , vVAConfigInternalSentryEnv :: String + , vVAConfigInternalSentryEnv :: String -- | Pinata API JWT - , vVAConfigInternalPinataApiJwt :: Maybe Text + , vVAConfigInternalPinataApiJwt :: Maybe Text } deriving (FromConfig, Generic, Show) @@ -101,19 +101,19 @@ instance DefaultConfig VVAConfigInternal where data VVAConfig = VVAConfig { -- | db-sync database credentials. - dbSyncConnectionString :: Text + dbSyncConnectionString :: Text -- | Server port. - , serverPort :: Int + , serverPort :: Int -- | Server host. - , serverHost :: Text + , serverHost :: Text -- | Request cache duration - , cacheDurationSeconds :: Int + , cacheDurationSeconds :: Int -- | Sentry DSN - , sentryDSN :: String + , sentryDSN :: String -- | Sentry environment - , sentryEnv :: String + , sentryEnv :: String -- | Pinata API JWT - , pinataApiJwt :: Maybe Text + , pinataApiJwt :: Maybe Text } deriving (Generic, Show, ToJSON) diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index f8c9f8737..39342a7eb 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -13,56 +13,59 @@ import Crypto.Hash import Data.Aeson (Value) import Data.ByteString (ByteString) -import qualified Data.ByteString.Base16 as Base16 -import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Base16 as Base16 +import qualified Data.ByteString.Char8 as C import Data.FileEmbed (embedFile) import Data.Foldable (Foldable (sum)) import Data.Has (Has) -import qualified Data.Map as M +import qualified Data.Map as M import Data.Maybe (fromMaybe, isJust, isNothing) import Data.Scientific import Data.String (fromString) -import Data.Text (Text, pack, unpack, intercalate) -import qualified Data.Text.Encoding as Text +import Data.Text (Text, intercalate, pack, unpack) +import qualified Data.Text.Encoding as Text import Data.Time -import qualified Database.PostgreSQL.Simple as SQL -import Database.PostgreSQL.Simple.Types (In(..)) +import qualified Database.PostgreSQL.Simple as SQL import Database.PostgreSQL.Simple.FromRow +import Database.PostgreSQL.Simple.Types (In (..)) import VVA.Config import VVA.Pool (ConnectionPool, withPool) -import qualified VVA.Proposal as Proposal -import VVA.Types (AppError, DRepInfo (..), DRepRegistration (..), DRepStatus (..), - DRepType (..), Proposal (..), Vote (..), DRepVotingPowerList (..)) +import qualified VVA.Proposal as Proposal +import VVA.Types (AppError, DRepInfo (..), DRepRegistration (..), + DRepStatus (..), DRepType (..), DRepVotingPowerList (..), + Proposal (..), Vote (..)) -data DRepQueryResult = DRepQueryResult - { queryDrepHash :: Text - , queryDrepView :: Text - , queryIsScriptBased :: Bool - , queryUrl :: Maybe Text - , queryDataHash :: Maybe Text - , queryDeposit :: Scientific - , queryVotingPower :: Maybe Integer - , queryIsActive :: Bool - , queryTxHash :: Maybe Text - , queryDate :: LocalTime - , queryLatestDeposit :: Scientific - , queryLatestNonDeregisterVotingAnchorWasNotNull :: Bool - , queryMetadataError :: Maybe Text - , queryPaymentAddress :: Maybe Text - , queryGivenName :: Maybe Text - , queryObjectives :: Maybe Text - , queryMotivations :: Maybe Text - , queryQualifications :: Maybe Text - , queryImageUrl :: Maybe Text - , queryImageHash :: Maybe Text - , queryIdentityReferences :: Maybe Value - , queryLinkReferences :: Maybe Value - } deriving (Show) +data DRepQueryResult + = DRepQueryResult + { queryDrepHash :: Text + , queryDrepView :: Text + , queryIsScriptBased :: Bool + , queryUrl :: Maybe Text + , queryDataHash :: Maybe Text + , queryDeposit :: Scientific + , queryVotingPower :: Maybe Integer + , queryIsActive :: Bool + , queryTxHash :: Maybe Text + , queryDate :: LocalTime + , queryLatestDeposit :: Scientific + , queryLatestNonDeregisterVotingAnchorWasNotNull :: Bool + , queryMetadataError :: Maybe Text + , queryPaymentAddress :: Maybe Text + , queryGivenName :: Maybe Text + , queryObjectives :: Maybe Text + , queryMotivations :: Maybe Text + , queryQualifications :: Maybe Text + , queryImageUrl :: Maybe Text + , queryImageHash :: Maybe Text + , queryIdentityReferences :: Maybe Value + , queryLinkReferences :: Maybe Value + } + deriving (Show) instance FromRow DRepQueryResult where - fromRow = DRepQueryResult + fromRow = DRepQueryResult <$> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field @@ -93,7 +96,7 @@ listDReps mSearchQuery = withPool $ \conn -> do timeZone <- liftIO getCurrentTimeZone return - [ DRepRegistration + [ DRepRegistration (queryDrepHash result) (queryDrepView result) (queryIsScriptBased result) @@ -127,7 +130,7 @@ listDReps mSearchQuery = withPool $ \conn -> do | latestDeposit' < 0 && queryLatestNonDeregisterVotingAnchorWasNotNull result = DRep | Data.Maybe.isJust (queryUrl result) = DRep ] - + getVotingPowerSql :: SQL.Query getVotingPowerSql = sqlFrom $(embedFile "sql/get-voting-power.sql") @@ -165,7 +168,7 @@ getVotes drepId selectedProposals = withPool $ \conn -> do timeZone <- liftIO getCurrentTimeZone - let votes = + let votes = [ Vote proposalId' govActionId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash' | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date', voteTxHash') <- results , govActionId' `elem` proposalsToSelect @@ -252,11 +255,11 @@ getDRepsVotingPowerList identifiers = withPool $ \conn -> do else do resultsPerIdentifier <- forM identifiers $ \identifier -> do liftIO $ SQL.query conn getFilteredDRepVotingPowerSql (identifier, identifier) - + return $ concat resultsPerIdentifier - + return [ DRepVotingPowerList view hashRaw votingPower givenName | (view, hashRaw, votingPower', givenName) <- results , let votingPower = floor @Scientific votingPower' - ] \ No newline at end of file + ] diff --git a/govtool/backend/src/VVA/Ipfs.hs b/govtool/backend/src/VVA/Ipfs.hs index 86202f298..067ff43db 100644 --- a/govtool/backend/src/VVA/Ipfs.hs +++ b/govtool/backend/src/VVA/Ipfs.hs @@ -1,41 +1,53 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -module VVA.Ipfs (ipfsUpload, IpfsError(..)) where - -import Control.Exception (SomeException, try) -import Control.Monad.IO.Class (liftIO) -import qualified Data.Aeson as A -import Data.Aeson (FromJSON(parseJSON), withObject, (.:), eitherDecode, ToJSON(..), encode,(.=),object) -import qualified Data.ByteString.Lazy as LBS -import Data.Text (Text) -import qualified Data.Text.Encoding as TE -import GHC.Generics (Generic) -import Network.HTTP.Client (newManager, parseRequest, httpLbs, method, requestHeaders, RequestBody(..), Request, responseBody, responseStatus) -import Network.HTTP.Client.TLS (tlsManagerSettings) +module VVA.Ipfs + ( IpfsError (..) + , ipfsUpload + ) where + +import Control.Exception (SomeException, try) +import Control.Monad.IO.Class (liftIO) + +import Data.Aeson (FromJSON (parseJSON), ToJSON (..), eitherDecode, + encode, object, withObject, (.:), (.=)) +import qualified Data.Aeson as A +import qualified Data.ByteString.Lazy as LBS +import qualified Data.ByteString.Lazy.Char8 as LBS8 +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Encoding as TL + +import GHC.Generics (Generic) + +import Network.HTTP.Client (Request, RequestBody (..), httpLbs, method, + newManager, parseRequest, requestHeaders, + responseBody, responseStatus) import Network.HTTP.Client.MultipartFormData (formDataBody, partBS, partFileRequestBody) -import Network.HTTP.Types.Status (statusIsSuccessful, Status, status503, status400) -import qualified Data.ByteString.Lazy.Char8 as LBS8 -import qualified Data.Text.Lazy as TL -import qualified Data.Text.Lazy.Encoding as TL -import qualified Data.Text as T -import Servant.Server (ServerError (errBody)) -import Servant.Exception (ToServantErr(..), Exception(..)) - - -data PinataData = PinataData - { cid :: Text - , size :: Int - , created_at :: Text - , isDuplicate :: Maybe Bool - } deriving (Show, Generic) +import Network.HTTP.Client.TLS (tlsManagerSettings) +import Network.HTTP.Types.Status (Status, status400, status503, statusIsSuccessful) + +import Servant.Exception (Exception (..), ToServantErr (..)) +import Servant.Server (ServerError (errBody)) + + +data PinataData + = PinataData + { cid :: Text + , size :: Int + , created_at :: Text + , isDuplicate :: Maybe Bool + } + deriving (Generic, Show) instance FromJSON PinataData -data PinataSuccessResponse = PinataSuccessResponse - { pinataData :: PinataData - } deriving (Show) +newtype PinataSuccessResponse + = PinataSuccessResponse { pinataData :: PinataData } + deriving (Show) instance FromJSON PinataSuccessResponse where parseJSON = withObject "PinataSuccessResponse" $ \v -> PinataSuccessResponse @@ -45,9 +57,9 @@ data IpfsError = PinataConnectionError String | PinataAPIError Status LBS.ByteString | PinataDecodingError String LBS.ByteString - | IpfsUnconfiguredError + | IpfsUnconfiguredError | OtherIpfsError String - deriving (Show, Generic) + deriving (Generic, Show) instance ToJSON IpfsError where toJSON (PinataConnectionError msg) = @@ -86,19 +98,19 @@ instance Exception IpfsError instance ToServantErr IpfsError where status (OtherIpfsError _) = status400 - status _ = status503 + status _ = status503 message (PinataConnectionError msg) = T.pack ("Pinata service connection error: " <> msg) message (PinataAPIError status body) = T.pack ("Pinata API error: " <> show status <> " - " <> LBS8.unpack body) message (PinataDecodingError msg body) = T.pack ("Pinata decoding error: " <> msg <> " - " <> LBS8.unpack body) - message IpfsUnconfiguredError = T.pack ("Backend is not configured to support ipfs upload") + message IpfsUnconfiguredError = T.pack "Backend is not configured to support ipfs upload" message (OtherIpfsError msg) = T.pack msg ipfsUpload :: Maybe Text -> Text -> LBS.ByteString -> IO (Either IpfsError Text) ipfsUpload maybeJwt fileName fileContent = case maybeJwt of - Nothing -> pure $ Left $ IpfsUnconfiguredError - Just "" -> pure $ Left $ IpfsUnconfiguredError + Nothing -> pure $ Left IpfsUnconfiguredError + Just "" -> pure $ Left IpfsUnconfiguredError Just jwt -> do manager <- newManager tlsManagerSettings initialRequest <- parseRequest "https://uploads.pinata.cloud/v3/files" diff --git a/govtool/backend/src/VVA/Network.hs b/govtool/backend/src/VVA/Network.hs index 797d47ac3..ec99dc82a 100644 --- a/govtool/backend/src/VVA/Network.hs +++ b/govtool/backend/src/VVA/Network.hs @@ -34,7 +34,7 @@ networkInfo :: networkInfo = withPool $ \conn -> do result <- liftIO $ SQL.query_ conn networkInfoSql current_time <- liftIO getCurrentTime - case result of + case result of [( network_epoch , network_block_no , network_name diff --git a/govtool/backend/src/VVA/Proposal.hs b/govtool/backend/src/VVA/Proposal.hs index 4d61ec752..b4c5b502d 100644 --- a/govtool/backend/src/VVA/Proposal.hs +++ b/govtool/backend/src/VVA/Proposal.hs @@ -2,42 +2,43 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ScopedTypeVariables #-} module VVA.Proposal where -import Control.Exception (throw, SomeException, try) -import Control.Monad.Except (MonadError, throwError) +import Control.Exception (SomeException, throw, try) +import Control.Monad.Except (MonadError, throwError) import Control.Monad.Reader import Data.Aeson -import Data.Aeson.Types (Parser, parseMaybe) -import Data.ByteString (ByteString) -import Data.FileEmbed (embedFile) -import Data.Foldable (fold) -import Data.Has (Has, getter) -import qualified Data.Map as Map -import Data.Maybe (fromMaybe, isJust) -import Data.Monoid (Sum (..), getSum) +import Data.Aeson.Types (Parser, parseMaybe) +import Data.ByteString (ByteString) +import Data.FileEmbed (embedFile) +import Data.Foldable (fold) +import Data.Has (Has, getter) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe, isJust) +import Data.Monoid (Sum (..), getSum) import Data.Scientific -import Data.String (fromString) -import Data.Text (Text, pack, unpack) -import qualified Data.Text.Encoding as Text -import qualified Data.Text.IO as Text +import Data.String (fromString) +import Data.Text (Text, pack, unpack) +import qualified Data.Text.Encoding as Text +import qualified Data.Text.IO as Text import Data.Time -import qualified Database.PostgreSQL.Simple as SQL -import qualified Database.PostgreSQL.Simple.Types as PG -import Database.PostgreSQL.Simple.ToField (ToField(..)) -import Database.PostgreSQL.Simple.ToRow (ToRow(..)) +import qualified Database.PostgreSQL.Simple as SQL +import Database.PostgreSQL.Simple.ToField (ToField (..)) +import Database.PostgreSQL.Simple.ToRow (ToRow (..)) +import qualified Database.PostgreSQL.Simple.Types as PG -import GHC.IO.Unsafe (unsafePerformIO) +import GHC.IO.Unsafe (unsafePerformIO) import VVA.Config -import VVA.Pool (ConnectionPool, withPool) -import VVA.Types (AppError (..), Proposal (..), EnactedProposalDetails (..)) +import VVA.Pool (ConnectionPool, withPool) +import VVA.Types (AppError (..), EnactedProposalDetails (..), + Proposal (..)) query1 :: (SQL.ToRow q, SQL.FromRow r) => SQL.Connection -> SQL.Query -> q -> IO (Maybe r) query1 conn q params = do @@ -52,7 +53,8 @@ sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs listProposalsSql :: SQL.Query listProposalsSql = sqlFrom $(embedFile "sql/list-proposals.sql") -newtype TextArray = TextArray [Text] +newtype TextArray + = TextArray [Text] instance ToRow TextArray where toRow (TextArray texts) = map toField texts @@ -81,7 +83,7 @@ getProposals :: getProposals mSearchTerms = withPool $ \conn -> do let searchParam = maybe "" head mSearchTerms liftIO $ do - result <- try $ SQL.query conn listProposalsSql + result <- try $ SQL.query conn listProposalsSql ( searchParam , "%" <> searchParam <> "%" , "%" <> searchParam <> "%" @@ -96,7 +98,7 @@ getProposals mSearchTerms = withPool $ \conn -> do Right rows -> return rows latestEnactedProposalSql :: SQL.Query -latestEnactedProposalSql = +latestEnactedProposalSql = let rawSql = sqlFrom $(embedFile "sql/get-previous-enacted-governance-action-proposal-details.sql") in unsafePerformIO $ do putStrLn $ "[DEBUG] SQL query content: " ++ show rawSql @@ -109,13 +111,13 @@ getPreviousEnactedProposal :: getPreviousEnactedProposal proposalType = withPool $ \conn -> do let query = latestEnactedProposalSql let params = [proposalType] - + result <- liftIO $ try $ do rows <- SQL.query conn query params :: IO [EnactedProposalDetails] case rows of [x] -> return (Just x) _ -> return Nothing - + case result of Left err -> do throwError $ CriticalError $ "Database error: " <> pack (show (err :: SomeException)) @@ -123,6 +125,6 @@ getPreviousEnactedProposal proposalType = withPool $ \conn -> do case proposal of Just details -> do liftIO $ putStrLn $ "[DEBUG] Previous enacted proposal details: " ++ show details - Nothing -> + Nothing -> liftIO $ putStrLn "[DEBUG] No previous enacted proposal found" return proposal diff --git a/govtool/backend/src/VVA/Transaction.hs b/govtool/backend/src/VVA/Transaction.hs index 11c1e349e..36b6edeaa 100644 --- a/govtool/backend/src/VVA/Transaction.hs +++ b/govtool/backend/src/VVA/Transaction.hs @@ -12,11 +12,11 @@ import Data.Aeson (Value) import Data.ByteString (ByteString) import Data.FileEmbed (embedFile) import Data.Has (Has) +import Data.Maybe (fromMaybe) import Data.String (fromString) import Data.Text (Text, pack, unpack) import qualified Data.Text.Encoding as Text import qualified Data.Text.IO as Text -import Data.Maybe (fromMaybe) import qualified Database.PostgreSQL.Simple as SQL @@ -39,4 +39,4 @@ getTransactionStatus transactionId = withPool $ \conn -> do case result of [(transactionConfirmed, votingProcedure)] -> do return $ Just $ TransactionStatus transactionConfirmed votingProcedure - _ -> return Nothing \ No newline at end of file + _ -> return Nothing diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index d5b0f2cf2..98fb7b44e 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -1,36 +1,38 @@ -{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module VVA.Types where import Control.Concurrent.QSem import Control.Exception -import Control.Monad.Except (MonadError) -import Control.Monad.Fail (MonadFail) -import Control.Monad.IO.Class (MonadIO) -import Control.Monad.Reader (MonadReader) -import qualified Data.Aeson as A -import Data.Aeson (Value, ToJSON (..), object, (.=)) -import qualified Data.Cache as Cache +import Control.Monad.Except (MonadError) +import Control.Monad.Fail (MonadFail) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Reader (MonadReader) + +import Data.Aeson (ToJSON (..), Value, object, (.=)) +import qualified Data.Aeson as A +import qualified Data.Cache as Cache import Data.Has -import Data.Pool (Pool) -import Data.Text (Text) -import Data.Time (UTCTime, LocalTime) +import Data.Pool (Pool) import Data.Scientific +import Data.Text (Text) +import Data.Time (LocalTime, UTCTime) -import Database.PostgreSQL.Simple (Connection) +import Database.PostgreSQL.Simple (Connection) +import Database.PostgreSQL.Simple.FromField (FromField (..), ResultError (ConversionFailed), + returnError) import Database.PostgreSQL.Simple.FromRow -import Database.PostgreSQL.Simple.FromField (FromField(..), returnError, ResultError(ConversionFailed)) import VVA.Cache import VVA.Config -import VVA.Ipfs (IpfsError) +import VVA.Ipfs (IpfsError) type App m = (MonadReader AppEnv m, MonadIO m, MonadFail m, MonadError AppError m) @@ -65,10 +67,10 @@ instance Exception AppError instance ToJSON AppError where toJSON (ValidationError msg) = object ["errorType" .= A.String "ValidationError", "message" .= msg] - toJSON (NotFoundError msg) = object ["errorType" .= A.String "NotFoundError", "message" .= msg] - toJSON (CriticalError msg) = object ["errorType" .= A.String "CriticalError", "message" .= msg] - toJSON (InternalError msg) = object ["errorType" .= A.String "InternalError", "message" .= msg] - toJSON (AppIpfsError err) = toJSON err + toJSON (NotFoundError msg) = object ["errorType" .= A.String "NotFoundError", "message" .= msg] + toJSON (CriticalError msg) = object ["errorType" .= A.String "CriticalError", "message" .= msg] + toJSON (InternalError msg) = object ["errorType" .= A.String "InternalError", "message" .= msg] + toJSON (AppIpfsError err) = toJSON err data Vote = Vote @@ -114,28 +116,28 @@ data DRepVotingPowerList , drepVotingPower :: Integer , drepGivenName :: Maybe Text } - deriving (Show, Eq) + deriving (Eq, Show) -data DRepStatus = Active | Inactive | Retired deriving (Show, Eq, Ord) +data DRepStatus = Active | Inactive | Retired deriving (Eq, Ord, Show) instance FromField DRepStatus where fromField f mdata = do (value :: Text) <- fromField f mdata case value of - "Active" -> return Active + "Active" -> return Active "Inactive" -> return Inactive - "Retired" -> return Retired - _ -> returnError ConversionFailed f "Invalid DRepStatus" + "Retired" -> return Retired + _ -> returnError ConversionFailed f "Invalid DRepStatus" -data DRepType = DRep | SoleVoter deriving (Show, Eq) +data DRepType = DRep | SoleVoter deriving (Eq, Show) instance FromField DRepType where fromField f mdata = do (value :: Text) <- fromField f mdata case value of - "DRep" -> return DRep + "DRep" -> return DRep "SoleVoter" -> return SoleVoter - _ -> returnError ConversionFailed f "Invalid DRepType" + _ -> returnError ConversionFailed f "Invalid DRepType" data DRepRegistration = DRepRegistration @@ -155,7 +157,7 @@ data DRepRegistration , dRepRegistrationGivenName :: Maybe Text , dRepRegistrationObjectives :: Maybe Text , dRepRegistrationMotivations :: Maybe Text - , dRepRegistrationQualifications :: Maybe Text + , dRepRegistrationQualifications :: Maybe Text , dRepRegistrationImageUrl :: Maybe Text , dRepRegistrationImageHash :: Maybe Text , dRepRegistrationIdentityReferences :: Maybe Value @@ -188,37 +190,37 @@ instance FromRow DRepRegistration where <*> field -- dRepRegistrationIdentityReferences <*> field -- dRepRegistrationLinkReferences -data Proposal +data Proposal = Proposal - { proposalId :: Integer - , proposalTxHash :: Text - , proposalIndex :: Integer - , proposalType :: Text - , proposalDetails :: Maybe Value - , proposalExpiryDate :: Maybe LocalTime - , proposalExpiryEpochNo :: Maybe Integer - , proposalCreatedDate :: LocalTime - , proposalCreatedEpochNo :: Integer - , proposalUrl :: Text - , proposalDocHash :: Text - , proposalProtocolParams :: Maybe Value - , proposalTitle :: Maybe Text - , proposalAbstract :: Maybe Text - , proposalMotivation :: Maybe Text - , proposalRationale :: Maybe Text - , proposalDRepYesVotes :: Integer - , proposalDRepNoVotes :: Integer - , proposalDRepAbstainVotes :: Integer - , proposalPoolYesVotes :: Integer - , proposalPoolNoVotes :: Integer - , proposalPoolAbstainVotes :: Integer - , proposalCcYesVotes :: Integer - , proposalCcNoVotes :: Integer - , proposalCcAbstainVotes :: Integer - , proposalPrevGovActionIndex :: Maybe Integer - , proposalPrevGovActionTxHash :: Maybe Text - , proposalJson :: Maybe Value - , proposalAuthors :: Maybe Value + { proposalId :: Integer + , proposalTxHash :: Text + , proposalIndex :: Integer + , proposalType :: Text + , proposalDetails :: Maybe Value + , proposalExpiryDate :: Maybe LocalTime + , proposalExpiryEpochNo :: Maybe Integer + , proposalCreatedDate :: LocalTime + , proposalCreatedEpochNo :: Integer + , proposalUrl :: Text + , proposalDocHash :: Text + , proposalProtocolParams :: Maybe Value + , proposalTitle :: Maybe Text + , proposalAbstract :: Maybe Text + , proposalMotivation :: Maybe Text + , proposalRationale :: Maybe Text + , proposalDRepYesVotes :: Integer + , proposalDRepNoVotes :: Integer + , proposalDRepAbstainVotes :: Integer + , proposalPoolYesVotes :: Integer + , proposalPoolNoVotes :: Integer + , proposalPoolAbstainVotes :: Integer + , proposalCcYesVotes :: Integer + , proposalCcNoVotes :: Integer + , proposalCcAbstainVotes :: Integer + , proposalPrevGovActionIndex :: Maybe Integer + , proposalPrevGovActionTxHash :: Maybe Text + , proposalJson :: Maybe Value + , proposalAuthors :: Maybe Value } deriving (Show) @@ -255,10 +257,11 @@ instance FromRow Proposal where <*> field -- proposalJson <*> field -- proposalAuthors -data TransactionStatus = TransactionStatus - { transactionConfirmed :: Bool - , votingProcedure :: Maybe Value - } +data TransactionStatus + = TransactionStatus + { transactionConfirmed :: Bool + , votingProcedure :: Maybe Value + } instance FromRow TransactionStatus where fromRow = TransactionStatus <$> field <*> field @@ -270,13 +273,14 @@ instance ToJSON TransactionStatus where , "votingProcedure" .= votingProcedure ] -data EnactedProposalDetails = EnactedProposalDetails - { enactedProposalDetailsId :: Integer - , enactedProposalDetailsTxId :: Integer - , enactedProposalDetailsIndex :: Integer - , enactedProposalDetailsDescription :: Maybe Value - , enactedProposalDetailsHash :: Text - } +data EnactedProposalDetails + = EnactedProposalDetails + { enactedProposalDetailsId :: Integer + , enactedProposalDetailsTxId :: Integer + , enactedProposalDetailsIndex :: Integer + , enactedProposalDetailsDescription :: Maybe Value + , enactedProposalDetailsHash :: Text + } deriving (Show) instance FromRow EnactedProposalDetails where @@ -306,20 +310,20 @@ instance ToJSON EnactedProposalDetails where data CacheEnv = CacheEnv - { proposalListCache :: Cache.Cache () [Proposal] - , getProposalCache :: Cache.Cache (Text, Integer) Proposal - , currentEpochCache :: Cache.Cache () (Maybe Value) - , adaHolderVotingPowerCache :: Cache.Cache Text Integer - , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Delegation) - , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) - , dRepInfoCache :: Cache.Cache Text DRepInfo - , dRepVotingPowerCache :: Cache.Cache Text Integer - , dRepListCache :: Cache.Cache Text [DRepRegistration] - , networkMetricsCache :: Cache.Cache () NetworkMetrics - , networkInfoCache :: Cache.Cache () NetworkInfo - , networkTotalStakeCache :: Cache.Cache () NetworkTotalStake - , dRepVotingPowerListCache :: Cache.Cache Text [DRepVotingPowerList] - , accountInfoCache :: Cache.Cache Text AccountInfo + { proposalListCache :: Cache.Cache () [Proposal] + , getProposalCache :: Cache.Cache (Text, Integer) Proposal + , currentEpochCache :: Cache.Cache () (Maybe Value) + , adaHolderVotingPowerCache :: Cache.Cache Text Integer + , adaHolderGetCurrentDelegationCache :: Cache.Cache Text (Maybe Delegation) + , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) + , dRepInfoCache :: Cache.Cache Text DRepInfo + , dRepVotingPowerCache :: Cache.Cache Text Integer + , dRepListCache :: Cache.Cache Text [DRepRegistration] + , networkMetricsCache :: Cache.Cache () NetworkMetrics + , networkInfoCache :: Cache.Cache () NetworkInfo + , networkTotalStakeCache :: Cache.Cache () NetworkTotalStake + , dRepVotingPowerListCache :: Cache.Cache Text [DRepVotingPowerList] + , accountInfoCache :: Cache.Cache Text AccountInfo } data NetworkInfo @@ -335,24 +339,24 @@ data NetworkTotalStake { networkTotalStakeControlledByDReps :: Integer , networkTotalStakeControlledBySPOs :: Integer , networkTotalAlwaysAbstainVotingPower :: Integer - , networkTotalAlwaysNoConfidenceVotingPower :: Integer + , networkTotalAlwaysNoConfidenceVotingPower :: Integer } data NetworkMetrics = NetworkMetrics - { networkMetricsUniqueDelegators :: Integer - , networkMetricsTotalDelegations :: Integer - , networkMetricsTotalGovernanceActions :: Integer - , networkMetricsTotalDRepVotes :: Integer - , networkMetricsTotalRegisteredDReps :: Integer - , networkMetricsTotalDRepDistr :: Integer - , networkMetricsTotalActiveDReps :: Integer - , networkMetricsTotalInactiveDReps :: Integer - , networkMetricsTotalActiveCIP119CompliantDReps :: Integer - , networkMetricsTotalRegisteredDirectVoters :: Integer - , networkMetricsNoOfCommitteeMembers :: Integer - , networkMetricsQuorumNumerator :: Integer - , networkMetricsQuorumDenominator :: Integer + { networkMetricsUniqueDelegators :: Integer + , networkMetricsTotalDelegations :: Integer + , networkMetricsTotalGovernanceActions :: Integer + , networkMetricsTotalDRepVotes :: Integer + , networkMetricsTotalRegisteredDReps :: Integer + , networkMetricsTotalDRepDistr :: Integer + , networkMetricsTotalActiveDReps :: Integer + , networkMetricsTotalInactiveDReps :: Integer + , networkMetricsTotalActiveCIP119CompliantDReps :: Integer + , networkMetricsTotalRegisteredDirectVoters :: Integer + , networkMetricsNoOfCommitteeMembers :: Integer + , networkMetricsQuorumNumerator :: Integer + , networkMetricsQuorumDenominator :: Integer } data Delegation From d896ece1ac56fa013355d9769329e690767ac238 Mon Sep 17 00:00:00 2001 From: Aaron Boyle Date: Thu, 31 Jul 2025 08:49:08 +0100 Subject: [PATCH 2/2] updates to ignore root.json file and do clean install --- .github/workflows/code_check_backend.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code_check_backend.yml b/.github/workflows/code_check_backend.yml index fca3fdf7c..747818b1c 100644 --- a/.github/workflows/code_check_backend.yml +++ b/.github/workflows/code_check_backend.yml @@ -13,8 +13,9 @@ jobs: - name: Use Haskell uses: haskell-actions/setup@v2 with: - ghc-version: '9.2.7' - cabal-version: '3.6.0.0' + ghc-version: '9.2.8' + cabal-version: '3.8.1.0' + cabal-update: false - name: Use Python uses: actions/setup-python@v2 @@ -26,6 +27,13 @@ jobs: - name: Install HLint run: | + rm -rf ~/.cabal ~/.ghcup + mkdir -p ~/.cabal + cat < ~/.cabal/config + repository hackage.haskell.org + url: http://hackage.haskell.org/ + secure: False + EOF cabal update cabal install hlint @@ -41,8 +49,9 @@ jobs: - name: Use Haskell uses: haskell-actions/setup@v2 with: - ghc-version: '9.2.7' - cabal-version: '3.6.0.0' + ghc-version: '9.2.8' + cabal-version: '3.8.1.0' + cabal-update: false - name: Use Python uses: actions/setup-python@v2 @@ -54,6 +63,13 @@ jobs: - name: Install HLint run: | + rm -rf ~/.cabal ~/.ghcup + mkdir -p ~/.cabal + cat < ~/.cabal/config + repository hackage.haskell.org + url: http://hackage.haskell.org/ + secure: False + EOF cabal update cabal install stylish-haskell