forked from filipg/gonito
Initial version of documentation conforming to Swagger 2
This commit is contained in:
parent
993fddb8e8
commit
2f077d75f4
@ -60,6 +60,7 @@ import Handler.Score
|
|||||||
import Handler.ExtraPoints
|
import Handler.ExtraPoints
|
||||||
import Handler.Dashboard
|
import Handler.Dashboard
|
||||||
import Handler.Evaluate
|
import Handler.Evaluate
|
||||||
|
import Handler.Swagger
|
||||||
|
|
||||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||||
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||||||
|
@ -219,6 +219,8 @@ instance Yesod App where
|
|||||||
|
|
||||||
isAuthorized (CompareFormR _ _) _ = regularAuthorization
|
isAuthorized (CompareFormR _ _) _ = regularAuthorization
|
||||||
|
|
||||||
|
isAuthorized SwaggerR _ = return Authorized
|
||||||
|
|
||||||
-- Default to Authorized for now.
|
-- Default to Authorized for now.
|
||||||
isAuthorized _ _ = isTrustedAuthorized
|
isAuthorized _ _ = isTrustedAuthorized
|
||||||
|
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE OverloadedLists #-}
|
||||||
|
|
||||||
module Handler.ListChallenges where
|
module Handler.ListChallenges where
|
||||||
|
|
||||||
import Import
|
import Import hiding (get, fromList, Proxy)
|
||||||
|
|
||||||
|
import Data.HashMap.Strict.InsOrd (fromList)
|
||||||
|
|
||||||
|
import Data.Proxy
|
||||||
|
import Data.Aeson
|
||||||
|
import Control.Lens hiding ((.=))
|
||||||
|
import Data.Swagger
|
||||||
|
import Data.Swagger.Lens
|
||||||
|
import Data.Swagger.Declare
|
||||||
|
|
||||||
mainCondition :: [Filter Challenge]
|
mainCondition :: [Filter Challenge]
|
||||||
mainCondition = [ChallengeArchived !=. Just True]
|
mainCondition = [ChallengeArchived !=. Just True]
|
||||||
@ -8,6 +20,24 @@ mainCondition = [ChallengeArchived !=. Just True]
|
|||||||
getListChallengesR :: Handler Html
|
getListChallengesR :: Handler Html
|
||||||
getListChallengesR = generalListChallenges mainCondition
|
getListChallengesR = generalListChallenges mainCondition
|
||||||
|
|
||||||
|
declareListChallengesSwagger :: Declare (Definitions Schema) Swagger
|
||||||
|
declareListChallengesSwagger = do
|
||||||
|
-- param schemas
|
||||||
|
listChallengesResponse <- declareResponse (Proxy :: Proxy [Entity Challenge])
|
||||||
|
|
||||||
|
return $ mempty
|
||||||
|
& paths .~
|
||||||
|
[ ("/api/list-challenges", mempty & get ?~ (mempty
|
||||||
|
& produces ?~ MimeList ["application/json"]
|
||||||
|
& description ?~ "Returns the list of all challenges"
|
||||||
|
& at 200 ?~ Inline listChallengesResponse))
|
||||||
|
]
|
||||||
|
|
||||||
|
listChallengesApi :: Swagger
|
||||||
|
listChallengesApi = spec & definitions .~ defs
|
||||||
|
where
|
||||||
|
(defs, spec) = runDeclare declareListChallengesSwagger mempty
|
||||||
|
|
||||||
getListChallengesJsonR :: Handler Value
|
getListChallengesJsonR :: Handler Value
|
||||||
getListChallengesJsonR = generalListChallengesJson mainCondition
|
getListChallengesJsonR = generalListChallengesJson mainCondition
|
||||||
|
|
||||||
@ -23,6 +53,22 @@ instance ToJSON (Entity Challenge) where
|
|||||||
, "archived" .= challengeArchived ch
|
, "archived" .= challengeArchived ch
|
||||||
]
|
]
|
||||||
|
|
||||||
|
instance ToSchema (Entity Challenge) where
|
||||||
|
declareNamedSchema _ = do
|
||||||
|
stringSchema <- declareSchemaRef (Proxy :: Proxy String)
|
||||||
|
booleanSchema <- declareSchemaRef (Proxy :: Proxy Bool)
|
||||||
|
return $ NamedSchema (Just "Challenge") $ mempty
|
||||||
|
& type_ .~ SwaggerObject
|
||||||
|
& properties .~
|
||||||
|
fromList [ ("name", stringSchema)
|
||||||
|
, ("title", stringSchema)
|
||||||
|
, ("description", stringSchema)
|
||||||
|
, ("starred", booleanSchema)
|
||||||
|
, ("archived", booleanSchema)
|
||||||
|
]
|
||||||
|
& required .~ [ "name", "title", "description", "starred", "archived" ]
|
||||||
|
|
||||||
|
|
||||||
generalListChallengesJson :: [Filter Challenge] -> Handler Value
|
generalListChallengesJson :: [Filter Challenge] -> Handler Value
|
||||||
generalListChallengesJson filterExpr = do
|
generalListChallengesJson filterExpr = do
|
||||||
challenges <- getChallenges filterExpr
|
challenges <- getChallenges filterExpr
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
{-# LANGUAGE OverloadedLists #-}
|
||||||
|
|
||||||
module Handler.ShowChallenge where
|
module Handler.ShowChallenge where
|
||||||
|
|
||||||
import Import
|
import Import hiding (Proxy, fromList)
|
||||||
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3, bfs)
|
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3, bfs)
|
||||||
|
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
@ -59,6 +61,14 @@ import Data.List (nub)
|
|||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
import Database.Esqueleto ((^.))
|
import Database.Esqueleto ((^.))
|
||||||
|
|
||||||
|
import Data.Swagger hiding (get)
|
||||||
|
import qualified Data.Swagger as DS
|
||||||
|
|
||||||
|
import Data.Swagger.Declare
|
||||||
|
import Control.Lens hiding ((.=), (^.))
|
||||||
|
import Data.Proxy
|
||||||
|
import Data.HashMap.Strict.InsOrd (fromList)
|
||||||
|
|
||||||
instance ToJSON LeaderboardEntry where
|
instance ToJSON LeaderboardEntry where
|
||||||
toJSON entry = object
|
toJSON entry = object
|
||||||
[ "submitter" .= (formatSubmitter $ leaderboardUser entry)
|
[ "submitter" .= (formatSubmitter $ leaderboardUser entry)
|
||||||
@ -70,6 +80,52 @@ instance ToJSON LeaderboardEntry where
|
|||||||
, "times" .= leaderboardNumberOfSubmissions entry
|
, "times" .= leaderboardNumberOfSubmissions entry
|
||||||
]
|
]
|
||||||
|
|
||||||
|
instance ToSchema LeaderboardEntry where
|
||||||
|
declareNamedSchema _ = do
|
||||||
|
stringSchema <- declareSchemaRef (Proxy :: Proxy String)
|
||||||
|
intSchema <- declareSchemaRef (Proxy :: Proxy Int)
|
||||||
|
return $ NamedSchema (Just "LeaderboardEntry") $ mempty
|
||||||
|
& type_ .~ SwaggerObject
|
||||||
|
& properties .~
|
||||||
|
fromList [ ("submitter", stringSchema)
|
||||||
|
, ("when", stringSchema)
|
||||||
|
, ("version", stringSchema)
|
||||||
|
, ("description", stringSchema)
|
||||||
|
, ("times", intSchema)
|
||||||
|
]
|
||||||
|
& required .~ [ "submitter", "when", "version", "description", "times" ]
|
||||||
|
|
||||||
|
|
||||||
|
declareLeaderboardSwagger :: Declare (Definitions Schema) Swagger
|
||||||
|
declareLeaderboardSwagger = do
|
||||||
|
-- param schemas
|
||||||
|
let challengeNameSchema = toParamSchema (Proxy :: Proxy String)
|
||||||
|
|
||||||
|
leaderboardResponse <- declareResponse (Proxy :: Proxy [LeaderboardEntry])
|
||||||
|
|
||||||
|
return $ mempty
|
||||||
|
& paths .~
|
||||||
|
[ ("/api/leaderboard/{challengeName}",
|
||||||
|
mempty & DS.get ?~ (mempty
|
||||||
|
& parameters .~ [ Inline $ mempty
|
||||||
|
& name .~ "challengeName"
|
||||||
|
& required ?~ True
|
||||||
|
& schema .~ ParamOther (mempty
|
||||||
|
& in_ .~ ParamPath
|
||||||
|
& paramSchema .~ challengeNameSchema) ]
|
||||||
|
& produces ?~ MimeList ["application/json"]
|
||||||
|
& description ?~ "Returns a leaderboard for a given challenge"
|
||||||
|
& at 200 ?~ Inline leaderboardResponse))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
leaderboardApi :: Swagger
|
||||||
|
leaderboardApi = spec & definitions .~ defs
|
||||||
|
where
|
||||||
|
(defs, spec) = runDeclare declareLeaderboardSwagger mempty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getLeaderboardJsonR :: Text -> Handler Value
|
getLeaderboardJsonR :: Text -> Handler Value
|
||||||
getLeaderboardJsonR name = do
|
getLeaderboardJsonR name = do
|
||||||
app <- getYesod
|
app <- getYesod
|
||||||
|
20
Handler/Swagger.hs
Normal file
20
Handler/Swagger.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module Handler.Swagger where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
|
import Data.Swagger
|
||||||
|
import Handler.ListChallenges
|
||||||
|
import Handler.ShowChallenge
|
||||||
|
|
||||||
|
import Control.Lens hiding ((.=))
|
||||||
|
|
||||||
|
getSwaggerR :: Handler Value
|
||||||
|
getSwaggerR = return $ toJSON apiDescription
|
||||||
|
|
||||||
|
apiDescription :: Swagger
|
||||||
|
apiDescription = generalApi <> listChallengesApi <> leaderboardApi
|
||||||
|
|
||||||
|
generalApi :: Swagger
|
||||||
|
generalApi = (mempty :: Swagger)
|
||||||
|
& info .~ (mempty &
|
||||||
|
title .~ "Gonito API")
|
@ -98,3 +98,5 @@
|
|||||||
/presentation/psnc-2019 PresentationPSNC2019R GET
|
/presentation/psnc-2019 PresentationPSNC2019R GET
|
||||||
/gonito-in-class GonitoInClassR GET
|
/gonito-in-class GonitoInClassR GET
|
||||||
/writing-papers WritingPapersWithGonitoR GET
|
/writing-papers WritingPapersWithGonitoR GET
|
||||||
|
|
||||||
|
/swagger.json SwaggerR GET
|
||||||
|
@ -59,6 +59,7 @@ library
|
|||||||
Data.SubmissionConditions
|
Data.SubmissionConditions
|
||||||
Gonito.ExtractMetadata
|
Gonito.ExtractMetadata
|
||||||
Data.Diff
|
Data.Diff
|
||||||
|
Handler.Swagger
|
||||||
|
|
||||||
if flag(dev) || flag(library-only)
|
if flag(dev) || flag(library-only)
|
||||||
cpp-options: -DDEVELOPMENT
|
cpp-options: -DDEVELOPMENT
|
||||||
@ -155,6 +156,9 @@ library
|
|||||||
, word8
|
, word8
|
||||||
, jose-jwt
|
, jose-jwt
|
||||||
, scientific
|
, scientific
|
||||||
|
, swagger2
|
||||||
|
, lens
|
||||||
|
, insert-ordered-containers
|
||||||
|
|
||||||
executable gonito
|
executable gonito
|
||||||
if flag(library-only)
|
if flag(library-only)
|
||||||
|
@ -18,4 +18,6 @@ extra-deps:
|
|||||||
- yesod-table-2.0.3
|
- yesod-table-2.0.3
|
||||||
- esqueleto-3.0.0
|
- esqueleto-3.0.0
|
||||||
- 'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810'
|
- 'ordered-containers-0.2.2@sha256:ebf2be3f592d9cf148ea6b8375f8af97148d44f82d8d04476899285e965afdbf,810'
|
||||||
|
- 'optics-core-0.3.0.1@sha256:50845a47810eb5a5f03dfa9bb1edd8a577fc7ca1702ba4bde68b235f7cb44528,4536'
|
||||||
|
- 'indexed-profunctors-0.1@sha256:ddf618d0d4c58319c1e735e746bc69a1021f13b6f475dc9614b80af03432e6d4,1016'
|
||||||
resolver: lts-12.26
|
resolver: lts-12.26
|
||||||
|
BIN
static/swagger-ui/favicon-16x16.png
Normal file
BIN
static/swagger-ui/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
static/swagger-ui/favicon-32x32.png
Normal file
BIN
static/swagger-ui/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
60
static/swagger-ui/index.html
Normal file
60
static/swagger-ui/index.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<!-- HTML for static distribution bundle build -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Swagger UI</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||||
|
<style>
|
||||||
|
html
|
||||||
|
{
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: -moz-scrollbars-vertical;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after
|
||||||
|
{
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
{
|
||||||
|
margin:0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||||
|
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
// Begin Swagger UI call region
|
||||||
|
const ui = SwaggerUIBundle({
|
||||||
|
url: "http://127.0.0.1:3000/swagger.json",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout"
|
||||||
|
})
|
||||||
|
// End Swagger UI call region
|
||||||
|
|
||||||
|
window.ui = ui
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
75
static/swagger-ui/oauth2-redirect.html
Normal file
75
static/swagger-ui/oauth2-redirect.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<title>Swagger UI: OAuth2 Redirect</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
function run () {
|
||||||
|
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||||
|
var sentState = oauth2.state;
|
||||||
|
var redirectUrl = oauth2.redirectUrl;
|
||||||
|
var isValid, qp, arr;
|
||||||
|
|
||||||
|
if (/code|token|error/.test(window.location.hash)) {
|
||||||
|
qp = window.location.hash.substring(1);
|
||||||
|
} else {
|
||||||
|
qp = location.search.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr = qp.split("&")
|
||||||
|
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||||
|
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||||
|
function (key, value) {
|
||||||
|
return key === "" ? value : decodeURIComponent(value)
|
||||||
|
}
|
||||||
|
) : {}
|
||||||
|
|
||||||
|
isValid = qp.state === sentState
|
||||||
|
|
||||||
|
if ((
|
||||||
|
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||||
|
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||||
|
) && !oauth2.auth.code) {
|
||||||
|
if (!isValid) {
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "warning",
|
||||||
|
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qp.code) {
|
||||||
|
delete oauth2.state;
|
||||||
|
oauth2.auth.code = qp.code;
|
||||||
|
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||||
|
} else {
|
||||||
|
let oauthErrorMsg
|
||||||
|
if (qp.error) {
|
||||||
|
oauthErrorMsg = "["+qp.error+"]: " +
|
||||||
|
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||||
|
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2.errCb({
|
||||||
|
authId: oauth2.auth.name,
|
||||||
|
source: "auth",
|
||||||
|
level: "error",
|
||||||
|
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function () {
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
</script>
|
3
static/swagger-ui/swagger-ui-bundle.js
Normal file
3
static/swagger-ui/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui-bundle.js.map
Normal file
1
static/swagger-ui/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
static/swagger-ui/swagger-ui-es-bundle-core.js
Normal file
3
static/swagger-ui/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui-es-bundle-core.js.map
Normal file
1
static/swagger-ui/swagger-ui-es-bundle-core.js.map
Normal file
File diff suppressed because one or more lines are too long
3
static/swagger-ui/swagger-ui-es-bundle.js
Normal file
3
static/swagger-ui/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui-es-bundle.js.map
Normal file
1
static/swagger-ui/swagger-ui-es-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
static/swagger-ui/swagger-ui-standalone-preset.js
Normal file
3
static/swagger-ui/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui-standalone-preset.js.map
Normal file
1
static/swagger-ui/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
4
static/swagger-ui/swagger-ui.css
Normal file
4
static/swagger-ui/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui.css.map
Normal file
1
static/swagger-ui/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
3
static/swagger-ui/swagger-ui.js
Normal file
3
static/swagger-ui/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
static/swagger-ui/swagger-ui.js.map
Normal file
1
static/swagger-ui/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user