Skip to content
Merged
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
@@ -0,0 +1,69 @@
package com.widyu.location.parentlocation.application;

import com.widyu.global.error.BusinessException;
import com.widyu.global.error.ErrorCode;
import com.widyu.location.parentlocation.dto.request.ParentLocationCreateRequest;
import com.widyu.location.parentlocation.dto.response.LocationInfo;
import com.widyu.location.parentlocation.dto.response.ParentLocationResponse;
import com.widyu.location.parentlocation.dto.response.SeniorWithLocationsResponse;
import com.widyu.location.parentlocation.repository.ParentLocationRepository;
import com.widyu.member.FamilyConnection;
import com.widyu.member.Member;
import com.widyu.member.repository.FamilyConnectionRepository;
import com.widyu.member.repository.MemberRepository;
import com.widyu.parentlocation.ParentLocation;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ParentLocationService {

private final ParentLocationRepository parentLocationRepository;
private final MemberRepository memberRepository;
private final FamilyConnectionRepository familyConnectionRepository;

public List<SeniorWithLocationsResponse> findAllByGuardianId(Long guardianId) {

List<FamilyConnection> familyConnections = familyConnectionRepository
.findAllByGuardianIdWithSeniorAndMember(guardianId);

return familyConnections.stream()
.map(fc -> {
Member senior = fc.getSenior().getMember();
List<LocationInfo> locations = parentLocationRepository.findAllByMember(senior)
.stream()
.map(LocationInfo::of)
.collect(Collectors.toList());
return SeniorWithLocationsResponse.of(senior, locations);
})
.collect(Collectors.toList());
}

@Transactional
public void create(ParentLocationCreateRequest request) {
Member seniorMember = memberRepository.findById(request.memberId())
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "존재하지 않는 회원입니다."));

if (parentLocationRepository.existsByMemberAndPlaceAddress(seniorMember, request.placeAddress())) {
throw new BusinessException(ErrorCode.BAD_REQUEST, "이미 등록된 주소입니다.");
}

parentLocationRepository.save(request.toEntity(seniorMember));
}

@Transactional
public void delete(Long memberId, Long parentLocationId) {
Member seniorMember = memberRepository.findById(memberId)
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "존재하지 않는 회원입니다."));

ParentLocation location = parentLocationRepository.findByIdAndMember(parentLocationId, seniorMember)
.orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "이미 삭제된 장소입니다."));

