Commit 956d424d authored by bow's avatar bow
Browse files

Add swagger-ui live documentation page

parent f84b8a20
# JSON schema for json-patch
## JSON schema for json-patch ##
Copyright (c) 2013 Francis Galiegue
Licensed under the BSD license.
## swagger-ui for live documentation ##
Copyright 2015 SmartBear Software
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at[apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Modifications: URL fetch and interface colors
\ No newline at end of file
......@@ -107,6 +107,16 @@ object SentinelBuild extends Build {
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-target:jvm-1.8"),
testOptions in Test += Tests.Argument("console", "junitxml"),
testOptions in IntegrationTest += Tests.Argument("console", "junitxml"),
resourceGenerators in Compile <+= (resourceManaged, baseDirectory) map {
(managedBase, base) =>
val webappBase = base / "src" / "main" / "webapp"
for {
(from, to) <- webappBase ** "*" pair rebase(webappBase, managedBase / "main" / "webapp")
} yield {
Sync.copy(from, to)
to
}
},
mainClass in assembly := Some("nl.lumc.sasc.sentinel.JettyLauncher"),
test in assembly := {},
assemblyMergeStrategy in assembly := {
......
......@@ -26,7 +26,8 @@ class RootControllerSpec extends SentinelServletSpec with Mockito {
implicit val system = mock[ActorSystem]
implicit val swagger = new SentinelSwagger
addServlet(new RootController, s"/*")
addServlet(new ResourcesApp, "/api-spec/*")
addServlet(new ApiDocsController, "/api-docs/*")
addServlet(new ApiSpecsController, "/api-spec/*")
"OPTIONS '/'" >> {
br
......@@ -77,4 +78,24 @@ class RootControllerSpec extends SentinelServletSpec with Mockito {
}
}
}
"GET '/api-docs' should" >> inline {
val endpoint = "/api-docs"
new Context.PriorRequests {
def request = () => get(endpoint) { response }
def priorRequests = Seq(request)
"return status 200" in {
priorResponse.status mustEqual 200
}
"return an HTML page of the live documentation" in {
priorResponse.contentType mustEqual "text/html"
priorResponse.body must contain("Sentinel API")
}
}
}
}
asc = text/plain
bin = application/octet-stream
css = text/css
csv = text/comma-separated-values
gif = image/gif
htm = text/html
html = text/html
jpe = image/jpeg
jpeg = image/jpeg
jpg = image/jpeg
js = application/x-javascript
pdf = application/pdf
png = image/png
tif = image/tiff
tiff = image/tiff
txt = text/plain
xhtml = application/xhtml+xml
xml = application/xml
......@@ -80,7 +80,8 @@ class ScalatraBootstrap extends LifeCycle {
context mount (new AnnotationsController, "/annotations/*")
context mount (new RunsController, "/runs/*")
context mount (new UsersController, "/users/*")
context mount (new ResourcesApp, "/api-spec/*")
context mount (new ApiSpecsController, "/api-spec/*")
context mount (new ApiDocsController, "/api-docs/*")
context setInitParameter (org.scalatra.EnvironmentKey, env)
} catch {
case e: Throwable => e.printStackTrace()
......
......@@ -34,11 +34,13 @@ object JettyLauncher {
val port = Try(conf.getInt(s"$SentinelConfKey.port")).getOrElse(8080)
val server = new Server(port)
val context = new WebAppContext()
val resource = getClass.getClassLoader.getResource("webapp").toExternalForm
context setContextPath "/"
context.setResourceBase("src/main/webapp")
context.setResourceBase(resource)
context.addEventListener(new ScalatraListener)
context.addServlet(classOf[DefaultServlet], "/")
server.setHandler(context)
server.start()
server.join()
......
/*
* Copyright (c) 2015 Leiden University Medical Center and contributors
* (see AUTHORS.md file for details).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nl.lumc.sasc.sentinel.api
import java.util.Properties
import org.scalatra._
import nl.lumc.sasc.sentinel.utils.getResourceStream
/** Controller for the `/api-docs` endpoint. */
class ApiDocsController extends ScalatraServlet {
/** Helper method to get requested static resource path. */
private def getResourcePath = {
val splatPath = multiParams("splat").head
if (splatPath.isEmpty) request.getServletPath + "/index.html"
else request.getServletPath + s"/$splatPath"
}
/** Endpoint for live Swagger documentation. */
get("/*") {
val resourcePath = getResourcePath
Option(getServletContext.getResourceAsStream(resourcePath)) match {
case Some(inputStream) =>
contentType = ApiDocsController.resolveContentType(resourcePath)
response.setHeader("Cache-Control", "public")
Ok(inputStream)
case None =>
contentType = "application/json"
NotFound("{\"message\":\"Requested resource not found.\"}")
}
}
}
object ApiDocsController {
/** MIME properties for setting mime type in HTTP responses. */
private val properties: Properties = new Properties()
properties.load(getResourceStream("/mime.properties"))
/** Helper method for getting file extension. */
private def suffix(path: String): String = path.reverse.takeWhile(_ != '.').reverse
/** Given a resource path, return its MIME-type. */
def resolveContentType(resourcePath: String) = Option(properties.get(suffix(resourcePath))) match {
case Some(ct) => ct.toString
case None => "text/plain"
}
}
......@@ -38,5 +38,5 @@ object SentinelSwagger {
}
/** Controller for auto-generated Swagger specification. */
class ResourcesApp(implicit protected val system: ActorSystem, val swagger: SentinelSwagger) extends ScalatraServlet
class ApiSpecsController(implicit protected val system: ActorSystem, val swagger: SentinelSwagger) extends ScalatraServlet
with JacksonSwaggerBase
......@@ -30,17 +30,15 @@ class RootController extends ScalatraServlet
/** JSON formatting used by this endpoint. */
protected implicit val jsonFormats: Formats = SentinelJsonFormats
before() {
contentType = formats("json")
}
options("/?") {
contentType = formats("json")
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"))
response.setHeader("Access-Control-Allow-Methods", "GET,HEAD")
}
/** Root endpoint, which permanently redirects to our specification. */
get("/") {
contentType = formats("json")
halt(status = 301, headers = Map("Location" -> "/api-spec"))
}
}
This diff is collapsed.
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
This diff is collapsed.
/* droid-sans-regular - latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: url('../fonts/droid-sans-v6-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Droid Sans'), local('DroidSans'),
url('../fonts/droid-sans-v6-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../fonts/droid-sans-v6-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('../fonts/droid-sans-v6-latin-regular.woff') format('woff'), /* Modern Browsers */
url('../fonts/droid-sans-v6-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('../fonts/droid-sans-v6-latin-regular.svg#DroidSans') format('svg'); /* Legacy iOS */
}
/* droid-sans-700 - latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: url('../fonts/droid-sans-v6-latin-700.eot'); /* IE9 Compat Modes */
src: local('Droid Sans Bold'), local('DroidSans-Bold'),
url('../fonts/droid-sans-v6-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../fonts/droid-sans-v6-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('../fonts/droid-sans-v6-latin-700.woff') format('woff'), /* Modern Browsers */
url('../fonts/droid-sans-v6-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('../fonts/droid-sans-v6-latin-700.svg#DroidSans') format('svg'); /* Legacy iOS */
}
This diff is collapsed.
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment