Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public enum ErrorCode implements ResponseCode {
FOLLOW_NOT_FOUND(HttpStatus.NOT_FOUND, 75000, "존재하지 않는 FOLLOW 입니다."),
USER_ALREADY_UNFOLLOWED(HttpStatus.BAD_REQUEST, 75001, "이미 언팔로우한 사용자입니다."),
USER_CANNOT_FOLLOW_SELF(HttpStatus.BAD_REQUEST, 75002, "사용자는 자신을 팔로우할 수 없습니다."),
FOLLOW_COUNT_IS_ZERO(HttpStatus.BAD_REQUEST, 75003, "사용자의 팔로우 수가 0일때는 언팔로우는 불가능합니다."),
FOLLOW_COUNT_CANNOT_BE_NEGATIVE(HttpStatus.BAD_REQUEST, 75003, "사용자의 팔로우 수가 0일때는 언팔로우는 불가능합니다."),

/**
* 80000 : book error
Expand Down Expand Up @@ -104,6 +104,15 @@ public enum ErrorCode implements ResponseCode {
* 120000 : voteItem error
*/
VOTE_ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, 120000, "투표는 존재하지만 투표항목이 비어있습니다."),
VOTE_ITEM_ALREADY_VOTED(HttpStatus.BAD_REQUEST, 120001, "이미 투표한 투표항목입니다."),
VOTE_ITEM_NOT_VOTED_CANNOT_CANCEL(HttpStatus.BAD_REQUEST, 120002, "투표하지 않은 투표항목은 취소할 수 없습니다."),
VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE(HttpStatus.BAD_REQUEST, 120003, "투표항목의 투표 수는 0 이하로 감소할 수 없습니다."),


/**
* 125000 : voteParticipant error
*/
VOTE_PARTICIPANT_NOT_FOUND(HttpStatus.NOT_FOUND, 125000, "존재하지 않는 VOTE PARTICIPANT 입니다."),

