Skip to content

Commit

Permalink
Merge pull request #555 from bounswe/529-backend-implement-question-s…
Browse files Browse the repository at this point in the history
…earch-endpoint

Implement Question Search Endpoint
  • Loading branch information
EnesBaserr authored Nov 23, 2024
2 parents d839675 + 007ed49 commit 294f0de
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static class UserEndpoints {
public static final String USER_FOLLOW = BASE_PATH + "/{id}/follow";
public static final String USER_UNFOLLOW = BASE_PATH + "/{id}/unfollow";
public static final String USER_FOLLOWERS = BASE_PATH + "/{id}/followers";
public static final String USER_FOLLOWING = BASE_PATH + "/{id}/following";
}

public static class QuestionEndpoints {
Expand Down Expand Up @@ -59,5 +60,6 @@ public static class BookmarkEndpoints {
public static class SearchEndpoints {
public static final String BASE_PATH = "/search";
public static final String SEARCH_USERS = BASE_PATH + "/users";
public static final String SEARCH_QUESTIONS = BASE_PATH + "/questions";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@
import com.group1.programminglanguagesforum.DTOs.Responses.GenericApiResponse;
import com.group1.programminglanguagesforum.Exceptions.ExceptionResponseHandler;
import com.group1.programminglanguagesforum.DTOs.Responses.GetQuestionDetailsResponseDto;
import com.group1.programminglanguagesforum.DTOs.Responses.QuestionSummaryDto;
import com.group1.programminglanguagesforum.Entities.DifficultyLevel;
import com.group1.programminglanguagesforum.Entities.Question;
import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException;
import com.group1.programminglanguagesforum.Services.QuestionService;
import com.group1.programminglanguagesforum.Util.ApiResponseBuilder;
import lombok.RequiredArgsConstructor;

import org.springframework.http.HttpStatus;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

@RestController
Expand All @@ -25,19 +34,25 @@ public class QuestionController extends BaseController {
private final QuestionService questionService;

@PostMapping(value = EndpointConstants.QuestionEndpoints.BASE_PATH)
public ResponseEntity<GenericApiResponse<CreateQuestionResponseDto>> createQuestion(@RequestBody CreateQuestionRequestDto dto) {
public ResponseEntity<GenericApiResponse<CreateQuestionResponseDto>> createQuestion(
@RequestBody CreateQuestionRequestDto dto) {
try {
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder.buildSuccessResponse(CreateQuestionResponseDto.class, "Question created successfully", 200, questionService.createQuestion(dto));
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder.buildSuccessResponse(
CreateQuestionResponseDto.class, "Question created successfully", 200,
questionService.createQuestion(dto));
return buildResponse(response, org.springframework.http.HttpStatus.OK);
} catch (UnauthorizedAccessException e) {
return ExceptionResponseHandler.UnauthorizedAccessException(e);
}
}

@GetMapping(value = EndpointConstants.QuestionEndpoints.QUESTION_ID)
public ResponseEntity<GenericApiResponse<GetQuestionDetailsResponseDto>> getQuestion(@PathVariable(value = "id") Long id) {
public ResponseEntity<GenericApiResponse<GetQuestionDetailsResponseDto>> getQuestion(
@PathVariable(value = "id") Long id) {
try {
GenericApiResponse<GetQuestionDetailsResponseDto> response = ApiResponseBuilder.buildSuccessResponse(GetQuestionDetailsResponseDto.class, "Question created successfully", 200, questionService.getQuestion(id));
GenericApiResponse<GetQuestionDetailsResponseDto> response = ApiResponseBuilder.buildSuccessResponse(
GetQuestionDetailsResponseDto.class, "Question created successfully", 200,
questionService.getQuestion(id));
return buildResponse(response, org.springframework.http.HttpStatus.OK);

} catch (NoSuchElementException e) {
Expand All @@ -50,39 +65,71 @@ public ResponseEntity<GenericApiResponse<GetQuestionDetailsResponseDto>> getQues
}
}


@DeleteMapping(value = EndpointConstants.QuestionEndpoints.QUESTION_ID)
public ResponseEntity<GenericApiResponse<String>> deleteQuestion(@PathVariable(value = "id") Long id) {
try {


GenericApiResponse<String> response = ApiResponseBuilder.buildSuccessResponse(String.class, "Question deleted successfully", 200, questionService.deleteQuestion(id));
GenericApiResponse<String> response = ApiResponseBuilder.buildSuccessResponse(String.class,
"Question deleted successfully", 200, questionService.deleteQuestion(id));
return buildResponse(response, org.springframework.http.HttpStatus.OK);
} catch (NoSuchElementException e) {
ErrorResponse errorResponse = ErrorResponse.builder()
.errorMessage(e.getMessage())
.stackTrace(Arrays.toString(e.getStackTrace()))
.build();
GenericApiResponse<String> response = ApiResponseBuilder.buildErrorResponse(String.class, e.getMessage(), 404, errorResponse);
GenericApiResponse<String> response = ApiResponseBuilder.buildErrorResponse(String.class, e.getMessage(),
404, errorResponse);
return buildResponse(response, org.springframework.http.HttpStatus.NOT_FOUND);
}

}

@PutMapping(value = EndpointConstants.QuestionEndpoints.QUESTION_ID)
public ResponseEntity<GenericApiResponse<CreateQuestionResponseDto>> updateQuestion(@PathVariable(value = "id") Long id, @RequestBody UpdateQuestionRequestDto dto) {
public ResponseEntity<GenericApiResponse<CreateQuestionResponseDto>> updateQuestion(
@PathVariable(value = "id") Long id, @RequestBody UpdateQuestionRequestDto dto) {
try {
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder.buildSuccessResponse(CreateQuestionResponseDto.class, "Question updated successfully", 200, questionService.updateQuestion(id, dto));
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder.buildSuccessResponse(
CreateQuestionResponseDto.class, "Question updated successfully", 200,
questionService.updateQuestion(id, dto));
return buildResponse(response, org.springframework.http.HttpStatus.OK);
}
catch (NoSuchElementException e) {
} catch (NoSuchElementException e) {
ErrorResponse errorResponse = ErrorResponse.builder()
.errorMessage(e.getMessage())
.stackTrace(Arrays.toString(e.getStackTrace()))
.build();
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder.buildErrorResponse(CreateQuestionResponseDto.class, e.getMessage(), 404, errorResponse);
GenericApiResponse<CreateQuestionResponseDto> response = ApiResponseBuilder
.buildErrorResponse(CreateQuestionResponseDto.class, e.getMessage(), 404, errorResponse);
return buildResponse(response, org.springframework.http.HttpStatus.NOT_FOUND);
}
}

@GetMapping(value = EndpointConstants.SearchEndpoints.SEARCH_QUESTIONS)
public ResponseEntity<GenericApiResponse<Map<String, Object>>> searchQuestions(
@RequestParam("q") String query,
@RequestParam(required = false) String tags,
@RequestParam(required = false) DifficultyLevel difficulty,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {

Page<Question> questionPage = questionService.searchQuestions(query, tags, difficulty, page, pageSize);

List<QuestionSummaryDto> questionSummaries = questionPage.getContent().stream()
.map(questionService::mapToQuestionSummary)
.toList();

Map<String, Object> response = new HashMap<>();
response.put("items", questionSummaries);
response.put("totalItems", questionPage.getTotalElements());
response.put("currentPage", page);
response.put("totalPages", questionPage.getTotalPages());

return buildResponse(
ApiResponseBuilder.buildSuccessResponse(
Map.class,
"Questions retrieved successfully",
HttpStatus.OK.value(),
response),
HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,35 @@ public ResponseEntity<GenericApiResponse<List<UserSummaryDto>>> getFollowers(
}
}

@GetMapping(value = EndpointConstants.UserEndpoints.USER_FOLLOWING)
public ResponseEntity<GenericApiResponse<List<UserSummaryDto>>> getFollowing(
@PathVariable(name = "id") Long id) {
try {
User user = userService.getUserById(id)
.orElseThrow(() -> new UserNotFoundException("User not found"));

List<UserSummaryDto> following = userService.getFollowing(user).stream()
.map(u -> modelMapper.map(u, UserSummaryDto.class))
.toList();

return buildResponse(
ApiResponseBuilder.buildSuccessResponse(
following.getClass(),
"Following users retrieved successfully",
HttpStatus.OK.value(),
following),
HttpStatus.OK);
} catch (UserNotFoundException e) {
return buildResponse(
ApiResponseBuilder.buildErrorResponse(
List.class,
e.getMessage(),
HttpStatus.NOT_FOUND.value(),
ErrorResponse.builder().errorMessage(e.getMessage()).build()),
HttpStatus.NOT_FOUND);
}
}

@GetMapping(value = EndpointConstants.SearchEndpoints.SEARCH_USERS)
public ResponseEntity<GenericApiResponse<Map<String, Object>>> searchUsers(
@RequestParam("q") String query,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.group1.programminglanguagesforum.DTOs.Responses;

import com.group1.programminglanguagesforum.Entities.DifficultyLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QuestionSummaryDto {
private Long id;
private String title;
private String content;
private DifficultyLevel difficulty;
private Long upvoteCount;
private Long downvoteCount;
private Long answerCount;
private String createdAt;
private AuthorDto author;
private List<TagDto> tags;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.group1.programminglanguagesforum.Repositories;

import com.group1.programminglanguagesforum.Entities.DifficultyLevel;
import com.group1.programminglanguagesforum.Entities.Question;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -9,7 +12,20 @@
import java.util.List;

@Repository
public interface QuestionRepository extends JpaRepository<Question,Long> {
public interface QuestionRepository extends JpaRepository<Question, Long> {
@Query("SELECT q FROM Question q JOIN q.tags t WHERE t.id = :tagId order by q.likeCount desc ")
List<Question> findQuestionsByTagId(@Param("tagId") Long tagId);

@Query("SELECT DISTINCT q FROM Question q " +
"LEFT JOIN q.tags t " +
"WHERE (:query IS NULL OR " +
" LOWER(q.title) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
" LOWER(q.questionBody) LIKE LOWER(CONCAT('%', :query, '%'))) " +
"AND (:tagIds IS NULL OR t.id IN :tagIds) " +
"AND (:difficulty IS NULL OR q.difficulty = :difficulty)")
Page<Question> searchQuestions(
@Param("query") String query,
@Param("tagIds") List<Long> tagIds,
@Param("difficulty") DifficultyLevel difficulty,
Pageable pageable);
}
Loading

0 comments on commit 294f0de

Please sign in to comment.