Skip to content

Commit

Permalink
Merge pull request #85 from capstone-five-ai/dev
Browse files Browse the repository at this point in the history
feat: 무중단 배포 및 버그 수정
  • Loading branch information
yujamint authored Jul 25, 2024
2 parents a62b41e + e93f2a9 commit 954f4d3
Show file tree
Hide file tree
Showing 20 changed files with 120 additions and 114 deletions.
55 changes: 49 additions & 6 deletions .github/workflows/deploy-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,53 @@ jobs:
host: ${{ secrets.PROD_SERVER_HOST }}
username: ${{ secrets.PROD_SERVER_USERNAME }}
key: ${{ secrets.PROD_SERVER_PEM_KEY }}
envs: GITHUB_SHA
envs: GITHUB_SHA, DOCKERHUB_USERNAME
script: |
sudo docker stop $(sudo docker ps -a -q)
sudo docker rm $(sudo docker ps -a -q)
sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server:prod
sudo docker run -d -p 8080:8080 --name qtudy-server-container ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server:prod
sudo docker image prune -f
# 현재 활성화된 Nginx 설정 파일 확인
CURRENT_CONF=$(sudo readlink /etc/nginx/sites-enabled/current)
echo "Current Nginx configuration: $CURRENT_CONF"
if [[ "$CURRENT_CONF" == *"blue" ]]; then
NEW_CONF="/etc/nginx/sites-available/green"
OLD_CONTAINER="blue-container"
NEW_CONTAINER="green-container"
NEW_PORT=8081
else
NEW_CONF="/etc/nginx/sites-available/blue"
OLD_CONTAINER="green-container"
NEW_CONTAINER="blue-container"
NEW_PORT=8080
fi
echo "New Nginx configuration: $NEW_CONF"
echo "Old container: $OLD_CONTAINER"
echo "New container: $NEW_CONTAINER"
echo "New port: $NEW_PORT"
# 새 Docker 이미지 가져오기
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod
# 새로운 컨테이너 실행
sudo docker run -d -p $NEW_PORT:8080 --name $NEW_CONTAINER ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod
# 새로운 컨테이너가 준비될 때까지 대기
echo "Waiting for the new container to be ready..."
sleep 10
# 헬스 체크를 통해 새 컨테이너가 정상적으로 실행 중인지 확인
until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://localhost:$NEW_PORT/actuator/health)" == "200" ]]; do
echo "Waiting for the new container to respond with HTTP 200..."
sleep 2
done
echo "New container is ready. Switching Nginx configuration."
# Nginx 설정 파일 교체 및 재시작
sudo ln -sf $NEW_CONF /etc/nginx/sites-enabled/current
sudo systemctl reload nginx
# 이전 컨테이너 중지 및 제거
sudo docker stop $OLD_CONTAINER
sudo docker rm $OLD_CONTAINER
# 사용하지 않는 Docker 이미지 정리
sudo docker image prune -f
7 changes: 7 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ jobs:
steps:
- name: 체크아웃
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}

- name: 서브모듈 업데이트
run: |
git submodule update --remote
- name: JDK 11 설치
uses: actions/setup-java@v4
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ dependencies {

// Spring Boot Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// GPT 스크립트 테스트 편의성을 위해 추가
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'com.theokanning.openai-gpt3-java:service:0.18.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
import com.app.domain.category.service.CategoryService;
import com.app.domain.member.entity.Member;
import com.app.domain.member.service.MemberService;
import com.app.domain.problem.aigeneratedproblem.dto.ProblemFile.AiRequest.AiGenerateProblemFromAiDto;
import com.app.domain.problem.entity.Problem;
import com.app.domain.problem.membersavedproblem.dto.MemberSavedProblemDto;
import com.app.domain.problem.membersavedproblem.mapper.MemberSavedProblemMapper;
import com.app.domain.problem.service.ProblemService;
import com.app.domain.summary.membersavedsummary.dto.MemberSavedSummaryDto;
import com.app.global.config.ENUM.PdfType;
import com.app.global.config.ENUM.ProblemType;
import com.app.global.error.ErrorCode;
import com.app.global.error.exception.BusinessException;
import com.app.global.error.exception.EntityNotFoundException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -32,6 +36,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.app.global.pdf.ProblemPdfMaker.CreatePdfFile;

@Service
@Transactional
@RequiredArgsConstructor
Expand Down Expand Up @@ -131,103 +137,45 @@ public MemberSavedSummaryDto.pdfResponse createCategorizedProblemsAnswerPdf(Long
}
}

