Skip to content

Commit

Permalink
Merge pull request #313 from tipi-tapi/develop
Browse files Browse the repository at this point in the history
Develop 1.3.0
  • Loading branch information
choihuk authored Mar 31, 2024
2 parents d64b6c0 + 49cc9ac commit ae23371
Show file tree
Hide file tree
Showing 59 changed files with 994 additions and 259 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,7 @@ jobs:
add_env_var "RDS_URL" "${{ secrets.RDS_URL }}"
add_env_var "RDS_USERNAME" "${{ secrets.RDS_USERNAME }}"
add_env_var "RDS_PASSWORD" "${{ secrets.RDS_PASSWORD }}"
add_env_var "GPT_CHAT_COMPLETIONS_PROMPT" "${{ secrets.GPT_CHAT_COMPLETIONS_PROMPT }}"
add_env_var "GPT_CHAT_COMPLETIONS_REGENERATE_PROMPT" "${{ secrets.GPT_CHAT_COMPLETIONS_REGENERATE_PROMPT }}"
./deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.springframework.web.client.RestTemplate;

@Configuration
public class ImageGenerateRestTemplateConfig {
public class RestTemplateConfig {

@Value("${openai.api.key}")
private String openaiApiKey;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tipitapi.drawmytoday.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

@Configuration
public class TransactionTemplateConfig {

@Bean
public TransactionTemplate writeTransactionTemplate(
PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setReadOnly(false);
return transactionTemplate;
}

@Bean
public TransactionTemplate readTransactionTemplate(
PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setReadOnly(true);
return transactionTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package tipitapi.drawmytoday.common.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class BusinessException extends RuntimeException {

private final ErrorCode errorCode;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public BusinessException(ErrorCode errorCode, Throwable throwable) {
super(errorCode.getMessage(), throwable);
this.errorCode = errorCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public enum ErrorCode {
EMOTION_NOT_FOUND(404, "E001", "감정을 찾을 수 없습니다."),

// Prompt
PROMPT_NOT_EXIST(500, "P001", "프롬프트가 존재하지 않습니다."),
PROMPT_NOT_FOUND(404, "P001", "프롬프트를 찾을 수 없습니다."),

// R2
R2_SERVICE_ERROR(500, "R001", "R2Exception 에러가 발생하였습니다."),
Expand All @@ -66,6 +66,9 @@ public enum ErrorCode {
// Karlo
KARLO_REQUEST_FAIL(500, "K001", "Karlo 요청에 실패하였습니다."),

// GPT
GPT_REQUEST_FAIL(500, "G001", "GPT 요청에 실패하였습니다."),

// Image InputStream
IMAGE_INPUT_STREAM_FAIL(500, "IIS001", "이미지 스트림을 가져오는데 실패하였습니다."),

Expand All @@ -78,7 +81,10 @@ public enum ErrorCode {
REST_CLIENT_FAILED(500, "R001", "외부로의 REST 통신에 실패하였습니다."),

// Ticket
VALID_TICKET_NOT_EXISTS(404, "T001", "유효한 티켓이 존재하지 않습니다.");
VALID_TICKET_NOT_EXISTS(404, "T001", "유효한 티켓이 존재하지 않습니다."),

// Apple
APPLE_EMAIL_NOT_FOUND(400, "A001", "애플 소셜서버로부터 이메일을 받지 못했습니다.");


private final int status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import java.util.stream.LongStream;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort.Direction;
Expand Down Expand Up @@ -60,4 +61,16 @@ public ResponseEntity<SuccessResponse<Page<GetDiaryAdminResponse>>> getDiaries(
withTest)
).asHttp(HttpStatus.OK);
}

@Operation(summary = "GPT Generator Content 추가")
@GetMapping("/add-gpt-generator-content")
public ResponseEntity<Integer> addGptGeneratorContent(
@AuthUser JwtTokenInfo jwtTokenInfo,
@RequestParam(value = "reputation", required = false, defaultValue = "1") Long reputation
) {
int addedCount = LongStream.range(0, reputation).
mapToObj(i -> adminService.addGptGeneratorContent(jwtTokenInfo.getUserId())).
reduce(0, Integer::sum);
return ResponseEntity.ok(addedCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tipitapi.drawmytoday.domain.admin.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;

@Getter
public class GetDiaryNoteAndPromptResponse {

private final Long promptId;
private String notes;
private final String prompt;

@QueryProjection
public GetDiaryNoteAndPromptResponse(Long promptId, String notes, String prompt) {
this.promptId = promptId;
this.notes = notes;
this.prompt = prompt;
}

public void updateNotes(String notes) {
this.notes = notes;
}

public String getGptPrompt() {
String[] promptTexts = prompt.split("Impressionist oil painting,");
return promptTexts[promptTexts.length - 1].trim();
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,106 @@
package tipitapi.drawmytoday.domain.admin.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import tipitapi.drawmytoday.domain.admin.dto.GetDiaryAdminResponse;
import tipitapi.drawmytoday.domain.admin.dto.GetDiaryNoteAndPromptResponse;
import tipitapi.drawmytoday.domain.diary.domain.Prompt;
import tipitapi.drawmytoday.domain.diary.domain.PromptGeneratorResult;
import tipitapi.drawmytoday.domain.diary.repository.PromptRepository;
import tipitapi.drawmytoday.domain.diary.service.AdminDiaryService;
import tipitapi.drawmytoday.domain.generator.api.gpt.domain.ChatCompletionsRole;
import tipitapi.drawmytoday.domain.generator.api.gpt.domain.Message;
import tipitapi.drawmytoday.domain.generator.api.gpt.dto.GptChatCompletionsRequest;
import tipitapi.drawmytoday.domain.generator.service.TranslateTextService;
import tipitapi.drawmytoday.domain.user.service.ValidateUserService;

@Slf4j
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AdminService {

private final ValidateUserService validateUserService;
private final AdminDiaryService adminDiaryService;
private final TranslateTextService translateTextService;
private final TransactionTemplate writeTransactionTemplate;
private final PromptRepository promptRepository;
private final ObjectMapper objectMapper;
@Value("${openai.gpt.chat_completions_prompt}")
private String gptChatCompletionsPrompt;

@Transactional(readOnly = true)
public Page<GetDiaryAdminResponse> getDiaries(Long userId, int size, int page,
Direction direction, Long emotionId, boolean withTest) {
validateUserService.validateAdminUserById(userId);
return adminDiaryService.getDiaries(size, page, direction, emotionId, withTest);
}

public int addGptGeneratorContent(Long userId) {
validateUserService.validateAdminUserById(userId);
List<GetDiaryNoteAndPromptResponse> responses = adminDiaryService.getDiaryNoteAndPrompt();
ExecutorService executor = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(responses.size());
for (int i = 0; i < responses.size(); i++) {
GetDiaryNoteAndPromptResponse response = responses.get(i);
int finalI = i;
executor.execute(() -> {
try {
String translatedNotes = translateTextService.translateAutoToEnglish(
response.getNotes());
response.updateNotes(translatedNotes);
} catch (Exception e) {
log.error("번역 API 예외가 발생했습니다.", e);
responses.remove(finalI);
} finally {
latch.countDown();
}
});
}

try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("작업이 중단되었습니다.", e);
throw new RuntimeException(e);
}

executor.shutdown();

if (responses.isEmpty()) {
throw new RuntimeException("번역할 데이터가 없거나 모두 실패했습니다.");
}

writeTransactionTemplate.executeWithoutResult(status -> {
for (GetDiaryNoteAndPromptResponse response : responses) {
Prompt prompt = promptRepository.findById(response.getPromptId()).get();
List<Message> messages = GptChatCompletionsRequest.createFirstMessage(
gptChatCompletionsPrompt, response.getNotes())
.getMessages();
messages.add(new Message(ChatCompletionsRole.assistant, response.getGptPrompt()));
String gptResponses;
try {
gptResponses = objectMapper.writeValueAsString(messages);
} catch (JsonProcessingException e) {
log.error("GPT Message를 JSON으로 변환하는데 실패했습니다.", e);
throw new RuntimeException(e);
}
PromptGeneratorResult result = PromptGeneratorResult.createGpt3Result(gptResponses);
prompt.updatePromptGeneratorResult(result);
}
});
return responses.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,19 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import java.util.List;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
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 tipitapi.drawmytoday.common.resolver.AuthUser;
import tipitapi.drawmytoday.common.response.SuccessResponse;
import tipitapi.drawmytoday.common.security.jwt.JwtTokenInfo;
import tipitapi.drawmytoday.domain.diary.dto.CreateDiaryRequest;
import tipitapi.drawmytoday.domain.diary.dto.CreateDiaryResponse;
import tipitapi.drawmytoday.domain.diary.dto.GetDiaryExistByDateResponse;
import tipitapi.drawmytoday.domain.diary.dto.GetDiaryLimitResponse;
import tipitapi.drawmytoday.domain.diary.dto.GetDiaryResponse;
import tipitapi.drawmytoday.domain.diary.dto.GetLastCreationResponse;
import tipitapi.drawmytoday.domain.diary.dto.GetMonthlyDiariesResponse;
import tipitapi.drawmytoday.domain.diary.dto.UpdateDiaryRequest;
import tipitapi.drawmytoday.domain.diary.dto.*;
import tipitapi.drawmytoday.domain.diary.service.CreateDiaryService;
import tipitapi.drawmytoday.domain.diary.service.DiaryService;
import tipitapi.drawmytoday.domain.generator.exception.ImageGeneratorException;
import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/diary")
Expand Down Expand Up @@ -168,10 +153,8 @@ public ResponseEntity<SuccessResponse<CreateDiaryResponse>> createDiary(
@RequestBody @Valid CreateDiaryRequest createDiaryRequest,
@AuthUser @Parameter(hidden = true) JwtTokenInfo tokenInfo
) throws ImageGeneratorException {
return SuccessResponse.of(createDiaryService.createDiary(tokenInfo.getUserId(),
createDiaryRequest.getEmotionId(),
createDiaryRequest.getKeyword(), createDiaryRequest.getNotes(),
createDiaryRequest.getDiaryDate(), createDiaryRequest.getUserTime())
return SuccessResponse.of(
createDiaryService.createDiary(tokenInfo.getUserId(), createDiaryRequest)
).asHttp(HttpStatus.CREATED);
}

Expand Down Expand Up @@ -256,9 +239,11 @@ public ResponseEntity<SuccessResponse<GetDiaryLimitResponse>> getDrawLimit(
@PostMapping("/{id}/regenerate")
public ResponseEntity<Void> regenerateDiaryImage(
@AuthUser JwtTokenInfo tokenInfo,
@Parameter(description = "일기 id", in = ParameterIn.PATH) @PathVariable("id") Long diaryId
@Parameter(description = "일기 id", in = ParameterIn.PATH) @PathVariable("id") Long diaryId,
@RequestBody(required = false) RegenerateDiaryRequest request
) throws ImageGeneratorException {
createDiaryService.regenerateDiaryImage(tokenInfo.getUserId(), diaryId);
String diary = request == null ? "" : request.getDiary();
createDiaryService.regenerateDiaryImage(tokenInfo.getUserId(), diaryId, diary);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import tipitapi.drawmytoday.common.entity.BaseEntityWithUpdate;
import tipitapi.drawmytoday.domain.diary.exception.ImageNotFoundException;
import tipitapi.drawmytoday.domain.emotion.domain.Emotion;
import tipitapi.drawmytoday.domain.user.domain.User;

Expand Down Expand Up @@ -110,5 +111,12 @@ public static Diary ofTest(User user, Emotion emotion, LocalDateTime diaryDateTi
public void setNotes(String notes) {
this.notes = notes;
}


public Image getSelectedImage() {
return imageList.stream()
.filter(Image::isSelected)
.findFirst()
.orElseThrow(ImageNotFoundException::new);
}

}
13 changes: 10 additions & 3 deletions src/main/java/tipitapi/drawmytoday/domain/diary/domain/Image.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tipitapi.drawmytoday.domain.diary.domain;

import java.time.LocalDateTime;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
Expand Down Expand Up @@ -31,6 +32,11 @@ public class Image extends BaseEntity {
@JoinColumn(name = "diary_id", nullable = false)
private Diary diary;

@NotNull
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "prompt_id", nullable = false)
private Prompt prompt;

@NotNull
@Column(nullable = false)
private String imageUrl;
Expand All @@ -42,15 +48,16 @@ public class Image extends BaseEntity {

private LocalDateTime deletedAt;

private Image(Diary diary, String imageUrl, boolean isSelected) {
private Image(Diary diary, Prompt prompt, String imageUrl, boolean isSelected) {
this.diary = diary;
diary.getImageList().add(this);
this.prompt = prompt;
this.imageUrl = imageUrl;
this.isSelected = isSelected;
}

public static Image create(Diary diary, String imageUrl, boolean isSelected) {
return new Image(diary, imageUrl, isSelected);
public static Image create(Diary diary, Prompt prompt, String imageUrl, boolean isSelected) {
return new Image(diary, prompt, imageUrl, isSelected);
}

public void setSelected(boolean isSelected) {
Expand Down
Loading

0 comments on commit ae23371

Please sign in to comment.