diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml deleted file mode 100644 index 02a1f2d..0000000 --- a/.github/workflows/auto-assign.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Auto Assignees and PR Reviewers - -on: - issues: - types: [ opened ] - pull_request: - types: [ opened, ready_for_review ] - -jobs: - auto_assign: - runs-on: ubuntu-latest - steps: - - name: Assign Issue assignees - if: ${{ github.event_name == 'issues' }} - uses: actions-ecosystem/action-add-assignees@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - assignees: ${{ github.actor }} - - - name: Assign PR reviewers - if: ${{ github.event_name == 'pull_request' }} - uses: hkusu/review-assign-action@v1 - with: - assignees: ${{ github.actor }} - reviewers: 2heunxun, JangYeongHu, semi-yu \ No newline at end of file diff --git a/.github/workflows/code-style-test.yml b/.github/workflows/code-style-test.yml new file mode 100644 index 0000000..b389c45 --- /dev/null +++ b/.github/workflows/code-style-test.yml @@ -0,0 +1,43 @@ +name: Java Code Style Check + +on: + pull_request: + branches: ['**'] + push: + branches: ['**'] + workflow_dispatch: + +jobs: + style-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Run Spotless and Checkstyle + run: | + set -e + ./gradlew spotlessCheck --no-daemon + ./gradlew checkstyleMain checkstyleTest --no-daemon + shell: bash + + - name: Show summary + run: | + if [ $? -eq 0 ]; then + echo "๐ŸŽ‰ Code style checks passed!" + else + echo "โŒ Code style violations detected!" + echo "Please run './gradlew spotlessApply' locally and fix Checkstyle issues." + exit 1 + fi + shell: bash diff --git a/build.gradle b/build.gradle index 10b912a..ed442ea 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.5.7' id 'io.spring.dependency-management' version '1.1.7' + id 'com.diffplug.spotless' version '6.25.0' } group = 'opensource' @@ -64,3 +65,27 @@ dependencies { tasks.named('test') { useJUnitPlatform() } + +apply plugin: 'checkstyle' + +checkstyle { + toolVersion = '10.12.0' + configFile = file('config/checkstyle/google_checks.xml') +} + +tasks.withType(Checkstyle) { + reports { + xml.required.set(true) + html.required.set(true) + } +} + +// Spotless ์ ์šฉ +apply plugin: 'com.diffplug.spotless' + +spotless { + java { + googleJavaFormat() + target 'src/**/*.java' + } +} \ No newline at end of file diff --git a/config/checkstyle/google_checks.xml b/config/checkstyle/google_checks.xml new file mode 100644 index 0000000..6eff7fe --- /dev/null +++ b/config/checkstyle/google_checks.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/opensource/bravest/BravestApplication.java b/src/main/java/opensource/bravest/BravestApplication.java index 58c22c0..144da2e 100644 --- a/src/main/java/opensource/bravest/BravestApplication.java +++ b/src/main/java/opensource/bravest/BravestApplication.java @@ -6,8 +6,7 @@ @SpringBootApplication public class BravestApplication { - public static void main(String[] args) { - SpringApplication.run(BravestApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(BravestApplication.class, args); + } } diff --git a/src/main/java/opensource/bravest/domain/chatList/controller/ChatListController.java b/src/main/java/opensource/bravest/domain/chatList/controller/ChatListController.java index 3f6bdc7..38810d3 100644 --- a/src/main/java/opensource/bravest/domain/chatList/controller/ChatListController.java +++ b/src/main/java/opensource/bravest/domain/chatList/controller/ChatListController.java @@ -1,9 +1,12 @@ package opensource.bravest.domain.chatList.controller; +import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListCreateRequest; import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListResponse; import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListUpdateRequest; -import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListCreateRequest; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; import opensource.bravest.domain.chatList.service.ChatListService; import opensource.bravest.global.apiPayload.ApiResponse; import org.springframework.web.bind.annotation.DeleteMapping; @@ -15,47 +18,42 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import jakarta.validation.Valid; -import java.util.List; - @RestController @RequestMapping("/chatlists") @RequiredArgsConstructor public class ChatListController { - private final ChatListService chatListService; - - @PostMapping - public ApiResponse createChatList(@Valid @RequestBody ChatListCreateRequest request) { - ChatListResponse response = chatListService.createChatList(request); - return ApiResponse.onSuccess(response); - } - - @GetMapping("/room/{roomId}") - public ApiResponse> getChatListsByRoomId(@PathVariable Long roomId) { - List response = chatListService.getChatListsByRoomId(roomId); - return ApiResponse.onSuccess(response); - } - - @GetMapping("/{id}") - public ApiResponse getChatListById(@PathVariable Long id) { - ChatListResponse response = chatListService.getChatListById(id); - return ApiResponse.onSuccess(response); - } - - @PutMapping("/{id}") - public ApiResponse updateChatList(@PathVariable Long id, - @Valid @RequestBody ChatListUpdateRequest request) { - ChatListResponse response = chatListService.updateChatList(id, request); - return ApiResponse.onSuccess(response); - } - - @DeleteMapping("/{id}") - public ApiResponse deleteChatList(@PathVariable Long id) { - chatListService.deleteChatList(id); - return ApiResponse.onSuccess(null); - } -} \ No newline at end of file + private final ChatListService chatListService; + + @PostMapping + public ApiResponse createChatList( + @Valid @RequestBody ChatListCreateRequest request) { + ChatListResponse response = chatListService.createChatList(request); + return ApiResponse.onSuccess(response); + } + + @GetMapping("/room/{roomId}") + public ApiResponse> getChatListsByRoomId(@PathVariable Long roomId) { + List response = chatListService.getChatListsByRoomId(roomId); + return ApiResponse.onSuccess(response); + } + + @GetMapping("/{id}") + public ApiResponse getChatListById(@PathVariable Long id) { + ChatListResponse response = chatListService.getChatListById(id); + return ApiResponse.onSuccess(response); + } + + @PutMapping("/{id}") + public ApiResponse updateChatList( + @PathVariable Long id, @Valid @RequestBody ChatListUpdateRequest request) { + ChatListResponse response = chatListService.updateChatList(id, request); + return ApiResponse.onSuccess(response); + } + + @DeleteMapping("/{id}") + public ApiResponse deleteChatList(@PathVariable Long id) { + chatListService.deleteChatList(id); + return ApiResponse.onSuccess(null); + } +} diff --git a/src/main/java/opensource/bravest/domain/chatList/dto/ChatListDto.java b/src/main/java/opensource/bravest/domain/chatList/dto/ChatListDto.java index 282cfb4..77baf7d 100644 --- a/src/main/java/opensource/bravest/domain/chatList/dto/ChatListDto.java +++ b/src/main/java/opensource/bravest/domain/chatList/dto/ChatListDto.java @@ -1,53 +1,51 @@ package opensource.bravest.domain.chatList.dto; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; import lombok.Setter; import opensource.bravest.domain.chatList.entity.ChatList; -import java.time.LocalDateTime; public class ChatListDto { - // 1. ์•„์ด๋””์–ด ์ƒ์„ฑ ์š”์ฒญ DTO (Create Request) - @Getter - @Setter - public static class ChatListCreateRequest { - - private Long roomId; - - private String content; - - private Long registeredBy; - } - - // 2. ์•„์ด๋””์–ด ์ˆ˜์ • ์š”์ฒญ DTO (Update Request) - @Getter - @Setter - public static class ChatListUpdateRequest { - - // ์•„์ด๋””์–ด ๋‚ด์šฉ ์ˆ˜์ •๋งŒ ๊ฐ€์ • - private String content; - } - - @Getter - @Builder - public static class ChatListResponse { - private Long id; - private Long roomId; - private String content; - private Long registeredBy; - private LocalDateTime createdAt; - - public static ChatListResponse fromEntity(ChatList chatList) { - return ChatListResponse.builder() - .id(chatList.getId()) - .roomId(chatList.getRoomId()) - .content(chatList.getContent()) - .registeredBy(chatList.getRegisteredBy().getId()) - .createdAt(chatList.getCreatedAt()) - .build(); - } + // 1. ์•„์ด๋””์–ด ์ƒ์„ฑ ์š”์ฒญ DTO (Create Request) + @Getter + @Setter + public static class ChatListCreateRequest { + + private Long roomId; + + private String content; + + private Long registeredBy; + } + + // 2. ์•„์ด๋””์–ด ์ˆ˜์ • ์š”์ฒญ DTO (Update Request) + @Getter + @Setter + public static class ChatListUpdateRequest { + + // ์•„์ด๋””์–ด ๋‚ด์šฉ ์ˆ˜์ •๋งŒ ๊ฐ€์ • + private String content; + } + + @Getter + @Builder + public static class ChatListResponse { + private Long id; + private Long roomId; + private String content; + private Long registeredBy; + private LocalDateTime createdAt; + + public static ChatListResponse fromEntity(ChatList chatList) { + return ChatListResponse.builder() + .id(chatList.getId()) + .roomId(chatList.getRoomId()) + .content(chatList.getContent()) + .registeredBy(chatList.getRegisteredBy().getId()) + .createdAt(chatList.getCreatedAt()) + .build(); } -} \ No newline at end of file + } +} diff --git a/src/main/java/opensource/bravest/domain/chatList/entity/ChatList.java b/src/main/java/opensource/bravest/domain/chatList/entity/ChatList.java index a9014d1..63cb45a 100644 --- a/src/main/java/opensource/bravest/domain/chatList/entity/ChatList.java +++ b/src/main/java/opensource/bravest/domain/chatList/entity/ChatList.java @@ -8,12 +8,10 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToOne; -import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,49 +19,46 @@ import opensource.bravest.domain.room.entity.AnonymousRoom; import org.hibernate.annotations.CreationTimestamp; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "chat_list") public class ChatList { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "room_id", nullable = false) - private AnonymousRoom room; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", nullable = false) + private AnonymousRoom room; - @NotNull - @Column(length = 255) - private String content; + @NotNull + @Column(length = 255) + private String content; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "profile_id", nullable = false) - private AnonymousProfile registeredBy; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "profile_id", nullable = false) + private AnonymousProfile registeredBy; - @CreationTimestamp - private LocalDateTime createdAt; + @CreationTimestamp private LocalDateTime createdAt; - @Builder - public ChatList(AnonymousRoom room, String content, AnonymousProfile registeredBy) { - this.room = room; - this.content = content; - this.registeredBy = registeredBy; - } + @Builder + public ChatList(AnonymousRoom room, String content, AnonymousProfile registeredBy) { + this.room = room; + this.content = content; + this.registeredBy = registeredBy; + } - public void updateContent(String content) { - this.content = content; - } + public void updateContent(String content) { + this.content = content; + } - public Long getRoomId() { - return this.room.getId(); - } + public Long getRoomId() { + return this.room.getId(); + } - public Long getProfileId() { - return this.registeredBy.getId(); - } -} \ No newline at end of file + public Long getProfileId() { + return this.registeredBy.getId(); + } +} diff --git a/src/main/java/opensource/bravest/domain/chatList/repository/ChatListRepository.java b/src/main/java/opensource/bravest/domain/chatList/repository/ChatListRepository.java index 45db599..b90efd1 100644 --- a/src/main/java/opensource/bravest/domain/chatList/repository/ChatListRepository.java +++ b/src/main/java/opensource/bravest/domain/chatList/repository/ChatListRepository.java @@ -1,13 +1,12 @@ package opensource.bravest.domain.chatList.repository; +import java.util.List; import opensource.bravest.domain.chatList.entity.ChatList; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import java.util.List; - public interface ChatListRepository extends JpaRepository { - @Query("SELECT c FROM ChatList c WHERE c.room.id = :roomId ORDER BY c.createdAt DESC") - List findAllByRoomId(Long roomId); -} \ No newline at end of file + @Query("SELECT c FROM ChatList c WHERE c.room.id = :roomId ORDER BY c.createdAt DESC") + List findAllByRoomId(Long roomId); +} diff --git a/src/main/java/opensource/bravest/domain/chatList/service/ChatListService.java b/src/main/java/opensource/bravest/domain/chatList/service/ChatListService.java index 6050c7e..e375a13 100644 --- a/src/main/java/opensource/bravest/domain/chatList/service/ChatListService.java +++ b/src/main/java/opensource/bravest/domain/chatList/service/ChatListService.java @@ -1,12 +1,14 @@ package opensource.bravest.domain.chatList.service; +import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListCreateRequest; import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListResponse; import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListUpdateRequest; -import static opensource.bravest.domain.chatList.dto.ChatListDto.ChatListCreateRequest; import static opensource.bravest.global.apiPayload.code.status.ErrorStatus._CHATLIST_NOT_FOUND; import static opensource.bravest.global.apiPayload.code.status.ErrorStatus._CHATROOM_NOT_FOUND; import static opensource.bravest.global.apiPayload.code.status.ErrorStatus._USER_NOT_FOUND; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import opensource.bravest.domain.chatList.entity.ChatList; import opensource.bravest.domain.chatList.repository.ChatListRepository; @@ -18,64 +20,60 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class ChatListService { - private final ChatListRepository chatListRepository; - private final AnonymousRoomRepository anonymousRoomRepository; - private final AnonymousProfileRepository anonymousProfileRepository; + private final ChatListRepository chatListRepository; + private final AnonymousRoomRepository anonymousRoomRepository; + private final AnonymousProfileRepository anonymousProfileRepository; - @Transactional - public ChatListResponse createChatList(ChatListCreateRequest request) { - AnonymousRoom room = anonymousRoomRepository.findById(request.getRoomId()) + @Transactional + public ChatListResponse createChatList(ChatListCreateRequest request) { + AnonymousRoom room = + anonymousRoomRepository + .findById(request.getRoomId()) .orElseThrow(() -> new CustomException(_CHATROOM_NOT_FOUND)); - AnonymousProfile profile = anonymousProfileRepository.findById(request.getRegisteredBy()) + AnonymousProfile profile = + anonymousProfileRepository + .findById(request.getRegisteredBy()) .orElseThrow(() -> new CustomException(_USER_NOT_FOUND)); - ChatList chatList = ChatList.builder() - .room(room) - .registeredBy(profile) - .content(request.getContent()) - .build(); + ChatList chatList = + ChatList.builder().room(room).registeredBy(profile).content(request.getContent()).build(); - ChatList savedList = chatListRepository.save(chatList); - return ChatListResponse.fromEntity(savedList); - } + ChatList savedList = chatListRepository.save(chatList); + return ChatListResponse.fromEntity(savedList); + } - public List getChatListsByRoomId(Long roomId) { - List chatLists = chatListRepository.findAllByRoomId(roomId); - return chatLists.stream() - .map(ChatListResponse::fromEntity) - .collect(Collectors.toList()); - } + public List getChatListsByRoomId(Long roomId) { + List chatLists = chatListRepository.findAllByRoomId(roomId); + return chatLists.stream().map(ChatListResponse::fromEntity).collect(Collectors.toList()); + } - public ChatListResponse getChatListById(Long id) { - ChatList chatList = chatListRepository.findById(id) - .orElseThrow(() -> new CustomException(_CHATLIST_NOT_FOUND)); - return ChatListResponse.fromEntity(chatList); - } + public ChatListResponse getChatListById(Long id) { + ChatList chatList = + chatListRepository.findById(id).orElseThrow(() -> new CustomException(_CHATLIST_NOT_FOUND)); + return ChatListResponse.fromEntity(chatList); + } - @Transactional - public ChatListResponse updateChatList(Long id, ChatListUpdateRequest request) { - ChatList chatList = chatListRepository.findById(id) - .orElseThrow(() -> new CustomException(_CHATLIST_NOT_FOUND)); + @Transactional + public ChatListResponse updateChatList(Long id, ChatListUpdateRequest request) { + ChatList chatList = + chatListRepository.findById(id).orElseThrow(() -> new CustomException(_CHATLIST_NOT_FOUND)); - chatList.updateContent(request.getContent()); + chatList.updateContent(request.getContent()); - return ChatListResponse.fromEntity(chatList); - } + return ChatListResponse.fromEntity(chatList); + } - @Transactional - public void deleteChatList(Long id) { - if (!chatListRepository.existsById(id)) { - throw new CustomException(_CHATLIST_NOT_FOUND); - } - chatListRepository.deleteById(id); + @Transactional + public void deleteChatList(Long id) { + if (!chatListRepository.existsById(id)) { + throw new CustomException(_CHATLIST_NOT_FOUND); } -} \ No newline at end of file + chatListRepository.deleteById(id); + } +} diff --git a/src/main/java/opensource/bravest/domain/message/controller/ChatMessageController.java b/src/main/java/opensource/bravest/domain/message/controller/ChatMessageController.java index 7c7bee1..609e347 100644 --- a/src/main/java/opensource/bravest/domain/message/controller/ChatMessageController.java +++ b/src/main/java/opensource/bravest/domain/message/controller/ChatMessageController.java @@ -1,36 +1,33 @@ package opensource.bravest.domain.message.controller; +import static opensource.bravest.domain.message.dto.MessageDto.MessageRequest; +import static opensource.bravest.domain.message.dto.MessageDto.MessageResponse; + +import java.security.Principal; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import opensource.bravest.domain.message.service.ChatMessageService; import opensource.bravest.global.apiPayload.ApiResponse; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.stereotype.Controller; import org.springframework.messaging.simp.SimpMessagingTemplate; - -import java.security.Principal; - -import static opensource.bravest.domain.message.dto.MessageDto.MessageRequest; -import static opensource.bravest.domain.message.dto.MessageDto.MessageResponse; +import org.springframework.stereotype.Controller; @Slf4j @Controller @RequiredArgsConstructor public class ChatMessageController { - private final ChatMessageService chatMessageService; - private final SimpMessagingTemplate messagingTemplate; + private final ChatMessageService chatMessageService; + private final SimpMessagingTemplate messagingTemplate; - @MessageMapping("/send") - @SendTo("/subs/chat-rooms") - public void receiveMessage(MessageRequest request, Principal principal) { - Long id = Long.parseLong(principal.getName()); - MessageResponse response = chatMessageService.send(request, id); + @MessageMapping("/send") + @SendTo("/subs/chat-rooms") + public void receiveMessage(MessageRequest request, Principal principal) { + Long id = Long.parseLong(principal.getName()); + MessageResponse response = chatMessageService.send(request, id); - // ํŠน์ • ์ฑ„ํŒ…๋ฐฉ ๊ตฌ๋…์ž๋“ค์—๊ฒŒ ๋ฉ”์‹œ์ง€ ์ „์†ก - messagingTemplate.convertAndSend( - "/subs/chat-rooms/" + request.getChatRoomId(), - ApiResponse.onSuccess(response) - ); - } + // ํŠน์ • ์ฑ„ํŒ…๋ฐฉ ๊ตฌ๋…์ž๋“ค์—๊ฒŒ ๋ฉ”์‹œ์ง€ ์ „์†ก + messagingTemplate.convertAndSend( + "/subs/chat-rooms/" + request.getChatRoomId(), ApiResponse.onSuccess(response)); + } } diff --git a/src/main/java/opensource/bravest/domain/message/dto/MessageDto.java b/src/main/java/opensource/bravest/domain/message/dto/MessageDto.java index 1660bed..72b32c5 100644 --- a/src/main/java/opensource/bravest/domain/message/dto/MessageDto.java +++ b/src/main/java/opensource/bravest/domain/message/dto/MessageDto.java @@ -1,44 +1,42 @@ package opensource.bravest.domain.message.dto; +import java.time.LocalDateTime; import lombok.Getter; import lombok.RequiredArgsConstructor; import opensource.bravest.domain.message.entity.ChatMessage; -import java.time.LocalDateTime; - public class MessageDto { - @Getter - public static class SendMessageRequest { - private String content; - } - - @Getter - @RequiredArgsConstructor - public static class MessageResponse { - private final String senderName; // ์ต๋ช… ๋‹‰๋„ค์ž„ - private final String content; - private final LocalDateTime createdAt; - - public static MessageResponse from(ChatMessage chatMessage) { - return new MessageResponse( - chatMessage.getSender().getAnonymousName(), - chatMessage.getContent(), - chatMessage.getCreatedAt() - ); - } - } - - @Getter - @RequiredArgsConstructor - public static class MessageRequest { - private final Long chatRoomId; - private final String content; - } - - @Getter - @RequiredArgsConstructor - public static class ChatReadRequest { - private final Long chatRoomId; + @Getter + public static class SendMessageRequest { + private String content; + } + + @Getter + @RequiredArgsConstructor + public static class MessageResponse { + private final String senderName; // ์ต๋ช… ๋‹‰๋„ค์ž„ + private final String content; + private final LocalDateTime createdAt; + + public static MessageResponse from(ChatMessage chatMessage) { + return new MessageResponse( + chatMessage.getSender().getAnonymousName(), + chatMessage.getContent(), + chatMessage.getCreatedAt()); } + } + + @Getter + @RequiredArgsConstructor + public static class MessageRequest { + private final Long chatRoomId; + private final String content; + } + + @Getter + @RequiredArgsConstructor + public static class ChatReadRequest { + private final Long chatRoomId; + } } diff --git a/src/main/java/opensource/bravest/domain/message/entity/ChatMessage.java b/src/main/java/opensource/bravest/domain/message/entity/ChatMessage.java index e70ba58..2ab7d59 100644 --- a/src/main/java/opensource/bravest/domain/message/entity/ChatMessage.java +++ b/src/main/java/opensource/bravest/domain/message/entity/ChatMessage.java @@ -1,12 +1,11 @@ package opensource.bravest.domain.message.entity; import jakarta.persistence.*; +import java.time.LocalDateTime; import lombok.*; import opensource.bravest.domain.profile.entity.AnonymousProfile; import opensource.bravest.domain.room.entity.AnonymousRoom; -import java.time.LocalDateTime; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -14,22 +13,22 @@ @Builder public class ChatMessage { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - // ์–ด๋А ๋ฐฉ์˜ ๋ฉ”์‹œ์ง€์ธ์ง€ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "room_id", nullable = false) - private AnonymousRoom room; + // ์–ด๋А ๋ฐฉ์˜ ๋ฉ”์‹œ์ง€์ธ์ง€ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", nullable = false) + private AnonymousRoom room; - // ๋ˆ„๊ฐ€ ๋ณด๋ƒˆ๋Š”์ง€ (์ต๋ช… ํ”„๋กœํ•„ ๊ธฐ์ค€) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "anonymous_profile_id", nullable = false) - private AnonymousProfile sender; + // ๋ˆ„๊ฐ€ ๋ณด๋ƒˆ๋Š”์ง€ (์ต๋ช… ํ”„๋กœํ•„ ๊ธฐ์ค€) + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anonymous_profile_id", nullable = false) + private AnonymousProfile sender; - @Column(nullable = false, length = 1000) - private String content; + @Column(nullable = false, length = 1000) + private String content; - private LocalDateTime createdAt; + private LocalDateTime createdAt; } diff --git a/src/main/java/opensource/bravest/domain/message/repository/ChatMessageRepository.java b/src/main/java/opensource/bravest/domain/message/repository/ChatMessageRepository.java index 2866c8b..eaae205 100644 --- a/src/main/java/opensource/bravest/domain/message/repository/ChatMessageRepository.java +++ b/src/main/java/opensource/bravest/domain/message/repository/ChatMessageRepository.java @@ -1,14 +1,12 @@ package opensource.bravest.domain.message.repository; - +import java.util.List; import opensource.bravest.domain.message.entity.ChatMessage; import opensource.bravest.domain.room.entity.AnonymousRoom; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; - public interface ChatMessageRepository extends JpaRepository { - // ๋ฐฉ ๊ธฐ์ค€์œผ๋กœ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก - List findByRoomOrderByCreatedAtAsc(AnonymousRoom room); + // ๋ฐฉ ๊ธฐ์ค€์œผ๋กœ ์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก + List findByRoomOrderByCreatedAtAsc(AnonymousRoom room); } diff --git a/src/main/java/opensource/bravest/domain/message/service/ChatMessageService.java b/src/main/java/opensource/bravest/domain/message/service/ChatMessageService.java index b0db7ef..57808a6 100644 --- a/src/main/java/opensource/bravest/domain/message/service/ChatMessageService.java +++ b/src/main/java/opensource/bravest/domain/message/service/ChatMessageService.java @@ -1,13 +1,12 @@ package opensource.bravest.domain.message.service; -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import static opensource.bravest.domain.message.dto.MessageDto.MessageResponse; - import static opensource.bravest.domain.message.dto.MessageDto.MessageRequest; +import static opensource.bravest.domain.message.dto.MessageDto.MessageResponse; import static opensource.bravest.global.apiPayload.code.status.ErrorStatus._CHATROOM_NOT_FOUND; import static opensource.bravest.global.apiPayload.code.status.ErrorStatus._USER_NOT_FOUND; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; import opensource.bravest.domain.message.entity.ChatMessage; import opensource.bravest.domain.message.repository.ChatMessageRepository; import opensource.bravest.domain.profile.entity.AnonymousProfile; @@ -17,45 +16,45 @@ import opensource.bravest.global.exception.CustomException; import org.springframework.stereotype.Service; -import java.util.Objects; - @Service @Transactional @RequiredArgsConstructor public class ChatMessageService { - private final AnonymousProfileRepository memberRepository; - private final AnonymousRoomRepository chatRoomRepository; - private final ChatMessageRepository chatMessageRepository; + private final AnonymousProfileRepository memberRepository; + private final AnonymousRoomRepository chatRoomRepository; + private final ChatMessageRepository chatMessageRepository; - // ๋ฉ”์‹œ์ง€ ์ „์†ก - public MessageResponse send(MessageRequest request, Long id) { - AnonymousProfile sender = memberRepository.findById(id) - .orElseThrow(() -> new CustomException(_USER_NOT_FOUND)); + // ๋ฉ”์‹œ์ง€ ์ „์†ก + public MessageResponse send(MessageRequest request, Long id) { + AnonymousProfile sender = + memberRepository.findById(id).orElseThrow(() -> new CustomException(_USER_NOT_FOUND)); - AnonymousRoom chatRoom = chatRoomRepository.findById(request.getChatRoomId()) + AnonymousRoom chatRoom = + chatRoomRepository + .findById(request.getChatRoomId()) .orElseThrow(() -> new CustomException(_CHATROOM_NOT_FOUND)); - ChatMessage chatMessage = ChatMessage.builder() - .room(chatRoom) - .sender(sender) - .content(request.getContent()) - .build(); + ChatMessage chatMessage = + ChatMessage.builder().room(chatRoom).sender(sender).content(request.getContent()).build(); - chatMessageRepository.save(chatMessage); + chatMessageRepository.save(chatMessage); - return MessageResponse.from(chatMessage); - } + return MessageResponse.from(chatMessage); + } - @Transactional - public void readMessages(Long chatRoomId, Long memberId) { - AnonymousRoom chatRoom = chatRoomRepository.findById(chatRoomId) + @Transactional + public void readMessages(Long chatRoomId, Long memberId) { + AnonymousRoom chatRoom = + chatRoomRepository + .findById(chatRoomId) .orElseThrow(() -> new CustomException(_CHATROOM_NOT_FOUND)); -// if (!Objects.equals(chatRoom.getMember1().getId(), memberId) && !Objects.equals(chatRoom.getMember2().getId(), -// memberId)) { -// throw new BaseException(ChatExceptionType.CHAT_ROOM_ACCESS_DENIED); -// } -// messageReceiptRepository.bulkUpdateStatusToRead(chatRoomId, memberId); - } -} \ No newline at end of file + // if (!Objects.equals(chatRoom.getMember1().getId(), memberId) && + // !Objects.equals(chatRoom.getMember2().getId(), + // memberId)) { + // throw new BaseException(ChatExceptionType.CHAT_ROOM_ACCESS_DENIED); + // } + // messageReceiptRepository.bulkUpdateStatusToRead(chatRoomId, memberId); + } +} diff --git a/src/main/java/opensource/bravest/domain/profile/controller/AnonymousProfileController.java b/src/main/java/opensource/bravest/domain/profile/controller/AnonymousProfileController.java index 26a5d5d..6b2923f 100644 --- a/src/main/java/opensource/bravest/domain/profile/controller/AnonymousProfileController.java +++ b/src/main/java/opensource/bravest/domain/profile/controller/AnonymousProfileController.java @@ -10,30 +10,26 @@ import opensource.bravest.global.apiPayload.code.status.SuccessStatus; import org.springframework.web.bind.annotation.*; - @RestController @RequiredArgsConstructor @RequestMapping("/anonymous-profiles") public class AnonymousProfileController { - private final AnonymousProfileService anonymousProfileService; - + private final AnonymousProfileService anonymousProfileService; - @Operation(summary = "์ต๋ช… ํ”„๋กœํ•„ ์ƒ์„ฑ", description = "ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์— ๋Œ€ํ•œ ์ƒˆ๋กœ์šด ์ต๋ช… ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") - @PostMapping("/rooms/{roomId}") - public ApiResponse createAnonymousProfile( - @PathVariable Long roomId, - @RequestBody CreateAnonymousProfileRequest request - ) { - AnonymousProfile profile = anonymousProfileService.createAnonymousProfile(roomId, request); - AnonymousProfileResponse response = AnonymousProfileResponse.from(profile); - return ApiResponse.of(SuccessStatus._CREATED, SuccessStatus._CREATED.getMessage(), response); - } + @Operation(summary = "์ต๋ช… ํ”„๋กœํ•„ ์ƒ์„ฑ", description = "ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์— ๋Œ€ํ•œ ์ƒˆ๋กœ์šด ์ต๋ช… ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") + @PostMapping("/rooms/{roomId}") + public ApiResponse createAnonymousProfile( + @PathVariable Long roomId, @RequestBody CreateAnonymousProfileRequest request) { + AnonymousProfile profile = anonymousProfileService.createAnonymousProfile(roomId, request); + AnonymousProfileResponse response = AnonymousProfileResponse.from(profile); + return ApiResponse.of(SuccessStatus._CREATED, SuccessStatus._CREATED.getMessage(), response); + } - @DeleteMapping("/{profileId}") - @Operation(summary = "์ต๋ช… ํ”„๋กœํ•„ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ์ต๋ช… ํ”„๋กœํ•„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse deleteAnonymousProfile(@PathVariable Long profileId) { - anonymousProfileService.deleteAnonymousProfile(profileId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); - } + @DeleteMapping("/{profileId}") + @Operation(summary = "์ต๋ช… ํ”„๋กœํ•„ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ์ต๋ช… ํ”„๋กœํ•„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse deleteAnonymousProfile(@PathVariable Long profileId) { + anonymousProfileService.deleteAnonymousProfile(profileId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); + } } diff --git a/src/main/java/opensource/bravest/domain/profile/dto/AnonymousProfileResponse.java b/src/main/java/opensource/bravest/domain/profile/dto/AnonymousProfileResponse.java index e7d8c8c..95165ea 100644 --- a/src/main/java/opensource/bravest/domain/profile/dto/AnonymousProfileResponse.java +++ b/src/main/java/opensource/bravest/domain/profile/dto/AnonymousProfileResponse.java @@ -7,16 +7,17 @@ @Getter @Builder public class AnonymousProfileResponse { - private Long id; - private Long roomId; - private String nickname; - // ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ + private Long id; + private Long roomId; + private String nickname; - public static AnonymousProfileResponse from(AnonymousProfile profile) { - return AnonymousProfileResponse.builder() - .id(profile.getId()) - .roomId(profile.getRoom().getId()) - .nickname(profile.getAnonymousName()) - .build(); - } + // ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ + + public static AnonymousProfileResponse from(AnonymousProfile profile) { + return AnonymousProfileResponse.builder() + .id(profile.getId()) + .roomId(profile.getRoom().getId()) + .nickname(profile.getAnonymousName()) + .build(); + } } diff --git a/src/main/java/opensource/bravest/domain/profile/dto/CreateAnonymousProfileRequest.java b/src/main/java/opensource/bravest/domain/profile/dto/CreateAnonymousProfileRequest.java index 4b06c9e..9c43be9 100644 --- a/src/main/java/opensource/bravest/domain/profile/dto/CreateAnonymousProfileRequest.java +++ b/src/main/java/opensource/bravest/domain/profile/dto/CreateAnonymousProfileRequest.java @@ -6,6 +6,6 @@ @Getter @NoArgsConstructor public class CreateAnonymousProfileRequest { - private Long realUserId; - private String anonymousName; + private Long realUserId; + private String anonymousName; } diff --git a/src/main/java/opensource/bravest/domain/profile/entity/AnonymousProfile.java b/src/main/java/opensource/bravest/domain/profile/entity/AnonymousProfile.java index 954be53..fe773e5 100644 --- a/src/main/java/opensource/bravest/domain/profile/entity/AnonymousProfile.java +++ b/src/main/java/opensource/bravest/domain/profile/entity/AnonymousProfile.java @@ -11,20 +11,20 @@ @Builder public class AnonymousProfile { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - // ์–ด๋–ค ๋ฐฉ์— ์†ํ•œ ์ต๋ช… ํ”„๋กœํ•„์ธ์ง€ - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "room_id", nullable = false) - private AnonymousRoom room; + // ์–ด๋–ค ๋ฐฉ์— ์†ํ•œ ์ต๋ช… ํ”„๋กœํ•„์ธ์ง€ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", nullable = false) + private AnonymousRoom room; - // ์‹ค์ œ ์œ ์ € PK (User ํ…Œ์ด๋ธ” ์—†๋‹ค๋ฉด JWT์˜ userId ๊ธฐ์ค€์œผ๋กœ) - @Column(nullable = false) - private Long realUserId; + // ์‹ค์ œ ์œ ์ € PK (User ํ…Œ์ด๋ธ” ์—†๋‹ค๋ฉด JWT์˜ userId ๊ธฐ์ค€์œผ๋กœ) + @Column(nullable = false) + private Long realUserId; - // ๋ฐฉ ์•ˆ์—์„œ ๋ณด์—ฌ์ค„ ์ต๋ช… ๋‹‰๋„ค์ž„ (์˜ˆ: BlueTiger12) - @Column(nullable = false, length = 50) - private String anonymousName; -} \ No newline at end of file + // ๋ฐฉ ์•ˆ์—์„œ ๋ณด์—ฌ์ค„ ์ต๋ช… ๋‹‰๋„ค์ž„ (์˜ˆ: BlueTiger12) + @Column(nullable = false, length = 50) + private String anonymousName; +} diff --git a/src/main/java/opensource/bravest/domain/profile/repository/AnonymousProfileRepository.java b/src/main/java/opensource/bravest/domain/profile/repository/AnonymousProfileRepository.java index bdef7a7..88bbdbe 100644 --- a/src/main/java/opensource/bravest/domain/profile/repository/AnonymousProfileRepository.java +++ b/src/main/java/opensource/bravest/domain/profile/repository/AnonymousProfileRepository.java @@ -1,13 +1,12 @@ package opensource.bravest.domain.profile.repository; +import java.util.Optional; import opensource.bravest.domain.profile.entity.AnonymousProfile; import opensource.bravest.domain.room.entity.AnonymousRoom; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface AnonymousProfileRepository extends JpaRepository { - // ๊ฐ™์€ ๋ฐฉ + ๊ฐ™์€ ์‹ค์ œ ์œ ์ €๋ผ๋ฉด ์ต๋ช… ํ”„๋กœํ•„ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉ - Optional findByRoomAndRealUserId(AnonymousRoom room, Long realUserId); -} \ No newline at end of file + // ๊ฐ™์€ ๋ฐฉ + ๊ฐ™์€ ์‹ค์ œ ์œ ์ €๋ผ๋ฉด ์ต๋ช… ํ”„๋กœํ•„ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉ + Optional findByRoomAndRealUserId(AnonymousRoom room, Long realUserId); +} diff --git a/src/main/java/opensource/bravest/domain/profile/service/AnonymousProfileService.java b/src/main/java/opensource/bravest/domain/profile/service/AnonymousProfileService.java index 74326d5..0217c7d 100644 --- a/src/main/java/opensource/bravest/domain/profile/service/AnonymousProfileService.java +++ b/src/main/java/opensource/bravest/domain/profile/service/AnonymousProfileService.java @@ -1,5 +1,6 @@ package opensource.bravest.domain.profile.service; +import java.util.Optional; import lombok.RequiredArgsConstructor; import opensource.bravest.domain.profile.dto.CreateAnonymousProfileRequest; import opensource.bravest.domain.profile.entity.AnonymousProfile; @@ -9,41 +10,44 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class AnonymousProfileService { - private final AnonymousProfileRepository anonymousProfileRepository; - private final AnonymousRoomRepository anonymousRoomRepository; - - @Transactional - public AnonymousProfile createAnonymousProfile(Long roomId, CreateAnonymousProfileRequest request) { - AnonymousRoom room = anonymousRoomRepository.findById(roomId) - .orElseThrow(() -> new RuntimeException("๋ฐฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ.๋ฟก")); - - // ์ค‘๋ณต ํ”„๋กœํ•„ ์ฒดํฌ - Optional existingProfile = anonymousProfileRepository.findByRoomAndRealUserId(room, request.getRealUserId()); - if (existingProfile.isPresent()) { - throw new RuntimeException("์ด๋ฏธ ๋ฐฉ์— ์กด์žฌํ•˜๋Š” ์œ ์ €์ž„. ๋‹ค๋ฅธ๊ฑธ๋กœ ์ ‘์†ํ•˜์…ˆ."); - } + private final AnonymousProfileRepository anonymousProfileRepository; + private final AnonymousRoomRepository anonymousRoomRepository; + + @Transactional + public AnonymousProfile createAnonymousProfile( + Long roomId, CreateAnonymousProfileRequest request) { + AnonymousRoom room = + anonymousRoomRepository + .findById(roomId) + .orElseThrow(() -> new RuntimeException("๋ฐฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ.๋ฟก")); + + // ์ค‘๋ณต ํ”„๋กœํ•„ ์ฒดํฌ + Optional existingProfile = + anonymousProfileRepository.findByRoomAndRealUserId(room, request.getRealUserId()); + if (existingProfile.isPresent()) { + throw new RuntimeException("์ด๋ฏธ ๋ฐฉ์— ์กด์žฌํ•˜๋Š” ์œ ์ €์ž„. ๋‹ค๋ฅธ๊ฑธ๋กœ ์ ‘์†ํ•˜์…ˆ."); + } - AnonymousProfile newProfile = AnonymousProfile.builder() - .room(room) - .realUserId(request.getRealUserId()) - .anonymousName(request.getAnonymousName()) - .build(); + AnonymousProfile newProfile = + AnonymousProfile.builder() + .room(room) + .realUserId(request.getRealUserId()) + .anonymousName(request.getAnonymousName()) + .build(); - return anonymousProfileRepository.save(newProfile); - } + return anonymousProfileRepository.save(newProfile); + } - @Transactional - public void deleteAnonymousProfile(Long profileId) { - if (!anonymousProfileRepository.existsById(profileId)) { - throw new RuntimeException("์—†๋Š” ์‚ฌ์šฉ์ž์ž„. ๋„ˆ~ ๋ˆ„๊ตฌ์•ผ!"); - } - anonymousProfileRepository.deleteById(profileId); + @Transactional + public void deleteAnonymousProfile(Long profileId) { + if (!anonymousProfileRepository.existsById(profileId)) { + throw new RuntimeException("์—†๋Š” ์‚ฌ์šฉ์ž์ž„. ๋„ˆ~ ๋ˆ„๊ตฌ์•ผ!"); } + anonymousProfileRepository.deleteById(profileId); + } } diff --git a/src/main/java/opensource/bravest/domain/room/controller/RoomController.java b/src/main/java/opensource/bravest/domain/room/controller/RoomController.java index b331461..3a12474 100644 --- a/src/main/java/opensource/bravest/domain/room/controller/RoomController.java +++ b/src/main/java/opensource/bravest/domain/room/controller/RoomController.java @@ -14,67 +14,81 @@ @RequestMapping("/rooms") public class RoomController { - private final RoomService roomService; + private final RoomService roomService; - @PostMapping - @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ", description = "์ƒˆ๋กœ์šด ์ฑ„ํŒ…๋ฐฉ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse createRoom(@RequestBody RoomDto.CreateRoomRequest request) { - AnonymousRoom room = roomService.createRoom(request); - return ApiResponse.of(SuccessStatus._CREATED, SuccessStatus._CREATED.getMessage(), RoomDto.RoomResponse.builder() - .id(room.getId()) - .roomCode(room.getRoomCode()) - .title(room.getTitle()) - .createdAt(room.getCreatedAt()) - .build()); - } + @PostMapping + @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ", description = "์ƒˆ๋กœ์šด ์ฑ„ํŒ…๋ฐฉ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse createRoom( + @RequestBody RoomDto.CreateRoomRequest request) { + AnonymousRoom room = roomService.createRoom(request); + return ApiResponse.of( + SuccessStatus._CREATED, + SuccessStatus._CREATED.getMessage(), + RoomDto.RoomResponse.builder() + .id(room.getId()) + .roomCode(room.getRoomCode()) + .title(room.getTitle()) + .createdAt(room.getCreatedAt()) + .build()); + } - @GetMapping("/{roomId}") - @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse getRoom(@PathVariable Long roomId) { - AnonymousRoom room = roomService.getRoom(roomId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), RoomDto.RoomResponse.builder() - .id(room.getId()) - .roomCode(room.getRoomCode()) - .title(room.getTitle()) - .createdAt(room.getCreatedAt()) - .build()); - } + @GetMapping("/{roomId}") + @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse getRoom(@PathVariable Long roomId) { + AnonymousRoom room = roomService.getRoom(roomId); + return ApiResponse.of( + SuccessStatus._OK, + SuccessStatus._OK.getMessage(), + RoomDto.RoomResponse.builder() + .id(room.getId()) + .roomCode(room.getRoomCode()) + .title(room.getTitle()) + .createdAt(room.getCreatedAt()) + .build()); + } - @PutMapping("/{roomId}") - @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์ •๋ณด ์ˆ˜์ •", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse updateRoom(@PathVariable Long roomId, @RequestBody RoomDto.UpdateRoomRequest request) { - AnonymousRoom room = roomService.updateRoom(roomId, request); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), RoomDto.RoomResponse.builder() - .id(room.getId()) - .roomCode(room.getRoomCode()) - .title(room.getTitle()) - .createdAt(room.getCreatedAt()) - .build()); - } + @PutMapping("/{roomId}") + @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์ •๋ณด ์ˆ˜์ •", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse updateRoom( + @PathVariable Long roomId, @RequestBody RoomDto.UpdateRoomRequest request) { + AnonymousRoom room = roomService.updateRoom(roomId, request); + return ApiResponse.of( + SuccessStatus._OK, + SuccessStatus._OK.getMessage(), + RoomDto.RoomResponse.builder() + .id(room.getId()) + .roomCode(room.getRoomCode()) + .title(room.getTitle()) + .createdAt(room.getCreatedAt()) + .build()); + } - @DeleteMapping("/{roomId}") - @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse deleteRoom(@PathVariable Long roomId) { - roomService.deleteRoom(roomId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); - } + @DeleteMapping("/{roomId}") + @Operation(summary = "์ฑ„ํŒ…๋ฐฉ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse deleteRoom(@PathVariable Long roomId) { + roomService.deleteRoom(roomId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); + } - @GetMapping("/{roomId}/invite-code") - @Operation(summary = "์ดˆ๋Œ€ ์ฝ”๋“œ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse getInviteCode(@PathVariable Long roomId) { - String inviteCode = roomService.getInviteCode(roomId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), inviteCode); - } + @GetMapping("/{roomId}/invite-code") + @Operation(summary = "์ดˆ๋Œ€ ์ฝ”๋“œ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์˜ ์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse getInviteCode(@PathVariable Long roomId) { + String inviteCode = roomService.getInviteCode(roomId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), inviteCode); + } - @PostMapping("/join") - @Operation(summary = "์ดˆ๋Œ€ ์ฝ”๋“œ๋กœ ์ฑ„ํŒ…๋ฐฉ ์ฐธ์—ฌ", description = "์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์— ์ฐธ์—ฌํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse joinRoom(@RequestBody RoomDto.JoinRoomRequest request) { - AnonymousRoom room = roomService.joinRoom(request.getRoomCode()); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), RoomDto.RoomResponse.builder() - .id(room.getId()) - .roomCode(room.getRoomCode()) - .title(room.getTitle()) - .createdAt(room.getCreatedAt()) - .build()); - } + @PostMapping("/join") + @Operation(summary = "์ดˆ๋Œ€ ์ฝ”๋“œ๋กœ ์ฑ„ํŒ…๋ฐฉ ์ฐธ์—ฌ", description = "์ดˆ๋Œ€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์ฑ„ํŒ…๋ฐฉ์— ์ฐธ์—ฌํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse joinRoom(@RequestBody RoomDto.JoinRoomRequest request) { + AnonymousRoom room = roomService.joinRoom(request.getRoomCode()); + return ApiResponse.of( + SuccessStatus._OK, + SuccessStatus._OK.getMessage(), + RoomDto.RoomResponse.builder() + .id(room.getId()) + .roomCode(room.getRoomCode()) + .title(room.getTitle()) + .createdAt(room.getCreatedAt()) + .build()); + } } diff --git a/src/main/java/opensource/bravest/domain/room/dto/RoomDto.java b/src/main/java/opensource/bravest/domain/room/dto/RoomDto.java index e1175e2..8655c0c 100644 --- a/src/main/java/opensource/bravest/domain/room/dto/RoomDto.java +++ b/src/main/java/opensource/bravest/domain/room/dto/RoomDto.java @@ -1,40 +1,39 @@ package opensource.bravest.domain.room.dto; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - public class RoomDto { - @Getter - @NoArgsConstructor - public static class CreateRoomRequest { - private String title; - } + @Getter + @NoArgsConstructor + public static class CreateRoomRequest { + private String title; + } - @Getter - @NoArgsConstructor - public static class UpdateRoomRequest { - private String title; - } + @Getter + @NoArgsConstructor + public static class UpdateRoomRequest { + private String title; + } - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class RoomResponse { - private Long id; - private String roomCode; - private String title; - private LocalDateTime createdAt; - } + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class RoomResponse { + private Long id; + private String roomCode; + private String title; + private LocalDateTime createdAt; + } - @Getter - @NoArgsConstructor - public static class JoinRoomRequest { - private String roomCode; - } + @Getter + @NoArgsConstructor + public static class JoinRoomRequest { + private String roomCode; + } } diff --git a/src/main/java/opensource/bravest/domain/room/entity/AnonymousRoom.java b/src/main/java/opensource/bravest/domain/room/entity/AnonymousRoom.java index aae4aa7..3be35e9 100644 --- a/src/main/java/opensource/bravest/domain/room/entity/AnonymousRoom.java +++ b/src/main/java/opensource/bravest/domain/room/entity/AnonymousRoom.java @@ -1,11 +1,11 @@ package opensource.bravest.domain.room.entity; -import jakarta.persistence.*; -import lombok.*; -import opensource.bravest.domain.profile.entity.AnonymousProfile; +import jakarta.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import lombok.*; +import opensource.bravest.domain.profile.entity.AnonymousProfile; @Entity @Getter @@ -14,26 +14,25 @@ @Builder public class AnonymousRoom { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - // ์นœ๊ตฌ๋“ค์—๊ฒŒ ๊ณต์œ ํ•˜๋Š” ์ฝ”๋“œ (์˜ˆ: ABC123) - @Column(nullable = false, unique = true, length = 20) - private String roomCode; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - // ๋ฐฉ ์ œ๋ชฉ (์„ ํƒ) - @Column(nullable = false, length = 100) - private String title; + // ์นœ๊ตฌ๋“ค์—๊ฒŒ ๊ณต์œ ํ•˜๋Š” ์ฝ”๋“œ (์˜ˆ: ABC123) + @Column(nullable = false, unique = true, length = 20) + private String roomCode; - @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true) - @Builder.Default - private List profiles = new ArrayList<>(); + // ๋ฐฉ ์ œ๋ชฉ (์„ ํƒ) + @Column(nullable = false, length = 100) + private String title; + @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + private List profiles = new ArrayList<>(); - private LocalDateTime createdAt; + private LocalDateTime createdAt; - public void updateTitle(String title) { - this.title = title; - } -} \ No newline at end of file + public void updateTitle(String title) { + this.title = title; + } +} diff --git a/src/main/java/opensource/bravest/domain/room/repository/AnonymousRoomRepository.java b/src/main/java/opensource/bravest/domain/room/repository/AnonymousRoomRepository.java index 51b518e..a900571 100644 --- a/src/main/java/opensource/bravest/domain/room/repository/AnonymousRoomRepository.java +++ b/src/main/java/opensource/bravest/domain/room/repository/AnonymousRoomRepository.java @@ -1,13 +1,12 @@ package opensource.bravest.domain.room.repository; +import java.util.Optional; import opensource.bravest.domain.room.entity.AnonymousRoom; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface AnonymousRoomRepository extends JpaRepository { - Optional findByRoomCode(String roomCode); + Optional findByRoomCode(String roomCode); - boolean existsByRoomCode(String roomCode); -} \ No newline at end of file + boolean existsByRoomCode(String roomCode); +} diff --git a/src/main/java/opensource/bravest/domain/room/service/RoomService.java b/src/main/java/opensource/bravest/domain/room/service/RoomService.java index 2ffb4a5..21d1c2c 100644 --- a/src/main/java/opensource/bravest/domain/room/service/RoomService.java +++ b/src/main/java/opensource/bravest/domain/room/service/RoomService.java @@ -1,5 +1,7 @@ package opensource.bravest.domain.room.service; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.RequiredArgsConstructor; import opensource.bravest.domain.room.dto.RoomDto; import opensource.bravest.domain.room.entity.AnonymousRoom; @@ -7,62 +9,62 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.UUID; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class RoomService { - private final AnonymousRoomRepository anonymousRoomRepository; + private final AnonymousRoomRepository anonymousRoomRepository; - @Transactional - public AnonymousRoom createRoom(RoomDto.CreateRoomRequest request) { - String roomCode = generateUniqueRoomCode(); - AnonymousRoom room = AnonymousRoom.builder() - .title(request.getTitle()) - .roomCode(roomCode) - .createdAt(LocalDateTime.now()) - .build(); - return anonymousRoomRepository.save(room); - } + @Transactional + public AnonymousRoom createRoom(RoomDto.CreateRoomRequest request) { + String roomCode = generateUniqueRoomCode(); + AnonymousRoom room = + AnonymousRoom.builder() + .title(request.getTitle()) + .roomCode(roomCode) + .createdAt(LocalDateTime.now()) + .build(); + return anonymousRoomRepository.save(room); + } - public AnonymousRoom getRoom(Long roomId) { - return anonymousRoomRepository.findById(roomId) - .orElseThrow(() -> new RuntimeException("Room not found")); - } + public AnonymousRoom getRoom(Long roomId) { + return anonymousRoomRepository + .findById(roomId) + .orElseThrow(() -> new RuntimeException("Room not found")); + } - @Transactional - public AnonymousRoom updateRoom(Long roomId, RoomDto.UpdateRoomRequest request) { - AnonymousRoom room = getRoom(roomId); - room.updateTitle(request.getTitle()); - return room; - } + @Transactional + public AnonymousRoom updateRoom(Long roomId, RoomDto.UpdateRoomRequest request) { + AnonymousRoom room = getRoom(roomId); + room.updateTitle(request.getTitle()); + return room; + } - @Transactional - public void deleteRoom(Long roomId) { - if (!anonymousRoomRepository.existsById(roomId)) { - throw new RuntimeException("Room not found"); - } - anonymousRoomRepository.deleteById(roomId); + @Transactional + public void deleteRoom(Long roomId) { + if (!anonymousRoomRepository.existsById(roomId)) { + throw new RuntimeException("Room not found"); } + anonymousRoomRepository.deleteById(roomId); + } - public String getInviteCode(Long roomId) { - AnonymousRoom room = getRoom(roomId); - return room.getRoomCode(); - } + public String getInviteCode(Long roomId) { + AnonymousRoom room = getRoom(roomId); + return room.getRoomCode(); + } - public AnonymousRoom joinRoom(String roomCode) { - return anonymousRoomRepository.findByRoomCode(roomCode) - .orElseThrow(() -> new RuntimeException("Room not found with code: " + roomCode)); - } + public AnonymousRoom joinRoom(String roomCode) { + return anonymousRoomRepository + .findByRoomCode(roomCode) + .orElseThrow(() -> new RuntimeException("Room not found with code: " + roomCode)); + } - private String generateUniqueRoomCode() { - String roomCode; - do { - roomCode = UUID.randomUUID().toString().substring(0, 6).toUpperCase(); - } while (anonymousRoomRepository.existsByRoomCode(roomCode)); - return roomCode; - } + private String generateUniqueRoomCode() { + String roomCode; + do { + roomCode = UUID.randomUUID().toString().substring(0, 6).toUpperCase(); + } while (anonymousRoomRepository.existsByRoomCode(roomCode)); + return roomCode; + } } diff --git a/src/main/java/opensource/bravest/domain/vote/controller/VoteController.java b/src/main/java/opensource/bravest/domain/vote/controller/VoteController.java index bf40633..e46aca8 100644 --- a/src/main/java/opensource/bravest/domain/vote/controller/VoteController.java +++ b/src/main/java/opensource/bravest/domain/vote/controller/VoteController.java @@ -14,49 +14,51 @@ @RequestMapping("/votes") public class VoteController { - private final VoteService voteService; - - @PostMapping - @Operation(summary = "ํˆฌํ‘œ ์ƒ์„ฑ", description = "์ƒˆ๋กœ์šด ํˆฌํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse createVote(@RequestBody VoteDto.CreateVoteRequest request) { - Vote vote = voteService.createVote(request); - // The response DTO needs to be built manually - VoteDto.VoteResponse responseDto = voteService.getVoteResult(vote.getId()); - return ApiResponse.of(SuccessStatus._CREATED, SuccessStatus._CREATED.getMessage(), responseDto); - } - - @GetMapping("/{voteId}") - @Operation(summary = "ํˆฌํ‘œ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ํˆฌํ‘œ์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse getVote(@PathVariable Long voteId) { - VoteDto.VoteResponse responseDto = voteService.getVoteResult(voteId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), responseDto); - } - - @PostMapping("/{voteId}/cast") - @Operation(summary = "ํˆฌํ‘œ ์ฐธ์—ฌ", description = "ํŠน์ • ํˆฌํ‘œ ํ•ญ๋ชฉ์— ํˆฌํ‘œํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse castVote(@PathVariable Long voteId, @RequestBody VoteDto.CastVoteRequest request) { - voteService.castVote(voteId, request); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); - } - - @PostMapping("/{voteId}/end") - @Operation(summary = "ํˆฌํ‘œ ์ข…๋ฃŒ", description = "ํŠน์ • ํˆฌํ‘œ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse endVote(@PathVariable Long voteId) { - voteService.endVote(voteId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); - } - - @GetMapping("/{voteId}/result") - @Operation(summary = "ํˆฌํ‘œ ๊ฒฐ๊ณผ ์กฐํšŒ", description = "์ข…๋ฃŒ๋œ ํˆฌํ‘œ์˜ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse getVoteResult(@PathVariable Long voteId) { - VoteDto.VoteResponse responseDto = voteService.getVoteResult(voteId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), responseDto); - } - - @DeleteMapping("/{voteId}") - @Operation(summary = "ํˆฌํ‘œ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ํˆฌํ‘œ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") - public ApiResponse deleteVote(@PathVariable Long voteId) { - voteService.deleteVote(voteId); - return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); - } + private final VoteService voteService; + + @PostMapping + @Operation(summary = "ํˆฌํ‘œ ์ƒ์„ฑ", description = "์ƒˆ๋กœ์šด ํˆฌํ‘œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse createVote( + @RequestBody VoteDto.CreateVoteRequest request) { + Vote vote = voteService.createVote(request); + // The response DTO needs to be built manually + VoteDto.VoteResponse responseDto = voteService.getVoteResult(vote.getId()); + return ApiResponse.of(SuccessStatus._CREATED, SuccessStatus._CREATED.getMessage(), responseDto); + } + + @GetMapping("/{voteId}") + @Operation(summary = "ํˆฌํ‘œ ์กฐํšŒ", description = "ID๋กœ ํŠน์ • ํˆฌํ‘œ์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse getVote(@PathVariable Long voteId) { + VoteDto.VoteResponse responseDto = voteService.getVoteResult(voteId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), responseDto); + } + + @PostMapping("/{voteId}/cast") + @Operation(summary = "ํˆฌํ‘œ ์ฐธ์—ฌ", description = "ํŠน์ • ํˆฌํ‘œ ํ•ญ๋ชฉ์— ํˆฌํ‘œํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse castVote( + @PathVariable Long voteId, @RequestBody VoteDto.CastVoteRequest request) { + voteService.castVote(voteId, request); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); + } + + @PostMapping("/{voteId}/end") + @Operation(summary = "ํˆฌํ‘œ ์ข…๋ฃŒ", description = "ํŠน์ • ํˆฌํ‘œ๋ฅผ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse endVote(@PathVariable Long voteId) { + voteService.endVote(voteId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); + } + + @GetMapping("/{voteId}/result") + @Operation(summary = "ํˆฌํ‘œ ๊ฒฐ๊ณผ ์กฐํšŒ", description = "์ข…๋ฃŒ๋œ ํˆฌํ‘œ์˜ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse getVoteResult(@PathVariable Long voteId) { + VoteDto.VoteResponse responseDto = voteService.getVoteResult(voteId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), responseDto); + } + + @DeleteMapping("/{voteId}") + @Operation(summary = "ํˆฌํ‘œ ์‚ญ์ œ", description = "ID๋กœ ํŠน์ • ํˆฌํ‘œ๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.") + public ApiResponse deleteVote(@PathVariable Long voteId) { + voteService.deleteVote(voteId); + return ApiResponse.of(SuccessStatus._OK, SuccessStatus._OK.getMessage(), null); + } } diff --git a/src/main/java/opensource/bravest/domain/vote/dto/VoteDto.java b/src/main/java/opensource/bravest/domain/vote/dto/VoteDto.java index 77ac32d..83cd3dd 100644 --- a/src/main/java/opensource/bravest/domain/vote/dto/VoteDto.java +++ b/src/main/java/opensource/bravest/domain/vote/dto/VoteDto.java @@ -1,43 +1,42 @@ package opensource.bravest.domain.vote.dto; +import java.time.LocalDateTime; +import java.util.List; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; -import java.util.List; - public class VoteDto { - @Getter - @NoArgsConstructor - public static class CreateVoteRequest { - private Long roomId; - private List messages; - } + @Getter + @NoArgsConstructor + public static class CreateVoteRequest { + private Long roomId; + private List messages; + } - @Getter - @NoArgsConstructor - public static class CastVoteRequest { - private Long voteOptionId; - private Long anonymousProfileId; - } + @Getter + @NoArgsConstructor + public static class CastVoteRequest { + private Long voteOptionId; + private Long anonymousProfileId; + } - @Getter - @Builder - public static class VoteResponse { - private Long id; - private String title; - private boolean isActive; - private LocalDateTime createdAt; - private List options; - } + @Getter + @Builder + public static class VoteResponse { + private Long id; + private String title; + private boolean isActive; + private LocalDateTime createdAt; + private List options; + } - @Getter - @Builder - public static class VoteOptionResponse { - private Long id; - private String messageContent; - private int voteCount; - } + @Getter + @Builder + public static class VoteOptionResponse { + private Long id; + private String messageContent; + private int voteCount; + } } diff --git a/src/main/java/opensource/bravest/domain/vote/entity/UserVote.java b/src/main/java/opensource/bravest/domain/vote/entity/UserVote.java index 2b71b82..f88a73b 100644 --- a/src/main/java/opensource/bravest/domain/vote/entity/UserVote.java +++ b/src/main/java/opensource/bravest/domain/vote/entity/UserVote.java @@ -11,19 +11,19 @@ @Builder public class UserVote { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "vote_id", nullable = false) - private Vote vote; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "vote_option_id", nullable = false) - private VoteOption voteOption; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "vote_id", nullable = false) + private Vote vote; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "anonymous_profile_id", nullable = false) - private AnonymousProfile voter; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "vote_option_id", nullable = false) + private VoteOption voteOption; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "anonymous_profile_id", nullable = false) + private AnonymousProfile voter; } diff --git a/src/main/java/opensource/bravest/domain/vote/entity/Vote.java b/src/main/java/opensource/bravest/domain/vote/entity/Vote.java index 03ca1d5..3f2df78 100644 --- a/src/main/java/opensource/bravest/domain/vote/entity/Vote.java +++ b/src/main/java/opensource/bravest/domain/vote/entity/Vote.java @@ -1,12 +1,11 @@ package opensource.bravest.domain.vote.entity; import jakarta.persistence.*; -import lombok.*; -import opensource.bravest.domain.room.entity.AnonymousRoom; - import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import lombok.*; +import opensource.bravest.domain.room.entity.AnonymousRoom; @Entity @Getter @@ -15,26 +14,26 @@ @Builder public class Vote { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "room_id", nullable = false) - private AnonymousRoom room; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", nullable = false) + private AnonymousRoom room; - @Column(nullable = false, length = 100) - private String title; + @Column(nullable = false, length = 100) + private String title; - @Builder.Default - @OneToMany(mappedBy = "vote", cascade = CascadeType.ALL, orphanRemoval = true) - private List options = new ArrayList<>(); + @Builder.Default + @OneToMany(mappedBy = "vote", cascade = CascadeType.ALL, orphanRemoval = true) + private List options = new ArrayList<>(); - private boolean isActive; + private boolean isActive; - private LocalDateTime createdAt; + private LocalDateTime createdAt; - public void endVote() { - this.isActive = false; - } + public void endVote() { + this.isActive = false; + } } diff --git a/src/main/java/opensource/bravest/domain/vote/entity/VoteOption.java b/src/main/java/opensource/bravest/domain/vote/entity/VoteOption.java index 2ed3825..70f03c7 100644 --- a/src/main/java/opensource/bravest/domain/vote/entity/VoteOption.java +++ b/src/main/java/opensource/bravest/domain/vote/entity/VoteOption.java @@ -2,7 +2,6 @@ import jakarta.persistence.*; import lombok.*; -import opensource.bravest.domain.message.entity.ChatMessage; @Entity @Getter @@ -11,21 +10,21 @@ @Builder public class VoteOption { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "vote_id", nullable = false) - private Vote vote; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "vote_id", nullable = false) + private Vote vote; - @Column(name = "message_content", nullable = false) - private String messageContent; + @Column(name = "message_content", nullable = false) + private String messageContent; - @Column(nullable = false) - private int voteCount; + @Column(nullable = false) + private int voteCount; - public void incrementVoteCount() { - this.voteCount++; - } + public void incrementVoteCount() { + this.voteCount++; + } } diff --git a/src/main/java/opensource/bravest/domain/vote/repository/UserVoteRepository.java b/src/main/java/opensource/bravest/domain/vote/repository/UserVoteRepository.java index d9e3579..7fe9954 100644 --- a/src/main/java/opensource/bravest/domain/vote/repository/UserVoteRepository.java +++ b/src/main/java/opensource/bravest/domain/vote/repository/UserVoteRepository.java @@ -1,12 +1,11 @@ package opensource.bravest.domain.vote.repository; +import java.util.Optional; import opensource.bravest.domain.profile.entity.AnonymousProfile; import opensource.bravest.domain.vote.entity.UserVote; import opensource.bravest.domain.vote.entity.Vote; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface UserVoteRepository extends JpaRepository { - Optional findByVoteAndVoter(Vote vote, AnonymousProfile voter); + Optional findByVoteAndVoter(Vote vote, AnonymousProfile voter); } diff --git a/src/main/java/opensource/bravest/domain/vote/repository/VoteOptionRepository.java b/src/main/java/opensource/bravest/domain/vote/repository/VoteOptionRepository.java index 5b97137..ccc2c49 100644 --- a/src/main/java/opensource/bravest/domain/vote/repository/VoteOptionRepository.java +++ b/src/main/java/opensource/bravest/domain/vote/repository/VoteOptionRepository.java @@ -3,5 +3,4 @@ import opensource.bravest.domain.vote.entity.VoteOption; import org.springframework.data.jpa.repository.JpaRepository; -public interface VoteOptionRepository extends JpaRepository { -} +public interface VoteOptionRepository extends JpaRepository {} diff --git a/src/main/java/opensource/bravest/domain/vote/repository/VoteRepository.java b/src/main/java/opensource/bravest/domain/vote/repository/VoteRepository.java index e7b9d91..8dcfded 100644 --- a/src/main/java/opensource/bravest/domain/vote/repository/VoteRepository.java +++ b/src/main/java/opensource/bravest/domain/vote/repository/VoteRepository.java @@ -3,5 +3,4 @@ import opensource.bravest.domain.vote.entity.Vote; import org.springframework.data.jpa.repository.JpaRepository; -public interface VoteRepository extends JpaRepository { -} +public interface VoteRepository extends JpaRepository {} diff --git a/src/main/java/opensource/bravest/domain/vote/service/VoteService.java b/src/main/java/opensource/bravest/domain/vote/service/VoteService.java index 8d39887..39224b9 100644 --- a/src/main/java/opensource/bravest/domain/vote/service/VoteService.java +++ b/src/main/java/opensource/bravest/domain/vote/service/VoteService.java @@ -1,8 +1,9 @@ package opensource.bravest.domain.vote.service; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import opensource.bravest.domain.message.entity.ChatMessage; -import opensource.bravest.domain.message.repository.ChatMessageRepository; import opensource.bravest.domain.profile.entity.AnonymousProfile; import opensource.bravest.domain.profile.repository.AnonymousProfileRepository; import opensource.bravest.domain.room.entity.AnonymousRoom; @@ -16,112 +17,112 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class VoteService { - private final VoteRepository voteRepository; - private final UserVoteRepository userVoteRepository; - private final AnonymousRoomRepository anonymousRoomRepository; - private final AnonymousProfileRepository anonymousProfileRepository; - - @Transactional - public Vote createVote(VoteDto.CreateVoteRequest request) { - AnonymousRoom room = anonymousRoomRepository.findById(request.getRoomId()) - .orElseThrow(() -> new RuntimeException("Room not found")); - - Vote vote = Vote.builder() - .room(room) - .title(room.getTitle()) - .isActive(true) - .createdAt(LocalDateTime.now()) - .build(); - - List options = request.getMessages().stream() - .map(message -> VoteOption.builder() - .vote(vote) - .messageContent(message) - .voteCount(0) - .build()) - .collect(Collectors.toList()); + private final VoteRepository voteRepository; + private final UserVoteRepository userVoteRepository; + private final AnonymousRoomRepository anonymousRoomRepository; + private final AnonymousProfileRepository anonymousProfileRepository; + + @Transactional + public Vote createVote(VoteDto.CreateVoteRequest request) { + AnonymousRoom room = + anonymousRoomRepository + .findById(request.getRoomId()) + .orElseThrow(() -> new RuntimeException("Room not found")); + + Vote vote = + Vote.builder() + .room(room) + .title(room.getTitle()) + .isActive(true) + .createdAt(LocalDateTime.now()) + .build(); + + List options = + request.getMessages().stream() + .map( + message -> + VoteOption.builder().vote(vote).messageContent(message).voteCount(0).build()) + .collect(Collectors.toList()); + + vote.getOptions().addAll(options); + + return voteRepository.save(vote); + } + + @Transactional + public void castVote(Long voteId, VoteDto.CastVoteRequest request) { + Vote vote = + voteRepository.findById(voteId).orElseThrow(() -> new RuntimeException("Vote not found")); + if (!vote.isActive()) { + throw new RuntimeException("Vote is not active"); + } - vote.getOptions().addAll(options); + AnonymousProfile voter = + anonymousProfileRepository + .findById(request.getAnonymousProfileId()) + .orElseThrow(() -> new RuntimeException("AnonymousProfile not found")); - return voteRepository.save(vote); + if (userVoteRepository.findByVoteAndVoter(vote, voter).isPresent()) { + throw new RuntimeException("User has already voted"); } - @Transactional - public void castVote(Long voteId, VoteDto.CastVoteRequest request) { - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new RuntimeException("Vote not found")); - if (!vote.isActive()) { - throw new RuntimeException("Vote is not active"); - } - - AnonymousProfile voter = anonymousProfileRepository.findById(request.getAnonymousProfileId()) - .orElseThrow(() -> new RuntimeException("AnonymousProfile not found")); - - if (userVoteRepository.findByVoteAndVoter(vote, voter).isPresent()) { - throw new RuntimeException("User has already voted"); - } - - VoteOption voteOption = vote.getOptions().stream() - .filter(option -> option.getId().equals(request.getVoteOptionId())) - .findFirst() - .orElseThrow(() -> new RuntimeException("VoteOption not found")); - - voteOption.incrementVoteCount(); - - UserVote userVote = UserVote.builder() - .vote(vote) - .voteOption(voteOption) - .voter(voter) - .build(); - userVoteRepository.save(userVote); - } + VoteOption voteOption = + vote.getOptions().stream() + .filter(option -> option.getId().equals(request.getVoteOptionId())) + .findFirst() + .orElseThrow(() -> new RuntimeException("VoteOption not found")); - @Transactional - public void endVote(Long voteId) { - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new RuntimeException("Vote not found")); - vote.endVote(); - } + voteOption.incrementVoteCount(); - public VoteDto.VoteResponse getVoteResult(Long voteId) { - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new RuntimeException("Vote not found")); + UserVote userVote = UserVote.builder().vote(vote).voteOption(voteOption).voter(voter).build(); + userVoteRepository.save(userVote); + } - return buildVoteResponse(vote); - } + @Transactional + public void endVote(Long voteId) { + Vote vote = + voteRepository.findById(voteId).orElseThrow(() -> new RuntimeException("Vote not found")); + vote.endVote(); + } - @Transactional - public void deleteVote(Long voteId) { - if (!voteRepository.existsById(voteId)) { - throw new RuntimeException("Vote not found"); - } - voteRepository.deleteById(voteId); - } + public VoteDto.VoteResponse getVoteResult(Long voteId) { + Vote vote = + voteRepository.findById(voteId).orElseThrow(() -> new RuntimeException("Vote not found")); + + return buildVoteResponse(vote); + } - private VoteDto.VoteResponse buildVoteResponse(Vote vote) { - List optionResponses = vote.getOptions().stream() - .map(option -> VoteDto.VoteOptionResponse.builder() + @Transactional + public void deleteVote(Long voteId) { + if (!voteRepository.existsById(voteId)) { + throw new RuntimeException("Vote not found"); + } + voteRepository.deleteById(voteId); + } + + private VoteDto.VoteResponse buildVoteResponse(Vote vote) { + List optionResponses = + vote.getOptions().stream() + .map( + option -> + VoteDto.VoteOptionResponse.builder() .id(option.getId()) .messageContent(option.getMessageContent()) .voteCount(option.getVoteCount()) .build()) - .collect(Collectors.toList()); - - return VoteDto.VoteResponse.builder() - .id(vote.getId()) - .title(vote.getTitle()) - .isActive(vote.isActive()) - .createdAt(vote.getCreatedAt()) - .options(optionResponses) - .build(); - } + .collect(Collectors.toList()); + + return VoteDto.VoteResponse.builder() + .id(vote.getId()) + .title(vote.getTitle()) + .isActive(vote.isActive()) + .createdAt(vote.getCreatedAt()) + .options(optionResponses) + .build(); + } } diff --git a/src/main/java/opensource/bravest/global/apiPayload/ApiResponse.java b/src/main/java/opensource/bravest/global/apiPayload/ApiResponse.java index 098aeec..b577884 100644 --- a/src/main/java/opensource/bravest/global/apiPayload/ApiResponse.java +++ b/src/main/java/opensource/bravest/global/apiPayload/ApiResponse.java @@ -1,6 +1,5 @@ package opensource.bravest.global.apiPayload; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -13,10 +12,11 @@ @Getter @AllArgsConstructor -@JsonPropertyOrder({ "isSuccess", "code", "message", "data" }) +@JsonPropertyOrder({"isSuccess", "code", "message", "data"}) public class ApiResponse { @JsonProperty("isSuccess") private final boolean isSuccess; + private final String code; private final String message; @@ -25,27 +25,17 @@ public class ApiResponse { public static ApiResponse onSuccess(T data) { return new ApiResponse<>( - true, - SuccessStatus._OK.getCode(), - SuccessStatus._OK.getMessage(), - data); + true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), data); } public static ApiResponse of(BaseCode code, String message, T data) { return new ApiResponse<>( - true, - code.getReasonHttpStatus().getCode(), - code.getReasonHttpStatus().getMessage(), - data); + true, code.getReasonHttpStatus().getCode(), code.getReasonHttpStatus().getMessage(), data); } public static ApiResponse onFailure(BaseErrorCode errorCode, T data) { ErrorReasonDto reason = errorCode.getReasonHttpStatus(); - return new ApiResponse<>( - reason.getIsSuccess(), - reason.getCode(), - reason.getMessage(), - data); + return new ApiResponse<>(reason.getIsSuccess(), reason.getCode(), reason.getMessage(), data); } public static ApiResponse onFailure(String code, String message, T data) { diff --git a/src/main/java/opensource/bravest/global/apiPayload/code/BaseCode.java b/src/main/java/opensource/bravest/global/apiPayload/code/BaseCode.java index df8f866..00f3dd4 100644 --- a/src/main/java/opensource/bravest/global/apiPayload/code/BaseCode.java +++ b/src/main/java/opensource/bravest/global/apiPayload/code/BaseCode.java @@ -2,5 +2,6 @@ public interface BaseCode { ReasonDto getReason(); + ReasonDto getReasonHttpStatus(); -} \ No newline at end of file +} diff --git a/src/main/java/opensource/bravest/global/apiPayload/code/BaseErrorCode.java b/src/main/java/opensource/bravest/global/apiPayload/code/BaseErrorCode.java index e52b4d4..6f514a7 100644 --- a/src/main/java/opensource/bravest/global/apiPayload/code/BaseErrorCode.java +++ b/src/main/java/opensource/bravest/global/apiPayload/code/BaseErrorCode.java @@ -2,5 +2,6 @@ public interface BaseErrorCode { ErrorReasonDto getReason(); + ErrorReasonDto getReasonHttpStatus(); } diff --git a/src/main/java/opensource/bravest/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/opensource/bravest/global/apiPayload/code/status/ErrorStatus.java index 8afd9f2..b0ae3d4 100644 --- a/src/main/java/opensource/bravest/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/opensource/bravest/global/apiPayload/code/status/ErrorStatus.java @@ -1,6 +1,5 @@ package opensource.bravest.global.apiPayload.code.status; - import lombok.AllArgsConstructor; import lombok.Getter; import opensource.bravest.global.apiPayload.code.BaseErrorCode; @@ -19,7 +18,6 @@ public enum ErrorStatus implements BaseErrorCode { _USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404", "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), _CHATROOM_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404", "์ฑ„ํŒ…๋ฐฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), _CHATLIST_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404", "๋ฆฌ์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."), - ; private final HttpStatus httpStatus; @@ -28,11 +26,7 @@ public enum ErrorStatus implements BaseErrorCode { @Override public ErrorReasonDto getReason() { - return ErrorReasonDto.builder() - .isSuccess(false) - .message(message) - .code(code) - .build(); + return ErrorReasonDto.builder().isSuccess(false).message(message).code(code).build(); } @Override diff --git a/src/main/java/opensource/bravest/global/apiPayload/code/status/SuccessStatus.java b/src/main/java/opensource/bravest/global/apiPayload/code/status/SuccessStatus.java index 2400189..7845515 100644 --- a/src/main/java/opensource/bravest/global/apiPayload/code/status/SuccessStatus.java +++ b/src/main/java/opensource/bravest/global/apiPayload/code/status/SuccessStatus.java @@ -18,11 +18,7 @@ public enum SuccessStatus implements BaseCode { @Override public ReasonDto getReason() { - return ReasonDto.builder() - .isSuccess(true) - .message(message) - .code(code) - .build(); + return ReasonDto.builder().isSuccess(true).message(message).code(code).build(); } @Override @@ -34,5 +30,4 @@ public ReasonDto getReasonHttpStatus() { .message(message) .build(); } - } diff --git a/src/main/java/opensource/bravest/global/config/OpenApiConfig.java b/src/main/java/opensource/bravest/global/config/OpenApiConfig.java index 48e4c39..25c4cff 100644 --- a/src/main/java/opensource/bravest/global/config/OpenApiConfig.java +++ b/src/main/java/opensource/bravest/global/config/OpenApiConfig.java @@ -13,28 +13,29 @@ @Configuration public class OpenApiConfig { - private static final String SECURITY_SCHEME_NAME = "bearerAuth"; + private static final String SECURITY_SCHEME_NAME = "bearerAuth"; - @Bean - public OpenAPI baseOpenAPI() { - return new OpenAPI() - // 1) ์ „์—ญ์œผ๋กœ "์ด API๋Š” ์ด ์ธ์ฆ ๋ฐฉ์‹์„ ์“ด๋‹ค" ์„ ์–ธ - .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) - // 2) JWT Bearer ์Šคํ‚ค๋งˆ ์ •์˜ - .components(new Components() - .addSecuritySchemes(SECURITY_SCHEME_NAME, - new SecurityScheme() - .name(SECURITY_SCHEME_NAME) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - ) - ) - .info(new Info() - .title("openSource Bravest API") - .description("openSource Bravest ๋ฐฑ์—”๋“œ API ๋ฌธ์„œ") - .version("v1.0.0") - .license(new License().name("MIT"))) - .externalDocs(new ExternalDocumentation().description("README")); - } + @Bean + public OpenAPI baseOpenAPI() { + return new OpenAPI() + // 1) ์ „์—ญ์œผ๋กœ "์ด API๋Š” ์ด ์ธ์ฆ ๋ฐฉ์‹์„ ์“ด๋‹ค" ์„ ์–ธ + .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) + // 2) JWT Bearer ์Šคํ‚ค๋งˆ ์ •์˜ + .components( + new Components() + .addSecuritySchemes( + SECURITY_SCHEME_NAME, + new SecurityScheme() + .name(SECURITY_SCHEME_NAME) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))) + .info( + new Info() + .title("openSource Bravest API") + .description("openSource Bravest ๋ฐฑ์—”๋“œ API ๋ฌธ์„œ") + .version("v1.0.0") + .license(new License().name("MIT"))) + .externalDocs(new ExternalDocumentation().description("README")); + } } diff --git a/src/main/java/opensource/bravest/global/config/SecurityConfig.java b/src/main/java/opensource/bravest/global/config/SecurityConfig.java index eb5b4be..35abcef 100644 --- a/src/main/java/opensource/bravest/global/config/SecurityConfig.java +++ b/src/main/java/opensource/bravest/global/config/SecurityConfig.java @@ -1,5 +1,8 @@ package opensource.bravest.global.config; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import opensource.bravest.global.apiPayload.code.status.ErrorStatus; import opensource.bravest.global.security.jwt.JwtAuthenticationFilter; @@ -17,10 +20,6 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.*; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -29,27 +28,30 @@ public class SecurityConfig { private final JwtTokenProvider jwtTokenProvider; // Swagger - private static final String[] SWAGGER = { - "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html" - }; + private static final String[] SWAGGER = {"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html"}; // ๋กœ๊ทธ์ธ/ํ† ํฐ ๊ตํ™˜/๋ฆฌ๋‹ค์ด๋ ‰ํŠธ/ํ—ฌ์Šค์ฒดํฌ ๋“ฑ ๊ณต๊ฐœ ๊ฒฝ๋กœ private static final String[] PUBLIC = { - "/", "/actuator/health", - "/api/auth/**", // ์นด์นด์˜ค ์ฝ”๋“œ ๊ตํ™˜ API ๋“ฑ - "/oauth2/**", - "/login/**", "/login/oauth2/**", - "/api/test/auth/**", - "/rooms/**", - "/chatlists/**", - "/anonymous-profiles/**", - "/votes/**", - "/ws-connect/**", "/chat-test", "/pub/**", "/sub/**" + "/", + "/actuator/health", + "/api/auth/**", // ์นด์นด์˜ค ์ฝ”๋“œ ๊ตํ™˜ API ๋“ฑ + "/oauth2/**", + "/login/**", + "/login/oauth2/**", + "/api/test/auth/**", + "/rooms/**", + "/chatlists/**", + "/anonymous-profiles/**", + "/votes/**", + "/ws-connect/**", + "/chat-test", + "/pub/**", + "/sub/**" }; // ์ •์  ๋ฆฌ์†Œ์Šค private static final String[] STATIC = { - "/favicon.ico", "/assets/**", "/css/**", "/js/**", "/images/**" + "/favicon.ico", "/assets/**", "/css/**", "/js/**", "/images/**" }; @Bean @@ -73,37 +75,46 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestCache(cache -> cache.disable()) // ๊ถŒํ•œ ๊ทœ์น™ - .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // CORS preflight ํ—ˆ์šฉ - .requestMatchers(SWAGGER).permitAll() - .requestMatchers(PUBLIC).permitAll() - .requestMatchers(STATIC).permitAll() - .anyRequest().authenticated()) + .authorizeHttpRequests( + auth -> + auth.requestMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() // CORS preflight ํ—ˆ์šฉ + .requestMatchers(SWAGGER) + .permitAll() + .requestMatchers(PUBLIC) + .permitAll() + .requestMatchers(STATIC) + .permitAll() + .anyRequest() + .authenticated()) // ์ธ์ฆ/์ธ๊ฐ€ ์‹คํŒจ ๊ณตํ†ต ์‘๋‹ต(JSON) - ApiResponse ํ˜•์‹ - .exceptionHandling(ex -> ex - .authenticationEntryPoint((req, res, ex1) -> { - ErrorStatus errorStatus = ErrorStatus._UNAUTHORIZED; - res.setStatus(errorStatus.getReasonHttpStatus().getHttpStatus().value()); - res.setContentType("application/json;charset=UTF-8"); - try (PrintWriter w = res.getWriter()) { - w.write(String.format( - "{\"isSuccess\":false,\"code\":\"%s\",\"message\":\"%s\",\"data\":null}", - errorStatus.getCode(), - errorStatus.getMessage())); - } - }) - .accessDeniedHandler((req, res, ex2) -> { - ErrorStatus errorStatus = ErrorStatus._FORBIDDEN; - res.setStatus(errorStatus.getReasonHttpStatus().getHttpStatus().value()); - res.setContentType("application/json;charset=UTF-8"); - try (PrintWriter w = res.getWriter()) { - w.write(String.format( - "{\"isSuccess\":false,\"code\":\"%s\",\"message\":\"%s\",\"data\":null}", - errorStatus.getCode(), - errorStatus.getMessage())); - } - })) + .exceptionHandling( + ex -> + ex.authenticationEntryPoint( + (req, res, ex1) -> { + ErrorStatus errorStatus = ErrorStatus._UNAUTHORIZED; + res.setStatus(errorStatus.getReasonHttpStatus().getHttpStatus().value()); + res.setContentType("application/json;charset=UTF-8"); + try (PrintWriter w = res.getWriter()) { + w.write( + String.format( + "{\"isSuccess\":false,\"code\":\"%s\",\"message\":\"%s\",\"data\":null}", + errorStatus.getCode(), errorStatus.getMessage())); + } + }) + .accessDeniedHandler( + (req, res, ex2) -> { + ErrorStatus errorStatus = ErrorStatus._FORBIDDEN; + res.setStatus(errorStatus.getReasonHttpStatus().getHttpStatus().value()); + res.setContentType("application/json;charset=UTF-8"); + try (PrintWriter w = res.getWriter()) { + w.write( + String.format( + "{\"isSuccess\":false,\"code\":\"%s\",\"message\":\"%s\",\"data\":null}", + errorStatus.getCode(), errorStatus.getMessage())); + } + })) // JWT ํ•„ํ„ฐ ๋“ฑ๋ก(UsernamePasswordAuthenticationFilter ์•ž) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); @@ -112,15 +123,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } private static void addAll(List target, String[] arr) { - for (String s : arr) - target.add(s); + for (String s : arr) target.add(s); } // CORS (๊ฐœ๋ฐœ์šฉ: ํ•„์š” ์‹œ ๋„๋ฉ”์ธ ๊ณ ์ •/์ถ•์†Œ) @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration c = new CorsConfiguration(); - c.setAllowedOrigins(List.of("http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173")); + c.setAllowedOrigins( + List.of("http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173")); c.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); c.setAllowedHeaders(List.of("*")); c.setExposedHeaders(List.of("Authorization", "Location")); diff --git a/src/main/java/opensource/bravest/global/config/ValkeyConfig.java b/src/main/java/opensource/bravest/global/config/ValkeyConfig.java index f13aea9..be6c578 100644 --- a/src/main/java/opensource/bravest/global/config/ValkeyConfig.java +++ b/src/main/java/opensource/bravest/global/config/ValkeyConfig.java @@ -2,15 +2,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration public class ValkeyConfig { - @Bean - public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { - return new StringRedisTemplate(connectionFactory); - } + @Bean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); + } } diff --git a/src/main/java/opensource/bravest/global/config/WebSocketConfig.java b/src/main/java/opensource/bravest/global/config/WebSocketConfig.java index 87931a0..177db70 100644 --- a/src/main/java/opensource/bravest/global/config/WebSocketConfig.java +++ b/src/main/java/opensource/bravest/global/config/WebSocketConfig.java @@ -14,23 +14,21 @@ @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - private final StompHandler stompHandler; + private final StompHandler stompHandler; - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/ws-connect") - .setAllowedOriginPatterns("*") - .withSockJS(); - } + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-connect").setAllowedOriginPatterns("*").withSockJS(); + } - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/subs"); - registry.setApplicationDestinationPrefixes("/pubs"); - } + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/subs"); + registry.setApplicationDestinationPrefixes("/pubs"); + } - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(stompHandler); - } + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(stompHandler); + } } diff --git a/src/main/java/opensource/bravest/global/exception/CustomException.java b/src/main/java/opensource/bravest/global/exception/CustomException.java index 1ae5428..3fb913e 100644 --- a/src/main/java/opensource/bravest/global/exception/CustomException.java +++ b/src/main/java/opensource/bravest/global/exception/CustomException.java @@ -1,19 +1,16 @@ package opensource.bravest.global.exception; - import lombok.Getter; import opensource.bravest.global.apiPayload.code.BaseErrorCode; -/** - * ์„œ๋น„์Šค/๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์—์„œ ํ‘œ์ค€ํ™”๋œ ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ๋˜์ง€๊ธฐ ์œ„ํ•œ ์˜ˆ์™ธ - */ +/** ์„œ๋น„์Šค/๋„๋ฉ”์ธ ๋ ˆ์ด์–ด์—์„œ ํ‘œ์ค€ํ™”๋œ ์—๋Ÿฌ์ฝ”๋“œ๋ฅผ ๋˜์ง€๊ธฐ ์œ„ํ•œ ์˜ˆ์™ธ */ @Getter public class CustomException extends RuntimeException { - private final BaseErrorCode errorCode; + private final BaseErrorCode errorCode; - public CustomException(BaseErrorCode errorCode) { - super(errorCode.getReason().getMessage()); - this.errorCode = errorCode; - } -} \ No newline at end of file + public CustomException(BaseErrorCode errorCode) { + super(errorCode.getReason().getMessage()); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/opensource/bravest/global/exception/GlobalExceptionHandler.java b/src/main/java/opensource/bravest/global/exception/GlobalExceptionHandler.java index aeddd45..6faa479 100644 --- a/src/main/java/opensource/bravest/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/opensource/bravest/global/exception/GlobalExceptionHandler.java @@ -14,8 +14,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(CustomException.class) public ResponseEntity> handleCustomException(CustomException e) { log.warn("CustomException: {}", e.getMessage()); - return ResponseEntity - .status(e.getErrorCode().getReasonHttpStatus().getHttpStatus()) + return ResponseEntity.status(e.getErrorCode().getReasonHttpStatus().getHttpStatus()) .body(ApiResponse.onFailure(e.getErrorCode(), null)); } @@ -27,31 +26,31 @@ public ResponseEntity> handleRuntimeException(RuntimeExcepti if (message != null) { if (message.contains("์œ ํšจํ•˜์ง€ ์•Š์€ ์ดˆ๋Œ€ ์ฝ”๋“œ") || message.contains("๊ฐ€์กฑ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")) { log.warn("Family not found: {}", message); - return ResponseEntity - .status(ErrorStatus._FAMILY_NOT_FOUND.getReasonHttpStatus().getHttpStatus()) + return ResponseEntity.status( + ErrorStatus._FAMILY_NOT_FOUND.getReasonHttpStatus().getHttpStatus()) .body(ApiResponse.onFailure(ErrorStatus._FAMILY_NOT_FOUND, null)); } if (message.contains("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")) { log.warn("User not found: {}", message); - return ResponseEntity - .status(ErrorStatus._USER_NOT_FOUND.getReasonHttpStatus().getHttpStatus()) + return ResponseEntity.status( + ErrorStatus._USER_NOT_FOUND.getReasonHttpStatus().getHttpStatus()) .body(ApiResponse.onFailure(ErrorStatus._USER_NOT_FOUND, null)); } } // ๊ธฐ๋ณธ๊ฐ’: 500 Internal Server Error log.error("RuntimeException: ", e); - return ResponseEntity - .status(ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus()) + return ResponseEntity.status( + ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus()) .body(ApiResponse.onFailure(ErrorStatus._INTERNAL_SERVER_ERROR, null)); } @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception e) { log.error("Unexpected exception: ", e); - return ResponseEntity - .status(ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus()) + return ResponseEntity.status( + ErrorStatus._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus()) .body(ApiResponse.onFailure(ErrorStatus._INTERNAL_SERVER_ERROR, null)); } } diff --git a/src/main/java/opensource/bravest/global/handler/StompHandler.java b/src/main/java/opensource/bravest/global/handler/StompHandler.java index c3ab889..d7ec8d8 100644 --- a/src/main/java/opensource/bravest/global/handler/StompHandler.java +++ b/src/main/java/opensource/bravest/global/handler/StompHandler.java @@ -1,5 +1,7 @@ package opensource.bravest.global.handler; +import java.security.Principal; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import opensource.bravest.domain.profile.repository.AnonymousProfileRepository; @@ -16,136 +18,141 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import java.security.Principal; -import java.util.List; - @Slf4j @Component @RequiredArgsConstructor public class StompHandler implements ChannelInterceptor { - private final AnonymousProfileRepository anonymousProfileRepository; - private final StringRedisTemplate redisTemplate; + private final AnonymousProfileRepository anonymousProfileRepository; + private final StringRedisTemplate redisTemplate; - private static final String USER_SUB_KEY_PREFIX = "ws:subs:user:"; // + anonymousId - private static final String METRIC_TOTAL_SUB = "ws:metrics:sub:total"; - private static final String METRIC_DUP_SUB = "ws:metrics:sub:duplicate"; + private static final String USER_SUB_KEY_PREFIX = "ws:subs:user:"; // + anonymousId + private static final String METRIC_TOTAL_SUB = "ws:metrics:sub:total"; + private static final String METRIC_DUP_SUB = "ws:metrics:sub:duplicate"; - @Override - public Message preSend(Message message, MessageChannel channel) { - StompHeaderAccessor accessor = - MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = + MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - if (accessor == null) { - return message; - } + if (accessor == null) { + return message; + } - StompCommand command = accessor.getCommand(); - - // 1) CONNECT: anonymousId๋ฅผ Principal๋กœ ์„ค์ • - if (StompCommand.CONNECT.equals(command)) { - String anonymousId = accessor.getFirstNativeHeader("anonymousId"); - if (anonymousId == null || anonymousId.isBlank()) { - log.warn("STOMP CONNECT: anonymousId missing"); - throw new IllegalArgumentException("anonymousId header is required"); - } - - anonymousProfileRepository.findById(Long.valueOf(anonymousId)) - .ifPresentOrElse(member -> { - Authentication auth = new UsernamePasswordAuthenticationToken( - anonymousId, - null, - List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) - ); - SecurityContextHolder.getContext().setAuthentication(auth); - accessor.setUser(auth); - log.info("STOMP CONNECT: anonymousId={} principal set", anonymousId); - }, () -> { - log.warn("STOMP CONNECT: invalid anonymousId={}", anonymousId); - throw new IllegalArgumentException("Invalid anonymousId"); - }); - } + StompCommand command = accessor.getCommand(); + + // 1) CONNECT: anonymousId๋ฅผ Principal๋กœ ์„ค์ • + if (StompCommand.CONNECT.equals(command)) { + String anonymousId = accessor.getFirstNativeHeader("anonymousId"); + if (anonymousId == null || anonymousId.isBlank()) { + log.warn("STOMP CONNECT: anonymousId missing"); + throw new IllegalArgumentException("anonymousId header is required"); + } + + anonymousProfileRepository + .findById(Long.valueOf(anonymousId)) + .ifPresentOrElse( + member -> { + Authentication auth = + new UsernamePasswordAuthenticationToken( + anonymousId, null, List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))); + SecurityContextHolder.getContext().setAuthentication(auth); + accessor.setUser(auth); + log.info("STOMP CONNECT: anonymousId={} principal set", anonymousId); + }, + () -> { + log.warn("STOMP CONNECT: invalid anonymousId={}", anonymousId); + throw new IllegalArgumentException("Invalid anonymousId"); + }); + } - // 2) SUBSCRIBE: Redis๋ฅผ ์‚ฌ์šฉํ•ด anonymousId ๊ธฐ์ค€ ์ค‘๋ณต ๊ตฌ๋… ๋ฐฉ์ง€ + ๋ฉ”ํŠธ๋ฆญ ๊ธฐ๋ก - if (StompCommand.SUBSCRIBE.equals(command)) { - Principal user = accessor.getUser(); - if (user == null) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth != null) { - accessor.setUser(auth); - user = auth; - } - } - - String destination = accessor.getDestination(); - - if (user != null && destination != null) { - String anonymousId = user.getName(); - String key = USER_SUB_KEY_PREFIX + anonymousId; - - log.info("[SUBSCRIBE] handling: anonymousId={}, destination={}, key={}", - anonymousId, destination, key); - - try { - Long total = redisTemplate.opsForValue().increment(METRIC_TOTAL_SUB); - Long added = redisTemplate.opsForSet().add(key, destination); - redisTemplate.expire(key, java.time.Duration.ofHours(1)); - - log.info("[SUBSCRIBE] redis result: total={}, added={}", total, added); - - if (added != null && added == 0L) { - Long dup = redisTemplate.opsForValue().increment(METRIC_DUP_SUB); - log.warn("[SUBSCRIBE] duplicate detected: anonymousId={}, dest={}, dupCount={}", - anonymousId, destination, dup); - return null; - } - - log.info("[SUBSCRIBE] stored in Redis: key={}, member={}", key, destination); - - } catch (Exception e) { - log.error("Redis error while handling SUBSCRIBE", e); - } - } else { - log.warn("[SUBSCRIBE] skipped: user or destination is null (user={}, dest={})", - user, destination); - } + // 2) SUBSCRIBE: Redis๋ฅผ ์‚ฌ์šฉํ•ด anonymousId ๊ธฐ์ค€ ์ค‘๋ณต ๊ตฌ๋… ๋ฐฉ์ง€ + ๋ฉ”ํŠธ๋ฆญ ๊ธฐ๋ก + if (StompCommand.SUBSCRIBE.equals(command)) { + Principal user = accessor.getUser(); + if (user == null) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + accessor.setUser(auth); + user = auth; } + } + + String destination = accessor.getDestination(); + if (user != null && destination != null) { + String anonymousId = user.getName(); + String key = USER_SUB_KEY_PREFIX + anonymousId; - // 3) SEND: Principal ๋น„์–ด ์žˆ์œผ๋ฉด SecurityContext์—์„œ ๋ณต๊ตฌ - if (StompCommand.SEND.equals(command)) { - Principal user = accessor.getUser(); - if (user == null) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth != null) { - accessor.setUser(auth); - } - } + log.info( + "[SUBSCRIBE] handling: anonymousId={}, destination={}, key={}", + anonymousId, + destination, + key); + + try { + Long total = redisTemplate.opsForValue().increment(METRIC_TOTAL_SUB); + Long added = redisTemplate.opsForSet().add(key, destination); + redisTemplate.expire(key, java.time.Duration.ofHours(1)); + + log.info("[SUBSCRIBE] redis result: total={}, added={}", total, added); + + if (added != null && added == 0L) { + Long dup = redisTemplate.opsForValue().increment(METRIC_DUP_SUB); + log.warn( + "[SUBSCRIBE] duplicate detected: anonymousId={}, dest={}, dupCount={}", + anonymousId, + destination, + dup); + return null; + } + + log.info("[SUBSCRIBE] stored in Redis: key={}, member={}", key, destination); + + } catch (Exception e) { + log.error("Redis error while handling SUBSCRIBE", e); } + } else { + log.warn( + "[SUBSCRIBE] skipped: user or destination is null (user={}, dest={})", + user, + destination); + } + } - // 4) DISCONNECT: ์œ ์ €๋ณ„ ๊ตฌ๋… ํ‚ค๋ฅผ ์ •๋ฆฌํ• ์ง€ ์—ฌ๋ถ€ (์˜ต์…˜) - // - ์ „์ฒด ๋ฐฉ ์ „์ฒด ์œ ์ € ์ˆ˜๊ฐ€ ํฌ์ง€ ์•Š๋‹ค๋ฉด TTL๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„. - if (StompCommand.DISCONNECT.equals(command)) { - Principal user = accessor.getUser(); - if (user == null) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth != null) { - user = auth; - } - } - if (user != null) { - String anonymousId = user.getName(); - String key = USER_SUB_KEY_PREFIX + anonymousId; - try { - // ์™„์ „ํžˆ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์œผ๋ฉด delete - redisTemplate.delete(key); - log.info("DISCONNECT: cleared subscriptions for anonymousId={}", anonymousId); - } catch (Exception e) { - log.error("Redis error while handling DISCONNECT", e); - } - } + // 3) SEND: Principal ๋น„์–ด ์žˆ์œผ๋ฉด SecurityContext์—์„œ ๋ณต๊ตฌ + if (StompCommand.SEND.equals(command)) { + Principal user = accessor.getUser(); + if (user == null) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + accessor.setUser(auth); } + } + } - return message; + // 4) DISCONNECT: ์œ ์ €๋ณ„ ๊ตฌ๋… ํ‚ค๋ฅผ ์ •๋ฆฌํ• ์ง€ ์—ฌ๋ถ€ (์˜ต์…˜) + // - ์ „์ฒด ๋ฐฉ ์ „์ฒด ์œ ์ € ์ˆ˜๊ฐ€ ํฌ์ง€ ์•Š๋‹ค๋ฉด TTL๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„. + if (StompCommand.DISCONNECT.equals(command)) { + Principal user = accessor.getUser(); + if (user == null) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + user = auth; + } + } + if (user != null) { + String anonymousId = user.getName(); + String key = USER_SUB_KEY_PREFIX + anonymousId; + try { + // ์™„์ „ํžˆ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์œผ๋ฉด delete + redisTemplate.delete(key); + log.info("DISCONNECT: cleared subscriptions for anonymousId={}", anonymousId); + } catch (Exception e) { + log.error("Redis error while handling DISCONNECT", e); + } + } } + + return message; + } } diff --git a/src/main/java/opensource/bravest/global/security/jwt/JwtAuthenticationFilter.java b/src/main/java/opensource/bravest/global/security/jwt/JwtAuthenticationFilter.java index 5f7c282..34ccece 100644 --- a/src/main/java/opensource/bravest/global/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/opensource/bravest/global/security/jwt/JwtAuthenticationFilter.java @@ -5,68 +5,67 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - /** - * JWT๊ฐ€ ํ•„์š”ํ•œ ๋ณดํ˜ธ ๊ฒฝ๋กœ์—๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“  ํ•„ํ„ฐ. - * - ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ(permitAll) ๊ฒฝ๋กœ์™€ OPTIONS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ๋Š” ํ•„ํ„ฐ๋ฅผ ๊ฑด๋„ˆ๋œ€. - * - ํ† ํฐ์ด ์œ ํšจํ•˜๋ฉด SecurityContext ์„ค์ •, ์•„๋‹ˆ๋ฉด ์ฒด์ธ ์ง„ํ–‰ (401์€ EntryPoint๊ฐ€ ์ฒ˜๋ฆฌ) + * JWT๊ฐ€ ํ•„์š”ํ•œ ๋ณดํ˜ธ ๊ฒฝ๋กœ์—๋งŒ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“  ํ•„ํ„ฐ. - ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ(permitAll) ๊ฒฝ๋กœ์™€ OPTIONS ํ”„๋ฆฌํ”Œ๋ผ์ดํŠธ๋Š” ํ•„ํ„ฐ๋ฅผ ๊ฑด๋„ˆ๋œ€. - ํ† ํฐ์ด ์œ ํšจํ•˜๋ฉด + * SecurityContext ์„ค์ •, ์•„๋‹ˆ๋ฉด ์ฒด์ธ ์ง„ํ–‰ (401์€ EntryPoint๊ฐ€ ์ฒ˜๋ฆฌ) */ public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenProvider jwtTokenProvider; - private final List skipPatterns; // ํ•„ํ„ฐ๋ฅผ ์Šคํ‚ตํ•  ๊ฒฝ๋กœ ํŒจํ„ด๋“ค(ant style) - private final AntPathMatcher matcher = new AntPathMatcher(); + private final JwtTokenProvider jwtTokenProvider; + private final List skipPatterns; // ํ•„ํ„ฐ๋ฅผ ์Šคํ‚ตํ•  ๊ฒฝ๋กœ ํŒจํ„ด๋“ค(ant style) + private final AntPathMatcher matcher = new AntPathMatcher(); - public JwtAuthenticationFilter(JwtTokenProvider provider, Collection skipPatterns) { - this.jwtTokenProvider = provider; - this.skipPatterns = skipPatterns == null ? List.of() : List.copyOf(skipPatterns); - } + public JwtAuthenticationFilter(JwtTokenProvider provider, Collection skipPatterns) { + this.jwtTokenProvider = provider; + this.skipPatterns = skipPatterns == null ? List.of() : List.copyOf(skipPatterns); + } - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - // 1) CORS preflight๋Š” ํ•ญ์ƒ ์Šคํ‚ต - if ("OPTIONS".equalsIgnoreCase(request.getMethod())) return true; + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + // 1) CORS preflight๋Š” ํ•ญ์ƒ ์Šคํ‚ต + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) return true; - // 2) ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ํŒจํ„ด์€ ์Šคํ‚ต - String path = request.getServletPath(); - for (String p : skipPatterns) { - if (matcher.match(p, path)) return true; - } - return false; + // 2) ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ํŒจํ„ด์€ ์Šคํ‚ต + String path = request.getServletPath(); + for (String p : skipPatterns) { + if (matcher.match(p, path)) return true; } + return false; + } - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws ServletException, IOException { + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { - String header = request.getHeader(HttpHeaders.AUTHORIZATION); + String header = request.getHeader(HttpHeaders.AUTHORIZATION); - if (header != null && header.startsWith("Bearer ")) { - String token = header.substring(7); - try { - Claims claims = jwtTokenProvider.parseClaims(token); - String subject = claims.getSubject(); - if (subject != null && SecurityContextHolder.getContext().getAuthentication() == null) { - // ํ•„์š” ์‹œ roles/authorities๋ฅผ claims์—์„œ ๊บผ๋‚ด์„œ ๋„ฃ์–ด๋„ ๋จ - var auth = new UsernamePasswordAuthenticationToken(subject, null, Collections.emptyList()); - SecurityContextHolder.getContext().setAuthentication(auth); - } - } catch (Exception ignored) { - // ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ทธ๋ƒฅ ํ†ต๊ณผ -> ์ตœ์ข…์ ์œผ๋กœ EntryPoint๊ฐ€ 401 ์‘๋‹ต ์ฒ˜๋ฆฌ - } + if (header != null && header.startsWith("Bearer ")) { + String token = header.substring(7); + try { + Claims claims = jwtTokenProvider.parseClaims(token); + String subject = claims.getSubject(); + if (subject != null && SecurityContextHolder.getContext().getAuthentication() == null) { + // ํ•„์š” ์‹œ roles/authorities๋ฅผ claims์—์„œ ๊บผ๋‚ด์„œ ๋„ฃ์–ด๋„ ๋จ + var auth = + new UsernamePasswordAuthenticationToken(subject, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(auth); } - - chain.doFilter(request, response); + } catch (Exception ignored) { + // ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๊ทธ๋ƒฅ ํ†ต๊ณผ -> ์ตœ์ข…์ ์œผ๋กœ EntryPoint๊ฐ€ 401 ์‘๋‹ต ์ฒ˜๋ฆฌ + } } -} + chain.doFilter(request, response); + } +} diff --git a/src/main/java/opensource/bravest/global/security/jwt/JwtTokenProvider.java b/src/main/java/opensource/bravest/global/security/jwt/JwtTokenProvider.java index e2e3b36..f5a8f66 100644 --- a/src/main/java/opensource/bravest/global/security/jwt/JwtTokenProvider.java +++ b/src/main/java/opensource/bravest/global/security/jwt/JwtTokenProvider.java @@ -1,99 +1,93 @@ package opensource.bravest.global.security.jwt; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.Claims; +import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Date; import java.util.Map; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; @Component public class JwtTokenProvider { - @Value("${jwt.secret}") - private String secret; - - @Value("${jwt.access-token-validity-seconds}") - private long accessValidity; - - @Value("${jwt.refresh-token-validity-seconds}") - private long refreshValidity; + @Value("${jwt.secret}") + private String secret; - private SecretKey key; + @Value("${jwt.access-token-validity-seconds}") + private long accessValidity; - @PostConstruct - void init() { - if (secret == null || secret.isBlank()) { - throw new IllegalStateException("jwt.secret is not configured. Check your application.yml / env."); - } + @Value("${jwt.refresh-token-validity-seconds}") + private long refreshValidity; - byte[] keyBytes; - try { - // secret์ด Base64๋ฉด ์—ฌ๊ธฐ์„œ ์ •์ƒ ๋””์ฝ”๋”ฉ - keyBytes = Decoders.BASE64.decode(secret); - } catch (IllegalArgumentException e) { - // Base64 ์•„๋‹ˆ๋ฉด ๊ทธ๋ƒฅ ๋ฌธ์ž์—ด ๋ฐ”์ดํŠธ๋กœ ์‚ฌ์šฉ - keyBytes = secret.getBytes(StandardCharsets.UTF_8); - } + private SecretKey key; - this.key = Keys.hmacShaKeyFor(keyBytes); + @PostConstruct + void init() { + if (secret == null || secret.isBlank()) { + throw new IllegalStateException( + "jwt.secret is not configured. Check your application.yml / env."); } - public String createAccessToken(String subject, Map claims) { - Instant now = Instant.now(); - return Jwts.builder() - .subject(subject) - .claims(claims) - .issuedAt(Date.from(now)) - .expiration(Date.from(now.plusSeconds(accessValidity))) - .signWith(key) - .compact(); + byte[] keyBytes; + try { + // secret์ด Base64๋ฉด ์—ฌ๊ธฐ์„œ ์ •์ƒ ๋””์ฝ”๋”ฉ + keyBytes = Decoders.BASE64.decode(secret); + } catch (IllegalArgumentException e) { + // Base64 ์•„๋‹ˆ๋ฉด ๊ทธ๋ƒฅ ๋ฌธ์ž์—ด ๋ฐ”์ดํŠธ๋กœ ์‚ฌ์šฉ + keyBytes = secret.getBytes(StandardCharsets.UTF_8); } - public String createRefreshToken(String subject) { - Instant now = Instant.now(); - return Jwts.builder() - .subject(subject) - .issuedAt(Date.from(now)) - .expiration(Date.from(now.plusSeconds(refreshValidity))) - .signWith(key) - .compact(); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + public String createAccessToken(String subject, Map claims) { + Instant now = Instant.now(); + return Jwts.builder() + .subject(subject) + .claims(claims) + .issuedAt(Date.from(now)) + .expiration(Date.from(now.plusSeconds(accessValidity))) + .signWith(key) + .compact(); + } + + public String createRefreshToken(String subject) { + Instant now = Instant.now(); + return Jwts.builder() + .subject(subject) + .issuedAt(Date.from(now)) + .expiration(Date.from(now.plusSeconds(refreshValidity))) + .signWith(key) + .compact(); + } + + public Long getIdFromToken(String token) { + Claims claims = + Jwts.parser() + .verifyWith(key) // init()์—์„œ ๋งŒ๋“  key ์žฌ์‚ฌ์šฉ + .build() + .parseSignedClaims(token) + .getPayload(); + + return claims.get("id", Long.class); + } + + public boolean validateToken(String token) { + try { + Jwts.parser().verifyWith(key).build().parseSignedClaims(token); + return true; + } catch (Exception e) { + return false; } + } - public Long getIdFromToken(String token) { - Claims claims = Jwts.parser() - .verifyWith(key) // init()์—์„œ ๋งŒ๋“  key ์žฌ์‚ฌ์šฉ - .build() - .parseSignedClaims(token) - .getPayload(); - - return claims.get("id", Long.class); - } - - public boolean validateToken(String token) { - try { - Jwts.parser() - .verifyWith(key) - .build() - .parseSignedClaims(token); - return true; - } catch (Exception e) { - return false; - } - } - - public Claims parseClaims(String token) { - return Jwts.parser() - .verifyWith(key) - .build() - .parseSignedClaims(token) - .getPayload(); - } + public Claims parseClaims(String token) { + return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload(); + } } diff --git a/src/test/java/opensource/bravest/BravestApplicationTests.java b/src/test/java/opensource/bravest/BravestApplicationTests.java index 6494944..bde485a 100644 --- a/src/test/java/opensource/bravest/BravestApplicationTests.java +++ b/src/test/java/opensource/bravest/BravestApplicationTests.java @@ -6,8 +6,6 @@ @SpringBootTest class BravestApplicationTests { - @Test - void contextLoads() { - } - + @Test + void contextLoads() {} }