Skip to content

Commit 722d154

Browse files
authored
AIM-23-챌린지-생성-기능-구현 (#20)
* feat: 챌린지 생성 및 수정 DTO 구현 * feat: 챌린지 생성 로직 구현 * feat: 챌린지 생성 컨트롤러 구현 * fix: dto 설계 변경에 따른 로직 수정 * chore: 테스트 호출 수정 * chore: swagger 문서화 * fix: 배포 .env 파일에 구글 로그인 관련 secret 추가
1 parent dadc006 commit 722d154

File tree

12 files changed

+315
-43
lines changed

12 files changed

+315
-43
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ jobs:
137137
echo "JWT_SECRET_KEY=${{ secrets.ENV_JWT_SECRET_KEY }}" >> .env.prod
138138
echo "GEMINI_API_KEY=${{ secrets.ENV_GEMINI_API_KEY }}" >> .env.prod
139139
echo "APP_CORS_ORIGINS=${{ secrets.FRONTEND_URL }}" >> .env.prod
140+
echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> .env.prod
141+
echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> .env.prod
140142
echo "TZ=Asia/Seoul" >> .env.prod
141143
142144
DEPLOY_PATH=${{ secrets.ENV_DEPLOY_PATH }}

src/main/java/targeter/aim/domain/challenge/controller/ChallengeController.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package targeter.aim.domain.challenge.controller;
22

33
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
45
import lombok.RequiredArgsConstructor;
56
import org.springdoc.core.annotations.ParameterObject;
67
import org.springframework.data.domain.Pageable;
78
import org.springframework.data.web.PageableDefault;
9+
import org.springframework.http.HttpStatus;
810
import org.springframework.security.core.annotation.AuthenticationPrincipal;
9-
import org.springframework.web.bind.annotation.GetMapping;
10-
import org.springframework.web.bind.annotation.ModelAttribute;
11-
import org.springframework.web.bind.annotation.RequestMapping;
12-
import org.springframework.web.bind.annotation.RestController;
11+
import org.springframework.web.bind.annotation.*;
1312
import targeter.aim.domain.challenge.dto.ChallengeDto;
1413
import targeter.aim.domain.challenge.service.ChallengeService;
1514
import targeter.aim.system.security.annotation.NoJwtAuth;
@@ -35,4 +34,18 @@ public ChallengeDto.ChallengePageResponse getVsChallenges(
3534
) {
3635
return challengeService.getVsChallenges(condition, userDetails, pageable);
3736
}
37+
38+
@PostMapping
39+
@Operation(
40+
summary = "챌린지 생성",
41+
description = "새로운 챌린지를 생성합니다."
42+
)
43+
@ApiResponse(responseCode = "201", description = "챌린지 생성 성공")
44+
@ResponseStatus(HttpStatus.CREATED)
45+
public ChallengeDto.ChallengeDetailsResponse createChallenge(
46+
@RequestBody ChallengeDto.ChallengeCreateRequest request,
47+
@AuthenticationPrincipal UserDetails userDetails
48+
) {
49+
return challengeService.createChallenge(userDetails, request);
50+
}
3851
}

src/main/java/targeter/aim/domain/challenge/dto/ChallengeDto.java

Lines changed: 135 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,6 @@
1313

