-
Notifications
You must be signed in to change notification settings - Fork 2
[Plan] 링크 기반 계획 공유 & UK 검증 #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
a849438
9f5e634
2662ee9
2ad3bc3
00e965b
a32e42b
9ca3f6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.back.web7_9_codecrete_be.domain.plans.dto.response; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @Schema(description = "플랜 공유 링크 응답 DTO") | ||
| public class PlanShareLinkResponse { | ||
|
|
||
| @Schema(description = "플랜 ID", example = "1") | ||
| private Long planId; | ||
|
|
||
| @Schema(description = "공유 토큰 (UUID 기반 13자)", example = "550e8400-e29b") | ||
| private String shareToken; | ||
|
|
||
| @Schema(description = "공유 링크", example = "/plans/share/550e8400-e29b") | ||
| private String shareLink; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -224,7 +224,7 @@ public PlanResponse updatePlan(Long planId, User user, PlanUpdateRequest request | |
| */ | ||
| @Transactional | ||
| public PlanDeleteResponse deletePlan(Long planId, User user) { | ||
| Plan plan = findPlanWithEditPermissionCheck(planId, user); | ||
| Plan plan = findPlanWithOwnerCheck(planId, user); | ||
| Long deletedPlanId = plan.getPlanId(); | ||
|
|
||
| // Plan 삭제 시 cascade 설정으로 인해 participants와 schedules도 함께 삭제. | ||
|
|
@@ -460,6 +460,7 @@ public ScheduleDeleteResponse deleteSchedule(Long planId, Long scheduleId, User | |
| .build(); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Plan을 조회하고 참가자 권한을 체크하는 메서드 | ||
| * | ||
|
|
@@ -491,6 +492,28 @@ private Plan findPlanWithParticipantCheck(Long planId, User user) { | |
| return plan; | ||
| } | ||
|
|
||
| /** | ||
| * Plan을 조회하고 소유자 권한을 체크 | ||
| * 소유자만 삭제 가능 | ||
| * | ||
| * @param planId 계획 ID | ||
| * @param user 현재 로그인한 사용자 | ||
| * @return Plan 엔티티 | ||
| * @throws BusinessException 계획을 찾을 수 없거나 소유자가 아닌 경우 | ||
| */ | ||
| private Plan findPlanWithOwnerCheck(Long planId, User user) { | ||
| Long userId = user.getId(); | ||
| Plan plan = planRepository.findById(planId) | ||
| .orElseThrow(() -> new BusinessException(PlanErrorCode.PLAN_NOT_FOUND)); | ||
|
|
||
| // 소유자만 삭제 가능 | ||
| if (!plan.getUserId().equals(userId)) { | ||
| throw new BusinessException(PlanErrorCode.PLAN_UNAUTHORIZED); | ||
| } | ||
|
|
||
| return plan; | ||
| } | ||
|
|
||
| /** | ||
| * Plan을 조회하고 수정/삭제 권한을 체크 | ||
| * OWNER 또는 EDITOR만 수정/삭제 가능 | ||
|
|
@@ -667,7 +690,95 @@ public void updateParticipantRole(Long planId, Long participantId, User user, | |
| participant.updateRole(request.getRole()); | ||
| } | ||
|
|
||
| // 계획 공유 초대 | ||
| /** | ||
| * 공유 링크 생성 (UUID 기반 13자) | ||
| * | ||
| * @param planId 계획 ID | ||
| * @param user 현재 로그인한 사용자 (권한 체크용) | ||
| * @return 공유 링크 응답 DTO | ||
| * @throws BusinessException 계획을 찾을 수 없거나 권한이 없는 경우 | ||
| */ | ||
| @Transactional | ||
| public PlanShareLinkResponse generateShareLink(Long planId, User user) { | ||
| // 권한 체크 (수정 권한 확인: OWNER 또는 EDITOR) | ||
| Plan plan = findPlanWithEditPermissionCheck(planId, user); | ||
|
|
||
| // shareToken이 이미 있으면 재사용, 없으면 생성 | ||
| if (plan.getShareToken() == null) { | ||
| plan.generateShareToken(); | ||
| planRepository.save(plan); | ||
| } | ||
|
|
||
| return PlanShareLinkResponse.builder() | ||
| .planId(plan.getPlanId()) | ||
| .shareToken(plan.getShareToken()) | ||
| .shareLink("/plans/share/" + plan.getShareToken()) | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * 공유 링크로 플랜 참가 | ||
| * | ||
| * @param shareToken 공유 토큰 (UUID 기반 13자) | ||
| * @param user 현재 로그인한 사용자 | ||
| * @return 플랜 상세 정보 | ||
| * @throws BusinessException 공유 링크가 유효하지 않은 경우, 이미 참가자인 경우 | ||
| */ | ||
| @Transactional | ||
| public PlanDetailResponse joinPlanByShareToken(String shareToken, User user) { | ||
| // shareToken으로 Plan 찾기 | ||
| Plan plan = planRepository.findByShareToken(shareToken) | ||
| .orElseThrow(() -> new BusinessException(PlanErrorCode.INVALID_SHARE_TOKEN)); | ||
|
|
||
| // 자기 자신의 플랜은 참가할 수 없음 | ||
| if (plan.getUserId().equals(user.getId())) { | ||
| throw new BusinessException(PlanErrorCode.USER_ALREADY_PARTICIPANT); | ||
| } | ||
|
|
||
| // DB 레벨에서 이미 참가자인지 확인 (유니크 제약조건 검증) | ||
| boolean isAlreadyParticipant = planParticipantRepository.existsByUser_IdAndPlan_PlanId( | ||
| user.getId(), plan.getPlanId()); | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exist로 검증한 부분이 좋네요 |
||
| if (isAlreadyParticipant) { | ||
| // 이미 참가자인 경우 상태를 ACCEPTED로 변경 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추후 계획 수락부분에서 변경하시려고 Join에서 accepted로 설정하게 해두신건가요...?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
아.. PENDING이 맞는데 초반 설계 단계에서 급하게 하느라 지나갔네요ㅎㅎ 조언 감사합니다!~
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래에 계획 수락 부분이 있었는데 accepted라서 당황했어요 ㅋㅋㅋㅋㅋ 혹시 다른 로직에서 검증한다고 일부러 accepted 해두신줄 알았습니다 |
||
| PlanParticipant participant = planParticipantRepository | ||
| .findByUser_IdAndPlan_PlanId(user.getId(), plan.getPlanId()) | ||
| .orElseThrow(() -> new BusinessException(PlanErrorCode.PLAN_NOT_FOUND)); | ||
|
|
||
| participant.updateInviteStatus(PlanParticipant.InviteStatus.ACCEPTED); | ||
| planParticipantRepository.save(participant); | ||
| } else { | ||
| // 새로운 참가자 추가 (기본 역할은 VIEWER, 상태는 ACCEPTED) | ||
| PlanParticipant participant = PlanParticipant.builder() | ||
| .user(user) | ||
| .plan(plan) | ||
| .inviteStatus(PlanParticipant.InviteStatus.ACCEPTED) | ||
| .role(PlanParticipant.ParticipantRole.VIEWER) | ||
| .build(); | ||
|
|
||
| plan.addParticipant(participant); | ||
| planRepository.save(plan); | ||
| } | ||
|
|
||
| return getPlanDetail(plan.getPlanId(), user); | ||
| } | ||
|
|
||
| /** | ||
| * 공유 링크 삭제 (shareToken 제거) | ||
| * | ||
| * @param planId 계획 ID | ||
| * @param user 현재 로그인한 사용자 (권한 체크용) | ||
| * @throws BusinessException 계획을 찾을 수 없거나 권한이 없는 경우 | ||
| */ | ||
| @Transactional | ||
| public void deleteShareLink(Long planId, User user) { | ||
| // 권한 체크 (수정 권한 확인: OWNER 또는 EDITOR) | ||
| Plan plan = findPlanWithEditPermissionCheck(planId, user); | ||
|
|
||
| plan.clearShareToken(); | ||
| planRepository.save(plan); | ||
| } | ||
|
|
||
| // 계획 공유 수락 | ||
| // 계획 공유 거절 | ||
| // 계획 공유 인원 추방 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UUID 쓰신 것은 좋아 보이는데, 너무 길어서 조금만 줄이시면 좋을 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
바로 반영하겠습니다!