Skip to content

Commit

Permalink
Merge pull request #567 from SWM-Morandi/feat/#566
Browse files Browse the repository at this point in the history
✨ [FEAT] 시험 다시 풀기 시작 API #562
  • Loading branch information
miiiinju1 authored Nov 2, 2023
2 parents d6410e5 + 6ba8a63 commit 1e5ce08
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 11 deletions.
13 changes: 13 additions & 0 deletions src/main/java/swm_nm/morandi/config/swagger/SwaggerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ public Docket practiceProblemApi() {
.build()
.apiInfo(apiInfo());
}

@Bean
public Docket testRetryApi() {
return new Docket(DocumentationType.OAS_30)
.servers(serverLocal, testServer)
.groupName("test-retry-api")
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("swm_nm.morandi.domain.testRetry.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Practice Swagger")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ public String getLanguageId() {
throw new MorandiException(SubmitErrorCode.LANGUAGE_CODE_NOT_FOUND);
}
}

public static String getLanguageId(Language language) {
try {
return SubmitConstants.valueOf(language.getLanguage()).getLanguageId();
} catch (IllegalArgumentException e) {
throw new MorandiException(SubmitErrorCode.LANGUAGE_CODE_NOT_FOUND);
}
}


}
11 changes: 11 additions & 0 deletions src/main/java/swm_nm/morandi/domain/codeSubmit/dto/SubmitDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package swm_nm.morandi.domain.codeSubmit.dto;

public interface SubmitDto {

String getBojProblemId();
String getLanguageId();

String getSourceCode();


}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import swm_nm.morandi.domain.codeSubmit.constants.CodeVisuabilityConstants;
import swm_nm.morandi.domain.codeSubmit.dto.BaekjoonUserDto;
import swm_nm.morandi.domain.codeSubmit.dto.SolutionIdDto;
import swm_nm.morandi.domain.common.Language;
import swm_nm.morandi.domain.member.entity.Member;
import swm_nm.morandi.domain.member.repository.MemberRepository;
import swm_nm.morandi.domain.testDuring.dto.TestInfo;
Expand Down Expand Up @@ -108,13 +109,15 @@ public ResponseEntity<SolutionIdDto> submit(SubmitCodeDto submitCodeDto) {
String cookie = getCookieFromRedis(generateKey(memberId));
String CSRFKey = getCSRFKey(cookie, submitCodeDto.getBojProblemId());

SolutionIdDto solutionId= sendSubmitRequest(cookie, CSRFKey, submitCodeDto);
SolutionIdDto solutionId = sendSubmitRequest(cookie, CSRFKey, submitCodeDto.getBojProblemId(), submitCodeDto.getLanguage(), submitCodeDto.getSourceCode());

//제출한 코드 정보를 저장
saveSubmitTempCode(submitCodeDto);

return ResponseEntity.status(HttpStatus.OK).body(solutionId);
}


