diff --git a/backend/src/main/java/mouda/backend/auth/controller/AuthController.java b/backend/src/main/java/mouda/backend/auth/controller/AuthController.java index 5aa401b14..10c72744b 100644 --- a/backend/src/main/java/mouda/backend/auth/controller/AuthController.java +++ b/backend/src/main/java/mouda/backend/auth/controller/AuthController.java @@ -1,8 +1,8 @@ package mouda.backend.auth.controller; import lombok.RequiredArgsConstructor; -import mouda.backend.auth.dto.LoginRequest; -import mouda.backend.auth.dto.LoginResponse; +import mouda.backend.auth.dto.request.LoginRequest; +import mouda.backend.auth.dto.response.LoginResponse; import mouda.backend.auth.service.AuthService; import mouda.backend.common.RestResponse; import org.springframework.http.ResponseEntity; diff --git a/backend/src/main/java/mouda/backend/auth/controller/AuthSwagger.java b/backend/src/main/java/mouda/backend/auth/controller/AuthSwagger.java index fd50e8c31..e30a35b51 100644 --- a/backend/src/main/java/mouda/backend/auth/controller/AuthSwagger.java +++ b/backend/src/main/java/mouda/backend/auth/controller/AuthSwagger.java @@ -3,8 +3,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import mouda.backend.auth.dto.LoginRequest; -import mouda.backend.auth.dto.LoginResponse; +import mouda.backend.auth.dto.request.LoginRequest; +import mouda.backend.auth.dto.response.LoginResponse; import mouda.backend.common.RestResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; diff --git a/backend/src/main/java/mouda/backend/auth/dto/LoginRequest.java b/backend/src/main/java/mouda/backend/auth/dto/request/LoginRequest.java similarity index 55% rename from backend/src/main/java/mouda/backend/auth/dto/LoginRequest.java rename to backend/src/main/java/mouda/backend/auth/dto/request/LoginRequest.java index f70e7477e..da4580f7c 100644 --- a/backend/src/main/java/mouda/backend/auth/dto/LoginRequest.java +++ b/backend/src/main/java/mouda/backend/auth/dto/request/LoginRequest.java @@ -1,4 +1,4 @@ -package mouda.backend.auth.dto; +package mouda.backend.auth.dto.request; public record LoginRequest(String nickname) { diff --git a/backend/src/main/java/mouda/backend/auth/dto/LoginResponse.java b/backend/src/main/java/mouda/backend/auth/dto/response/LoginResponse.java similarity index 56% rename from backend/src/main/java/mouda/backend/auth/dto/LoginResponse.java rename to backend/src/main/java/mouda/backend/auth/dto/response/LoginResponse.java index 375a99bd2..d79bf2c26 100644 --- a/backend/src/main/java/mouda/backend/auth/dto/LoginResponse.java +++ b/backend/src/main/java/mouda/backend/auth/dto/response/LoginResponse.java @@ -1,4 +1,4 @@ -package mouda.backend.auth.dto; +package mouda.backend.auth.dto.response; public record LoginResponse(String accessToken) { diff --git a/backend/src/main/java/mouda/backend/auth/service/AuthService.java b/backend/src/main/java/mouda/backend/auth/service/AuthService.java index ec5127932..a1d610a5d 100644 --- a/backend/src/main/java/mouda/backend/auth/service/AuthService.java +++ b/backend/src/main/java/mouda/backend/auth/service/AuthService.java @@ -1,7 +1,7 @@ package mouda.backend.auth.service; -import mouda.backend.auth.dto.LoginRequest; -import mouda.backend.auth.dto.LoginResponse; +import mouda.backend.auth.dto.request.LoginRequest; +import mouda.backend.auth.dto.response.LoginResponse; import mouda.backend.auth.exception.AuthErrorMessage; import mouda.backend.auth.exception.AuthException; import mouda.backend.member.domain.Member; diff --git a/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoController.java b/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoController.java new file mode 100644 index 000000000..04829d22a --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoController.java @@ -0,0 +1,53 @@ +package mouda.backend.chamyo.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import mouda.backend.chamyo.dto.response.ChamyoFindAllResponses; +import mouda.backend.chamyo.dto.request.MoimChamyoRequest; +import mouda.backend.chamyo.dto.response.MoimRoleFindResponse; +import mouda.backend.chamyo.service.ChamyoService; +import mouda.backend.common.RestResponse; +import mouda.backend.config.argumentresolver.LoginMember; +import mouda.backend.member.domain.Member; + +@RestController +@RequestMapping("/v1/chamyo") +@RequiredArgsConstructor +public class ChamyoController implements ChamyoSwagger { + + private final ChamyoService chamyoService; + + @Override + @GetMapping("/me") + public ResponseEntity> findMoimRoleByMember( + @RequestParam Long moimId, @LoginMember Member member + ) { + MoimRoleFindResponse moimRoleFindResponse = chamyoService.findMoimRole(moimId, member); + + return ResponseEntity.ok().body(new RestResponse<>(moimRoleFindResponse)); + } + + @Override + @GetMapping("/all") + public ResponseEntity> findAllChamyoByMoim(@RequestParam Long moimId) { + ChamyoFindAllResponses chamyoFindAllResponses = chamyoService.findAllChamyo(moimId); + + return ResponseEntity.ok().body(new RestResponse<>(chamyoFindAllResponses)); + } + + @Override + @PostMapping + public ResponseEntity chamyoMoim(@Valid @RequestBody MoimChamyoRequest request, @LoginMember Member member) { + chamyoService.chamyoMoim(request, member); + + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoSwagger.java b/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoSwagger.java new file mode 100644 index 000000000..dd74b92b3 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/controller/ChamyoSwagger.java @@ -0,0 +1,38 @@ +package mouda.backend.chamyo.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import mouda.backend.chamyo.dto.response.ChamyoFindAllResponses; +import mouda.backend.chamyo.dto.request.MoimChamyoRequest; +import mouda.backend.chamyo.dto.response.MoimRoleFindResponse; +import mouda.backend.common.RestResponse; +import mouda.backend.config.argumentresolver.LoginMember; +import mouda.backend.member.domain.Member; + +public interface ChamyoSwagger { + + @Operation(summary = "모임 참여 여부 조회", description = "현재 로그인된 회원의 모임 참여 여부를 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 참여 여부 조회 성공") + }) + ResponseEntity> findMoimRoleByMember(@RequestParam Long moimId, + @LoginMember Member member); + + @Operation(summary = "모든 모임 참여자 조회", description = "모임에 참여한 모든 회원을 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모든 모임 참여자 조회 성공") + }) + ResponseEntity> findAllChamyoByMoim(@RequestParam Long moimId); + + @Operation(summary = "모임 참여", description = "모임에 참여합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 참여 성공") + }) + ResponseEntity chamyoMoim(@Valid @RequestBody MoimChamyoRequest request, @LoginMember Member member); +} diff --git a/backend/src/main/java/mouda/backend/chamyo/domain/Chamyo.java b/backend/src/main/java/mouda/backend/chamyo/domain/Chamyo.java index 919d8d767..fab67de7b 100644 --- a/backend/src/main/java/mouda/backend/chamyo/domain/Chamyo.java +++ b/backend/src/main/java/mouda/backend/chamyo/domain/Chamyo.java @@ -1,5 +1,6 @@ package mouda.backend.chamyo.domain; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; @@ -8,6 +9,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import mouda.backend.member.domain.Member; @@ -23,13 +25,21 @@ public class Chamyo { private Long id; @ManyToOne - @JoinColumn(name = "moim_id") + @JoinColumn(nullable = false) private Moim moim; @ManyToOne - @JoinColumn(name = "member_id") + @JoinColumn(nullable = false) private Member member; @Enumerated(EnumType.STRING) + @Column(nullable = false) private MoimRole moimRole; + + @Builder + public Chamyo(Moim moim, Member member, MoimRole moimRole) { + this.moim = moim; + this.member = member; + this.moimRole = moimRole; + } } diff --git a/backend/src/main/java/mouda/backend/chamyo/domain/MoimRole.java b/backend/src/main/java/mouda/backend/chamyo/domain/MoimRole.java index 00fe2ed0c..5e005c2a6 100644 --- a/backend/src/main/java/mouda/backend/chamyo/domain/MoimRole.java +++ b/backend/src/main/java/mouda/backend/chamyo/domain/MoimRole.java @@ -4,5 +4,5 @@ @Getter public enum MoimRole { - DEFAULT + MOIMER, MOIMEE, NON_MOIMEE } diff --git a/backend/src/main/java/mouda/backend/chamyo/dto/request/MoimChamyoRequest.java b/backend/src/main/java/mouda/backend/chamyo/dto/request/MoimChamyoRequest.java new file mode 100644 index 000000000..ccb41444c --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/dto/request/MoimChamyoRequest.java @@ -0,0 +1,10 @@ +package mouda.backend.chamyo.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record MoimChamyoRequest( + @NotNull @Positive + Long moimId +) { +} diff --git a/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponse.java b/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponse.java new file mode 100644 index 000000000..afbd548e2 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponse.java @@ -0,0 +1,20 @@ +package mouda.backend.chamyo.dto.response; + +import lombok.Builder; +import mouda.backend.chamyo.domain.Chamyo; + +@Builder +public record ChamyoFindAllResponse( + String nickname, + String profile, + String role +) { + + public static ChamyoFindAllResponse toResponse(Chamyo chamyo) { + return ChamyoFindAllResponse.builder() + .nickname(chamyo.getMember().getNickname()) + .profile("") + .role(chamyo.getMoimRole().name()) + .build(); + } +} diff --git a/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponses.java b/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponses.java new file mode 100644 index 000000000..afde75077 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/dto/response/ChamyoFindAllResponses.java @@ -0,0 +1,8 @@ +package mouda.backend.chamyo.dto.response; + +import java.util.List; + +public record ChamyoFindAllResponses( + List chamyos +) { +} diff --git a/backend/src/main/java/mouda/backend/chamyo/dto/response/MoimRoleFindResponse.java b/backend/src/main/java/mouda/backend/chamyo/dto/response/MoimRoleFindResponse.java new file mode 100644 index 000000000..fb8d0bc2f --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/dto/response/MoimRoleFindResponse.java @@ -0,0 +1,6 @@ +package mouda.backend.chamyo.dto.response; + +public record MoimRoleFindResponse( + String role +) { +} diff --git a/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoErrorMessage.java b/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoErrorMessage.java new file mode 100644 index 000000000..8f2dfb1ad --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoErrorMessage.java @@ -0,0 +1,17 @@ +package mouda.backend.chamyo.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ChamyoErrorMessage { + + MOIM_NOT_FOUND("참여하려는 모임이 존재하지 않습니다."), + MOIM_ALREADY_JOINED("이미 참여한 모임입니다."), + MOIM_FULL("최대 인원 수를 초과했습니다."), + MOIMING_CANCLED("취소된 모임입니다."), + MOIMING_COMPLETE("모집이 완료된 모임입니다."); + + private final String message; +} diff --git a/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoException.java b/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoException.java new file mode 100644 index 000000000..c3bd1de37 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/exception/ChamyoException.java @@ -0,0 +1,12 @@ +package mouda.backend.chamyo.exception; + +import org.springframework.http.HttpStatus; + +import mouda.backend.exception.MoudaException; + +public class ChamyoException extends MoudaException { + + public ChamyoException(HttpStatus httpStatus, ChamyoErrorMessage chamyoErrorMessage) { + super(httpStatus, chamyoErrorMessage.getMessage()); + } +} diff --git a/backend/src/main/java/mouda/backend/chamyo/repository/ChamyoRepository.java b/backend/src/main/java/mouda/backend/chamyo/repository/ChamyoRepository.java new file mode 100644 index 000000000..cd7ad0705 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/repository/ChamyoRepository.java @@ -0,0 +1,23 @@ +package mouda.backend.chamyo.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import mouda.backend.chamyo.domain.Chamyo; +import mouda.backend.member.domain.Member; +import mouda.backend.moim.domain.Moim; + +public interface ChamyoRepository extends JpaRepository { + + Optional findByMoimIdAndMemberId(Long moimId, Long id); + + List findAllByMoimId(Long moimId); + + int countByMoim(Moim moim); + + boolean existsByMoimAndMember(Moim moim, Member member); + + void deleteAllByMoimId(Long moimId); +} diff --git a/backend/src/main/java/mouda/backend/chamyo/service/ChamyoService.java b/backend/src/main/java/mouda/backend/chamyo/service/ChamyoService.java new file mode 100644 index 000000000..78a6067a9 --- /dev/null +++ b/backend/src/main/java/mouda/backend/chamyo/service/ChamyoService.java @@ -0,0 +1,84 @@ +package mouda.backend.chamyo.service; + +import java.util.List; +import java.util.Optional; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import mouda.backend.chamyo.domain.Chamyo; +import mouda.backend.chamyo.domain.MoimRole; +import mouda.backend.chamyo.dto.response.ChamyoFindAllResponse; +import mouda.backend.chamyo.dto.response.ChamyoFindAllResponses; +import mouda.backend.chamyo.dto.request.MoimChamyoRequest; +import mouda.backend.chamyo.dto.response.MoimRoleFindResponse; +import mouda.backend.chamyo.exception.ChamyoErrorMessage; +import mouda.backend.chamyo.exception.ChamyoException; +import mouda.backend.chamyo.repository.ChamyoRepository; +import mouda.backend.member.domain.Member; +import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.domain.MoimStatus; +import mouda.backend.moim.repository.MoimRepository; + +@Service +@RequiredArgsConstructor +@Transactional +public class ChamyoService { + + private final ChamyoRepository chamyoRepository; + private final MoimRepository moimRepository; + + @Transactional(readOnly = true) + public MoimRoleFindResponse findMoimRole(Long moimId, Member member) { + Optional chamyoOptional = chamyoRepository.findByMoimIdAndMemberId(moimId, member.getId()); + + MoimRole moimRole = chamyoOptional.map(Chamyo::getMoimRole).orElse(MoimRole.NON_MOIMEE); + + return new MoimRoleFindResponse(moimRole.name()); + } + + @Transactional(readOnly = true) + public ChamyoFindAllResponses findAllChamyo(Long moimId) { + List responses = chamyoRepository.findAllByMoimId(moimId).stream() + .map(ChamyoFindAllResponse::toResponse) + .toList(); + + return new ChamyoFindAllResponses(responses); + } + + public void chamyoMoim(MoimChamyoRequest request, Member member) { + Moim moim = moimRepository.findById(request.moimId()) + .orElseThrow(() -> new ChamyoException(HttpStatus.NOT_FOUND, ChamyoErrorMessage.MOIM_NOT_FOUND)); + validateCanChamyoMoim(moim, member); + + Chamyo chamyo = Chamyo.builder() + .moim(moim) + .member(member) + .moimRole(MoimRole.MOIMEE) + .build(); + chamyoRepository.save(chamyo); + + int currentPeople = chamyoRepository.countByMoim(moim); + if (currentPeople >= moim.getMaxPeople()) { + moimRepository.updateMoimStatusById(moim.getId(), MoimStatus.COMPLETED); + } + } + + private void validateCanChamyoMoim(Moim moim, Member member) { + int currentPeople = chamyoRepository.countByMoim(moim); + if (currentPeople >= moim.getMaxPeople()) { + throw new ChamyoException(HttpStatus.BAD_REQUEST, ChamyoErrorMessage.MOIM_FULL); + } + if (moim.getMoimStatus() == MoimStatus.CANCELED) { + throw new ChamyoException(HttpStatus.BAD_REQUEST, ChamyoErrorMessage.MOIMING_CANCLED); + } + if (moim.getMoimStatus() == MoimStatus.COMPLETED) { + throw new ChamyoException(HttpStatus.BAD_REQUEST, ChamyoErrorMessage.MOIMING_COMPLETE); + } + if (chamyoRepository.existsByMoimAndMember(moim, member)) { + throw new ChamyoException(HttpStatus.BAD_REQUEST, ChamyoErrorMessage.MOIM_ALREADY_JOINED); + } + } +} diff --git a/backend/src/main/java/mouda/backend/moim/controller/MoimController.java b/backend/src/main/java/mouda/backend/moim/controller/MoimController.java index c3bd5225b..7454bb58e 100644 --- a/backend/src/main/java/mouda/backend/moim/controller/MoimController.java +++ b/backend/src/main/java/mouda/backend/moim/controller/MoimController.java @@ -31,8 +31,11 @@ public class MoimController implements MoimSwagger { @Override @PostMapping - public ResponseEntity> createMoim(@Valid @RequestBody MoimCreateRequest moimCreateRequest) { - Moim moim = moimService.createMoim(moimCreateRequest); + public ResponseEntity> createMoim( + @Valid @RequestBody MoimCreateRequest moimCreateRequest, + @LoginMember Member member + ) { + Moim moim = moimService.createMoim(moimCreateRequest, member); return ResponseEntity.ok().body(new RestResponse<>(moim.getId())); } @@ -53,6 +56,7 @@ public ResponseEntity> findMoimDetails(@Pa return ResponseEntity.ok().body(new RestResponse<>(moimDetailsFindResponse)); } + @Deprecated @Override @PostMapping("/join") public ResponseEntity joinMoim(@RequestBody MoimJoinRequest moimJoinRequest) { @@ -63,8 +67,8 @@ public ResponseEntity joinMoim(@RequestBody MoimJoinRequest moimJoinReques @Override @DeleteMapping("/{moimId}") - public ResponseEntity deleteMoim(@PathVariable Long moimId) { - moimService.deleteMoim(moimId); + public ResponseEntity deleteMoim(@PathVariable Long moimId, @LoginMember Member member) { + moimService.deleteMoim(moimId, member); return ResponseEntity.ok().build(); } diff --git a/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java b/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java index d58e1beff..3ff7e49d5 100644 --- a/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java +++ b/backend/src/main/java/mouda/backend/moim/controller/MoimSwagger.java @@ -22,7 +22,8 @@ public interface MoimSwagger { @ApiResponses({ @ApiResponse(responseCode = "200", description = "모임 생성 성공!"), }) - ResponseEntity> createMoim(@RequestBody MoimCreateRequest moimCreateRequest); + ResponseEntity> createMoim(@RequestBody MoimCreateRequest moimCreateRequest, + @LoginMember Member member); @Operation(summary = "모임 전체 조회", description = "모든 모임을 조회한다.") @ApiResponses({ @@ -46,7 +47,7 @@ public interface MoimSwagger { @ApiResponses({ @ApiResponse(responseCode = "200", description = "모임 삭제 성공!"), }) - ResponseEntity deleteMoim(@PathVariable Long moimId); + ResponseEntity deleteMoim(@PathVariable Long moimId, @LoginMember Member member); @Operation(summary = "댓글 작성", description = "해당하는 id의 모임에 댓글을 생성한다.") @ApiResponses({ diff --git a/backend/src/main/java/mouda/backend/moim/domain/Moim.java b/backend/src/main/java/mouda/backend/moim/domain/Moim.java index 18e589638..c56d76f9a 100644 --- a/backend/src/main/java/mouda/backend/moim/domain/Moim.java +++ b/backend/src/main/java/mouda/backend/moim/domain/Moim.java @@ -80,6 +80,7 @@ public Moim( this.place = place; this.maxPeople = maxPeople; this.description = description; + this.moimStatus = MoimStatus.MOIMING; } private void validateTitle(String title) { diff --git a/backend/src/main/java/mouda/backend/moim/domain/MoimStatus.java b/backend/src/main/java/mouda/backend/moim/domain/MoimStatus.java index af42907cc..421456331 100644 --- a/backend/src/main/java/mouda/backend/moim/domain/MoimStatus.java +++ b/backend/src/main/java/mouda/backend/moim/domain/MoimStatus.java @@ -4,5 +4,5 @@ @Getter public enum MoimStatus { - DEFAULT + MOIMING, COMPLETED, CANCELED } diff --git a/backend/src/main/java/mouda/backend/moim/dto/request/MoimCreateRequest.java b/backend/src/main/java/mouda/backend/moim/dto/request/MoimCreateRequest.java index 6e4424835..04d726198 100644 --- a/backend/src/main/java/mouda/backend/moim/dto/request/MoimCreateRequest.java +++ b/backend/src/main/java/mouda/backend/moim/dto/request/MoimCreateRequest.java @@ -23,9 +23,6 @@ public record MoimCreateRequest( @NotNull Integer maxPeople, - @NotBlank - String authorNickname, - String description ) { diff --git a/backend/src/main/java/mouda/backend/moim/exception/MoimErrorMessage.java b/backend/src/main/java/mouda/backend/moim/exception/MoimErrorMessage.java index f2d9c97ef..70b0af14b 100644 --- a/backend/src/main/java/mouda/backend/moim/exception/MoimErrorMessage.java +++ b/backend/src/main/java/mouda/backend/moim/exception/MoimErrorMessage.java @@ -22,7 +22,8 @@ public enum MoimErrorMessage { AUTHOR_NICKNAME_TOO_LONG("모임 생성자 이름을 조금 더 짧게 입력해주세요."), DESCRIPTION_TOO_LONG("모임 설명을 조금 더 짧게 입력해주세요."), MEMBER_NICKNAME_NOT_EXISTS("모임 참여자 닉네임을 입력해주세요."), - MEMBER_NICKNAME_TOO_LONG("모임 참여자 닉네임을 조금 더 짧게 입력해주세요."); + MEMBER_NICKNAME_TOO_LONG("모임 참여자 닉네임을 조금 더 짧게 입력해주세요."), + NOT_ALLOWED_TO_DELETE("방장만 삭제할 수 있어요."); private final String message; } diff --git a/backend/src/main/java/mouda/backend/moim/repository/MoimRepository.java b/backend/src/main/java/mouda/backend/moim/repository/MoimRepository.java index 7dcc394cc..ace337a8d 100644 --- a/backend/src/main/java/mouda/backend/moim/repository/MoimRepository.java +++ b/backend/src/main/java/mouda/backend/moim/repository/MoimRepository.java @@ -1,8 +1,20 @@ package mouda.backend.moim.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.domain.MoimStatus; public interface MoimRepository extends JpaRepository { + + @Query(""" + UPDATE Moim m + SET m.moimStatus = :status + WHERE m.id = :id + """) + @Modifying + int updateMoimStatusById(@Param("id") Long moimId, @Param("status") MoimStatus status); } diff --git a/backend/src/main/java/mouda/backend/moim/service/MoimService.java b/backend/src/main/java/mouda/backend/moim/service/MoimService.java index 6f6a68114..f40091fe2 100644 --- a/backend/src/main/java/mouda/backend/moim/service/MoimService.java +++ b/backend/src/main/java/mouda/backend/moim/service/MoimService.java @@ -10,6 +10,9 @@ import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; +import mouda.backend.chamyo.domain.Chamyo; +import mouda.backend.chamyo.domain.MoimRole; +import mouda.backend.chamyo.repository.ChamyoRepository; import mouda.backend.comment.domain.Comment; import mouda.backend.comment.dto.request.CommentCreateRequest; import mouda.backend.comment.dto.response.CommentResponse; @@ -19,6 +22,7 @@ import mouda.backend.member.domain.Member; import mouda.backend.member.repository.MemberRepository; import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.domain.MoimStatus; import mouda.backend.moim.dto.request.MoimCreateRequest; import mouda.backend.moim.dto.request.MoimJoinRequest; import mouda.backend.moim.dto.response.MoimDetailsFindResponse; @@ -27,6 +31,7 @@ import mouda.backend.moim.exception.MoimErrorMessage; import mouda.backend.moim.exception.MoimException; import mouda.backend.moim.repository.MoimRepository; +import mouda.backend.zzim.repository.ZzimRepository; @Transactional @Service @@ -34,18 +39,19 @@ public class MoimService { private final MoimRepository moimRepository; - private final MemberRepository memberRepository; - + private final ChamyoRepository chamyoRepository; + private final ZzimRepository zzimRepository; private final CommentRepository commentRepository; - public Moim createMoim(MoimCreateRequest moimCreateRequest) { - Member author = Member.builder() - .nickname(moimCreateRequest.authorNickname()) - .build(); + public Moim createMoim(MoimCreateRequest moimCreateRequest, Member member) { Moim moim = moimRepository.save(moimCreateRequest.toEntity()); - author.joinMoim(moim); - memberRepository.save(author); + Chamyo chamyo = Chamyo.builder() + .member(member) + .moim(moim) + .moimRole(MoimRole.MOIMER) + .build(); + chamyoRepository.save(chamyo); return moim; } @@ -89,6 +95,7 @@ private List toCommentResponse(List comments) { .toList(); } + @Deprecated public void joinMoim(MoimJoinRequest moimJoinRequest) { Member member = new Member(moimJoinRequest.nickname()); Moim moim = moimRepository.findById(moimJoinRequest.moimId()) @@ -101,17 +108,30 @@ public void joinMoim(MoimJoinRequest moimJoinRequest) { moim.validateAlreadyFullMoim(participants.size()); } - public void deleteMoim(long id) { + public void deleteMoim(long id, Member member) { Moim moim = moimRepository.findById(id) .orElseThrow(() -> new MoimException(HttpStatus.NOT_FOUND, MoimErrorMessage.NOT_FOUND)); - List participants = memberRepository.findAllByMoimId(moim.getId()); - for (Member participant : participants) { - memberRepository.deleteById(participant.getId()); - } + validateCanDeleteMoim(moim, member); + chamyoRepository.deleteAllByMoimId(id); + zzimRepository.deleteAllByMoimId(id); moimRepository.delete(moim); } + private void validateCanDeleteMoim(Moim moim, Member member) { + MoimRole moimRole = chamyoRepository.findByMoimIdAndMemberId(moim.getId(), member.getId()) + .orElseThrow(() -> new MoimException(HttpStatus.NOT_FOUND, MoimErrorMessage.NOT_FOUND)) + .getMoimRole(); + + if (moimRole != MoimRole.MOIMER) { + throw new MoimException(HttpStatus.FORBIDDEN, MoimErrorMessage.NOT_ALLOWED_TO_DELETE); + } + } + + public void updateMoimStatusById(long id, MoimStatus status) { + moimRepository.updateMoimStatusById(id, status); + } + public void createComment(Member member, Long moimId, CommentCreateRequest commentCreateRequest) { Moim moim = moimRepository.findById(moimId).orElseThrow( () -> new MoimException(HttpStatus.NOT_FOUND, MoimErrorMessage.NOT_FOUND) diff --git a/backend/src/main/java/mouda/backend/zzim/controller/ZzimController.java b/backend/src/main/java/mouda/backend/zzim/controller/ZzimController.java new file mode 100644 index 000000000..ea2a03a64 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/controller/ZzimController.java @@ -0,0 +1,44 @@ +package mouda.backend.zzim.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import mouda.backend.common.RestResponse; +import mouda.backend.config.argumentresolver.LoginMember; +import mouda.backend.member.domain.Member; +import mouda.backend.zzim.dto.response.ZzimCheckResponse; +import mouda.backend.zzim.dto.request.ZzimUpdateRequest; +import mouda.backend.zzim.service.ZzimService; + +@RestController +@RequestMapping("/v1/zzim") +@RequiredArgsConstructor +public class ZzimController implements ZzimSwagger { + + private final ZzimService zzimService; + + @Override + @GetMapping("/me") + public ResponseEntity> checkZzimByMoimAndMember( + @RequestParam Long moimId, @LoginMember Member member + ) { + ZzimCheckResponse zzimCheckResponse = zzimService.checkZzimByMember(moimId, member); + + return ResponseEntity.ok().body(new RestResponse<>(zzimCheckResponse)); + } + + @Override + @PostMapping + public ResponseEntity updateZzim(@Valid @RequestBody ZzimUpdateRequest request, @LoginMember Member member) { + zzimService.updateZzim(request, member); + + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/mouda/backend/zzim/controller/ZzimSwagger.java b/backend/src/main/java/mouda/backend/zzim/controller/ZzimSwagger.java new file mode 100644 index 000000000..29db37e2e --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/controller/ZzimSwagger.java @@ -0,0 +1,31 @@ +package mouda.backend.zzim.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; +import mouda.backend.common.RestResponse; +import mouda.backend.config.argumentresolver.LoginMember; +import mouda.backend.member.domain.Member; +import mouda.backend.zzim.dto.response.ZzimCheckResponse; +import mouda.backend.zzim.dto.request.ZzimUpdateRequest; + +public interface ZzimSwagger { + + @Operation(summary = "찜 여부 조회", description = "해당 모임에 대한 회원의 찜 여부를 조회한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "찜 여부 조회 성공!") + }) + ResponseEntity> checkZzimByMoimAndMember(@RequestParam Long moimId, + @LoginMember Member member); + + @Operation(summary = "모임 찜하기", description = "해당 모임을 찜한다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "모임 찜하기 성공!") + }) + public ResponseEntity updateZzim(@Valid @RequestBody ZzimUpdateRequest request, @LoginMember Member member); +} diff --git a/backend/src/main/java/mouda/backend/zzim/domain/Zzim.java b/backend/src/main/java/mouda/backend/zzim/domain/Zzim.java index bf8a3081f..58f283d32 100644 --- a/backend/src/main/java/mouda/backend/zzim/domain/Zzim.java +++ b/backend/src/main/java/mouda/backend/zzim/domain/Zzim.java @@ -4,7 +4,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import mouda.backend.member.domain.Member; @@ -20,8 +22,16 @@ public class Zzim { private Long id; @ManyToOne + @JoinColumn(nullable = false) private Moim moim; @ManyToOne + @JoinColumn(nullable = false) private Member member; + + @Builder + public Zzim(Moim moim, Member member) { + this.moim = moim; + this.member = member; + } } diff --git a/backend/src/main/java/mouda/backend/zzim/dto/request/ZzimUpdateRequest.java b/backend/src/main/java/mouda/backend/zzim/dto/request/ZzimUpdateRequest.java new file mode 100644 index 000000000..3bda59708 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/dto/request/ZzimUpdateRequest.java @@ -0,0 +1,10 @@ +package mouda.backend.zzim.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; + +public record ZzimUpdateRequest( + @NotNull @Positive + Long moimId +) { +} diff --git a/backend/src/main/java/mouda/backend/zzim/dto/response/ZzimCheckResponse.java b/backend/src/main/java/mouda/backend/zzim/dto/response/ZzimCheckResponse.java new file mode 100644 index 000000000..5ede2b683 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/dto/response/ZzimCheckResponse.java @@ -0,0 +1,6 @@ +package mouda.backend.zzim.dto.response; + +public record ZzimCheckResponse( + boolean isZzimed +) { +} diff --git a/backend/src/main/java/mouda/backend/zzim/exception/ZzimErrorMessage.java b/backend/src/main/java/mouda/backend/zzim/exception/ZzimErrorMessage.java new file mode 100644 index 000000000..44287ff3e --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/exception/ZzimErrorMessage.java @@ -0,0 +1,13 @@ +package mouda.backend.zzim.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ZzimErrorMessage { + + MOIN_NOT_FOUND("찜할 모임을 찾을 수 없습니다."); + + private final String message; +} diff --git a/backend/src/main/java/mouda/backend/zzim/exception/ZzimException.java b/backend/src/main/java/mouda/backend/zzim/exception/ZzimException.java new file mode 100644 index 000000000..a9be7f454 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/exception/ZzimException.java @@ -0,0 +1,12 @@ +package mouda.backend.zzim.exception; + +import org.springframework.http.HttpStatus; + +import mouda.backend.exception.MoudaException; + +public class ZzimException extends MoudaException { + + public ZzimException(HttpStatus httpStatus, ZzimErrorMessage zzimErrorMessage) { + super(httpStatus, zzimErrorMessage.getMessage()); + } +} diff --git a/backend/src/main/java/mouda/backend/zzim/repository/ZzimRepository.java b/backend/src/main/java/mouda/backend/zzim/repository/ZzimRepository.java new file mode 100644 index 000000000..a6ca0f2d2 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/repository/ZzimRepository.java @@ -0,0 +1,16 @@ +package mouda.backend.zzim.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import mouda.backend.zzim.domain.Zzim; + +public interface ZzimRepository extends JpaRepository { + + boolean existsByMoimIdAndMemberId(Long moimId, Long memberId); + + Optional findByMoimIdAndMemberId(Long moimId, Long memberId); + + void deleteAllByMoimId(Long moimId); +} diff --git a/backend/src/main/java/mouda/backend/zzim/service/ZzimService.java b/backend/src/main/java/mouda/backend/zzim/service/ZzimService.java new file mode 100644 index 000000000..d2680f8a4 --- /dev/null +++ b/backend/src/main/java/mouda/backend/zzim/service/ZzimService.java @@ -0,0 +1,42 @@ +package mouda.backend.zzim.service; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import mouda.backend.member.domain.Member; +import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.repository.MoimRepository; +import mouda.backend.zzim.domain.Zzim; +import mouda.backend.zzim.dto.response.ZzimCheckResponse; +import mouda.backend.zzim.dto.request.ZzimUpdateRequest; +import mouda.backend.zzim.exception.ZzimErrorMessage; +import mouda.backend.zzim.exception.ZzimException; +import mouda.backend.zzim.repository.ZzimRepository; + +@Service +@RequiredArgsConstructor +public class ZzimService { + + private final ZzimRepository zzimRepository; + private final MoimRepository moimRepository; + + @Transactional(readOnly = true) + public ZzimCheckResponse checkZzimByMember(Long moimId, Member member) { + boolean isZzimed = zzimRepository.existsByMoimIdAndMemberId(moimId, member.getId()); + + return new ZzimCheckResponse(isZzimed); + } + + public void updateZzim(ZzimUpdateRequest request, Member member) { + Moim moim = moimRepository.findById(request.moimId()) + .orElseThrow(() -> new ZzimException(HttpStatus.NOT_FOUND, ZzimErrorMessage.MOIN_NOT_FOUND)); + + zzimRepository.findByMoimIdAndMemberId(request.moimId(), member.getId()) + .ifPresentOrElse( + zzimRepository::delete, + () -> zzimRepository.save(Zzim.builder().moim(moim).member(member).build()) + ); + } +} diff --git a/backend/src/test/java/mouda/backend/auth/controller/AuthControllerTest.java b/backend/src/test/java/mouda/backend/auth/controller/AuthControllerTest.java index 6f3062bca..6d89c17a6 100644 --- a/backend/src/test/java/mouda/backend/auth/controller/AuthControllerTest.java +++ b/backend/src/test/java/mouda/backend/auth/controller/AuthControllerTest.java @@ -2,7 +2,7 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; -import mouda.backend.auth.dto.LoginRequest; +import mouda.backend.auth.dto.request.LoginRequest; import mouda.backend.config.DatabaseCleaner; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; diff --git a/backend/src/test/java/mouda/backend/auth/service/AuthServiceTest.java b/backend/src/test/java/mouda/backend/auth/service/AuthServiceTest.java index 118476568..ac5bbd1ad 100644 --- a/backend/src/test/java/mouda/backend/auth/service/AuthServiceTest.java +++ b/backend/src/test/java/mouda/backend/auth/service/AuthServiceTest.java @@ -1,7 +1,7 @@ package mouda.backend.auth.service; -import mouda.backend.auth.dto.LoginRequest; -import mouda.backend.auth.dto.LoginResponse; +import mouda.backend.auth.dto.request.LoginRequest; +import mouda.backend.auth.dto.response.LoginResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/backend/src/test/java/mouda/backend/chamyo/controller/ChamyoControllerTest.java b/backend/src/test/java/mouda/backend/chamyo/controller/ChamyoControllerTest.java new file mode 100644 index 000000000..336b57a17 --- /dev/null +++ b/backend/src/test/java/mouda/backend/chamyo/controller/ChamyoControllerTest.java @@ -0,0 +1,257 @@ +package mouda.backend.chamyo.controller; + +import static org.assertj.core.api.Assertions.*; +import static org.hamcrest.Matchers.*; + +import java.time.LocalDate; +import java.time.LocalTime; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; + +import io.restassured.RestAssured; +import mouda.backend.chamyo.dto.request.MoimChamyoRequest; +import mouda.backend.chamyo.service.ChamyoService; +import mouda.backend.config.DatabaseCleaner; +import mouda.backend.fixture.MemberFixture; +import mouda.backend.fixture.MoimFixture; +import mouda.backend.fixture.TokenFixture; +import mouda.backend.member.domain.Member; +import mouda.backend.member.repository.MemberRepository; +import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.domain.MoimStatus; +import mouda.backend.moim.dto.request.MoimCreateRequest; +import mouda.backend.moim.repository.MoimRepository; +import mouda.backend.moim.service.MoimService; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ChamyoControllerTest { + + @Autowired + private ChamyoService chamyoService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private MoimRepository moimRepository; + + @Autowired + private DatabaseCleaner dbCleaner; + + @Autowired + private MoimService moimService; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Nested + @DisplayName("현재 로그인 된 회원의 참여 여부를 조회한다.") + class FindMoimRoleByMemberTest { + + @AfterEach + void setUp() { + dbCleaner.cleanUp(); + } + + @DisplayName("현재 로그인 된 회원은 방장이다.") + @Test + void findMoimRole_WhemMemberIsMoimer() { + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member tebah = memberRepository.findByNickname("테바").get(); + Moim moim = moimService.createMoim(getMoimCreateRequestByMaxPeople(2), tebah); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .param("moimId", moim.getId()) + .when().get("/v1/chamyo/me") + .then().statusCode(200) + .body("data.role", is("MOIMER")); + } + + @DisplayName("현재 로그인 된 회원은 참여자이다.") + @Test + void findMoimRole_WhemMemberIsMoimee() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequestByMaxPeople(2), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member tebah = memberRepository.findByNickname("테바").get(); + chamyoService.chamyoMoim(new MoimChamyoRequest(moim.getId()), tebah); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .param("moimId", moim.getId()) + .when().get("/v1/chamyo/me") + .then().log().all().statusCode(200) + .body("data.role", is("MOIMEE")); + } + + @DisplayName("현재 로그인 된 회원은 아직 모임에 참여하지 않은 상태이다.") + @Test + void findMoimRole_WhemMemberIsNonMoimee() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequestByMaxPeople(2), hogee); + + RestAssured.given().log().all() + .header("Authorization", "Bearer " + TokenFixture.getTokenWithNicknameTebah()) + .param("moimId", moim.getId()) + .when().get("/v1/chamyo/me") + .then().log().all().statusCode(200) + .body("data.role", is("NON_MOIMEE")); + } + } + + @Nested + @DisplayName("모임의 모든 참여자 조회 테스트") + class FindAllChamyoTest { + + @AfterEach + void setUp() { + dbCleaner.cleanUp(); + } + + @DisplayName("모든 참여자를 조회한다.") + @Test + void success() { + Moim moim = moimRepository.save(MoimFixture.getSoccerMoim()); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member member = memberRepository.findByNickname("테바").get(); + Member member1 = memberRepository.save(MemberFixture.getHogee()); + + chamyoService.chamyoMoim(new MoimChamyoRequest(moim.getId()), member); + chamyoService.chamyoMoim(new MoimChamyoRequest(moim.getId()), member1); + + RestAssured.given().log().all() + .header("Authorization", "Bearer " + accessToken) + .param("moimId", moim.getId()) + .when().get("/v1/chamyo/all") + .then().log().all().statusCode(200) + .body("data.chamyos.size()", is(2)) + .body("data.chamyos[0].role", is("MOIMEE")) + .body("data.chamyos[1].role", is("MOIMEE")); + } + } + + @Nested + @DisplayName("모임 참여 테스트") + class ChamyoMoimTest { + + @AfterEach + void tearDown() { + dbCleaner.cleanUp(); + } + + @DisplayName("모임에 성공적으로 참여한다") + @Test + void success() { + Moim moim = moimRepository.save(MoimFixture.getSoccerMoim()); + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .body(new MoimChamyoRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/chamyo") + .then().statusCode(200); + } + + @DisplayName("모임에 이미 참여한 경우 예외가 발생한다.") + @Test + void fail_whenAlreadyChamyo() { + Moim moim = moimRepository.save(MoimFixture.getSoccerMoim()); + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member member = memberRepository.findByNickname("테바").get(); + + chamyoService.chamyoMoim(new MoimChamyoRequest(moim.getId()), member); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .body(new MoimChamyoRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/chamyo") + .then().statusCode(400); + } + + @DisplayName("모임의 최대 인원을 초과하여 참여할 경우 예외가 발생한다.") + @Test + void fail_whenMoimFull() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequestByMaxPeople(1), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .body(new MoimChamyoRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/chamyo") + .then().statusCode(400); + } + + @DisplayName("최대 인원을 초과하지 않아도 모임장이 모집을 종료한 경우 예외가 발생한다.") + @Test + void fail_whenMoimClosed() { + Moim moim = moimRepository.save(MoimFixture.getSoccerMoim()); + moimService.updateMoimStatusById(moim.getId(), MoimStatus.COMPLETED); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + + RestAssured.given().log().all() + .header("Authorization", "Bearer " + accessToken) + .body(new MoimChamyoRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/chamyo") + .then().log().all().statusCode(400); + } + + @DisplayName("취소된 모임은 참여할 수 없다.") + @Test + void fail_whenMoimCancled() { + Moim moim = moimRepository.save(MoimFixture.getSoccerMoim()); + moimService.updateMoimStatusById(moim.getId(), MoimStatus.CANCELED); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + + RestAssured.given().log().all() + .header("Authorization", "Bearer " + accessToken) + .body(new MoimChamyoRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/chamyo") + .then().log().all().statusCode(400); + } + + @DisplayName("마지막 참여로 인해 모임이 꽉 찬 경우 모임 상태를 변경한다.") + @Test + void checkIfMoimStatusChanged() { + Member tebah = memberRepository.save(MemberFixture.getTebah()); + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequestByMaxPeople(2), tebah); + + chamyoService.chamyoMoim(new MoimChamyoRequest(moim.getId()), hogee); + + Moim updatedMoim = moimRepository.findById(moim.getId()).get(); + assertThat(updatedMoim.getMoimStatus()).isEqualTo(MoimStatus.COMPLETED); + } + } + + private MoimCreateRequest getMoimCreateRequestByMaxPeople(int maxPeople) { + return new MoimCreateRequest( + "test", LocalDate.now().plusDays(1), LocalTime.now(), + "test", maxPeople, "test" + ); + } +} diff --git a/backend/src/test/java/mouda/backend/config/DatabaseCleaner.java b/backend/src/test/java/mouda/backend/config/DatabaseCleaner.java index c02848957..38247f4d6 100644 --- a/backend/src/test/java/mouda/backend/config/DatabaseCleaner.java +++ b/backend/src/test/java/mouda/backend/config/DatabaseCleaner.java @@ -18,9 +18,18 @@ public DatabaseCleaner(EntityManager entityManager) { @Transactional public void cleanUp() { + entityManager.createNativeQuery("DELETE FROM CHAT").executeUpdate(); + entityManager.createNativeQuery("ALTER TABLE CHAT alter column id restart with 1").executeUpdate(); + entityManager.createNativeQuery("DELETE FROM COMMENT").executeUpdate(); entityManager.createNativeQuery("ALTER TABLE COMMENT alter column id restart with 1").executeUpdate(); + entityManager.createNativeQuery("DELETE FROM ZZIM").executeUpdate(); + entityManager.createNativeQuery("ALTER TABLE ZZIM alter column id restart with 1").executeUpdate(); + + entityManager.createNativeQuery("DELETE FROM CHAMYO").executeUpdate(); + entityManager.createNativeQuery("ALTER TABLE CHAMYO alter column id restart with 1").executeUpdate(); + entityManager.createNativeQuery("DELETE FROM MEMBER").executeUpdate(); entityManager.createNativeQuery("ALTER TABLE MEMBER alter column id restart with 1").executeUpdate(); diff --git a/backend/src/test/java/mouda/backend/fixture/TokenFixture.java b/backend/src/test/java/mouda/backend/fixture/TokenFixture.java index 06aea9a31..b45449b65 100644 --- a/backend/src/test/java/mouda/backend/fixture/TokenFixture.java +++ b/backend/src/test/java/mouda/backend/fixture/TokenFixture.java @@ -3,7 +3,7 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.Response; -import mouda.backend.auth.dto.LoginRequest; +import mouda.backend.auth.dto.request.LoginRequest; public class TokenFixture { diff --git a/backend/src/test/java/mouda/backend/moim/service/MoimServiceTest.java b/backend/src/test/java/mouda/backend/moim/service/MoimServiceTest.java index d143d3e7c..3f2d31cc1 100644 --- a/backend/src/test/java/mouda/backend/moim/service/MoimServiceTest.java +++ b/backend/src/test/java/mouda/backend/moim/service/MoimServiceTest.java @@ -6,7 +6,6 @@ import java.time.LocalDate; import java.time.LocalTime; import java.util.List; -import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -25,10 +24,8 @@ import mouda.backend.member.repository.MemberRepository; import mouda.backend.moim.domain.Moim; import mouda.backend.moim.dto.request.MoimCreateRequest; -import mouda.backend.moim.dto.request.MoimJoinRequest; import mouda.backend.moim.dto.response.MoimDetailsFindResponse; import mouda.backend.moim.dto.response.MoimFindAllResponses; -import mouda.backend.moim.exception.MoimException; import mouda.backend.moim.repository.MoimRepository; @SpringBootTest @@ -59,10 +56,11 @@ void cleanUp() { void createMoim() { MoimCreateRequest moimCreateRequest = new MoimCreateRequest( "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 10, "안나", "설명" + 10, "설명" ); - Moim moim = moimService.createMoim(moimCreateRequest); + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(moimCreateRequest, hogee); assertThat(moim.getId()).isEqualTo(1L); } @@ -72,10 +70,12 @@ void createMoim() { void findAllMoim() { MoimCreateRequest moimCreateRequest = new MoimCreateRequest( "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 10, "안나", "설명" + 10, "설명" ); - moimService.createMoim(moimCreateRequest); - moimService.createMoim(moimCreateRequest); + + Member hogee = memberRepository.save(MemberFixture.getHogee()); + moimService.createMoim(moimCreateRequest, hogee); + moimService.createMoim(moimCreateRequest, hogee); MoimFindAllResponses moimResponses = moimService.findAllMoim(); @@ -87,64 +87,32 @@ void findAllMoim() { void findMoimDetails() { MoimCreateRequest moimCreateRequest = new MoimCreateRequest( "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 10, "안나", "설명" + 10, "설명" ); - moimService.createMoim(moimCreateRequest); + Member hogee = memberRepository.save(MemberFixture.getHogee()); + moimService.createMoim(moimCreateRequest, hogee); MoimDetailsFindResponse moimDetails = moimService.findMoimDetails(1L); assertThat(moimDetails.title()).isEqualTo("title"); } - @DisplayName("모임에 참여한다.") - @Test - void joinMoim() { - MoimCreateRequest moimCreateRequest = new MoimCreateRequest( - "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 10, "안나", "설명" - ); - Moim moim = moimService.createMoim(moimCreateRequest); - - MoimJoinRequest moimJoinRequest = new MoimJoinRequest(moim.getId(), "호기"); - moimService.joinMoim(moimJoinRequest); - List participants = memberRepository.findAllByMoimId(moim.getId()); - - Optional moimOptional = moimRepository.findById(moim.getId()); - assertThat(moimOptional).isNotEmpty(); - assertThat(participants.size()).isEqualTo(2); - } - @DisplayName("모임을 삭제한다.") @Test void deleteMoim() { MoimCreateRequest moimCreateRequest = new MoimCreateRequest( "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 10, "안나", "설명" + 10, "설명" ); - moimService.createMoim(moimCreateRequest); + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(moimCreateRequest, hogee); - moimService.deleteMoim(1L); + moimService.deleteMoim(moim.getId(), hogee); List moims = moimRepository.findAll(); assertThat(moims).hasSize(0); } - @DisplayName("최대 참여 인원을 넘으면 예외가 발생한다.") - @Test - void failToJoinMoimWhenExceedMaxPeople() { - MoimCreateRequest moimCreateRequest = new MoimCreateRequest( - "title", LocalDate.now().plusDays(1), LocalTime.now(), "place", - 2, "안나", "설명" - ); - - Moim moim = moimService.createMoim(moimCreateRequest); - MoimJoinRequest hogee = new MoimJoinRequest(moim.getId(), "호기"); - moimService.joinMoim(hogee); - MoimJoinRequest tebah = new MoimJoinRequest(moim.getId(), "테바"); - - assertThrows(MoimException.class, () -> moimService.joinMoim(tebah)); - } - @DisplayName("댓글을 생성한다.") @Test void createComment() { diff --git a/backend/src/test/java/mouda/backend/zzim/controller/ZzimControllerTest.java b/backend/src/test/java/mouda/backend/zzim/controller/ZzimControllerTest.java new file mode 100644 index 000000000..fe0e36bc9 --- /dev/null +++ b/backend/src/test/java/mouda/backend/zzim/controller/ZzimControllerTest.java @@ -0,0 +1,156 @@ +package mouda.backend.zzim.controller; + +import static org.assertj.core.api.Assertions.*; +import static org.hamcrest.Matchers.*; + +import java.time.LocalDate; +import java.time.LocalTime; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; + +import io.restassured.RestAssured; +import mouda.backend.config.DatabaseCleaner; +import mouda.backend.fixture.MemberFixture; +import mouda.backend.fixture.TokenFixture; +import mouda.backend.member.domain.Member; +import mouda.backend.member.repository.MemberRepository; +import mouda.backend.moim.domain.Moim; +import mouda.backend.moim.dto.request.MoimCreateRequest; +import mouda.backend.moim.service.MoimService; +import mouda.backend.zzim.dto.response.ZzimCheckResponse; +import mouda.backend.zzim.dto.request.ZzimUpdateRequest; +import mouda.backend.zzim.service.ZzimService; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ZzimControllerTest { + + @Autowired + private DatabaseCleaner databaseCleaner; + + @Autowired + private MoimService moimService; + + @Autowired + private ZzimService zzimService; + + @Autowired + private MemberRepository memberRepository; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Nested + @DisplayName("회원의 찜 여부 조회 테스트") + class CheckZzimByMoimAndMemberTest { + + @BeforeEach + void setUp() { + databaseCleaner.cleanUp(); + } + + @DisplayName("회원은 해당 모임에 찜을 한 상태이다.") + @Test + void checkZzimByMoimAndMember_WhenMemberZzimed() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequest(), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member tebah = memberRepository.findByNickname("테바").get(); + + zzimService.updateZzim(new ZzimUpdateRequest(moim.getId()), tebah); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .param("moimId", moim.getId()) + .when().get("/v1/zzim/me") + .then().statusCode(200) + .body("data.isZzimed", is(true)); + } + + @DisplayName("회원은 해당 모임에 찜을 하지 않은 상태이다.") + @Test + void checkZzimByMoimAndMember_WhenMemberDidntZzim() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequest(), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .param("moimId", moim.getId()) + .when().get("/v1/zzim/me") + .then().statusCode(200) + .body("data.isZzimed", is(false)); + } + } + + @Nested + @DisplayName("찜 상태 변경 테스트") + class UpdateZzimTest { + + @BeforeEach + void setUp() { + databaseCleaner.cleanUp(); + } + + @DisplayName("회원이 모임을 찜한다.") + @Test + void zzimMoim() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequest(), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member tebah = memberRepository.findByNickname("테바").get(); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .body(new ZzimUpdateRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/zzim") + .then().statusCode(200); + + ZzimCheckResponse zzimCheckResponse = zzimService.checkZzimByMember(moim.getId(), tebah); + assertThat(zzimCheckResponse.isZzimed()).isTrue(); + } + + @DisplayName("회원이 찜을 취소한다.") + @Test + void cancelZzim() { + Member hogee = memberRepository.save(MemberFixture.getHogee()); + Moim moim = moimService.createMoim(getMoimCreateRequest(), hogee); + + String accessToken = TokenFixture.getTokenWithNicknameTebah(); + Member tebah = memberRepository.findByNickname("테바").get(); + zzimService.updateZzim(new ZzimUpdateRequest(moim.getId()), tebah); + + RestAssured.given() + .header("Authorization", "Bearer " + accessToken) + .body(new ZzimUpdateRequest(moim.getId())) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/v1/zzim") + .then().statusCode(200); + + ZzimCheckResponse zzimCheckResponse = zzimService.checkZzimByMember(moim.getId(), tebah); + assertThat(zzimCheckResponse.isZzimed()).isFalse(); + } + } + + private MoimCreateRequest getMoimCreateRequest() { + return new MoimCreateRequest( + "test", LocalDate.now().plusDays(1), LocalTime.now(), + "test", 10, "test" + ); + } +}