feat(swagger): update to OpenAPI 3

migrate from Springfox to Springdoc
This commit is contained in:
Gauthier Roebroeck 2020-04-13 20:08:12 +08:00
parent b900227c83
commit c9de7c8074
6 changed files with 65 additions and 38 deletions

View file

@ -51,9 +51,11 @@ dependencies {
implementation("io.micrometer:micrometer-registry-influx") implementation("io.micrometer:micrometer-registry-influx")
run { run {
val springfoxVersion = "2.9.2" val springdocVersion = "1.3.1"
implementation("io.springfox:springfox-swagger2:$springfoxVersion") implementation("org.springdoc:springdoc-openapi-ui:$springdocVersion")
implementation("io.springfox:springfox-swagger-ui:$springfoxVersion") implementation("org.springdoc:springdoc-openapi-data-rest:$springdocVersion")
implementation("org.springdoc:springdoc-openapi-security:$springdocVersion")
implementation("org.springdoc:springdoc-openapi-kotlin:$springdocVersion")
} }
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-kotlin")

View file

@ -1,36 +1,34 @@
package org.gotson.komga.infrastructure.swagger package org.gotson.komga.infrastructure.swagger
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.ExternalDocumentation
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.info.License
import io.swagger.v3.oas.models.security.SecurityScheme
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.context.request.WebRequest
import springfox.documentation.schema.AlternateTypeRules
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spring.web.plugins.Docket
import springfox.documentation.swagger2.annotations.EnableSwagger2
@Configuration @Configuration
@EnableSwagger2
class SwaggerConfiguration { class SwaggerConfiguration {
@Bean @Bean
fun getDocket(): Docket = fun openApi(): OpenAPI =
Docket(DocumentationType.SWAGGER_2) OpenAPI()
.ignoredParameterTypes( .info(Info()
AuthenticationPrincipal::class.java, .title("Komga API")
WebRequest::class.java .version("v1.0")
) .description("""
.alternateTypeRules(AlternateTypeRules.newRule( Komga offers 2 APIs: REST and OPDS.
Pageable::class.java,
PageableMixin::class.java,
Ordered.HIGHEST_PRECEDENCE
))
private class PageableMixin { Both APIs are secured using HTTP Basic Authentication.
val page = 0 """.trimIndent())
val size = 20 .license(License().name("MIT").url("https://github.com/gotson/komga/blob/master/LICENSE")))
val sort = "" .externalDocs(ExternalDocumentation()
} .description("Komga documentation")
.url("https://komga.org"))
.components(Components()
.addSecuritySchemes(
"basicAuth",
SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("basic")))
} }

View file

