Skip to content

Commit

Permalink
feat: add REST documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
bayang committed Feb 27, 2022
1 parent 4ee4c45 commit aa3c635
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 8 deletions.
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ dependencies {

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")

val springdocVersion = "1.5.12" // later uses Spring Boot 2.6.0
implementation("org.springdoc:springdoc-openapi-ui:$springdocVersion")
implementation("org.springdoc:springdoc-openapi-security:$springdocVersion")
implementation("org.springdoc:springdoc-openapi-kotlin:$springdocVersion")
implementation("org.springdoc:springdoc-openapi-data-rest:$springdocVersion")
}

tasks.withType<Test> {
Expand Down
36 changes: 36 additions & 0 deletions src/main/kotlin/io/github/bayang/jelu/config/OpenApiConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.github.bayang.jelu.config

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.Configuration

@Configuration
class OpenApiConfig {

@Bean
fun openApi(): OpenAPI =
OpenAPI()
.info(
Info()
.title("Jelu API")
.version("v1.0")
.license(License().name("MIT").url("https://github.com/bayang/jelu/blob/main/LICENSE")),
)
.externalDocs(
ExternalDocumentation()
.description("jelu documentation")
.url("https://github.com/bayang/jelu"),
)
.components(
Components()
.addSecuritySchemes(
"basicAuth",
SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("basic"),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import io.github.bayang.jelu.dto.UserBookLightDto
import io.github.bayang.jelu.dto.UserBookUpdateDto
import io.github.bayang.jelu.dto.assertIsJeluUser
import io.github.bayang.jelu.service.BookService
import io.swagger.v3.oas.annotations.responses.ApiResponse
import mu.KotlinLogging
import org.springdoc.api.annotations.ParameterObject
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
Expand Down Expand Up @@ -55,7 +57,7 @@ class BooksController(
@RequestParam(name = "authors", required = false) authors: List<String>?,
@RequestParam(name = "tags", required = false) tags: List<String>?,
@RequestParam(name = "libraryFilter", required = false) libraryFilter: LibraryFilter?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) pageable: Pageable,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) @ParameterObject pageable: Pageable,
principal: Authentication
): Page<BookDto> {
return repository.findAll(title, isbn10, isbn13, series, authors, tags, pageable, (principal.principal as JeluUser).user, libraryFilter ?: LibraryFilter.ANY)
Expand All @@ -67,18 +69,21 @@ class BooksController(
@GetMapping(path = ["/userbooks/{id}"])
fun userbookById(@PathVariable("id") userbookId: UUID) = repository.findUserBookById(userbookId)

@ApiResponse(responseCode = "204", description = "Deleted the userbook")
@DeleteMapping(path = ["/userbooks/{id}"])
fun deleteUserbookById(@PathVariable("id") userbookId: UUID): ResponseEntity<Unit> {
repository.deleteUserBookById(userbookId)
return ResponseEntity.noContent().build()
}

@ApiResponse(responseCode = "204", description = "Deleted the book")
@DeleteMapping(path = ["/books/{id}"])
fun deletebookById(@PathVariable("id") bookId: UUID): ResponseEntity<Unit> {
repository.deleteBookById(bookId)
return ResponseEntity.noContent().build()
}

@ApiResponse(responseCode = "204", description = "Deleted the tag from the book")
@DeleteMapping(path = ["/books/{bookId}/tags/{tagId}"])
fun deleteTagFromBook(
@PathVariable("bookId") bookId: UUID,
Expand All @@ -88,12 +93,14 @@ class BooksController(
return ResponseEntity.noContent().build()
}

@ApiResponse(responseCode = "204", description = "Deleted the tag")
@DeleteMapping(path = ["/tags/{tagId}"])
fun deleteTagById(@PathVariable("tagId") tagId: UUID): ResponseEntity<Unit> {
repository.deleteTagById(tagId)
return ResponseEntity.noContent().build()
}

@ApiResponse(responseCode = "204", description = "Deleted the author from the book")
@DeleteMapping(path = ["/books/{bookId}/authors/{authorId}"])
fun deleteAuthorFromBook(
@PathVariable("bookId") bookId: UUID,
Expand All @@ -103,6 +110,7 @@ class BooksController(
return ResponseEntity.noContent().build()
}

@ApiResponse(responseCode = "204", description = "Deleted the author")
@DeleteMapping(path = ["/authors/{authorId}"])
fun deleteAuthorById(@PathVariable("authorId") authorId: UUID): ResponseEntity<Unit> {
repository.deleteAuthorById(authorId)
Expand All @@ -115,7 +123,7 @@ class BooksController(
@RequestParam(name = "lastEventTypes", required = false) eventTypes: List<ReadingEventType>?,
@RequestParam(name = "toRead", required = false) toRead: Boolean?,
@RequestParam(name = "user", required = false) userId: UUID?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) pageable: Pageable
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) @ParameterObject pageable: Pageable
): Page<UserBookLightDto> {
assertIsJeluUser(principal.principal)
val finalUserId = if ((principal.principal as JeluUser).user.isAdmin && userId != null) {
Expand All @@ -129,15 +137,15 @@ class BooksController(
@GetMapping(path = ["/authors"])
fun authors(
@RequestParam(name = "name", required = false) name: String?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["name"]) pageable: Pageable
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["name"]) @ParameterObject pageable: Pageable
): Page<AuthorDto> {
return repository.findAllAuthors(name, pageable)
}

@GetMapping(path = ["/tags"])
fun tags(
@RequestParam(name = "name", required = false) name: String?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["name"]) pageable: Pageable
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["name"]) @ParameterObject pageable: Pageable
): Page<TagDto> = repository.findAllTags(name, pageable)

@GetMapping(path = ["/tags/{id}"])
Expand All @@ -153,7 +161,7 @@ class BooksController(
fun tagBooksById(
@PathVariable("id") tagId: UUID,
@RequestParam(name = "libraryFilter", required = false) libraryFilter: LibraryFilter?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) pageable: Pageable,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) @ParameterObject pageable: Pageable,
principal: Authentication
): Page<BookDto> {
assertIsJeluUser(principal.principal)
Expand All @@ -167,7 +175,7 @@ class BooksController(
fun authorBooksById(
@PathVariable("id") authorId: UUID,
@RequestParam(name = "libraryFilter", required = false) libraryFilter: LibraryFilter?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) pageable: Pageable,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["title"]) @ParameterObject pageable: Pageable,
principal: Authentication
): Page<BookDto> =
repository.findAuthorBooksById(authorId, (principal.principal as JeluUser).user, pageable, libraryFilter ?: LibraryFilter.ANY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import io.github.bayang.jelu.config.JeluProperties
import io.github.bayang.jelu.dto.ImportConfigurationDto
import io.github.bayang.jelu.dto.JeluUser
import io.github.bayang.jelu.service.import.CsvImportService
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.responses.ApiResponse
import mu.KotlinLogging
import org.apache.commons.io.FilenameUtils
import org.springframework.http.HttpStatus
Expand All @@ -27,6 +29,8 @@ class ImportController(
private val properties: JeluProperties,
) {

@ApiResponse(responseCode = "201", description = "Imported the csv file")
@Operation(description = "Trigger a csv import")
@PostMapping(path = ["/imports"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
fun importCsv(
principal: Authentication,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.bayang.jelu.config.JeluProperties
import io.github.bayang.jelu.dto.MetadataDto
import io.github.bayang.jelu.errors.JeluException
import io.github.bayang.jelu.service.metadata.FetchMetadataService
import io.swagger.v3.oas.annotations.Operation
import mu.KotlinLogging
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
Expand All @@ -19,6 +20,7 @@ class MetadataController(
private val metadataService: FetchMetadataService
) {

@Operation(description = "fetch metadata from the configured providers")
@GetMapping(path = ["/metadata"])
fun fetchMetadata(
@RequestParam(name = "isbn", required = false) isbn: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package io.github.bayang.jelu.controllers

import io.github.bayang.jelu.dto.QuoteDto
import io.github.bayang.jelu.service.quotes.IQuoteProvider
import io.swagger.v3.oas.annotations.media.ArraySchema
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
Expand All @@ -14,10 +19,52 @@ class QuotesController(
private val quotesProvider: IQuoteProvider
) {

@ApiResponses(
value = [
ApiResponse(
responseCode = "200", description = "quotes list",
content = [
(
Content(
mediaType = "application/json",
array = (
ArraySchema(
schema = Schema(
implementation = QuoteDto::class
)
)
)
)
)
]
)
]
)
@GetMapping(path = ["/quotes"])
fun quotes(@RequestParam(name = "query", required = false) query: String?): Mono<List<QuoteDto>> =
quotesProvider.quotes(query)

@ApiResponses(
value = [
ApiResponse(
responseCode = "200", description = "random quotes list",
content = [
(
Content(
mediaType = "application/json",
array = (
ArraySchema(
schema = Schema(
implementation = QuoteDto::class
)
)
)
)
)
]
)
]
)
@GetMapping(path = ["/quotes/random"])
fun quotes(): Mono<List<QuoteDto>> = quotesProvider.random()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import io.github.bayang.jelu.dto.ReadingEventDto
import io.github.bayang.jelu.dto.UpdateReadingEventDto
import io.github.bayang.jelu.dto.assertIsJeluUser
import io.github.bayang.jelu.service.ReadingEventService
import io.swagger.v3.oas.annotations.responses.ApiResponse
import mu.KotlinLogging
import org.springdoc.api.annotations.ParameterObject
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
Expand Down Expand Up @@ -40,13 +42,13 @@ class ReadingEventsController(
fun readingEvents(
@RequestParam(name = "eventTypes", required = false) eventTypes: List<ReadingEventType>?,
@RequestParam(name = "userId", required = false) userId: UUID?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) pageable: Pageable
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) @ParameterObject pageable: Pageable
): Page<ReadingEventDto> = repository.findAll(eventTypes, userId, pageable)

@GetMapping(path = ["/reading-events/me"])
fun myReadingEvents(
@RequestParam(name = "eventTypes", required = false) eventTypes: List<ReadingEventType>?,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) pageable: Pageable,
@PageableDefault(page = 0, size = 20, direction = Sort.Direction.DESC, sort = ["modificationDate"]) @ParameterObject pageable: Pageable,
principal: Authentication
): Page<ReadingEventDto> {
assertIsJeluUser(principal.principal)
Expand All @@ -65,6 +67,7 @@ class ReadingEventsController(
return repository.updateReadingEvent(readingEventId, readingEvent)
}

@ApiResponse(responseCode = "204", description = "Deleted the reading event")
@DeleteMapping(path = ["/reading-events/{id}"])
fun deleteEventById(@PathVariable("id") eventId: UUID): ResponseEntity<Unit> {
repository.deleteReadingEventById(eventId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.bayang.jelu.controllers

import io.github.bayang.jelu.config.JeluProperties
import io.github.bayang.jelu.dto.ServerSettingsDto
import io.swagger.v3.oas.annotations.Operation
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
Expand All @@ -12,6 +13,7 @@ class ServerSettingsController(
private val properties: JeluProperties
) {

@Operation(description = "Get the capabilities configured for this server, eg : is the metadata binary installed etc...")
@GetMapping(path = ["/server-settings"])
fun getServerSettings(): ServerSettingsDto {
return if (properties.metadata.calibre.path.isNullOrEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.bayang.jelu.controllers
import io.github.bayang.jelu.config.JeluProperties
import io.github.bayang.jelu.dto.*
import io.github.bayang.jelu.service.UserService
import io.swagger.v3.oas.annotations.Operation
import mu.KotlinLogging
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.SimpleGrantedAuthority
Expand All @@ -20,9 +21,11 @@ class UsersController(
private val properties: JeluProperties
) {

@Operation(description = "get the current session token that the caller should provide in the X-Auth-Token header")
@GetMapping(path = ["/token"])
fun getToken(session: HttpSession) = mapOf<String, String>("token" to session.id)

@Operation(description = "tells if the initial setup has been done (first user has been created)")
@GetMapping(path = ["/setup/status"])
fun setupStatus(session: HttpSession) = mapOf<String, Boolean>("isInitialSetup" to repository.isInitialSetup())

Expand Down

0 comments on commit aa3c635

Please sign in to comment.