mirror of
https://github.com/Start9Labs/registry.git
synced 2026-03-26 02:11:53 +00:00
always get package dependencies when querying for a specified package
This commit is contained in:
committed by
Keagan McClelland
parent
c0279fcae8
commit
94a1c66f6c
@@ -25,11 +25,10 @@ import Conduit ( (.|)
|
|||||||
, sinkList
|
, sinkList
|
||||||
, sourceFile
|
, sourceFile
|
||||||
, takeC
|
, takeC
|
||||||
|
, MonadUnliftIO
|
||||||
)
|
)
|
||||||
import Control.Monad.Except.CoHas ( liftEither )
|
import Control.Monad.Except.CoHas ( liftEither )
|
||||||
import Control.Monad.Reader.Has ( Has
|
|
||||||
, ask
|
|
||||||
)
|
|
||||||
import Control.Parallel.Strategies ( parMap
|
import Control.Parallel.Strategies ( parMap
|
||||||
, rpar
|
, rpar
|
||||||
)
|
)
|
||||||
@@ -90,7 +89,7 @@ import Database.Persist ( PersistUniqueRead(getBy)
|
|||||||
, insertUnique
|
, insertUnique
|
||||||
)
|
)
|
||||||
import Foundation ( Handler
|
import Foundation ( Handler
|
||||||
, RegistryCtx(appSettings)
|
, RegistryCtx(appSettings, appConnPool)
|
||||||
)
|
)
|
||||||
import Lib.Error ( S9Error(..)
|
import Lib.Error ( S9Error(..)
|
||||||
, toStatus
|
, toStatus
|
||||||
@@ -145,6 +144,7 @@ import Yesod.Core ( MonadResource
|
|||||||
, sendChunkBS
|
, sendChunkBS
|
||||||
, sendResponseStatus
|
, sendResponseStatus
|
||||||
, typeOctet
|
, typeOctet
|
||||||
|
, getYesod
|
||||||
)
|
)
|
||||||
import Yesod.Persist ( YesodDB )
|
import Yesod.Persist ( YesodDB )
|
||||||
import Yesod.Persist.Core ( YesodPersist(runDB) )
|
import Yesod.Persist.Core ( YesodPersist(runDB) )
|
||||||
@@ -153,6 +153,11 @@ import Data.Tuple.Extra hiding ( second
|
|||||||
, (&&&)
|
, (&&&)
|
||||||
)
|
)
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
|
import Database.Persist.Sql ( runSqlPool )
|
||||||
|
import Database.Persist.Postgresql ( ConnectionPool )
|
||||||
|
import Control.Monad.Reader.Has ( Has
|
||||||
|
, ask
|
||||||
|
)
|
||||||
|
|
||||||
type URL = Text
|
type URL = Text
|
||||||
newtype CategoryRes = CategoryRes {
|
newtype CategoryRes = CategoryRes {
|
||||||
@@ -354,7 +359,7 @@ getPackageListR = do
|
|||||||
Nothing -> const True
|
Nothing -> const True
|
||||||
Just v -> flip satisfies v
|
Just v -> flip satisfies v
|
||||||
pkgIds <- getPkgIdsQuery
|
pkgIds <- getPkgIdsQuery
|
||||||
filteredServices <- case pkgIds of
|
filteredPackages <- case pkgIds of
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
-- query for all
|
-- query for all
|
||||||
category <- getCategoryQuery
|
category <- getCategoryQuery
|
||||||
@@ -384,26 +389,18 @@ getPackageListR = do
|
|||||||
)
|
)
|
||||||
.| filterOsCompatible osPredicate
|
.| filterOsCompatible osPredicate
|
||||||
.| sinkList
|
.| sinkList
|
||||||
let keys = unPkgRecordKey . entityKey . fst3 <$> filteredServices
|
(keys, packageMetadata) <- runDB $ createPackageMetadata filteredPackages
|
||||||
cats <- runDB $ fetchAppCategories keys
|
appConnPool <- appConnPool <$> getYesod
|
||||||
let vers =
|
serviceDetailResult <- mapConcurrently (getServiceDetails osPredicate appConnPool packageMetadata) keys
|
||||||
filteredServices
|
let (errors, res) = partitionEithers serviceDetailResult
|
||||||
<&> first3 (unPkgRecordKey . entityKey)
|
case errors of
|
||||||
<&> second3 (sortOn Down . fmap (versionRecordNumber . entityVal))
|
x : xs -> do
|
||||||
<&> (\(a, vs, vr) -> (,) a $ (,) vs vr)
|
-- log all errors but just throw first error until Validation implemented - TODO https://hackage.haskell.org/package/validation
|
||||||
& HM.fromListWith mergeDupes
|
for_ xs (\e -> $logWarn [i|Get package list errors: #{e}|])
|
||||||
let packageMetadata = HM.intersectionWith (,) vers (categoryName <<$>> cats)
|
sendResponseStatus (toStatus x) x
|
||||||
serviceDetailResult <- mapConcurrently (getServiceDetails packageMetadata)
|
[] -> pure $ ServiceAvailableRes res
|
||||||
(unPkgRecordKey . entityKey . fst3 <$> filteredServices)
|
|
||||||
let res = partitionEithers serviceDetailResult
|
|
||||||
case fst res of
|
|
||||||
-- just throw first error?
|
|
||||||
x : _ -> sendResponseStatus (toStatus x) x
|
|
||||||
[] -> pure $ ServiceAvailableRes $ snd res
|
|
||||||
|
|
||||||
where
|
where
|
||||||
mergeDupes :: ([Version], VersionRange) -> ([Version], VersionRange) -> ([Version], VersionRange)
|
|
||||||
mergeDupes (vs, vr) (vs', _) = (,) ((++) vs vs') vr
|
|
||||||
defaults = ServiceListDefaults { serviceListOrder = DESC
|
defaults = ServiceListDefaults { serviceListOrder = DESC
|
||||||
, serviceListPageLimit = 20
|
, serviceListPageLimit = 20
|
||||||
, serviceListPageNumber = 1
|
, serviceListPageNumber = 1
|
||||||
@@ -458,11 +455,33 @@ getPackageListR = do
|
|||||||
sendResponseStatus status400 e
|
sendResponseStatus status400 e
|
||||||
Right v -> pure $ Just v
|
Right v -> pure $ Just v
|
||||||
|
|
||||||
getServiceDetails :: (MonadIO m, MonadResource m, MonadReader r m, MonadLogger m, Has AppSettings r)
|
mergeDupes :: ([Version], VersionRange) -> ([Version], VersionRange) -> ([Version], VersionRange)
|
||||||
=> (HM.HashMap PkgId (([Version], VersionRange), [CategoryTitle]))
|
mergeDupes (vs, vr) (vs', _) = (,) ((++) vs vs') vr
|
||||||
|
|
||||||
|
createPackageMetadata :: (MonadReader r m, MonadIO m)
|
||||||
|
=> [(Entity PkgRecord, [Entity VersionRecord], VersionRange)]
|
||||||
|
-> ReaderT
|
||||||
|
SqlBackend
|
||||||
|
m
|
||||||
|
([PkgId], HM.HashMap PkgId (([Version], VersionRange), [CategoryTitle]))
|
||||||
|
createPackageMetadata pkgs = do
|
||||||
|
let keys = unPkgRecordKey . entityKey . fst3 <$> pkgs
|
||||||
|
cats <- fetchAppCategories keys
|
||||||
|
let vers =
|
||||||
|
pkgs
|
||||||
|
<&> first3 (unPkgRecordKey . entityKey)
|
||||||
|
<&> second3 (sortOn Down . fmap (versionRecordNumber . entityVal))
|
||||||
|
<&> (\(a, vs, vr) -> (,) a $ (,) vs vr)
|
||||||
|
& HM.fromListWith mergeDupes
|
||||||
|
pure $ (keys, HM.intersectionWith (,) vers (categoryName <<$>> cats))
|
||||||
|
|
||||||
|
getServiceDetails :: (MonadIO m, MonadResource m, MonadReader r m, MonadLogger m, Has AppSettings r, MonadUnliftIO m)
|
||||||
|
=> (Version -> Bool)
|
||||||
|
-> ConnectionPool
|
||||||
|
-> (HM.HashMap PkgId (([Version], VersionRange), [CategoryTitle]))
|
||||||
-> PkgId
|
-> PkgId
|
||||||
-> m (Either S9Error ServiceRes)
|
-> m (Either S9Error ServiceRes)
|
||||||
getServiceDetails metadata pkg = runExceptT $ do
|
getServiceDetails osPredicate appConnPool metadata pkg = runExceptT $ do
|
||||||
settings <- ask
|
settings <- ask
|
||||||
packageMetadata <- case HM.lookup pkg metadata of
|
packageMetadata <- case HM.lookup pkg metadata of
|
||||||
Nothing -> liftEither . Left $ NotFoundE [i|#{pkg} not found.|]
|
Nothing -> liftEither . Left $ NotFoundE [i|#{pkg} not found.|]
|
||||||
@@ -483,12 +502,15 @@ getServiceDetails metadata pkg = runExceptT $ do
|
|||||||
case eitherDecode manifest of
|
case eitherDecode manifest of
|
||||||
Left _ -> liftEither . Left $ AssetParseE [i|#{pkg}:manifest|] (decodeUtf8 $ BS.toStrict manifest)
|
Left _ -> liftEither . Left $ AssetParseE [i|#{pkg}:manifest|] (decodeUtf8 $ BS.toStrict manifest)
|
||||||
Right m -> do
|
Right m -> do
|
||||||
let deps = partitionEithers $ parMap rpar
|
let depVerList =
|
||||||
(mapDependencyMetadata domain metadata)
|
(fst &&& (serviceDependencyInfoVersion . snd)) <$> (HM.toList $ serviceManifestDependencies m)
|
||||||
|
(_, depMetadata) <- lift $ runSqlPool (createPackageMetadata =<< getDependencies depVerList) appConnPool
|
||||||
|
let (errors, deps) = partitionEithers $ parMap
|
||||||
|
rpar
|
||||||
|
(mapDependencyMetadata domain $ (HM.union depMetadata metadata))
|
||||||
(HM.toList $ serviceManifestDependencies m)
|
(HM.toList $ serviceManifestDependencies m)
|
||||||
case fst deps of
|
case errors of
|
||||||
_ : xs -> do
|
_ : xs -> liftEither . Left $ DepMetadataE xs
|
||||||
liftEither . Left $ DepMetadataE xs
|
|
||||||
[] -> pure $ ServiceRes { serviceResIcon = [i|https://#{domain}/package/icon/#{pkg}|]
|
[] -> pure $ ServiceRes { serviceResIcon = [i|https://#{domain}/package/icon/#{pkg}|]
|
||||||
-- pass through raw JSON Value, we have checked its correct parsing above
|
-- pass through raw JSON Value, we have checked its correct parsing above
|
||||||
, serviceResManifest = unsafeFromJust . decode $ manifest
|
, serviceResManifest = unsafeFromJust . decode $ manifest
|
||||||
@@ -496,8 +518,23 @@ getServiceDetails metadata pkg = runExceptT $ do
|
|||||||
, serviceResInstructions = [i|https://#{domain}/package/instructions/#{pkg}|]
|
, serviceResInstructions = [i|https://#{domain}/package/instructions/#{pkg}|]
|
||||||
, serviceResLicense = [i|https://#{domain}/package/license/#{pkg}|]
|
, serviceResLicense = [i|https://#{domain}/package/license/#{pkg}|]
|
||||||
, serviceResVersions = fst . fst $ packageMetadata
|
, serviceResVersions = fst . fst $ packageMetadata
|
||||||
, serviceResDependencyInfo = HM.fromList $ snd deps
|
, serviceResDependencyInfo = HM.fromList deps
|
||||||
}
|
}
|
||||||
|
where
|
||||||
|
getDependencies :: (MonadResource m, MonadUnliftIO m)
|
||||||
|
=> [(PkgId, VersionRange)]
|
||||||
|
-> ReaderT SqlBackend m [(Entity PkgRecord, [Entity VersionRecord], VersionRange)]
|
||||||
|
getDependencies deps =
|
||||||
|
runConduit
|
||||||
|
$ getPkgData (fst <$> deps)
|
||||||
|
.| zipVersions
|
||||||
|
.| mapC
|
||||||
|
(\(a, vs) ->
|
||||||
|
let spec = fromMaybe Any $ lookup (unPkgRecordKey $ entityKey a) deps
|
||||||
|
in (a, filter ((<|| spec) . versionRecordNumber . entityVal) vs, spec)
|
||||||
|
)
|
||||||
|
.| filterOsCompatible osPredicate
|
||||||
|
.| sinkList
|
||||||
|
|
||||||
mapDependencyMetadata :: Text
|
mapDependencyMetadata :: Text
|
||||||
-> HM.HashMap PkgId (([Version], VersionRange), [CategoryTitle])
|
-> HM.HashMap PkgId (([Version], VersionRange), [CategoryTitle])
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ data VersionInfo = VersionInfo
|
|||||||
}
|
}
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
-- TODO rename to PackageDependencyInfo
|
||||||
data ServiceDependencyInfo = ServiceDependencyInfo
|
data ServiceDependencyInfo = ServiceDependencyInfo
|
||||||
{ serviceDependencyInfoOptional :: Maybe Text
|
{ serviceDependencyInfoOptional :: Maybe Text
|
||||||
, serviceDependencyInfoVersion :: VersionRange
|
, serviceDependencyInfoVersion :: VersionRange
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ spec = do
|
|||||||
let pkg = fromJust $ head res
|
let pkg = fromJust $ head res
|
||||||
let (manifest :: ServiceManifest) = fromRight' $ eitherDecode $ encode $ serviceResManifest pkg
|
let (manifest :: ServiceManifest) = fromRight' $ eitherDecode $ encode $ serviceResManifest pkg
|
||||||
assertEq "manifest id should be bitcoind" (serviceManifestId manifest) "bitcoind"
|
assertEq "manifest id should be bitcoind" (serviceManifestId manifest) "bitcoind"
|
||||||
xdescribe "GET /package/index?ids"
|
describe "GET /package/index?ids"
|
||||||
$ withApp
|
$ withApp
|
||||||
$ it "returns list of packages and dependencies at specified version"
|
$ it "returns list of packages and dependencies at specified version"
|
||||||
$ do
|
$ do
|
||||||
@@ -50,7 +50,6 @@ spec = do
|
|||||||
(res :: [ServiceRes]) <- requireJSONResponse
|
(res :: [ServiceRes]) <- requireJSONResponse
|
||||||
assertEq "response should have one package" (length res) 1
|
assertEq "response should have one package" (length res) 1
|
||||||
let pkg = fromJust $ head res
|
let pkg = fromJust $ head res
|
||||||
printBody
|
|
||||||
assertEq "package dependency metadata should not be empty" (null $ serviceResDependencyInfo pkg) False
|
assertEq "package dependency metadata should not be empty" (null $ serviceResDependencyInfo pkg) False
|
||||||
describe "GET /package/index?ids" $ withApp $ it "returns list of packages at exactly specified version" $ do
|
describe "GET /package/index?ids" $ withApp $ it "returns list of packages at exactly specified version" $ do
|
||||||
_ <- seedBitcoinLndStack
|
_ <- seedBitcoinLndStack
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ spec = do
|
|||||||
packages <- runDBtest $ runConduit $ searchServices Nothing "lightning" .| sinkList
|
packages <- runDBtest $ runConduit $ searchServices Nothing "lightning" .| sinkList
|
||||||
assertEq "should exist" (length packages) 1
|
assertEq "should exist" (length packages) 1
|
||||||
let pkg = fromJust $ head packages
|
let pkg = fromJust $ head packages
|
||||||
print pkg
|
assertEq "package should be lnd" (entityKey pkg) (PkgRecordKey "lnd")
|
||||||
describe "searchServices with fuzzy query"
|
describe "searchServices with fuzzy query"
|
||||||
$ withApp
|
$ withApp
|
||||||
$ it "runs search service with fuzzy text in long description and bitcoin category"
|
$ it "runs search service with fuzzy text in long description and bitcoin category"
|
||||||
@@ -48,7 +48,7 @@ spec = do
|
|||||||
packages <- runDBtest $ runConduit $ searchServices (Just BITCOIN) "proxy" .| sinkList
|
packages <- runDBtest $ runConduit $ searchServices (Just BITCOIN) "proxy" .| sinkList
|
||||||
assertEq "should exist" (length packages) 1
|
assertEq "should exist" (length packages) 1
|
||||||
let pkg = fromJust $ head packages
|
let pkg = fromJust $ head packages
|
||||||
print pkg
|
assertEq "package should be lnc" (entityKey pkg) (PkgRecordKey "btc-rpc-proxy")
|
||||||
describe "searchServices with any category" $ withApp $ it "runs search service for any category" $ do
|
describe "searchServices with any category" $ withApp $ it "runs search service for any category" $ do
|
||||||
_ <- seedBitcoinLndStack
|
_ <- seedBitcoinLndStack
|
||||||
packages <- runDBtest $ runConduit $ searchServices Nothing "" .| sinkList
|
packages <- runDBtest $ runConduit $ searchServices Nothing "" .| sinkList
|
||||||
|
|||||||
Reference in New Issue
Block a user