Skip to content

Commit

Permalink
Handle feedbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
ndkhanh-axonivy committed Jul 10, 2024
1 parent 36aba62 commit 7074333
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ 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";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.axonivy.market.constants;

public class GitHubJsonConstants {

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";

private GitHubJsonConstants() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import com.axonivy.market.service.JwtService;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand All @@ -30,49 +28,48 @@
@RequestMapping(FEEDBACK)
public class FeedbackController {

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

private final PagedResourcesAssembler<Feedback> pagedResourcesAssembler;

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

@Operation(summary = "Find all feedbacks by product id")
@GetMapping("/product/{productId}")
public ResponseEntity<PagedModel<FeedbackModel>> findFeedbacks(@PathVariable String productId, Pageable pageable) {
Page<Feedback> results = service.findFeedbacks(productId, 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, assembler);
var pageResources = pagedResourcesAssembler.toModel(responseContent, feedbackModelAssembler);
return new ResponseEntity<>(pageResources, HttpStatus.OK);
}

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

@PostMapping
public ResponseEntity<Void> createFeedback(@RequestBody @Valid Feedback feedback, HttpServletRequest request, @RequestHeader(value = "Authorization", required = false) String authorizationHeader) {

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
Expand All @@ -85,7 +82,7 @@ public ResponseEntity<Void> createFeedback(@RequestBody @Valid Feedback feedback

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

URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
Expand All @@ -98,7 +95,7 @@ public ResponseEntity<Void> createFeedback(@RequestBody @Valid Feedback feedback
@Operation(summary = "Find rating information of product by id")
@GetMapping("/product/{productId}/rating")
public ResponseEntity<List<ProductRating>> getProductRating(@PathVariable String productId) {
return ResponseEntity.ok(service.getProductRatingById(productId));
return ResponseEntity.ok(feedbackService.getProductRatingById(productId));
}

@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.axonivy.market.controller;

import com.axonivy.market.constants.GitHubJsonConstants;
import com.axonivy.market.entity.User;
import com.axonivy.market.github.service.GitHubService;
import com.axonivy.market.model.Oauth2AuthorizationCode;
Expand Down Expand Up @@ -35,14 +36,14 @@ public OAuth2Controller(GitHubService gitHubService, JwtService jwtService) {
}

@PostMapping("/github/login")
public ResponseEntity<?> gitHubLogin(@RequestBody Oauth2AuthorizationCode oauth2AuthorizationCode, HttpServletResponse response) {
public ResponseEntity<?> gitHubLogin(@RequestBody Oauth2AuthorizationCode oauth2AuthorizationCode) {
Map<String, Object> tokenResponse = gitHubService.getAccessToken(oauth2AuthorizationCode.getCode(), clientId, clientSecret);
String accessToken = (String) tokenResponse.get("access_token");
String accessToken = (String) tokenResponse.get(GitHubJsonConstants.ACCESS_TOKEN);

User user = gitHubService.getAndUpdateUser(accessToken);

String jwtToken = jwtService.generateToken(user);

return ResponseEntity.ok().body(Collections.singletonMap("token", jwtToken));
return ResponseEntity.ok().body(Collections.singletonMap(GitHubJsonConstants.TOKEN, jwtToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
import static com.axonivy.market.constants.RequestMappingConstants.SYNC;

import org.apache.commons.lang3.time.StopWatch;
import com.axonivy.market.entity.Feedback;
import com.axonivy.market.model.FeedbackModel;
import com.axonivy.market.model.ProductRating;
import jakarta.websocket.server.PathParam;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
Expand All @@ -20,7 +16,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import com.axonivy.market.assembler.ProductModelAssembler;
import com.axonivy.market.entity.Product;
Expand All @@ -31,34 +26,32 @@

import io.swagger.v3.oas.annotations.Operation;

import java.util.List;

@RestController
@RequestMapping(PRODUCT)
public class ProductController {

private final ProductService service;
private final ProductService productService;
private final ProductModelAssembler assembler;
private final PagedResourcesAssembler<Product> pagedResourcesAssembler;

public ProductController(ProductService service, ProductModelAssembler assembler,
PagedResourcesAssembler<Product> pagedResourcesAssembler) {
this.service = service;
public ProductController(ProductService productService, ProductModelAssembler assembler,
PagedResourcesAssembler<Product> pagedResourcesAssembler) {
this.productService = productService;
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(required = true, name = "type") String type,
@RequestParam(name = "type") String type,
@RequestParam(required = false, name = "keyword") String keyword,
@RequestParam(required = true, name = "language") String language, Pageable pageable) {
Page<Product> results = service.findProducts(type, keyword, language, pageable);
@RequestParam(name = "language") String language, Pageable pageable) {
Page<Product> results = productService.findProducts(type, keyword, language, pageable);
if (results.isEmpty()) {
return generateEmptyPagedModel();
}
var responseContent = new PageImpl<Product>(results.getContent(), pageable, results.getTotalElements());
var responseContent = new PageImpl<>(results.getContent(), pageable, results.getTotalElements());
var pageResources = pagedResourcesAssembler.toModel(responseContent, assembler);
return new ResponseEntity<>(pageResources, HttpStatus.OK);
}
Expand All @@ -67,7 +60,7 @@ public ResponseEntity<PagedModel<ProductModel>> findProducts(
public ResponseEntity<Message> syncProducts() {
var stopWatch = new StopWatch();
stopWatch.start();
var isAlreadyUpToDate = service.syncLatestDataFromMarketRepo();
var isAlreadyUpToDate = productService.syncLatestDataFromMarketRepo();
var message = new Message();
message.setHelpCode(ErrorCode.SUCCESSFUL.getCode());
message.setHelpText(ErrorCode.SUCCESSFUL.getHelpText());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@

public interface GitHubService {

public GitHub getGitHub() throws IOException;
GitHub getGitHub() throws IOException;

public GHOrganization getOrganization(String orgName) throws IOException;
GHOrganization getOrganization(String orgName) throws IOException;

public GHRepository getRepository(String repositoryPath) throws IOException;
GHRepository getRepository(String repositoryPath) throws IOException;

public List<GHContent> getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException;
List<GHContent> getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException;

public GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException;
GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException;

public Map<String, Object> getAccessToken(String code, String clientId, String clientSecret);
Map<String, Object> getAccessToken(String code, String clientId, String clientSecret);

public User getAndUpdateUser(String accessToken);
User getAndUpdateUser(String accessToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.List;
import java.util.Map;

import com.axonivy.market.constants.GitHubConstants;
import com.axonivy.market.constants.GitHubJsonConstants;
import com.axonivy.market.entity.User;
import com.axonivy.market.exceptions.model.Oauth2ExchangeCodeException;
import com.axonivy.market.repository.UserRepository;
Expand Down Expand Up @@ -70,19 +72,18 @@ public GHContent getGHContent(GHRepository ghRepository, String path, String ref

@Override
public Map<String, Object> getAccessToken(String code, String clientId, String clientSecret) throws Oauth2ExchangeCodeException {
String url = "https://github.com/login/oauth/access_token";
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
params.add("code", code);
params.add(GitHubJsonConstants.CLIENT_ID, clientId);
params.add(GitHubJsonConstants.CLIENT_SECRET, clientSecret);
params.add(GitHubJsonConstants.CODE, code);

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

ResponseEntity<Map> response = restTemplate.postForEntity(url, request, Map.class);
if (response.getBody().containsKey("error")) {
throw new Oauth2ExchangeCodeException(response.getBody().get("error").toString(), response.getBody().get("error_description").toString());
ResponseEntity<Map> response = restTemplate.postForEntity(GitHubConstants.GITHUB_GET_ACCESS_TOKEN_URL, request, Map.class);
if (response.getBody().containsKey(GitHubJsonConstants.ERROR)) {
throw new Oauth2ExchangeCodeException(response.getBody().get(GitHubJsonConstants.ERROR).toString(), response.getBody().get(GitHubJsonConstants.ERROR_DESCRIPTION).toString());
}
return response.getBody();
}
Expand All @@ -102,10 +103,10 @@ public User getAndUpdateUser(String accessToken) {
throw new RuntimeException("Failed to fetch user details from GitHub");
}

String gitHubId = userDetails.get("id").toString();
String name = (String) userDetails.get("name");
String avatarUrl = (String) userDetails.get("avatar_url");
String username = (String) userDetails.get("login");
String gitHubId = (String) userDetails.get(GitHubJsonConstants.USER_ID);
String name = (String) userDetails.get(GitHubJsonConstants.USER_NAME);
String avatarUrl = (String) userDetails.get(GitHubJsonConstants.USER_AVATAR_URL);
String username = (String) userDetails.get(GitHubJsonConstants.USER_LOGIN_NAME);

User user = userRepository.searchByGitHubId(gitHubId);
if (user == null) {
Expand All @@ -115,7 +116,7 @@ public User getAndUpdateUser(String accessToken) {
user.setName(name);
user.setUsername(username);
user.setAvatarUrl(avatarUrl);
user.setProvider("GitHub");
user.setProvider(GitHubConstants.GITHUB_PROVIDER_NAME);

userRepository.save(user);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,19 @@ public Feedback findFeedbackByUserIdAndProductId(String userId, String productId
return existingFeedbacks.get(0);
}

@Transactional
@Override
public Feedback upsertFeedback(Feedback feedback) throws NotFoundException {
userRepository.findById(feedback.getUserId())
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND,"Not found user with id: " + feedback.getUserId()));
Product product = productRepository.findById(feedback.getProductId())
.orElseThrow(() -> new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + feedback.getProductId()));

List<Feedback> existingFeedbacks = feedbackRepository.searchByProductIdAndUserId(feedback.getUserId(), feedback.getProductId());

if (existingFeedbacks.isEmpty()) {
productRepository.save(product);
return feedbackRepository.save(feedback);
} else {
Feedback existingFeedback = existingFeedbacks.get(0);
existingFeedback.setRating(feedback.getRating());
existingFeedback.setContent(feedback.getContent());
productRepository.save(product);
return feedbackRepository.save(existingFeedback);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.axonivy.market.entity.User;
import com.axonivy.market.service.JwtService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -37,14 +38,18 @@ public String generateToken(User user) {

public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
getClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}

public Claims getClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
return getClaimsJws(token).getBody();
}

private Jws<Claims> getClaimsJws(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ void testCreateFeedback() {
when(jwtService.getClaimsFromToken(TOKEN_SAMPLE)).thenReturn(mockClaims);
when(service.upsertFeedback(any())).thenReturn(mockFeedback);

var result = feedbackController.createFeedback(mockFeedback, request, "Bearer " + TOKEN_SAMPLE);
var result = feedbackController.createFeedback(mockFeedback, "Bearer " + TOKEN_SAMPLE);
assertEquals(HttpStatus.CREATED, result.getStatusCode());
assertTrue(result.getHeaders().getLocation().toString().contains(mockFeedback.getId()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ void testGitHubLogin() {
when(gitHubService.getAndUpdateUser(accessToken)).thenReturn(user);
when(jwtService.generateToken(user)).thenReturn(jwtToken);

ResponseEntity<?> response = oAuth2Controller.gitHubLogin(oauth2AuthorizationCode, null);
ResponseEntity<?> response = oAuth2Controller.gitHubLogin(oauth2AuthorizationCode);

assertEquals(200, response.getStatusCodeValue());
assertEquals(Map.of("token", jwtToken), response.getBody());
Expand Down

0 comments on commit 7074333

Please sign in to comment.