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
Expand Up @@ -38,8 +38,7 @@ public ResponseEntity<Void> deleteDevice(
@AuthDevice final DeviceIdentifier identifier,
@RequestBody final DeviceDeleteRequest request
) {
final DeviceDeleteInput input = DeviceDeleteInput.from(request.email(), identifier,
request.targetDeviceIdentifier());
final DeviceDeleteInput input = DeviceDeleteInput.from(identifier, request.targetDeviceIdentifier());
memberService.deleteDevice(input);
return ResponseEntity.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -46,12 +45,8 @@ public ResponseEntity<MemberSaveResponse> saveMember(@RequestBody final MemberSa
}

@GetMapping
public ResponseEntity<MemberFindResponse> findAllMemberDevices(
@RequestParam(name = "email") final String email,
@AuthDevice final DeviceIdentifier identifier
) {

final MemberFindInput input = MemberFindInput.from(email, identifier);
public ResponseEntity<MemberFindResponse> findAllMemberDevices(@AuthDevice final DeviceIdentifier identifier) {
final MemberFindInput input = MemberFindInput.from(identifier);
final MemberFindOutput output = memberService.findAllMemberDevices(input);
final MemberFindResponse response = MemberFindResponse.from(output);
return ResponseEntity.ok(response);
Expand Down
24 changes: 19 additions & 5 deletions src/main/java/com/recyclestudy/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ public MemberSaveOutput saveDevice(final MemberSaveInput input) {

@Transactional(readOnly = true)
public MemberFindOutput findAllMemberDevices(final MemberFindInput input) {
final List<Device> devices = deviceRepository.findAllByMemberEmail(input.email());
return MemberFindOutput.of(input.email(), devices);
final Member member = memberRepository.findByIdentifier(input.deviceIdentifier())
.orElseThrow(() -> new UnauthorizedException("인증되지 않은 디바이스입니다"));
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memberRepository.findByIdentifier() 실패 시 findAllMemberDevices()만 "인증되지 않은 디바이스입니다"를 사용하고, 같은 패턴을 쓰는 다른 메서드들(findNotificationTime/updateNotificationTime/deleteDevice)은 "유효하지 않은 디바이스입니다"를 사용하고 있습니다. @AuthDevice 리졸버의 메시지/다른 서비스들과도 달라서 클라이언트 입장에서 에러 메시지가 일관되지 않습니다. 이 메서드도 동일한 메시지로 통일하거나(권장), 여기서 구분하려면 실제로 활성 여부를 판단하는 로직을 함께 두는 쪽이 맞습니다.

Suggested change
.orElseThrow(() -> new UnauthorizedException("인증되지 않은 디바이스입니다"));
.orElseThrow(() -> new UnauthorizedException("유효하지 않은 디바이스입니다"));

Copilot uses AI. Check for mistakes.

final List<Device> devices = deviceRepository.findAllByMemberEmail(member.getEmail());
return MemberFindOutput.of(member.getEmail(), devices);
}

@Transactional(readOnly = true)
Expand All @@ -69,11 +72,11 @@ public void authenticateDevice(final Email email, final DeviceIdentifier deviceI
checkExistedMember(email);

final Device device = deviceRepository.findByIdentifier(deviceIdentifier)
.orElseThrow(() -> new NotFoundException("존재하지 않는 디바이스 아이디입니다: %s"
.orElseThrow(() -> new NotFoundException("존재하지 않는 디바이스 식별자입니다: %s"
.formatted(deviceIdentifier.getValue())));

if (device.isActive()) {
throw new BadRequestException("이미 인증되었습니다");
throw new BadRequestException("이미 인증된 디바이스입니다");
}

device.verifyOwner(email);
Expand All @@ -83,7 +86,18 @@ public void authenticateDevice(final Email email, final DeviceIdentifier deviceI

@Transactional
public void deleteDevice(final DeviceDeleteInput input) {
deviceRepository.deleteByIdentifier(input.targetDeviceIdentifier());
final Member requestMember = memberRepository.findByIdentifier(input.deviceIdentifier())
.orElseThrow(() -> new UnauthorizedException("유효하지 않은 디바이스입니다"));

final Device targetDevice = deviceRepository.findByIdentifier(input.targetDeviceIdentifier())
.orElseThrow(() -> new NotFoundException("존재하지 않는 디바이스입니다: %s"
.formatted(input.targetDeviceIdentifier().getValue())));

if (!targetDevice.getMember().hasEmail(requestMember.getEmail())) {
throw new NotFoundException("존재하지 않는 디바이스입니다: %s".formatted(input.targetDeviceIdentifier().getValue()));
}

deviceRepository.delete(targetDevice);
Comment on lines 88 to +100
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteDevice()에서 deviceRepository.findByIdentifier(...)는 member를 fetch join하지 않기 때문에, 이후 targetDevice.getMember().hasEmail(...) 호출 시 추가 쿼리가 발생합니다(지금은 1건이라도 항상 추가 round-trip). 소유권 검증/삭제를 한 번의 쿼리로 처리하도록 (1) findByIdentifier에 JOIN FETCH를 추가한 메서드를 만들거나, (2) identifier+member 조건으로 조회/삭제하는 전용 repository 메서드(예: findByIdentifierAndMemberEmail / deleteByIdentifierAndMemberEmail)를 고려해 주세요.

Copilot uses AI. Check for mistakes.
log.info("[DEVICE_DELETED] 디바이스 삭제 성공: {}", input.targetDeviceIdentifier());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package com.recyclestudy.member.service.input;

import com.recyclestudy.member.domain.DeviceIdentifier;
import com.recyclestudy.member.domain.Email;

public record DeviceDeleteInput(Email email, DeviceIdentifier deviceIdentifier,
DeviceIdentifier targetDeviceIdentifier) {
public record DeviceDeleteInput(DeviceIdentifier deviceIdentifier, DeviceIdentifier targetDeviceIdentifier) {

public static DeviceDeleteInput from(
final String emailValue,
final DeviceIdentifier identifier,
final String targetIdentifier
) {
final Email email = Email.from(emailValue);
final DeviceIdentifier targetDeviceIdentifier = DeviceIdentifier.from(targetIdentifier);
return new DeviceDeleteInput(email, identifier, targetDeviceIdentifier);
return new DeviceDeleteInput(identifier, targetDeviceIdentifier);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.recyclestudy.member.service.input;

import com.recyclestudy.member.domain.DeviceIdentifier;
import com.recyclestudy.member.domain.Email;

public record MemberFindInput(Email email, DeviceIdentifier deviceIdentifier) {
public record MemberFindInput(DeviceIdentifier deviceIdentifier) {

public static MemberFindInput from(final String emailValue, final DeviceIdentifier identifier) {
final Email email = Email.from(emailValue);
return new MemberFindInput(email, identifier);
public static MemberFindInput from(final DeviceIdentifier identifier) {
return new MemberFindInput(identifier);
}
}
Loading