Skip to content

Commit 2662ee9

Browse files
committed
feat: UUID 기능
1 parent 9f5e634 commit 2662ee9

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

src/main/java/com/back/web7_9_codecrete_be/domain/plans/repository/PlanParticipantRepository.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,24 @@
44
import org.springframework.data.jpa.repository.JpaRepository;
55
import org.springframework.stereotype.Repository;
66

7+
import java.util.Optional;
8+
79
@Repository
810
public interface PlanParticipantRepository extends JpaRepository<PlanParticipant, Long> {
911

12+
/**
13+
* 특정 사용자와 플랜의 조합으로 참가자 존재 여부 확인
14+
* @param userId 사용자 ID
15+
* @param planId 플랜 ID
16+
* @return 존재 여부
17+
*/
18+
boolean existsByUser_IdAndPlan_PlanId(Long userId, Long planId);
19+
20+
/**
21+
* 특정 사용자와 플랜의 조합으로 참가자 조회
22+
* @param userId 사용자 ID
23+
* @param planId 플랜 ID
24+
* @return PlanParticipant
25+
*/
26+
Optional<PlanParticipant> findByUser_IdAndPlan_PlanId(Long userId, Long planId);
1027
}

src/main/java/com/back/web7_9_codecrete_be/domain/plans/service/PlanService.java

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ public PlanResponse updatePlan(Long planId, User user, PlanUpdateRequest request
224224
*/
225225
@Transactional
226226
public PlanDeleteResponse deletePlan(Long planId, User user) {
227-
Plan plan = findPlanWithEditPermissionCheck(planId, user);
227+
Plan plan = findPlanWithOwnerCheck(planId, user);
228228
Long deletedPlanId = plan.getPlanId();
229229

230230
// Plan 삭제 시 cascade 설정으로 인해 participants와 schedules도 함께 삭제.
@@ -460,6 +460,7 @@ public ScheduleDeleteResponse deleteSchedule(Long planId, Long scheduleId, User
460460
.build();
461461
}
462462

463+
463464
/**
464465
* Plan을 조회하고 참가자 권한을 체크하는 메서드
465466
*
@@ -491,6 +492,28 @@ private Plan findPlanWithParticipantCheck(Long planId, User user) {
491492
return plan;
492493
}
493494

495+
/**
496+
* Plan을 조회하고 소유자 권한을 체크
497+
* 소유자만 삭제 가능
498+
*
499+
* @param planId 계획 ID
500+
* @param user 현재 로그인한 사용자
501+
* @return Plan 엔티티
502+
* @throws BusinessException 계획을 찾을 수 없거나 소유자가 아닌 경우
503+
*/
504+
private Plan findPlanWithOwnerCheck(Long planId, User user) {
505+
Long userId = user.getId();
506+
Plan plan = planRepository.findById(planId)
507+
.orElseThrow(() -> new BusinessException(PlanErrorCode.PLAN_NOT_FOUND));
508+
509+
// 소유자만 삭제 가능
510+
if (!plan.getUserId().equals(userId)) {
511+
throw new BusinessException(PlanErrorCode.PLAN_UNAUTHORIZED);
512+
}
513+
514+
return plan;
515+
}
516+
494517
/**
495518
* Plan을 조회하고 수정/삭제 권한을 체크
496519
* OWNER 또는 EDITOR만 수정/삭제 가능
@@ -667,7 +690,95 @@ public void updateParticipantRole(Long planId, Long participantId, User user,
667690
participant.updateRole(request.getRole());
668691
}
669692

670-
// 계획 공유 초대
693+
/**
694+
* 공유 링크 생성 (UUID)
695+
*
696+
* @param planId 계획 ID
697+
* @param user 현재 로그인한 사용자 (권한 체크용)
698+
* @return 공유 링크 응답 DTO
699+
* @throws BusinessException 계획을 찾을 수 없거나 권한이 없는 경우
700+
*/
701+
@Transactional
702+
public PlanShareLinkResponse generateShareLink(Long planId, User user) {
703+
// 권한 체크 (수정 권한 확인: OWNER 또는 EDITOR)
704+
Plan plan = findPlanWithEditPermissionCheck(planId, user);
705+
706+
// shareToken이 이미 있으면 재사용, 없으면 생성
707+
if (plan.getShareToken() == null) {
708+
plan.generateShareToken();
709+
planRepository.save(plan);
710+
}
711+
712+
return PlanShareLinkResponse.builder()
713+
.planId(plan.getPlanId())
714+
.shareToken(plan.getShareToken())
715+
.shareLink("/plans/share/" + plan.getShareToken())
716+
.build();
717+
}
718+
719+
/**
720+
* 공유 링크로 플랜 참가
721+
*
722+
* @param shareToken 공유 토큰 (UUID)
723+
* @param user 현재 로그인한 사용자
724+
* @return 플랜 상세 정보
725+
* @throws BusinessException 공유 링크가 유효하지 않은 경우, 이미 참가자인 경우
726+
*/
727+
@Transactional
728+
public PlanDetailResponse joinPlanByShareToken(String shareToken, User user) {
729+
// shareToken으로 Plan 찾기
730+
Plan plan = planRepository.findByShareToken(shareToken)
731+
.orElseThrow(() -> new BusinessException(PlanErrorCode.INVALID_SHARE_TOKEN));
732+
733+
// 자기 자신의 플랜은 참가할 수 없음
734+
if (plan.getUserId().equals(user.getId())) {
735+
throw new BusinessException(PlanErrorCode.USER_ALREADY_PARTICIPANT);
736+
}
737+
738+
// DB 레벨에서 이미 참가자인지 확인 (유니크 제약조건 검증)
739+
boolean isAlreadyParticipant = planParticipantRepository.existsByUser_IdAndPlan_PlanId(
740+
user.getId(), plan.getPlanId());
741+
742+
if (isAlreadyParticipant) {
743+
// 이미 참가자인 경우 상태를 ACCEPTED로 변경
744+
PlanParticipant participant = planParticipantRepository
745+
.findByUser_IdAndPlan_PlanId(user.getId(), plan.getPlanId())
746+
.orElseThrow(() -> new BusinessException(PlanErrorCode.PLAN_NOT_FOUND));
747+
748+
participant.updateInviteStatus(PlanParticipant.InviteStatus.ACCEPTED);
749+
planParticipantRepository.save(participant);
750+
} else {
751+
// 새로운 참가자 추가 (기본 역할은 VIEWER, 상태는 ACCEPTED)
752+
PlanParticipant participant = PlanParticipant.builder()
753+
.user(user)
754+
.plan(plan)
755+
.inviteStatus(PlanParticipant.InviteStatus.ACCEPTED)
756+
.role(PlanParticipant.ParticipantRole.VIEWER)
757+
.build();
758+
759+
plan.addParticipant(participant);
760+
planRepository.save(plan);
761+
}
762+
763+
return getPlanDetail(plan.getPlanId(), user);
764+
}
765+
766+
/**
767+
* 공유 링크 삭제 (shareToken 제거)
768+
*
769+
* @param planId 계획 ID
770+
* @param user 현재 로그인한 사용자 (권한 체크용)
771+
* @throws BusinessException 계획을 찾을 수 없거나 권한이 없는 경우
772+
*/
773+
@Transactional
774+
public void deleteShareLink(Long planId, User user) {
775+
// 권한 체크 (수정 권한 확인: OWNER 또는 EDITOR)
776+
Plan plan = findPlanWithEditPermissionCheck(planId, user);
777+
778+
plan.clearShareToken();
779+
planRepository.save(plan);
780+
}
781+
671782
// 계획 공유 수락
672783
// 계획 공유 거절
673784
// 계획 공유 인원 추방

0 commit comments

Comments
 (0)