Skip to content

Commit

Permalink
10. OpenAPI и Swagger - документирование REST API [#505117]
Browse files Browse the repository at this point in the history
  • Loading branch information
vadimstrya committed Jan 28, 2025
1 parent 9fc10d2 commit 07b52a1
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 2 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.3</version>
</dependency>

<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/ru/job4j/api/config/OpenAPIConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ru.job4j.api.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.servers.Server;

@Configuration
public class OpenAPIConfig {

@Value("${socialmedia.openapi.dev-url}")
private String devUrl;

@Value("${socialmedia.openapi.prod-url}")
private String prodUrl;

@Bean
public OpenAPI myOpenAPI() {
Server devServer = new Server();
devServer.setUrl(devUrl);
devServer.setDescription("Server URL in Development environment");

Server prodServer = new Server();
prodServer.setUrl(prodUrl);
prodServer.setDescription("Server URL in Production environment");

Contact contact = new Contact();
contact.setEmail("[email protected]");
contact.setName("MyName");
contact.setUrl("https://www.mydomen.com");

License mitLicense = new License().name("MIT License").url("https://choosealicense.com/licenses/mit/");

Info info = new Info()
.title("SocialMedia Management API")
.version("1.0")
.contact(contact)
.description("This API exposes endpoints to manage project.").termsOfService("https://mydomen.com")
.license(mitLicense);

return new OpenAPI().info(info).servers(List.of(devServer, prodServer));
}
}
53 changes: 53 additions & 0 deletions src/main/java/ru/job4j/api/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package ru.job4j.api.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
Expand All @@ -10,6 +16,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import ru.job4j.api.dto.response.PostDto;
import ru.job4j.api.entity.User;
import ru.job4j.api.entity.UserPost;
import ru.job4j.api.service.PostService;

Expand All @@ -19,17 +26,36 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
@Tag(name = "PostController", description = "PostController management APIs")
public class PostController {

private final PostService postService;

@Operation(
summary = "Получение UserPost по postId",
description = "Получите пост пользователя, указав его ID. Ответ - это объект с ID поста, заголовком, текстом и датой создания",
tags = {"UserPost", "get"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = UserPost.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@GetMapping("/{postId}")
public ResponseEntity<UserPost> get(@PathVariable @NotNull @Min(value = 1, message = "postId должен быть больше нуля") Long postId) {
return postService.getById(postId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

@Operation(
summary = "Создание UserPost",
description = "Создайте пост пользователя. Ответ - это объект с ID поста, заголовком, текстом и датой создания",
tags = {"UserPost", "post"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = UserPost.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@PostMapping
public ResponseEntity<UserPost> save(@Valid @RequestBody UserPost post) {
postService.create(post);
Expand All @@ -43,6 +69,15 @@ public ResponseEntity<UserPost> save(@Valid @RequestBody UserPost post) {
.body(post);
}

@Operation(
summary = "Обновление UserPost",
description = "Обновите пост пользователя",
tags = {"UserPost", "put"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = UserPost.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@PutMapping
public ResponseEntity<Void> update(@Valid @RequestBody UserPost post) {
if (postService.update(post)) {
Expand All @@ -51,6 +86,15 @@ public ResponseEntity<Void> update(@Valid @RequestBody UserPost post) {
return ResponseEntity.notFound().build();
}

@Operation(
summary = "Удаление UserPost по postId",
description = "Удалите пост пользователя, указав его ID",
tags = {"UserPost", "delete"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = UserPost.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@DeleteMapping("/{postId}")
public ResponseEntity<Void> delete(@PathVariable @NotNull @Min(value = 1, message = "postId должен быть больше нуля") Long postId) {
if (postService.deleteById(postId)) {
Expand All @@ -59,6 +103,15 @@ public ResponseEntity<Void> delete(@PathVariable @NotNull @Min(value = 1, messag
return ResponseEntity.notFound().build();
}

@Operation(
summary = "Получение списка UserPost по списку postId",
description = "Получите список постов пользователя, указав список их ID. Ответ - это список с объектами с ID поста, заголовком, текстом и датой создания",
tags = {"UserPost", "get"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(allOf = {UserPost.class}), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@GetMapping("/getPosts")
public List<PostDto> getPosts(@RequestBody List<Long> userIdList) {
return postService.getPosts(userIdList);
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/ru/job4j/api/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package ru.job4j.api.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
Expand All @@ -16,17 +22,36 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
@Tag(name = "UserController", description = "UserController management APIs")
public class UserController {

private final UserService userService;

@Operation(
summary = "Получение User по userId",
description = "Получите пользователя, указав его ID. Ответ - это объект с ID пользователя, именем и датой создания",
tags = {"User", "get"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = User.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "404", content = {@Content(schema = @Schema())})
})
@GetMapping("/{userId}")
public ResponseEntity<User> get(@PathVariable @NotNull @Min(value = 1, message = "userId должен быть больше нуля") Long userId) {
return userService.getById(userId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

@Operation(
summary = "Создание User",
description = "Создайте пользователя. Ответ - это объект с ID пользователя, именем и датой создания",
tags = {"User", "post"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = User.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "400", content = {@Content(schema = @Schema())})
})
@PostMapping
public ResponseEntity<User> save(@Valid @RequestBody User user) {
userService.create(user);
Expand All @@ -40,6 +65,15 @@ public ResponseEntity<User> save(@Valid @RequestBody User user) {
.body(user);
}

@Operation(
summary = "Обновление User",
description = "Обновите пользователя",
tags = {"User", "put"}
)
@ApiResponses({
@ApiResponse(responseCode = "200", content = {@Content(schema = @Schema(implementation = User.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "404", content = {@Content(schema = @Schema())})
})
@PutMapping
public ResponseEntity<Void> update(@Valid @RequestBody User user) {
if (userService.update(user)) {
Expand All @@ -48,6 +82,15 @@ public ResponseEntity<Void> update(@Valid @RequestBody User user) {
return ResponseEntity.notFound().build();
}

@Operation(
summary = "Удаление User по userId",
description = "Удалите пользователя, указав его ID",
tags = {"User", "delete"}
)
@ApiResponses({
@ApiResponse(responseCode = "204", content = {@Content(schema = @Schema(implementation = User.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "404", content = {@Content(schema = @Schema())})
})
@DeleteMapping("/{userId}")
public ResponseEntity<Void> delete(@PathVariable @NotNull @Min(value = 1, message = "userId должен быть больше нуля") Long userId) {
if (userService.deleteById(userId)) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/ru/job4j/api/dto/response/PostDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.job4j.api.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import ru.job4j.api.entity.UserPost;
Expand All @@ -8,6 +9,7 @@

@Getter
@Setter
@Schema(description = "PostDto Model Information")
public class PostDto {

private Long userId;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/ru/job4j/api/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.job4j.api.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
Expand All @@ -12,6 +13,7 @@
/** Пользователь */
@Getter
@Setter
@Schema(description = "User Model Information")
@Entity(name = "ru.job4j.api.entity.User")
@Table(schema = "public", name = "USER_AUTH")
public class User {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/ru/job4j/api/entity/UserPost.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.job4j.api.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
Expand All @@ -13,6 +14,7 @@
/** Пост пользователя */
@Getter
@Setter
@Schema(description = "UserPost Model Information")
@Entity(name = "ru.job4j.api.entity.UserPost")
@Table(schema = "public", name = "USER_POST")
public class UserPost {
Expand Down
7 changes: 6 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres

spring.liquibase.change-log=classpath:db/liquibase-changeLog.xml
spring.liquibase.change-log=classpath:db/liquibase-changeLog.xml

socialmedia.openapi.dev-url=http://localhost:8080
socialmedia.openapi.prod-url=https://mydomen.com

springdoc.swagger-ui.tryItOutEnabled=true
5 changes: 4 additions & 1 deletion src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=
spring.datasource.password=

spring.liquibase.change-log=classpath:db/liquibase-changeLog.xml
spring.liquibase.change-log=classpath:db/liquibase-changeLog.xml

socialmedia.openapi.dev-url=http://localhost:8080
socialmedia.openapi.prod-url=https://mydomen.com

0 comments on commit 07b52a1

Please sign in to comment.