Skip to content

Commit

Permalink
GH-458 Implement Javalin 4.0.0.R0 with OpenAPI 1.1.0 and fix shutting…
Browse files Browse the repository at this point in the history
… down the instance
  • Loading branch information
dzikoysk committed Aug 5, 2021
1 parent 12f0251 commit 8c3d545
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 84 deletions.
10 changes: 5 additions & 5 deletions reposilite-backend/reposilite-backend.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ dependencies {
implementation("software.amazon.awssdk:bom:$awssdk")
implementation("software.amazon.awssdk:s3:$awssdk")

val javalin = "4.0.0.ALPHA3"
val javalin = "4.0.0.RC0"
implementation("io.javalin:javalin:$javalin")

val openapi = "1.0.9"
kapt("com.dzikoysk:openapi-annotation-processor:$openapi")
implementation("com.dzikoysk:javalin-openapi-plugin:$openapi")
implementation("com.dzikoysk:javalin-swagger-plugin:$openapi")
val openapi = "1.1.0"
kapt("io.javalin-rfc:openapi-annotation-processor:$openapi")
implementation("io.javalin-rfc:javalin-openapi-plugin:$openapi")
implementation("io.javalin-rfc:javalin-swagger-plugin:$openapi")

val jetty = "9.4.42.v20210604"
implementation("org.eclipse.jetty:jetty-server:$jetty")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ object ReposiliteWebConfiguration {

fun dispose(reposilite: Reposilite) {
ConsoleWebConfiguration.dispose(reposilite.consoleFacade)
StatisticsWebConfiguration.dispose()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,22 @@
*/
package com.reposilite.auth.infrastructure

import com.dzikoysk.openapi.annotations.HttpMethod
import com.dzikoysk.openapi.annotations.OpenApi
import com.dzikoysk.openapi.annotations.OpenApiContent
import com.dzikoysk.openapi.annotations.OpenApiParam
import com.dzikoysk.openapi.annotations.OpenApiResponse
import com.reposilite.auth.AuthenticationFacade
import com.reposilite.auth.api.AuthenticationResponse
import com.reposilite.failure.api.ErrorResponse
import com.reposilite.web.api.Route
import com.reposilite.web.api.RouteMethod.GET
import com.reposilite.web.api.Routes

private const val ROUTE = "/api/auth"
import io.javalin.openapi.HttpMethod
import io.javalin.openapi.OpenApi
import io.javalin.openapi.OpenApiContent
import io.javalin.openapi.OpenApiParam
import io.javalin.openapi.OpenApiResponse

internal class AuthenticationEndpoint(private val authenticationFacade: AuthenticationFacade) : Routes {

@OpenApi(
path = ROUTE,
path = "/api/auth",
methods = [HttpMethod.GET],
summary = "Get token details",
description = "Returns details about the requested token",
Expand All @@ -51,7 +49,7 @@ internal class AuthenticationEndpoint(private val authenticationFacade: Authenti
)
]
)
private val authInfo = Route(ROUTE, GET) {
private val authInfo = Route("/api/auth", GET) {
authenticationFacade.authenticateByHeader(ctx.headerMap())
.map { AuthenticationResponse(it.alias, it.permissions.map { permission -> permission.toString() }) }
.let { ctx.json(it.any) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
*/
package com.reposilite.auth.infrastructure

import io.javalin.http.HttpCode
import com.reposilite.web.api.Route
import com.reposilite.web.api.RouteMethod.AFTER
import com.reposilite.web.api.Routes
import io.javalin.http.HttpCode

private const val WWW_AUTHENTICATE = "www-authenticate"
private const val WWW_BASIC_REALM = """Basic realm="Reposilite", charset="UTF-8" """

internal class PostAuthHandler : Routes {

private val realmDescription = Route("/*", AFTER) {
private val realmDescription = Route("/<*>", AFTER) {
if (ctx.status() == HttpCode.UNAUTHORIZED.status) {
ctx.header(WWW_AUTHENTICATE, WWW_BASIC_REALM)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@
*/
package com.reposilite.console.infrastructure

import com.dzikoysk.openapi.annotations.HttpMethod
import com.dzikoysk.openapi.annotations.OpenApi
import com.dzikoysk.openapi.annotations.OpenApiContent
import com.dzikoysk.openapi.annotations.OpenApiParam
import com.dzikoysk.openapi.annotations.OpenApiResponse
import io.javalin.http.HttpCode.UNAUTHORIZED
import com.reposilite.console.ConsoleFacade
import com.reposilite.console.MAX_COMMAND_LENGTH
import com.reposilite.console.api.ExecutionResponse
Expand All @@ -29,13 +23,17 @@ import com.reposilite.failure.api.errorResponse
import com.reposilite.web.api.Route
import com.reposilite.web.api.RouteMethod.POST
import com.reposilite.web.api.Routes

private const val ROUTE = "/api/execute"
import io.javalin.http.HttpCode.UNAUTHORIZED
import io.javalin.openapi.HttpMethod
import io.javalin.openapi.OpenApi
import io.javalin.openapi.OpenApiContent
import io.javalin.openapi.OpenApiParam
import io.javalin.openapi.OpenApiResponse

internal class RemoteExecutionEndpoint(private val consoleFacade: ConsoleFacade) : Routes {

@OpenApi(
path = ROUTE,
path = "/api/execute",
methods = [HttpMethod.POST],
summary = "Remote command execution",
description = "Execute command using POST request. The commands are the same as in the console and can be listed using the 'help' command.",
Expand All @@ -59,7 +57,7 @@ internal class RemoteExecutionEndpoint(private val consoleFacade: ConsoleFacade)
)
]
)
private val executeCommand = Route(ROUTE, POST) {
private val executeCommand = Route("/api/execute", POST) {
context.logger.info("REMOTE EXECUTION ${context.uri} from ${context.address}")

authenticated {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ internal class CustomFrontendHandler(frontendFacade: FrontendFacade, directory:
override val routes: Set<Route> = Files.list(directory)
.map {
if (it.isDirectory())
Route("/${it.getSimpleName()}/*", GET) {
respondWithFile(ctx, it.getSimpleName(), it.resolve(ctx.splat(0) ?: "").inputStream().orNull())
Route("/${it.getSimpleName()}/<path>", GET) {
respondWithFile(ctx, it.getSimpleName(), it.resolve(ctx.pathParam("path")).inputStream().orNull())
}
else
Route("/${it.getSimpleName()}", GET) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ internal class ResourcesFrontendHandler(frontendFacade: FrontendFacade) : Fronte
respondWithResource(ctx, "index.html")
}

private val assetsHandler = Route("/assets/*", GET) {
respondWithResource(ctx, "assets/${wildcard()}")
private val assetsHandler = Route("/assets/<path>", GET) {
respondWithResource(ctx, "assets/${ctx.pathParam("path")}")
}

private fun respondWithResource(ctx: Context, uri: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
package com.reposilite.maven.infrastructure

import com.dzikoysk.openapi.annotations.HttpMethod
import com.dzikoysk.openapi.annotations.OpenApi
import com.dzikoysk.openapi.annotations.OpenApiContent
import com.dzikoysk.openapi.annotations.OpenApiParam
import com.dzikoysk.openapi.annotations.OpenApiResponse
import com.reposilite.failure.api.ErrorResponse
import com.reposilite.failure.api.errorResponse
import com.reposilite.maven.MavenFacade
import com.reposilite.maven.api.DeployRequest
import com.reposilite.maven.api.DirectoryInfo
import com.reposilite.maven.api.DocumentInfo
import com.reposilite.maven.api.FileDetails
import com.reposilite.maven.api.LookupRequest
Expand All @@ -23,76 +17,87 @@ import com.reposilite.web.api.RouteMethod.PUT
import com.reposilite.web.api.Routes
import com.reposilite.web.resultAttachment
import io.javalin.http.HttpCode.NO_CONTENT
import io.javalin.openapi.HttpMethod
import io.javalin.openapi.OpenApi
import io.javalin.openapi.OpenApiContent
import io.javalin.openapi.OpenApiParam
import io.javalin.openapi.OpenApiResponse
import panda.std.Result

internal class MavenFileEndpoint(private val mavenFacade: MavenFacade) : Routes {

@OpenApi(
path = "/:repository-name/*",
path = "/{repository}/*",
methods = [HttpMethod.GET],
tags = ["Maven"],
summary = "Browse the contents of repositories",
description = "The route may return various responses to properly handle Maven specification and frontend application using the same path.",
pathParams = [OpenApiParam(name = "*", description = "Artifact path qualifier", required = true, allowEmptyValue = true)],
pathParams = [
OpenApiParam(name = "*", description = "Artifact path qualifier", required = true, allowEmptyValue = true)
],
responses = [
OpenApiResponse(status = "200", description = "Input stream of requested file", content = [OpenApiContent(type = MULTIPART_FORM_DATA)]),
OpenApiResponse(status = "404", description = "Returns 404 (for Maven) with frontend (for user) as a response if requested resource is not located in the current repository")
]
)
val findFile = Route("/:repository-name/*", GET) {
val findFile = Route("/{repository}/<gav>", GET) {
accessed {
LookupRequest(parameter("repository-name"), wildcard(), this?.accessToken)
LookupRequest(parameter("repository"), parameter("gav"), this?.accessToken)
.let { mavenFacade.findFile(it) }
.peek {
when (it) {
is DocumentInfo -> ctx.resultAttachment(it)
is DirectoryInfo -> response = errorResponse(NO_CONTENT, "Requested file is a directory")
else -> response = errorResponse(NO_CONTENT, "Requested file is a directory")
}
}
.onError { response = Result.error(it) }
}
}

@OpenApi(
path = "/:repository-name/*",
path = "/{repository}/*",
methods = [HttpMethod.POST, HttpMethod.PUT],
summary = "Deploy artifact to the repository",
description = "Deploy supports both, POST and PUT, methods and allows to deploy artifact builds",
tags = [ "Maven" ],
pathParams = [ OpenApiParam(name = "*", description = "Artifact path qualifier", required = true) ],
pathParams = [
OpenApiParam(name = "*", description = "Artifact path qualifier", required = true)
],
responses = [
OpenApiResponse(status = "200", description = "Input stream of requested file", content = [OpenApiContent(type = MULTIPART_FORM_DATA)]),
OpenApiResponse(status = "401", description = "Returns 401 for invalid credentials"),
OpenApiResponse(status = "405", description = "Returns 405 if deployment is disabled in configuration"),
OpenApiResponse(status = "507", description = "Returns 507 if Reposilite does not have enough disk space to store the uploaded file")
]
)
private val deployFile = Route("/:repository-name/*", POST, PUT) {
private val deployFile = Route("/{repository}/<gav>", POST, PUT) {
authorized {
response = DeployRequest(parameter("repository-name"), wildcard(), getSessionIdentifier(), context.input())
response = DeployRequest(parameter("repository"), parameter("gav"), getSessionIdentifier(), context.input())
.let { mavenFacade.deployFile(it) }
.onError { context.logger.debug("Cannot deploy artifact due to: ${it.message}") }
}
}

@OpenApi(
path = "/:repository-name/*",
path = "/{repository}/<gav>",
summary = "Delete the given file from repository",
methods = [HttpMethod.DELETE]
)
private val deleteFile = Route("/:repository-name/*", DELETE) {
private val deleteFile = Route("/{repository}/<gav>", DELETE) {
authorized {
response = mavenFacade.deleteFile(parameter("repository-name"), wildcard())
response = mavenFacade.deleteFile(parameter("repository"), parameter("gav"))
}
}

@OpenApi(
path = "/api/maven/details/:repository-name/*",
path = "/api/maven/details/{repository}/<gav>",
methods = [HttpMethod.HEAD, HttpMethod.GET],
summary = "Browse the contents of repositories using API",
description = "Get details about the requested file as JSON response",
tags = ["Maven"],
pathParams = [OpenApiParam(name = "*", description = "Artifact path qualifier", required = true, allowEmptyValue = true)],
pathParams = [
OpenApiParam(name = "*", description = "Artifact path qualifier", required = true, allowEmptyValue = true)
],
responses = [
OpenApiResponse(
status = "200",
Expand All @@ -110,9 +115,9 @@ internal class MavenFileEndpoint(private val mavenFacade: MavenFacade) : Routes
)
]
)
val findFileDetails = Route("/api/maven/details/:repository-name/*", HEAD, GET) {
val findFileDetails = Route("/api/maven/details/{repository}/<gav>", HEAD, GET) {
accessed {
response = LookupRequest(parameter("repository-name"), wildcard(), this?.accessToken)
response = LookupRequest(parameter("repository"), parameter("gav"), this?.accessToken)
.let { mavenFacade.findFile(it) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@

package com.reposilite.statistics.application

import net.dzikoysk.dynamiclogger.Journalist
import com.reposilite.console.ConsoleFacade
import com.reposilite.statistics.StatisticsFacade
import com.reposilite.statistics.StatsCommand
import com.reposilite.statistics.infrastructure.SqlStatisticsRepository
import com.reposilite.statistics.infrastructure.StatisticsHandler
import com.reposilite.web.api.Routes
import net.dzikoysk.dynamiclogger.Journalist
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.MINUTES

internal object StatisticsWebConfiguration {

private val scheduler = Executors.newSingleThreadScheduledExecutor() // Maybe use some shared ThreadPool to avoid Thread creation

fun createFacade(journalist: Journalist): StatisticsFacade =
StatisticsFacade(journalist, SqlStatisticsRepository())

fun initialize(statisticsFacade: StatisticsFacade, consoleFacade: ConsoleFacade) {
val scheduler = Executors.newSingleThreadScheduledExecutor() // Maybe use some shared ThreadPool to avoid Thread creation
scheduler.scheduleWithFixedDelay({ statisticsFacade.saveRecordsBulk() }, 1, 1, MINUTES)

consoleFacade.registerCommand(StatsCommand(statisticsFacade))
}

Expand All @@ -43,4 +43,8 @@ internal object StatisticsWebConfiguration {
StatisticsHandler(statisticsFacade)
)

fun dispose() {
scheduler.shutdown()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.reposilite.web.api.Routes

internal class StatisticsHandler(private val statisticsFacade: StatisticsFacade) : Routes {

private val collectRequests = Route("/*", BEFORE) {
private val collectRequests = Route("/<*>", BEFORE) {
if (ctx.req.requestURI.length < MAX_IDENTIFIER_LENGTH) {
statisticsFacade.increaseRecord(REQUEST, ctx.req.requestURI)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

package com.reposilite.web

import io.javalin.http.Context
import io.javalin.http.HttpCode
import com.reposilite.auth.Session
import com.reposilite.failure.api.ErrorResponse
import io.javalin.http.Context
import io.javalin.http.HttpCode
import panda.std.Result

class ContextDsl(val ctx: Context, val context: ReposiliteContext) {
Expand Down Expand Up @@ -58,14 +58,6 @@ class ContextDsl(val ctx: Context, val context: ReposiliteContext) {
}
}

/**
* Get first available splat or empty string
*/
fun wildcard(defaultValue: String = ""): String =
ctx.splat(0)
.takeIf { it?.isNotEmpty() ?: false }
?: defaultValue

fun parameter(name: String): String =
ctx.pathParam(name)

Expand Down
Loading

0 comments on commit 8c3d545

Please sign in to comment.