Skip to content

Commit

Permalink
feature/marp-763 doc for swagger UI on mp api
Browse files Browse the repository at this point in the history
  • Loading branch information
ntqdinh-axonivy authored Aug 1, 2024
1 parent 3bf8097 commit 07b997f
Show file tree
Hide file tree
Showing 22 changed files with 238 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class RequestParamConstants {
public static final String LANGUAGE = "language";
public static final String USER_ID = "userId";
public static final String AUTHORIZATION = "Authorization";
public static final String X_AUTHORIZATION = "X-Authorization";
public static final String RESET_SYNC = "resetSync";
public static final String SHOW_DEV_VERSION = "isShowDevVersion";
public static final String DESIGNER_VERSION = "designerVersion";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package com.axonivy.market.controller;

import static com.axonivy.market.constants.RequestMappingConstants.BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.FEEDBACK;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_RATING_BY_ID;
import static com.axonivy.market.constants.RequestParamConstants.AUTHORIZATION;
import static com.axonivy.market.constants.RequestParamConstants.ID;
import static com.axonivy.market.constants.RequestParamConstants.USER_ID;

import java.net.URI;
import java.util.List;

import com.axonivy.market.assembler.FeedbackModelAssembler;
import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.entity.Feedback;
import com.axonivy.market.model.FeedbackModel;
import com.axonivy.market.model.FeedbackModelRequest;
import com.axonivy.market.model.ProductRating;
import com.axonivy.market.service.FeedbackService;
import com.axonivy.market.service.JwtService;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
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 org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -28,20 +37,20 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.axonivy.market.assembler.FeedbackModelAssembler;
import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.entity.Feedback;
import com.axonivy.market.model.FeedbackModel;
import com.axonivy.market.model.ProductRating;
import com.axonivy.market.service.FeedbackService;
import com.axonivy.market.service.JwtService;
import java.net.URI;
import java.util.List;

import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import static com.axonivy.market.constants.RequestMappingConstants.BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.FEEDBACK;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_RATING_BY_ID;
import static com.axonivy.market.constants.RequestParamConstants.X_AUTHORIZATION;
import static com.axonivy.market.constants.RequestParamConstants.ID;
import static com.axonivy.market.constants.RequestParamConstants.USER_ID;

@RestController
@RequestMapping(FEEDBACK)
@Tag(name = "User Feedback Controllers", description = "API collection to handle user's feedback.")
public class FeedbackController {

private final FeedbackService feedbackService;
Expand All @@ -50,18 +59,21 @@ public class FeedbackController {

private final PagedResourcesAssembler<Feedback> pagedResourcesAssembler;

public FeedbackController(FeedbackService feedbackService, JwtService jwtService,
FeedbackModelAssembler feedbackModelAssembler, PagedResourcesAssembler<Feedback> pagedResourcesAssembler) {
public FeedbackController(FeedbackService feedbackService, JwtService jwtService, FeedbackModelAssembler feedbackModelAssembler, PagedResourcesAssembler<Feedback> pagedResourcesAssembler) {
this.feedbackService = feedbackService;
this.jwtService = jwtService;
this.feedbackModelAssembler = feedbackModelAssembler;
this.pagedResourcesAssembler = pagedResourcesAssembler;
}

@Operation(summary = "Find all feedbacks by product id")
@GetMapping(PRODUCT_BY_ID)
public ResponseEntity<PagedModel<FeedbackModel>> findFeedbacks(@PathVariable(ID) String productId,
Pageable pageable) {
@Operation(summary = "Find feedbacks by product id with lazy loading", description = "Get all user feedback by product id (from meta.json) with lazy loading", parameters = {
@Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true),
@Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true),
@Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting order(asc|desc)", in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true)})
public ResponseEntity<PagedModel<FeedbackModel>> findFeedbacks(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String productId,
@ParameterObject Pageable pageable) {
Page<Feedback> results = feedbackService.findFeedbacks(productId, pageable);
if (results.isEmpty()) {
return generateEmptyPagedModel();
Expand All @@ -72,25 +84,34 @@ public ResponseEntity<PagedModel<FeedbackModel>> findFeedbacks(@PathVariable(ID)
}

@GetMapping(BY_ID)
public ResponseEntity<FeedbackModel> findFeedback(@PathVariable(ID) String id) {
@Operation(summary = "Find all feedbacks by product id", description = "Get all feedbacks by product id(from meta.json) which is used in mobile viewport.")
public ResponseEntity<FeedbackModel> findFeedback(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String id) {
Feedback feedback = feedbackService.findFeedback(id);
return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback));
}

@Operation(summary = "Find all feedbacks by user id and product id")
@GetMapping()
public ResponseEntity<FeedbackModel> findFeedbackByUserIdAndProductId(@RequestParam(USER_ID) String userId,
@RequestParam("productId") String productId) {
@Operation(summary = "Find all feedbacks by user id and product id", description = "Get current user feedback on target product.")
public ResponseEntity<FeedbackModel> findFeedbackByUserIdAndProductId(
@RequestParam(USER_ID) @Parameter(description = "Id of current user from DB", example = "1234", in = ParameterIn.QUERY) String userId,
@RequestParam("productId") @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.QUERY) String productId) {
Feedback feedback = feedbackService.findFeedbackByUserIdAndProductId(userId, productId);
return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback));
}

@PostMapping
public ResponseEntity<Void> createFeedback(@RequestBody @Valid FeedbackModel feedback,
@RequestHeader(value = AUTHORIZATION) String authorizationHeader) {
@Operation(summary = "Create user feedback", description = "Save user feedback of product with their token from Github account.")
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Example request body for feedback", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = FeedbackModelRequest.class)))
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created user feedback"),
@ApiResponse(responseCode = "401", description = "Unauthorized request")})
public ResponseEntity<Void> createFeedback(
@RequestBody @Valid FeedbackModelRequest feedbackRequest,
@RequestHeader(value = X_AUTHORIZATION) @Parameter(description = "JWT Bearer token", example = "Bearer 123456", in = ParameterIn.HEADER) String bearerToken) {
String token = null;
if (authorizationHeader != null && authorizationHeader.startsWith(CommonConstants.BEARER)) {
token = authorizationHeader.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix
if (bearerToken != null && bearerToken.startsWith(CommonConstants.BEARER)) {
token = bearerToken.substring(CommonConstants.BEARER.length()).trim(); // Remove "Bearer " prefix
}

// Validate the token
Expand All @@ -99,18 +120,18 @@ public ResponseEntity<Void> createFeedback(@RequestBody @Valid FeedbackModel fee
}

Claims claims = jwtService.getClaimsFromToken(token);
feedback.setUserId(claims.getSubject());
Feedback newFeedback = feedbackService.upsertFeedback(feedback);
Feedback newFeedback = feedbackService.upsertFeedback(feedbackRequest, claims.getSubject());

URI location = ServletUriComponentsBuilder.fromCurrentRequest().path(BY_ID).buildAndExpand(newFeedback.getId())
.toUri();

return ResponseEntity.created(location).build();
}

@Operation(summary = "Find rating information of product by id")
@Operation(summary = "Find rating information of product by its id.", description = "Get overall rating of product by its id.")
@GetMapping(PRODUCT_RATING_BY_ID)
public ResponseEntity<List<ProductRating>> getProductRating(@PathVariable(ID) String productId) {
public ResponseEntity<List<ProductRating>> getProductRating(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "portal", in = ParameterIn.PATH) String productId) {
return ResponseEntity.ok(feedbackService.getProductRatingById(productId));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
import java.util.Collections;
import java.util.Map;

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 org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -39,11 +45,15 @@ public OAuth2Controller(GitHubService gitHubService, JwtService jwtService, GitH
}

@PostMapping(GIT_HUB_LOGIN)
@Operation(description = "Get rating authentication token")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully login to GitHub provider", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Map.class))),
@ApiResponse(responseCode = "400", description = "Bad Request")})
@io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = Oauth2AuthorizationCode.class)))
public ResponseEntity<Map<String, String>> gitHubLogin(@RequestBody Oauth2AuthorizationCode oauth2AuthorizationCode) {
String accessToken = EMPTY;
String accessToken;
try {
GitHubAccessTokenResponse tokenResponse = gitHubService.getAccessToken(oauth2AuthorizationCode.getCode(),
gitHubProperty);
GitHubAccessTokenResponse tokenResponse = gitHubService.getAccessToken(oauth2AuthorizationCode.getCode(), gitHubProperty);
accessToken = tokenResponse.getAccessToken();
} catch (Exception e) {
return new ResponseEntity<>(Map.of(e.getClass().getName(), e.getMessage()), HttpStatus.BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package com.axonivy.market.controller;

import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT;
import static com.axonivy.market.constants.RequestMappingConstants.SYNC;
import static com.axonivy.market.constants.RequestParamConstants.AUTHORIZATION;
import static com.axonivy.market.constants.RequestParamConstants.KEYWORD;
import static com.axonivy.market.constants.RequestParamConstants.LANGUAGE;
import static com.axonivy.market.constants.RequestParamConstants.RESET_SYNC;
import static com.axonivy.market.constants.RequestParamConstants.TYPE;

import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import com.axonivy.market.assembler.ProductModelAssembler;
import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.constants.GitHubConstants;
Expand All @@ -13,6 +25,7 @@
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import org.apache.commons.lang3.time.StopWatch;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
Expand All @@ -30,16 +43,10 @@
import org.springframework.web.bind.annotation.RestController;

import static com.axonivy.market.constants.RequestMappingConstants.CUSTOM_SORT;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT;
import static com.axonivy.market.constants.RequestMappingConstants.SYNC;
import static com.axonivy.market.constants.RequestParamConstants.AUTHORIZATION;
import static com.axonivy.market.constants.RequestParamConstants.KEYWORD;
import static com.axonivy.market.constants.RequestParamConstants.LANGUAGE;
import static com.axonivy.market.constants.RequestParamConstants.RESET_SYNC;
import static com.axonivy.market.constants.RequestParamConstants.TYPE;

@RestController
@RequestMapping(PRODUCT)
@Tag(name = "Product Controller", description = "API collection to get and search products")
public class ProductController {

private final ProductService productService;
Expand All @@ -48,18 +55,24 @@ public class ProductController {
private final PagedResourcesAssembler<Product> pagedResourcesAssembler;

public ProductController(ProductService productService, GitHubService gitHubService, ProductModelAssembler assembler,
PagedResourcesAssembler<Product> pagedResourcesAssembler) {
PagedResourcesAssembler<Product> pagedResourcesAssembler) {
this.productService = productService;
this.gitHubService = gitHubService;
this.assembler = assembler;
this.pagedResourcesAssembler = pagedResourcesAssembler;
}

@Operation(summary = "Find all products", description = "Be default system will finds product by type as 'all'")
@GetMapping()
public ResponseEntity<PagedModel<ProductModel>> findProducts(@RequestParam(name = TYPE) String type,
@RequestParam(required = false, name = KEYWORD) String keyword, @RequestParam(name = LANGUAGE) String language,
Pageable pageable) {
@Operation(summary = "Retrieve a paginated list of all products, optionally filtered by type, keyword, and language", description = "By default, the system finds products with type 'all'", parameters = {
@Parameter(name = "page", description = "Page number to retrieve", in = ParameterIn.QUERY, example = "0", required = true),
@Parameter(name = "size", description = "Number of items per page", in = ParameterIn.QUERY, example = "20", required = true),
@Parameter(name = "sort", description = "Sorting criteria in the format: Sorting criteria(popularity|alphabetically|recent), Sorting order(asc|desc)",
in = ParameterIn.QUERY, example = "[\"popularity\",\"asc\"]", required = true)})
public ResponseEntity<PagedModel<ProductModel>> findProducts(
@RequestParam(name = TYPE) @Parameter(description = "Type of product.", in = ParameterIn.QUERY, schema = @Schema(type = "string", allowableValues = {"all", "connectors", "utilities", "solutions", "demos"})) String type,
@RequestParam(required = false, name = KEYWORD) @Parameter(description = "Keyword that exist in product's name or short description", example = "connector", in = ParameterIn.QUERY) String keyword,
@RequestParam(name = LANGUAGE) @Parameter(description = "Language of product short description", in = ParameterIn.QUERY, schema = @Schema(allowableValues = {"en", "de"})) String language,
@ParameterObject Pageable pageable) {
Page<Product> results = productService.findProducts(type, keyword, language, pageable);
if (results.isEmpty()) {
return generateEmptyPagedModel();
Expand All @@ -70,7 +83,9 @@ public ResponseEntity<PagedModel<ProductModel>> findProducts(@RequestParam(name
}

@PutMapping(SYNC)
public ResponseEntity<Message> syncProducts(@RequestHeader(value = AUTHORIZATION) String authorizationHeader,
@Operation(hidden = true)
public ResponseEntity<Message> syncProducts(
@RequestHeader(value = AUTHORIZATION) String authorizationHeader,
@RequestParam(value = RESET_SYNC, required = false) Boolean resetSync) {
String token = getBearerToken(authorizationHeader);
gitHubService.validateUserOrganization(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME);
Expand All @@ -94,6 +109,7 @@ public ResponseEntity<Message> syncProducts(@RequestHeader(value = AUTHORIZATION
}

@PostMapping(CUSTOM_SORT)
@Operation(hidden = true)
public ResponseEntity<Message> createCustomSortProducts(
@RequestHeader(value = AUTHORIZATION) String authorizationHeader,
@RequestBody @Valid ProductCustomSortRequest productCustomSortRequest) {
Expand All @@ -108,7 +124,7 @@ public ResponseEntity<Message> createCustomSortProducts(
@SuppressWarnings("unchecked")
private ResponseEntity<PagedModel<ProductModel>> generateEmptyPagedModel() {
var emptyPagedModel = (PagedModel<ProductModel>) pagedResourcesAssembler.toEmptyModel(Page.empty(),
ProductModel.class);
ProductModel.class);
return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK);
}

Expand Down
Loading

0 comments on commit 07b997f

Please sign in to comment.