summaryrefslogtreecommitdiff
path: root/Biz
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2020-12-07 21:38:04 -0500
committerBen Sima <ben@bsima.me>2020-12-07 21:38:04 -0500
commit94c9fbf38bc711431cd55d2c7cbe7443e56976f6 (patch)
treec903e9fcf7b5d4b9142e4fd7f21f8f7ce7c1f73b /Biz
parent2642499df1795c8024992b3cfd8891c8fc576ed8 (diff)
Auth and load repos from GitHub
Diffstat (limited to 'Biz')
-rw-r--r--Biz/Bild/Deps/Haskell.nix2
-rw-r--r--Biz/Bild/Sources.json13
-rw-r--r--Biz/Devalloc.hs199
-rw-r--r--Biz/Devalloc.nix5
4 files changed, 187 insertions, 32 deletions
diff --git a/Biz/Bild/Deps/Haskell.nix b/Biz/Bild/Deps/Haskell.nix
index 5263163..78ae6b2 100644
--- a/Biz/Bild/Deps/Haskell.nix
+++ b/Biz/Bild/Deps/Haskell.nix
@@ -17,6 +17,7 @@
"fast-logger"
"filepath"
"ghcjs-base"
+ "github"
"haskeline"
"http-types"
"ixset"
@@ -52,6 +53,7 @@
"unagi-chan"
"unix"
"unordered-containers"
+ "uuid"
"vector"
"wai"
"wai-app-static"
diff --git a/Biz/Bild/Sources.json b/Biz/Bild/Sources.json
index d6228e4..2aea2c8 100644
--- a/Biz/Bild/Sources.json
+++ b/Biz/Bild/Sources.json
@@ -97,6 +97,19 @@
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
"version": "0.3.4"
},
+ "req": {
+ "branch": "master",
+ "description": "Easy-to-use, type-safe, expandable, high-level HTTP client library",
+ "homepage": "",
+ "owner": "mrkkrp",
+ "repo": "req",
+ "rev": "0f799e9076053c4bdd685b81e0393d1682de8735",
+ "sha256": "1xrzplgas107zxnv23ai14r4s6wz57ycsav1zhikhk04zz442zhh",
+ "type": "tarball",
+ "url": "https://github.com/mrkkrp/req/archive/0f799e9076053c4bdd685b81e0393d1682de8735.tar.gz",
+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
+ "version": "3.8.0"
+ },
"servant-auth": {
"branch": "master",
"description": null,
diff --git a/Biz/Devalloc.hs b/Biz/Devalloc.hs
index 7654785..cb09f10 100644
--- a/Biz/Devalloc.hs
+++ b/Biz/Devalloc.hs
@@ -1,6 +1,9 @@
{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
@@ -14,11 +17,15 @@
-- dep cmark
-- sys cmark
-- : dep envy
+-- : dep github
-- : dep lucid
-- : dep protolude
+-- : dep req
-- : dep servant
-- : dep servant-lucid
-- : dep servant-server
+-- : dep uuid
+-- : dep vector
-- : dep warp
module Biz.Devalloc
( main,
@@ -30,9 +37,18 @@ import Biz.App (CSS (..), HtmlApp (..))
import qualified Biz.Look
import qualified Clay
import qualified Control.Exception as Exception
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy as LBS
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Encoding
+import Data.Vector (Vector)
+import qualified Data.Vector as Vector
+import qualified GitHub
import qualified Lucid
import qualified Lucid.Base as Lucid
import qualified Lucid.Servant as Lucid
+import Network.HTTP.Req ((/:), (=:))
+import qualified Network.HTTP.Req as Req
import qualified Network.Wai as Wai
import Network.Wai.Application.Static (defaultWebAppSettings)
import qualified Network.Wai.Handler.Warp as Warp
@@ -40,16 +56,18 @@ import Network.Wai.Middleware.RequestLogger (logStdout)
import Servant
import Servant.HTML.Lucid
import qualified System.Envy as Envy
+import qualified Web.FormUrlEncoded
main :: IO ()
main = Exception.bracket startup shutdown run
where
- startup =
- Envy.decodeWithDefaults Envy.defConfig >>= \cfg -> do
- putText "@"
- putText "devalloc"
- putText <| "port: " <> (show <| port cfg)
- return (cfg, serve (Proxy @AllPaths) <| paths cfg)
+ startup = do
+ cfg <- Envy.decodeWithDefaults Envy.defConfig
+ oAuthArgs <- Envy.decodeWithDefaults Envy.defConfig
+ putText "@"
+ putText "devalloc"
+ putText <| "port: " <> (show <| port cfg)
+ return (cfg, serve (Proxy @AllPaths) <| paths cfg oAuthArgs)
shutdown :: (Config, Application) -> IO ()
shutdown _ = pure ()
run :: (Config, Wai.Application) -> IO ()
@@ -70,6 +88,25 @@ instance Envy.DefConfig Config where
instance Envy.FromEnv Config
+-- | These are arguments that a 3rd-party OAuth provider needs in order for us
+-- to authenticate a user.
+data OAuthArgs = OAuthArgs
+ { githubClientSecret :: Text,
+ githubClientId :: Text,
+ githubState :: Text
+ }
+ deriving (Generic, Show)
+
+instance Envy.DefConfig OAuthArgs where
+ defConfig =
+ OAuthArgs
+ { githubClientSecret = mempty,
+ githubClientId = mempty,
+ githubState = mempty
+ }
+
+instance Envy.FromEnv OAuthArgs
+
-- | Wraps pages in default HTML
instance Lucid.ToHtml a => Lucid.ToHtml (HtmlApp a) where
toHtmlRaw = Lucid.toHtml
@@ -100,67 +137,166 @@ instance Lucid.ToHtml a => Lucid.ToHtml (HtmlApp a) where
type AllPaths =
Get '[HTML] (HtmlApp Page)
- :<|> Signup
+ :<|> "auth" :> "github" :> "callback"
+ :> QueryParam "code" Text
+ :> Get '[HTML] (HtmlApp Page)
+ :<|> ShowAnalysis
:<|> ("static" :> Raw)
:<|> "css" :> "main.css" :> Get '[CSS] Text
-type Signup = "signup" :> Get '[HTML] (HtmlApp Page)
+allPaths :: Proxy AllPaths
+allPaths = Proxy :: Proxy AllPaths
+
+type ShowAnalysis = "analysis" :> QueryParam "id" Int :> Get '[HTML] (HtmlApp Analysis)
-paths :: Config -> Server AllPaths
-paths cfg =
- path Home
- :<|> path Signup
+paths :: Config -> OAuthArgs -> Server AllPaths
+paths (Config {assets}) oAuthArgs =
+ page (Home oAuthArgs)
+ :<|> auth oAuthArgs
+ :<|> analyze
:<|> static
:<|> look
where
- path = pure . HtmlApp
- static = serveDirectoryWith <| defaultWebAppSettings <| assets cfg
+ page = pure . HtmlApp
+ analyze Nothing = panic "could not analyze this repo"
+ analyze (Just id) =
+ pure . HtmlApp <| Analysis {targetRepo = GitHub.mkId (Proxy :: Proxy GitHub.Repo) id}
+ static = serveDirectoryWith <| defaultWebAppSettings assets
look =
return . toStrict . Clay.render <| do
Biz.Look.fuckingStyle
"body" Clay.? Biz.Look.fontStack
+data Response = Response
+ { access_token :: Text,
+ scope :: Text,
+ token_type :: Text
+ }
+ deriving (Generic, Aeson.FromJSON)
+
+auth :: OAuthArgs -> Maybe Text -> Handler (HtmlApp Page)
+auth _ Nothing = panic "no code from github api"
+auth (OAuthArgs {..}) (Just code) =
+ liftIO <| getAccessToken
+ >>= getRepos
+ >>= \case
+ Left err -> panic <| show err
+ Right response ->
+ GitHubRepos response |> HtmlApp |> pure
+ where
+ getRepos oAuthToken =
+ GitHub.github
+ (GitHub.OAuth <| Encoding.encodeUtf8 oAuthToken)
+ (GitHub.currentUserReposR GitHub.RepoPublicityAll GitHub.FetchAll)
+ getAccessToken =
+ Req.runReq Req.defaultHttpConfig
+ <| accessTokenRequest
+ >>= Req.responseBody
+ /> access_token
+ /> return
+ accessTokenRequest =
+ Req.req
+ Req.POST
+ (Req.https "github.com" /: "login" /: "oauth" /: "access_token")
+ Req.NoReqBody
+ Req.jsonResponse
+ <| "client_id" =: githubClientId
+ <> "client_secret" =: githubClientSecret
+ <> "code" =: code
+ <> "state" =: githubState
+
linkTo ::
- (HasLink path, IsElem path api) =>
- Proxy api ->
+ (HasLink path, IsElem path AllPaths) =>
Proxy path ->
MkLink path Lucid.Attribute
-linkTo allPaths thisPath = Lucid.safeHref_ "/" allPaths thisPath
+linkTo thisPath = Lucid.safeHref_ "/" allPaths thisPath
data Page
- = Home
- | Signup
+ = Home OAuthArgs
+ | GitHubRepos (Vector GitHub.Repo)
instance Lucid.ToHtml Page where
toHtmlRaw = Lucid.toHtml
- toHtml = \case
- Home -> Lucid.toHtml pitch
- Signup -> Lucid.toHtml signup
+ toHtml page =
+ Lucid.toHtml <| case page of
+ Home authArgs -> pitch authArgs
+ GitHubRepos repos -> do
+ Lucid.h1_ "Select a repo to analyze"
+ selectRepo repos
+
+data Analysis = Analysis
+ { targetRepo :: (GitHub.Id GitHub.Repo)
+ }
+
+instance Lucid.ToHtml Analysis where
+ toHtmlRaw = Lucid.toHtml
+ toHtml analysis =
+ Lucid.toHtml <| render
+ where
+ render :: Lucid.Html ()
+ render =
+ Lucid.div_ <| do
+ Lucid.h1_ "Analysis Results"
+ Lucid.p_ (Lucid.toHtml <| Text.pack <| show <| targetRepo analysis)
-- * parts
-signup :: Lucid.Html ()
-signup =
- Lucid.p_ <| Lucid.a_ [Lucid.href_ "mailto:ben@bsima.me?subject=Devalloc+signup"] "Request access via email"
+encodeParams :: [(Text, Text)] -> Text
+encodeParams =
+ Encoding.decodeUtf8
+ . LBS.toStrict
+ . Web.FormUrlEncoded.urlEncodeParams
-pitch :: Lucid.Html ()
-pitch =
+selectRepo :: Vector GitHub.Repo -> Lucid.Html ()
+selectRepo repos =
+ repos
+ |> Vector.toList
+ |> mapM_ render
+ |> Lucid.ul_
+ where
+ render :: GitHub.Repo -> Lucid.Html ()
+ render repo =
+ Lucid.li_
+ . Lucid.a_
+ [ linkTo
+ (Proxy :: Proxy ShowAnalysis)
+ (Just <| GitHub.untagId <| GitHub.repoId repo)
+ ]
+ . Lucid.toHtml
+ . GitHub.untagName
+ <| GitHub.repoName repo
+
+loginButton :: OAuthArgs -> Lucid.Html ()
+loginButton (OAuthArgs {..}) =
+ Lucid.a_
+ [ Lucid.href_
+ <| "https://github.com/login/oauth/authorize?"
+ <> encodeParams
+ [ ("client_id", githubClientId),
+ ("state", githubState)
+ -- ("redirect_uri", "https://devalloc.io")
+ ]
+ ]
+ "Get Started with GitHub"
+
+pitch :: OAuthArgs -> Lucid.Html ()
+pitch oAuthArgs =
Lucid.div_ <| do
Lucid.h1_ "Devalloc"
Lucid.p_
"Devalloc analyzes your codebase trends, finds patterns \
\ in how your developers work, and protects against tech debt."
Lucid.p_ "Just hook it up to your CI system - it will warn you when it finds a problem."
- Lucid.a_
- [linkTo (Proxy :: Proxy AllPaths) (Proxy :: Proxy Signup)]
- "Go to signup"
+ loginButton oAuthArgs
Lucid.h2_ "Identify blackholes in your codebase"
Lucid.p_
"What if none of your active employees have touched some part of the codebase? \
\ This happens too often with legacy code, and then it turns into a huge source of tech debt. \
\ Devalloc finds these \"blackholes\" and warns you about them so you can be proactive in eliminating tech debt."
+ loginButton oAuthArgs
Lucid.h2_ "Protect against lost knowledge"
Lucid.p_ "Not everyone can know every part of a codebase. By finding pieces of code that only 1 or 2 people have touched, devalloc identifes siloed knowledge. This allows you to protect against the risk of this knowledge leaving the company if an employee leaves."
+ loginButton oAuthArgs
Lucid.h2_ "Don't just measure code coverage - also know your dev coverage"
Lucid.p_ "No matter how smart your employees are, if you are under- or over-utilizing your developers then you will never get optimal performance from your team."
Lucid.ul_ <| do
@@ -168,7 +304,8 @@ pitch =
Lucid.li_ "Know how your devs work best: which ones have depth of knowledge, and which ones have breadth?"
Lucid.p_ "(Paid only)"
+ loginButton oAuthArgs
Lucid.h2_ "See how your teams *actually* organize themselves with cluster analysis"
Lucid.p_ "Does your team feel splintered or not cohesive? Which developers work best together? Devalloc analyzes the collaboration patterns between devs and helps you form optimal pairings and teams based on shared code and mindspace."
Lucid.p_ "(Paid only)"
- signup
+ loginButton oAuthArgs
diff --git a/Biz/Devalloc.nix b/Biz/Devalloc.nix
index 0fd1550..b1cf0cd 100644
--- a/Biz/Devalloc.nix
+++ b/Biz/Devalloc.nix
@@ -35,7 +35,10 @@ in
Devalloc
'';
serviceConfig = {
- Environment = ["PORT=${toString cfg.port}"];
+ Environment = [
+ "PORT=${toString cfg.port}"
+ ];
+ EnvironmentFile="/run/devalloc/env";
KillSignal = "INT";
Type = "simple";
Restart = "on-abort";