Skip to content

Commit d46c64c

Browse files
committed
Merge branch 'dev' into fix/#67
2 parents e0a0313 + 86690a1 commit d46c64c

File tree

17 files changed

+547
-105
lines changed

17 files changed

+547
-105
lines changed

.github/workflows/ci.yml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI
22

33
on:
44
pull_request:
5-
branches: [ "dev" ]
5+
branches: [ "main", "dev" ]
66

77
jobs:
88
build:
@@ -18,8 +18,26 @@ jobs:
1818
java-version: '21'
1919
cache: gradle
2020

21+
- name: Cache Gradle packages
22+
uses: actions/cache@v3
23+
with:
24+
path: |
25+
~/.gradle/caches
26+
~/.gradle/wrapper
27+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
28+
restore-keys: |
29+
${{ runner.os }}-gradle-
30+
31+
- name: Cache Gradle Build
32+
uses: actions/cache@v3
33+
with:
34+
path: build
35+
key: ${{ runner.os }}-gradle-build-${{ github.sha }}
36+
restore-keys: |
37+
${{ runner.os }}-gradle-build-
38+
2139
- name: Grant execute permission for gradlew
2240
run: chmod +x gradlew
2341

2442
- name: Build with Gradle
25-
run: ./gradlew clean build
43+
run: ./gradlew clean build --build-cache

.github/workflows/cicd.yml

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI/CD
22

33
on:
44
push:
5-
branches: [ "dev" ]
5+
branches: [ "main" ]
66

77
jobs:
88
build:
@@ -11,29 +11,63 @@ jobs:
1111
- name: Checkout code
1212
uses: actions/[email protected]
1313

14+
- name: Set up JDK 21
15+
uses: actions/[email protected]
16+
with:
17+
distribution: 'temurin'
18+
java-version: '21'
19+
cache: gradle
20+
21+
- name: Cache Gradle packages
22+
uses: actions/cache@v3
23+
with:
24+
path: |
25+
~/.gradle/caches
26+
~/.gradle/wrapper
27+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
28+
restore-keys: |
29+
${{ runner.os }}-gradle-
30+
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@v3
33+
1434
- name: Log in to Docker Hub
1535
uses: docker/[email protected]
1636
with:
1737
username: ${{ secrets.DOCKER_USERNAME }}
1838
password: ${{ secrets.DOCKER_PASSWORD }}
1939

40+
- name: Cache Docker layers
41+
uses: actions/cache@v3
42+
with:
43+
path: /tmp/.buildx-cache
44+
key: ${{ runner.os }}-buildx-${{ github.sha }}
45+
restore-keys: |
46+
${{ runner.os }}-buildx-
47+
2048
- name: Build and Push Docker image
21-
if: github.ref == 'refs/heads/dev'
49+
if: github.ref == 'refs/heads/main'
2250
uses: docker/[email protected]
2351
with:
2452
context: .
2553
file: ./Dockerfile
2654
push: true
2755
tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.PROD_IMAGE_NAME }}:latest
2856
platforms: linux/amd64
57+
cache-from: type=local,src=/tmp/.buildx-cache
58+
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
59+
60+
- name: Move cache
61+
run: |
62+
rm -rf /tmp/.buildx-cache
63+
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
2964
3065
deploy:
3166
runs-on: ubuntu-latest
3267
needs: build
33-
3468
steps:
3569
- name: SSH to Server and Deploy
36-
if: github.ref == 'refs/heads/dev'
70+
if: github.ref == 'refs/heads/main'
3771
uses: appleboy/[email protected]
3872
with:
3973
host: ${{ secrets.PROD_SERVER_HOST }}

