Skip to content

Commit

Permalink
Merge pull request #563 from bounswe/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
mmtftr authored Nov 24, 2024
2 parents c04add4 + 294f0de commit 91113e7
Show file tree
Hide file tree
Showing 79 changed files with 4,893 additions and 883 deletions.
775 changes: 775 additions & 0 deletions backend/openapi.yaml

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,42 @@
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>

<!-- JUnit Jupiter API for writing tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version> <!-- Use the latest stable version -->
<scope>test</scope>
</dependency>

<!-- Mockito Core for mocking objects -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version> <!-- Use the latest stable version -->
<scope>test</scope>
</dependency>

<!-- Mockito JUnit Integration -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>

<!-- Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.group1.programminglanguagesforum.Services.CustomUserDetailsService;
import com.group1.programminglanguagesforum.Services.JwtService;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -18,6 +19,7 @@
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
Expand Down Expand Up @@ -91,7 +93,7 @@ protected void doFilterInternal(
response.getWriter().write(objectMapper.writeValueAsString(genericApiResponse));
return;
}
catch (Exception e){
catch (JwtException | UsernameNotFoundException e) {
GenericApiResponse<Void> genericApiResponse = GenericApiResponse.<Void>builder()
.status(HttpServletResponse.SC_UNAUTHORIZED)
.message("Invalid token")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws
.requestMatchers(HttpMethod.PUT, API_BASE + "/**").authenticated() // All PUTs need authentication
.requestMatchers(HttpMethod.DELETE, API_BASE + "/**").authenticated()// All DELETEs need authentication
.requestMatchers(HttpMethod.GET, "/**").permitAll() // General GET requests, allow everything else
.anyRequest().authenticated()
.anyRequest().permitAll()
)
.anonymous(anonymous -> anonymous.disable())
.sessionManagement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ public class EndpointConstants {

public static final String TEST = "/test";

// This class should not be instantiated. It should be used only by referencing
// the class itself.
private EndpointConstants() {
}

public static class AuthenticationEndpoints {
public static final String BASE_PATH = "/auth";
public static final String SIGNIN = BASE_PATH + "/login";
Expand All @@ -17,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 All @@ -26,6 +32,7 @@ public static class QuestionEndpoints {
public static final String QUESTION_DOWNVOTE = QUESTION_ID + "/downvote";
public static final String QUESTION_DELETE_UPVOTE = QUESTION_ID + "/deleteUpvote";
public static final String QUESTION_DELETE_DOWNVOTE = QUESTION_ID + "/deleteDownvote";
public static final String QUESTION_ANSWERS = BASE_PATH + "/{questionId}/answers";
}

public static class AnswerEndpoints {
Expand All @@ -42,10 +49,17 @@ public static class SparqlEndpoints {
public static class TagEndpoints {
public static final String BASE_PATH = "/tags";
public static final String TAG_ID = BASE_PATH + "/{id}";
public static final String SEARCH = "/search" + BASE_PATH;
}

public static class BookmarkEndpoints {
public static final String BASE_PATH = "/questions/{questionId}/bookmarks";
public static final String BOOKMARK_DELETE = BASE_PATH;
}

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
@@ -0,0 +1,79 @@
package com.group1.programminglanguagesforum.Controllers;

import com.group1.programminglanguagesforum.Constants.EndpointConstants;
import com.group1.programminglanguagesforum.DTOs.Requests.CreateAnswerRequestDto;
import com.group1.programminglanguagesforum.DTOs.Responses.CreateAnswerResponseDto;
import com.group1.programminglanguagesforum.DTOs.Responses.ErrorResponse;
import com.group1.programminglanguagesforum.DTOs.Responses.GenericApiResponse;
import com.group1.programminglanguagesforum.DTOs.Responses.GetAnswersResponseDto;
import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException;
import com.group1.programminglanguagesforum.Services.AnswerService;
import com.group1.programminglanguagesforum.Util.ApiResponseBuilder;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class AnswerController extends BaseController {
private final AnswerService answerService;

@PutMapping(value= EndpointConstants.AnswerEndpoints.ANSWER_ID)
public ResponseEntity<GenericApiResponse<CreateAnswerResponseDto>> updateAnswer(
@PathVariable(value = "id") Long answerId,
@RequestBody CreateAnswerRequestDto createAnswerRequestDto) throws UnauthorizedAccessException {
CreateAnswerResponseDto response = answerService.updateAnswer(answerId, createAnswerRequestDto);
GenericApiResponse<CreateAnswerResponseDto> apiResponse = GenericApiResponse.<CreateAnswerResponseDto>builder()
.status(200)
.message("Answer updated successfully")
.data(response)
.build();
return buildResponse(apiResponse, HttpStatus.OK);
}
@PostMapping(value= EndpointConstants.QuestionEndpoints.QUESTION_ANSWERS)
public ResponseEntity<GenericApiResponse<CreateAnswerResponseDto>> createAnswer(
@PathVariable(value = "questionId") Long questionId,
@RequestBody CreateAnswerRequestDto createAnswerRequestDto) throws UnauthorizedAccessException {
CreateAnswerResponseDto response = answerService.createAnswer(questionId, createAnswerRequestDto);
GenericApiResponse<CreateAnswerResponseDto> apiResponse = GenericApiResponse.<CreateAnswerResponseDto>builder()
.status(201)
.message("Answer created successfully")
.data(response)
.build();
return buildResponse(apiResponse, HttpStatus.CREATED);
}
@DeleteMapping(value= EndpointConstants.AnswerEndpoints.ANSWER_ID)
public ResponseEntity<GenericApiResponse<String>> deleteAnswer(@PathVariable(value = "id") Long answerId) throws UnauthorizedAccessException {
try {
answerService.deleteAnswer(answerId);
GenericApiResponse<String> apiResponse = GenericApiResponse.<String>builder()
.status(200)
.message("Answer deleted successfully")
.data("Answer deleted successfully")
.build();
return buildResponse(apiResponse, HttpStatus.OK);
}
catch (Exception e){
ErrorResponse errorResponse = ErrorResponse.builder()
.errorMessage(e.getMessage())
.stackTrace(Arrays.toString(e.getStackTrace()))
.build();
ApiResponseBuilder.buildErrorResponse(String.class, "An error occurred", 500, errorResponse);
return buildResponse(ApiResponseBuilder.buildErrorResponse(String.class, "An error occurred", 500, errorResponse), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping(value=EndpointConstants.QuestionEndpoints.QUESTION_ANSWERS)
public ResponseEntity<GenericApiResponse<GetAnswersResponseDto>>getAnswersForQuestion(@PathVariable(value = "questionId") Long questionId) throws UnauthorizedAccessException {
GetAnswersResponseDto response = answerService.getAnswersForQuestion(questionId);
GenericApiResponse<GetAnswersResponseDto> apiResponse = GenericApiResponse.<GetAnswersResponseDto>builder()
.status(200)
.message("Answers retrieved successfully")
.data(response)
.build();
return buildResponse(apiResponse, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@

import com.group1.programminglanguagesforum.Constants.EndpointConstants;
import com.group1.programminglanguagesforum.DTOs.Requests.CreateQuestionRequestDto;
import com.group1.programminglanguagesforum.DTOs.Requests.UpdateQuestionRequestDto;
import com.group1.programminglanguagesforum.DTOs.Responses.CreateQuestionResponseDto;
import com.group1.programminglanguagesforum.DTOs.Responses.ErrorResponse;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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
@RequestMapping("/api/v1")
Expand All @@ -24,25 +34,102 @@ 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);
}
catch (UnauthorizedAccessException e) {
}

@GetMapping(value = EndpointConstants.QuestionEndpoints.QUESTION_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));
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<CreateQuestionResponseDto> response= ApiResponseBuilder.buildErrorResponse(CreateQuestionResponseDto.class, e.getMessage(), 401, errorResponse);
return buildResponse(response, org.springframework.http.HttpStatus.UNAUTHORIZED);

GenericApiResponse<GetQuestionDetailsResponseDto> response = ApiResponseBuilder.buildErrorResponse(GetQuestionDetailsResponseDto.class, e.getMessage(), 404, errorResponse);
return buildResponse(response, org.springframework.http.HttpStatus.NOT_FOUND);
}
}

@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));
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);
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) {
try {
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) {
ErrorResponse errorResponse = ErrorResponse.builder()
.errorMessage(e.getMessage())
.stackTrace(Arrays.toString(e.getStackTrace()))
.build();
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);
}

}
Loading

0 comments on commit 91113e7

Please sign in to comment.