diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..5d47a96 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,67 @@ +name: Deploy to Amazon EC2 + +on: + push: + branches: [ "main" ] + +env: + AWS_REGION: ap-northeast-2 + S3_BUCKET_NAME: eday-s3-bucket + CODE_DEPLOY_APPLICATION_NAME: eday-codedeploy-app + CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: eday-codedeploy-deploy-group + APPLICATION: ${{ secrets.APPLICATION }} + +permissions: + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + environment: production + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - uses: actions/checkout@v3 + - run: mkdir src/main/resources + - run: touch ./src/main/resources/application.yml + - run: echo "${{ env.APPLICATION }}" > ./src/main/resources/application.yml + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + arguments: clean build -x test + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Upload to AWS S3 + run: | + aws deploy push \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --ignore-hidden-files \ + --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \ + --source . + + - name: Deploy to AWS EC2 from S3 + run: | + aws deploy create-deployment \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \ + --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip diff --git a/.gitignore b/.gitignore index 94f15a7..498f61c 100644 --- a/.gitignore +++ b/.gitignore @@ -175,6 +175,7 @@ gradle-app.setting .idea/ application.yml +application.yaml .env # End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij,macos,windows diff --git a/README.md b/README.md index 92bf588..2beadbb 100644 --- a/README.md +++ b/README.md @@ -1 +1,167 @@ -# EDAY-BACK +# 🌱 Ewha-Day, E-Day + +> EFUB 3rd SWS(Summer Web Surf) 2νŒ€ E-Day Project + +![](https://velog.velcdn.com/images/chhaewxn/post/b7a59ccb-3ef9-474e-9cea-616ace518712/image.png) + +
+ +## πŸͺ΄ ν”„λ‘œμ νŠΈ μ„€λͺ… + + + +
+μŠ€ν¬λ¦°μƒ· + + + + + + + + + + + +
+ +### πŸ•ŠοΈ μ˜ˆλΉ„ 이화인을 μœ„ν•œ ν€΄μ¦ˆ μ„œλΉ„μŠ€ + +> μ˜ˆλΉ„ 벗듀이 κ°œκ°• D-7λΆ€ν„° ν•˜λ£¨ν•˜λ£¨ μ—΄λ¦¬λŠ” ν€΄μ¦ˆλ₯Ό λ§žμΆ”λ©° μ΄ν™”μ—¬λŒ€μ— λŒ€ν•΄ μ•Œμ•„κ°€λŠ” ν€΄μ¦ˆ+정보 μ‚¬μ΄νŠΈμž…λ‹ˆλ‹€. μ΄ν™”μΈμœΌλ‘œμ„œ μ•Œμ•„λ‘λ©΄ μ“Έλͺ¨ μžˆλŠ”, μ•Œμ•„λ‘λ©΄ 쒋을 지식과 κΏ€νŒλ“€μ„ λ°›μ•„κ°ˆ 수 μžˆλŠ” μœ μš©ν•œ μ‚¬μ΄νŠΈκ°€ 될 +> κ²ƒμž…λ‹ˆλ‹€. D-7λΆ€ν„° ν€΄μ¦ˆλ₯Ό ν•˜λ‚˜μ”© 맞좜 λ•Œλ§ˆλ‹€ 메인 화면에 μžˆλŠ” 학ꡐ 지도에 색이 μž…ν˜€μ§€λ©΄μ„œ, κ°œκ°• λ‚ μ—λŠ” 색이 λͺ¨λ‘ 칠해진 학ꡐ 지도λ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€. + +### πŸ“† 개발 κΈ°κ°„ + +- ν”„λ‘œμ νŠΈ μ„ΈνŒ…: 2023.07.04. - 2023.07.09. +- API 개발: 2023.07.10. - 2023.07.24. +- 배포 및 API μ—°κ²°: 2023.07.25 - 2023.08. + +
+ +## πŸ‘©β€πŸ’» νŒ€μ› μ†Œκ°œ + +| κΆŒλ―Όμ•„ | μ΅œμœ€μ§€ | 솑채원 | μ΄ν•œλ‚˜ | +|:-----------------------------------------------------------------------------------------:|:------------------------------------------------------------------:|:---------------------------------------------------------------------------:|:-----------------------------------------------------------------:| +| | | | +| [@mingulmangul](https://github.com/mingulmangul) | [@choiyounji](https://github.com/choiyounji) | [@chhaewxn](https://github.com/chhaewxn) | [@hannah0226](https://github.com/hannah0226) | +| ν”„λ‘œμ νŠΈ μ„ΈνŒ… 및 μ—”ν‹°ν‹° 생성
CI/CD ν™˜κ²½ ꡬ좕
카카였 OAuth 둜그인 개발
μ‚¬μš©μž 정보 쑰회 API 개발
DB 섀계 및 데이터 ꡬ좕 | 카카였 OAuth 둜그인 개발
μ‚¬μš©μž API 개발
API λͺ…μ„Έμ„œ μž‘μ„±
μœ μ € 정보 μ €μž₯ DB 섀계 | 좔가정보 API 개발
λ¬Έμ˜μ‚¬ν•­ API 개발
μ‚¬μš©μž 칭호 API 개발
데이터 μ‚½μž… SQLλ¬Έ μž‘μ„±
API λͺ…μ„Έμ„œ μž‘μ„± | ν€΄μ¦ˆ λ‚΄μš© 보기 API 개발
ν€΄μ¦ˆ μ •λ‹΅ 확인 API 개발
API λͺ…μ„Έμ„œ μž‘μ„± + +
+ +### πŸ“š APIs + +| View | Method | Detail | Developer | +|:--------:|:------:|:-----------:|:---------:| +| μ‚¬μš©μž API | POST | μ‚¬μš©μž 둜그인 | μœ€μ§€/λ―Όμ•„ | +| | GET | μ‚¬μš©μž 정보 보기 | μœ€μ§€/λ―Όμ•„ | +| ν€΄μ¦ˆ API | GET | ν€΄μ¦ˆ λ‚΄μš© 보기 | ν•œλ‚˜ | +| | POST | ν€΄μ¦ˆ μ •λ‹΅ 선택 | ν•œλ‚˜ | +| 문의 API | POST | λ¬Έμ˜μ‚¬ν•­ μž‘μ„±ν•˜κΈ° | 채원 | +| 좔가정보 API | GET | 좔가정보 νŽ˜μ΄μ§€ 보기 | 채원 | +| 칭호 API | GET | 칭호 νŽ˜μ΄μ§€ 보기 | 채원 | + +
+ +## πŸ“Œ Commit Convention + +### [TAG] λ©”μ‹œμ§€ + +| νƒœκ·Έ 이름 | μ„€λͺ… | +|:--------:|:---------------------------:| +| feat | μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€ | +| fix | 버그, 였λ₯˜ μˆ˜μ • | +| style | μ½”λ“œ ν¬λ§·νŒ…, μ˜€νƒ€ μˆ˜μ •, 주석 μˆ˜μ • 및 μ‚­μ œ λ“± | +| docs | λ¬Έμ„œ μˆ˜μ • | +| chore | λΉŒλ“œ 및 νŒ¨ν‚€μ§€ μˆ˜μ • 및 μ‚­μ œ | +| refactor | μ½”λ“œ λ¦¬νŒ©ν† λ§ | +| setting | ν™˜κ²½μ„€μ • | + +### πŸͺ΅ Branch Strategy + +1. issue 생성 +2. local - feature/~ μ—μ„œ 각자 κΈ°λŠ₯ μž‘μ—… +3. remote - feature/~ 에 Push +4. remote - develop 으둜 Pull Request +5. μ½”λ“œ 리뷰 ν›„ remote - develop Merge +6. remote - develop 에 Merge 될 λ•Œ local - develop pull λ°›μ•„ μ΅œμ‹  μƒνƒœ μœ μ§€ + +
+ +## βš™οΈ 기술 아킀텍쳐 + +### μ‚¬μš© μŠ€νƒ + +| 톡합 개발 ν™˜κ²½ | IntelliJ | +|------------------|---------------------------------| +| Spring 버전 | 2.7.11 | +| λ°μ΄ν„°λ² μ΄μŠ€ | AWS RDS(MySQL) | +| 배포 | AWS EC2(Ubuntu), S3, CodeDepoly | +| Project λΉŒλ“œ 관리 도ꡬ | Gradle | +| CI/CD 툴 | Github Actions | +| ERD λ‹€μ΄μ–΄κ·Έλž¨ 툴 | ERD Cloud | +| Java version | Java 11 | + +
+ +### 아킀텍쳐 ꡬ쑰 + +image + +
+ +## ☁️ ERD + +![](https://velog.velcdn.com/images/chhaewxn/post/6976bd24-ca03-405f-9083-02c320313816/image.png) + +
+ +## πŸ“ ν”„λ‘œμ νŠΈ 폴더 ꡬ쑰 + +``` +πŸ“‚ src/main/java/efub/eday + └── edayback + β”œβ”€β”€ domain + β”‚Β Β  β”œβ”€β”€ day + β”‚Β Β  β”‚ β”œβ”€β”€ dday + | | | β”œβ”€β”€ entity + β”‚ β”‚ β”‚ └── repository + β”‚Β Β  β”‚ β”œβ”€β”€ info + | | | β”œβ”€β”€ controller + β”‚ β”‚ β”‚ β”œβ”€β”€ dto + β”‚ β”‚ β”‚ β”œβ”€β”€ entity + β”‚ β”‚ β”‚ β”œβ”€β”€ repository + β”‚ β”‚ β”‚ └── service + β”‚Β Β  β”‚ β”œβ”€β”€ quiz + | | | β”œβ”€β”€ controller + β”‚ β”‚ β”‚ β”œβ”€β”€ dto + β”‚ β”‚ β”‚ β”œβ”€β”€ entity + β”‚ β”‚ β”‚ β”œβ”€β”€ repository + β”‚ β”‚ β”‚ └── service + β”‚Β Β  β”‚ └── title + | | β”œβ”€β”€ controller + β”‚ β”‚ β”œβ”€β”€ dto + β”‚ β”‚ β”œβ”€β”€ entity + β”‚ β”‚ β”œβ”€β”€ repository + β”‚ β”‚ └── service + β”‚Β Β  β”œβ”€β”€ global + β”‚Β Β  β”‚ └── exception + β”‚Β Β  β”œβ”€β”€ member + | | β”œβ”€β”€ auth + β”‚ β”‚ β”œβ”€β”€ controller + β”‚ β”‚ β”œβ”€β”€ dto + β”‚ β”‚ β”œβ”€β”€ entity + β”‚ β”‚ β”œβ”€β”€ repository + β”‚ β”‚ └── service + β”‚Β Β  └── query + | β”œβ”€β”€ controller + β”‚ β”œβ”€β”€ dto + β”‚ β”œβ”€β”€ entity + β”‚ β”œβ”€β”€ repository + β”‚ └── service + └── global + β”œβ”€β”€ config + β”œβ”€β”€ feign + └── jwt +``` + + diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..c5ad304 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,22 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/eday + overwrite: yes +file_exists_behavior: OVERWRITE +permissions: + - object: / + pattern: "**" + owner: ubuntu + group: ubuntu +hooks: + AfterInstall: + - location: scripts/stop.sh + timeout: 60 + runas: ubuntu + ApplicationStart: + - location: scripts/start.sh + timeout: 60 + runas: ubuntu diff --git a/build.gradle b/build.gradle index 25e97a6..7b17c2d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.1.1' + id 'org.springframework.boot' version '2.7.11' id 'io.spring.dependency-management' version '1.1.0' } @@ -8,7 +8,11 @@ group = 'efub.eday' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '11' +} + +jar { + enabled = false } configurations { @@ -21,10 +25,31 @@ repositories { mavenCentral() } +ext { + set('springCloudVersion', "2021.0.8") +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' + // Spring web implementation 'org.springframework.boot:spring-boot-starter-web' + // Spring Data JPA + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' + // JWT + implementation 'io.jsonwebtoken:jjwt:0.9.1' + // OpenFeign + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + // Swagger + implementation 'org.springdoc:springdoc-openapi-ui:1.7.0' + implementation 'org.springdoc:springdoc-openapi-security:1.7.0' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..55d2002 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/eday" +JAR_FILE="$PROJECT_ROOT/eday-webapp.jar" + +APP_LOG="$PROJECT_ROOT/application.log" +ERROR_LOG="$PROJECT_ROOT/error.log" +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +echo "$TIME_NOW > $JAR_FILE 파일 볡사" >> $DEPLOY_LOG +cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE + +echo "$TIME_NOW > $JAR_FILE 파일 μ‹€ν–‰" >> $DEPLOY_LOG +nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & + +CURRENT_PID=$(pgrep -f $JAR_FILE) +echo "$TIME_NOW > μ‹€ν–‰λœ ν”„λ‘œμ„ΈμŠ€ 아이디 $CURRENT_PID μž…λ‹ˆλ‹€." >> $DEPLOY_LOG diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 0000000..1d2ef9a --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/eday" +JAR_FILE="$PROJECT_ROOT/eday-webapp.jar" + +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +CURRENT_PID=$(pgrep -f $JAR_FILE) + +if [ -z $CURRENT_PID ]; then + echo "$TIME_NOW > ν˜„μž¬ 싀행쀑인 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ—†μŠ΅λ‹ˆλ‹€" >> $DEPLOY_LOG +else + echo "$TIME_NOW > 싀행쀑인 $CURRENT_PID μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ’…λ£Œ " >> $DEPLOY_LOG + kill -15 $CURRENT_PID +fi diff --git a/src/main/java/efub/eday/edayback/domain/day/dday/entity/Dday.java b/src/main/java/efub/eday/edayback/domain/day/dday/entity/Dday.java new file mode 100644 index 0000000..f53432c --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/dday/entity/Dday.java @@ -0,0 +1,30 @@ +package efub.eday.edayback.domain.day.dday.entity; + +public enum Dday { + SEVEN(7), + SIX(6), + FIVE(5), + FOUR(4), + THREE(3), + TWO(2), + ONE(1); + + private final int remainingDays; + + Dday(int remainingDays) { + this.remainingDays = remainingDays; + } + + public int getRemainingDays() { + return remainingDays; + } + + public static Dday fromRemainingDays(int remainingDays) { // λ¬Έμžμ—΄ 값을 μ—΄κ±°ν˜•μœΌλ‘œ λ³€ν™˜ν•˜λŠ” λ©”μ†Œλ“œλ₯Ό μΆ”κ°€ + for (Dday dday : Dday.values()) { + if (dday.getRemainingDays() == remainingDays) { + return dday; + } + } + throw new IllegalArgumentException("Invalid remaining days value: " + remainingDays); + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/dday/entity/Subject.java b/src/main/java/efub/eday/edayback/domain/day/dday/entity/Subject.java new file mode 100644 index 0000000..5dc369c --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/dday/entity/Subject.java @@ -0,0 +1,47 @@ +package efub.eday.edayback.domain.day.dday.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Getter; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor +@Table(name = "subjects") +public class Subject { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "subject_id") + private Integer id; + + @Enumerated(value = EnumType.STRING) + @Column(name = "d_day", nullable = false) + private Dday dday; + + @Column(nullable = false) + private String headline; + + public int getDday() { + return dday.getRemainingDays(); + } + + public String getHeadline() { + return headline; + } + + @Builder + public Subject(Dday dday, String headline) { + this.dday = dday; + this.headline = headline; + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/dday/repository/SubjectRepository.java b/src/main/java/efub/eday/edayback/domain/day/dday/repository/SubjectRepository.java new file mode 100644 index 0000000..957b683 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/dday/repository/SubjectRepository.java @@ -0,0 +1,12 @@ +package efub.eday.edayback.domain.day.dday.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import efub.eday.edayback.domain.day.dday.entity.Dday; +import efub.eday.edayback.domain.day.dday.entity.Subject; + +public interface SubjectRepository extends JpaRepository { + Optional findByDday(Dday dday); +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/controller/InfoController.java b/src/main/java/efub/eday/edayback/domain/day/info/controller/InfoController.java new file mode 100644 index 0000000..75d606b --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/controller/InfoController.java @@ -0,0 +1,45 @@ +package efub.eday.edayback.domain.day.info.controller; + +import org.springframework.http.HttpStatus; +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.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import efub.eday.edayback.domain.day.dday.entity.Dday; +import efub.eday.edayback.domain.day.info.dto.InfoDto; +import efub.eday.edayback.domain.day.info.entity.Info; +import efub.eday.edayback.domain.day.info.service.InfoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; + +@Tag(name = "좔가정보", description = "좔가정보 κ΄€λ ¨ apiμž…λ‹ˆλ‹€.") +@RestController +@RequestMapping("/infos") +@RequiredArgsConstructor +public class InfoController { + private final InfoService infoService; + + @Operation(summary = "좔가정보 확인 λ©”μ†Œλ“œ", description = "디데이 μΌμˆ˜μ™€ 주제, 그리고 사진정보λ₯Ό μ œκ³΅ν•˜λŠ” λ©”μ†Œλ“œμž…λ‹ˆλ‹€.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "좔가정보 확인 성곡", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = InfoDto.class))), + @ApiResponse(responseCode = "404", description = "좔가정보 확인 μ‹€νŒ¨(μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 디데이)", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = InfoDto.class))) + }) + @GetMapping("/{d_day}") + @ResponseStatus(value = HttpStatus.OK) + public InfoDto getInfoByDday(@PathVariable("d_day") int dday) { + Dday ddayEnum = Dday.fromRemainingDays(dday); + Info info = infoService.getInfoByDday(ddayEnum); + return InfoDto.from(info); + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/dto/ImageDto.java b/src/main/java/efub/eday/edayback/domain/day/info/dto/ImageDto.java new file mode 100644 index 0000000..07764ac --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/dto/ImageDto.java @@ -0,0 +1,14 @@ +package efub.eday.edayback.domain.day.info.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ImageDto { + @Schema(description = "이미지 pk") + private Long imageId; + @Schema(description = "이미지 url") + private String imageUrl; +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/dto/InfoDto.java b/src/main/java/efub/eday/edayback/domain/day/info/dto/InfoDto.java new file mode 100644 index 0000000..95154ce --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/dto/InfoDto.java @@ -0,0 +1,45 @@ +package efub.eday.edayback.domain.day.info.dto; + +import java.util.ArrayList; +import java.util.List; + +import efub.eday.edayback.domain.day.info.entity.Info; +import efub.eday.edayback.domain.day.info.entity.InfoImage; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class InfoDto { + // private Long infoId; + // private Long infoImageId; + @Schema(description = "디데이 일수") + private int dday; + @Schema(description = "좔가정보 주제") + private String headline; + @Schema(description = "사진 url 리슀트") + private List imageList; + + public static InfoDto from(Info info) { + InfoDto infoDto = new InfoDto(); + + infoDto.setDday(info.getSubject().getDday()); + infoDto.setHeadline(info.getSubject().getHeadline()); + + List imageList = new ArrayList<>(); + for (InfoImage infoImage : info.getInfoImageList()) { + ImageDto imageDto = new ImageDto(); + imageDto.setImageId(infoImage.getId()); + imageDto.setImageUrl(infoImage.getUrl()); + imageList.add(imageDto); + } + infoDto.setImageList(imageList); + + return infoDto; + } +} + diff --git a/src/main/java/efub/eday/edayback/domain/day/info/entity/Info.java b/src/main/java/efub/eday/edayback/domain/day/info/entity/Info.java new file mode 100644 index 0000000..5406e0f --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/entity/Info.java @@ -0,0 +1,41 @@ +package efub.eday.edayback.domain.day.info.entity; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +import efub.eday.edayback.domain.day.dday.entity.Subject; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class Info { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "info_id") + private Integer id; + + @OneToOne + @JoinColumn(name = "subject_id", nullable = false, unique = true) + private Subject subject; + + @OneToMany(mappedBy = "info") + private List infoImageList; + + @Builder + public Info(Subject subject, List infoImageList) { + this.subject = subject; + this.infoImageList = infoImageList; + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/entity/InfoImage.java b/src/main/java/efub/eday/edayback/domain/day/info/entity/InfoImage.java new file mode 100644 index 0000000..219b8f6 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/entity/InfoImage.java @@ -0,0 +1,30 @@ +package efub.eday.edayback.domain.day.info.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class InfoImage { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "info_image_id") + private Long id; + + @Column(name = "image_url", nullable = false) + private String url; + + @ManyToOne + @JoinColumn(name = "info_id", nullable = false) + private Info info; +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/repository/InfoRepository.java b/src/main/java/efub/eday/edayback/domain/day/info/repository/InfoRepository.java new file mode 100644 index 0000000..0c1d6e3 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/repository/InfoRepository.java @@ -0,0 +1,12 @@ +package efub.eday.edayback.domain.day.info.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import efub.eday.edayback.domain.day.dday.entity.Dday; +import efub.eday.edayback.domain.day.info.entity.Info; + +public interface InfoRepository extends JpaRepository { + Optional findInfoBySubject_Dday(Dday dday); +} diff --git a/src/main/java/efub/eday/edayback/domain/day/info/service/InfoService.java b/src/main/java/efub/eday/edayback/domain/day/info/service/InfoService.java new file mode 100644 index 0000000..a4a0e70 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/info/service/InfoService.java @@ -0,0 +1,24 @@ +package efub.eday.edayback.domain.day.info.service; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import efub.eday.edayback.domain.day.dday.entity.Dday; +import efub.eday.edayback.domain.day.info.entity.Info; +import efub.eday.edayback.domain.day.info.repository.InfoRepository; +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class InfoService { + private final InfoRepository infoRepository; + + @Transactional(readOnly = true) + public Info getInfoByDday(Dday dday) { + return infoRepository.findInfoBySubject_Dday(dday) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, dday + "λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λ””λ°μ΄μž…λ‹ˆλ‹€.")); + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/controller/QuizController.java b/src/main/java/efub/eday/edayback/domain/day/quiz/controller/QuizController.java new file mode 100644 index 0000000..3f28ad2 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/controller/QuizController.java @@ -0,0 +1,78 @@ +package efub.eday.edayback.domain.day.quiz.controller; + +import efub.eday.edayback.domain.day.quiz.dto.QuizAnswerResponseDto; +import efub.eday.edayback.domain.day.quiz.dto.QuizRequestDto; +import efub.eday.edayback.domain.day.quiz.dto.QuizResponseDto; +import efub.eday.edayback.domain.day.quiz.entity.Quiz; +import efub.eday.edayback.domain.day.quiz.service.QuizService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "ν€΄μ¦ˆ", description = "ν€΄μ¦ˆ κ΄€λ ¨ apiμž…λ‹ˆλ‹€.") +@Slf4j +@RestController +@RequestMapping("/quiz/{dDay}") +@RequiredArgsConstructor +public class QuizController { + private final QuizService quizService; + + //ν€΄μ¦ˆ λ‚΄μš© 쑰회 + @Operation(summary = "ν€΄μ¦ˆ λ‚΄μš© 쑰회 λ©”μ„œλ“œ", description = "ν•΄λ‹Ή λ””λ°μ΄μ˜ ν€΄μ¦ˆλ₯Ό μ‘°νšŒν•˜λŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "ν€΄μ¦ˆ λ‚΄μš© 쑰회 성곡", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = QuizResponseDto.class))), + @ApiResponse(responseCode = "404", description = "ν€΄μ¦ˆ λ‚΄μš© 쑰회 μ‹€νŒ¨", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = QuizResponseDto.class))) + }) + @GetMapping + @ResponseStatus(value = HttpStatus.OK) + public QuizResponseDto findQuiz(@PathVariable int dDay) { + Quiz quiz = quizService.findQuiz(dDay); + return QuizResponseDto.from(quiz); + } + + //ν€΄μ¦ˆ μ •λ‹΅ 확인 + @Operation(summary = "ν€΄μ¦ˆ μ •λ‹΅ 확인 λ©”μ„œλ“œ", description = "ν€΄μ¦ˆ 선지 쀑 ν•˜λ‚˜λ₯Ό 보내면 μ •λ‹΅ μ—¬λΆ€λ₯Ό νŒλ³„ν•΄ μ£ΌλŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "ν€΄μ¦ˆ μ •λ‹΅ 확인 성곡", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = QuizAnswerResponseDto.class))), + @ApiResponse(responseCode = "404", description = "ν€΄μ¦ˆ μ •λ‹΅ 확인 μ‹€νŒ¨", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = QuizAnswerResponseDto.class))) + }) + @PostMapping + public ResponseEntity checkAnswer( + @PathVariable int dDay, + @RequestBody QuizRequestDto quizRequestDto + ) { + boolean isCorrect; + String quizDescription = null; + String descriptionImageUrl = null; + String titleImageUrl = null; + try { + isCorrect = quizService.checkAnswer(dDay, quizRequestDto.getOptionNumber()); + if (isCorrect) { + quizDescription = quizService.getQuizDescription(dDay); + descriptionImageUrl = quizService.getDescriptionImg(dDay); + titleImageUrl = quizService.getTitleImage(dDay); + } + } catch (IllegalArgumentException e) { + String errorMessage = e.getMessage(); + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(new QuizAnswerResponseDto(false, errorMessage, null, null)); + } + return ResponseEntity.ok(new QuizAnswerResponseDto(isCorrect, quizDescription, descriptionImageUrl, titleImageUrl)); + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/dto/OptionsResponseDto.java b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/OptionsResponseDto.java new file mode 100644 index 0000000..129448a --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/OptionsResponseDto.java @@ -0,0 +1,28 @@ +package efub.eday.edayback.domain.day.quiz.dto; + +import efub.eday.edayback.domain.day.quiz.entity.Option; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class OptionsResponseDto { + @Schema(description = "선지 번호") + private Integer optionNumber; + @Schema(description = "선지 λ‚΄μš©") + private String content; + + private OptionsResponseDto(Integer optionNumber, String content) { + this.optionNumber = optionNumber; + this.content = content; + } + + public static OptionsResponseDto from(Option option) { + return new OptionsResponseDto( + option.getOptionNumber(), + option.getContent() + ); + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizAnswerResponseDto.java b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizAnswerResponseDto.java new file mode 100644 index 0000000..abf1d74 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizAnswerResponseDto.java @@ -0,0 +1,27 @@ +package efub.eday.edayback.domain.day.quiz.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class QuizAnswerResponseDto { + @Schema(description = "μ •λ‹΅ μ—¬λΆ€") + private boolean isAnswer; + @Schema(description = "ν•΄λ‹Ή ν€΄μ¦ˆμ— λŒ€ν•œ μ„€λͺ…") + private String quizDescription; + @Schema(description = "ν€΄μ¦ˆ μ •λ‹΅ ν•΄μ„€ 이미지") + private String quizDescriptionImage; + @Schema(description = "칭호 이미지") + private String titleImage; + + public QuizAnswerResponseDto(boolean isAnswer, String quizDescription, String quizDescriptionImage, String titleImage) { + this.isAnswer = isAnswer; + this.quizDescription = quizDescription; + this.quizDescriptionImage = quizDescriptionImage; + this.titleImage = titleImage; + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizRequestDto.java b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizRequestDto.java new file mode 100644 index 0000000..59df8be --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizRequestDto.java @@ -0,0 +1,10 @@ +package efub.eday.edayback.domain.day.quiz.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class QuizRequestDto { + @Schema(description = "선지 번호", example = "1") + private int optionNumber; +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizResponseDto.java b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizResponseDto.java new file mode 100644 index 0000000..287fcf2 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/dto/QuizResponseDto.java @@ -0,0 +1,46 @@ +package efub.eday.edayback.domain.day.quiz.dto; + +import java.util.List; +import java.util.stream.Collectors; + +import efub.eday.edayback.domain.day.quiz.entity.Quiz; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class QuizResponseDto { + @Schema(description = "디데이") + private int dday; + @Schema(description = "각 κ΅¬μ—­μ˜ 주제") + private String topic; + @Schema(description = "ν€΄μ¦ˆ 질문") + private String quizContent; + @Schema(description = "μ •λ‹΅ 선택지 리슀트") + private List optionList; + + public QuizResponseDto(int dday, String topic, String quizContent, List optionList) { + this.dday = dday; + this.topic = topic; + this.quizContent = quizContent; + this.optionList = optionList; + } + + public static QuizResponseDto from(Quiz quiz) { + List optionList = quiz.getOptionList() + .stream() + .map(OptionsResponseDto::from) + .collect(Collectors.toList()); + return new QuizResponseDto( + quiz.getSubject().getDday(), + quiz.getSubject().getHeadline(), + quiz.getContent(), + optionList + ); + } + +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Option.java b/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Option.java new file mode 100644 index 0000000..2e73a97 --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Option.java @@ -0,0 +1,47 @@ +package efub.eday.edayback.domain.day.quiz.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@Table(name = "quiz_option") +public class Option { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "option_id") + private Integer id; + + @Column(nullable = false) + private String content; + + @Column(nullable = false) + private Boolean isAnswer; + + @Column(nullable = false) + private Integer optionNumber; + + @ManyToOne + @JoinColumn(name = "quiz_id", nullable = false) + private Quiz quiz; + + @Builder + public Option(String content, Boolean isAnswer, Quiz quiz, int optionNumber) { + this.content = content; + this.isAnswer = isAnswer; + this.quiz = quiz; + this.optionNumber = optionNumber; + } +} diff --git a/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Quiz.java b/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Quiz.java new file mode 100644 index 0000000..b753dab --- /dev/null +++ b/src/main/java/efub/eday/edayback/domain/day/quiz/entity/Quiz.java @@ -0,0 +1,64 @@ +package efub.eday.edayback.domain.day.quiz.entity; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +import efub.eday.edayback.domain.day.dday.entity.Subject; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Quiz { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "quiz_id") + private Integer id; + + @Column(nullable = false) + private String content; + + @Column(nullable = false, length = 500) + private String explanation; + + @Column(name = "image_url") + private String imageUrl; + + @OneToOne + @JoinColumn(name = "subject_id", nullable = false) + private Subject subject; + + @OneToMany(mappedBy = "quiz") + private List