summaryrefslogtreecommitdiff
path: root/app/SlackBuilder/Updater.hs
blob: 1ebf7fe456982107c12bbeb5c307b907eabe75a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
module SlackBuilder.Updater
    ( latestGitHub
    , latestPackagist
    , latestText
    ) where

import SlackBuilder.Config
import qualified Data.Aeson as Aeson
import Data.Aeson ((.:))
import Data.Aeson.TH (defaultOptions, deriveJSON)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text.Encoding
import Data.Vector (Vector, (!?))
import qualified Data.Vector as Vector
import Network.HTTP.Req
    ( header
    , runReq
    , defaultHttpConfig
    , req
    , GET(..)
    , https
    , jsonResponse
    , NoReqBody(..)
    , (/:)
    , responseBody
    , useHttpsURI
    , bsResponse
    , POST(..)
    , ReqBodyJson(..)
    )
import Text.URI (mkURI)
import SlackBuilder.CommandLine
import SlackBuilder.Trans
import qualified Data.Aeson.KeyMap as KeyMap
import GHC.Records (HasField(..))
import Control.Monad.Trans.Reader (asks)
import Control.Monad.IO.Class (MonadIO(..))

newtype PackagistPackage = PackagistPackage
    { version :: Text
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''PackagistPackage)

newtype PackagistResponse = PackagistResponse
    { packages :: HashMap Text (Vector PackagistPackage)
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''PackagistResponse)

newtype GhRefNode = GhRefNode
    { name :: Text
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''GhRefNode)

newtype GhRef = GhRef
    { nodes :: Vector GhRefNode
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''GhRef)

newtype GhRepository = GhRepository
    { refs :: GhRef
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''GhRepository)

newtype GhData = GhData
    { repository :: GhRepository
    } deriving (Eq, Show)

instance Aeson.FromJSON GhData where
    parseJSON (Aeson.Object keyMap)
        | Just data' <- KeyMap.lookup "data" keyMap =
            GhData <$> Aeson.withObject "GhData" (.: "repository") data'
    parseJSON _ = fail "data key not found in the response"

data GhVariables = GhVariables
    { name :: Text
    , owner :: Text
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''GhVariables)

data GhQuery = GhQuery
    { query :: Text
    , variables :: GhVariables
    } deriving (Eq, Show)

$(deriveJSON defaultOptions ''GhQuery)

latestPackagist :: PackagistArguments -> SlackBuilderT (Maybe Text)
latestPackagist PackagistArguments{..} = do
    packagistResponse <- runReq defaultHttpConfig $
        let uri = https "repo.packagist.org" /: "p2"
                /: vendor
                /: name <> ".json"
         in req GET uri NoReqBody jsonResponse mempty
    let packagistPackages = packages $ responseBody packagistResponse
        fullName = Text.intercalate "/" [vendor, name]

    pure $ HashMap.lookup fullName packagistPackages
        >>= fmap (version . fst) . Vector.uncons

latestText :: TextArguments -> SlackBuilderT (Maybe Text)
latestText TextArguments{..} = do
    uri <- liftIO $ useHttpsURI <$> mkURI textURL
    packagistResponse <- traverse (runReq defaultHttpConfig . go . fst) uri

    pure $ versionPicker . Text.Encoding.decodeUtf8 . responseBody
        <$> packagistResponse
  where
    go uri = req GET uri NoReqBody bsResponse mempty

latestGitHub
    :: GhArguments
    -> (Text -> Maybe Text)
    -> SlackBuilderT (Maybe Text)
latestGitHub GhArguments{..} versionTransform = do
    ghToken' <- SlackBuilderT $ asks ghToken
    ghResponse <- runReq defaultHttpConfig $
        let uri = https "api.github.com" /: "graphql"
            query = GhQuery
                {  query = githubQuery
                , variables = GhVariables
                    { owner = owner
                    , name = name
                    }
                }
            authorizationHeader = header "authorization"
                $ Text.Encoding.encodeUtf8
                $ "Bearer " <> ghToken'
         in req POST uri (ReqBodyJson query) jsonResponse
            $ authorizationHeader <> header "User-Agent" "SlackBuilder"
    let ghNodes = nodes
            $ refs
            $ (getField @"repository" :: GhData -> GhRepository)
            $ responseBody ghResponse
        refs' = Vector.reverse
            $ Vector.catMaybes
            $ versionTransform . getField @"name" <$> ghNodes
    pure $ refs' !? 0
  where
    githubQuery =
        "query ($name: String!, $owner: String!) {\n\
        \  repository(name: $name, owner: $owner) {\n\
        \    refs(last: 10, refPrefix: \"refs/tags/\", orderBy: { field: TAG_COMMIT_DATE, direction: ASC }) {\n\
        \      nodes {\n\
        \        id,\n\
        \        name\n\
        \      }\n\
        \    }\n\
        \  }\n\
        \}"