private void validateBojProblemId(String bojProblemId) {
try {
int problemId = Integer.parseInt(bojProblemId);
Expand Down Expand Up @@ -159,10 +162,10 @@ private HttpHeaders createHeaders(String cookie) {
return headers;
}

private SolutionIdDto sendSubmitRequest(String cookie, String CSRFKey, SubmitCodeDto submitCodeDto) {
String acmicpcUrl = String.format("https://www.acmicpc.net/submit/%s", submitCodeDto.getBojProblemId());
private SolutionIdDto sendSubmitRequest(String cookie, String CSRFKey, String bojProblemId, Language language, String sourceCode) {
String acmicpcUrl = String.format("https://www.acmicpc.net/submit/%s", bojProblemId);
HttpHeaders headers = createHeaders(cookie);
MultiValueMap<String, String> parameters = createParameters(submitCodeDto, CSRFKey);
MultiValueMap<String, String> parameters = createParameters(bojProblemId, language,sourceCode, CSRFKey);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(parameters, headers);

try {
Expand Down Expand Up @@ -203,12 +206,12 @@ private SolutionIdDto sendSubmitRequest(String cookie, String CSRFKey, SubmitCod
throw new MorandiException(SubmitErrorCode.BAEKJOON_UNKNOWN_ERROR);
}
//POST로 보낼 때 필요한 파라미터들을 생성
private MultiValueMap<String, String> createParameters(SubmitCodeDto submitCodeDto, String CSRFKey) {
private MultiValueMap<String, String> createParameters(String bojProblemId, Language language, String sourceCode, String CSRFKey) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("problem_id", submitCodeDto.getBojProblemId());
parameters.add("language", submitCodeDto.getLanguageId());
parameters.add("problem_id", bojProblemId);
parameters.add("language", SubmitCodeDto.getLanguageId(language));
parameters.add("code_open", CodeVisuabilityConstants.CLOSE.getCodeVisuability());
parameters.add("source", submitCodeDto.getSourceCode());
parameters.add("source", sourceCode);
parameters.add("csrf_key", CSRFKey);
return parameters;
}
Expand Down Expand Up @@ -253,6 +256,7 @@ private void saveSubmitCodeToDatabase(Long testId, SubmitCodeDto submitCodeDto)
.orElseThrow(() -> new MorandiException(SubmitErrorCode.TEST_NOT_EXIST));

attemptProblem.setSubmitCode(submitCodeDto.getSourceCode());
attemptProblem.setSubmitLanguage(submitCodeDto.getLanguage());
//TODO
//attemptProblem에 마지막으로 제출한 language의 정보가 빠져있음 -> 이거 나중에 추가하던지 해야함
attemptProblemRepository.save(attemptProblem);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,23 @@ public void writeCode(String code, Language language) {
case Cpp -> this.cppCode = code;
case Java -> this.javaCode = code;
}
log.error("writeCode"+code);

this.lastAccessCode = language;
}

public String getCode(String code, Language language) {
switch (language) {
case Python -> {
return this.pythonCode;
}
case Cpp -> {
return this.cppCode;
}
case Java -> {
return this.javaCode;
}
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import swm_nm.morandi.config.configuration.InitialCodeConfig;
import swm_nm.morandi.domain.common.Language;
import swm_nm.morandi.domain.testDuring.dto.TempCodeDto;
import swm_nm.morandi.domain.testRetry.response.RetryAttemptProblemResponse;

import java.util.HashMap;

Expand All @@ -23,4 +24,15 @@ public TempCodeDto getTempCodeDto() {
.lastAccessCode(Language.Cpp)
.build();
}

public RetryAttemptProblemResponse getRetryAttemptProblemResponse() {
HashMap<Language, String> initialCode = initialCodeConfig.getInitialCode();

return RetryAttemptProblemResponse.builder()
.pythonCode(initialCode.get(Language.Python))
.cppCode(initialCode.get(Language.Cpp))
.javaCode(initialCode.get(Language.Java))
.lastAccessCode(Language.Cpp)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.*;
import swm_nm.morandi.domain.common.BaseEntity;
import swm_nm.morandi.domain.common.Language;
import swm_nm.morandi.domain.member.entity.Member;
import swm_nm.morandi.domain.problem.entity.Problem;

Expand All @@ -24,6 +25,10 @@ public class AttemptProblem extends BaseEntity {

private Long executionTime;

@Builder.Default
@Enumerated(EnumType.STRING)
private Language submitLanguage = Language.Cpp;

@Column(columnDefinition = "TEXT")
private String submitCode;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package swm_nm.morandi.domain.testInfo.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import swm_nm.morandi.domain.testDuring.dto.TestStatus;
import swm_nm.morandi.domain.testInfo.entity.Tests;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface TestRepository extends JpaRepository<Tests, Long>{
//Paging하여 테스트 기록을 가져옴
Page<Tests> findAllTestsByMember_MemberIdAndTestStatus(Long memberId, TestStatus testStatus, Pageable pageable);
//1년동안의 테스트 기록을 가져와서 레이팅 반환에 사용함
List<Tests> findAllTestsByMember_MemberIdAndTestStatusAndTestDateAfterOrderByTestDateAsc(Long memberId, TestStatus testStatus, LocalDateTime oneYearAgo);
Long countByMember_MemberIdAndTestStatus(Long memberId, TestStatus testStatus);

Optional<Tests> findTestByTestIdAndMember_MemberId(Long testId, Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
public interface AttemptProblemRepository extends JpaRepository<AttemptProblem, Long> {
List<AttemptProblem> findAllByMember_MemberId(Long memberId);
List<AttemptProblem> findAllByTest_TestId(Long testId);
List<AttemptProblem> findAllByTestOrderByAttemptProblemIdAsc(Tests test);


@EntityGraph(attributePaths = {"test", "problem"})
Optional<AttemptProblem> findByTest_TestIdAndProblem_BojProblemId(Long testId, Long bojProblemId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package swm_nm.morandi.domain.testRetry.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import swm_nm.morandi.domain.testInfo.dto.TestDto;
import swm_nm.morandi.domain.testRetry.request.RetryTestRequest;
import swm_nm.morandi.domain.testRetry.response.TestRetryResponse;
import swm_nm.morandi.domain.testRetry.service.TestRetryService;

@RestController
@RequestMapping("/tests")
@RequiredArgsConstructor
@Tag(name = "TestRetryController", description = "테스트 다시 풀기와 관련된 컨트롤러")
public class TestRetryController {

private final TestRetryService testRetryService;

@PostMapping("/retry")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "테스트 다시 풀기", description = "테스트 다시 풀기")
public TestRetryResponse retryTest(@RequestBody RetryTestRequest retryTestRequest) {
return testRetryService.retryTest(retryTestRequest);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package swm_nm.morandi.domain.testRetry.request;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class RetryTestRequest {
private Long testId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package swm_nm.morandi.domain.testRetry.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import swm_nm.morandi.domain.common.Language;

@Builder
@Getter
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class RetryAttemptProblemResponse {
private Long bojProblemId;
private String pythonCode;
private String javaCode;
private String cppCode;
private Language lastAccessCode;

public void initialRetryAttemptProblemResponse(String code, Language language, Long bojProblemId) {
switch (language) {
case Python -> this.pythonCode = code;
case Cpp -> this.cppCode = code;
case Java -> this.javaCode = code;
}
this.bojProblemId = bojProblemId;
this.lastAccessCode = language;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package swm_nm.morandi.domain.testRetry.response;

import lombok.*;
import swm_nm.morandi.domain.common.Language;
import swm_nm.morandi.domain.testDuring.dto.TempCodeDto;
import swm_nm.morandi.domain.testStart.dto.BojProblemDto;
import swm_nm.morandi.domain.testStart.dto.TestCodeDto;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TestRetryResponse {
private Long testId;
private List<RetryAttemptProblemResponse> retryAttemptProblems;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package swm_nm.morandi.domain.testRetry.service;


import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CachePut;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import swm_nm.morandi.domain.testDuring.dto.TempCodeDto;
import swm_nm.morandi.domain.testDuring.dto.factory.TempCodeFactory;
import swm_nm.morandi.domain.testInfo.entity.AttemptProblem;
import swm_nm.morandi.domain.testInfo.entity.Tests;
import swm_nm.morandi.domain.testInfo.repository.TestRepository;
import swm_nm.morandi.domain.testRecord.repository.AttemptProblemRepository;
import swm_nm.morandi.domain.testRetry.request.RetryTestRequest;
import swm_nm.morandi.domain.testRetry.response.RetryAttemptProblemResponse;
import swm_nm.morandi.domain.testRetry.response.TestRetryResponse;
import swm_nm.morandi.global.exception.MorandiException;
import swm_nm.morandi.global.exception.errorcode.TestErrorCode;
import swm_nm.morandi.global.utils.SecurityUtils;
import swm_nm.morandi.redis.utils.RedisKeyGenerator;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Service
@RequiredArgsConstructor
public class TestRetryService {

private final TestRepository testRepository;

private final RedisKeyGenerator redisKeyGenerator;

private final RedisTemplate<String, Object> redisTemplate;

private final TempCodeFactory tempCodeFactory;
private final Long expireTime = 60000L;

public TestRetryResponse retryTest(RetryTestRequest retryTestRequest) {
Tests test = testRepository.findTestByTestIdAndMember_MemberId(retryTestRequest.getTestId(), SecurityUtils.getCurrentMemberId())
.orElseThrow(() -> new MorandiException(TestErrorCode.TEST_NOT_FOUND));


List<RetryAttemptProblemResponse> attemptProblemResponses =
test.getAttemptProblems().stream()
.map(attemptProblem -> {
RetryAttemptProblemResponse attemptProblemResponse = tempCodeFactory.getRetryAttemptProblemResponse();
attemptProblemResponse.initialRetryAttemptProblemResponse(attemptProblem.getSubmitCode(),
attemptProblem.getSubmitLanguage(),
attemptProblem.getProblem().getBojProblemId());
return attemptProblemResponse;
}).toList();



saveCodeToRedis(retryTestRequest.getTestId(), attemptProblemResponses);

return TestRetryResponse.builder()
.testId(retryTestRequest.getTestId())
.retryAttemptProblems(attemptProblemResponses)
.build();

}

private void saveCodeToRedis(Long testId, List<RetryAttemptProblemResponse> attemptProblemResponses) {
String retryTestTempCodeKey = redisKeyGenerator.generateRetryTestTempCodeKey(testId);

HashOperations<String, String, RetryAttemptProblemResponse> hashOps = redisTemplate.opsForHash();
int problemCount = attemptProblemResponses.size();
IntStream.rangeClosed(1, problemCount).forEach(problemNumber ->
hashOps.put(retryTestTempCodeKey,
String.valueOf(problemNumber),
attemptProblemResponses.get(problemNumber-1))
);
redisTemplate.expire(retryTestTempCodeKey, expireTime, TimeUnit.MINUTES);
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public String generatePracticeProblemTempCodeKey(Long practiceProblemId) {
return String.format("practiceProblemId:%s", practiceProblemId);
}

public String generateRetryTestTempCodeKey(Long testId) {
return String.format("retryTestId:%s", testId);
}

}

0 comments on commit 1e5ce08

Please sign in to comment.