public MemberSavedSummaryDto.pdfResponse createCategorizedProblemsPdf(Long categoryId) throws IOException {
public MemberSavedSummaryDto.pdfResponse createCategorizedProblemsPdf(Long categoryId){
List<CategorizedProblem> categorizedProblemList = categorizedProblemRepository.findByCategoryCategoryId(categoryId);
Category category = categoryService.findVerifiedCategoryByCategoryId(categoryId);
String title = category.getCategoryName();

try (PDDocument document = new PDDocument()) {
PDPage page = new PDPage();
document.addPage(page);
PDPageContentStream contentStream = new PDPageContentStream(document, page);
PDType0Font font = PDType0Font.load(document, getClass().getResourceAsStream("/fonts/malgun.ttf"));
contentStream.setFont(font, 12);
float yPosition = page.getMediaBox().getHeight() - 50;
int linesInCurrentPage = 0;
int maxLinesPerPage = 45;
int problemNumber = 0; // 문제 번호
// Pdf 생성 DTO 변환
AiGenerateProblemFromAiDto[] problems = new AiGenerateProblemFromAiDto[categorizedProblemList.size()];
for(int i = 0; i < categorizedProblemList.size(); i++) {
Problem problem = categorizedProblemList.get(i).getProblem();
problems[i] = AiGenerateProblemFromAiDto.create(
problem.getProblemName(),
problem.getProblemChoices(),
problem.getProblemAnswer(),
problem.getProblemCommentary()
);
}

for (CategorizedProblem categorizedProblem : categorizedProblemList) {
problemNumber++;
Problem problem = categorizedProblem.getProblem();
String problemName = problemNumber + ". " + problem.getProblemName();
File tempFile = null;
byte[] pdfContent = null;

List<String> problemChoices = new ArrayList<>();
if (problem.getProblemType() == ProblemType.MULTIPLE) {
problemChoices = problem.getProblemChoices();
}
float maxWidth = page.getMediaBox().getWidth() - 100; // 페이지 폭에서 양쪽 여백을 뺀 값
List<String> wrappedProblemName = wrapText(problemName, font, 12, maxWidth);
try {
tempFile = CreatePdfFile(category.getCategoryName(), problems, PdfType.PROBLEM); // Problem PDF 생성

for (String line : wrappedProblemName) {
if (linesInCurrentPage >= maxLinesPerPage || yPosition < 50) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
contentStream.setFont(font, 12);
yPosition = page.getMediaBox().getHeight() - 50;
linesInCurrentPage = 0;
}
// 파일을 바이트 배열로 변환
pdfContent = Files.readAllBytes(tempFile.toPath());

contentStream.beginText();
contentStream.newLineAtOffset(50, yPosition);
contentStream.showText(line);
contentStream.endText();
yPosition -= 15; // 줄 간의 간격
linesInCurrentPage++;
}
yPosition -= 10; // 문제명과 선택지 사이의 간격 조정

// 보기 리스트 출력
if (problemChoices != null) {
char choiceLetter = 'A'; // 선지 번호 (알파벳)
int totalChoices = problemChoices.size();

for (String choice : problemChoices) {
List<String> wrappedText = wrapText(choice, font, 12, maxWidth);
boolean isFirstLineOfChoice = true;

for (String line : wrappedText) {
if (linesInCurrentPage >= maxLinesPerPage || yPosition < 50) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
contentStream.setFont(font, 12);
yPosition = page.getMediaBox().getHeight() - 50;
linesInCurrentPage = 0;
}

contentStream.beginText();
contentStream.newLineAtOffset(50, yPosition);

if (isFirstLineOfChoice) {
contentStream.showText(choiceLetter + ". " + line); // 알파벳 번호 추가
isFirstLineOfChoice = false; // 첫 줄 출력 후 false로 변경
} else {
contentStream.showText(line);
}
contentStream.endText();
yPosition -= 15;
linesInCurrentPage++;
}
if (choiceLetter == 'A' + totalChoices - 1) { // 마지막 선지 확인
yPosition -= 20; // 마지막 선지 이후에 더 큰 간격 추가
}
choiceLetter++; // 다음 선지를 위해 알파벳 증가
}
}
} catch (IOException e) {
throw new BusinessException(ErrorCode.NOT_UPLOAD_PROBLEM);
} finally {
// 임시 파일 삭제
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}

contentStream.close();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
document.save(byteArrayOutputStream); // 문서를 ByteArrayOutputStream에 저장
document.close(); // 문서를 닫아 리소스를 해제
return new MemberSavedSummaryDto.pdfResponse(byteArrayOutputStream.toByteArray(), title);
}

return new MemberSavedSummaryDto.pdfResponse(pdfContent, title);
}

