diff --git a/src/main/java/com/back/catchmate/domain/board/dto/BoardResponse.java b/src/main/java/com/back/catchmate/domain/board/dto/BoardResponse.java new file mode 100644 index 0000000..21e0b0f --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/board/dto/BoardResponse.java @@ -0,0 +1,16 @@ +package com.back.catchmate.domain.board.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public abstract class BoardResponse { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class BoardInfo { + String title; + } +} diff --git a/src/main/java/com/back/catchmate/domain/board/entity/Board.java b/src/main/java/com/back/catchmate/domain/board/entity/Board.java index c241a33..026ce44 100644 --- a/src/main/java/com/back/catchmate/domain/board/entity/Board.java +++ b/src/main/java/com/back/catchmate/domain/board/entity/Board.java @@ -40,4 +40,8 @@ public class Board extends BaseTimeEntity { @Builder.Default @OneToMany(mappedBy = "board") private List notificationList = new ArrayList<>(); + + public boolean isWriterSameAsLoginUser(User user) { + return this.user.equals(user); + } } diff --git a/src/main/java/com/back/catchmate/domain/board/repository/BoardRepository.java b/src/main/java/com/back/catchmate/domain/board/repository/BoardRepository.java new file mode 100644 index 0000000..4a6aafb --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/board/repository/BoardRepository.java @@ -0,0 +1,7 @@ +package com.back.catchmate.domain.board.repository; + +import com.back.catchmate.domain.board.entity.Board; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BoardRepository extends JpaRepository { +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/controller/EnrollController.java b/src/main/java/com/back/catchmate/domain/enroll/controller/EnrollController.java new file mode 100644 index 0000000..7ce1891 --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/controller/EnrollController.java @@ -0,0 +1,42 @@ +package com.back.catchmate.domain.enroll.controller; + +import com.back.catchmate.domain.enroll.dto.EnrollRequest.CreateEnrollRequest; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CancelEnrollInfo; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CreateEnrollInfo; +import com.back.catchmate.domain.enroll.service.EnrollService; +import com.back.catchmate.global.jwt.JwtValidation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +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; + +import java.io.IOException; + +@Tag(name = "직관 신청 관련 API") +@RestController +@RequestMapping("/enroll") +@RequiredArgsConstructor +public class EnrollController { + private final EnrollService enrollService; + + @PostMapping("/{boardId}") + @Operation(summary = "직관 신청 API", description = "직관 신청을 요청하는 API 입니다.") + public CreateEnrollInfo createEnroll(@Valid @RequestBody CreateEnrollRequest createEnrollRequest, + @PathVariable Long boardId, + @JwtValidation Long userId) throws IOException { + return enrollService.createEnroll(createEnrollRequest, boardId, userId); + } + + @DeleteMapping("/cancel/{enrollId}") + @Operation(summary = "직관 신청 취소 API", description = "직관 신청을 취소하는 API 입니다.") + public CancelEnrollInfo cancelEnroll(@PathVariable Long enrollId, + @JwtValidation Long userId) { + return enrollService.cancelEnroll(enrollId, userId); + } +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/converter/EnrollConverter.java b/src/main/java/com/back/catchmate/domain/enroll/converter/EnrollConverter.java new file mode 100644 index 0000000..7f89b05 --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/converter/EnrollConverter.java @@ -0,0 +1,40 @@ +package com.back.catchmate.domain.enroll.converter; + +import com.back.catchmate.domain.board.entity.Board; +import com.back.catchmate.domain.enroll.dto.EnrollRequest.CreateEnrollRequest; +import com.back.catchmate.domain.enroll.dto.EnrollResponse; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CancelEnrollInfo; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CreateEnrollInfo; +import com.back.catchmate.domain.enroll.entity.AcceptStatus; +import com.back.catchmate.domain.enroll.entity.Enroll; +import com.back.catchmate.domain.user.entity.User; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Component +public class EnrollConverter { + public Enroll toEntity(CreateEnrollRequest createEnrollRequest, User user, Board board) { + return Enroll.builder() + .user(user) + .board(board) + .acceptStatus(AcceptStatus.PENDING) + .description(createEnrollRequest.getDescription()) + .isNew(true) + .build(); + } + + public CreateEnrollInfo toCreateEnrollInfo(Enroll enroll) { + return CreateEnrollInfo.builder() + .enrollId(enroll.getId()) + .requestAt(enroll.getCreatedAt()) + .build(); + } + + public CancelEnrollInfo toCancelEnrollInfo(Enroll enroll) { + return CancelEnrollInfo.builder() + .enrollId(enroll.getId()) + .deletedAt(enroll.getUpdatedAt()) + .build(); + } +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollRequest.java b/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollRequest.java new file mode 100644 index 0000000..e4e6719 --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollRequest.java @@ -0,0 +1,19 @@ +package com.back.catchmate.domain.enroll.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public abstract class EnrollRequest { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateEnrollRequest { + @NotNull + private String description; + } +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollResponse.java b/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollResponse.java new file mode 100644 index 0000000..da1213a --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/dto/EnrollResponse.java @@ -0,0 +1,62 @@ +package com.back.catchmate.domain.enroll.dto; + +import com.back.catchmate.domain.board.dto.BoardResponse.BoardInfo; +import com.back.catchmate.domain.enroll.entity.AcceptStatus; +import com.back.catchmate.domain.user.dto.UserResponse.UserInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public abstract class EnrollResponse { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EnrollInfo { + private Long enrollId; + private AcceptStatus acceptStatus; + private String description; + private LocalDateTime requestDate; + private boolean isNew; + private UserInfo userInfo; + private BoardInfo boardInfo; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class NewEnrollCountInfo { + private int newEnrollCount; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UpdateEnrollInfo { + private Long enrollId; + private AcceptStatus acceptStatus; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateEnrollInfo { + private Long enrollId; + private LocalDateTime requestAt; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CancelEnrollInfo { + private Long enrollId; + private LocalDateTime deletedAt; + } +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/entity/Enroll.java b/src/main/java/com/back/catchmate/domain/enroll/entity/Enroll.java index 3d931d7..c6387f8 100644 --- a/src/main/java/com/back/catchmate/domain/enroll/entity/Enroll.java +++ b/src/main/java/com/back/catchmate/domain/enroll/entity/Enroll.java @@ -50,4 +50,8 @@ public class Enroll extends BaseTimeEntity { @Column(nullable = false) private boolean isNew; + + public boolean isDifferentFromLoginUser(User user) { + return !this.user.equals(user); + } } diff --git a/src/main/java/com/back/catchmate/domain/enroll/repository/EnrollRepository.java b/src/main/java/com/back/catchmate/domain/enroll/repository/EnrollRepository.java new file mode 100644 index 0000000..c8a025b --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/repository/EnrollRepository.java @@ -0,0 +1,10 @@ +package com.back.catchmate.domain.enroll.repository; + +import com.back.catchmate.domain.enroll.entity.Enroll; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface EnrollRepository extends JpaRepository { + Optional findByUserIdAndBoardId(Long userId, Long boardId); +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/service/EnrollService.java b/src/main/java/com/back/catchmate/domain/enroll/service/EnrollService.java new file mode 100644 index 0000000..8242890 --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/service/EnrollService.java @@ -0,0 +1,13 @@ +package com.back.catchmate.domain.enroll.service; + +import com.back.catchmate.domain.enroll.dto.EnrollRequest.CreateEnrollRequest; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CancelEnrollInfo; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CreateEnrollInfo; + +import java.io.IOException; + +public interface EnrollService { + CreateEnrollInfo createEnroll(CreateEnrollRequest request, Long boardId, Long userId) throws IOException; + + CancelEnrollInfo cancelEnroll(Long enrollId, Long userId); +} diff --git a/src/main/java/com/back/catchmate/domain/enroll/service/EnrollServiceImpl.java b/src/main/java/com/back/catchmate/domain/enroll/service/EnrollServiceImpl.java new file mode 100644 index 0000000..e51cdca --- /dev/null +++ b/src/main/java/com/back/catchmate/domain/enroll/service/EnrollServiceImpl.java @@ -0,0 +1,66 @@ +package com.back.catchmate.domain.enroll.service; + +import com.back.catchmate.domain.board.entity.Board; +import com.back.catchmate.domain.board.repository.BoardRepository; +import com.back.catchmate.domain.enroll.converter.EnrollConverter; +import com.back.catchmate.domain.enroll.dto.EnrollRequest.CreateEnrollRequest; +import com.back.catchmate.domain.enroll.dto.EnrollResponse; +import com.back.catchmate.domain.enroll.dto.EnrollResponse.CreateEnrollInfo; +import com.back.catchmate.domain.enroll.entity.Enroll; +import com.back.catchmate.domain.enroll.repository.EnrollRepository; +import com.back.catchmate.domain.user.entity.User; +import com.back.catchmate.domain.user.repository.UserRepository; +import com.back.catchmate.global.error.ErrorCode; +import com.back.catchmate.global.error.exception.BaseException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class EnrollServiceImpl implements EnrollService { + private final EnrollRepository enrollRepository; + private final UserRepository userRepository; + private final BoardRepository boardRepository; + private final EnrollConverter enrollConverter; + + @Override + public CreateEnrollInfo createEnroll(CreateEnrollRequest request, Long boardId, Long userId) throws IOException { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND)); + + // 존재하는 게시글인지, 자신의 게시글인지 확인 + Board board = boardRepository.findById(boardId) + .orElseThrow(() -> new BaseException(ErrorCode.BOARD_NOT_FOUND)); + if (board.isWriterSameAsLoginUser(user)) { + throw new BaseException(ErrorCode.ENROLL_BAD_REQUEST); + } + + enrollRepository.findByUserIdAndBoardId(user.getId(), board.getId()) + .ifPresent(enroll -> { + throw new BaseException(ErrorCode.ENROLL_ALREADY_EXIST); + }); + + Enroll enroll = enrollConverter.toEntity(request, user, board); + enrollRepository.save(enroll); + return enrollConverter.toCreateEnrollInfo(enroll); + } + + @Override + public EnrollResponse.CancelEnrollInfo cancelEnroll(Long enrollId, Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND)); + + Enroll enroll = enrollRepository.findById(enrollId) + .orElseThrow(() -> new BaseException(ErrorCode.ENROLL_NOT_FOUND)); + + // 직관 신청한 사용자와 로그인한 사용자가 일치하는지 확인 + if (enroll.isDifferentFromLoginUser(user)) { + throw new BaseException(ErrorCode.ENROLL_CANCEL_INVALID); + } + + enrollRepository.delete(enroll); + return enrollConverter.toCancelEnrollInfo(enroll); + } +} diff --git a/src/main/java/com/back/catchmate/domain/user/service/UserServiceImpl.java b/src/main/java/com/back/catchmate/domain/user/service/UserServiceImpl.java index aa3f0f2..9f84676 100644 --- a/src/main/java/com/back/catchmate/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/back/catchmate/domain/user/service/UserServiceImpl.java @@ -8,13 +8,14 @@ import com.back.catchmate.domain.user.dto.UserRequest.UserJoinRequest; import com.back.catchmate.domain.user.dto.UserRequest.UserProfileUpdateRequest; import com.back.catchmate.domain.user.dto.UserResponse; +import com.back.catchmate.domain.user.dto.UserResponse.LoginInfo; import com.back.catchmate.domain.user.dto.UserResponse.UpdateAlarmInfo; import com.back.catchmate.domain.user.dto.UserResponse.UserInfo; import com.back.catchmate.domain.user.entity.AlarmType; import com.back.catchmate.domain.user.entity.User; import com.back.catchmate.domain.user.repository.UserRepository; import com.back.catchmate.global.auth.entity.RefreshToken; -import com.back.catchmate.global.auth.repository.RefreshTokenRedisRepository; +import com.back.catchmate.global.auth.repository.RefreshTokenRepository; import com.back.catchmate.global.dto.StateResponse; import com.back.catchmate.global.error.ErrorCode; import com.back.catchmate.global.error.exception.BaseException; @@ -27,21 +28,22 @@ import java.io.IOException; +import static com.back.catchmate.global.auth.service.AuthServiceImpl.PROVIDER_ID_SEPARATOR; + @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService{ - public final String PROVIDER_ID_SEPARATOR = "@"; private final JwtService jwtService; private final S3Service s3Service; private final UserRepository userRepository; private final ClubRepository clubRepository; - private final RefreshTokenRedisRepository refreshTokenRedisRepository; + private final RefreshTokenRepository refreshTokenRepository; private final UserConverter userConverter; private final ClubConverter clubConverter; @Override @Transactional - public UserResponse.LoginInfo joinUser(UserJoinRequest request) { + public LoginInfo joinUser(UserJoinRequest request) { String providerIdWithProvider = request.getProviderId() + PROVIDER_ID_SEPARATOR + request.getProvider(); Club favoreiteClub = clubRepository.findById(request.getFavoriteClubId()) .orElseThrow(() -> new BaseException(ErrorCode.CLUB_NOT_FOUND)); @@ -59,7 +61,7 @@ public UserResponse.LoginInfo joinUser(UserJoinRequest request) { // accessToken, refreshToken 발급 String accessToken = jwtService.createAccessToken(user.getId()); String refreshToken = jwtService.createRefreshToken(user.getId()); - refreshTokenRedisRepository.save(RefreshToken.of(refreshToken, user.getId())); + refreshTokenRepository.save(RefreshToken.of(refreshToken, user.getId())); return userConverter.toLoginInfo(user, accessToken, refreshToken); } diff --git a/src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRedisRepository.java b/src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRepository.java similarity index 72% rename from src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRedisRepository.java rename to src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRepository.java index 501d81d..305b697 100644 --- a/src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRedisRepository.java +++ b/src/main/java/com/back/catchmate/global/auth/repository/RefreshTokenRepository.java @@ -5,6 +5,6 @@ import org.springframework.stereotype.Repository; @Repository -public interface RefreshTokenRedisRepository extends CrudRepository { +public interface RefreshTokenRepository extends CrudRepository { } diff --git a/src/main/java/com/back/catchmate/global/auth/service/AuthServiceImpl.java b/src/main/java/com/back/catchmate/global/auth/service/AuthServiceImpl.java index 9f4c8c0..84acf89 100644 --- a/src/main/java/com/back/catchmate/global/auth/service/AuthServiceImpl.java +++ b/src/main/java/com/back/catchmate/global/auth/service/AuthServiceImpl.java @@ -8,7 +8,7 @@ import com.back.catchmate.global.auth.dto.response.AuthResponse.NicknameCheckInfo; import com.back.catchmate.global.auth.dto.response.AuthResponse.ReissueInfo; import com.back.catchmate.global.auth.entity.RefreshToken; -import com.back.catchmate.global.auth.repository.RefreshTokenRedisRepository; +import com.back.catchmate.global.auth.repository.RefreshTokenRepository; import com.back.catchmate.global.dto.StateResponse; import com.back.catchmate.global.error.ErrorCode; import com.back.catchmate.global.error.exception.BaseException; @@ -25,7 +25,7 @@ public class AuthServiceImpl implements AuthService { private final JwtService jwtService; private final UserRepository userRepository; - private final RefreshTokenRedisRepository refreshTokenRedisRepository; + private final RefreshTokenRepository refreshTokenRepository; private final AuthConverter authConverter; // Provider ID와 구분자를 결합하기 위한 상수 @@ -60,7 +60,7 @@ public LoginInfo login(AuthRequest.LoginRequest loginRequest) { String refreshToken = jwtService.createRefreshToken(userId); // RefreshToken을 Redis에 저장 - refreshTokenRedisRepository.save(RefreshToken.of(refreshToken, userId)); + refreshTokenRepository.save(RefreshToken.of(refreshToken, userId)); loginInfo = authConverter.toLoginInfo(accessToken, refreshToken, isFirstLogin); } @@ -86,7 +86,7 @@ public ReissueInfo reissue(String refreshToken) { // RefreshToken을 파싱하여 사용자 ID를 가져옴 Long userId = jwtService.parseJwtToken(refreshToken); // RefreshToken이 유효한지 확인 - refreshTokenRedisRepository.findById(refreshToken) + refreshTokenRepository.findById(refreshToken) .orElseThrow(() -> new BaseException(ErrorCode.INVALID_REFRESH_TOKEN)); // 새로운 AccessToken을 생성 @@ -106,7 +106,7 @@ public StateResponse logout(String refreshToken) { // FCM 토큰 삭제 user.deleteFcmToken(); // RefreshToken을 Redis에서 삭제 - refreshTokenRedisRepository.deleteById(refreshToken); + refreshTokenRepository.deleteById(refreshToken); return new StateResponse(true); } }