diff --git a/src/main/java/com/spaceclub/club/controller/ClubEventController.java b/src/main/java/com/spaceclub/club/controller/ClubEventController.java index 6a288f39..0414b302 100644 --- a/src/main/java/com/spaceclub/club/controller/ClubEventController.java +++ b/src/main/java/com/spaceclub/club/controller/ClubEventController.java @@ -10,7 +10,6 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,15 +25,18 @@ public class ClubEventController { private final ClubEventService clubEventService; @GetMapping("/{clubId}/events") - public ResponseEntity> getClubEvents(@PathVariable Long clubId, @PageableDefault(size = 1000) Pageable pageable, @Authenticated JwtUser jwtUser) { + public PageResponse getClubEvents( + @PathVariable Long clubId, + @PageableDefault(size = 1000) Pageable pageable, + @Authenticated JwtUser jwtUser + ) { Page events = clubEventService.getClubEvents(clubId, pageable, jwtUser.id()); - List clubEventOverviewGetResponse = events.getContent() .stream() .map(ClubEventOverviewGetResponse::from) .toList(); - return ResponseEntity.ok(new PageResponse<>(clubEventOverviewGetResponse, events)); + return new PageResponse<>(clubEventOverviewGetResponse, events); } } diff --git a/src/main/java/com/spaceclub/club/controller/ClubUserController.java b/src/main/java/com/spaceclub/club/controller/ClubUserController.java index 9012c09d..6461e56a 100644 --- a/src/main/java/com/spaceclub/club/controller/ClubUserController.java +++ b/src/main/java/com/spaceclub/club/controller/ClubUserController.java @@ -27,35 +27,53 @@ public class ClubUserController { private final ClubMemberManagerService clubMemberManagerService; @GetMapping("/{clubId}/users") - public ResponseEntity getClubUserRole(@PathVariable Long clubId, @Authenticated JwtUser jwtUser) { + public ResponseEntity getClubUserRole( + @PathVariable Long clubId, + @Authenticated JwtUser jwtUser + ) { String role = clubMemberManagerService.getUserRole(clubId, jwtUser.id()); return ResponseEntity.ok(new ClubUserRoleResponse(role)); } @PatchMapping("/{clubId}/members/{memberId}") - public ResponseEntity updateMemberRole(@PathVariable Long clubId, @PathVariable Long memberId, @RequestBody ClubUserUpdateRequest request, @Authenticated JwtUser jwtUser) { + public ResponseEntity updateMemberRole( + @PathVariable Long clubId, + @PathVariable Long memberId, + @RequestBody ClubUserUpdateRequest request, + @Authenticated JwtUser jwtUser + ) { clubMemberManagerService.updateMemberRole(new ClubUserUpdate(clubId, memberId, request.role(), jwtUser.id())); return ResponseEntity.noContent().build(); } @GetMapping("/{clubId}/members") - public ResponseEntity> getMembers(@PathVariable Long clubId, @Authenticated JwtUser jwtUser) { + public ResponseEntity> getMembers( + @PathVariable Long clubId, + @Authenticated JwtUser jwtUser + ) { List response = clubMemberManagerService.getMembers(clubId, jwtUser.id()); return ResponseEntity.ok(response); } @DeleteMapping("/{clubId}/members/{memberId}") - public ResponseEntity deleteMember(@PathVariable Long clubId, @PathVariable Long memberId, @Authenticated JwtUser jwtUser) { + public ResponseEntity deleteMember( + @PathVariable Long clubId, + @PathVariable Long memberId, + @Authenticated JwtUser jwtUser + ) { clubMemberManagerService.deleteMember(clubId, memberId, jwtUser.id()); return ResponseEntity.noContent().build(); } @DeleteMapping("/{clubId}/users") - public ResponseEntity exitClub(@PathVariable Long clubId, @Authenticated JwtUser jwtUser) { + public ResponseEntity exitClub( + @PathVariable Long clubId, + @Authenticated JwtUser jwtUser + ) { clubMemberManagerService.exitClub(clubId, jwtUser.id()); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/spaceclub/club/domain/Club.java b/src/main/java/com/spaceclub/club/domain/Club.java index 84c10280..ce877ff0 100644 --- a/src/main/java/com/spaceclub/club/domain/Club.java +++ b/src/main/java/com/spaceclub/club/domain/Club.java @@ -139,17 +139,4 @@ public Club updateLogoImage(String logoImageName) { .build(); } - public Club updateCoverImage(String coverImageName) { - return Club.builder() - .id(this.id) - .name(this.name) - .logoImageName(this.logoImageName) - .coverImageName(coverImageName) - .info(this.info) - .notices(this.notices != null ? this.notices : null) - .clubUser(this.clubUser != null ? this.clubUser : null) - .createdAt(this.createdAt) - .build(); - } - } diff --git a/src/main/java/com/spaceclub/club/domain/ClubUser.java b/src/main/java/com/spaceclub/club/domain/ClubUser.java index 23eade75..6127b672 100644 --- a/src/main/java/com/spaceclub/club/domain/ClubUser.java +++ b/src/main/java/com/spaceclub/club/domain/ClubUser.java @@ -42,13 +42,21 @@ public class ClubUser extends BaseTimeEntity { private ClubUserRole role; @Builder - public ClubUser(Long id, Club club, Long userId, ClubUserRole role) { + private ClubUser(Long id, Club club, Long userId, ClubUserRole role) { this.id = id; this.club = club; this.userId = userId; this.role = role; } + public static ClubUser createManger(Club club, Long userId) { + return ClubUser.builder() + .club(club) + .userId(userId) + .role(ClubUserRole.MANAGER) + .build(); + } + public ClubUser updateRole(ClubUserRole role) { return ClubUser.builder() .id(this.id) @@ -66,4 +74,6 @@ public boolean isManager() { return this.role.equals(ClubUserRole.MANAGER); } + + } diff --git a/src/main/java/com/spaceclub/club/domain/ClubUserRole.java b/src/main/java/com/spaceclub/club/domain/ClubUserRole.java index 645ac22e..62acecc2 100644 --- a/src/main/java/com/spaceclub/club/domain/ClubUserRole.java +++ b/src/main/java/com/spaceclub/club/domain/ClubUserRole.java @@ -1,5 +1,8 @@ package com.spaceclub.club.domain; +import lombok.Getter; + +@Getter public enum ClubUserRole { MANAGER(1), @@ -11,8 +14,4 @@ public enum ClubUserRole { this.sortPriority = sortPriority; } - public int getSortPriority() { - return sortPriority; - } - } diff --git a/src/main/java/com/spaceclub/club/domain/repository/ClubRepository.java b/src/main/java/com/spaceclub/club/domain/repository/ClubRepository.java index 8183d8b8..0a6f808a 100644 --- a/src/main/java/com/spaceclub/club/domain/repository/ClubRepository.java +++ b/src/main/java/com/spaceclub/club/domain/repository/ClubRepository.java @@ -3,6 +3,10 @@ import com.spaceclub.club.domain.Club; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface ClubRepository extends JpaRepository { + Optional findByName(String name); + } diff --git a/src/main/java/com/spaceclub/club/domain/repository/ClubUserRepository.java b/src/main/java/com/spaceclub/club/domain/repository/ClubUserRepository.java index 646dabd6..7d37e396 100644 --- a/src/main/java/com/spaceclub/club/domain/repository/ClubUserRepository.java +++ b/src/main/java/com/spaceclub/club/domain/repository/ClubUserRepository.java @@ -18,12 +18,8 @@ public interface ClubUserRepository extends JpaRepository { List findByUserId(Long userId); - boolean existsByClubAndUserId(Club club, Long userId); - Long countByClub(Club club); - ClubUser findByClub_IdAndRole(Long clubId, ClubUserRole role); - Boolean existsByClub_IdAndUserId(Long clubId, Long userId); int countByUserId(Long userId); diff --git a/src/main/java/com/spaceclub/club/service/ClubEventService.java b/src/main/java/com/spaceclub/club/service/ClubEventService.java index 052dfdd0..f6ca26e3 100644 --- a/src/main/java/com/spaceclub/club/service/ClubEventService.java +++ b/src/main/java/com/spaceclub/club/service/ClubEventService.java @@ -27,8 +27,9 @@ public Page getClubEvents(Long clubId, Pageable pageab } private void checkClubMember(Long clubId, Long userId) { - if (!clubUserRepository.existsByClub_IdAndUserId(clubId, userId)) + if (!clubUserRepository.existsByClub_IdAndUserId(clubId, userId)){ throw new IllegalArgumentException(NOT_CLUB_MEMBER.toString()); + } } } diff --git a/src/main/java/com/spaceclub/club/service/ClubMemberManagerService.java b/src/main/java/com/spaceclub/club/service/ClubMemberManagerService.java index ba2b3916..1a216d4e 100644 --- a/src/main/java/com/spaceclub/club/service/ClubMemberManagerService.java +++ b/src/main/java/com/spaceclub/club/service/ClubMemberManagerService.java @@ -33,19 +33,8 @@ public class ClubMemberManagerService { private final UserService userService; @Transactional - public void updateMemberRole(ClubUserUpdate updateVo) { - this.validManager(updateVo.clubId(), updateVo.userId()); - - int count = clubUserRepository.countByClub_IdAndRole(updateVo.clubId(), MANAGER); - - if (isSelfDegrading(count, updateVo)) { - throw new IllegalArgumentException(CAN_NOT_SELF_DEGRADING.toString()); - } - - ClubUser clubMember = this.getClubUser(updateVo.clubId(), updateVo.memberId()); - ClubUser updateClubUser = clubMember.updateRole(updateVo.role()); - - clubUserRepository.save(updateClubUser); + public void save(ClubUser clubUser) { + clubUserRepository.save(clubUser); } public List getMembers(Long clubId, Long userId) { @@ -61,10 +50,6 @@ public List getMembers(Long clubId, Long userId) { .toList(); } - public Long countMember(Club club) { - return clubUserRepository.countByClub(club); - } - public String getUserRole(Long clubId, Long userId) { Optional clubUser = clubUserRepository.findByClub_IdAndUserId(clubId, userId); @@ -73,6 +58,26 @@ public String getUserRole(Long clubId, Long userId) { } + @Transactional + public void updateMemberRole(ClubUserUpdate updateVo) { + validManager(updateVo.clubId(), updateVo.userId()); + + int count = clubUserRepository.countByClub_IdAndRole(updateVo.clubId(), MANAGER); + + if (isSelfDegrading(count, updateVo)) { + throw new IllegalArgumentException(CAN_NOT_SELF_DEGRADING.toString()); + } + + ClubUser clubMember = getClubUser(updateVo.clubId(), updateVo.memberId()); + ClubUser updateClubUser = clubMember.updateRole(updateVo.role()); + + clubUserRepository.save(updateClubUser); + } + + public Long countMember(Club club) { + return clubUserRepository.countByClub(club); + } + @Transactional public void exitClub(Long clubId, Long memberId) { ClubUser clubUser = this.getClubUser(clubId, memberId); @@ -83,7 +88,7 @@ public void exitClub(Long clubId, Long memberId) { @Transactional public void deleteMember(Long clubId, Long memberId, Long userId) { - this.validManager(clubId, userId); + validManager(clubId, userId); int count = clubUserRepository.countByClub_IdAndRole(clubId, MANAGER); @@ -93,7 +98,7 @@ public void deleteMember(Long clubId, Long memberId, Long userId) { throw new IllegalArgumentException(CAN_NOT_WITHDRAW.toString()); } - ClubUser clubMember = this.getClubUser(clubId, memberId); + ClubUser clubMember = getClubUser(clubId, memberId); if (clubMember.isManager()) throw new IllegalStateException(UNAUTHORIZED.toString()); @@ -126,9 +131,4 @@ public boolean isAlreadyJoined(Club club, Long userId) { return clubUserRepository.existsByClub_IdAndUserId(club.getId(), userId); } - @Transactional - public void save(ClubUser clubUser) { - clubUserRepository.save(clubUser); - } - } diff --git a/src/main/java/com/spaceclub/club/service/ClubService.java b/src/main/java/com/spaceclub/club/service/ClubService.java index 6859e57b..fdfb1d4a 100644 --- a/src/main/java/com/spaceclub/club/service/ClubService.java +++ b/src/main/java/com/spaceclub/club/service/ClubService.java @@ -10,7 +10,6 @@ import com.spaceclub.global.aws.s3.S3Folder; import com.spaceclub.global.aws.s3.S3ImageUploader; import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -21,7 +20,6 @@ import static com.spaceclub.club.ClubExceptionMessage.DUPLICATED_CLUB_NAME; import static com.spaceclub.club.ClubExceptionMessage.NOT_CLUB_MEMBER; import static com.spaceclub.club.ClubExceptionMessage.UNAUTHORIZED; -import static com.spaceclub.club.domain.ClubUserRole.MANAGER; @Service @Transactional(readOnly = true) @@ -40,29 +38,20 @@ public class ClubService implements ClubProvider { @Transactional public Club createClub(Club club, Long userId, MultipartFile logoImage) { - if (logoImage != null) { - String logoImageName = imageUploader.upload(logoImage, S3Folder.LOGO); - club = club.updateLogoImage(logoImageName); - } - - ClubUser clubUser = ClubUser.builder() - .club(club) - .userId(userId) - .role(MANAGER) - .build(); - try { - clubUserRepository.save(clubUser); - return clubRepository.save(club); - } catch (DataIntegrityViolationException e) { - throw new IllegalArgumentException(DUPLICATED_CLUB_NAME.toString()); - } + club = uploadImage(logoImage, club, S3Folder.LOGO); + ClubUser clubUser = ClubUser.createManger(club, userId); + + clubRepository.findByName(club.getName()).ifPresent(duplicateClubName -> { + throw new IllegalArgumentException(DUPLICATED_CLUB_NAME.toString()); + }); + clubUserRepository.save(clubUser); + return clubRepository.save(club); } public Club getClub(Long clubId, Long userId) { - ClubUser clubUser = clubUserRepository.findByClub_IdAndUserId(clubId, userId) - .orElseThrow(() -> new IllegalArgumentException(NOT_CLUB_MEMBER.toString())); - - return clubUser.getClub(); + return clubUserRepository.findByClub_IdAndUserId(clubId, userId) + .orElseThrow(() -> new IllegalArgumentException(NOT_CLUB_MEMBER.toString())) + .getClub(); } @Transactional @@ -88,24 +77,22 @@ public void updateClub(Club newClub, Long userId, MultipartFile logoImage, Multi Club club = clubRepository.findById(clubId) .orElseThrow(() -> new IllegalArgumentException(CLUB_NOT_FOUND.toString())); - if (logoImage != null) { - String logoImageName = imageUploader.upload(logoImage, S3Folder.LOGO); - club = club.updateLogoImage(logoImageName); - } - - if (coverImage != null) { - String coverImageName = imageUploader.upload(coverImage, S3Folder.COVER); - club = club.updateCoverImage(coverImageName); - } - + club = uploadImage(logoImage, club, S3Folder.LOGO); + club = uploadImage(coverImage, club, S3Folder.COVER); Club updatedClub = club.update(newClub); - try { - clubRepository.save(updatedClub); - } catch (DataIntegrityViolationException e) { + clubRepository.findByName(updatedClub.getName()).ifPresent(duplicateClubName -> { throw new IllegalArgumentException(DUPLICATED_CLUB_NAME.toString()); - } + }); + clubRepository.save(updatedClub); + } + private Club uploadImage(MultipartFile logoImage, Club club, S3Folder folderName) { + if (logoImage != null) { + String logoImageName = imageUploader.upload(logoImage, folderName); + club = club.updateLogoImage(logoImageName); + } + return club; } @Override diff --git a/src/main/java/com/spaceclub/form/controller/FormController.java b/src/main/java/com/spaceclub/form/controller/FormController.java index 3770b8f6..fa0803bb 100644 --- a/src/main/java/com/spaceclub/form/controller/FormController.java +++ b/src/main/java/com/spaceclub/form/controller/FormController.java @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; @@ -37,10 +36,10 @@ public ResponseEntity create( } @GetMapping("/{eventId}/forms") - public ResponseEntity get(@PathVariable Long eventId, @Authenticated JwtUser jwtUser) { - FormGetInfo formGetInfoVo = formService.get(eventId); + public ResponseEntity get(@PathVariable Long eventId) { + FormGetInfo formInfo = formService.get(eventId); - return ResponseEntity.ok(FormGetResponse.from(formGetInfoVo)); + return ResponseEntity.ok(FormGetResponse.from(formInfo)); } } diff --git a/src/main/java/com/spaceclub/form/controller/SubmitController.java b/src/main/java/com/spaceclub/form/controller/SubmitController.java index 1ae302ef..500c1de6 100644 --- a/src/main/java/com/spaceclub/form/controller/SubmitController.java +++ b/src/main/java/com/spaceclub/form/controller/SubmitController.java @@ -24,7 +24,7 @@ public class SubmitController { private final SubmitService submitService; - @GetMapping(value = {"/{eventId}/forms/submit"}) + @GetMapping("/{eventId}/forms/submit") public ResponseEntity getAll( @PathVariable Long eventId, Pageable pageable, @@ -35,7 +35,7 @@ public ResponseEntity getAll( return ResponseEntity.ok(FormSubmitGetResponse.from(formSubmitGetInfo)); } - @PatchMapping(value = {"/{eventId}/forms/submit"}) + @PatchMapping("/{eventId}/forms/submit") public ResponseEntity updateStatus( @PathVariable Long eventId, @RequestBody FormSubmitUpdateRequest request, diff --git a/src/main/java/com/spaceclub/form/controller/response/FormGetResponse.java b/src/main/java/com/spaceclub/form/controller/response/FormGetResponse.java index 5c34f6f8..afc282a7 100644 --- a/src/main/java/com/spaceclub/form/controller/response/FormGetResponse.java +++ b/src/main/java/com/spaceclub/form/controller/response/FormGetResponse.java @@ -16,10 +16,10 @@ public record FormGetResponse( public FormGetResponse { } - public static FormGetResponse from(FormGetInfo vo) { + public static FormGetResponse from(FormGetInfo formInfo) { return FormGetResponse.builder() - .event(new EventResponse(vo.title())) - .form(new FormResponse(vo.getFormDescription(), from(vo.getFormOptions()))) + .event(new EventResponse(formInfo.title())) + .form(new FormResponse(formInfo.getFormDescription(), from(formInfo.getFormOptions()))) .build(); } diff --git a/src/main/java/com/spaceclub/form/service/FormService.java b/src/main/java/com/spaceclub/form/service/FormService.java index 26a08899..fcf0dfb6 100644 --- a/src/main/java/com/spaceclub/form/service/FormService.java +++ b/src/main/java/com/spaceclub/form/service/FormService.java @@ -26,18 +26,18 @@ public class FormService { private final ClubUserValidator clubUserValidator; @Transactional - public Long create(FormCreateInfo vo) { - Event event = eventProvider.getById(vo.eventId()); - clubUserValidator.validateClubManager(event.getClubId(), vo.userId()); + public Long create(FormCreateInfo formInfo) { + Event event = eventProvider.getById(formInfo.eventId()); + clubUserValidator.validateClubManager(event.getClubId(), formInfo.userId()); if (event.isFormed()) throw new IllegalStateException(EXISTED_FORM.toString()); - vo.form().addItems(vo.options()); - Form savedForm = formRepository.save(vo.form()); + formInfo.form().addItems(formInfo.options()); + Form savedForm = formRepository.save(formInfo.form()); Event formRegisteredEvent = event.registerForm(savedForm); eventProvider.save(formRegisteredEvent); - return vo.eventId(); + return formInfo.eventId(); } public FormGetInfo get(Long eventId) { diff --git a/src/main/java/com/spaceclub/invite/controller/InviteController.java b/src/main/java/com/spaceclub/invite/controller/InviteController.java index 6c947092..e2112377 100644 --- a/src/main/java/com/spaceclub/invite/controller/InviteController.java +++ b/src/main/java/com/spaceclub/invite/controller/InviteController.java @@ -6,6 +6,7 @@ import com.spaceclub.invite.service.vo.InviteGetInfo; import com.spaceclub.oauth.jwt.vo.JwtUser; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,13 +15,15 @@ import org.springframework.web.bind.annotation.RestController; import java.net.URI; +import java.time.LocalDateTime; @RestController @RequestMapping("/api/v1/clubs") @RequiredArgsConstructor public class InviteController { - public static final String INVITE_LINK_PREFIX = "https://spaceclub.site/api/v1/clubs/invite/"; + @Value("${invite.link-prefix}") + private String inviteLinkPrefix; private final InviteService inviteService; @@ -29,7 +32,8 @@ public ResponseEntity createInviteCode( @PathVariable Long clubId, @Authenticated JwtUser jwtUser ) { - String inviteCode = inviteService.createInviteCode(clubId, jwtUser.id()); + LocalDateTime now = LocalDateTime.now(); + String inviteCode = inviteService.createInviteCode(clubId, jwtUser.id(), now); String locationPath = "/api/v1/clubs/%d/invite/%s".formatted(clubId, inviteCode); return ResponseEntity.created(URI.create(locationPath)).build(); @@ -38,12 +42,10 @@ public ResponseEntity createInviteCode( @GetMapping("/{clubId}/invite") public ResponseEntity getInviteLink(@PathVariable Long clubId, @Authenticated JwtUser jwtUser) { Long userId = jwtUser.id(); - InviteGetInfo vo = inviteService.getInviteLink(clubId, userId); + LocalDateTime now = LocalDateTime.now(); + InviteGetInfo inviteInfo = inviteService.getInviteLink(clubId, userId, now); - String inviteLink = vo.inviteCode() != null ? INVITE_LINK_PREFIX + vo.inviteCode() : null; - boolean expired = vo.isExpired(); - - return ResponseEntity.ok(new InviteGetResponse(inviteLink, expired)); + return ResponseEntity.ok(new InviteGetResponse(inviteInfo, inviteLinkPrefix)); } } diff --git a/src/main/java/com/spaceclub/invite/controller/InviteJoinController.java b/src/main/java/com/spaceclub/invite/controller/InviteJoinController.java index 42f71bd0..634407f3 100644 --- a/src/main/java/com/spaceclub/invite/controller/InviteJoinController.java +++ b/src/main/java/com/spaceclub/invite/controller/InviteJoinController.java @@ -16,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDateTime; + @RestController @RequestMapping("/api/v1/clubs/invites") @RequiredArgsConstructor @@ -29,9 +31,8 @@ public class InviteJoinController { @PostMapping("/{code}") public ResponseEntity joinClub(@PathVariable String code, @Authenticated JwtUser jwtUser) { - - Long clubId = inviteJoinService.joinClub(code, jwtUser.id()); - + LocalDateTime now = LocalDateTime.now(); + Long clubId = inviteJoinService.joinClub(code, jwtUser.id(), now); JoinClubResponse response = new JoinClubResponse(clubId); return ResponseEntity.ok(response); @@ -39,10 +40,10 @@ public ResponseEntity joinClub(@PathVariable String code, @Aut @GetMapping("/{code}") public ResponseEntity requestToJoinClub(@PathVariable String code) { - Club club = inviteJoinService.requestToJoinClub(code); + LocalDateTime now = LocalDateTime.now(); + Club club = inviteJoinService.validateAndRetrieveClub(code, now, null); Long memberCount = clubMemberManagerService.countMember(club); - ClubRequestToJoinResponse response = ClubRequestToJoinResponse.from(club, memberCount, s3Properties.url()); return ResponseEntity.ok(response); diff --git a/src/main/java/com/spaceclub/invite/controller/response/InviteGetResponse.java b/src/main/java/com/spaceclub/invite/controller/response/InviteGetResponse.java index f059cdb7..355d893e 100644 --- a/src/main/java/com/spaceclub/invite/controller/response/InviteGetResponse.java +++ b/src/main/java/com/spaceclub/invite/controller/response/InviteGetResponse.java @@ -1,8 +1,14 @@ package com.spaceclub.invite.controller.response; +import com.spaceclub.invite.service.vo.InviteGetInfo; + public record InviteGetResponse( String inviteLink, boolean isExpired ) { + public InviteGetResponse(InviteGetInfo inviteLink, String inviteLinkPrefix) { + this(inviteLink.inviteCode() != null ? inviteLinkPrefix + inviteLink.inviteCode() : null, inviteLink.isExpired()); + } + } diff --git a/src/main/java/com/spaceclub/invite/domain/Invite.java b/src/main/java/com/spaceclub/invite/domain/Invite.java index dadcb9d8..762f4601 100644 --- a/src/main/java/com/spaceclub/invite/domain/Invite.java +++ b/src/main/java/com/spaceclub/invite/domain/Invite.java @@ -59,8 +59,15 @@ public Invite updateCode(String code) { .build(); } - public boolean isExpired() { - return LocalDateTime.now().isAfter(this.expiredAt); + public String validatedCode(LocalDateTime now) { + if (isExpired(now)) { + return null; + } + return this.code; + } + + public boolean isExpired(LocalDateTime now) { + return now.isAfter(this.expiredAt); } } diff --git a/src/main/java/com/spaceclub/invite/service/InviteJoinService.java b/src/main/java/com/spaceclub/invite/service/InviteJoinService.java index 7ed8114a..23505b82 100644 --- a/src/main/java/com/spaceclub/invite/service/InviteJoinService.java +++ b/src/main/java/com/spaceclub/invite/service/InviteJoinService.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + import static com.spaceclub.club.ClubExceptionMessage.CLUB_ALREADY_JOINED; import static com.spaceclub.invite.InviteExceptionMessage.INVITE_EXPIRED; import static com.spaceclub.invite.InviteExceptionMessage.INVITE_NOT_FOUND; @@ -23,17 +25,8 @@ public class InviteJoinService { private final ClubMemberManagerService clubMemberManagerService; - public Long joinClub(String code, Long userId) { - Invite invite = inviteRepository.findByCode(code) - .orElseThrow(() -> new IllegalStateException(INVITE_NOT_FOUND.toString())); - - if (invite.isExpired()) throw new IllegalStateException(INVITE_EXPIRED.toString()); - - Club club = invite.getClub(); - - if (clubMemberManagerService.isAlreadyJoined(club, userId)) - throw new IllegalStateException(CLUB_ALREADY_JOINED.toString()); - + public Long joinClub(String code, Long userId, LocalDateTime now) { + Club club = validateAndRetrieveClub(code, now, userId); ClubUser clubUser = ClubUser.builder() .club(club) .userId(userId) @@ -45,11 +38,15 @@ public Long joinClub(String code, Long userId) { return club.getId(); } - public Club requestToJoinClub(String code) { + public Club validateAndRetrieveClub(String code, LocalDateTime now, Long userId) { Invite invite = inviteRepository.findByCode(code) .orElseThrow(() -> new IllegalStateException(INVITE_NOT_FOUND.toString())); + if (invite.isExpired(now)) throw new IllegalStateException(INVITE_EXPIRED.toString()); - return invite.getClub(); + Club club = invite.getClub(); + if (userId != null && clubMemberManagerService.isAlreadyJoined(club, userId)) + throw new IllegalStateException(CLUB_ALREADY_JOINED.toString()); + return club; } } diff --git a/src/main/java/com/spaceclub/invite/service/InviteService.java b/src/main/java/com/spaceclub/invite/service/InviteService.java index 1d2bb167..9be4bf87 100644 --- a/src/main/java/com/spaceclub/invite/service/InviteService.java +++ b/src/main/java/com/spaceclub/invite/service/InviteService.java @@ -11,7 +11,6 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.Optional; import static com.spaceclub.invite.domain.Invite.INVITE_LINK_VALID_HOURS; @@ -20,7 +19,7 @@ @RequiredArgsConstructor public class InviteService { - private final InviteCodeGenerator inviteCodeGenerator = new UuidCodeGenerator(); + private final InviteCodeGenerator uuidCodeGenerator; private final InviteRepository inviteRepository; @@ -28,44 +27,31 @@ public class InviteService { private final ClubService clubService; - public String createInviteCode(Long clubId, Long userId) { - Club club = clubService.getClub(clubId, userId); + public String createInviteCode(Long clubId, Long userId, LocalDateTime now) { + Club club = clubService.getClub(clubId, userId); // 클럽 정보 조회 - String inviteCode = inviteCodeGenerator.generateCode(); + return inviteRepository.findByClub_Id(clubId) + .orElseGet(() -> createAndSaveInvite(club, now)).getCode(); + } - Invite tempInvite = Invite.builder() + private Invite createAndSaveInvite(Club club, LocalDateTime now) { + String inviteCode = uuidCodeGenerator.generateCode(); + Invite newInvite = Invite.builder() .code(inviteCode) .club(club) - .expiredAt(LocalDateTime.now().plusHours(INVITE_LINK_VALID_HOURS)) + .expiredAt(now.plusHours(INVITE_LINK_VALID_HOURS)) .build(); + inviteRepository.save(newInvite); - Invite invite = inviteRepository.findByClub(club) - .orElse(tempInvite); - - if (invite.isExpired()) { - invite = invite.updateCode(inviteCode); - } - - inviteRepository.save(invite); - - return invite.getCode(); + return newInvite; } - public InviteGetInfo getInviteLink(Long clubId, Long userId) { - clubUserValidator.validateClubManager(clubId, userId); - - Optional optionalInvite = inviteRepository.findByClub_Id(clubId); - - if (optionalInvite.isEmpty()) { - return new InviteGetInfo(null, false); - } - - Invite invite = optionalInvite.get(); - - String code = invite.getCode(); - boolean expired = invite.isExpired(); + public InviteGetInfo getInviteLink(Long clubId, Long userId, LocalDateTime now) { + clubUserValidator.validateClubManager(clubId, userId); // 인가처리 - return new InviteGetInfo(code, expired); + return inviteRepository.findByClub_Id(clubId) + .map(invite -> new InviteGetInfo(invite.validatedCode(now))) + .orElseThrow(); } } diff --git a/src/main/java/com/spaceclub/invite/service/UuidCodeGenerator.java b/src/main/java/com/spaceclub/invite/service/UuidCodeGenerator.java index 5891471e..35792d05 100644 --- a/src/main/java/com/spaceclub/invite/service/UuidCodeGenerator.java +++ b/src/main/java/com/spaceclub/invite/service/UuidCodeGenerator.java @@ -1,7 +1,10 @@ package com.spaceclub.invite.service; +import org.springframework.stereotype.Component; + import java.util.UUID; +@Component public class UuidCodeGenerator implements InviteCodeGenerator { @Override diff --git a/src/main/java/com/spaceclub/invite/service/vo/InviteGetInfo.java b/src/main/java/com/spaceclub/invite/service/vo/InviteGetInfo.java index b1d045bc..70f9dae0 100644 --- a/src/main/java/com/spaceclub/invite/service/vo/InviteGetInfo.java +++ b/src/main/java/com/spaceclub/invite/service/vo/InviteGetInfo.java @@ -5,4 +5,8 @@ public record InviteGetInfo( boolean isExpired ) { + public InviteGetInfo (String inviteCode) { + this(inviteCode, inviteCode != null); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0b91a291..f7614539 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -91,3 +91,6 @@ interceptor: web: interceptor-path-pattern: /api/v1/** + +invite: + link-prefix: "https://space-club.site/api/v1/clubs/invite/" diff --git a/src/test/java/com/spaceclub/form/FormTestFixture.java b/src/test/java/com/spaceclub/form/FormTestFixture.java index 36d9129e..be02984e 100644 --- a/src/test/java/com/spaceclub/form/FormTestFixture.java +++ b/src/test/java/com/spaceclub/form/FormTestFixture.java @@ -1,9 +1,9 @@ package com.spaceclub.form; import com.spaceclub.form.domain.Form; +import com.spaceclub.form.domain.FormAnswer; import com.spaceclub.form.domain.FormOption; import com.spaceclub.form.domain.FormOptionType; -import com.spaceclub.form.domain.FormAnswer; public class FormTestFixture { diff --git a/src/test/java/com/spaceclub/invite/InviteTestFixture.java b/src/test/java/com/spaceclub/invite/InviteTestFixture.java deleted file mode 100644 index 81b5e59e..00000000 --- a/src/test/java/com/spaceclub/invite/InviteTestFixture.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.spaceclub.invite; - -import com.spaceclub.invite.domain.Invite; - -import java.time.LocalDateTime; - -import static com.spaceclub.club.ClubTestFixture.club1; -import static com.spaceclub.club.ClubTestFixture.club2; - -public class InviteTestFixture { - - public static Invite invite1() { - return Invite.builder() - .code("this-is-code") - .club(club1()) - .expiredAt(LocalDateTime.of(2001, 10, 1, 10, 0)) - .build(); - } - - public static Invite invite2() { - return Invite.builder() - .code("this-is-future-code") - .club(club2()) - .expiredAt(LocalDateTime.of(2999, 10, 1, 10, 0)) - .build(); - } - -} diff --git a/src/test/java/com/spaceclub/invite/controller/InviteControllerTest.java b/src/test/java/com/spaceclub/invite/controller/InviteControllerTest.java index d3ca9143..c4fcecd3 100644 --- a/src/test/java/com/spaceclub/invite/controller/InviteControllerTest.java +++ b/src/test/java/com/spaceclub/invite/controller/InviteControllerTest.java @@ -74,7 +74,7 @@ class InviteControllerTest { Club club = club1(); Long clubId = club.getId(); - given(inviteService.createInviteCode(any(Long.class), any())).willReturn("650d2d91-a8cf-45e7-8a43-a0c798173ecb"); + given(inviteService.createInviteCode(any(Long.class), any(), any())).willReturn("650d2d91-a8cf-45e7-8a43-a0c798173ecb"); // when ResultActions actions = mockMvc.perform(post("/api/v1/clubs/{clubId}/invite", clubId) @@ -107,7 +107,7 @@ class InviteControllerTest { boolean isExpired = false; InviteGetInfo vo = new InviteGetInfo(code, isExpired); - given(inviteService.getInviteLink(any(Long.class), any())).willReturn(vo); + given(inviteService.getInviteLink(any(Long.class), any(), any())).willReturn(vo); // when ResultActions actions = mockMvc.perform(get("/api/v1/clubs/{clubId}/invite", 1L) diff --git a/src/test/java/com/spaceclub/invite/controller/InviteJoinControllerTest.java b/src/test/java/com/spaceclub/invite/controller/InviteJoinControllerTest.java index 9eec7ce4..d626b5e2 100644 --- a/src/test/java/com/spaceclub/invite/controller/InviteJoinControllerTest.java +++ b/src/test/java/com/spaceclub/invite/controller/InviteJoinControllerTest.java @@ -108,7 +108,7 @@ class InviteJoinControllerTest { void 초대_링크를_통해_클럽_가입전_가입_의사를_묻는데_성공한다() throws Exception { // given String code = UUID.randomUUID().toString(); - given(inviteJoinService.requestToJoinClub(any(String.class))).willReturn(club1()); + given(inviteJoinService.validateAndRetrieveClub(any(String.class), any(), any())).willReturn(club1()); // when ResultActions actions = diff --git a/src/test/java/com/spaceclub/invite/domain/InviteTest.java b/src/test/java/com/spaceclub/invite/domain/InviteTest.java new file mode 100644 index 00000000..f8c5df95 --- /dev/null +++ b/src/test/java/com/spaceclub/invite/domain/InviteTest.java @@ -0,0 +1,52 @@ +package com.spaceclub.invite.domain; + +import com.spaceclub.SpaceClubCustomDisplayNameGenerator; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayNameGeneration(SpaceClubCustomDisplayNameGenerator.class) +class InviteTest { + + @Test + void 생성시점이_만료되었으면_초대코드를_null로_반환한다() { + // given + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0); + LocalDateTime invitationExpireDate = LocalDateTime.of(2024, 1, 1, 0, 0); + String invitationCode = "this-is-code"; + + Invite invite = Invite.builder() + .expiredAt(invitationExpireDate) + .code(invitationCode) + .build(); + + // when + String code = invite.validatedCode(now); + + // then + assertThat(code).isNull(); + } + + @Test + void 생성시점이_만료되었으면_기존_초대코드를_반환한다() { + // given + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0); + LocalDateTime invitationExpireDate = LocalDateTime.of(2024, 3, 1, 0, 0, 0, 1); + String invitationCode = "this-is-code"; + + Invite invite = Invite.builder() + .expiredAt(invitationExpireDate) + .code(invitationCode) + .build(); + + // when + String code = invite.validatedCode(now); + + // then + assertThat(code).isEqualTo(invitationCode); + } + +} diff --git a/src/test/java/com/spaceclub/invite/service/InviteJoinServiceTest.java b/src/test/java/com/spaceclub/invite/service/InviteJoinServiceTest.java index 1648e5c6..50e16bd7 100644 --- a/src/test/java/com/spaceclub/invite/service/InviteJoinServiceTest.java +++ b/src/test/java/com/spaceclub/invite/service/InviteJoinServiceTest.java @@ -2,61 +2,108 @@ import com.spaceclub.SpaceClubCustomDisplayNameGenerator; import com.spaceclub.club.domain.Club; -import com.spaceclub.club.domain.repository.ClubUserRepository; +import com.spaceclub.club.domain.ClubUser; +import com.spaceclub.club.service.ClubMemberManagerService; import com.spaceclub.invite.domain.Invite; import com.spaceclub.invite.domain.repository.InviteRepository; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import java.time.LocalDateTime; import java.util.Optional; -import static com.spaceclub.invite.InviteTestFixture.invite1; +import static com.spaceclub.club.ClubTestFixture.club1; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.doNothing; -@ExtendWith(MockitoExtension.class) +@SpringBootTest +@Transactional @DisplayNameGeneration(SpaceClubCustomDisplayNameGenerator.class) class InviteJoinServiceTest { - @InjectMocks - private InviteJoinService inviteJoinService; - - @Mock + @MockBean private InviteRepository inviteRepository; + @MockBean + private ClubMemberManagerService clubMemberManagerService; + @Autowired + private InviteJoinService inviteJoinService; - @Mock - private ClubUserRepository clubUserRepository; + private final LocalDateTime now = LocalDateTime.of(2024, 4, 24, 0, 0, 0); @Test - void 클럽_초대_링크가_만료된_경우_클럽_가입에_실패한다() { + void 클럽_초대링크를통해_클럽_가입에_성공한다() { // given - Invite expiredInvite = invite1(); + given(clubMemberManagerService.isAlreadyJoined(any(Club.class), any(Long.class))).willReturn(false); + doNothing().when(clubMemberManagerService).save(any(ClubUser.class)); + + LocalDateTime expiredAt = LocalDateTime.of(2024, 5, 1, 0, 0, 0); // 만료 안된 invite entity + Invite notExpiredInvitation = invitationEntity(expiredAt); + given(inviteRepository.findByCode(any(String.class))).willReturn(Optional.of(notExpiredInvitation)); + + var userId = 1L; - given(inviteRepository.findByCode(any(String.class))).willReturn(Optional.of(expiredInvite)); + // when, then + assertThatNoException() + .isThrownBy(() -> inviteJoinService.joinClub(notExpiredInvitation.getCode(), userId, now)); + } + + @Test + void 클럽_초대_링크가_존재하지_않는_경우_클럽_가입에_실패한다() { + // given + given(clubMemberManagerService.isAlreadyJoined(any(Club.class), any(Long.class))).willReturn(false); + doNothing().when(clubMemberManagerService).save(any(ClubUser.class)); + var code = "not-existing-code"; // when, then - assertThatThrownBy(() -> inviteJoinService.joinClub("123", 1L)) + assertThatThrownBy(() -> inviteJoinService.validateAndRetrieveClub(code, now, 1L)) .isInstanceOf(IllegalStateException.class); + } + + @Test + void 클럽_초대_링크가_만료된_경우_클럽_가입에_실패한다() { + // given + given(clubMemberManagerService.isAlreadyJoined(any(Club.class), any(Long.class))).willReturn(false); + LocalDateTime expiredAt = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Invite expiredInvitation = invitationEntity(expiredAt); + given(inviteRepository.findByCode(any(String.class))).willReturn(Optional.of(expiredInvitation)); + + var userId = 1L; + // when + assertThatThrownBy(() -> inviteJoinService.joinClub(expiredInvitation.getCode(), userId, now)) + .isInstanceOf(IllegalStateException.class); } @Test void 해당_클럽에_이미_가입한_경우_클럽_가입에_실패한다() { // given - given(inviteRepository.findByCode(any(String.class))).willReturn(Optional.of(invite1())); - lenient().doReturn(true).when(clubUserRepository) - .existsByClubAndUserId(any(Club.class), any(Long.class)); + given(clubMemberManagerService.isAlreadyJoined(any(Club.class), any(Long.class))).willReturn(true); + + LocalDateTime expiredAt = LocalDateTime.of(2024, 5, 1, 0, 0, 0); // 만료 안된 invite entity + Invite notExpiredInvitation = invitationEntity(expiredAt); + given(inviteRepository.findByCode(any(String.class))).willReturn(Optional.of(notExpiredInvitation)); + + var userId = 1L; // when, then - assertThatThrownBy(() -> inviteJoinService.joinClub("123", 1L)) + assertThatThrownBy(() -> inviteJoinService.joinClub(notExpiredInvitation.getCode(), userId, now)) .isInstanceOf(IllegalStateException.class); } + private Invite invitationEntity(LocalDateTime expiredAt) { + return Invite.builder() + .code("this-is-code") + .club(club1()) + .expiredAt(expiredAt) + .build(); + } + } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 3906fc8e..4536c310 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -89,3 +89,6 @@ web: profanity: file-path: src/main/resources/secrets/bad_word_list.txt + +invite: + link-prefix: "https://space-club.site/api/v1/clubs/invite/"