Skip to content

feature/api-documentation- Add OpenAPI (Swagger) UI for API Documentation in Spring Boot Application #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.8.0</version>
</dependency>


</dependencies>

<build>
Expand Down
45 changes: 28 additions & 17 deletions src/main/java/com/sopromadze/blogapi/BlogApiApplication.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.sopromadze.blogapi;

import com.sopromadze.blogapi.security.JwtAuthenticationFilter;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.modelmapper.ModelMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -11,28 +13,37 @@
import javax.annotation.PostConstruct;
import java.util.TimeZone;

@OpenAPIDefinition(
info = @Info(
title = "Spring-Boot-Blog-REST-API",
description = "A RESTful webservice for managing blog posts, built with Spring Boot. Includes JWT-based authentication and MySQL for data storage.",
summary = "Build Restful CRUD API for a blog using Spring Boot, Mysql, JPA and Hibernate.",
version = "1.0.1"
)
)

@SpringBootApplication
@EntityScan(basePackageClasses = { BlogApiApplication.class, Jsr310Converters.class })
@EntityScan(basePackageClasses = {BlogApiApplication.class, Jsr310Converters.class})

public class BlogApiApplication {

public static void main(String[] args) {
SpringApplication.run(BlogApiApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(BlogApiApplication.class, args);
}

@PostConstruct
void init() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
@PostConstruct
void init() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}

@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected void configure(HttpSecurity http) throws Exception {
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/**").permitAll()
.antMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.antMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/users/checkUsernameAvailability", "/api/users/checkEmailAvailability").permitAll()
.anyRequest().authenticated();
Expand Down
116 changes: 63 additions & 53 deletions src/main/java/com/sopromadze/blogapi/controller/AlbumController.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import com.sopromadze.blogapi.service.PhotoService;
import com.sopromadze.blogapi.utils.AppConstants;
import com.sopromadze.blogapi.utils.AppUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -30,61 +32,69 @@

import javax.validation.Valid;

@Tag(name = "5- Albums", description = "Operations related to albums")

@RestController
@RequestMapping("/api/albums")
public class AlbumController {
@Autowired
private AlbumService albumService;

@Autowired
private PhotoService photoService;

@ExceptionHandler(ResponseEntityErrorException.class)
public ResponseEntity<ApiResponse> handleExceptions(ResponseEntityErrorException exception) {
return exception.getApiResponse();
}

@GetMapping
public PagedResponse<AlbumResponse> getAllAlbums(
@RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page,
@RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) {
AppUtils.validatePageNumberAndSize(page, size);

return albumService.getAllAlbums(page, size);
}

@PostMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Album> addAlbum(@Valid @RequestBody AlbumRequest albumRequest, @CurrentUser UserPrincipal currentUser) {
return albumService.addAlbum(albumRequest, currentUser);
}

@GetMapping("/{id}")
public ResponseEntity<Album> getAlbum(@PathVariable(name = "id") Long id) {
return albumService.getAlbum(id);
}

@PutMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public ResponseEntity<AlbumResponse> updateAlbum(@PathVariable(name = "id") Long id, @Valid @RequestBody AlbumRequest newAlbum,
@CurrentUser UserPrincipal currentUser) {
return albumService.updateAlbum(id, newAlbum, currentUser);
}

@DeleteMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public ResponseEntity<ApiResponse> deleteAlbum(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) {
return albumService.deleteAlbum(id, currentUser);
}

@GetMapping("/{id}/photos")
public ResponseEntity<PagedResponse<PhotoResponse>> getAllPhotosByAlbum(@PathVariable(name = "id") Long id,
@RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page,
@RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) {

PagedResponse<PhotoResponse> response = photoService.getAllPhotosByAlbum(id, page, size);

return new ResponseEntity<>(response, HttpStatus.OK);
}
@Autowired
private AlbumService albumService;

@Autowired
private PhotoService photoService;

@ExceptionHandler(ResponseEntityErrorException.class)
public ResponseEntity<ApiResponse> handleExceptions(ResponseEntityErrorException exception) {
return exception.getApiResponse();
}

@Operation(description = "Get all albums", summary = "Get albums")
@GetMapping
public PagedResponse<AlbumResponse> getAllAlbums(
@RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page,
@RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) {
AppUtils.validatePageNumberAndSize(page, size);

return albumService.getAllAlbums(page, size);
}

@Operation(description = "Create new album (By logged in user)", summary = "Create album")
@PostMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<Album> addAlbum(@Valid @RequestBody AlbumRequest albumRequest, @CurrentUser UserPrincipal currentUser) {
return albumService.addAlbum(albumRequest, currentUser);
}

@Operation(description = "Get album by id", summary = "Get album")
@GetMapping("/{id}")
public ResponseEntity<Album> getAlbum(@PathVariable(name = "id") Long id) {
return albumService.getAlbum(id);
}

@Operation(description = "Update album (If album belongs to logged in user or logged in user is admin)", summary = "Update album")
@PutMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public ResponseEntity<AlbumResponse> updateAlbum(@PathVariable(name = "id") Long id, @Valid @RequestBody AlbumRequest newAlbum,
@CurrentUser UserPrincipal currentUser) {
return albumService.updateAlbum(id, newAlbum, currentUser);
}

@Operation(description = "Delete album (If album belongs to logged in user or logged in user is admin)", summary = "Delete album")
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public ResponseEntity<ApiResponse> deleteAlbum(@PathVariable(name = "id") Long id, @CurrentUser UserPrincipal currentUser) {
return albumService.deleteAlbum(id, currentUser);
}

@Operation(description = "Get all photos which belongs to album with id = id", summary = "Get photos")
@GetMapping("/{id}/photos")
public ResponseEntity<PagedResponse<PhotoResponse>> getAllPhotosByAlbum(@PathVariable(name = "id") Long id,
@RequestParam(name = "page", required = false, defaultValue = AppConstants.DEFAULT_PAGE_NUMBER) Integer page,
@RequestParam(name = "size", required = false, defaultValue = AppConstants.DEFAULT_PAGE_SIZE) Integer size) {

PagedResponse<PhotoResponse> response = photoService.getAllPhotosByAlbum(id, page, size);

return new ResponseEntity<>(response, HttpStatus.OK);
}

}
104 changes: 55 additions & 49 deletions src/main/java/com/sopromadze/blogapi/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import com.sopromadze.blogapi.repository.RoleRepository;
import com.sopromadze.blogapi.repository.UserRepository;
import com.sopromadze.blogapi.security.JwtTokenProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -31,78 +33,82 @@
import java.util.ArrayList;
import java.util.List;

@Tag(name = "1- Auth", description = "Operations related to sign in and sign up")

@RestController
@RequestMapping("/api/auth")
public class AuthController {
private static final String USER_ROLE_NOT_SET = "User role not set";
private static final String USER_ROLE_NOT_SET = "User role not set";

@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private UserRepository userRepository;
@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;
@Autowired
private RoleRepository roleRepository;

@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private JwtTokenProvider jwtTokenProvider;

@PostMapping("/signin")
public ResponseEntity<JwtAuthenticationResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword()));
@Operation(description = "Log in", summary = "Log in / sign in")
@PostMapping("/signin")
public ResponseEntity<JwtAuthenticationResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(), loginRequest.getPassword()));

SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);

String jwt = jwtTokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
String jwt = jwtTokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}

@PostMapping("/signup")
public ResponseEntity<ApiResponse> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
if (Boolean.TRUE.equals(userRepository.existsByUsername(signUpRequest.getUsername()))) {
throw new BlogapiException(HttpStatus.BAD_REQUEST, "Username is already taken");
}
@Operation(description = "Sign up", summary = "Sign up")
@PostMapping("/signup")
public ResponseEntity<ApiResponse> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
if (Boolean.TRUE.equals(userRepository.existsByUsername(signUpRequest.getUsername()))) {
throw new BlogapiException(HttpStatus.BAD_REQUEST, "Username is already taken");
}

if (Boolean.TRUE.equals(userRepository.existsByEmail(signUpRequest.getEmail()))) {
throw new BlogapiException(HttpStatus.BAD_REQUEST, "Email is already taken");
}
if (Boolean.TRUE.equals(userRepository.existsByEmail(signUpRequest.getEmail()))) {
throw new BlogapiException(HttpStatus.BAD_REQUEST, "Email is already taken");
}

String firstName = signUpRequest.getFirstName().toLowerCase();
String firstName = signUpRequest.getFirstName().toLowerCase();

String lastName = signUpRequest.getLastName().toLowerCase();
String lastName = signUpRequest.getLastName().toLowerCase();

String username = signUpRequest.getUsername().toLowerCase();
String username = signUpRequest.getUsername().toLowerCase();

String email = signUpRequest.getEmail().toLowerCase();
String email = signUpRequest.getEmail().toLowerCase();

String password = passwordEncoder.encode(signUpRequest.getPassword());
String password = passwordEncoder.encode(signUpRequest.getPassword());

User user = new User(firstName, lastName, username, email, password);
User user = new User(firstName, lastName, username, email, password);

List<Role> roles = new ArrayList<>();
List<Role> roles = new ArrayList<>();

if (userRepository.count() == 0) {
roles.add(roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
} else {
roles.add(roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
}
if (userRepository.count() == 0) {
roles.add(roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
roles.add(roleRepository.findByName(RoleName.ROLE_ADMIN)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
} else {
roles.add(roleRepository.findByName(RoleName.ROLE_USER)
.orElseThrow(() -> new AppException(USER_ROLE_NOT_SET)));
}

user.setRoles(roles);
user.setRoles(roles);

User result = userRepository.save(user);
User result = userRepository.save(user);

URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{userId}")
.buildAndExpand(result.getId()).toUri();
URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/{userId}")
.buildAndExpand(result.getId()).toUri();

return ResponseEntity.created(location).body(new ApiResponse(Boolean.TRUE, "User registered successfully"));
}
return ResponseEntity.created(location).body(new ApiResponse(Boolean.TRUE, "User registered successfully"));
}
}
Loading