Skip to content

Commit 6244bc1

Browse files
authored
Merge pull request #8 from Wedvice/f/homeGetAPI/jehyeok
F/home get api/jehyeok
2 parents 8972e2a + 58fd337 commit 6244bc1

12 files changed

Lines changed: 315 additions & 16 deletions

File tree

src/main/java/com/wedvice/subtask/controller/SubTaskController.java

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package com.wedvice.subtask.controller;
22

3+
import com.wedvice.common.ApiResponse;
34
import com.wedvice.security.login.CustomUserDetails;
45
import com.wedvice.security.login.LoginUser;
6+
import com.wedvice.subtask.dto.CompleteRateResponseDto;
7+
import com.wedvice.subtask.dto.SubTaskHomeResponseDto;
58
import com.wedvice.subtask.dto.SubTaskResponseDTO;
69
import com.wedvice.subtask.service.SubTaskService;
10+
import io.swagger.v3.oas.annotations.Operation;
11+
import io.swagger.v3.oas.annotations.Parameter;
712
import lombok.RequiredArgsConstructor;
813
import lombok.extern.slf4j.Slf4j;
14+
import org.springdoc.core.annotations.ParameterObject;
15+
import org.springframework.data.domain.Pageable;
16+
import org.springframework.data.domain.Slice;
17+
import org.springframework.data.web.PageableDefault;
18+
import org.springframework.http.ResponseEntity;
919
import org.springframework.web.bind.annotation.*;
1020

