diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd51998..aa2962e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,4 @@ name: Build and Deploy to EC2 - # 워크플로우가 언제 실행될 것인지 조건 명시 on: push: @@ -39,8 +38,10 @@ jobs: touch ./application-common.yml touch ./application-prod.yml echo "${{ secrets.APPLICATION }}" > ./application.yml - echo "${{ secrets.COMMON }}" > ./application-common.yml - echo "${{ secrets.PROD }}" > ./application-prod.yml + echo "${{ secrets.APPLICATION }}" > ./application-common.yml + echo "${{ secrets.APPLICATION }}" > ./application-prod.yml +# echo "${{ secrets.COMMON }}" > ./application-common.yml +# echo "${{ secrets.PROD }}" > ./application-prod.yml # 권한 부여 - name: Grant execute permission for gradlew @@ -59,7 +60,7 @@ jobs: - name: AWS credential 설정 uses: aws-actions/configure-aws-credentials@v1 with: - aws-region: ${{ env.AWS_REGION }} + aws-region: ${{ secrets.AWS_REGION }} aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }} diff --git a/build.gradle b/build.gradle index 88426c9..aaa5f26 100644 --- a/build.gradle +++ b/build.gradle @@ -38,8 +38,14 @@ dependencies { implementation group: 'com.querydsl', name: 'querydsl-jpa', version: '5.0.0' implementation group: 'com.auth0', name: 'java-jwt', version: '4.4.0' + + //스웨거 + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' } tasks.named('test') { useJUnitPlatform() } +jar { + enabled = false +} \ No newline at end of file diff --git a/src/main/java/com/example/glowtales/config/SwaggerConfig.java b/src/main/java/com/example/glowtales/config/SwaggerConfig.java new file mode 100644 index 0000000..429d4a2 --- /dev/null +++ b/src/main/java/com/example/glowtales/config/SwaggerConfig.java @@ -0,0 +1,24 @@ +package com.example.glowtales.config; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.OpenAPI; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Member and Post API") + .version("1.0") + .description("API for managing members and their posts") + .contact(new Contact() + .name("Your Name") + .email("your.email@example.com") + .url("https://your-website.com"))); + } +} diff --git a/src/main/java/com/example/glowtales/controller/TaleController.java b/src/main/java/com/example/glowtales/controller/TaleController.java new file mode 100644 index 0000000..d0f79ad --- /dev/null +++ b/src/main/java/com/example/glowtales/controller/TaleController.java @@ -0,0 +1,71 @@ +package com.example.glowtales.controller; + +import com.example.glowtales.dto.response.TaleResponseDto; +import com.example.glowtales.service.TaleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1") +@Tag(name = "Tale API", description = "API for managing tales") +public class TaleController { + +//#001 전체 동화 상태창 불러오기 +// +//#002 단어장 미리보기 불러오기 +//#003 단어장 모두 불러오기 +// +//#008 동화 만들기 +//#009 동화의 학습 언어 선택하기 +//#010 사진에서 키워드 추출하기 +// +//#011 동화의 퀴즈와 정답 불러오기 +//#012 동화 퀴즈의 답 제출하기 + + + + private final TaleService tale_service; + + @Operation(summary = "#004 완료하지 않은 동화 미리보기 불러오기", description = "학습을 완료하지 않은 동화 중 최신 동화를 3개 불러오는 API입니다.") + @GetMapping("/member/{memberId}/1") + public ResponseEntity> getUnlearnedTalesTop3ByMemberId(@PathVariable Long memberId) { + List posts = tale_service.getUnlearnedTaleTop3ByMemberId(memberId); + return ResponseEntity.ok(posts); + } + + @Operation(summary = "#005 완료하지 않은 동화 모두 불러오기", description = "학습을 완료하지 않은 동화를 최신순으로 불러오는 API입니다.") + @GetMapping("/member/{memberId}/2") + public ResponseEntity> getUnlearnedTalesByMemberId(@PathVariable Long memberId) { + List posts = tale_service.getUnlearnedTaleByMemberId(memberId); + return ResponseEntity.ok(posts); + } + + @Operation(summary = "#006 최근 학습한 동화 미리보기 불러오기", description = "최근 학습한 동화 중 최신 동화를 3개 불러오는 API입니다.") + @GetMapping("/member/{memberId}/3") + public ResponseEntity> getStudiedTalesTop3ByMemberId(@PathVariable Long memberId) { + List posts = tale_service.getStudiedTaleTop3ByMemberId(memberId); + return ResponseEntity.ok(posts); + } + + @Operation(summary = "#007 최근 학습한 동화 모두 불러오기", description = "최근 학습한 동화를 최신순으로 불러오는 API입니다.") + @GetMapping("/member/{memberId}/4") + public ResponseEntity> getStudiedTalesByMemberId(@PathVariable Long memberId) { + List posts = tale_service.getStudiedTaleByMemberId(memberId); + return ResponseEntity.ok(posts); + } + + +} diff --git a/src/main/java/com/example/glowtales/converter/YesOrNoConverter.java b/src/main/java/com/example/glowtales/converter/YesOrNoConverter.java new file mode 100644 index 0000000..fd6d327 --- /dev/null +++ b/src/main/java/com/example/glowtales/converter/YesOrNoConverter.java @@ -0,0 +1,20 @@ +package com.example.glowtales.converter; + + +import com.example.glowtales.domain.YesOrNo; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class YesOrNoConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(YesOrNo attribute) { + return attribute != null ? attribute.getValue() : null; + } + + @Override + public YesOrNo convertToEntityAttribute(Integer dbData) { + return dbData != null ? YesOrNo.fromValue(dbData) : null; + } +} diff --git a/src/main/java/com/example/glowtales/domain/Choice.java b/src/main/java/com/example/glowtales/domain/Choice.java index 06b03b5..fab3f0f 100644 --- a/src/main/java/com/example/glowtales/domain/Choice.java +++ b/src/main/java/com/example/glowtales/domain/Choice.java @@ -1,5 +1,6 @@ package com.example.glowtales.domain; +import com.example.glowtales.converter.YesOrNoConverter; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -24,6 +25,6 @@ public class Choice { //TODO 변수명 변경 필요 private String sunji; - @Enumerated(EnumType.STRING) + @Convert(converter = YesOrNoConverter.class) private YesOrNo is_correct; } diff --git a/src/main/java/com/example/glowtales/domain/LanguageTale.java b/src/main/java/com/example/glowtales/domain/LanguageTale.java index bbaa6c9..f0e5954 100644 --- a/src/main/java/com/example/glowtales/domain/LanguageTale.java +++ b/src/main/java/com/example/glowtales/domain/LanguageTale.java @@ -1,5 +1,6 @@ package com.example.glowtales.domain; +import com.example.glowtales.converter.YesOrNoConverter; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -27,7 +28,7 @@ public class LanguageTale { private String title; - @Enumerated(EnumType.STRING) + @Convert(converter = YesOrNoConverter.class) private YesOrNo is_learned; } diff --git a/src/main/java/com/example/glowtales/domain/YesOrNo.java b/src/main/java/com/example/glowtales/domain/YesOrNo.java index 46f9010..ec61af4 100644 --- a/src/main/java/com/example/glowtales/domain/YesOrNo.java +++ b/src/main/java/com/example/glowtales/domain/YesOrNo.java @@ -1,5 +1,25 @@ package com.example.glowtales.domain; + +import lombok.Getter; + +@Getter public enum YesOrNo { - YES, NO + YES(0), + NO(1); + + private final int value; + + YesOrNo(int value) { + this.value = value; + } + + public static YesOrNo fromValue(int value) { + for (YesOrNo yesOrNo : YesOrNo.values()) { + if (yesOrNo.getValue() == value) { + return yesOrNo; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } } diff --git a/src/main/java/com/example/glowtales/dto/response/LanguageTaleTitleResponseDto.java b/src/main/java/com/example/glowtales/dto/response/LanguageTaleTitleResponseDto.java new file mode 100644 index 0000000..3545523 --- /dev/null +++ b/src/main/java/com/example/glowtales/dto/response/LanguageTaleTitleResponseDto.java @@ -0,0 +1,17 @@ +package com.example.glowtales.dto.response; + +import com.example.glowtales.domain.LanguageTale; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LanguageTaleTitleResponseDto { + private String title; + + @Builder + public LanguageTaleTitleResponseDto(LanguageTale languageTale) { + this.title = languageTale.getTitle(); + } +} diff --git a/src/main/java/com/example/glowtales/dto/response/TaleResponseDto.java b/src/main/java/com/example/glowtales/dto/response/TaleResponseDto.java new file mode 100644 index 0000000..d2148c9 --- /dev/null +++ b/src/main/java/com/example/glowtales/dto/response/TaleResponseDto.java @@ -0,0 +1,35 @@ +package com.example.glowtales.dto.response; + +import com.example.glowtales.domain.Tale; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.stream.Collectors; + +@Getter +@Setter +public class TaleResponseDto { + private Long tale_id; + private LocalDateTime created_at; + private Long member_id; + private LanguageTaleTitleResponseDto title; + + @Builder + public TaleResponseDto(Tale tale) { + this.tale_id = tale.getId(); + this.created_at = tale.getCreated_at(); + this.member_id = tale.getMember().getId(); + this.title = tale.getLanguage_tale_list().stream() +// .filter(languageTale -> isKorean(languageTale.getLanguage().getId())) + .findFirst() + .map(LanguageTaleTitleResponseDto::new) + .orElse(null); + + } + +// private boolean isKorean(Long language_id) { +// return language_id == 1; +// } +} \ No newline at end of file diff --git a/src/main/java/com/example/glowtales/repository/LanguageTaleRepository.java b/src/main/java/com/example/glowtales/repository/LanguageTaleRepository.java new file mode 100644 index 0000000..56e2129 --- /dev/null +++ b/src/main/java/com/example/glowtales/repository/LanguageTaleRepository.java @@ -0,0 +1,8 @@ +package com.example.glowtales.repository; + +import com.example.glowtales.domain.LanguageTale; +import com.example.glowtales.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LanguageTaleRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/glowtales/repository/MemberRepository.java b/src/main/java/com/example/glowtales/repository/MemberRepository.java new file mode 100644 index 0000000..47d996b --- /dev/null +++ b/src/main/java/com/example/glowtales/repository/MemberRepository.java @@ -0,0 +1,8 @@ +package com.example.glowtales.repository; + +import com.example.glowtales.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { +} + diff --git a/src/main/java/com/example/glowtales/repository/TaleRepository.java b/src/main/java/com/example/glowtales/repository/TaleRepository.java new file mode 100644 index 0000000..5bc7583 --- /dev/null +++ b/src/main/java/com/example/glowtales/repository/TaleRepository.java @@ -0,0 +1,10 @@ +package com.example.glowtales.repository; + +import com.example.glowtales.domain.Tale; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TaleRepository extends JpaRepository { + List findByMemberId(Long memberId); +} diff --git a/src/main/java/com/example/glowtales/service/TaleService.java b/src/main/java/com/example/glowtales/service/TaleService.java new file mode 100644 index 0000000..b079e0a --- /dev/null +++ b/src/main/java/com/example/glowtales/service/TaleService.java @@ -0,0 +1,73 @@ +package com.example.glowtales.service; + +import com.example.glowtales.domain.Tale; +import com.example.glowtales.dto.response.TaleResponseDto; +import com.example.glowtales.repository.TaleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class TaleService { + + private final TaleRepository tale_repository; + + @Autowired + public TaleService(TaleRepository tale_repository) { + this.tale_repository = tale_repository; + } + + //#004 완료하지 않은 동화 미리보기 불러오기 + public List getUnlearnedTaleTop3ByMemberId(Long memberId) { + List tales = tale_repository.findByMemberId(memberId); + return tales.stream() + .filter(tale -> tale.getLanguage_tale_list().stream() + .anyMatch(languageTale -> languageTale.getLanguage().getId() == 1 && languageTale.getIs_learned().getValue() == 2)) + + .sorted(Comparator.comparing(Tale::getStudied_at).reversed()) + .limit(3) + .map(TaleResponseDto::new) + .collect(Collectors.toList()); + } + + //#005 완료하지 않은 동화 모두 불러오기 + public List getUnlearnedTaleByMemberId(Long memberId) { + List tales = tale_repository.findByMemberId(memberId); + return tales.stream() + .filter(tale -> tale.getLanguage_tale_list().stream() + .anyMatch(languageTale -> languageTale.getLanguage().getId() == 1 && languageTale.getIs_learned().getValue() == 2)) + + .sorted(Comparator.comparing(Tale::getStudied_at).reversed()) + .map(TaleResponseDto::new) + .collect(Collectors.toList()); + } + + //#006 최근 학습한 동화 미리보기 불러오기 + public List getStudiedTaleTop3ByMemberId(Long memberId) { + List tales = tale_repository.findByMemberId(memberId); + return tales.stream() + .filter(tale -> tale.getLanguage_tale_list().stream() + .anyMatch(languageTale -> languageTale.getLanguage().getId() == 1 && languageTale.getIs_learned().getValue() == 1)) + .sorted(Comparator.comparing(Tale::getStudied_at).reversed()) + .limit(3) + .map(TaleResponseDto::new) + .collect(Collectors.toList()); + } + + //#007 최근 학습한 동화 모두 불러오기 + public List getStudiedTaleByMemberId(Long memberId) { + List tales = tale_repository.findByMemberId(memberId); + return tales.stream() + .filter(tale -> tale.getLanguage_tale_list().stream() + .anyMatch(languageTale -> languageTale.getLanguage().getId() == 1 && languageTale.getIs_learned().getValue() == 1)) + + .sorted(Comparator.comparing(Tale::getStudied_at).reversed()) + .map(TaleResponseDto::new) + .collect(Collectors.toList()); + } + + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..8863d7a --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,44 @@ +server: + port: 8080 + servlet: + context-path: / + encoding: + charset: UTF-8 + enabled: true + force: true + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://13.209.226.209:3306/glowTales?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + #username: root + #password: my-secret-pw + #url: jdbc:mysql://localhost:3306/glowTales?serverTimezone=Asia/Seoul + username: glow + password: glow1234 + + jpa: + hibernate: + ddl-auto: update #create update none + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + show-sql: true + +springdoc: + swagger-ui: + groups-order: DESC + tags-sorter: alpha + operations-sorter: method + disable-swagger-default-url: true + display-request-duration: true + defaultModelsExpandDepth: 2 + defaultModelExpandDepth: 2 + api-docs: + path: /api-docs + show-actuator: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + writer-with-default-pretty-printer: true + model-and-view-allowed: true + paths-to-match: + - /api/v1/** \ No newline at end of file