1
1
mirror of https://github.com/aelve/guide.git synced 2024-11-22 03:12:58 +03:00

Generate swagger.json for branches (#380)

* Generate swagger.json for branches

* Fix

* Fix again

* Fix once more

* [skip ci] Regenerate swagger.json

* Don't log debug info to stdout

* Push even if the branch is ahead

* Fix

* Empty

* [skip ci] Regenerate swagger.json

* Don't use "skip ci"
This commit is contained in:
Artyom Kazak 2019-08-19 17:58:24 +03:00 committed by GitHub
parent a100905cbf
commit ce62066598
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1369 additions and 51 deletions

View File

@ -47,10 +47,31 @@ jobs:
- curl -sSL https://get.haskellstack.org/ | sh - curl -sSL https://get.haskellstack.org/ | sh
# travis_retry works around https://github.com/commercialhaskell/stack/issues/4888 # travis_retry works around https://github.com/commercialhaskell/stack/issues/4888
- travis_retry stack setup - travis_retry stack setup
# Decrypt the GitHub deploy key (travis_key.enc)
- openssl aes-256-cbc -k "$travis_key_password" -d -md sha256 -a -in travis_key.enc -out travis_key
- echo "Host github.com" > ~/.ssh/config
- echo " IdentityFile $(pwd)/travis_key" >> ~/.ssh/config
- chmod 400 travis_key
- git remote set-url origin git@github.com:aelve/guide.git
script: script:
# Build # Build
- stack --no-terminal build --test --no-run-tests --dependencies-only - stack --no-terminal build --test --no-run-tests --dependencies-only
- stack --no-terminal build --test --no-run-tests - stack --no-terminal build --test --no-run-tests
# Regenerate Swagger and push to the same branch, even if the branch
# is already ahead (which may happen if the previous build in the
# pipeline also pushed to it)
- |
if [ "$TRAVIS_EVENT_TYPE" = "push" ]; then
git checkout "$TRAVIS_BRANCH" && git pull
stack exec -- guide api-docs > back/swagger.json
git add back/swagger.json
# Will only push if the commit was created successfully. Note
# that we don't use "[skip ci]" in the commit message here
# because then Mergify goes "oh, but Travis hasn't passed, so I
# shouldn't merge this".
(git commit -m "Regenerate swagger.json" && git push) || true
git checkout "$TRAVIS_COMMIT"
fi
# Upload the Docker image # Upload the Docker image
- | - |
if [ "$TRAVIS_EVENT_TYPE" = "push" ]; then if [ "$TRAVIS_EVENT_TYPE" = "push" ]; then
@ -113,3 +134,9 @@ jobs:
notifications: notifications:
slack: slack:
secure: BgQpUYFmvXrf7HVBP/fefS/8UVwES800+fT+ufgJX8b2HMx2FvaWVsdv3ErKAryLE0B3fwmvforWugTdgLO3kq66YUgSt51SNQOBLkMVGubIoQsgvr3Ernu+Wpw1DyoMkXQH9q9O9rfCIc4IwkQCEHqu5SVRqdOd5px/CHFl/ktTI22JkT8ap/Be53qjlB2U2sWUf4GxYXq0V/gGF6fDwsUwTVKFb14RfSDrOgK5Vlce2GRf3gNr1C/j7A7EHIR/Z+rNd2hvv69cFw6TRc3s39QmP8XPe3SLZPIHTZ8vRveX1SZioMeEy747r5rHd9vylEjxWtVHhvP9fOt693+woXa8ZAl5uVRgB6S4mTWLZ+LAbqhaCmDGJYr9GrrBMoqWvJiMuBX3ZvHptsAc6O2l/fxZQU3otTE++SmHkhbyoDQkcPCjXPDUi/ZlnoLc5zfMAfApcsZZ8b9t47z12H0O4uDZd2YiNPiQJ1iUA6R879LH3pcxPB3RaoWsfXzv/klkKrU/V2K4SXD9j4/bmAFArlig+dar+Dm44L/a3/G7vbU1lQIa1bG0EqB36qgUS3UCkuy2ppti/JTHpkYx7HVF2BipoCjOVvfBl9G8RkvcQIhyuCfOGm7WL1TjrKVMccIEGJKhm7OO6wOZYCBfAI5zILxi8XEJAIvBm9NywhQlwxI= secure: BgQpUYFmvXrf7HVBP/fefS/8UVwES800+fT+ufgJX8b2HMx2FvaWVsdv3ErKAryLE0B3fwmvforWugTdgLO3kq66YUgSt51SNQOBLkMVGubIoQsgvr3Ernu+Wpw1DyoMkXQH9q9O9rfCIc4IwkQCEHqu5SVRqdOd5px/CHFl/ktTI22JkT8ap/Be53qjlB2U2sWUf4GxYXq0V/gGF6fDwsUwTVKFb14RfSDrOgK5Vlce2GRf3gNr1C/j7A7EHIR/Z+rNd2hvv69cFw6TRc3s39QmP8XPe3SLZPIHTZ8vRveX1SZioMeEy747r5rHd9vylEjxWtVHhvP9fOt693+woXa8ZAl5uVRgB6S4mTWLZ+LAbqhaCmDGJYr9GrrBMoqWvJiMuBX3ZvHptsAc6O2l/fxZQU3otTE++SmHkhbyoDQkcPCjXPDUi/ZlnoLc5zfMAfApcsZZ8b9t47z12H0O4uDZd2YiNPiQJ1iUA6R879LH3pcxPB3RaoWsfXzv/klkKrU/V2K4SXD9j4/bmAFArlig+dar+Dm44L/a3/G7vbU1lQIa1bG0EqB36qgUS3UCkuy2ppti/JTHpkYx7HVF2BipoCjOVvfBl9G8RkvcQIhyuCfOGm7WL1TjrKVMccIEGJKhm7OO6wOZYCBfAI5zILxi8XEJAIvBm9NywhQlwxI=
# travis_key.enc decryption password, see:
# http://markbucciarelli.com/posts/2019-01-26_how-to-push-to-github-from-travis-ci.html
env:
matrix:
secure: RqWbR4JjNKL5+KS6um6R2xKRGMxYO4qxRdI/RFi5oHsjRwKAQPGATb00Zmd84V/jaVb+NOccG1qp0/SNVwAaFf6Yr1MO9aV3GKC0fDFwSffKLdtVO2yx/JHrRYZ2ymrjEkBGug1FMrHFGZsSLaZYsYKx974tBs04xwAmPv6Yby8bZTGJyknbuyIMeSe6XZIkinyKaw/Zay6QC2CtSLoX50gqY2/HX0fVBgWKDNStigH0UEEynSetRYm/PFRcoBwu3XmHW89Y9B0E/zgvnvFuAgMVmZSUZnD9wBRlnFvfC1pUaFZP8Tz2VZhcJ4xTRTq+r3t2IRTNQBIhlQseobQMxoTrv1Y/ZiEmO14CLSwy3yeX5vCiGmCaA3957FqnjTP07svbru/A0qWMyEtuBtpheCVbQSVvMUBnrl0txTMFiBb4dsJ7zWrp3f7RQ8SFB11FwpGv89d+FifOpXN42DWRXjU0fLCPs8S5iKODWkTSQ41vpGXXUoZdaUOUg1y6tZSoc8gs61KlhcDTrBI2ZCJMNY6c6JVE/BOnJrp6zqyKhY2znyJUodEvjPsy9iccmrt0bEZTVshzbW4Q9okQ26usNtwIJoHNDUdifpmMcobb/ATYEr+C7n9ztxRy9AnZLzp6SsCDlfSDDp32fr7762PMk+2jR9w7fMrR/PBMtDXNsYQ=

View File

@ -3,9 +3,11 @@ module Guide.Api
module Guide.Api.Methods, module Guide.Api.Methods,
module Guide.Api.Server, module Guide.Api.Server,
module Guide.Api.Types, module Guide.Api.Types,
module Guide.Api.Docs,
) )
where where
import Guide.Api.Methods import Guide.Api.Methods
import Guide.Api.Server import Guide.Api.Server
import Guide.Api.Types import Guide.Api.Types
import Guide.Api.Docs