1121
import java.util.List;
@@ -19,27 +29,56 @@ public class SubTaskController {
1929
private final SubTaskService subTaskService;
2030

2131
@GetMapping
22-
public List<SubTaskResponseDTO> get(@LoginUser CustomUserDetails loginUser, long taskId){
32+
public List<SubTaskResponseDTO> get(@LoginUser CustomUserDetails loginUser, long taskId) {
2333

24-
return subTaskService.getAllSubTask(loginUser.getUserId(),taskId);
34+
return subTaskService.getAllSubTask(loginUser.getUserId(), taskId);
2535

2636
}
2737

2838
@PatchMapping("/align")
29-
public String patchAlign(){
39+
public String patchAlign() {
3040
return "subtask 정렬 위치 변경";
3141
}
3242

3343

3444
@PostMapping()
35-
public String post(){
45+
public String post() {
3646
return "create subtask";
3747
}
3848

3949
@DeleteMapping()
40-
public String delete(){
50+
public String delete() {
4151
return "delete subtask";
4252
}
4353

54+
@GetMapping("/home")
55+
@Operation(summary = "서브태스크 홈 목록 조회", description = "완료 여부 및 top3 여부에 따라 목록을 조회합니다. 무한스크롤 지원")
56+
public ResponseEntity<ApiResponse<Slice<SubTaskHomeResponseDto>>> getHomeSubtask(
57+
@LoginUser CustomUserDetails loginUser,
4458

59+
@Parameter(description = "완료 여부 (true = 완료, false = 남은)", required = true)
60+
@RequestParam boolean completed,
61+
62+
@Parameter(description = "top3 여부 (true면 상위 3개만 반환)", required = false)
63+
@RequestParam(defaultValue = "false") boolean top3,
64+
65+
@Parameter(description = "정렬 기준 설정", required = false)
66+
@RequestParam(defaultValue = "date") String sort,
67+
68+
@Parameter(description = "", required = true)
69+
@RequestParam String role,
70+
71+
@ParameterObject // Swagger에서 page, size, sort
72+
@PageableDefault(size = 10) Pageable pageable
73+
) {
74+
Slice<SubTaskHomeResponseDto> responseDto = subTaskService.getHomeSubTasks(loginUser.getUserId(), completed, role, pageable, top3, sort);
75+
return ResponseEntity.ok(ApiResponse.success(responseDto));
76+
}
77+
78+
@GetMapping("/progress")
79+
@Operation(summary = "완료율 조회", description = "커플의 전체 SubTask 완료율을 조회합니다.")
80+
public ResponseEntity<ApiResponse<CompleteRateResponseDto>> getProgress(@LoginUser CustomUserDetails loginUser) {
81+
CompleteRateResponseDto responseDto = subTaskService.getProgressRate(loginUser.getUserId());
82+
return ResponseEntity.ok(ApiResponse.success(responseDto));
83+
}
4584
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.wedvice.subtask.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.*;
5+
6+
@Getter
7+
@Setter
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
@Builder
11+
@Schema(description = "완료율 응답 DTO")
12+
public class CompleteRateResponseDto {
13+
14+
@Schema(description = "완료 퍼센트 (0~100)", example = "54")
15+
private int percent;
16+
17+
@Schema(description = "완료된 개수", example = "13")
18+
private long completed;
19+
20+
@Schema(description = "전체 개수", example = "24")
21+
private long total;
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.wedvice.subtask.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.*;
5+
6+
import java.time.LocalDate;
7+
8+
@Getter
9+
@Setter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
@Builder
13+
@Schema(description = "홈 화면에 표시되는 서브태스크 요약 DTO")
14+
public class SubTaskHomeResponseDto {
15+
16+
@Schema(description = "SubTask ID", example = "100")
17+
private Long subTaskId;
18+
19+
@Schema(description = "SubTask ID", example = "100")
20+
private String subTaskContent;
21+
22+
@Schema(description = "CoupleTask ID", example = "25")
23+
private Long coupleTaskId;
24+
25+
@Schema(description = "예정일", example = "2025-07-01")
26+
private LocalDate targetDate;
27+
28+
@Schema(description = "완료 여부", example = "false")
29+
private boolean completed;
30+
31+
@Schema(description = "정렬용 인덱스 (카테고리 내 순서)", example = "0")
32+
private int orders;
33+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.wedvice.subtask.exception;
2+
3+
import com.wedvice.common.exception.CustomException;
4+
import org.springframework.http.HttpStatus;
5+
6+
public class NotExistRoleException extends CustomException {
7+
private static final String message = "존재하지 않는 역할입니다.";
8+
9+
public NotExistRoleException() {
10+
super(message, HttpStatus.BAD_REQUEST);
11+
}
12+
}
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package com.wedvice.subtask.repository;
22

3+
import com.wedvice.subtask.dto.CompleteRateResponseDto;
4+
import com.wedvice.subtask.dto.SubTaskHomeResponseDto;
35
import com.wedvice.subtask.dto.SubTaskResponseDTO;
6+
import com.wedvice.user.entity.User;
7+
import org.springframework.data.domain.Pageable;
8+
import org.springframework.data.domain.Slice;
49

510
import java.util.List;
611

712
public interface SubTaskCustomRepository {
813

914
public List<SubTaskResponseDTO> getSubTasks(Long userId, Long taskId);
1015

11-
}
16+
Slice<SubTaskHomeResponseDto> findHomeSubTasksByCondition(Long userId,
17+
boolean completed,
18+
boolean top3,
19+
User.Role role, String sortType,
20+
Pageable pageable);
21+
22+
CompleteRateResponseDto getProgressRate(Long userId);
23+
}

src/main/java/com/wedvice/subtask/repository/SubTaskCustomRepositoryImpl.java

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package com.wedvice.subtask.repository;
22

3+
import com.querydsl.core.types.OrderSpecifier;
34
import com.querydsl.core.types.Projections;
5+
import com.querydsl.core.types.dsl.BooleanExpression;
46
import com.querydsl.jpa.impl.JPAQueryFactory;
7+
import com.wedvice.subtask.dto.CompleteRateResponseDto;
8+
import com.wedvice.subtask.dto.SubTaskHomeResponseDto;
59
import com.wedvice.subtask.dto.SubTaskResponseDTO;
10+
import com.wedvice.user.entity.User;
611
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.domain.Slice;
14+
import org.springframework.data.domain.SliceImpl;
715

816
import java.util.List;
17+
import java.util.stream.Collectors;
918

1019
import static com.wedvice.coupletask.entity.QCoupleTask.coupleTask;
1120
import static com.wedvice.subtask.entity.QSubTask.subTask;
@@ -14,7 +23,7 @@
1423

1524

1625
@RequiredArgsConstructor
17-
public class SubTaskCustomRepositoryImpl implements SubTaskCustomRepository{
26+
public class SubTaskCustomRepositoryImpl implements SubTaskCustomRepository {
1827

1928
private final JPAQueryFactory queryFactory;
2029

@@ -53,4 +62,116 @@ public List<SubTaskResponseDTO> getSubTasks(Long userId, Long taskId) {
5362
.orderBy(subTask.orders.asc())
5463
.fetch();
5564
}
65+
66+
@Override
67+
public Slice<SubTaskHomeResponseDto> findHomeSubTasksByCondition(Long userId,
68+
boolean completed,
69+
boolean top3,
70+
User.Role role,
71+
String sortType,
72+
Pageable pageable) {
73+
// 커플 ID 찾기
74+
Long coupleId = queryFactory
75+
.select(user.couple.id)
76+
.from(user)
77+
.where(user.id.eq(userId))
78+
.fetchOne();
79+
80+
if (coupleId == null) return new SliceImpl<>(List.of());
81+
82+
// 2. SubTask 데이터 조회 (Entity fetch → DTO 변환)
83+
List<SubTaskHomeResponseDto> results = queryFactory
84+
.selectFrom(subTask)
85+
.join(subTask.coupleTask, coupleTask)
86+
.join(coupleTask.task, task)
87+
.where(
88+
coupleTask.couple.id.eq(coupleId),
89+
subTask.completed.eq(completed),
90+
roleEq(role)
91+
)
92+
.orderBy(getOrderSpecifiers(sortType, top3))
93+
.offset(top3 ? 0 : pageable.getOffset())
94+
.limit(top3 ? 3 : pageable.getPageSize() + 1)
95+
.fetch()
96+
.stream()
97+
.map(st -> SubTaskHomeResponseDto.builder()
98+
.subTaskId(st.getId())
99+
.coupleTaskId(st.getCoupleTask().getId())
100+
.subTaskContent(st.getContent())
101+
.targetDate(st.getTargetDate())
102+
.completed(st.isCompleted())
103+
.orders(st.getOrders())
104+
.build())
105+
.collect(Collectors.toList());
106+
107+
// 3. Slice 처리
108+
boolean hasNext = !top3 && results.size() > pageable.getPageSize();
109+
if (hasNext) {
110+
results.remove(results.size() - 1); // 초과분 제거
111+
}
112+
113+
return new SliceImpl<>(results, pageable, hasNext);
114+
}
115+
116+
@Override
117+
public CompleteRateResponseDto getProgressRate(Long userId) {
118+
// 커플 ID 조회
119+
Long coupleId = queryFactory
120+
.select(user.couple.id)
121+
.from(user)
122+
.where(user.id.eq(userId))
123+
.fetchOne();
124+
125+
if (coupleId == null) {
126+
return new CompleteRateResponseDto(0, 0, 0);
127+
}
128+
129+
// 전체 SubTask 개수
130+
Long total = queryFactory
131+
.select(subTask.count())
132+
.from(subTask)
133+
.join(subTask.coupleTask, coupleTask)
134+
.where(coupleTask.couple.id.eq(coupleId))
135+
.fetchOne();
136+
137+
if (total == null || total == 0) {
138+
return new CompleteRateResponseDto(0, 0, 0);
139+
}
140+
141+
// 완료된 SubTask 개수
142+
Long completed = queryFactory
143+
.select(subTask.count())
144+
.from(subTask)
145+
.join(subTask.coupleTask, coupleTask)
146+
.where(
147+
coupleTask.couple.id.eq(coupleId),
148+
subTask.completed.isTrue()
149+
)
150+
.fetchOne();
151+
152+
int percent = (int) Math.round((completed * 100.0) / total);
153+
154+
return new CompleteRateResponseDto(percent, completed, total);
155+
}
156+
157+
private OrderSpecifier<?>[] getOrderSpecifiers(String sortType, boolean top3) {
158+
if (top3 || "date".equalsIgnoreCase(sortType)) {
159+
return new OrderSpecifier[]{
160+
subTask.targetDate.asc().nullsLast()
161+
};
162+
} else if ("category".equalsIgnoreCase(sortType)) {
163+
return new OrderSpecifier[]{
164+
coupleTask.task.title.asc(),
165+
subTask.orders.asc()
166+
};
167+
} else {
168+
return new OrderSpecifier[]{
169+
subTask.createdAt.desc()
170+
};
171+
}
172+
}
173+
174+
private BooleanExpression roleEq(User.Role role) {
175+
return role != null ? subTask.role.eq(role) : null;
176+
}
56177
}

src/main/java/com/wedvice/subtask/service/SubTaskService.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.wedvice.subtask.service;
22

33
import com.wedvice.coupletask.entity.CoupleTask;
4+
import com.wedvice.subtask.dto.CompleteRateResponseDto;
5+
import com.wedvice.subtask.dto.SubTaskHomeResponseDto;
46
import com.wedvice.subtask.dto.SubTaskResponseDTO;
57
import com.wedvice.subtask.entity.SubTask;
8+
import com.wedvice.subtask.exception.NotExistRoleException;
69
import com.wedvice.subtask.repository.SubTaskRepository;
710
import com.wedvice.task.entity.Task;
811
import com.wedvice.user.entity.User;
912
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Pageable;
14+
import org.springframework.data.domain.Slice;
1015
import org.springframework.stereotype.Service;
1116
import org.springframework.transaction.annotation.Transactional;
1217

@@ -56,4 +61,23 @@ public List<SubTaskResponseDTO> getAllSubTask(Long userId, Long taskId) {
5661
}
5762

5863

64+
@Transactional(readOnly = true)
65+
public Slice<SubTaskHomeResponseDto> getHomeSubTasks(Long userId, boolean completed, String role, Pageable pageable, boolean top3, String sort) {
66+
User.Role roleEnum = convertToRole(role); // 👈 문자열 → enum
67+
68+
return subTaskRepository.findHomeSubTasksByCondition(userId, completed, top3, roleEnum, sort, pageable);
69+
}
70+
71+
private User.Role convertToRole(String roleStr) {
72+
if (roleStr == null || roleStr.isBlank()) return null;
73+
try {
74+
return User.Role.valueOf(roleStr.toUpperCase());
75+
} catch (IllegalArgumentException e) {
76+
throw new NotExistRoleException();
77+
}
78+
}
79+
80+
public CompleteRateResponseDto getProgressRate(Long userId) {
81+
return subTaskRepository.getProgressRate(userId);
82+
}
5983
}

src/main/java/com/wedvice/task/entity/Task.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import com.wedvice.common.BaseEntity;
44
import jakarta.persistence.*;
5+
import lombok.Getter;
56

67
@Entity
8+
@Getter
79
public class Task extends BaseEntity {
810

911

@@ -17,5 +19,4 @@ public class Task extends BaseEntity {
1719
private String title;
1820

1921

20-
2122
}

0 commit comments

Comments
 (0)