/**
* 130000 : record error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public enum SwaggerResponseDescription {
USER_ALREADY_FOLLOWED,
USER_ALREADY_UNFOLLOWED,
USER_CANNOT_FOLLOW_SELF,
FOLLOW_COUNT_IS_ZERO
FOLLOW_COUNT_CANNOT_BE_NEGATIVE
))),
GET_USER_FOLLOW(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND
Expand Down Expand Up @@ -132,6 +132,14 @@ public enum SwaggerResponseDescription {
VOTE_CANNOT_BE_OVERVIEW,
INVALID_VOTE_PAGE_RANGE
))),
VOTE(new LinkedHashSet<>(Set.of(
ROOM_ACCESS_FORBIDDEN,
VOTE_ITEM_NOT_FOUND,
VOTE_ITEM_ALREADY_VOTED,
VOTE_ITEM_NOT_VOTED_CANNOT_CANCEL,
VOTE_ITEM_COUNT_CANNOT_BE_NEGATIVE
))),


// FEED
FEED_CREATE(new LinkedHashSet<>(Set.of(
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/konkuk/thip/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import konkuk.thip.common.security.oauth2.CustomOAuth2UserService;
import konkuk.thip.common.security.oauth2.CustomSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -28,6 +29,9 @@
@RequiredArgsConstructor
public class SecurityConfig {

@Value("${server.web-url}")
private String webUrl;

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CustomOAuth2UserService customOAuth2UserService;
Expand Down Expand Up @@ -90,8 +94,8 @@ public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"http://localhost:5173",
"https://thip-git-develop-thips-projects.vercel.app"
)); // 배포 시 도메인 명시
webUrl
));
Comment on lines -93 to +98
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳 FE 배포 url을 명시하는 것을 고민했었는데, yml에 숨기는것도 좋은거같습니다

config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.setAllowedHeaders(Collections.singletonList("*"));
config.setAllowCredentials(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class RoomParticipantQueryPersistenceAdapter implements RoomParticipantQu

@Override
public boolean existByUserIdAndRoomId(Long userId, Long roomId) {
return roomParticipantJpaRepository.existByUserIdAndRoomId(userId, roomId);
return roomParticipantJpaRepository.existsByUserIdAndRoomId(userId, roomId);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@

public interface RoomParticipantJpaRepository extends JpaRepository<RoomParticipantJpaEntity, Long>, RoomParticipantQueryRepository{

@Query(value = "SELECT * FROM room_participants WHERE user_id = :userId AND room_id = :roomId AND status = 'ACTIVE'", nativeQuery = true)
@Query("SELECT rp FROM RoomParticipantJpaEntity rp " +
"WHERE rp.userJpaEntity.userId = :userId " +
"AND rp.roomJpaEntity.roomId = :roomId " +
"AND rp.status = 'ACTIVE'")
Comment on lines +13 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳

Optional<RoomParticipantJpaEntity> findByUserIdAndRoomId(@Param("userId") Long userId, @Param("roomId") Long roomId);

@Query(value = "SELECT * FROM room_participants WHERE room_id = :roomId AND status = 'ACTIVE'", nativeQuery = true)
@Query("SELECT rp FROM RoomParticipantJpaEntity rp " +
"WHERE rp.roomJpaEntity.roomId = :roomId " +
"AND rp.status = 'ACTIVE'")
List<RoomParticipantJpaEntity> findAllByRoomId(@Param("roomId") Long roomId);

@Query(
value = "SELECT EXISTS (SELECT 1 FROM room_participants rp WHERE rp.user_id = :userId AND rp.room_id = :roomId AND rp.status = 'ACTIVE')",
nativeQuery = true
)
boolean existByUserIdAndRoomId(@Param("userId") Long userId, @Param("roomId") Long roomId);
@Query("SELECT CASE WHEN COUNT(rp) > 0 THEN true ELSE false END " +
"FROM RoomParticipantJpaEntity rp " +
"WHERE rp.userJpaEntity.userId = :userId " +
"AND rp.roomJpaEntity.roomId = :roomId " +
"AND rp.status = 'ACTIVE'")
boolean existsByUserIdAndRoomId(@Param("userId") Long userId, @Param("roomId") Long roomId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class RoomParticipantValidator{

// 사용자가 방에 속해있는지 검증
public void validateUserIsRoomMember(Long roomId, Long userId) {
if (!participantPort.existByUserIdAndRoomId(roomId, userId)) {
if (!participantPort.existByUserIdAndRoomId(userId, roomId)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

허허 뭐죠,, 아마 테스트 코드에서는 유저, 방 모두 id = 1 이니 문제가 없었던게 아닐까 싶네요

throw new InvalidStateException(ROOM_ACCESS_FORBIDDEN,
new IllegalArgumentException("사용자가 이 방의 참가자가 아닙니다. roomId=" + roomId + ", userId=" + userId));
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/konkuk/thip/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void increaseFollowerCount() {

public void decreaseFollowerCount() {
if(followerCount == 0) {
throw new InvalidStateException(ErrorCode.FOLLOW_COUNT_IS_ZERO);
throw new InvalidStateException(ErrorCode.FOLLOW_COUNT_CANNOT_BE_NEGATIVE);
}
followerCount--;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,28 @@
import jakarta.validation.Valid;
import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
import konkuk.thip.common.swagger.annotation.ExceptionDescription;
import konkuk.thip.vote.adapter.in.web.request.VoteCreateRequest;
import konkuk.thip.vote.adapter.in.web.request.VoteRequest;
import konkuk.thip.vote.adapter.in.web.response.VoteCreateResponse;
import konkuk.thip.vote.adapter.in.web.response.VoteResponse;
import konkuk.thip.vote.application.port.in.VoteCreateUseCase;
import konkuk.thip.vote.application.port.in.VoteUseCase;
import lombok.RequiredArgsConstructor;
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.RestController;

import static konkuk.thip.common.swagger.SwaggerResponseDescription.VOTE;

@Tag(name = "Vote Command API", description = "투표 상태변경 관련 API")
@RestController
@RequiredArgsConstructor
public class VoteCommandController {

private final VoteCreateUseCase voteCreateUseCase;
private final VoteUseCase voteUseCase;

@Operation(
summary = "투표 생성",
Expand All @@ -36,4 +43,20 @@ public BaseResponse<VoteCreateResponse> createVote(
voteCreateUseCase.createVote(request.toCommand(userId, roomId))
));
}

@Operation(
summary = "투표하기",
description = "특정 투표에 대해 사용자가 투표를 진행합니다. type이 true이면 투표하기, false이면 투표 취소입니다."
)
@ExceptionDescription(VOTE)
@PostMapping("/rooms/{roomId}/vote/{voteId}")
public BaseResponse<VoteResponse> vote(
@Parameter(hidden = true) @UserId Long userId,
@Parameter(description = "투표를 진행할 방 ID", example = "1") @PathVariable Long roomId,
@Parameter(description = "투표할 투표 ID", example = "1") @PathVariable Long voteId,
@Valid @RequestBody VoteRequest request) {
return BaseResponse.ok(VoteResponse.of(
voteUseCase.vote(request.toCommand(userId, roomId, voteId)))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package konkuk.thip.vote.adapter.in.web.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import konkuk.thip.vote.application.port.in.dto.VoteCommand;

@Schema(
description = "투표하기 요청 DTO 정보"
)
public record VoteRequest(
@Schema(
description = "투표하려는 투표 항목 ID",
example = "1"
)
@NotNull(message = "voteItemId는 필수입니다.")
Long voteItemId,
@Schema(
description = "투표 유형 (true: 투표하기, false: 투표 취소하기)",
example = "true"
)
@NotNull(message = "type은 필수입니다.")
Boolean type
) {
public VoteCommand toCommand(Long userId, Long roomId, Long voteId) {
return new VoteCommand(userId, roomId, voteId, voteItemId, type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package konkuk.thip.vote.adapter.in.web.response;

import konkuk.thip.vote.application.port.in.dto.VoteResult;

public record VoteResponse(
Long voteItemId,
Long roomId,
Boolean type
) {
public static VoteResponse of(VoteResult voteResult) {
return new VoteResponse(voteResult.voteItemId(), voteResult.roomId(), voteResult.type());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.persistence.*;
import konkuk.thip.common.entity.BaseJpaEntity;
import konkuk.thip.vote.domain.VoteItem;
import lombok.*;

@Entity
Expand All @@ -28,4 +29,9 @@ public class VoteItemJpaEntity extends BaseJpaEntity {
@JoinColumn(name = "post_id")
private VoteJpaEntity voteJpaEntity;

public VoteItemJpaEntity updateFrom(VoteItem voteItem) {
this.itemName = voteItem.getItemName();
this.count = voteItem.getCount();
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ public class VoteParticipantJpaEntity extends BaseJpaEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "vote_item_id")
private VoteItemJpaEntity voteItemJpaEntity;

public VoteParticipantJpaEntity updateVoteItem(VoteItemJpaEntity voteItemJpaEntity) {
this.voteItemJpaEntity = voteItemJpaEntity;
return this;
}
Comment on lines +30 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository;
import konkuk.thip.vote.adapter.out.jpa.VoteItemJpaEntity;
import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity;
import konkuk.thip.vote.adapter.out.jpa.VoteParticipantJpaEntity;
import konkuk.thip.vote.adapter.out.mapper.VoteItemMapper;
import konkuk.thip.vote.adapter.out.mapper.VoteMapper;
import konkuk.thip.vote.adapter.out.mapper.VoteParticipantMapper;
import konkuk.thip.vote.adapter.out.persistence.repository.VoteItemJpaRepository;
import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository;
import konkuk.thip.vote.adapter.out.persistence.repository.VoteParticipantJpaRepository;
import konkuk.thip.vote.application.port.out.VoteCommandPort;
import konkuk.thip.vote.domain.Vote;
import konkuk.thip.vote.domain.VoteItem;
import konkuk.thip.vote.domain.VoteParticipant;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand All @@ -30,9 +34,11 @@ public class VoteCommandPersistenceAdapter implements VoteCommandPort {
private final VoteItemJpaRepository voteItemJpaRepository;
private final UserJpaRepository userJpaRepository;
private final RoomJpaRepository roomJpaRepository;
private final VoteParticipantJpaRepository voteParticipantJpaRepository;

private final VoteMapper voteMapper;
private final VoteItemMapper voteItemMapper;
private final VoteParticipantMapper voteParticipantMapper;

@Override
public Long saveVote(Vote vote) {
Expand Down Expand Up @@ -69,6 +75,66 @@ public Optional<Vote> findById(Long id) {
.map(voteMapper::toDomainEntity);
}

@Override
public Optional<VoteItem> findVoteItemById(Long id) {
return voteItemJpaRepository.findById(id)
.map(voteItemMapper::toDomainEntity);
}

@Override
public Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteId(Long userId, Long voteId) {
return voteParticipantJpaRepository.findVoteParticipantByUserIdAndVoteId(userId, voteId)
.map(voteParticipantMapper::toDomainEntity);
}

@Override
public Optional<VoteParticipant> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId) {
return voteParticipantJpaRepository.findVoteParticipantByUserIdAndVoteItemId(userId, voteItemId)
.map(voteParticipantMapper::toDomainEntity);
}

@Override
public void updateVoteParticipant(VoteParticipant voteParticipant) {
VoteParticipantJpaEntity voteParticipantJpaEntity = voteParticipantJpaRepository.findById(voteParticipant.getId()).orElseThrow(
() -> new EntityNotFoundException(VOTE_PARTICIPANT_NOT_FOUND)
);

VoteItemJpaEntity voteItemJpaEntity = voteItemJpaRepository.findById(voteParticipant.getVoteItemId()).orElseThrow(
() -> new EntityNotFoundException(VOTE_ITEM_NOT_FOUND)
);

voteParticipantJpaRepository.save(voteParticipantJpaEntity.updateVoteItem(voteItemJpaEntity));
}

@Override
public void saveVoteParticipant(VoteParticipant voteParticipant) {
UserJpaEntity userJpaEntity = userJpaRepository.findById(voteParticipant.getUserId()).orElseThrow(
() -> new EntityNotFoundException(USER_NOT_FOUND)
);

VoteItemJpaEntity voteItemJpaEntity = voteItemJpaRepository.findById(voteParticipant.getVoteItemId()).orElseThrow(
() -> new EntityNotFoundException(VOTE_ITEM_NOT_FOUND)
);

VoteParticipantJpaEntity voteParticipantJpaEntity = voteParticipantMapper.toJpaEntity(userJpaEntity, voteItemJpaEntity);
voteParticipantJpaRepository.save(voteParticipantJpaEntity);
}

@Override
public void deleteVoteParticipant(VoteParticipant voteParticipant) {
// 앞에서 이미 존재 여부를 확인했으므로, 여기서는 ID로 삭제
voteParticipantJpaRepository.deleteById(voteParticipant.getId());
}

@Override
public void updateVoteItem(VoteItem voteItem) {
VoteItemJpaEntity voteItemJpaEntity = voteItemJpaRepository.findById(voteItem.getId()).orElseThrow(
() -> new EntityNotFoundException(VOTE_ITEM_NOT_FOUND)
);

voteItemJpaRepository.save(voteItemJpaEntity.updateFrom(voteItem));
}


@Override
public void updateVote(Vote vote) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

import konkuk.thip.vote.adapter.out.jpa.VoteParticipantJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

public interface VoteParticipantJpaRepository extends JpaRepository<VoteParticipantJpaEntity, Long> {
boolean existsByUserJpaEntity_UserIdAndVoteItemJpaEntity_VoteItemId(Long userId, Long voteItemId);
import java.util.Optional;

@Repository
public interface VoteParticipantJpaRepository extends JpaRepository<VoteParticipantJpaEntity, Long>, VoteParticipantQueryRepository {
@Query("SELECT vp FROM VoteParticipantJpaEntity vp WHERE vp.userJpaEntity.userId = :userId AND vp.voteItemJpaEntity.voteItemId = :voteItemId")
Optional<VoteParticipantJpaEntity> findVoteParticipantByUserIdAndVoteItemId(Long userId, Long voteItemId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package konkuk.thip.vote.adapter.out.persistence.repository;

import konkuk.thip.vote.adapter.out.jpa.VoteParticipantJpaEntity;

import java.util.Optional;

public interface VoteParticipantQueryRepository {

Optional<VoteParticipantJpaEntity> findVoteParticipantByUserIdAndVoteId(Long userId, Long voteId);
}
Loading