1414
public class ChallengeDto {
1515

16-
@Data
17-
@AllArgsConstructor
18-
@NoArgsConstructor
19-
@Builder
20-
public static class ProgressCreateRequest {
21-
private String name;
22-
23-
private LocalDate startedAt;
24-
25-
private Integer duration;
26-
27-
private List<String> tags;
28-
29-
private List<String> fields;
30-
31-
private List<String> jobs;
32-
33-
private String userRequest;
34-
35-
private ChallengeMode mode;
36-
37-
private ChallengeVisibility visibility;
38-
}
39-
4016
@Data
4117
@NoArgsConstructor
4218
@AllArgsConstructor
@@ -143,4 +119,139 @@ public static ChallengePageResponse from(org.springframework.data.domain.Page<Ch
143119
);
144120
}
145121
}
122+
123+
@Data
124+
@AllArgsConstructor
125+
@NoArgsConstructor
126+
@Builder
127+
@Schema(description = "챌린지 생성 요청")
128+
public static class ChallengeCreateRequest {
129+
@Schema(description = "챌린지 이름", example = "다이어트 챌린지")
130+
private String name;
131+
132+
@Schema(description = "시작일", example = "2026-01-01")
133+
private LocalDate startedAt;
134+
135+
@Schema(description = "기간(주)", example = "6")
136+
private Integer duration;
137+
138+
@Schema(description = "태그 목록(1~3개)", example = "[\"태그1\", \"태그2\", \"태그3\"]")
139+
private List<String> tags;
140+
141+
@Schema(description = "분야 목록(1~3개)", example = "[\"IT\", \"경영\"]")
142+
private List<String> fields;
143+
144+
@Schema(description = "직무 목록(1~3개)", example = "[\"직업1\"]")
145+
private List<String> jobs;
146+
147+
@Schema(description = "AI 요청사항", example = "4주동안 빠르게 다이어트할 수 있는 방법을 알려줘. 금식은 최대한 자제할거야.")
148+
private String userRequest;
149+
150+
@Schema(description = "챌린지 모드", example = "SOLO", allowableValues = { "SOLO", "VS" })
151+
private ChallengeMode mode;
152+
153+
@Schema(description = "공개 여부", example = "PUBLIC", allowableValues = { "PUBLIC", "PRIVATE" })
154+
private ChallengeVisibility visibility;
155+
}
156+
157+
// 챌린지 생성 테스트 용도, 추후 챌린지 상세보기 개발 시 수정
158+
@Data
159+
@AllArgsConstructor
160+
@NoArgsConstructor
161+
@Builder
162+
public static class ChallengeDetailsResponse {
163+
private ChallengeInfo challengeInfo; // 전체 챌린지 정보
164+
165+
private Participants participants; // 참여자 정보 (vs에는 me,opponent / solo에는 me)
166+
167+
private CurrentWeekDetails currentWeekDetails; // 이번주 챌린지 내용 상세 정보
168+
169+
@Data
170+
@AllArgsConstructor
171+
@NoArgsConstructor
172+
@Builder
173+
public static class ChallengeInfo {
174+
private FileDto.FileResponse challengeThumbnail;
175+
176+
private String title;
177+
178+
private List<String> tags;
179+
180+
private List<String> fields;
181+
182+
private List<String> jobs;
183+
184+
private LocalDate startedAt;
185+
186+
private Integer durationWeek;
187+
188+
private ChallengeStatus status;
189+
}
190+
191+
@Data
192+
@AllArgsConstructor
193+
@NoArgsConstructor
194+
@Builder
195+
public static class Participants {
196+
private ParticipantDetails me;
197+
198+
private ParticipantDetails opponent;
199+
}
200+
201+
@Data
202+
@AllArgsConstructor
203+
@NoArgsConstructor
204+
@Builder
205+
public static class ParticipantDetails {
206+
private FileDto.FileResponse profileImage;
207+
208+
private String nickname;
209+
210+
private String progressRate;
211+
212+
private Integer successRate;
213+
214+
private Boolean isSuccess;
215+
216+
private Boolean isRealTimeActive;
217+
}
218+
219+
@Data
220+
@AllArgsConstructor
221+
@NoArgsConstructor
222+
@Builder
223+
public static class CurrentWeekDetails {
224+
private Integer weekNumber;
225+
226+
private String period;
227+
228+
private String weekTitle;
229+
230+
private String weekContent;
231+
232+
private String recordTime;
233+
234+
private FileDto.FileResponse certifiedFile;
235+
236+
private Boolean isFinished;
237+
238+
private List<CommentDetails> comments;
239+
}
240+
241+
@Data
242+
@AllArgsConstructor
243+
@NoArgsConstructor
244+
@Builder
245+
public static class CommentDetails {
246+
private Long commentId;
247+
248+
private String writer;
249+
250+
private String content;
251+
252+
private LocalDateTime createdAt;
253+
254+
private List<CommentDetails> replyComments;
255+
}
256+
}
146257
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package targeter.aim.domain.challenge.repository;
22

33
import org.springframework.data.jpa.repository.JpaRepository;
4+
import targeter.aim.domain.challenge.entity.Challenge;
45
import targeter.aim.domain.challenge.entity.WeeklyProgress;
56

7+
import java.util.Optional;
8+
69
public interface WeeklyProgressRepository extends JpaRepository<WeeklyProgress, Long> {
10+
11+
Optional<WeeklyProgress> findByChallengeAndWeekNumber(Challenge challenge, Integer weekNumber);
712
}

src/main/java/targeter/aim/domain/challenge/service/ChallengeRouteGenerationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class ChallengeRouteGenerationService {
2121
private static final int MAX_TITLE_LENGTH = 100;
2222
private static final int MAX_CONTENT_LENGTH = 500;
2323

24-
public RoutePayload generateRoute(ChallengeDto.ProgressCreateRequest req) {
24+
public RoutePayload generateRoute(ChallengeDto.ChallengeCreateRequest req) {
2525
// 1. AI 호출 (Retry 최대 3회 수행)
2626
RoutePayload payload = retry(() ->
2727
routeGenerator.generate(

src/main/java/targeter/aim/domain/challenge/service/ChallengeRoutePersistService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class ChallengeRoutePersistService {
2828

2929
// 상위 트랜잭션과 독립적으로 실행
3030
@Transactional(propagation = Propagation.REQUIRES_NEW)
31-
public Long persistAtomic(Long userId, ChallengeDto.ProgressCreateRequest req, RoutePayload payload) {
31+
public Long persistAtomic(Long userId, ChallengeDto.ChallengeCreateRequest req, RoutePayload payload) {
3232
// 1. Host 유저 조회
3333
User host = userRepository.findById(userId)
3434
.orElseThrow(() -> new IllegalArgumentException("User not found"));
@@ -48,9 +48,9 @@ public Long persistAtomic(Long userId, ChallengeDto.ProgressCreateRequest req, R
4848
.job(String.join(",", req.getJobs()))
4949
.startedAt(req.getStartedAt())
5050
.durationWeek(req.getDuration())
51+
.status(ChallengeStatus.IN_PROGRESS)
5152
.mode(req.getMode())
5253
.visibility(req.getVisibility())
53-
.status(ChallengeStatus.IN_PROGRESS)
5454
.build();
5555

5656
Challenge savedChallenge = challengeRepository.save(challenge);

0 commit comments

Comments
 (0)