src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public enum ErrorCode {
3636
SLOT_NOT_ACTIVE(400, "C027", "해당 슬롯이 비활성화 되었습니다."),
3737
FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다."),
3838
RECRUITMENT_NOT_ACTIVE(400, "C029", "모집 기간이 아닙니다."),
39+
NOT_FOUND_PARTICIPANT(400, "C030", "참가자를 찾을 수 없습니다.")
3940
;
4041

4142
private final int status;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dmu.dasom.api.domain.email.config;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.mail.javamail.JavaMailSender;
7+
import org.springframework.mail.javamail.JavaMailSenderImpl;
8+
9+
import java.util.Properties;
10+
11+
@Configuration
12+
public class EmailConfig {
13+
14+
@Value("${spring.mail.username}")
15+
private String username;
16+
@Value("${spring.mail.password}")
17+
private String password;
18+
19+
@Bean
20+
public JavaMailSender javaMailSender() {
21+
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
22+
mailSender.setHost("smtp.gmail.com");
23+
mailSender.setPort(587);
24+
mailSender.setUsername(username);
25+
mailSender.setPassword(password);
26+
27+
Properties props = mailSender.getJavaMailProperties();
28+
props.put("mail.transport.protocol", "smtp");
29+
props.put("mail.smtp.auth", "true");
30+
props.put("mail.smtp.starttls.enable", "true");
31+
props.put("mail.debug", "true");
32+
33+
return mailSender;
34+
}
35+
}

src/main/java/dmu/dasom/api/domain/google/service/EmailService.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,36 @@
1717
@Service
1818
public class EmailService {
1919

20-
private JavaMailSender javaMailSender;
21-
private TemplateEngine templateEngine;
20+
private final TemplateEngine templateEngine;
21+
private final JavaMailSender javaMailSender;
22+
2223
@Value("${spring.mail.username}")
2324
private String from;
2425

2526
public void sendEmail(String to, String name, MailType mailType) throws MessagingException {
2627
if (mailType == null){
2728
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
2829
}
30+
2931
// 메일 제목 및 템플릿 설정
3032
String subject;
3133
String emailContent;
32-
String buttonUrl = "#";
34+
String buttonUrl = "https://dmu-dasom.or.kr/recruit/result";
3335
String buttonText;
36+
3437
switch (mailType) {
3538
case DOCUMENT_RESULT -> {
3639
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내";
37-
emailContent = "먼저 다솜 34기에 많은 관심을 갖고 지원해 주셔서 감사드리며,<br>" +
38-
"내부 서류 평가 결과 및 추후 일정에 관해 안내드리고자 이메일을 발송하게 되었습니다.<br>" +
39-
"모집 폼 합/불합 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
40+
emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
41+
"내부 서류 평가 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" +
42+
"서류 전형 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
4043
buttonText = "서류 결과 확인하기";
4144
}
4245
case FINAL_RESULT -> {
43-
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 합격 안내";
44-
emailContent = "먼저 다솜 34기에 많은 관심을 갖고 지원해 주셔서 감사드리며,<br>" +
45-
"최종 면접 결과 및 추후 일정에 관해 안내드리고자 이메일을 발송하게 되었습니다.<br>" +
46-
"모집 폼 합/불합 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
46+
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 면접 결과 안내";
47+
emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
48+
"최종 면접 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" +
49+
"최종 면접 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
4750
buttonText = "최종 결과 확인하기";
4851
}
4952
default -> throw new IllegalStateException("Unexpected value: " + mailType);
@@ -66,7 +69,10 @@ public void sendEmail(String to, String name, MailType mailType) throws Messagin
6669
helper.setTo(to);
6770
helper.setSubject(subject);
6871
helper.setText(htmlBody, true);
69-
helper.setFrom(from);
72+
helper.setFrom((from != null && !from.isEmpty()) ? from : "[email protected]");
73+
74+
// Content-Type을 명시적으로 설정
75+
message.setContent(htmlBody, "text/html; charset=utf-8");
7076

7177
javaMailSender.send(message);
7278
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dmu.dasom.api.domain.interview.repositoty;
1+
package dmu.dasom.api.domain.interview.repository;
22

33
import dmu.dasom.api.domain.interview.entity.InterviewReservation;
44
import org.springframework.data.jpa.repository.JpaRepository;

src/main/java/dmu/dasom/api/domain/interview/repositoty/InterviewSlotRepository.java renamed to src/main/java/dmu/dasom/api/domain/interview/repository/InterviewSlotRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dmu.dasom.api.domain.interview.repositoty;
1+
package dmu.dasom.api.domain.interview.repository;
22

33
import dmu.dasom.api.domain.interview.entity.InterviewSlot;
44
import dmu.dasom.api.domain.interview.enums.InterviewStatus;

src/main/java/dmu/dasom/api/domain/interview/service/InterviewServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
import dmu.dasom.api.domain.interview.entity.InterviewReservation;
1111
import dmu.dasom.api.domain.interview.entity.InterviewSlot;
1212
import dmu.dasom.api.domain.interview.enums.InterviewStatus;
13-
import dmu.dasom.api.domain.interview.repositoty.InterviewReservationRepository;
14-
import dmu.dasom.api.domain.interview.repositoty.InterviewSlotRepository;
13+
import dmu.dasom.api.domain.interview.repository.InterviewReservationRepository;
14+
import dmu.dasom.api.domain.interview.repository.InterviewSlotRepository;
1515
import dmu.dasom.api.domain.recruit.service.RecruitServiceImpl;
1616
import jakarta.persistence.EntityListeners;
1717
import lombok.RequiredArgsConstructor;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package dmu.dasom.api.domain.somkathon.controller;
2+
3+
import dmu.dasom.api.domain.somkathon.dto.SomParticipantRequestDto;
4+
import dmu.dasom.api.domain.somkathon.dto.SomParticipantResponseDto;
5+
import dmu.dasom.api.domain.somkathon.service.SomParticipantService;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import jakarta.validation.Valid;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.validation.annotation.Validated;
16+
import org.springframework.web.ErrorResponse;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
import java.util.List;
20+
21+
@RestController
22+
@RequestMapping("/api/somkathon/participants")
23+
@RequiredArgsConstructor
24+
@Validated
25+
public class SomParticipantController {
26+
27+
private final SomParticipantService somParticipantService;
28+
29+
/**
30+
* 참가자 등록
31+
*/
32+
@Operation(summary = "솜커톤 참가자 등록", description = "솜커톤 참가자를 등록합니다.")
33+
@ApiResponses(value = {
34+
@ApiResponse(responseCode = "200", description = "참가자 등록 성공"),
35+
@ApiResponse(responseCode = "400", description = "중복 학번 또는 필수 값 누락",
36+
content = @Content(
37+
mediaType = "application/json",
38+
schema = @Schema(implementation = ErrorResponse.class),
39+
examples = {
40+
@ExampleObject(
41+
name = "중복된 학번",
42+
value = "{ \"code\": \"C002\", \"message\": \"이미 등록된 학번입니다.\" }"
43+
),
44+
@ExampleObject(
45+
name = "필수 값 누락",
46+
value = "{ \"code\": \"C001\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"
47+
)}))})
48+
@PostMapping("/create")
49+
public ResponseEntity<SomParticipantResponseDto> create(@Valid @RequestBody final SomParticipantRequestDto request) {
50+
return ResponseEntity.ok(somParticipantService.createParticipant(request));
51+
}
52+
53+
/**
54+
* 모든 참가자 조회
55+
*/
56+
@Operation(summary = "솜커톤 참가자 목록 조회", description = "모든 솜커톤 참가자를 조회합니다.")
57+
@ApiResponses(value = {
58+
@ApiResponse(responseCode = "200", description = "참가자 목록 조회 성공")})
59+
@GetMapping
60+
public ResponseEntity<List<SomParticipantResponseDto>> findAll() {
61+
return ResponseEntity.ok(somParticipantService.getAllParticipants());
62+
}
63+
64+
/**
65+
* 특정 참가자 조회
66+
*/
67+
@Operation(summary = "솜커톤 참가자 상세 조회", description = "특정 솜커톤 참가자의 상세 정보를 조회합니다.")
68+
@ApiResponses(value = {
69+
@ApiResponse(responseCode = "200", description = "참가자 상세 조회 성공"),
70+
@ApiResponse(responseCode = "400", description = "존재하지 않는 ID",
71+
content = @Content(
72+
mediaType = "application/json",
73+
schema = @Schema(implementation=ErrorResponse.class),
74+
examples={
75+
@ExampleObject(
76+
name="존재하지 않는 ID",
77+
value="{\"code\":\"C004\",\"message\":\"참가자를 찾을 수 없습니다.\"}")}))})
78+
@GetMapping("/{id}")
79+
public ResponseEntity<SomParticipantResponseDto> getById(@PathVariable final Long id) {
80+
return ResponseEntity.ok(somParticipantService.getParticipant(id));
81+
}
82+
83+
/**
84+
* 참가자 정보 수정
85+
*/
86+
@Operation(summary = "솜커톤 참가자 정보 수정", description = "특정 솜커톤 참가자의 정보를 수정합니다.")
87+
@ApiResponses(value = {
88+
@ApiResponse(responseCode = "200", description = "참가자 정보 수정 성공"),
89+
@ApiResponse(responseCode = "400", description = "중복 학번 또는 존재하지 않는 ID",
90+
content = @Content(
91+
mediaType = "application/json",
92+
schema=@Schema(implementation=ErrorResponse.class),
93+
examples={
94+
@ExampleObject(
95+
name="중복된 학번",
96+
value="{\"code\":\"C002\",\"message\":\"이미 등록된 학번입니다.\"}"),
97+
@ExampleObject(
98+
name="존재하지 않는 ID",
99+
value="{\"code\":\"C004\",\"message\":\"참가자를 찾을 수 없습니다.\"}")}))})
100+
@PutMapping("/{id}")
101+
public ResponseEntity<SomParticipantResponseDto> update(@PathVariable final Long id,
102+
@Valid @RequestBody final SomParticipantRequestDto request) {
103+
return ResponseEntity.ok(somParticipantService.updateParticipant(id, request));
104+
}
105+
106+
/**
107+
* 참가자 삭제 (Delete)
108+
*/
109+
@Operation(summary = "솜커톤 참가자 삭제", description = "특정 솜커톤 참가자를 삭제합니다.")
110+
@ApiResponses(value={
111+
@ApiResponse(responseCode="204",description="참가자 삭제 성공"),
112+
@ApiResponse(responseCode="400",description="존재하지 않는 ID",
113+
content=@Content(
114+
mediaType="application/json",
115+
schema=@Schema(implementation=ErrorResponse.class),
116+
examples={
117+
@ExampleObject(
118+
name="존재하지 않는 ID",
119+
value="{\"code\":\"C004\",\"message\":\"참가자를 찾을 수 없습니다.\"}")}))})
120+
@DeleteMapping("/{id}")
121+
public ResponseEntity<Void> delete(@PathVariable final Long id) {
122+
somParticipantService.deleteParticipant(id);
123+
return ResponseEntity.noContent().build();
124+
}
125+
126+
}

0 commit comments

Comments
 (0)