parentLocationRepository.delete(location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.widyu.location.parentlocation.controller;

import com.widyu.global.response.ApiResponseTemplate;
import com.widyu.location.parentlocation.application.ParentLocationService;
import com.widyu.location.parentlocation.controller.docs.ParentLocationDocs;
import com.widyu.location.parentlocation.dto.request.ParentLocationCreateRequest;
import com.widyu.location.parentlocation.dto.response.ParentLocationResponse;
import com.widyu.location.parentlocation.dto.response.SeniorWithLocationsResponse;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/goals/parent-locations")
public class ParentLocationController implements ParentLocationDocs {

private final ParentLocationService parentLocationService;

@PostMapping
public ApiResponseTemplate<Void> createParentLocation(
@Valid @RequestBody ParentLocationCreateRequest request
) {
parentLocationService.create(request);
return ApiResponseTemplate.ok()
.code("PLO_2001")
.message("부모님 장소가 등록되었습니다.")
.build();
}

@DeleteMapping("/{memberId}/{parentLocationId}")
public ApiResponseTemplate<Void> deleteParentLocation(
@PathVariable Long memberId,
@PathVariable Long parentLocationId
) {
parentLocationService.delete(memberId, parentLocationId);
return ApiResponseTemplate.ok()
.code("PLO_2002")
.message("부모님 장소가 삭제되었습니다.")
.build();
}

@GetMapping("/{guardianId}")
public ApiResponseTemplate<List<SeniorWithLocationsResponse>> getParentLocations(
@PathVariable Long guardianId
) {
List<SeniorWithLocationsResponse> data = parentLocationService.findAllByGuardianId(guardianId);
return ApiResponseTemplate.ok()
.code("PLO_2000")
.message("부모님 장소 목록이 조회되었습니다.")
.body(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.widyu.location.parentlocation.controller.docs;

import com.widyu.global.response.ApiResponseTemplate;
import com.widyu.location.parentlocation.dto.request.ParentLocationCreateRequest;
import com.widyu.location.parentlocation.dto.response.ParentLocationResponse;
import com.widyu.location.parentlocation.dto.response.SeniorWithLocationsResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;

@Tag(name = "Parent-Location", description = "부모님 장소 관리 API")
public interface ParentLocationDocs {

@Operation(
summary = "부모님 장소 등록",
description = "시니어 회원의 자주 가는 장소를 등록합니다. 장소 타입은 HOME(집) 또는 OTHER(기타)로 구분됩니다."
)
@RequestBody(
description = "부모님 장소 등록 정보",
required = true,
content = @Content(
schema = @Schema(implementation = ParentLocationCreateRequest.class),
examples = @ExampleObject(
value = """
{
"memberId": 1,
"locationType": "HOME",
"placeAddress": "서울특별시 강남구 테헤란로 123",
"latitude": "37.5012",
"longitude": "127.0396"
}
"""
)
)
)
@ApiResponse(
responseCode = "200",
description = "부모님 장소 등록 성공",
content = @Content(
schema = @Schema(implementation = ApiResponseTemplate.class),
examples = @ExampleObject(
value = """
{
"code": "PLO_2001",
"message": "부모님 장소가 등록되었습니다.",
"data": null
}
"""
)
)
)
ApiResponseTemplate<Void> createParentLocation(ParentLocationCreateRequest request);

@Operation(
summary = "부모님 장소 삭제",
description = "등록된 부모님 장소를 삭제합니다. 소프트 삭제 방식으로 상태만 변경됩니다."
)
@ApiResponse(
responseCode = "200",
description = "부모님 장소 삭제 성공",
content = @Content(
schema = @Schema(implementation = ApiResponseTemplate.class),
examples = @ExampleObject(
value = """
{
"code": "PLO_2002",
"message": "부모님 장소가 삭제되었습니다.",
"data": null
}
"""
)
)
)
ApiResponseTemplate<Void> deleteParentLocation(
@Parameter(description = "시니어 회원 ID", required = true, example = "1")
Long memberId,
@Parameter(description = "삭제할 부모님 장소 ID", required = true, example = "1")
Long parentLocationId
);

@Operation(
summary = "부모님 장소 목록 조회",
description = "보호자가 관리하는 모든 부모님들의 등록된 장소 목록을 부모님별로 그룹핑하여 조회합니다."
)
@ApiResponse(
responseCode = "200",
description = "부모님 장소 목록 조회 성공",
content = @Content(
schema = @Schema(implementation = ApiResponseTemplate.class),
examples = @ExampleObject(
value = """
{
"code": "PLO_2000",
"message": "부모님 장소 목록이 조회되었습니다.",
"data": [
{
"memberId": 1,
"memberName": "홍길동",
"locations": [
{
"parentLocationId": 1,
"locationType": "HOME",
"placeAddress": "서울특별시 강남구 테헤란로 123",
"latitude": "37.5012",
"longitude": "127.0396"
},
{
"parentLocationId": 2,
"locationType": "OTHER",
"placeAddress": "서울특별시 마포구 월드컵북로 123",
"latitude": "37.5665",
"longitude": "126.9780"
}
]
},
{
"memberId": 2,
"memberName": "김영희",
"locations": [
{
"parentLocationId": 3,
"locationType": "HOME",
"placeAddress": "서울특별시 송파구 올림픽로 456",
"latitude": "37.5145",
"longitude": "127.1059"
}
]
}
]
}
"""
)
)
)
ApiResponseTemplate<List<SeniorWithLocationsResponse>> getParentLocations(
@Parameter(description = "보호자 회원 ID", required = true, example = "1")
Long guardianId
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.widyu.location.parentlocation.dto.request;

import com.widyu.global.entity.Status;
import com.widyu.member.Member;
import com.widyu.parentlocation.LocationType;
import com.widyu.parentlocation.ParentLocation;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record ParentLocationCreateRequest(
@NotNull(message = "시니어 회원 ID는 필수입니다.")
Long memberId,
@NotNull(message = "장소 타입은 필수입니다.")
LocationType locationType,
@NotBlank(message = "장소 주소는 필수입니다.")
String placeAddress,
@NotBlank(message = "위도는 필수입니다.")
String latitude,
@NotBlank(message = "경도는 필수입니다.")
String longitude
) {
public ParentLocation toEntity(Member seniorMember) {
return ParentLocation.builder()
.member(seniorMember)
.locationType(locationType)
.placeAddress(placeAddress)
.latitude(latitude)
.longitude(longitude)
.status(Status.ACTIVE)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.widyu.location.parentlocation.dto.response;

import com.widyu.parentlocation.LocationType;
import com.widyu.parentlocation.ParentLocation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class LocationInfo {
private Long parentLocationId;
private LocationType locationType;
private String placeAddress;
private String latitude;
private String longitude;

public static LocationInfo of(ParentLocation parentLocation) {
return LocationInfo.builder()
.parentLocationId(parentLocation.getId())
.locationType(parentLocation.getLocationType())
.placeAddress(parentLocation.getPlaceAddress())
.latitude(parentLocation.getLatitude())
.longitude(parentLocation.getLongitude())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.widyu.location.parentlocation.dto.response;

import com.widyu.parentlocation.LocationType;
import com.widyu.parentlocation.ParentLocation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class ParentLocationResponse {
private Long parentLocationId;
private Long memberId;
private String memberName;
private LocationType locationType;
private String placeAddress;
private String latitude;
private String longitude;

public static ParentLocationResponse of(ParentLocation parentLocation) {
return ParentLocationResponse.builder()
.parentLocationId(parentLocation.getId())
.memberId(parentLocation.getMember().getId())
.memberName(parentLocation.getMember().getName())
.locationType(parentLocation.getLocationType())
.placeAddress(parentLocation.getPlaceAddress())
.latitude(parentLocation.getLatitude())
.longitude(parentLocation.getLongitude())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.widyu.location.parentlocation.dto.response;

import com.widyu.member.Member;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class SeniorWithLocationsResponse {
private Long memberId;
private String memberName;
private List<LocationInfo> locations;

public static SeniorWithLocationsResponse of(Member senior, List<LocationInfo> locations) {
return SeniorWithLocationsResponse.builder()
.memberId(senior.getId())
.memberName(senior.getName())
.locations(locations)
.build();
}
}
Loading
Loading