From 42892aa2854d6fe1e998a4f6e530d465dafb367f Mon Sep 17 00:00:00 2001 From: Simon Weis Date: Sun, 26 Nov 2023 16:21:18 +0100 Subject: [PATCH] Export api documentation --- build.gradle.kts | 4 ++- .../kotlin/de/w3is/recipes/Application.kt | 29 +++++++++++++++---- .../images/infra/api/ImagesController.kt | 10 +++++-- .../importer/infra/api/ImportController.kt | 4 +++ .../recipes/infra/api/RecipeController.kt | 12 ++++++++ .../infra/api/RecipeSearchController.kt | 4 +++ .../users/infra/api/InvitationController.kt | 10 +++++++ .../users/infra/api/ProfileController.kt | 6 ++++ src/main/resources/application.yml | 6 ++++ 9 files changed, 77 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6925352..d2e86e4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,9 +35,11 @@ repositories { } dependencies { + compileOnly("io.micronaut.openapi:micronaut-openapi-annotations") jooqGenerator("org.jooq:jooq-meta-extensions:$jooqVersion") ksp("io.micronaut.serde:micronaut-serde-processor") + ksp("io.micronaut.openapi:micronaut-openapi") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") @@ -78,7 +80,7 @@ node { } application { - mainClass.set("de.w3is.recipes.ApplicationKt") + mainClass.set("de.w3is.recipes.Application") } tasks { diff --git a/src/main/kotlin/de/w3is/recipes/Application.kt b/src/main/kotlin/de/w3is/recipes/Application.kt index e63bd2c..9413f89 100644 --- a/src/main/kotlin/de/w3is/recipes/Application.kt +++ b/src/main/kotlin/de/w3is/recipes/Application.kt @@ -1,10 +1,29 @@ package de.w3is.recipes import io.micronaut.runtime.Micronaut.build +import io.swagger.v3.oas.annotations.OpenAPIDefinition +import io.swagger.v3.oas.annotations.info.Info +import io.swagger.v3.oas.annotations.info.License -fun main(args: Array) { - build() - .args(*args) - .packages("de.w3is.recipes") - .start() +@OpenAPIDefinition( + info = Info( + title = "Salt and Pepper", + version = "1.0.0", + description = "Salt and Pepper Rest API", + license = License( + name = "GPL-3.0", + url = "https://www.gnu.org/licenses/gpl-3.0.en.html", + ), + ), +) +class Application { + companion object { + @JvmStatic + fun main(args: Array) { + build() + .args(*args) + .packages("de.w3is.recipes") + .start() + } + } } diff --git a/src/main/kotlin/de/w3is/recipes/images/infra/api/ImagesController.kt b/src/main/kotlin/de/w3is/recipes/images/infra/api/ImagesController.kt index 5aca30d..01542ef 100644 --- a/src/main/kotlin/de/w3is/recipes/images/infra/api/ImagesController.kt +++ b/src/main/kotlin/de/w3is/recipes/images/infra/api/ImagesController.kt @@ -10,17 +10,23 @@ import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.PathVariable import io.micronaut.security.annotation.Secured import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag @Controller("/api/images") @Secured(SecurityRule.IS_ANONYMOUS) class ImagesController(private val imageRepository: ImageRepository) { @Get("/{id}", produces = [MediaType.IMAGE_PNG]) - fun getImage(@PathVariable id: String): HttpResponse = + @Operation(summary = "Get image by id", operationId = "getImage") + @Tag(name = "images") + fun getImage(@PathVariable("id") id: String): HttpResponse = HttpResponse.ok(imageRepository.get(ImageId(id)).readAllBytes()).cacheControl(31536000) @Get("/{id}/thumbnail", produces = [MediaType.IMAGE_PNG]) - fun getThumbnail(@PathVariable id: String): HttpResponse = + @Operation(summary = "Get thumbnail by id", operationId = "getThumbnail") + @Tag(name = "images") + fun getThumbnail(@PathVariable("id") id: String): HttpResponse = HttpResponse.ok(imageRepository.getThumbnail(ImageId(id)).readAllBytes()).cacheControl(31536000) } diff --git a/src/main/kotlin/de/w3is/recipes/importer/infra/api/ImportController.kt b/src/main/kotlin/de/w3is/recipes/importer/infra/api/ImportController.kt index eb80e24..a15d3f5 100644 --- a/src/main/kotlin/de/w3is/recipes/importer/infra/api/ImportController.kt +++ b/src/main/kotlin/de/w3is/recipes/importer/infra/api/ImportController.kt @@ -12,6 +12,8 @@ import io.micronaut.http.multipart.StreamingFileUpload import io.micronaut.security.annotation.Secured import io.micronaut.security.authentication.Authentication import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers import java.io.File @@ -23,6 +25,8 @@ class ImportController( private val importService: ImportService, ) { @Post("/gourmet", consumes = [MediaType.MULTIPART_FORM_DATA]) + @Operation(summary = "Import XML files, exported by gourmet", operationId = "importGourmetXml") + @Tag(name = "import") fun importGourmetXml( file: StreamingFileUpload, authentication: Authentication, diff --git a/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeController.kt b/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeController.kt index 529d7a4..3dc3d41 100644 --- a/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeController.kt +++ b/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeController.kt @@ -23,6 +23,8 @@ import io.micronaut.http.multipart.StreamingFileUpload import io.micronaut.security.annotation.Secured import io.micronaut.security.authentication.Authentication import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag import reactor.core.publisher.Mono import reactor.core.scheduler.Schedulers import java.io.File @@ -36,6 +38,8 @@ class RecipeController( ) { @Post("/") + @Operation(summary = "Store new recipe", operationId = "storeRecipe") + @Tag(name = "recipes") fun storeRecipe(@Body request: NewRecipeRequest, authentication: Authentication): NewRecipeResponse { val user = with(userService) { authentication.getUser() } val recipe = recipeService.createNewRecipe( @@ -56,17 +60,23 @@ class RecipeController( } @Get("/{id}") + @Operation(summary = "Get recipe by id", operationId = "getRecipe") + @Tag(name = "recipes") fun getRecipe(@PathVariable("id") recipeId: String): RecipeViewModel { return recipeService.get(RecipeId(recipeId)).toModel() } @Delete("/{id}") + @Operation(summary = "Delete recipe by id", operationId = "deleteRecipe") + @Tag(name = "recipes") fun deleteRecipe(@PathVariable("id") recipeId: String, authentication: Authentication) { val user = with(userService) { authentication.getUser() } recipeService.deleteRecipe(RecipeId(recipeId), user) } @Put("/{id}") + @Operation(summary = "Update recipe", operationId = "updateRecipe") + @Tag(name = "recipes") fun updateRecipe( @PathVariable("id") recipeId: String, @Body request: RecipeViewModel, @@ -87,6 +97,8 @@ class RecipeController( } @Post("/{id}/images", consumes = [MediaType.MULTIPART_FORM_DATA]) + @Operation(summary = "Add image to recipe", operationId = "addImageToRecipe") + @Tag(name = "recipes") fun addImageToRecipe( @PathVariable("id") recipeId: String, file: StreamingFileUpload, diff --git a/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeSearchController.kt b/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeSearchController.kt index 8326907..81f53d4 100644 --- a/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeSearchController.kt +++ b/src/main/kotlin/de/w3is/recipes/recipes/infra/api/RecipeSearchController.kt @@ -12,6 +12,8 @@ import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import io.micronaut.security.annotation.Secured import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag @Controller("/api/recipe/search") @Secured(SecurityRule.IS_AUTHENTICATED) @@ -21,6 +23,8 @@ class RecipeSearchController( ) { @Post + @Operation(summary = "Search recipes", operationId = "searchRecipes") + @Tag(name = "recipes") fun search(@Body searchRequestViewModel: SearchRequestViewModel): SearchResponseViewModel { val searchResponse = recipeRepository.search(searchRequestViewModel.toSearchRequest()) val possibleFilter = searchResponse.possibleFilter.toViewModel() diff --git a/src/main/kotlin/de/w3is/recipes/users/infra/api/InvitationController.kt b/src/main/kotlin/de/w3is/recipes/users/infra/api/InvitationController.kt index 75dbb9b..5823880 100644 --- a/src/main/kotlin/de/w3is/recipes/users/infra/api/InvitationController.kt +++ b/src/main/kotlin/de/w3is/recipes/users/infra/api/InvitationController.kt @@ -14,6 +14,8 @@ import io.micronaut.http.annotation.Put import io.micronaut.security.annotation.Secured import io.micronaut.security.authentication.Authentication import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Singleton @Singleton @@ -25,6 +27,8 @@ class InvitationController( @Get @Secured(SecurityRule.IS_AUTHENTICATED) + @Operation(summary = "Get own invite code if any", operationId = "getExistingInviteCode") + @Tag(name = "invitations") fun getExistingInviteCode( authentication: Authentication, ): HttpResponse { @@ -40,6 +44,8 @@ class InvitationController( @Post @Secured(SecurityRule.IS_AUTHENTICATED) + @Operation(summary = "Create new invite link", operationId = "createInviteLink") + @Tag(name = "invitations") fun createInviteLink( authentication: Authentication, ): InvitationCodeResponse { @@ -51,6 +57,8 @@ class InvitationController( @Get("/{code}") @Secured(SecurityRule.IS_ANONYMOUS) + @Operation(summary = "Get invite information by code", operationId = "getInvitationInfo") + @Tag(name = "invitations") fun getInvitationInfo(@PathVariable("code") inviteCode: String): InvitationInfoResponse { val invite = invitationService.getInviteByCode(inviteCode) val invitingUser = userService.getUser(invite.creator) @@ -61,6 +69,8 @@ class InvitationController( @Put("/{code}") @Secured(SecurityRule.IS_ANONYMOUS) + @Operation(summary = "Create new user by invitation code", operationId = "useInvitation") + @Tag(name = "invitations") fun useInvitation( @PathVariable("code") inviteCode: String, @Body invitationRequest: InvitationRequest, diff --git a/src/main/kotlin/de/w3is/recipes/users/infra/api/ProfileController.kt b/src/main/kotlin/de/w3is/recipes/users/infra/api/ProfileController.kt index 91b5215..6cadc40 100644 --- a/src/main/kotlin/de/w3is/recipes/users/infra/api/ProfileController.kt +++ b/src/main/kotlin/de/w3is/recipes/users/infra/api/ProfileController.kt @@ -15,6 +15,8 @@ import io.micronaut.http.annotation.Patch import io.micronaut.security.annotation.Secured import io.micronaut.security.authentication.Authentication import io.micronaut.security.rules.SecurityRule +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.inject.Singleton @Singleton @@ -26,6 +28,8 @@ class ProfileController( ) { @Get + @Operation(summary = "Get own profile", operationId = "getProfile") + @Tag(name = "profile") fun getProfile(authentication: Authentication): Profile { val user = with(userService) { authentication.getUser() } @@ -40,6 +44,8 @@ class ProfileController( } @Patch("/password") + @Operation(summary = "Change own password", operationId = "changePassword") + @Tag(name = "profile") fun changePassword(@Body request: ChangePasswordRequest, authentication: Authentication): HttpResponse { val user = with(userService) { authentication.getUser() } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0b58be4..6205fc8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,6 +14,9 @@ micronaut: index: paths: "classpath:/public/index.html" mapping: "/" + swagger: + paths: "classpath:META-INF/swagger" + mapping: "/swagger/**" default: paths: "classpath:public" mapping: "/**" @@ -58,6 +61,9 @@ micronaut: http-method: GET access: - isAnonymous() + - pattern: /swagger/** + access: + - isAnonymous() reject-not-found: true