@ -3,6 +3,8 @@ package org.gotson.komga.interfaces.rest
import com.github.klinq.jpaspec.`in` import com.github.klinq.jpaspec.`in`
import com.github.klinq.jpaspec.likeLower import com.github.klinq.jpaspec.likeLower
import com.github.klinq.jpaspec.toJoin import com.github.klinq.jpaspec.toJoin
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import mu.KotlinLogging import mu.KotlinLogging
import org.gotson.komga.application.service.AsyncOrchestrator import org.gotson.komga.application.service.AsyncOrchestrator
import org.gotson.komga.application.service.BookLifecycle import org.gotson.komga.application.service.BookLifecycle
@ -21,6 +23,7 @@ import org.gotson.komga.interfaces.rest.dto.BookDto
import org.gotson.komga.interfaces.rest.dto.BookMetadataUpdateDto import org.gotson.komga.interfaces.rest.dto.BookMetadataUpdateDto
import org.gotson.komga.interfaces.rest.dto.PageDto import org.gotson.komga.interfaces.rest.dto.PageDto
import org.gotson.komga.interfaces.rest.dto.toDto import org.gotson.komga.interfaces.rest.dto.toDto
import org.springdoc.api.annotations.ParameterObject
import org.springframework.core.io.FileSystemResource import org.springframework.core.io.FileSystemResource
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
@ -71,7 +74,7 @@ class BookController(
@RequestParam(name = "search", required = false) searchTerm: String?, @RequestParam(name = "search", required = false) searchTerm: String?,
@RequestParam(name = "library_id", required = false) libraryIds: List<Long>?, @RequestParam(name = "library_id", required = false) libraryIds: List<Long>?,
@RequestParam(name = "media_status", required = false) mediaStatus: List<Media.Status>?, @RequestParam(name = "media_status", required = false) mediaStatus: List<Media.Status>?,
page: Pageable @ParameterObject page: Pageable
): Page<BookDto> { ): Page<BookDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -115,10 +118,12 @@ class BookController(
} }
@Operation(description = "Return newly added or updated books.")
@GetMapping("api/v1/books/latest") @GetMapping("api/v1/books/latest")
@Parameter(name = "sort", hidden = true)
fun getLatestSeries( fun getLatestSeries(
@AuthenticationPrincipal principal: KomgaPrincipal, @AuthenticationPrincipal principal: KomgaPrincipal,
page: Pageable @ParameterObject page: Pageable
): Page<BookDto> { ): Page<BookDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -193,6 +198,7 @@ class BookController(
} else throw ResponseStatusException(HttpStatus.NOT_FOUND) } else throw ResponseStatusException(HttpStatus.NOT_FOUND)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@Operation(description = "Download the book file.")
@GetMapping(value = [ @GetMapping(value = [
"api/v1/books/{bookId}/file", "api/v1/books/{bookId}/file",
"api/v1/books/{bookId}/file/*", "api/v1/books/{bookId}/file/*",
@ -348,6 +354,7 @@ class BookController(
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun updateMetadata( fun updateMetadata(
@PathVariable bookId: Long, @PathVariable bookId: Long,
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")
@Valid @RequestBody newMetadata: BookMetadataUpdateDto @Valid @RequestBody newMetadata: BookMetadataUpdateDto
): BookDto = ): BookDto =
bookRepository.findByIdOrNull(bookId)?.let { book -> bookRepository.findByIdOrNull(bookId)?.let { book ->

View file

@ -3,6 +3,8 @@ package org.gotson.komga.interfaces.rest
import com.github.klinq.jpaspec.`in` import com.github.klinq.jpaspec.`in`
import com.github.klinq.jpaspec.likeLower import com.github.klinq.jpaspec.likeLower
import com.github.klinq.jpaspec.toJoin import com.github.klinq.jpaspec.toJoin
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import mu.KotlinLogging import mu.KotlinLogging
import org.gotson.komga.application.service.AsyncOrchestrator import org.gotson.komga.application.service.AsyncOrchestrator
import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.model.Library
@ -16,6 +18,7 @@ import org.gotson.komga.interfaces.rest.dto.BookDto
import org.gotson.komga.interfaces.rest.dto.SeriesDto import org.gotson.komga.interfaces.rest.dto.SeriesDto
import org.gotson.komga.interfaces.rest.dto.SeriesMetadataUpdateDto import org.gotson.komga.interfaces.rest.dto.SeriesMetadataUpdateDto
import org.gotson.komga.interfaces.rest.dto.toDto import org.gotson.komga.interfaces.rest.dto.toDto
import org.springdoc.api.annotations.ParameterObject
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
@ -57,7 +60,7 @@ class SeriesController(
@RequestParam(name = "search", required = false) searchTerm: String?, @RequestParam(name = "search", required = false) searchTerm: String?,
@RequestParam(name = "library_id", required = false) libraryIds: List<Long>?, @RequestParam(name = "library_id", required = false) libraryIds: List<Long>?,
@RequestParam(name = "status", required = false) metadataStatus: List<SeriesMetadata.Status>?, @RequestParam(name = "status", required = false) metadataStatus: List<SeriesMetadata.Status>?,
page: Pageable @ParameterObject page: Pageable
): Page<SeriesDto> { ): Page<SeriesDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -100,11 +103,12 @@ class SeriesController(
}.map { it.toDto(includeUrl = principal.user.isAdmin()) } }.map { it.toDto(includeUrl = principal.user.isAdmin()) }
} }
// all updated series, whether newly added or updated @Operation(description = "Return recently added or updated series.")
@GetMapping("/latest") @GetMapping("/latest")
@Parameter(name = "sort", hidden = true)
fun getLatestSeries( fun getLatestSeries(
@AuthenticationPrincipal principal: KomgaPrincipal, @AuthenticationPrincipal principal: KomgaPrincipal,
page: Pageable @ParameterObject page: Pageable
): Page<SeriesDto> { ): Page<SeriesDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -119,11 +123,12 @@ class SeriesController(
}.map { it.toDto(includeUrl = principal.user.isAdmin()) } }.map { it.toDto(includeUrl = principal.user.isAdmin()) }
} }
// new series only, doesn't contain existing updated series @Operation(description = "Return newly added series.")
@GetMapping("/new") @GetMapping("/new")
@Parameter(name = "sort", hidden = true)
fun getNewSeries( fun getNewSeries(
@AuthenticationPrincipal principal: KomgaPrincipal, @AuthenticationPrincipal principal: KomgaPrincipal,
page: Pageable @ParameterObject page: Pageable
): Page<SeriesDto> { ): Page<SeriesDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -138,11 +143,12 @@ class SeriesController(
}.map { it.toDto(includeUrl = principal.user.isAdmin()) } }.map { it.toDto(includeUrl = principal.user.isAdmin()) }
} }
// updated series only, doesn't contain new series @Operation(description = "Return recently updated series, but not newly added ones.")
@GetMapping("/updated") @GetMapping("/updated")
@Parameter(name = "sort", hidden = true)
fun getUpdatedSeries( fun getUpdatedSeries(
@AuthenticationPrincipal principal: KomgaPrincipal, @AuthenticationPrincipal principal: KomgaPrincipal,
page: Pageable @ParameterObject page: Pageable
): Page<SeriesDto> { ): Page<SeriesDto> {
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
page.pageNumber, page.pageNumber,
@ -185,7 +191,7 @@ class SeriesController(
@AuthenticationPrincipal principal: KomgaPrincipal, @AuthenticationPrincipal principal: KomgaPrincipal,
@PathVariable(name = "seriesId") id: Long, @PathVariable(name = "seriesId") id: Long,
@RequestParam(name = "media_status", required = false) mediaStatus: List<Media.Status>?, @RequestParam(name = "media_status", required = false) mediaStatus: List<Media.Status>?,
page: Pageable @ParameterObject page: Pageable
): Page<BookDto> { ): Page<BookDto> {
seriesRepository.findByIdOrNull(id)?.let { seriesRepository.findByIdOrNull(id)?.let {
if (!principal.user.canAccessSeries(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) if (!principal.user.canAccessSeries(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
@ -234,6 +240,7 @@ class SeriesController(
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun updateMetadata( fun updateMetadata(
@PathVariable seriesId: Long, @PathVariable seriesId: Long,
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")
@Valid @RequestBody newMetadata: SeriesMetadataUpdateDto @Valid @RequestBody newMetadata: SeriesMetadataUpdateDto
): SeriesDto = ): SeriesDto =
seriesRepository.findByIdOrNull(seriesId)?.let { series -> seriesRepository.findByIdOrNull(seriesId)?.let { series ->

View file

@ -34,6 +34,10 @@ management.metrics.export.influx:
# enabled: true # enabled: true
uri: http://localhost:8086 uri: http://localhost:8086
springdoc:
cache:
disabled: true
#server: #server:
# servlet: # servlet:
# context-path: /komga # context-path: /komga

View file

@ -53,3 +53,12 @@ management:
export: export:
influx: influx:
enabled: false enabled: false
springdoc:
group-configs:
- group: REST API
paths-to-match: /api/**
- group: OPDS
paths-to-match: /opds/**
swagger-ui:
groups-order: desc
operations-sorter: alpha