Skip to content

Commit 502756e

Browse files
authored
Merge branch 'main' into feature/district
2 parents 177f310 + 63baee5 commit 502756e

File tree

11 files changed

+224
-10
lines changed

11 files changed

+224
-10
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.trashheroesbe.feature.disposal.api;
2+
3+
import static com.trashheroesbe.global.response.type.SuccessCode.OK;
4+
5+
import com.trashheroesbe.feature.disposal.application.DisposalService;
6+
import com.trashheroesbe.feature.disposal.dto.response.DistrictTrashDisposalResponse;
7+
import com.trashheroesbe.global.auth.security.CustomerDetails;
8+
import com.trashheroesbe.global.response.ApiResponse;
9+
import java.util.List;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RestController
17+
@RequiredArgsConstructor
18+
@RequestMapping("/api/v1/disposals")
19+
public class DisposalController implements DisposalControllerApi {
20+
21+
private final DisposalService disposalService;
22+
23+
@Override
24+
@GetMapping("/today")
25+
public ApiResponse<List<DistrictTrashDisposalResponse>> getTodayTrashDisposal(
26+
@AuthenticationPrincipal CustomerDetails customerDetails
27+
) {
28+
List<DistrictTrashDisposalResponse> response = disposalService.getTodayTrashDisposal(
29+
customerDetails.getUser().getId());
30+
return ApiResponse.success(OK, response);
31+
}
32+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.trashheroesbe.feature.disposal.api;
2+
3+
import com.trashheroesbe.feature.disposal.dto.response.DistrictTrashDisposalResponse;
4+
import com.trashheroesbe.feature.user.dto.response.UserDistrictResponse;
5+
import com.trashheroesbe.global.auth.security.CustomerDetails;
6+
import com.trashheroesbe.global.response.ApiResponse;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import java.util.List;
10+
11+
@Tag(name = "Disposal", description = "자치구별 배출 관련 API")
12+
public interface DisposalControllerApi {
13+
14+
@Operation(summary = "오늘의 분리수거 조회", description = "나의 등록된 자치구의 분리수거 정보를 조회 합니다.")
15+
ApiResponse<List<DistrictTrashDisposalResponse>> getTodayTrashDisposal(
16+
CustomerDetails customerDetails
17+
);
18+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.trashheroesbe.feature.disposal.application;
2+
3+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_FOUND_DEFAULT_USER_DISTRICTS;
4+
import static com.trashheroesbe.global.response.type.ErrorCode.NOT_FOUND_USER_DISTRICTS;
5+
6+
import com.trashheroesbe.feature.disposal.domain.Disposal;
7+
import com.trashheroesbe.feature.disposal.dto.response.DistrictTrashDisposalResponse;
8+
import com.trashheroesbe.feature.disposal.infrastructure.DisposalRepository;
9+
import com.trashheroesbe.feature.user.domain.entity.UserDistrict;
10+
import com.trashheroesbe.feature.user.domain.service.UserDistrictFinder;
11+
import com.trashheroesbe.global.exception.BusinessException;
12+
import com.trashheroesbe.global.util.DateTimeUtils;
13+
import java.time.LocalDate;
14+
import java.util.List;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.stereotype.Service;
17+
import org.springframework.transaction.annotation.Transactional;
18+
19+
@Service
20+
@Transactional(readOnly = true)
21+
@RequiredArgsConstructor
22+
public class DisposalService {
23+
24+
private final UserDistrictFinder userDistrictFinder;
25+
private final DisposalRepository disposalRepository;
26+
27+
public List<DistrictTrashDisposalResponse> getTodayTrashDisposal(Long userId) {
28+
List<UserDistrict> userDistricts = userDistrictFinder.findByUserId(userId);
29+
UserDistrict defaultUserDistrict = userDistricts.stream()
30+
.filter(UserDistrict::getIsDefault)
31+
.findFirst()
32+
.orElseThrow(() -> userDistricts.isEmpty()
33+
? new BusinessException(NOT_FOUND_USER_DISTRICTS)
34+
: new BusinessException(NOT_FOUND_DEFAULT_USER_DISTRICTS)
35+
);
36+
37+
String todayDay = DateTimeUtils.getTodayKorean();
38+
LocalDate todayDate = DateTimeUtils.getTodayDate();
39+
List<Disposal> disposals = disposalRepository.findAllByDistrictAndDay(
40+
defaultUserDistrict.getDistrict().getId(),
41+
todayDay
42+
);
43+
44+
return DistrictTrashDisposalResponse.of(disposals, todayDay, todayDate);
45+
}
46+
}

src/main/java/com/trashheroesbe/feature/disposal/domain/Disposal.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
import jakarta.persistence.JoinColumn;
1313
import jakarta.persistence.ManyToOne;
1414
import jakarta.persistence.Table;
15+
import java.util.List;
1516
import lombok.AccessLevel;
1617
import lombok.AllArgsConstructor;
1718
import lombok.Builder;
1819
import lombok.Getter;
1920
import lombok.NoArgsConstructor;
21+
import org.hibernate.annotations.JdbcTypeCode;
22+
import org.hibernate.type.SqlTypes;
2023

2124
@Getter
2225
@Entity
@@ -42,6 +45,7 @@ public class Disposal extends BaseTimeEntity {
4245
@Column(name = "method_detail", columnDefinition = "TEXT")
4346
private String methodDetail;
4447

48+
@JdbcTypeCode(SqlTypes.JSON)
4549
@Column(name = "days", columnDefinition = "JSON")
46-
private String days;
50+
private List<String> days;
4751
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.trashheroesbe.feature.disposal.dto.response;
2+
3+
import com.trashheroesbe.feature.disposal.domain.Disposal;
4+
import com.trashheroesbe.feature.trash.domain.Type;
5+
import java.time.LocalDate;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
9+
import lombok.Builder;
10+
11+
@Builder
12+
public record DistrictTrashDisposalResponse(
13+
String categoryName,
14+
List<String> trashTypes,
15+
String location,
16+
String todayDay,
17+
LocalDate todayDate
18+
) {
19+
20+
public static List<DistrictTrashDisposalResponse> of(
21+
List<Disposal> disposals,
22+
String todayDay,
23+
LocalDate todayDate
24+
) {
25+
if (disposals.isEmpty()) {
26+
return List.of();
27+
}
28+
29+
String location = disposals.getFirst().getDistrict().getSigungu();
30+
31+
// 카테고리별로 그룹핑
32+
Map<String, List<String>> grouped = disposals.stream()
33+
.collect(Collectors.groupingBy(
34+
disposal -> getCategoryName(disposal.getTrashType().getType()),
35+
Collectors.mapping(
36+
disposal -> disposal.getTrashType().getType().getNameKo(),
37+
Collectors.toList()
38+
)
39+
));
40+
41+
return grouped.entrySet().stream()
42+
.map(entry -> DistrictTrashDisposalResponse.builder()
43+
.categoryName(entry.getKey())
44+
.trashTypes(entry.getValue())
45+
.location(location)
46+
.todayDay(todayDay)
47+
.todayDate(todayDate)
48+
.build())
49+
.sorted((a, b) -> getCategoryOrder(a.categoryName) - getCategoryOrder(b.categoryName))
50+
.toList();
51+
}
52+
53+
private static String getCategoryName(Type type) {
54+
return switch (type) {
55+
case NON_RECYCLABLE, FOOD_WASTE -> "일반쓰레기/음식물쓰레기";
56+
case PET, VINYL_FILM -> "투명페트병/비닐류";
57+
case PAPER, PAPER_PACK, PLASTIC, STYROFOAM, GLASS, METAL, TEXTILES,
58+
E_WASTE, HAZARDOUS_SMALL_WASTE -> "그외 재활용품";
59+
default -> "기타";
60+
};
61+
}
62+
63+
private static int getCategoryOrder(String categoryName) {
64+
return switch (categoryName) {
65+
case "일반쓰레기/음식물쓰레기" -> 1;
66+
case "투명페트병/비닐류" -> 2;
67+
case "그외 재활용품" -> 3;
68+
default -> 4;
69+
};
70+
}
71+
}

src/main/java/com/trashheroesbe/feature/user/api/UserController.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import static com.trashheroesbe.global.response.type.SuccessCode.OK;
44

5-
import com.trashheroesbe.feature.district.dto.response.DistrictListResponse;
65
import com.trashheroesbe.feature.user.application.UserService;
76
import com.trashheroesbe.feature.user.dto.request.UpdateUserRequest;
87
import com.trashheroesbe.feature.user.dto.response.UserDistrictResponse;
@@ -18,7 +17,6 @@
1817
import org.springframework.web.bind.annotation.PatchMapping;
1918
import org.springframework.web.bind.annotation.PathVariable;
2019
import org.springframework.web.bind.annotation.PostMapping;
21-
import org.springframework.web.bind.annotation.RequestBody;
2220
import org.springframework.web.bind.annotation.RequestMapping;
2321
import org.springframework.web.bind.annotation.RequestPart;
2422
import org.springframework.web.bind.annotation.RestController;
@@ -56,9 +54,10 @@ public ApiResponse<Void> createUserDistrict(
5654
@Override
5755
@DeleteMapping("/districts/{userDistrictId}")
5856
public ApiResponse<Void> deleteUserDistrict(
59-
@PathVariable Long userDistrictId
57+
@PathVariable Long userDistrictId,
58+
@AuthenticationPrincipal CustomerDetails customerDetails
6059
) {
61-
userService.deleteUserDistrict(userDistrictId);
60+
userService.deleteUserDistrict(userDistrictId, customerDetails.getUser().getId());
6261
return ApiResponse.success(OK);
6362
}
6463

src/main/java/com/trashheroesbe/feature/user/api/UserControllerApi.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ ApiResponse<Long> updateUser(
3636
ApiResponse<Void> createUserDistrict(CustomerDetails customerDetails, String districtId);
3737

3838
@Operation(summary = "유저 자치구 삭제하기", description = "유저의 자치구를 삭제합니다.")
39-
ApiResponse<Void> deleteUserDistrict(Long userDistrictId);
39+
ApiResponse<Void> deleteUserDistrict(Long userDistrictId, CustomerDetails customerDetails);
4040

4141
@Operation(summary = "내 자치구 조회하기", description = "나의 등록된 자치구를 조회합니다.")
4242
ApiResponse<List<UserDistrictResponse>> getMyDistricts(CustomerDetails customerDetails);
4343

44-
@Operation(summary = "유저 대표 자치구 수정하기", description = "유저의 자치구를 수정합니다.")
45-
ApiResponse<Void> updateDefaultUserDistrict(Long userDistrictId, CustomerDetails customerDetails);
44+
@Operation(summary = "유저 대표(default) 자치구 수정하기", description = "유저 자치구의 default 값을 수정합니다.")
45+
ApiResponse<Void> updateDefaultUserDistrict(Long userDistrictId,
46+
CustomerDetails customerDetails);
4647
}

src/main/java/com/trashheroesbe/feature/user/application/UserService.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,20 @@ public void createUserDistrict(Long userId, String districtId) {
9696
}
9797

9898
@Transactional
99-
public void deleteUserDistrict(Long userDistrictId) {
99+
public void deleteUserDistrict(Long userDistrictId, Long userId) {
100100
if (!userDistrictFinder.existsByUserDistrictId(userDistrictId)) {
101101
throw new BusinessException(ENTITY_NOT_FOUND);
102102
}
103+
104+
List<UserDistrict> userDistricts = userDistrictFinder.findByUserId(userId);
105+
if (userDistricts.size() == MAX_USER_DISTRICTS) {
106+
UserDistrict userDistrict = userDistricts.stream()
107+
.filter(defaultUserDistrict -> !defaultUserDistrict.getId().equals(userDistrictId))
108+
.findFirst()
109+
.orElseThrow(() -> new BusinessException(ENTITY_NOT_FOUND));
110+
userDistrict.updateDefaultDistrict(true);
111+
}
112+
103113
userDistrictRepository.deleteById(userDistrictId);
104114
}
105115

src/main/java/com/trashheroesbe/global/auth/jwt/entity/TokenType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@Getter
77
@RequiredArgsConstructor
88
public enum TokenType {
9-
ACCESS_TOKEN("access_token", 1_800_000L), // 30분
9+
ACCESS_TOKEN("access_token", 10_800_000L), // 3시간
1010
REFRESH_TOKEN("refresh_token", 1_209_600_000L); // 2주
1111

1212
private final String name;

src/main/java/com/trashheroesbe/global/response/type/ErrorCode.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ public enum ErrorCode {
2626
// user
2727
DUPLICATE_USER_DISTRICT(HttpStatus.BAD_REQUEST, "중복된 자치구 입니다."),
2828
MAX_USER_DISTRICTS_EXCEEDED(HttpStatus.BAD_REQUEST, "자치구는 최대 2개만 가질 수 있습니다."),
29+
NOT_FOUND_USER_DISTRICTS(HttpStatus.NOT_FOUND, "자치구 등록을 하지 않으셨습니다. 등록해주세요."),
30+
NOT_FOUND_DEFAULT_USER_DISTRICTS(HttpStatus.NOT_FOUND, "대표 유저 자치구를 찾을 수 없습니다."),
2931

3032
// trash
3133
NOT_EXISTS_TRASH_TYPE(HttpStatus.INTERNAL_SERVER_ERROR, "쓰레기 타입이 존재하지 않습니다. 개발자에게 문의 주세요."),
3234
NOT_EXISTS_TRASH_ITEM(HttpStatus.INTERNAL_SERVER_ERROR, "쓰레기 품목이 존재하지 않습니다. 개발자에게 문의 주세요."),
3335
NOT_EXISTS_TRASH_DESCRIPTION(HttpStatus.INTERNAL_SERVER_ERROR, "쓰레기 설명이 존재하지 않습니다. 개발자에게 문의 주세요."),
3436
INVALID_SEARCH_KEYWORD(HttpStatus.BAD_REQUEST, "검색 키워드가 비어있습니다."),
3537

38+
// gpt
3639
ERROR_GPT_CALL(HttpStatus.BAD_GATEWAY, "GPT 호출에 실패했습니다."),
3740
EMPTY_GPT_RESPONSE(HttpStatus.BAD_GATEWAY, "GPT 응답이 비었습니다."),
3841
FAIL_PARSING_RESPONSE(HttpStatus.UNPROCESSABLE_ENTITY, "응답 파싱에 실패했습니다"),

0 commit comments

Comments
 (0)