View File

@ -0,0 +1,32 @@
{-# LANGUAGE OverloadedStrings #-}
-- | Rendered documentation for the API.
module Guide.Api.Docs
(
apiSwaggerDoc,
apiSwaggerRendered,
)
where
import Imports
import Servant.Swagger (toSwagger)
import Data.Swagger
import qualified Data.Aeson.Encode.Pretty as AesonPretty
import Guide.Api.Types
----------------------------------------------------------------------------
-- Swagger
----------------------------------------------------------------------------
-- | Swagger docs for the 'Api'.
apiSwaggerDoc :: Swagger
apiSwaggerDoc =
toSwagger (Proxy @Api)
& info.title .~ "Aelve Guide API"
& info.version .~ "alpha"
-- | Pretty-printed @swagger.json@ for the 'Api'.
apiSwaggerRendered :: Text
apiSwaggerRendered = utf8ToText $ AesonPretty.encodePretty apiSwaggerDoc

View File

@ -13,19 +13,18 @@ where
import Imports import Imports
import Data.Swagger.Lens hiding (format)
import Network.Wai (Middleware, Request) import Network.Wai (Middleware, Request)
import Network.Wai.Middleware.Cors (CorsResourcePolicy (..), corsOrigins, import Network.Wai.Middleware.Cors (CorsResourcePolicy (..), corsOrigins,
simpleCorsResourcePolicy) simpleCorsResourcePolicy)
import Servant import Servant
import Servant.API.Generic import Servant.API.Generic
import Servant.Server.Generic import Servant.Server.Generic
import Servant.Swagger
import Servant.Swagger.UI import Servant.Swagger.UI
import Guide.Api.Guider import Guide.Api.Guider
import Guide.Api.Methods import Guide.Api.Methods
import Guide.Api.Types import Guide.Api.Types
import Guide.Api.Docs
import Guide.Logger import Guide.Logger
import Guide.Config import Guide.Config
import Guide.State import Guide.State
@ -76,11 +75,13 @@ logException logger mbReq ex =
-- Servant servers -- Servant servers
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- Collect API and Swagger server to united 'FullApi'. First takes precedence in case of overlap. -- | Collect API and Swagger server to united 'FullApi'. First takes
-- precedence in case of overlap.
fullServer :: DB -> Logger -> Config -> Server FullApi fullServer :: DB -> Logger -> Config -> Server FullApi
fullServer db di config = apiServer db di config :<|> docServer fullServer db di config = apiServer db di config :<|> docServer
-- Collect api out of guiders and convert them to handlers. Type 'type Server api = ServerT api Handler' needed it. -- | Collect api out of guiders and convert them to handlers. Type 'type
-- Server api = ServerT api Handler' needed it.
apiServer :: DB -> Logger -> Config -> Server Api apiServer :: DB -> Logger -> Config -> Server Api
apiServer db di config = do apiServer db di config = do
requestDetails <- ask requestDetails <- ask
@ -89,11 +90,7 @@ apiServer db di config = do
-- | A 'Server' for Swagger docs. -- | A 'Server' for Swagger docs.
docServer :: Server (SwaggerSchemaUI "api" "swagger.json") docServer :: Server (SwaggerSchemaUI "api" "swagger.json")
docServer = swaggerSchemaUIServer doc docServer = swaggerSchemaUIServer apiSwaggerDoc
where
doc = toSwagger (Proxy @Api)
& info.title .~ "Aelve Guide API"
& info.version .~ "alpha"
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- API handlers put together ('Site') -- API handlers put together ('Site')

View File

@ -24,42 +24,10 @@ import qualified Options.Applicative as Opt
-- | All available commands -- | All available commands
data Command data Command
= RunServer -- ^ run server = RunServer -- ^ Run server
| DryRun -- ^ load database and exit | DryRun -- ^ Load database and exit
| LoadPublic FilePath -- ^ load PublicDB, create base on it and exit | LoadPublic FilePath -- ^ Load PublicDB, create base on it and exit
| ApiDocs -- ^ Show docs for the backend API
----------------------------------------------------------------------------
-- Parsers
----------------------------------------------------------------------------
{-
To see help run command:
$ guide --help
Usage: guide [-v|--version] [COMMAND]
Available options:
-h,--help Show this help text
-v,--version Show Guide version
Available commands:
run Run server
dry-run Load database and exit
load-public Load PublicDB, create base on it and exit
NOTE:
Command 'guide' is the same as 'guide run'
----------------------------------------------------------------------------
$ guide load-public --help
Usage: guide load-public (-p|--path FILEPATH)
Load PublicDB, create base on it and exit
Available options:
-h,--help Show this help text
-p,--path FILEPATH Public DB file name
-}
-- | Parse the command line of the application. -- | Parse the command line of the application.
-- --
@ -72,10 +40,14 @@ parseCommandLine = Opt.execParser
-- | All possible commands. -- | All possible commands.
commandsParser :: Parser Command commandsParser :: Parser Command
commandsParser = Opt.subparser commandsParser = Opt.subparser
$ Opt.command "run" (infoP (pure RunServer) "Start server") $ Opt.command "run"
<> Opt.command "dry-run" (infoP (pure DryRun) "Load database and exit") (infoP (pure RunServer) "Start server")
<> Opt.command "dry-run"
(infoP (pure DryRun) "Load database and exit")
<> Opt.command "load-public" <> Opt.command "load-public"
(infoP loadPublicParser "Load PublicDB, create base on it and exit") (infoP loadPublicParser "Load PublicDB, create base on it and exit")
<> Opt.command "api-docs"
(infoP (pure ApiDocs) "Show swagger.json for the backend API")
where where
infoP parser desc = Opt.info (Opt.helper <*> parser) $ Opt.progDesc desc infoP parser desc = Opt.info (Opt.helper <*> parser) $ Opt.progDesc desc

View File

@ -16,11 +16,10 @@ where
import Imports hiding ((.=)) import Imports hiding ((.=))
-- JSON
import Data.Aeson as Aeson import Data.Aeson as Aeson
import Data.Aeson.Encode.Pretty as Aeson hiding (Config) import Data.Aeson.Encode.Pretty as Aeson hiding (Config)
-- Default
import Data.Default import Data.Default
import Say (sayErr)
import Guide.Utils import Guide.Utils
@ -125,7 +124,7 @@ readConfig = do
let filename = "config.json" let filename = "config.json"
exists <- doesFileExist filename exists <- doesFileExist filename
unless exists $ do unless exists $ do
putStrLn "config.json doesn't exist, creating it" sayErr "config.json doesn't exist, creating it"
BSL.writeFile filename (Aeson.encodePretty (def :: Config)) BSL.writeFile filename (Aeson.encodePretty (def :: Config))
contents <- toLazyByteString <$> BS.readFile filename contents <- toLazyByteString <$> BS.readFile filename
case Aeson.eitherDecode' contents of case Aeson.eitherDecode' contents of

View File

@ -15,6 +15,7 @@ module Guide.Main
runServer, runServer,
dryRun, dryRun,
loadPublic, loadPublic,
apiDocs,
) )
where where
@ -47,7 +48,7 @@ import System.Signal
-- HVect -- HVect
import Data.HVect hiding (length) import Data.HVect hiding (length)
import Guide.Api (runApiServer) import Guide.Api (runApiServer, apiSwaggerRendered)
import Guide.App import Guide.App
import Guide.Cli import Guide.Cli
import Guide.Config import Guide.Config
@ -124,6 +125,7 @@ runCommand config = \case
RunServer -> runServer config RunServer -> runServer config
DryRun -> dryRun config DryRun -> dryRun config
LoadPublic path -> loadPublic config path LoadPublic path -> loadPublic config path
ApiDocs -> apiDocs config
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- Commands -- Commands
@ -167,6 +169,11 @@ loadPublic config path = withLogger config $ \logger ->
logDebugIO logger "PublicDB imported to GlobalState" logDebugIO logger "PublicDB imported to GlobalState"
exitSuccess exitSuccess
-- | Dump API docs to the output.
apiDocs :: Config -> IO ()
apiDocs config = withLogger config $ \_logger ->
T.putStrLn apiSwaggerRendered
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
-- Helpers -- Helpers
---------------------------------------------------------------------------- ----------------------------------------------------------------------------

1211
back/swagger.json Normal file

File diff suppressed because it is too large Load Diff

71
travis_key.enc Normal file
View File

@ -0,0 +1,71 @@
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