mirror of
https://github.com/gotson/komga.git
synced 2025-12-24 17:35:03 +01:00
docs: cleanup openApi documentation
This commit is contained in:
parent
d4d3f641a2
commit
ad8ee86a17
24 changed files with 489 additions and 33 deletions
|
|
@ -6,21 +6,86 @@ 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 io.swagger.v3.oas.models.tags.Tag
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.ANNOUNCEMENTS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.API_KEYS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOKS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOK_FONTS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOK_IMPORT
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOK_PAGES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOK_POSTER
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.BOOK_WEBPUB
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.CLAIM
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.CLIENT_SETTINGS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.COLLECTIONS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.COLLECTION_POSTER
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.COLLECTION_SERIES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.COMICRACK
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.CURRENT_USER
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.DEPRECATED
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.DUPLICATE_PAGES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.FILE_SYSTEM
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.HISTORY
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.LIBRARIES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.LOGIN
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.MIHON
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.OAUTH2
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.READLISTS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.READLIST_BOOKS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.READLIST_POSTER
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.REFERENTIAL
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.RELEASES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.SERIES
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.SERIES_POSTER
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.SERVER_SETTINGS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.SYNCPOINTS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.TASKS
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames.USERS
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class OpenApiConfiguration {
|
||||
class OpenApiConfiguration(
|
||||
@Value("\${application.version}") private val appVersion: String,
|
||||
) {
|
||||
@Bean
|
||||
fun openApi(): OpenAPI =
|
||||
OpenAPI()
|
||||
.info(
|
||||
Info()
|
||||
.title("Komga API")
|
||||
.version("v1.0")
|
||||
.version(appVersion)
|
||||
.description(
|
||||
"""
|
||||
Komga RESTful API.
|
||||
|
||||
# Authentication
|
||||
|
||||
Most endpoints require authentication. Authentication is done using either:
|
||||
- Basic Authentication
|
||||
- Passing an API Key in the `X-API-Key` header
|
||||
|
||||
# Sessions
|
||||
|
||||
Upon successful authentication, a session is created, and can be reused.
|
||||
|
||||
- By default, a `SESSION` cookie is set via `Set-Cookie` response header. This works well for browsers and clients that can handle cookies.
|
||||
- If you specify a header `X-Auth-Token` during authentication, the session ID will be returned via this same header. You can then pass that header again for subsequent requests to reuse the session.
|
||||
|
||||
If you need to set the session cookie later on, you can call `/api/v1/login/set-cookie` with `X-Auth-Token`. The response will contain the `Set-Cookie` header.
|
||||
|
||||
# Remember Me
|
||||
|
||||
During authentication, if a request parameter `remember-me` is passed and set to `true`, the server will also return a `remember-me` cookie. This cookie will be used to login automatically even if the session has expired.
|
||||
|
||||
# Logout
|
||||
|
||||
You can explicitly logout an existing session by calling `/api/logout`. This would return a `204`.
|
||||
|
||||
# Deprecation
|
||||
|
||||
API endpoints marked as deprecated will be removed in the next major version.
|
||||
""".trimIndent(),
|
||||
).license(License().name("MIT").url("https://github.com/gotson/komga/blob/master/LICENSE")),
|
||||
).externalDocs(
|
||||
|
|
@ -41,5 +106,183 @@ class OpenApiConfiguration {
|
|||
.`in`(SecurityScheme.In.HEADER)
|
||||
.name("X-API-Key"),
|
||||
),
|
||||
)
|
||||
).tags(tags)
|
||||
.extensions(mapOf("x-tagGroups" to tagGroups))
|
||||
|
||||
data class TagGroup(
|
||||
val name: String,
|
||||
val tags: List<String>,
|
||||
)
|
||||
|
||||
private val tagGroups =
|
||||
listOf(
|
||||
TagGroup(
|
||||
"Libraries",
|
||||
listOf(
|
||||
LIBRARIES,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Series",
|
||||
listOf(
|
||||
SERIES,
|
||||
SERIES_POSTER,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Books",
|
||||
listOf(
|
||||
BOOKS,
|
||||
BOOK_PAGES,
|
||||
BOOK_POSTER,
|
||||
BOOK_IMPORT,
|
||||
DUPLICATE_PAGES,
|
||||
BOOK_WEBPUB,
|
||||
BOOK_FONTS,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Collections",
|
||||
listOf(
|
||||
COLLECTIONS,
|
||||
COLLECTION_SERIES,
|
||||
COLLECTION_POSTER,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Readlists",
|
||||
listOf(
|
||||
READLISTS,
|
||||
READLIST_BOOKS,
|
||||
READLIST_POSTER,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Referential",
|
||||
listOf(
|
||||
REFERENTIAL,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Users",
|
||||
listOf(
|
||||
CURRENT_USER,
|
||||
USERS,
|
||||
API_KEYS,
|
||||
LOGIN,
|
||||
OAUTH2,
|
||||
SYNCPOINTS,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Server",
|
||||
listOf(
|
||||
CLAIM,
|
||||
SERVER_SETTINGS,
|
||||
TASKS,
|
||||
HISTORY,
|
||||
FILE_SYSTEM,
|
||||
RELEASES,
|
||||
ANNOUNCEMENTS,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Integrations",
|
||||
listOf(
|
||||
CLIENT_SETTINGS,
|
||||
MIHON,
|
||||
COMICRACK,
|
||||
),
|
||||
),
|
||||
TagGroup(
|
||||
"Deprecation",
|
||||
listOf(
|
||||
DEPRECATED,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
object TagNames {
|
||||
const val LIBRARIES = "Libraries"
|
||||
|
||||
const val SERIES = "Series"
|
||||
const val SERIES_POSTER = "Series Poster"
|
||||
|
||||
const val BOOKS = "Books"
|
||||
const val BOOK_POSTER = "Book Poster"
|
||||
const val BOOK_PAGES = "Book Pages"
|
||||
const val BOOK_WEBPUB = "WebPub Manifest"
|
||||
const val BOOK_IMPORT = "Import"
|
||||
const val BOOK_FONTS = "Fonts"
|
||||
const val DUPLICATE_PAGES = "Duplicate Pages"
|
||||
|
||||
const val COLLECTIONS = "Collections"
|
||||
const val COLLECTION_SERIES = "Collection Series"
|
||||
const val COLLECTION_POSTER = "Collection Poster"
|
||||
|
||||
const val READLISTS = "Readlists"
|
||||
const val READLIST_BOOKS = "Readlist Books"
|
||||
const val READLIST_POSTER = "Readlist Poster"
|
||||
|
||||
const val REFERENTIAL = "Referential metadata"
|
||||
|
||||
const val CURRENT_USER = "Current user"
|
||||
const val USERS = "Users"
|
||||
const val API_KEYS = "API Keys"
|
||||
const val LOGIN = "User login"
|
||||
const val OAUTH2 = "OAuth2"
|
||||
const val SYNCPOINTS = "Sync points"
|
||||
|
||||
const val CLAIM = "Claim server"
|
||||
const val TASKS = "Tasks"
|
||||
const val HISTORY = "History"
|
||||
const val FILE_SYSTEM = "File system"
|
||||
const val SERVER_SETTINGS = "Server settings"
|
||||
const val RELEASES = "Releases"
|
||||
const val ANNOUNCEMENTS = "Announcements"
|
||||
|
||||
const val MIHON = "Mihon"
|
||||
const val COMICRACK = "ComicRack"
|
||||
const val CLIENT_SETTINGS = "Client settings"
|
||||
|
||||
const val DEPRECATED = "Deprecated"
|
||||
}
|
||||
|
||||
private val tags =
|
||||
listOf(
|
||||
Tag().name(LIBRARIES).description("Manage libraries."),
|
||||
Tag().name(SERIES).description("Manage series."),
|
||||
Tag().name(SERIES_POSTER).description("Manage posters for series."),
|
||||
Tag().name(BOOKS).description("Manage books."),
|
||||
Tag().name(BOOK_POSTER).description("Manage posters for books."),
|
||||
Tag().name(BOOK_PAGES),
|
||||
Tag().name(BOOK_WEBPUB),
|
||||
Tag().name(BOOK_IMPORT),
|
||||
Tag().name(BOOK_FONTS).description("Provide font files and CSS for the Epub Reader."),
|
||||
Tag().name(DUPLICATE_PAGES).description("Manage duplicate pages. Duplicate pages are identified by a page hash."),
|
||||
Tag().name(COLLECTIONS).description("Manage collections."),
|
||||
Tag().name(COLLECTION_POSTER).description("Manage posters for collections."),
|
||||
Tag().name(COLLECTION_SERIES),
|
||||
Tag().name(READLISTS).description("Manage readlists."),
|
||||
Tag().name(READLIST_POSTER).description("Manage posters for readlists."),
|
||||
Tag().name(READLIST_BOOKS),
|
||||
Tag().name(REFERENTIAL).description("Retrieve referential metadata from all items in the Komga server."),
|
||||
Tag().name(CURRENT_USER).description("Manage current user."),
|
||||
Tag().name(USERS).description("Manage users."),
|
||||
Tag().name(API_KEYS).description("Manage API Keys"),
|
||||
Tag().name(LOGIN),
|
||||
Tag().name(OAUTH2).description("List registered OAuth2 providers"),
|
||||
Tag().name(SYNCPOINTS).description("Sync points are automatically created during a Kobo sync."),
|
||||
Tag().name(CLAIM).description("Claim a freshly installed Komga server."),
|
||||
Tag().name(TASKS).description("Manage server tasks"),
|
||||
Tag().name(HISTORY).description("Server events history"),
|
||||
Tag().name(FILE_SYSTEM).description("List files from the host server's file system"),
|
||||
Tag().name(SERVER_SETTINGS).description("Store and retrieve server settings"),
|
||||
Tag().name(RELEASES).description("Retrieve releases information"),
|
||||
Tag().name(ANNOUNCEMENTS).description("Retrieve announcements from the Komga website"),
|
||||
Tag().name(MIHON),
|
||||
Tag().name(COMICRACK),
|
||||
Tag().name(CLIENT_SETTINGS).description("Store and retrieve global and per-user settings. Those settings are not used by Komga itself, but can be stored for convenience by client applications."),
|
||||
Tag().name(DEPRECATED),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import org.gotson.komga.domain.service.BookLifecycle
|
|||
import org.gotson.komga.infrastructure.image.ImageType
|
||||
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.web.getMediaTypeOrDefault
|
||||
import org.gotson.komga.interfaces.api.dto.MEDIATYPE_PROGRESSION_JSON_VALUE
|
||||
import org.gotson.komga.interfaces.api.persistence.BookDtoRepository
|
||||
|
|
@ -190,6 +191,7 @@ class CommonBookController(
|
|||
}
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get raw book page", description = "Returns the book page in raw format, without content negotiation.", tags = [OpenApiConfiguration.TagNames.BOOK_PAGES])
|
||||
@GetMapping(
|
||||
value = [
|
||||
"api/v1/books/{bookId}/pages/{pageNumber}/raw",
|
||||
|
|
@ -252,6 +254,7 @@ class CommonBookController(
|
|||
throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved")
|
||||
}
|
||||
|
||||
@Operation(summary = "Get Eoub resource", description = "Return a resource from within an Epub book.", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = [
|
||||
"api/v1/books/{bookId}/resource/{*resource}",
|
||||
|
|
@ -306,7 +309,7 @@ class CommonBookController(
|
|||
.body(bytes)
|
||||
}
|
||||
|
||||
@Operation(description = "Download the book file.")
|
||||
@Operation(summary = "Download book file", description = "Download the book file.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@GetMapping(
|
||||
value = [
|
||||
"api/v1/books/{bookId}/file",
|
||||
|
|
@ -360,6 +363,7 @@ class CommonBookController(
|
|||
}
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get book progression", description = "The Progression API is a proposed standard for OPDS 2 and Readium. It is used by the Epub Reader.", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = [
|
||||
"api/v1/books/{bookId}/progression",
|
||||
|
|
@ -379,6 +383,7 @@ class CommonBookController(
|
|||
} ?: ResponseEntity.noContent().build()
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Mark book progression", description = "The Progression API is a proposed standard for OPDS 2 and Readium. It is used by the Epub Reader.", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@PutMapping(
|
||||
value = [
|
||||
"api/v1/books/{bookId}/progression",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.domain.persistence.KomgaUserRepository
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.interfaces.api.rest.dto.JsonFeedDto
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
|
|
@ -22,6 +25,7 @@ private const val WEBSITE = "https://komga.org"
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/announcements", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.ANNOUNCEMENTS)
|
||||
class AnnouncementController(
|
||||
private val userRepository: KomgaUserRepository,
|
||||
webClientBuilder: WebClient.Builder,
|
||||
|
|
@ -36,6 +40,7 @@ class AnnouncementController(
|
|||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "Retrieve announcements")
|
||||
fun getAnnouncements(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): JsonFeedDto =
|
||||
|
|
@ -50,6 +55,7 @@ class AnnouncementController(
|
|||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PutMapping
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Mark announcements as read")
|
||||
fun markAnnouncementsRead(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestBody announcementIds: Set<String>,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.gotson.komga.infrastructure.image.ImageAnalyzer
|
|||
import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
||||
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.getMediaTypeOrDefault
|
||||
|
|
@ -118,7 +119,7 @@ class BookController(
|
|||
@Deprecated("use /v1/books/list instead")
|
||||
@PageableAsQueryParam
|
||||
@GetMapping("api/v1/books")
|
||||
@Operation(summary = "Use POST /api/v1/books/list instead")
|
||||
@Operation(summary = "List books", description = "Use POST /api/v1/books/list instead. Deprecated since 1.19.0.", tags = [OpenApiConfiguration.TagNames.BOOKS, OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
fun getAllBooks(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", required = false) searchTerm: String? = null,
|
||||
|
|
@ -170,6 +171,7 @@ class BookController(
|
|||
|
||||
@PageableAsQueryParam
|
||||
@PostMapping("api/v1/books/list")
|
||||
@Operation(summary = "List books", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
fun getBooksList(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestBody search: BookSearch,
|
||||
|
|
@ -198,7 +200,7 @@ class BookController(
|
|||
.map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(description = "Return newly added or updated books.")
|
||||
@Operation(summary = "List latest books", description = "Return newly added or updated books.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("api/v1/books/latest")
|
||||
fun getLatestBooks(
|
||||
|
|
@ -225,7 +227,7 @@ class BookController(
|
|||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(description = "Return first unread book of series with at least one book read and no books in progress.")
|
||||
@Operation(summary = "List books on deck", description = "Return first unread book of series with at least one book read and no books in progress.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("api/v1/books/ondeck")
|
||||
fun getBooksOnDeck(
|
||||
|
|
@ -241,6 +243,7 @@ class BookController(
|
|||
principal.user.restrictions,
|
||||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
|
||||
@Operation(summary = "List duplicate books", description = "Return books that have the same file hash.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PageableAsQueryParam
|
||||
@GetMapping("api/v1/books/duplicates")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
|
|
@ -268,6 +271,7 @@ class BookController(
|
|||
return bookDtoRepository.findAllDuplicates(principal.user.id, pageRequest)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get book details", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@GetMapping("api/v1/books/{bookId}")
|
||||
fun getOneBook(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -279,6 +283,7 @@ class BookController(
|
|||
it.restrictUrl(!principal.user.isAdmin)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get previous book in series", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@GetMapping("api/v1/books/{bookId}/previous")
|
||||
fun getBookSiblingPrevious(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -292,6 +297,7 @@ class BookController(
|
|||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get next book in series", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@GetMapping("api/v1/books/{bookId}/next")
|
||||
fun getBookSiblingNext(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -305,6 +311,7 @@ class BookController(
|
|||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List book's readlists", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@GetMapping("api/v1/books/{bookId}/readlists")
|
||||
fun getAllReadListsByBook(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -317,6 +324,7 @@ class BookController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Get book's poster image", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/thumbnail"],
|
||||
|
|
@ -331,6 +339,7 @@ class BookController(
|
|||
return bookLifecycle.getThumbnailBytes(bookId)?.bytes ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get book poster image", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["api/v1/books/{bookId}/thumbnails/{thumbnailId}"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getBookThumbnailById(
|
||||
|
|
@ -344,6 +353,7 @@ class BookController(
|
|||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List book posters", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@GetMapping(value = ["api/v1/books/{bookId}/thumbnails"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getBookThumbnails(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -356,6 +366,7 @@ class BookController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Add book poster", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@PostMapping(value = ["api/v1/books/{bookId}/thumbnails"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun addUserUploadedBookThumbnail(
|
||||
|
|
@ -385,6 +396,7 @@ class BookController(
|
|||
).toDto()
|
||||
}
|
||||
|
||||
@Operation(summary = "Mark book poster as selected", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@PutMapping("api/v1/books/{bookId}/thumbnails/{thumbnailId}/selected")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -399,6 +411,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete book poster", description = "Only uploaded posters can be deleted.", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@DeleteMapping("api/v1/books/{bookId}/thumbnails/{thumbnailId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -416,6 +429,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List book pages", tags = [OpenApiConfiguration.TagNames.BOOK_PAGES])
|
||||
@GetMapping("api/v1/books/{bookId}/pages")
|
||||
fun getBookPages(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -450,6 +464,7 @@ class BookController(
|
|||
}
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get book page image", tags = [OpenApiConfiguration.TagNames.BOOK_PAGES])
|
||||
@ApiResponse(content = [Content(mediaType = "image/*", schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/pages/{pageNumber}"],
|
||||
|
|
@ -477,6 +492,7 @@ class BookController(
|
|||
contentNegotiation: Boolean,
|
||||
): ResponseEntity<ByteArray> = commonBookController.getBookPageInternal(bookId, if (zeroBasedIndex) pageNumber + 1 else pageNumber, convertTo, request, principal, if (contentNegotiation) acceptHeaders else null)
|
||||
|
||||
@Operation(summary = "Get book page thumbnail", description = "The image is resized to 300px on the largest dimension.", tags = [OpenApiConfiguration.TagNames.BOOK_PAGES])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/pages/{pageNumber}/thumbnail"],
|
||||
|
|
@ -519,6 +535,7 @@ class BookController(
|
|||
}
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get book's WebPub manifest", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/manifest"],
|
||||
produces = [MEDIATYPE_WEBPUB_JSON_VALUE, MEDIATYPE_DIVINA_JSON_VALUE],
|
||||
|
|
@ -534,6 +551,7 @@ class BookController(
|
|||
.body(manifest)
|
||||
}
|
||||
|
||||
@Operation(summary = "List book's positions", description = "The Positions API is a proposed standard for OPDS 2 and Readium. It is used by the Epub Reader.", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/positions"],
|
||||
produces = [MEDIATYPE_POSITION_LIST_JSON_VALUE],
|
||||
|
|
@ -566,6 +584,7 @@ class BookController(
|
|||
.body(R2Positions(extension.positions.size, extension.positions))
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get book's WebPub manifest (Epub)", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/manifest/epub"],
|
||||
produces = [MEDIATYPE_WEBPUB_JSON_VALUE],
|
||||
|
|
@ -575,6 +594,7 @@ class BookController(
|
|||
@PathVariable bookId: String,
|
||||
): WPPublicationDto = commonBookController.getWebPubManifestEpubInternal(principal, bookId, webPubGenerator)
|
||||
|
||||
@Operation(summary = "Get book's WebPub manifest (PDF)", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/manifest/pdf"],
|
||||
produces = [MEDIATYPE_WEBPUB_JSON_VALUE],
|
||||
|
|
@ -584,6 +604,7 @@ class BookController(
|
|||
@PathVariable bookId: String,
|
||||
): WPPublicationDto = commonBookController.getWebPubManifestPdfInternal(principal, bookId, webPubGenerator)
|
||||
|
||||
@Operation(summary = "Get book's WebPub manifest (DiViNa)", tags = [OpenApiConfiguration.TagNames.BOOK_WEBPUB])
|
||||
@GetMapping(
|
||||
value = ["api/v1/books/{bookId}/manifest/divina"],
|
||||
produces = [MEDIATYPE_DIVINA_JSON_VALUE],
|
||||
|
|
@ -593,6 +614,7 @@ class BookController(
|
|||
@PathVariable bookId: String,
|
||||
): WPPublicationDto = commonBookController.getWebPubManifestDivinaInternal(principal, bookId, webPubGenerator)
|
||||
|
||||
@Operation(summary = "Analyze book", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PostMapping("api/v1/books/{bookId}/analyze")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -604,6 +626,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Refresh book metadata", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PostMapping("api/v1/books/{bookId}/metadata/refresh")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -616,6 +639,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Update book metadata", description = "Set a field to null to unset the metadata. You can omit fields you don't want to update.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PatchMapping("api/v1/books/{bookId}/metadata")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -635,6 +659,7 @@ class BookController(
|
|||
}
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Update book metadata in bulk", description = "Set a field to null to unset the metadata. You can omit fields you don't want to update.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PatchMapping("api/v1/books/metadata")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -658,7 +683,7 @@ class BookController(
|
|||
updatedBooks.map { it.seriesId }.distinct().forEach { taskEmitter.aggregateSeriesMetadata(it) }
|
||||
}
|
||||
|
||||
@Operation(description = "Mark book as read and/or change page progress")
|
||||
@Operation(summary = "Mark book's read progress", description = "Mark book as read and/or change page progress.", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@PatchMapping("api/v1/books/{bookId}/read-progress")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgress(
|
||||
|
|
@ -683,7 +708,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(description = "Mark book as unread")
|
||||
@Operation(summary = "Mark book as unread", description = "Mark book as unread", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@DeleteMapping("api/v1/books/{bookId}/read-progress")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun deleteReadProgress(
|
||||
|
|
@ -697,6 +722,7 @@ class BookController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Import books", tags = [OpenApiConfiguration.TagNames.BOOK_IMPORT])
|
||||
@PostMapping("api/v1/books/import")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -719,10 +745,11 @@ class BookController(
|
|||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete book file", tags = [OpenApiConfiguration.TagNames.BOOKS])
|
||||
@DeleteMapping("api/v1/books/{bookId}/file")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
fun deleteBook(
|
||||
fun deleteBookFile(
|
||||
@PathVariable bookId: String,
|
||||
) {
|
||||
taskEmitter.deleteBook(
|
||||
|
|
@ -731,6 +758,7 @@ class BookController(
|
|||
)
|
||||
}
|
||||
|
||||
@Operation(summary = "Regenerate books posters", tags = [OpenApiConfiguration.TagNames.BOOK_POSTER])
|
||||
@PutMapping("api/v1/books/thumbnails")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import jakarta.validation.constraints.Email
|
||||
import jakarta.validation.constraints.NotBlank
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.gotson.komga.domain.model.UserRoles
|
||||
import org.gotson.komga.domain.service.KomgaUserLifecycle
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.interfaces.api.rest.dto.UserDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.toDto
|
||||
import org.springframework.http.HttpStatus
|
||||
|
|
@ -19,14 +22,17 @@ import org.springframework.web.server.ResponseStatusException
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/claim", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.CLAIM)
|
||||
@Validated
|
||||
class ClaimController(
|
||||
private val userDetailsLifecycle: KomgaUserLifecycle,
|
||||
) {
|
||||
@GetMapping
|
||||
@Operation(summary = "Retrieve claim status", description = "Check whether this server has already been claimed.")
|
||||
fun getClaimStatus() = ClaimStatus(userDetailsLifecycle.countUsers() > 0)
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "Claim server", description = "Creates an admin user with the provided credentials.")
|
||||
fun claimAdmin(
|
||||
@Email(regexp = ".+@.+\\..+")
|
||||
@RequestHeader("X-Komga-Email")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotNull
|
|||
import jakarta.validation.constraints.Pattern
|
||||
import org.gotson.komga.infrastructure.jooq.main.ClientSettingsDtoDao
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ClientSettingDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ClientSettingGlobalUpdateDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ClientSettingUserUpdateDto
|
||||
|
|
@ -30,13 +31,7 @@ private const val KEY_REGEX = """^[a-z](?:[a-z0-9_-]*[a-z0-9])*(?:\.[a-z0-9](?:[
|
|||
|
||||
@RestController
|
||||
@RequestMapping(value = ["api/v1/client-settings"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(
|
||||
name = "Client Settings",
|
||||
description = """
|
||||
Store and retrieve global and per-user settings.
|
||||
Those settings are not used by Komga itself, but can be stored for convenience by client applications.
|
||||
""",
|
||||
)
|
||||
@Tag(name = OpenApiConfiguration.TagNames.CLIENT_SETTINGS)
|
||||
@Validated
|
||||
class ClientSettingsController(
|
||||
private val clientSettingsDtoDao: ClientSettingsDtoDao,
|
||||
|
|
@ -48,7 +43,7 @@ class ClientSettingsController(
|
|||
): Map<String, ClientSettingDto> = clientSettingsDtoDao.findAllGlobal(principal == null)
|
||||
|
||||
@GetMapping("user/list")
|
||||
@Operation(summary = "Retrieve client settings for the current user")
|
||||
@Operation(summary = "Retrieve user client settings")
|
||||
fun getUserSettings(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): Map<String, ClientSettingDto> = clientSettingsDtoDao.findAllUser(principal.user.id)
|
||||
|
|
@ -95,7 +90,7 @@ class ClientSettingsController(
|
|||
|
||||
@PatchMapping("user")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Save user settings for the current user", description = "Setting key should be a valid lowercase namespace string like 'application.domain.key'")
|
||||
@Operation(summary = "Save user settings", description = "Setting key should be a valid lowercase namespace string like 'application.domain.key'")
|
||||
@OASRequestBody(
|
||||
content = [
|
||||
Content(
|
||||
|
|
@ -155,7 +150,7 @@ class ClientSettingsController(
|
|||
|
||||
@DeleteMapping("user")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Delete user settings for the current user", description = "Setting key should be a valid lowercase namespace string like 'application.domain.key'")
|
||||
@Operation(summary = "Delete user settings", description = "Setting key should be a valid lowercase namespace string like 'application.domain.key'")
|
||||
@OASRequestBody(
|
||||
content = [
|
||||
Content(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
|
@ -18,10 +21,15 @@ import kotlin.streams.asSequence
|
|||
@RestController
|
||||
@RequestMapping("api/v1/filesystem", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Tag(name = OpenApiConfiguration.TagNames.FILE_SYSTEM)
|
||||
class FileSystemController {
|
||||
private val fs = FileSystems.getDefault()
|
||||
|
||||
@PostMapping
|
||||
@Operation(
|
||||
summary = "Directory listing",
|
||||
description = "List folders and files from the host server's file system. If no request body is passed then the root directories are returned.",
|
||||
)
|
||||
fun getDirectoryListing(
|
||||
@RequestBody(required = false) request: DirectoryRequestDto = DirectoryRequestDto(),
|
||||
): DirectoryListingDto =
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.language.contains
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import org.springframework.core.io.FileSystemResource
|
||||
|
|
@ -28,6 +31,7 @@ private val logger = KotlinLogging.logger {}
|
|||
|
||||
@RestController
|
||||
@RequestMapping(value = ["api/v1/fonts"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.BOOK_FONTS)
|
||||
class FontsController(
|
||||
komgaProperties: KomgaProperties,
|
||||
) {
|
||||
|
|
@ -81,9 +85,11 @@ class FontsController(
|
|||
}
|
||||
|
||||
@GetMapping("families")
|
||||
@Operation(summary = "List font families", description = "List all available font families.")
|
||||
fun listFonts(): Set<String> = fonts.keys
|
||||
|
||||
@GetMapping("resource/{fontFamily}/{fontFile}")
|
||||
@Operation(summary = "Download font file")
|
||||
fun getFontFile(
|
||||
@PathVariable fontFamily: String,
|
||||
@PathVariable fontFile: String,
|
||||
|
|
@ -105,6 +111,7 @@ class FontsController(
|
|||
}
|
||||
|
||||
@GetMapping("resource/{fontFamily}/css", produces = ["text/css"])
|
||||
@Operation(summary = "Download CSS file", description = "Download a CSS file with the @font-face block for the font family. This is used by the Epub Reader to change fonts.")
|
||||
fun getFontFamilyAsCss(
|
||||
@PathVariable fontFamily: String,
|
||||
): ResponseEntity<Resource> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.interfaces.api.persistence.HistoricalEventDtoRepository
|
||||
import org.gotson.komga.interfaces.api.rest.dto.HistoricalEventDto
|
||||
|
|
@ -17,11 +20,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||
@RestController
|
||||
@RequestMapping("api/v1/history", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Tag(name = OpenApiConfiguration.TagNames.HISTORY)
|
||||
class HistoricalEventController(
|
||||
private val historicalEventDtoRepository: HistoricalEventDtoRepository,
|
||||
) {
|
||||
@GetMapping
|
||||
@PageableAsQueryParam
|
||||
@Operation(summary = "List historical events")
|
||||
fun getAll(
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<HistoricalEventDto> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package org.gotson.komga.interfaces.api.rest
|
|||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import jakarta.validation.Valid
|
||||
import org.gotson.komga.application.tasks.HIGHEST_PRIORITY
|
||||
import org.gotson.komga.application.tasks.HIGH_PRIORITY
|
||||
|
|
@ -18,6 +19,7 @@ import org.gotson.komga.domain.persistence.LibraryRepository
|
|||
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||
import org.gotson.komga.domain.service.LibraryLifecycle
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.web.filePathToUrl
|
||||
import org.gotson.komga.interfaces.api.rest.dto.LibraryCreationDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.LibraryDto
|
||||
|
|
@ -45,6 +47,7 @@ import java.io.FileNotFoundException
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/libraries", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.LIBRARIES)
|
||||
class LibraryController(
|
||||
private val taskEmitter: TaskEmitter,
|
||||
private val libraryLifecycle: LibraryLifecycle,
|
||||
|
|
@ -53,6 +56,10 @@ class LibraryController(
|
|||
private val seriesRepository: SeriesRepository,
|
||||
) {
|
||||
@GetMapping
|
||||
@Operation(
|
||||
summary = "List all libraries",
|
||||
description = "The libraries are filtered based on the current user's permissions",
|
||||
)
|
||||
fun getAll(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): List<LibraryDto> =
|
||||
|
|
@ -63,6 +70,7 @@ class LibraryController(
|
|||
}.sortedBy { it.name.lowercase() }.map { it.toDto(includeRoot = principal.user.isAdmin) }
|
||||
|
||||
@GetMapping("{libraryId}")
|
||||
@Operation(summary = "Get details for a single library")
|
||||
fun getOne(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@PathVariable libraryId: String,
|
||||
|
|
@ -74,6 +82,7 @@ class LibraryController(
|
|||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "Create a library")
|
||||
fun addOne(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@Valid @RequestBody
|
||||
|
|
@ -130,7 +139,7 @@ class LibraryController(
|
|||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Deprecated("Use PATCH /v1/libraries/{libraryId} instead", ReplaceWith("patchOne"))
|
||||
@Operation(summary = "Use PATCH /api/v1/libraries/{libraryId} instead")
|
||||
@Operation(summary = "Update a library", description = "Use PATCH /api/v1/libraries/{libraryId} instead. Deprecated since 1.3.0.", tags = [OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
fun updateOne(
|
||||
@PathVariable libraryId: String,
|
||||
@Valid @RequestBody
|
||||
|
|
@ -142,6 +151,7 @@ class LibraryController(
|
|||
@PatchMapping("/{libraryId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Update a library", description = "You can omit fields you don't want to update")
|
||||
fun patchOne(
|
||||
@PathVariable libraryId: String,
|
||||
@Parameter(description = "Fields to update. You can omit fields you don't want to update.")
|
||||
|
|
@ -204,6 +214,7 @@ class LibraryController(
|
|||
@DeleteMapping("/{libraryId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Delete a library")
|
||||
fun deleteOne(
|
||||
@PathVariable libraryId: String,
|
||||
) {
|
||||
|
|
@ -215,6 +226,7 @@ class LibraryController(
|
|||
@PostMapping("{libraryId}/scan")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
@Operation(summary = "Scan a library")
|
||||
fun scan(
|
||||
@PathVariable libraryId: String,
|
||||
@RequestParam(required = false) deep: Boolean = false,
|
||||
|
|
@ -227,6 +239,7 @@ class LibraryController(
|
|||
@PostMapping("{libraryId}/analyze")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
@Operation(summary = "Analyze a library")
|
||||
fun analyze(
|
||||
@PathVariable libraryId: String,
|
||||
) {
|
||||
|
|
@ -243,6 +256,7 @@ class LibraryController(
|
|||
@PostMapping("{libraryId}/metadata/refresh")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
@Operation(summary = "Refresh metadata for a library")
|
||||
fun refreshMetadata(
|
||||
@PathVariable libraryId: String,
|
||||
) {
|
||||
|
|
@ -261,6 +275,7 @@ class LibraryController(
|
|||
@PostMapping("{libraryId}/empty-trash")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
@Operation(summary = "Empty trash for a library")
|
||||
fun emptyTrash(
|
||||
@PathVariable libraryId: String,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import jakarta.servlet.http.HttpSession
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.session.web.http.CookieSerializer
|
||||
|
|
@ -13,9 +16,11 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
@RestController
|
||||
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.LOGIN)
|
||||
class LoginController(
|
||||
private val cookieSerializer: CookieSerializer,
|
||||
) {
|
||||
@Operation(summary = "Set cookie", description = "Forcefully return Set-Cookie header, even if the session is contained in the X-Auth-Token header.")
|
||||
@GetMapping("api/v1/login/set-cookie")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun headerToCookie(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
|
@ -8,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/oauth2", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.OAUTH2)
|
||||
class OAuth2Controller(
|
||||
clientRegistrationRepository: InMemoryClientRegistrationRepository?,
|
||||
) {
|
||||
|
|
@ -17,6 +21,7 @@ class OAuth2Controller(
|
|||
} ?: emptyList()
|
||||
|
||||
@GetMapping("providers")
|
||||
@Operation(summary = "List registered OAuth2 providers")
|
||||
fun getProviders() = registrationIds
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
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.tags.Tag
|
||||
import jakarta.validation.Valid
|
||||
import org.gotson.komga.application.tasks.TaskEmitter
|
||||
import org.gotson.komga.domain.model.BookPageNumbered
|
||||
import org.gotson.komga.domain.model.PageHashKnown
|
||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||
import org.gotson.komga.domain.service.PageHashLifecycle
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.getMediaTypeOrDefault
|
||||
import org.gotson.komga.interfaces.api.rest.dto.PageHashCreationDto
|
||||
|
|
@ -37,11 +40,13 @@ import org.springframework.web.server.ResponseStatusException
|
|||
@RestController
|
||||
@RequestMapping("api/v1/page-hashes", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Tag(name = OpenApiConfiguration.TagNames.DUPLICATE_PAGES)
|
||||
class PageHashController(
|
||||
private val pageHashRepository: PageHashRepository,
|
||||
private val pageHashLifecycle: PageHashLifecycle,
|
||||
private val taskEmitter: TaskEmitter,
|
||||
) {
|
||||
@Operation(summary = "List known duplicates")
|
||||
@GetMapping
|
||||
@PageableAsQueryParam
|
||||
fun getKnownPageHashes(
|
||||
|
|
@ -49,6 +54,7 @@ class PageHashController(
|
|||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<PageHashKnownDto> = pageHashRepository.findAllKnown(actions, page).map { it.toDto() }
|
||||
|
||||
@Operation(summary = "Get known duplicate image thumbnail")
|
||||
@GetMapping("/{pageHash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
fun getKnownPageHashThumbnail(
|
||||
|
|
@ -57,12 +63,14 @@ class PageHashController(
|
|||
pageHashRepository.getKnownThumbnail(pageHash)
|
||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "List unknown duplicates")
|
||||
@GetMapping("/unknown")
|
||||
@PageableAsQueryParam
|
||||
fun getUnknownPageHashes(
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<PageHashUnknownDto> = pageHashRepository.findAllUnknown(page).map { it.toDto() }
|
||||
|
||||
@Operation(summary = "List duplicate matches")
|
||||
@GetMapping("{pageHash}")
|
||||
@PageableAsQueryParam
|
||||
fun getPageHashMatches(
|
||||
|
|
@ -75,6 +83,7 @@ class PageHashController(
|
|||
page,
|
||||
).map { it.toDto() }
|
||||
|
||||
@Operation(summary = "Get unknown duplicate image thumbnail")
|
||||
@GetMapping("unknown/{pageHash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
fun getUnknownPageHashThumbnail(
|
||||
|
|
@ -88,6 +97,7 @@ class PageHashController(
|
|||
.body(it.bytes)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Mark duplicate page as known")
|
||||
@PutMapping
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
fun createOrUpdateKnownPageHash(
|
||||
|
|
@ -107,6 +117,7 @@ class PageHashController(
|
|||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete all duplicate pages by hash")
|
||||
@PostMapping("{pageHash}/delete-all")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
fun performDelete(
|
||||
|
|
@ -131,6 +142,7 @@ class PageHashController(
|
|||
taskEmitter.removeDuplicatePages(toRemove)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete specific duplicate page")
|
||||
@PostMapping("{pageHash}/delete-match")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
fun deleteSingleMatch(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.media.Content
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
|
|
@ -34,6 +35,7 @@ import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
|||
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.AuthorsAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.Authors
|
||||
import org.gotson.komga.interfaces.api.persistence.BookDtoRepository
|
||||
|
|
@ -98,6 +100,7 @@ class ReadListController(
|
|||
private val bookLifecycle: BookLifecycle,
|
||||
private val eventPublisher: ApplicationEventPublisher,
|
||||
) {
|
||||
@Operation(summary = "List readlists", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping
|
||||
fun getAll(
|
||||
|
|
@ -129,6 +132,7 @@ class ReadListController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Get readlist details", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@GetMapping("{id}")
|
||||
fun getOne(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -139,6 +143,7 @@ class ReadListController(
|
|||
?.toDto()
|
||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get readlist's poster image", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["{id}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getReadListThumbnail(
|
||||
|
|
@ -153,6 +158,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get readlist poster image", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["{id}/thumbnails/{thumbnailId}"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getReadListThumbnailById(
|
||||
|
|
@ -166,6 +172,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List readlist's posters", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@GetMapping(value = ["{id}/thumbnails"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getReadListThumbnails(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -176,6 +183,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Add readlist poster", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@PostMapping(value = ["{id}/thumbnails"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun addUserUploadedReadListThumbnail(
|
||||
|
|
@ -205,6 +213,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Mark readlist poster as selected", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@PutMapping("{id}/thumbnails/{thumbnailId}/selected")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -221,6 +230,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete readlist poster", tags = [OpenApiConfiguration.TagNames.READLIST_POSTER])
|
||||
@DeleteMapping("{id}/thumbnails/{thumbnailId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -236,6 +246,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Create readlist", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun addOne(
|
||||
|
|
@ -256,6 +267,7 @@ class ReadListController(
|
|||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
|
||||
}
|
||||
|
||||
@Operation(summary = "Match ComicRack list", tags = [OpenApiConfiguration.TagNames.COMICRACK])
|
||||
@PostMapping("match/comicrack")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun matchFromComicRackList(
|
||||
|
|
@ -267,6 +279,7 @@ class ReadListController(
|
|||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.code)
|
||||
}
|
||||
|
||||
@Operation(summary = "Update readlist", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@PatchMapping("{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -291,6 +304,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete readlist", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -302,6 +316,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List readlist's books", tags = [OpenApiConfiguration.TagNames.READLIST_BOOKS])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@AuthorsAsQueryParam
|
||||
@GetMapping("{id}/books")
|
||||
|
|
@ -353,6 +368,7 @@ class ReadListController(
|
|||
.map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get previous book in readlist", tags = [OpenApiConfiguration.TagNames.READLIST_BOOKS])
|
||||
@GetMapping("{id}/books/{bookId}/previous")
|
||||
fun getBookSiblingPrevious(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -370,6 +386,7 @@ class ReadListController(
|
|||
)?.restrictUrl(!principal.user.isAdmin)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get next book in readlist", tags = [OpenApiConfiguration.TagNames.READLIST_BOOKS])
|
||||
@GetMapping("{id}/books/{bookId}/next")
|
||||
fun getBookSiblingNext(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -387,6 +404,7 @@ class ReadListController(
|
|||
)?.restrictUrl(!principal.user.isAdmin)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get readlist read progress (Mihon)", description = "Mihon specific, due to how read progress is handled in Mihon.", tags = [OpenApiConfiguration.TagNames.MIHON])
|
||||
@GetMapping("{id}/read-progress/tachiyomi")
|
||||
fun getReadProgress(
|
||||
@PathVariable id: String,
|
||||
|
|
@ -396,6 +414,7 @@ class ReadListController(
|
|||
readProgressDtoRepository.findProgressByReadList(readList.id, principal.user.id)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Update readlist read progress (Mihon)", description = "Mihon specific, due to how read progress is handled in Mihon.", tags = [OpenApiConfiguration.TagNames.MIHON])
|
||||
@PutMapping("{id}/read-progress/tachiyomi")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgressTachiyomi(
|
||||
|
|
@ -415,6 +434,7 @@ class ReadListController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Download readlist", description = "Download the whole readlist as a ZIP file.", tags = [OpenApiConfiguration.TagNames.READLISTS])
|
||||
@GetMapping("{id}/file", produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
|
||||
@PreAuthorize("hasRole('FILE_DOWNLOAD')")
|
||||
fun getReadListFile(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.domain.persistence.ReferentialRepository
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.interfaces.api.rest.dto.AuthorDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.toDto
|
||||
|
|
@ -18,10 +21,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.REFERENTIAL)
|
||||
class ReferentialController(
|
||||
private val referentialRepository: ReferentialRepository,
|
||||
) {
|
||||
@GetMapping("v1/authors")
|
||||
@Deprecated("Use GET /v2/authors instead", ReplaceWith("getAuthors"))
|
||||
@Operation(summary = "List authors", description = "Use GET /api/v2/authors instead. Deprecated since 1.20.0.", tags = [OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
fun getAuthorsV1(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", defaultValue = "") search: String,
|
||||
|
|
@ -39,6 +45,7 @@ class ReferentialController(
|
|||
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("v2/authors")
|
||||
@Operation(summary = "List authors", description = "Can be filtered by various criteria")
|
||||
fun getAuthors(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", required = false) search: String?,
|
||||
|
|
@ -69,17 +76,20 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/authors/names")
|
||||
@Operation(summary = "List authors' names")
|
||||
fun getAuthorsNames(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", defaultValue = "") search: String,
|
||||
): List<String> = referentialRepository.findAllAuthorsNamesByName(search, principal.user.getAuthorizedLibraryIds(null))
|
||||
|
||||
@GetMapping("v1/authors/roles")
|
||||
@Operation(summary = "List authors' roles")
|
||||
fun getAuthorsRoles(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): List<String> = referentialRepository.findAllAuthorsRoles(principal.user.getAuthorizedLibraryIds(null))
|
||||
|
||||
@GetMapping("v1/genres")
|
||||
@Operation(summary = "List genres", description = "Can be filtered by various criteria")
|
||||
fun getGenres(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -92,6 +102,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/sharing-labels")
|
||||
@Operation(summary = "List sharing labels", description = "Can be filtered by various criteria")
|
||||
fun getSharingLabels(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -104,6 +115,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/tags")
|
||||
@Operation(summary = "List tags", description = "Can be filtered by various criteria")
|
||||
fun getTags(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -116,6 +128,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/tags/book")
|
||||
@Operation(summary = "List book tags", description = "Can be filtered by various criteria")
|
||||
fun getBookTags(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "series_id", required = false) seriesId: String?,
|
||||
|
|
@ -128,6 +141,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/tags/series")
|
||||
@Operation(summary = "List series tags", description = "Can be filtered by various criteria")
|
||||
fun getSeriesTags(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -140,6 +154,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/languages")
|
||||
@Operation(summary = "List languages", description = "Can be filtered by various criteria")
|
||||
fun getLanguages(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -152,6 +167,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/publishers")
|
||||
@Operation(summary = "List publishers", description = "Can be filtered by various criteria")
|
||||
fun getPublishers(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -164,6 +180,7 @@ class ReferentialController(
|
|||
}
|
||||
|
||||
@GetMapping("v1/age-ratings")
|
||||
@Operation(summary = "List age ratings", description = "Can be filtered by various criteria")
|
||||
fun getAgeRatings(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
@ -176,6 +193,7 @@ class ReferentialController(
|
|||
}.map { it?.toString() ?: "None" }.toSet()
|
||||
|
||||
@GetMapping("v1/series/release-dates")
|
||||
@Operation(summary = "List series release dates", description = "Can be filtered by various criteria")
|
||||
fun getSeriesReleaseDates(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.interfaces.api.rest.dto.GithubReleaseDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ReleaseDto
|
||||
import org.springframework.core.ParameterizedTypeReference
|
||||
|
|
@ -21,6 +24,7 @@ private const val GITHUB_API = "https://api.github.com/repos/gotson/komga/releas
|
|||
@RestController
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequestMapping("api/v1/releases", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.RELEASES)
|
||||
class ReleaseController(
|
||||
webClientBuilder: WebClient.Builder,
|
||||
) {
|
||||
|
|
@ -34,7 +38,8 @@ class ReleaseController(
|
|||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun getAnnouncements(
|
||||
@Operation(summary = "List releases")
|
||||
fun getReleases(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): List<ReleaseDto> =
|
||||
cache
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.media.Content
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
|
|
@ -26,6 +27,7 @@ import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
|||
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.AuthorsAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.Authors
|
||||
import org.gotson.komga.interfaces.api.persistence.SeriesDtoRepository
|
||||
|
|
@ -77,6 +79,7 @@ class SeriesCollectionController(
|
|||
private val thumbnailSeriesCollectionRepository: ThumbnailSeriesCollectionRepository,
|
||||
private val eventPublisher: ApplicationEventPublisher,
|
||||
) {
|
||||
@Operation(summary = "List collections", tags = [OpenApiConfiguration.TagNames.COLLECTIONS])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping
|
||||
fun getAll(
|
||||
|
|
@ -107,6 +110,7 @@ class SeriesCollectionController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Get collection details", tags = [OpenApiConfiguration.TagNames.COLLECTIONS])
|
||||
@GetMapping("{id}")
|
||||
fun getOne(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -117,6 +121,7 @@ class SeriesCollectionController(
|
|||
?.toDto()
|
||||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get collection's poster image", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["{id}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getCollectionThumbnail(
|
||||
|
|
@ -131,6 +136,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get collection poster image", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["{id}/thumbnails/{thumbnailId}"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getCollectionThumbnailById(
|
||||
|
|
@ -144,6 +150,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List collection's posters", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@GetMapping(value = ["{id}/thumbnails"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getCollectionThumbnails(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -154,6 +161,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Add collection poster", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@PostMapping(value = ["{id}/thumbnails"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun addUserUploadedCollectionThumbnail(
|
||||
|
|
@ -183,6 +191,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Mark collection poster as selected", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@PutMapping("{id}/thumbnails/{thumbnailId}/selected")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -199,6 +208,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete collection poster", tags = [OpenApiConfiguration.TagNames.COLLECTION_POSTER])
|
||||
@DeleteMapping("{id}/thumbnails/{thumbnailId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -214,6 +224,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Create collection", tags = [OpenApiConfiguration.TagNames.COLLECTIONS])
|
||||
@PostMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun addOne(
|
||||
|
|
@ -233,6 +244,7 @@ class SeriesCollectionController(
|
|||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
|
||||
}
|
||||
|
||||
@Operation(summary = "Update collection", tags = [OpenApiConfiguration.TagNames.COLLECTIONS])
|
||||
@PatchMapping("{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -256,6 +268,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete collection", tags = [OpenApiConfiguration.TagNames.COLLECTIONS])
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -267,6 +280,7 @@ class SeriesCollectionController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List collection's series", tags = [OpenApiConfiguration.TagNames.COLLECTION_SERIES])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@AuthorsAsQueryParam
|
||||
@GetMapping("{id}/series")
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
|||
import org.gotson.komga.infrastructure.mediacontainer.ContentDetector
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.AuthorsAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.Authors
|
||||
|
|
@ -119,6 +120,7 @@ class SeriesController(
|
|||
private val thumbnailsSeriesRepository: ThumbnailSeriesRepository,
|
||||
private val contentRestrictionChecker: ContentRestrictionChecker,
|
||||
) {
|
||||
@Operation(summary = "List series", description = "Use POST /api/v1/series/list instead. Deprecated since 1.19.0.", tags = [OpenApiConfiguration.TagNames.SERIES, OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
@Deprecated("use /v1/series/list instead")
|
||||
@PageableAsQueryParam
|
||||
@AuthorsAsQueryParam
|
||||
|
|
@ -131,7 +133,6 @@ class SeriesController(
|
|||
),
|
||||
)
|
||||
@GetMapping("v1/series")
|
||||
@Operation(summary = "Use POST /api/v1/series/list instead")
|
||||
fun getAllSeries(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", required = false) searchTerm: String? = null,
|
||||
|
|
@ -222,6 +223,7 @@ class SeriesController(
|
|||
.map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(summary = "List series", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PageableAsQueryParam
|
||||
@PostMapping("v1/series/list")
|
||||
fun getSeriesList(
|
||||
|
|
@ -252,6 +254,7 @@ class SeriesController(
|
|||
.map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(summary = "List series groups", description = "Use POST /api/v1/series/list/alphabetical-groups instead. Deprecated since 1.19.0.", tags = [OpenApiConfiguration.TagNames.SERIES, OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
@Deprecated("use /v1/series/list/alphabetical-groups instead")
|
||||
@AuthorsAsQueryParam
|
||||
@Parameters(
|
||||
|
|
@ -263,7 +266,6 @@ class SeriesController(
|
|||
),
|
||||
)
|
||||
@GetMapping("v1/series/alphabetical-groups")
|
||||
@Operation(summary = "Use POST /api/v1/series/list/alphabetical-groups instead")
|
||||
fun getAlphabeticalGroups(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "search", required = false) searchTerm: String?,
|
||||
|
|
@ -334,13 +336,14 @@ class SeriesController(
|
|||
return seriesDtoRepository.countByFirstCharacter(seriesSearch, SearchContext(principal.user))
|
||||
}
|
||||
|
||||
@Operation(summary = "List series groups", description = "List series grouped by the first character of their sort title.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PostMapping("v1/series/list/alphabetical-groups")
|
||||
fun getSeriesListByAlphabeticalGroups(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestBody search: SeriesSearch,
|
||||
): List<GroupCountDto> = seriesDtoRepository.countByFirstCharacter(search, SearchContext(principal.user))
|
||||
|
||||
@Operation(description = "Return recently added or updated series.")
|
||||
@Operation(summary = "List latest series", description = "Return recently added or updated series.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("v1/series/latest")
|
||||
fun getLatestSeries(
|
||||
|
|
@ -379,7 +382,7 @@ class SeriesController(
|
|||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(description = "Return newly added series.")
|
||||
@Operation(summary = "List new series", description = "Return newly added series.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("v1/series/new")
|
||||
fun getNewSeries(
|
||||
|
|
@ -418,7 +421,7 @@ class SeriesController(
|
|||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(description = "Return recently updated series, but not newly added ones.")
|
||||
@Operation(summary = "List updated series", description = "Return recently updated series, but not newly added ones.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping("v1/series/updated")
|
||||
fun getUpdatedSeries(
|
||||
|
|
@ -457,6 +460,7 @@ class SeriesController(
|
|||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(summary = "Get series details", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@GetMapping("v1/series/{seriesId}")
|
||||
fun getOneSeries(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -467,6 +471,7 @@ class SeriesController(
|
|||
it.restrictUrl(!principal.user.isAdmin)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(summary = "Get series' poster image", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["v1/series/{seriesId}/thumbnail"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getSeriesDefaultThumbnail(
|
||||
|
|
@ -479,6 +484,7 @@ class SeriesController(
|
|||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get series poster image", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
@GetMapping(value = ["v1/series/{seriesId}/thumbnails/{thumbnailId}"], produces = [MediaType.IMAGE_JPEG_VALUE])
|
||||
fun getSeriesThumbnailById(
|
||||
|
|
@ -492,6 +498,7 @@ class SeriesController(
|
|||
?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List series posters", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@GetMapping(value = ["v1/series/{seriesId}/thumbnails"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getSeriesThumbnails(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -504,6 +511,7 @@ class SeriesController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Add series poster", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@PostMapping(value = ["v1/series/{seriesId}/thumbnails"], consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
fun postUserUploadedSeriesThumbnail(
|
||||
|
|
@ -532,6 +540,7 @@ class SeriesController(
|
|||
).toDto()
|
||||
}
|
||||
|
||||
@Operation(summary = "Mark series poster as selected", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@PutMapping("v1/series/{seriesId}/thumbnails/{thumbnailId}/selected")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -546,6 +555,7 @@ class SeriesController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete series poster", tags = [OpenApiConfiguration.TagNames.SERIES_POSTER])
|
||||
@DeleteMapping("v1/series/{seriesId}/thumbnails/{thumbnailId}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -563,11 +573,11 @@ class SeriesController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
@Operation(summary = "List series' books", description = "Use POST /api/v1/books/list instead. Deprecated since 1.19.0.", tags = [OpenApiConfiguration.TagNames.SERIES, OpenApiConfiguration.TagNames.DEPRECATED])
|
||||
@Deprecated("use /v1/books/list instead")
|
||||
@PageableAsQueryParam
|
||||
@AuthorsAsQueryParam
|
||||
@GetMapping("v1/series/{seriesId}/books")
|
||||
@Operation(summary = "Use POST /api/v1/books/list instead")
|
||||
fun getAllBooksBySeries(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@PathVariable(name = "seriesId") seriesId: String,
|
||||
|
|
@ -618,6 +628,7 @@ class SeriesController(
|
|||
).map { it.restrictUrl(!principal.user.isAdmin) }
|
||||
}
|
||||
|
||||
@Operation(summary = "List series' collections", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@GetMapping("v1/series/{seriesId}/collections")
|
||||
fun getAllCollectionsBySeries(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -630,6 +641,7 @@ class SeriesController(
|
|||
.map { it.toDto() }
|
||||
}
|
||||
|
||||
@Operation(summary = "Analyze series", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PostMapping("v1/series/{seriesId}/analyze")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -639,6 +651,7 @@ class SeriesController(
|
|||
taskEmitter.analyzeBook(bookRepository.findAllBySeriesId(seriesId), HIGH_PRIORITY)
|
||||
}
|
||||
|
||||
@Operation(summary = "Refresh series metadata", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PostMapping("v1/series/{seriesId}/metadata/refresh")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
@ -651,6 +664,7 @@ class SeriesController(
|
|||
taskEmitter.refreshSeriesLocalArtwork(seriesId, priority = HIGH_PRIORITY)
|
||||
}
|
||||
|
||||
@Operation(summary = "Update series metadata", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PatchMapping("v1/series/{seriesId}/metadata")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
|
|
@ -725,7 +739,7 @@ class SeriesController(
|
|||
seriesRepository.findByIdOrNull(seriesId)?.let { eventPublisher.publishEvent(DomainEvent.SeriesUpdated(it)) }
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@Operation(description = "Mark all book for series as read")
|
||||
@Operation(summary = "Mark series as read", description = "Mark all book for series as read", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@PostMapping("v1/series/{seriesId}/read-progress")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markAsRead(
|
||||
|
|
@ -737,7 +751,7 @@ class SeriesController(
|
|||
seriesLifecycle.markReadProgressCompleted(seriesId, principal.user)
|
||||
}
|
||||
|
||||
@Operation(description = "Mark all book for series as unread")
|
||||
@Operation(summary = "Mark series as unread", description = "Mark all book for series as unread", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@DeleteMapping("v1/series/{seriesId}/read-progress")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markAsUnread(
|
||||
|
|
@ -749,6 +763,7 @@ class SeriesController(
|
|||
seriesLifecycle.deleteReadProgress(seriesId, principal.user)
|
||||
}
|
||||
|
||||
@Operation(summary = "Get series read progress (Mihon)", description = "Mihon specific, due to how read progress is handled in Mihon.", tags = [OpenApiConfiguration.TagNames.MIHON])
|
||||
@GetMapping("v2/series/{seriesId}/read-progress/tachiyomi")
|
||||
fun getReadProgressTachiyomiV2(
|
||||
@PathVariable seriesId: String,
|
||||
|
|
@ -759,6 +774,7 @@ class SeriesController(
|
|||
return readProgressDtoRepository.findProgressV2BySeries(seriesId, principal.user.id)
|
||||
}
|
||||
|
||||
@Operation(summary = "Update series read progress (Mihon)", description = "Mihon specific, due to how read progress is handled in Mihon.", tags = [OpenApiConfiguration.TagNames.MIHON])
|
||||
@PutMapping("v2/series/{seriesId}/read-progress/tachiyomi")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgressTachiyomiV2(
|
||||
|
|
@ -781,6 +797,7 @@ class SeriesController(
|
|||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "Download series", description = "Download the whole series as a ZIP file.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@GetMapping("v1/series/{seriesId}/file", produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
|
||||
@PreAuthorize("hasRole('FILE_DOWNLOAD')")
|
||||
fun getSeriesFile(
|
||||
|
|
@ -828,6 +845,7 @@ class SeriesController(
|
|||
.body(streamingResponse)
|
||||
}
|
||||
|
||||
@Operation(summary = "Delete series files", description = "Delete all of the series' books files on disk.", tags = [OpenApiConfiguration.TagNames.SERIES])
|
||||
@DeleteMapping("v1/series/{seriesId}/file")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import jakarta.validation.Valid
|
||||
import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider
|
||||
import org.gotson.komga.infrastructure.kobo.KepubConverter
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.web.WebServerEffectiveSettings
|
||||
import org.gotson.komga.interfaces.api.rest.dto.SettingMultiSource
|
||||
import org.gotson.komga.interfaces.api.rest.dto.SettingsDto
|
||||
|
|
@ -24,6 +28,7 @@ import kotlin.time.Duration.Companion.days
|
|||
@RestController
|
||||
@RequestMapping(value = ["api/v1/settings"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Tag(name = OpenApiConfiguration.TagNames.SERVER_SETTINGS)
|
||||
class SettingsController(
|
||||
private val komgaSettingsProvider: KomgaSettingsProvider,
|
||||
@Value("\${server.port:#{null}}") private val configServerPort: Int?,
|
||||
|
|
@ -32,6 +37,7 @@ class SettingsController(
|
|||
private val kepubConverter: KepubConverter,
|
||||
) {
|
||||
@GetMapping
|
||||
@Operation(summary = "Retrieve server settings")
|
||||
fun getSettings(): SettingsDto =
|
||||
SettingsDto(
|
||||
komgaSettingsProvider.deleteEmptyCollections,
|
||||
|
|
@ -48,8 +54,10 @@ class SettingsController(
|
|||
|
||||
@PatchMapping
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Update server settings", description = "You can omit fields you don't want to update")
|
||||
fun updateSettings(
|
||||
@Valid @RequestBody
|
||||
@Parameter(description = "Fields to update. You can omit fields you don't want to update.")
|
||||
newSettings: SettingsUpdateDto,
|
||||
) {
|
||||
newSettings.deleteEmptyCollections?.let { komgaSettingsProvider.deleteEmptyCollections = it }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.domain.persistence.SyncPointRepository
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
|
|
@ -13,11 +16,16 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/syncpoints", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.SYNCPOINTS)
|
||||
class SyncPointController(
|
||||
private val syncPointRepository: SyncPointRepository,
|
||||
) {
|
||||
@DeleteMapping("me")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(
|
||||
summary = "Delete all sync points",
|
||||
description = "If an API Key ID is passed, deletes only the sync points associated with that API Key. Deleting sync points will allow a Kobo to sync from scratch upon the next sync.",
|
||||
)
|
||||
fun deleteMySyncPointsByApiKey(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(required = false, name = "key_id") keyIds: Collection<String>?,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.application.tasks.TasksRepository
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
|
|
@ -11,11 +14,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
@RestController
|
||||
@RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@Tag(name = OpenApiConfiguration.TagNames.TASKS)
|
||||
class TaskController(
|
||||
private val tasksRepository: TasksRepository,
|
||||
) {
|
||||
@DeleteMapping("api/v1/tasks")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "Clear task queue", description = "Cancel all tasks queued")
|
||||
fun emptyTaskQueue(): Int = tasksRepository.deleteAllWithoutOwner()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package org.gotson.komga.interfaces.api.rest
|
|||
|
||||
import com.jakewharton.byteunits.BinaryByteUnit
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.gotson.komga.domain.model.CodedException
|
||||
import org.gotson.komga.domain.model.MediaNotReadyException
|
||||
import org.gotson.komga.domain.model.MediaProfile
|
||||
|
|
@ -9,6 +11,7 @@ import org.gotson.komga.domain.model.TransientBook
|
|||
import org.gotson.komga.domain.persistence.TransientBookRepository
|
||||
import org.gotson.komga.domain.service.BookAnalyzer
|
||||
import org.gotson.komga.domain.service.TransientBookLifecycle
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration
|
||||
import org.gotson.komga.infrastructure.web.getMediaTypeOrDefault
|
||||
import org.gotson.komga.infrastructure.web.toFilePath
|
||||
import org.gotson.komga.interfaces.api.rest.dto.PageDto
|
||||
|
|
@ -31,12 +34,14 @@ private val logger = KotlinLogging.logger {}
|
|||
@RestController
|
||||
@RequestMapping("api/v1/transient-books", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Tag(name = OpenApiConfiguration.TagNames.BOOK_IMPORT)
|
||||
class TransientBooksController(
|
||||
private val transientBookLifecycle: TransientBookLifecycle,
|
||||
private val transientBookRepository: TransientBookRepository,
|
||||
private val bookAnalyzer: BookAnalyzer,
|
||||
) {
|
||||
@PostMapping
|
||||
@Operation(summary = "Scan folder for transient books", description = "Scan provided folder for transient books.")
|
||||
fun scanForTransientBooks(
|
||||
@RequestBody request: ScanRequestDto,
|
||||
): List<TransientBookDto> =
|
||||
|
|
@ -50,6 +55,7 @@ class TransientBooksController(
|
|||
}
|
||||
|
||||
@PostMapping("{id}/analyze")
|
||||
@Operation(summary = "Analyze transient book")
|
||||
fun analyze(
|
||||
@PathVariable id: String,
|
||||
): TransientBookDto =
|
||||
|
|
@ -61,6 +67,7 @@ class TransientBooksController(
|
|||
value = ["{id}/pages/{pageNumber}"],
|
||||
produces = [MediaType.ALL_VALUE],
|
||||
)
|
||||
@Operation(summary = "Get transient book page")
|
||||
fun getSourcePage(
|
||||
@PathVariable id: String,
|
||||
@PathVariable pageNumber: Int,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import jakarta.validation.Valid
|
||||
import org.gotson.komga.domain.model.AgeRestriction
|
||||
|
|
@ -14,6 +15,7 @@ import org.gotson.komga.domain.persistence.LibraryRepository
|
|||
import org.gotson.komga.domain.service.KomgaUserLifecycle
|
||||
import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.OpenApiConfiguration.TagNames
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ApiKeyDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.ApiKeyRequestDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.AuthenticationActivityDto
|
||||
|
|
@ -59,12 +61,14 @@ class UserController(
|
|||
private val demo = env.activeProfiles.contains("demo")
|
||||
|
||||
@GetMapping("me")
|
||||
@Operation(summary = "Retrieve current user", tags = [TagNames.CURRENT_USER])
|
||||
fun getMe(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): UserDto = principal.toDto()
|
||||
|
||||
@PatchMapping("me/password")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Update current user's password", tags = [TagNames.CURRENT_USER])
|
||||
fun updateMyPassword(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@Valid @RequestBody
|
||||
|
|
@ -78,11 +82,13 @@ class UserController(
|
|||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "List users", tags = [TagNames.USERS])
|
||||
fun getAll(): List<UserDto> = userRepository.findAll().map { it.toDto() }
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "Create user", tags = [TagNames.USERS])
|
||||
fun addOne(
|
||||
@Valid @RequestBody
|
||||
newUser: UserCreationDto,
|
||||
|
|
@ -96,6 +102,7 @@ class UserController(
|
|||
@DeleteMapping("{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasRole('ADMIN') and #principal.user.id != #id")
|
||||
@Operation(summary = "Delete user", tags = [TagNames.USERS])
|
||||
fun delete(
|
||||
@PathVariable id: String,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -108,6 +115,7 @@ class UserController(
|
|||
@PatchMapping("{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasRole('ADMIN') and #principal.user.id != #id")
|
||||
@Operation(summary = "Update user", tags = [TagNames.USERS])
|
||||
fun updateUser(
|
||||
@PathVariable id: String,
|
||||
@Valid @RequestBody
|
||||
|
|
@ -162,6 +170,7 @@ class UserController(
|
|||
@PatchMapping("{id}/password")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PreAuthorize("hasRole('ADMIN') or #principal.user.id == #id")
|
||||
@Operation(summary = "Update user's password", tags = [TagNames.USERS])
|
||||
fun updatePassword(
|
||||
@PathVariable id: String,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -176,6 +185,7 @@ class UserController(
|
|||
|
||||
@GetMapping("me/authentication-activity")
|
||||
@PageableAsQueryParam
|
||||
@Operation(summary = "Retrieve authentication activity for the current user", tags = [TagNames.CURRENT_USER])
|
||||
fun getMyAuthenticationActivity(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||
|
|
@ -204,6 +214,7 @@ class UserController(
|
|||
@GetMapping("authentication-activity")
|
||||
@PageableAsQueryParam
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Operation(summary = "Retrieve authentication activity", tags = [TagNames.USERS])
|
||||
fun getAuthenticationActivity(
|
||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
|
|
@ -229,6 +240,7 @@ class UserController(
|
|||
|
||||
@GetMapping("{id}/authentication-activity/latest")
|
||||
@PreAuthorize("hasRole('ADMIN') or #principal.user.id == #id")
|
||||
@Operation(summary = "Retrieve latest authentication activity for a user", tags = [TagNames.USERS])
|
||||
fun getLatestAuthenticationActivityForUser(
|
||||
@PathVariable id: String,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
|
|
@ -240,6 +252,7 @@ class UserController(
|
|||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@GetMapping("me/api-keys")
|
||||
@Operation(summary = "Retrieve API keys", tags = [TagNames.API_KEYS])
|
||||
fun getApiKeys(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): Collection<ApiKeyDto> {
|
||||
|
|
@ -248,6 +261,7 @@ class UserController(
|
|||
}
|
||||
|
||||
@PostMapping("me/api-keys")
|
||||
@Operation(summary = "Create API key", tags = [TagNames.API_KEYS])
|
||||
fun createApiKey(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@Valid @RequestBody apiKeyRequest: ApiKeyRequestDto,
|
||||
|
|
@ -263,6 +277,7 @@ class UserController(
|
|||
|
||||
@DeleteMapping("me/api-keys/{keyId}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Delete API key", tags = [TagNames.API_KEYS])
|
||||
fun deleteApiKey(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@PathVariable keyId: String,
|
||||
|
|
|
|||
|
|
@ -94,8 +94,6 @@ management:
|
|||
step: 24h
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
groups-order: desc
|
||||
operations-sorter: alpha
|
||||
disable-swagger-default-url: true
|
||||
tags-sorter: alpha
|
||||
paths-to-match: "/api/**"
|
||||
writer-with-order-by-keys: true
|
||||
|
|
|
|||
Loading…
Reference in a new issue