Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public OpenAPI zimdugoOpenAPI() {
.info(new Info()
.title("Zimdugo API")
.description("Zimdugo 백엔드 API 문서"))
.addServersItem(new Server().url("https://api.zimdugo.com").description("Production"))
.addServersItem(new Server().url("http://localhost:8080").description("Local"))
.addServersItem(new Server().url("/").description("Current server"))
.addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME))
.components(new Components()
.addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme()
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/zimdugo/core/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public enum ErrorCode implements BaseCode {
USER_ALREADY_WITHDRAWN("U4002", "user.already_withdrawn", HttpStatus.BAD_REQUEST),


LOCKER_NOT_FOUND("L4041", "locker.not_found", HttpStatus.NOT_FOUND),


UNSUPPORTED_SOCIAL_LOGIN("A4005", "auth.unsupported_social_login", HttpStatus.BAD_REQUEST),
AUTHENTICATED_USER_NOT_FOUND("A4011", "auth.authenticated_user_not_found", HttpStatus.UNAUTHORIZED);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.zimdugo.locker.application;

import com.zimdugo.locker.domain.FavoriteLockerStore;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class FavoriteLockerCommandService {

private final FavoriteLockerStore favoriteLockerStore;

public void add(Long userId, Long lockerId) {
favoriteLockerStore.add(userId, lockerId);
}

public void remove(Long userId, Long lockerId) {
favoriteLockerStore.remove(userId, lockerId);
}

public void reorder(Long userId, List<Long> lockerIds) {
favoriteLockerStore.reorder(userId, lockerIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.zimdugo.locker.application;

import java.time.LocalDateTime;

public record FavoriteLockerItemResponse(
Long lockerId,
String poiName,
String roadAddress,
double latitude,
double longitude,
LocalDateTime favoritedAt,
LocalDateTime lastCompletedVoteAt,
Long distanceMeters
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.zimdugo.locker.application;

import com.zimdugo.locker.domain.FavoriteLocker;
import com.zimdugo.locker.domain.FavoriteLockerPage;
import com.zimdugo.locker.domain.FavoriteLockerReader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class FavoriteLockerQueryService {

private final FavoriteLockerReader favoriteLockerReader;

@Transactional(readOnly = true)
public FavoriteLockerResponse getFavorites(Long userId, int page, int size, Double latitude, Double longitude) {
FavoriteLockerPage favoritePage = favoriteLockerReader.findByUserId(userId, page, size, latitude, longitude);

return new FavoriteLockerResponse(
favoritePage.totalCount(),
favoritePage.page(),
favoritePage.size(),
favoritePage.hasNext(),
favoritePage.favorites().stream()
.map(this::toItemResponse)
.toList()
);
}

@Transactional(readOnly = true)
public FavoriteLockerStatusResponse getFavoriteStatus(Long userId, Long lockerId) {
return new FavoriteLockerStatusResponse(
lockerId,
favoriteLockerReader.existsByUserIdAndLockerId(userId, lockerId)
);
}

private FavoriteLockerItemResponse toItemResponse(FavoriteLocker favorite) {
return new FavoriteLockerItemResponse(
favorite.lockerId(),
favorite.poiName(),
favorite.roadAddress(),
favorite.latitude(),
favorite.longitude(),
favorite.favoritedAt(),
favorite.lastCompletedVoteAt(),
favorite.distanceMeters()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zimdugo.locker.application;

import java.util.List;

public record FavoriteLockerResponse(
long totalCount,
int page,
int size,
boolean hasNext,
List<FavoriteLockerItemResponse> items
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.zimdugo.locker.application;

public record FavoriteLockerStatusResponse(
Long lockerId,
boolean favorite
) {
}
15 changes: 15 additions & 0 deletions src/main/java/com/zimdugo/locker/domain/FavoriteLocker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.zimdugo.locker.domain;

import java.time.LocalDateTime;

public record FavoriteLocker(
Long lockerId,
String poiName,
String roadAddress,
double latitude,
double longitude,
LocalDateTime favoritedAt,
LocalDateTime lastCompletedVoteAt,
Long distanceMeters
) {
}
12 changes: 12 additions & 0 deletions src/main/java/com/zimdugo/locker/domain/FavoriteLockerPage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zimdugo.locker.domain;

import java.util.List;

public record FavoriteLockerPage(
long totalCount,
int page,
int size,
boolean hasNext,
List<FavoriteLocker> favorites
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.zimdugo.locker.domain;

public interface FavoriteLockerReader {

FavoriteLockerPage findByUserId(Long userId, int page, int size, Double latitude, Double longitude);

boolean existsByUserIdAndLockerId(Long userId, Long lockerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zimdugo.locker.domain;

import java.util.List;

public interface FavoriteLockerStore {

void add(Long userId, Long lockerId);

void remove(Long userId, Long lockerId);

void reorder(Long userId, List<Long> lockerIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zimdugo.locker.entrypoint;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.List;

public record FavoriteLockerOrderUpdateRequest(
@NotEmpty(message = "validation.not_empty")
List<@NotNull @Positive Long> lockerIds
) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
129 changes: 129 additions & 0 deletions src/main/java/com/zimdugo/locker/entrypoint/LockerFavoriteApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.zimdugo.locker.entrypoint;

import com.zimdugo.core.response.RestResponse;
import com.zimdugo.locker.application.FavoriteLockerResponse;
import com.zimdugo.locker.application.FavoriteLockerStatusResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Positive;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "Locker Favorite", description = "보관함 즐겨찾기 API")
public interface LockerFavoriteApi {

@Operation(
summary = "내 즐겨찾기 보관함 조회",
description = "로그인 사용자가 즐겨찾기한 보관함 목록을 조회합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 페이지 요청"),
@ApiResponse(responseCode = "401", description = "로그인 필요")
})
@GetMapping("/me/favorite-lockers")
ResponseEntity<RestResponse<FavoriteLockerResponse>> getMyFavoriteLockers(
Authentication authentication,
@RequestParam(name = "page", defaultValue = "0")
@Parameter(description = "페이지 번호(0부터 시작)", example = "0")
@Min(0)
int page,
@RequestParam(name = "size", defaultValue = "20")
@Parameter(description = "페이지 크기", example = "20")
@Min(1)
@Max(50)
int size,
@RequestParam(name = "lat", required = false)
@Parameter(description = "현재 사용자 위도", example = "37.556")
@DecimalMin(value = "-90.0")
@DecimalMax(value = "90.0")
Double latitude,
@RequestParam(name = "lng", required = false)
@Parameter(description = "현재 사용자 경도", example = "126.923")
@DecimalMin(value = "-180.0")
@DecimalMax(value = "180.0")
Double longitude
);

@Operation(
summary = "보관함 즐겨찾기 상태 조회",
description = "로그인 사용자의 특정 보관함 즐겨찾기 등록 여부를 조회합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "로그인 필요")
})
@GetMapping("/me/favorite-lockers/{lockerId}/status")
ResponseEntity<RestResponse<FavoriteLockerStatusResponse>> getFavoriteLockerStatus(
Authentication authentication,
@PathVariable("lockerId")
@Parameter(description = "보관함 ID", example = "10")
@Positive
Long lockerId
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Operation(
summary = "즐겨찾기 순서 조정",
description = "로그인 사용자의 전체 즐겨찾기 보관함 순서를 전달한 순서대로 재정렬합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "순서 변경 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 순서 변경 요청"),
@ApiResponse(responseCode = "401", description = "로그인 필요")
})
@PatchMapping("/me/favorite-lockers/order")
ResponseEntity<RestResponse<Void>> reorderFavoriteLockers(
Authentication authentication,
@Valid @RequestBody FavoriteLockerOrderUpdateRequest request
);

@Operation(
summary = "보관함 즐겨찾기 등록",
description = "로그인 사용자의 즐겨찾기 보관함으로 등록합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "등록 성공"),
@ApiResponse(responseCode = "401", description = "로그인 필요"),
@ApiResponse(responseCode = "404", description = "보관함이 존재하지 않음")
})
@PostMapping("/me/favorite-lockers/{lockerId}")
ResponseEntity<RestResponse<Void>> addFavoriteLocker(
Authentication authentication,
@PathVariable("lockerId")
@Parameter(description = "보관함 ID", example = "10")
@Positive
Long lockerId
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Operation(
summary = "보관함 즐겨찾기 해제",
description = "로그인 사용자의 즐겨찾기 보관함에서 제거합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "해제 성공"),
@ApiResponse(responseCode = "401", description = "로그인 필요")
})
@DeleteMapping("/me/favorite-lockers/{lockerId}")
ResponseEntity<RestResponse<Void>> removeFavoriteLocker(
Authentication authentication,
@PathVariable("lockerId")
@Parameter(description = "보관함 ID", example = "10")
@Positive
Long lockerId
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Loading
Loading