Skip to content

Commit

Permalink
Merge branch 'develop' into feature/MARP-473-follow-up-for-marp-264-i…
Browse files Browse the repository at this point in the history
…nstallation-frequency-providing-data
  • Loading branch information
tvtphuc-axonivy committed Jul 12, 2024
2 parents dcc19f3 + f5797b4 commit d16dccc
Show file tree
Hide file tree
Showing 68 changed files with 1,491 additions and 292 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/service-dev-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ jobs:
- name: Restart Tomcat server
run: |
sudo systemctl stop tomcat
sudo systemctl start tomcat
sudo systemctl start tomcat
10 changes: 10 additions & 0 deletions marketplace-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@
<artifactId>github-api</artifactId>
<version>1.321</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.axonivy.market;

import com.axonivy.market.service.ProductService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand All @@ -9,10 +11,6 @@
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import com.axonivy.market.service.ProductService;

import lombok.extern.log4j.Log4j2;

@Log4j2
@EnableAsync
@EnableScheduling
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.axonivy.market.assembler;

import com.axonivy.market.controller.FeedbackController;
import com.axonivy.market.entity.Feedback;
import com.axonivy.market.entity.User;
import com.axonivy.market.exceptions.model.NotFoundException;
import com.axonivy.market.model.FeedbackModel;
import com.axonivy.market.service.UserService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.stereotype.Component;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@Log4j2
@Component
public class FeedbackModelAssembler extends RepresentationModelAssemblerSupport<Feedback, FeedbackModel> {

private final UserService userService;

public FeedbackModelAssembler(UserService userService) {
super(Feedback.class, FeedbackModel.class);
this.userService = userService;
}

@Override
public FeedbackModel toModel(Feedback feedback) {
FeedbackModel resource = new FeedbackModel();
resource.add(linkTo(methodOn(FeedbackController.class).findFeedback(feedback.getId()))
.withSelfRel());
return createResource(resource, feedback);
}

private FeedbackModel createResource(FeedbackModel model, Feedback feedback) {
User user;
try {
user = userService.findUser(feedback.getUserId());
}
catch (NotFoundException e) {
log.warn(e.getMessage());
user = new User();
}
model.setId(feedback.getId());
model.setUsername(StringUtils.isBlank(user.getName()) ? user.getUsername() : user.getName());
model.setUserAvatarUrl(user.getAvatarUrl());
model.setUserProvider(user.getProvider());
model.setProductId(feedback.getProductId());
model.setContent(feedback.getContent());
model.setRating(feedback.getRating());
model.setCreatedAt(feedback.getCreatedAt());
model.setUpdatedAt(feedback.getUpdatedAt());
return model;
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.axonivy.market.assembler;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.stereotype.Component;

import com.axonivy.market.controller.ProductDetailsController;
import com.axonivy.market.entity.Product;
import com.axonivy.market.model.ProductModel;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.stereotype.Component;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@Component
public class ProductModelAssembler extends RepresentationModelAssemblerSupport<Product, ProductModel> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.axonivy.market.config;

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import static com.axonivy.market.constants.CommonConstants.*;
import static com.axonivy.market.constants.CommonConstants.REQUESTED_BY;

@Configuration
public class MarketApiDocumentConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.axonivy.market.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.exceptions.model.MissingHeaderException;

import io.swagger.v3.oas.models.PathItem.HttpMethod;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class MarketHeaderInterceptor implements HandlerInterceptor {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
package com.axonivy.market.config;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;

@Configuration
@EnableMongoRepositories(basePackages = "com.axonivy.market.repository")
@EnableMongoAuditing
public class MongoConfig extends AbstractMongoClientConfiguration {

@Value("${spring.data.mongodb.host}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public class EntityConstants {
public static final String PRODUCT = "Product";
public static final String MAVEN_ARTIFACT_VERSION = "MavenArtifactVersion";
public static final String GH_REPO_META = "GitHubRepoMeta";
public static final String FEEDBACK = "Feedback";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,21 @@ public class GitHubConstants {
public static final String AXONIVY_MARKETPLACE_PATH = "market";
public static final String DEFAULT_BRANCH = "feature/MARP-463-Multilingualism-for-Website";
public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json";
public static final String GITHUB_PROVIDER_NAME = "GitHub";
public static final String GITHUB_GET_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Json {
public static final String ACCESS_TOKEN = "access_token";
public static final String TOKEN = "token";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
public static final String CODE = "code";
public static final String ERROR = "error";
public static final String ERROR_DESCRIPTION = "error";
public static final String USER_ID = "id";
public static final String USER_NAME = "name";
public static final String USER_AVATAR_URL = "avatar_url";
public static final String USER_LOGIN_NAME = "login";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public class RequestMappingConstants {
public static final String USER_MAPPING = "/user";
public static final String PRODUCT = API + "/product";
public static final String PRODUCT_DETAILS = API + "/product-details";
public static final String FEEDBACK = API + "/feedback";
public static final String SWAGGER_URL = "/swagger-ui/index.html";
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package com.axonivy.market.controller;

import static com.axonivy.market.constants.RequestMappingConstants.ROOT;
import static com.axonivy.market.constants.RequestMappingConstants.SWAGGER_URL;

import com.axonivy.market.enums.ErrorCode;
import com.axonivy.market.model.Message;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
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 org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.axonivy.market.enums.ErrorCode;
import com.axonivy.market.model.Message;

import lombok.extern.log4j.Log4j2;
import static com.axonivy.market.constants.RequestMappingConstants.ROOT;
import static com.axonivy.market.constants.RequestMappingConstants.SWAGGER_URL;

@Log4j2
@RestController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.axonivy.market.controller;

import com.axonivy.market.assembler.FeedbackModelAssembler;
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 io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
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.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

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

import static com.axonivy.market.constants.RequestMappingConstants.FEEDBACK;

@RestController
@RequestMapping(FEEDBACK)
public class FeedbackController {

private final FeedbackService feedbackService;
private final JwtService jwtService;
private final FeedbackModelAssembler feedbackModelAssembler;

private final 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/{productId}")
public ResponseEntity<PagedModel<FeedbackModel>> findFeedbacks(@PathVariable("productId") String productId, Pageable pageable) {
Page<Feedback> results = feedbackService.findFeedbacks(productId, pageable);
if (results.isEmpty()) {
return generateEmptyPagedModel();
}
var responseContent = new PageImpl<>(results.getContent(), pageable, results.getTotalElements());
var pageResources = pagedResourcesAssembler.toModel(responseContent, feedbackModelAssembler);
return new ResponseEntity<>(pageResources, HttpStatus.OK);
}

@GetMapping("/{id}")
public ResponseEntity<FeedbackModel> findFeedback(@PathVariable("id") 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 String userId,
@RequestParam String productId) {
Feedback feedback = feedbackService.findFeedbackByUserIdAndProductId(userId, productId);
return ResponseEntity.ok(feedbackModelAssembler.toModel(feedback));
}

@PostMapping
public ResponseEntity<Void> createFeedback(@RequestBody @Valid Feedback feedback, @RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
String token = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7); // Remove "Bearer " prefix
}

// Validate the token
if (token == null || !jwtService.validateToken(token)) {
return ResponseEntity.status(401).build(); // Unauthorized if token is missing or invalid
}

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

URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(newFeedback.getId())
.toUri();

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

@Operation(summary = "Find rating information of product by id")
@GetMapping("/product/{productId}/rating")
public ResponseEntity<List<ProductRating>> getProductRating(@PathVariable("productId") String productId) {
return ResponseEntity.ok(feedbackService.getProductRatingById(productId));
}

@SuppressWarnings("unchecked")
private ResponseEntity<PagedModel<FeedbackModel>> generateEmptyPagedModel() {
var emptyPagedModel = (PagedModel<FeedbackModel>) pagedResourcesAssembler
.toEmptyModel(Page.empty(), FeedbackModel.class);
return new ResponseEntity<>(emptyPagedModel, HttpStatus.OK);
}
}
Loading

0 comments on commit d16dccc

Please sign in to comment.