@CacheEvict(value = "categorizedProblem", key = "#result.getCategory().getCategoryId()")
public CategorizedProblem updateCategorizedProblem(Long categorizedProblemId, MemberSavedProblemDto.Patch problemPatchDto) {
CategorizedProblem categorizedProblem = findVerifiedCategorizedProblemByCategorizedProblemId(categorizedProblemId);
problemService.updateProblem(
Expand All @@ -238,21 +186,23 @@ public CategorizedProblem updateCategorizedProblem(Long categorizedProblemId, Me
}

@Transactional(readOnly = true)
// @Cacheable(value = "categorizedProblem", key = "#categoryId")
@Cacheable(value = "categorizedProblem", key = "#categoryId")
public Page<CategorizedProblem> findCategorizedProblemsByCategoryId(Long categoryId, int page, int size) {
PageRequest pageRequest = PageRequest.of(page, size);
return categorizedProblemRepository.findByCategoryCategoryId(categoryId, pageRequest);
}

public void deleteCategorizedProblem(Long categorizedProblemID){
@CacheEvict(value = "categorizedProblem", key = "#result")
public Long deleteCategorizedProblem(Long categorizedProblemID) {
CategorizedProblem categorizedProblem = findVerifiedCategorizedProblemByCategorizedProblemId(categorizedProblemID);
categorizedProblemRepository.deleteById(categorizedProblemID);
categorizedProblemRepository.deleteById(categorizedProblem.getCategorizedProblemId());

Problem problem = categorizedProblem.getProblem();
Long problemId = problem.getProblemId();
if (problem.isMemberSavedProblem() && !isProblemUsedInOtherCategorizedProblems(problemId)) {
problemService.deleteProblem(problemId);
}
return categorizedProblem.getCategory().getCategoryId();
}

private boolean isProblemUsedInOtherCategorizedProblems(Long problemId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public SummaryDto.pdfResponse createSummaryPdf(Long categorizedSummaryId) throws
return summaryService.createSummaryPdf(summaryId);
}

@CacheEvict(value = "categorizedSummary", key = "#result.getCategory().getCategoryId()")
public CategorizedSummary updateCategorizedSummary(Long categorizedSummaryId, SummaryDto.Patch summaryPatchDto) {
CategorizedSummary categorizedSummary = findVerifiedCategorizedSummaryByCategorizedSummaryId(categorizedSummaryId);
summaryService.updateSummary(
Expand All @@ -85,7 +86,8 @@ public CategorizedSummary updateCategorizedSummary(Long categorizedSummaryId, Su
return categorizedSummaryRepository.save(categorizedSummary);
}

public void deleteCategorizedSummary(Long categorizedSummaryId) {
@CacheEvict(value = "categorizedSummary", key = "#result")
public Long deleteCategorizedSummary(Long categorizedSummaryId) {
CategorizedSummary categorizedSummary = findVerifiedCategorizedSummaryByCategorizedSummaryId(categorizedSummaryId);

categorizedSummaryRepository.deleteById(categorizedSummaryId);
Expand All @@ -95,6 +97,8 @@ public void deleteCategorizedSummary(Long categorizedSummaryId) {
if (summary.isMemberSavedSummary() && !isSummaryUsedInOtherCategorizedSummarys(summaryId)) {
summaryRepository.deleteById(summaryId);
}
Long categoryId = categorizedSummary.getCategory().getCategoryId();
return categoryId;
}

private boolean isSummaryUsedInOtherCategorizedSummarys(Long summaryId){
Expand All @@ -107,7 +111,7 @@ public CategorizedSummary findVerifiedCategorizedSummaryByCategorizedSummaryId(L
}

@Transactional(readOnly = true)
// @Cacheable(value = "categorizedSummary", key = "#categoryId")
@Cacheable(value = "categorizedSummary", key = "#categoryId")
public Page<CategorizedSummary> findCategorziedSummarysByCategoryId(Long categoryId, int page, int size) {
PageRequest pageRequest = PageRequest.of(page, size);
return categorizedSummaryRepository.findByCategoryCategoryId(categoryId, pageRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ public class AiGenerateProblemFromAiDto {

@Schema(description = "문제 해설", example = "문제 해설 예시")
private String problemCommentary;

public static AiGenerateProblemFromAiDto create(String problemName, List<String> problemChoices, String problemAnswer, String problemCommentary) {
return new AiGenerateProblemFromAiDto(problemName, problemChoices, problemAnswer, problemCommentary);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,7 @@ public void UploadS3(AiGenerateProblemFromAiDto[] aiGenerateProblemFromAiDto, Ai


} catch (IOException e) {

throw new BusinessException(ErrorCode.NOT_UPLOAD_PROBLEM);
e.printStackTrace();
} finally {
if (tempFile != null) {
tempFile.delete(); // 방금 생성한 파일 삭제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public void UploadS3(AiGenerateSummaryFromAiDto aiGenerateSummaryFromAiDto, AiGe


} catch (IOException e) {
throw new BusinessException(ErrorCode.NOT_UPLOAD_PROBLEM);
e.printStackTrace();
} finally {
if (tempFile != null) {
tempFile.delete(); // 방금 생성한 파일 삭제
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.app.integration.fake;
package com.app.fake;

import com.app.domain.member.constant.MemberType;
import com.app.domain.member.constant.Role;
import com.app.domain.member.entity.Member;
import com.app.domain.member.service.MemberService;
import com.app.global.jwt.dto.JwtTokenDto;
import com.app.global.jwt.service.TokenManager;
import com.app.integration.dto.FakeSignUpRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
Expand All @@ -17,7 +16,7 @@
@RequiredArgsConstructor
@Profile(value = {"local", "test"})
@RestController
public class FakeSignUpController {
public class FakeLoginController {

private final MemberService memberService;
private final TokenManager tokenManager;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.app.integration.dto;
package com.app.fake;

import lombok.Builder;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.app.domain.category.dto.CategoryDto;
import com.app.domain.category.dto.CategoryDto.RequestDto;
import com.app.domain.category.contsant.CategoryType;
import com.app.integration.dto.FakeSignUpRequest;
import com.app.fake.FakeSignUpRequest;
import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import com.app.domain.problem.aigeneratedproblem.repository.ProblemFileRepository;
import com.app.domain.summary.aigeneratedsummary.entity.SummaryFile;
import com.app.domain.summary.aigeneratedsummary.repository.SummaryFileRepository;
import com.app.fake.FakeSignUpRequest;
import com.app.global.config.ENUM.PdfType;
import com.app.integration.dto.FakeSignUpRequest;
import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import com.app.domain.problem.aigeneratedproblem.repository.AiGeneratedProblemRepository;
import com.app.domain.problem.aigeneratedproblem.repository.ProblemFileRepository;
import com.app.domain.problem.aigeneratedproblem.service.AiGeneratedProblemService;
import com.app.fake.FakeSignUpRequest;
import com.app.global.config.ENUM.ProblemType;
import com.app.integration.dto.FakeSignUpRequest;
import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
Expand Down
Loading

0 comments on commit 954f4d3

Please sign in to comment.