Skip to content

Commit

Permalink
Document paths with OpenApi Swagger
Browse files Browse the repository at this point in the history
Signed-off-by: Mykhailo Marchuk <[email protected]>
  • Loading branch information
marchuk-engineer committed May 29, 2024
1 parent 0d84e8e commit 95b41ae
Show file tree
Hide file tree
Showing 60 changed files with 859 additions and 770 deletions.
55 changes: 55 additions & 0 deletions src/main/java/com/booking/app/annotation/GlobalApiResponses.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.booking.app.annotation;

import com.booking.app.exception.ErrorDetails;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.MediaType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Custom annotation to define global API responses for server errors.
* <p>
* This annotation can be applied to both classes and methods to include
* standard responses for various 5xx server error status codes in the Swagger documentation.
*
* <p>It defines the following responses:
* <ul>
* <li>500 Internal Server Error</li>
* <li>501 Not Implemented</li>
* <li>502 Bad Gateway</li>
* <li>503 Service Unavailable</li>
* <li>504 Gateway Timeout</li>
* </ul>
*
* <p>Example usage:
* <pre>
* {@code
* @RestController
* @GlobalApiResponses
* public class MyController {
*
* @GetMapping("/example")
* public String exampleEndpoint() {
* return "Example response";
* }
* }
* }
* </pre>
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponses(value = {
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorDetails.class))),
@ApiResponse(responseCode = "501", description = "Not Implemented", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorDetails.class))),
@ApiResponse(responseCode = "502", description = "Bad Gateway", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorDetails.class))),
@ApiResponse(responseCode = "503", description = "Service Unavailable", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorDetails.class))),
@ApiResponse(responseCode = "504", description = "Gateway Timeout", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ErrorDetails.class)))
})
public @interface GlobalApiResponses {
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.logout(AbstractHttpConfigurer::disable);
http.cors(Customizer.withDefaults());

http.authorizeHttpRequests(request -> request.requestMatchers(PUBLIC_PATHS).permitAll().anyRequest().authenticated());
// http.authorizeHttpRequests(request -> request.requestMatchers(PUBLIC_PATHS).permitAll()
http.authorizeHttpRequests(t -> t.anyRequest().permitAll());

http.addFilterBefore(jwtAuthenticationFilter, ExceptionTranslationFilter.class);
http.exceptionHandling(ex -> ex.authenticationEntryPoint(new RestAuthenticationEntryPoint()));
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/booking/app/constant/ApiMessagesConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.booking.app.constant;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ApiMessagesConstants {
public static final String USER_HAS_BEEN_DELETED_MESSSAGE = "User was deleted successfully";
public static final String UNAUTHENTICATED_MESSAGE = "Full authentication is required to access this resource";
public static final String INVALID_CLIENT_PROVIDER_ID_MESSAGE = "Client ID is not right";
public static final String USER_HAS_BEEN_AUTHENTICATED_MESSAGE = "User has been authenticated";
public static final String NO_SOCIAL_IDENTITY_PROVIDERS_MESSAGE = "Illegal Social Identity Providers was chosen";
public static final String INVALID_REQUEST_BODY_FORMAT_OR_VALIDATION_ERROR_MESSAGE = "Invalid request body format OR validation error";
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public class PasswordConstantMessages {
public static final String MESSAGE_PASSWORD_HAS_BEEN_SUCCESSFULLY_RESET = "Password has been successfully reset";
public static final String MESSAGE_NEW_PASSWORD_HAS_BEEN_CREATED = "New password has been created";
public static final String MESSAGE_CURRENT_PASSWORD_IS_NOT_RIGHT = "The passed current password is not right.";
public static final String THE_SPECIFIED_EMAIL_IS_NOT_REGISTERED_OR_THE_ACCOUNT_IS_DISABLED = "The specified email is not registered or the account is disabled";
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegistrationConstantMessages {
public static final String EMAIL_IS_ALREADY_TAKEN_MESSAGE = "We’re sorry. This email already exists.";
public static final String EMAIL_IS_ALREADY_TAKEN_MESSAGE = "We’re sorry. This email already taken.";
public static final String USER_REGISTERED_SUCCESSFULLY_MESSAGE = "User registered successfully.";
public static final String CODE_IS_NOT_RIGHT_MESSAGE = "Code is not right";
public static final String USER_IS_VERIFIED_MESSAGE = "User is verified";
public static final String CONFIRM_CODE_HAS_BEEN_SENT_ONE_MORE_TIME_MESSAGE = "Confirm token has been sent one more time";
public static final String CONFIRM_CODE_HAS_BEEN_SENT_ONE_MORE_TIME_MESSAGE = "Confirm token has been sent";
public static final String THE_SPECIFIED_EMAIL_IS_NOT_REGISTERED_MESSAGE = "The specified email is not registered";
}

28 changes: 28 additions & 0 deletions src/main/java/com/booking/app/controller/CookieManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.booking.app.controller;

import com.booking.app.util.CookieUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import static com.booking.app.constant.CustomHttpHeaders.REMEMBER_ME;
import static com.booking.app.constant.CustomHttpHeaders.USER_ID;
import static com.booking.app.constant.JwtTokenConstants.REFRESH_TOKEN;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CookieManager {

/**
* Deletes cookies related to user authentication.
*
* @param request the HTTP request
* @param response the HTTP response
*/
public static void deleteCookies(HttpServletRequest request, HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, REFRESH_TOKEN);
CookieUtils.deleteCookie(request, response, USER_ID);
CookieUtils.deleteCookie(request, response, REMEMBER_ME);
}

}
87 changes: 44 additions & 43 deletions src/main/java/com/booking/app/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.booking.app.controller;

import com.booking.app.dto.AuthorizedUserDTO;
import com.booking.app.dto.LoginDTO;
import com.booking.app.dto.SocialSignInRequestDto;
import com.booking.app.annotation.GlobalApiResponses;
import com.booking.app.constant.ApiMessagesConstants;
import com.booking.app.dto.AuthenticatedUserDto;
import com.booking.app.dto.LoginDto;
import com.booking.app.dto.SocialLoginDto;
import com.booking.app.enums.SocialProvider;
import com.booking.app.exception.ErrorDetails;
import com.booking.app.services.LoginService;
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;
Expand All @@ -16,72 +21,68 @@
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import static com.booking.app.constant.ApiMessagesConstants.INVALID_REQUEST_BODY_FORMAT_OR_VALIDATION_ERROR_MESSAGE;

/**
* Controller responsible for handling login requests, both basic and social login.
*/
@RestController
@RequestMapping
@RequiredArgsConstructor
@Log4j2
@Tag(name = "Login", description = "Basic and socials login")
@RequestMapping("/auth")
@Tag(name = "Authentication", description = "Basic and socials login")
@GlobalApiResponses
public class LoginController {

public static final String CLIENT_ID_IS_NOT_RIGHT_MESSAGE = "Client ID is not right";
public static final String USER_HAS_BEEN_AUTHENTICATED = "User has been authenticated";
public static final String NO_SOCIAL_IDENTITY_PROVIDERS_MESSAGE = "No Social Identity Providers was chosen";

private final LoginService loginService;

/**
* Handles basic authentication.
*
* @param loginDTO the login data transfer object containing the user's email and password
* @param request the HTTP request
* @param response the HTTP response
* @return a ResponseEntity containing the authentication result:
* - HTTP 200 OK with the user details if authentication is successful
* - HTTP 401 Unauthorized if authentication fails
*/
@PostMapping("/sign-in")
@Operation(summary = "Basic authentication")
@Operation(summary = "Basic authentication", description = "Authenticate by using email and password")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = USER_HAS_BEEN_AUTHENTICATED,
content = {@Content(schema = @Schema(implementation = AuthorizedUserDTO.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "401", description = "Invalid credentials or such user doesn't exist")
@ApiResponse(responseCode = "200",
description = ApiMessagesConstants.USER_HAS_BEEN_AUTHENTICATED_MESSAGE,
content = {@Content(schema = @Schema(implementation = AuthenticatedUserDto.class), mediaType = MediaType.APPLICATION_JSON_VALUE)}),
@ApiResponse(responseCode = "400",
description = INVALID_REQUEST_BODY_FORMAT_OR_VALIDATION_ERROR_MESSAGE,
content = {@Content(schema = @Schema(implementation = ErrorDetails.class), mediaType = MediaType.APPLICATION_JSON_VALUE)}),
@ApiResponse(responseCode = "401",
description = "Invalid credentials or such user doesn't exist",
content = {@Content(schema = @Schema(implementation = ErrorDetails.class), mediaType = MediaType.APPLICATION_JSON_VALUE)})
})
public ResponseEntity<?> basicLogin(@RequestBody @Valid @NotNull LoginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) {
return loginService.loginWithEmailAndPassword(loginDTO, request, response);
public ResponseEntity<?> basicLogin(@RequestBody @Valid @NotNull LoginDto dto,
HttpServletRequest request,
HttpServletResponse response) {
return loginService.loginWithEmailAndPassword(dto, request, response);
}

/**
* Handles social authentication via OAuth2 providers like Google and Facebook.
*
* @param dto the OAuth2 token data transfer object containing the token from the social provider
* @param request the HTTP request
* @param response the HTTP response
* @return a ResponseEntity containing the authentication result:
* - HTTP 200 OK with the user details if authentication is successful
* - HTTP 401 Unauthorized if the client ID is incorrect
* - HTTP 400 Bad Request if no social identity providers were chosen
*/
@Operation(summary = "Social authentication", description = "Authenticate using Google or Facebook")
@Operation(summary = "Social authentication", description = "Authenticate by using Google or Facebook")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = USER_HAS_BEEN_AUTHENTICATED,
content = {@Content(schema = @Schema(implementation = AuthorizedUserDTO.class), mediaType = "application/json")}),
@ApiResponse(responseCode = "401", description = CLIENT_ID_IS_NOT_RIGHT_MESSAGE),
@ApiResponse(responseCode = "400", description = NO_SOCIAL_IDENTITY_PROVIDERS_MESSAGE)
@ApiResponse(responseCode = "200", description = ApiMessagesConstants.USER_HAS_BEEN_AUTHENTICATED_MESSAGE,
content = {@Content(schema = @Schema(implementation = AuthenticatedUserDto.class), mediaType = MediaType.APPLICATION_JSON_VALUE)}),
@ApiResponse(responseCode = "400",
description = ApiMessagesConstants.NO_SOCIAL_IDENTITY_PROVIDERS_MESSAGE + " OR " + INVALID_REQUEST_BODY_FORMAT_OR_VALIDATION_ERROR_MESSAGE,
content = {@Content(schema = @Schema(implementation = ErrorDetails.class), mediaType = MediaType.APPLICATION_JSON_VALUE)}),
@ApiResponse(responseCode = "401",
description = ApiMessagesConstants.INVALID_CLIENT_PROVIDER_ID_MESSAGE,
content = {@Content(schema = @Schema(implementation = ErrorDetails.class), mediaType = MediaType.APPLICATION_JSON_VALUE)}),
})
@PostMapping("/sign-in/{provider}")
public ResponseEntity<?> socialLogin(@PathVariable String provider, @RequestBody @Valid @NotNull SocialSignInRequestDto dto, HttpServletRequest request, HttpServletResponse response) {
if (request.getRequestURI().contains("google")) {
public ResponseEntity<?> socialLogin(@PathVariable("provider") @Parameter(required = true, description = "Provider type", schema = @Schema(type = "string", allowableValues = {"google", "facebook"})) SocialProvider provider,
@RequestBody @Valid @NotNull SocialLoginDto dto,
HttpServletRequest request,
HttpServletResponse response) {
if (provider == SocialProvider.GOOGLE) {
return loginService.loginWithGoogle(dto, request, response);
} else if (request.getRequestURI().contains("facebook")) {
// TODO complete authentication via Facebook
}
return ResponseEntity.badRequest().body(NO_SOCIAL_IDENTITY_PROVIDERS_MESSAGE);
return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build();
}

}
34 changes: 15 additions & 19 deletions src/main/java/com/booking/app/controller/LogoutController.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
package com.booking.app.controller;

import com.booking.app.util.CookieUtils;
import com.booking.app.annotation.GlobalApiResponses;
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.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static com.booking.app.constant.CustomHttpHeaders.REMEMBER_ME;
import static com.booking.app.constant.CustomHttpHeaders.USER_ID;
import static com.booking.app.constant.JwtTokenConstants.REFRESH_TOKEN;

/**
* Controller handling user logout functionality.
*/
@RestController
@RequestMapping
@RequiredArgsConstructor
@Tag(name = "Log out", description = "Log out authenticated user")
@GlobalApiResponses
public class LogoutController {

/**
* Handles HTTP GET request to '/logout'
*
* @return ResponseEntity with an HTTP status representing successful logout
*/
@GetMapping("/sign-out")
@Operation(summary = "Log out a user")
@ApiResponse(responseCode = "200", description = "Successfully logged")
public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, REFRESH_TOKEN);
CookieUtils.deleteCookie(request, response, USER_ID);
CookieUtils.deleteCookie(request, response, REMEMBER_ME);
return ResponseEntity.ok().build();
@GetMapping("/auth/sign-out")
@Operation(summary = "Log out a user",
description = "Logs out the authenticated user by clearing the authentication cookies",
responses = {
@ApiResponse(responseCode = "200",
description = "Successfully logged out",
content = @Content(schema = @Schema(hidden = true)))
}
)
public void logout(HttpServletRequest request, HttpServletResponse response) {
CookieManager.deleteCookies(request, response);
}

}
Loading

0 comments on commit 95b41ae

Please sign in to comment.