diff --git a/.env b/.env deleted file mode 100644 index e69de29..0000000 diff --git a/.gitignore b/.gitignore index 39b0984..cf64bb1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ build/ !**/src/test/**/build/ h2/ src/main/resources/application.yaml +src/main/resources/application.yml ### STS ### .apt_generated @@ -29,7 +30,7 @@ out/ !**/src/test/**/out/ ### env ### -src/main/java/com/app/treen/.env +.env ### NetBeans ### /nbproject/private/ diff --git a/.gradle/8.11.1/executionHistory/executionHistory.bin b/.gradle/8.11.1/executionHistory/executionHistory.bin index 7e08eae..fd4a227 100644 Binary files a/.gradle/8.11.1/executionHistory/executionHistory.bin and b/.gradle/8.11.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.11.1/executionHistory/executionHistory.lock b/.gradle/8.11.1/executionHistory/executionHistory.lock index 6c98856..96b16d5 100644 Binary files a/.gradle/8.11.1/executionHistory/executionHistory.lock and b/.gradle/8.11.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.11.1/fileHashes/fileHashes.bin b/.gradle/8.11.1/fileHashes/fileHashes.bin index 7c51d50..13a6970 100644 Binary files a/.gradle/8.11.1/fileHashes/fileHashes.bin and b/.gradle/8.11.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.11.1/fileHashes/fileHashes.lock b/.gradle/8.11.1/fileHashes/fileHashes.lock index df1dfca..60fa3f0 100644 Binary files a/.gradle/8.11.1/fileHashes/fileHashes.lock and b/.gradle/8.11.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/.gradle/8.11.1/fileHashes/resourceHashesCache.bin index 1983f42..12eb67a 100644 Binary files a/.gradle/8.11.1/fileHashes/resourceHashesCache.bin and b/.gradle/8.11.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 9ffc909..ff6bfe3 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index e6f14f5..6be594c 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/build/classes/java/main/com/app/treen/TreenApplication.class b/build/classes/java/main/com/app/treen/TreenApplication.class index f2d9d48..ef3dba5 100644 Binary files a/build/classes/java/main/com/app/treen/TreenApplication.class and b/build/classes/java/main/com/app/treen/TreenApplication.class differ diff --git a/build/classes/java/main/com/app/treen/common/config/S3Uploader.class b/build/classes/java/main/com/app/treen/common/config/S3Uploader.class index 8b01bfc..52c14af 100644 Binary files a/build/classes/java/main/com/app/treen/common/config/S3Uploader.class and b/build/classes/java/main/com/app/treen/common/config/S3Uploader.class differ diff --git a/build/classes/java/main/com/app/treen/common/response/code/status/ErrorStatus.class b/build/classes/java/main/com/app/treen/common/response/code/status/ErrorStatus.class index 8ccf3eb..be817ba 100644 Binary files a/build/classes/java/main/com/app/treen/common/response/code/status/ErrorStatus.class and b/build/classes/java/main/com/app/treen/common/response/code/status/ErrorStatus.class differ diff --git a/build/classes/java/main/com/app/treen/products/controller/ProductController.class b/build/classes/java/main/com/app/treen/products/controller/ProductController.class index cd0ee2c..3631849 100644 Binary files a/build/classes/java/main/com/app/treen/products/controller/ProductController.class and b/build/classes/java/main/com/app/treen/products/controller/ProductController.class differ diff --git a/build/classes/java/main/com/app/treen/products/dto/request/TransProductSaveDto.class b/build/classes/java/main/com/app/treen/products/dto/request/TransProductSaveDto.class index e0fe46a..7ab4535 100644 Binary files a/build/classes/java/main/com/app/treen/products/dto/request/TransProductSaveDto.class and b/build/classes/java/main/com/app/treen/products/dto/request/TransProductSaveDto.class differ diff --git a/build/classes/java/main/com/app/treen/products/dto/response/TransProductResponseDto.class b/build/classes/java/main/com/app/treen/products/dto/response/TransProductResponseDto.class index 5606e03..5c94989 100644 Binary files a/build/classes/java/main/com/app/treen/products/dto/response/TransProductResponseDto.class and b/build/classes/java/main/com/app/treen/products/dto/response/TransProductResponseDto.class differ diff --git a/build/classes/java/main/com/app/treen/products/dto/response/TransResponseListDto.class b/build/classes/java/main/com/app/treen/products/dto/response/TransResponseListDto.class index 0e92363..a959ce1 100644 Binary files a/build/classes/java/main/com/app/treen/products/dto/response/TransResponseListDto.class and b/build/classes/java/main/com/app/treen/products/dto/response/TransResponseListDto.class differ diff --git a/build/classes/java/main/com/app/treen/products/entity/QTransProduct.class b/build/classes/java/main/com/app/treen/products/entity/QTransProduct.class index 34bf2cb..ea6fbe5 100644 Binary files a/build/classes/java/main/com/app/treen/products/entity/QTransProduct.class and b/build/classes/java/main/com/app/treen/products/entity/QTransProduct.class differ diff --git a/build/classes/java/main/com/app/treen/products/entity/TransPImg.class b/build/classes/java/main/com/app/treen/products/entity/TransPImg.class index fc77ac3..823f0ec 100644 Binary files a/build/classes/java/main/com/app/treen/products/entity/TransPImg.class and b/build/classes/java/main/com/app/treen/products/entity/TransPImg.class differ diff --git a/build/classes/java/main/com/app/treen/products/entity/TransProduct$TransProductBuilder.class b/build/classes/java/main/com/app/treen/products/entity/TransProduct$TransProductBuilder.class index 81a2187..e66f185 100644 Binary files a/build/classes/java/main/com/app/treen/products/entity/TransProduct$TransProductBuilder.class and b/build/classes/java/main/com/app/treen/products/entity/TransProduct$TransProductBuilder.class differ diff --git a/build/classes/java/main/com/app/treen/products/entity/TransProduct.class b/build/classes/java/main/com/app/treen/products/entity/TransProduct.class index 13fc984..4783428 100644 Binary files a/build/classes/java/main/com/app/treen/products/entity/TransProduct.class and b/build/classes/java/main/com/app/treen/products/entity/TransProduct.class differ diff --git a/build/classes/java/main/com/app/treen/products/service/ProductService.class b/build/classes/java/main/com/app/treen/products/service/ProductService.class index cd3ff57..addd7d7 100644 Binary files a/build/classes/java/main/com/app/treen/products/service/ProductService.class and b/build/classes/java/main/com/app/treen/products/service/ProductService.class differ diff --git a/build/resources/main/application.yml b/build/resources/main/application.yml index 0639e58..fdfc689 100644 --- a/build/resources/main/application.yml +++ b/build/resources/main/application.yml @@ -5,6 +5,9 @@ # username: ${DB_USERNAME:root} # password: ${DB_PASSWORD:password} spring: + redis: + host: localhost + port: 6379 config: import: optional:file:.env[.properties] # .env 파일을 직접 로드 datasource: @@ -13,16 +16,15 @@ spring: username: sa # 기본 사용자명 password: jpa: - hibernate.ddl-auto: create + hibernate.ddl-auto: update show-sql: true properties: hibernate: format_sql: true - database-platform: org.hibernate.dialect.H2Dialect # H2Dialect 설정 data: mongodb: - uri: mongodb+srv://cymn6032:leeujin0309%40@cluster0.q68ku.mongodb.net/ - database: treen_dev + uri: ${MONGO_DB_URI} + database: ${MONGO_DB_DATABASE} # database-platform: org.hibernate.dialect.MySQL8Dialect cloud: aws: @@ -36,5 +38,9 @@ cloud: auto: false jwt: secret: - key: 780f7e25c9e36b276459459a13b081bbe121af603c2d039b4228739a690d399c75386d3d89f63a099ba817e3764fbf1c7bf744c060c24d2e812e10d511cc3b64 - refresh: 4dedc73032e5574ca6133ddfd6c767b4d7567012482d21f3a268248209997fc52af16f46a3624da10116d9e3e6cacd2ae0750b468738d15e12ba9952d710a7af \ No newline at end of file + key: ${JWT_SECRET_KEY} + refresh: ${JWT_REFRESH_KEY} +coolsms: + apiKey: ${COOL_SMS_KEY} + apiSecret: ${COOL_SMS_SECRET_KEY} + senderNumber: ${COOL_SMS_SEND_NUMBER} \ No newline at end of file diff --git a/out/production/classes/com/app/treen/TreenApplication.class b/out/production/classes/com/app/treen/TreenApplication.class deleted file mode 100644 index d56827e..0000000 Binary files a/out/production/classes/com/app/treen/TreenApplication.class and /dev/null differ diff --git a/out/production/classes/com/app/treen/user/controller/UserController.class b/out/production/classes/com/app/treen/user/controller/UserController.class deleted file mode 100644 index bd798ac..0000000 Binary files a/out/production/classes/com/app/treen/user/controller/UserController.class and /dev/null differ diff --git a/src/main/generated/com/app/treen/products/entity/QTransProduct.java b/src/main/generated/com/app/treen/products/entity/QTransProduct.java index c64cfa6..2c46a25 100644 --- a/src/main/generated/com/app/treen/products/entity/QTransProduct.java +++ b/src/main/generated/com/app/treen/products/entity/QTransProduct.java @@ -35,8 +35,6 @@ public class QTransProduct extends EntityPathBase { public final NumberPath id = createNumber("id", Long.class); - public final ListPath images = this.createList("images", TransPImg.class, QTransPImg.class, PathInits.DIRECT2); - public final NumberPath likedCount = createNumber("likedCount", Long.class); public final EnumPath method = createEnum("method", com.app.treen.products.entity.enumeration.Method.class); diff --git a/src/main/java/com/app/treen/TreenApplication.java b/src/main/java/com/app/treen/TreenApplication.java index 0fe7e89..a4f5adc 100644 --- a/src/main/java/com/app/treen/TreenApplication.java +++ b/src/main/java/com/app/treen/TreenApplication.java @@ -8,7 +8,7 @@ @EnableJpaAuditing @EnableJpaRepositories(basePackages = "com.app.treen.jpa.repository") -@EnableMongoRepositories(basePackages = "com.app.treen.mongo") +@EnableMongoRepositories(basePackages = "com.app.treen.mongo.repository") @SpringBootApplication public class TreenApplication { diff --git a/src/main/java/com/app/treen/chat_room/controller/ChatRoomController.java b/src/main/java/com/app/treen/chat_room/controller/ChatRoomController.java index 7779252..6af93d8 100644 --- a/src/main/java/com/app/treen/chat_room/controller/ChatRoomController.java +++ b/src/main/java/com/app/treen/chat_room/controller/ChatRoomController.java @@ -1,124 +1,57 @@ -//package com.app.treen.chat_room.controller; -// -//import com.app.treen.chat_room.dto.request.ChatRoomRequestDto; -//import com.app.treen.chat_room.dto.request.MessageRequestDto; -//import com.app.treen.chat_room.dto.response.*; -//import com.app.treen.chat_room.service.ChatRoomService; -//import com.app.treen.common.jwt.service.dto.CustomUserDetails; -//import com.app.treen.common.response.ResponseEntity; -//import jakarta.validation.Valid; -//import lombok.RequiredArgsConstructor; -//import org.springframework.http.ResponseEntity; -//import org.springframework.messaging.handler.annotation.MessageMapping; -//import org.springframework.messaging.simp.SimpMessageSendingOperations; -//import org.springframework.security.core.annotation.AuthenticationPrincipal; -//import org.springframework.web.bind.annotation.*; -//import reactor.core.publisher.Flux; -//import reactor.core.publisher.Mono; -// -//import java.util.List; -// -//@RestController -//@RequiredArgsConstructor -//public class ChatRoomController { -// -// private final SimpMessageSendingOperations template; -// private final ChatRoomService chatRoomService; -// -// // 채팅리스트 반환 -//// @GetMapping("/chat/{id}") -//// public ResponseEntity> getChatMessages(@PathVariable Long id) { -//// // 임시로 리스트 형식으로 구현, 실제로는 DB 접근 필요 -//// ChatMessageResponseDto test = new ChatMessageResponseDto(1L, "test", "test"); -//// return ResponseEntity.ok().body(List.of(test)); -//// } -// -// // 메시지 송신 및 수신, /pub가 생략된 모습. 클라이언트 단에서는 /pub/message 로 요청 -//// @MessageMapping("/message") -//// public ResponseEntity receiveMessage(@RequestBody MessageResponseDto chat) { -//// // 메시지를 해당 채팅방 구독자들에게 전송 -//// template.convertAndSend("/sub/chatroom/1", chat); -//// return ResponseEntity.ok().build(); -//// } -// -// // 채팅방 목록 조회 -// @GetMapping("/auth/chat-list") -// public ResponseEntity getChatRooms(@AuthenticationPrincipal CustomUserDetails userDetails) { -// -// ChatRoomsResponse chatRoomsResponse = ChatRoomsResponse.builder() -// .chatRooms(chatRoomService.getChatRooms(userDetails.getMember().getId())) -// .build(); -// -// return ResponseEntity.onSuccess(chatRoomsResponse); -// } -// -// // 채팅방 조회 -// @GetMapping("auth/{chatRoomId}") -// public Mono> getChatRoomDetail(@AuthenticationPrincipal CustomUserDetails userDetails, -// @PathVariable Long chatRoomId) { -// -// return chatRoomService.getChatRoomDetail(userDetails.getMember().getId(), chatRoomId) -// .map(ResponseEntity::onSuccess); -// } -// -// // 채팅방 생성 -// @PostMapping("auth/create") -// public ResponseEntity createChatRoom(@AuthenticationPrincipal CustomUserDetails userDetails, -// @Valid @RequestBody ChatRoomRequestDto dto) { -// Long chatRoomId = chatRoomService.saveChatRoom(userDetails.getMember().getId(), -// dto.getSellerId(), dto.getProductId()); -// return ResponseEntity.onSuccess(ChatRoomCreateResponse.builder() -// .chatRoomId(chatRoomId) -// .build()); -// } -// -// // 이전 채팅 메시지들 조회 -// @GetMapping("/find/chat/list/{id}") -// public Mono>> findMessages(@PathVariable("id") Long id) { -// Flux response = chatRoomService.findChatMessages(id); -// return response.collectList().map(ResponseEntity::onSuccess); -// } -// -// // 메시지 송신 및 수신 -// @MessageMapping("/message") -// public Mono> receiveMessage(@RequestBody MessageRequestDto chat) { -// return chatRoomService.saveChatMessage(chat).doOnNext(message -> { -// // 메시지를 해당 채팅방 구독자들에게 전송 -// template.convertAndSend("/sub/chatroom/" + chat.getRoomId(), -// MessageResponseDto.of(message)); -// -// }).thenReturn(ResponseEntity.ok().build()); -// } -// -//} -/* +package com.app.treen.chat_room.controller; + +import com.app.treen.chat_room.dto.request.ChatRoomRequestDto; +import com.app.treen.chat_room.dto.request.MessageRequestDto; +import com.app.treen.chat_room.dto.response.*; +import com.app.treen.chat_room.service.ChatRoomService; +import com.app.treen.user.service.CustomUserDetails; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/chatroom") +public class ChatRoomController { + + private final SimpMessageSendingOperations template; + private final ChatRoomService chatRoomService; + // 채팅방 목록 조회 @GetMapping("/auth/chat-list") public ResponseEntity getChatRooms(@AuthenticationPrincipal CustomUserDetails userDetails) { ChatRoomsResponse chatRoomsResponse = ChatRoomsResponse.builder() - .chatRooms(chatRoomService.getChatRooms(userDetails.getMember().getId())) + .chatRooms(chatRoomService.getChatRooms(userDetails.getUser().getId())) .build(); - return ResponseEntity.onSuccess(chatRoomsResponse); + return ResponseEntity.ok(chatRoomsResponse); } // 채팅방 조회 @GetMapping("auth/{chatRoomId}") public Mono> getChatRoomDetail(@AuthenticationPrincipal CustomUserDetails userDetails, - @PathVariable Long chatRoomId) { + @PathVariable Long chatRoomId) { - return chatRoomService.getChatRoomDetail(userDetails.getMember().getId(), chatRoomId) - .map(ResponseEntity::onSuccess); + return chatRoomService.getChatRoomDetail(userDetails.getUser().getId(), chatRoomId) + .map(ResponseEntity::ok); } // 채팅방 생성 @PostMapping("auth/create") public ResponseEntity createChatRoom(@AuthenticationPrincipal CustomUserDetails userDetails, - @Valid @RequestBody ChatRoomRequestDto dto) { - Long chatRoomId = chatRoomService.saveChatRoom(userDetails.getMember().getId(), + @Valid @RequestBody ChatRoomRequestDto dto) { + Long chatRoomId = chatRoomService.saveChatRoom(userDetails.getUser().getId(), dto.getSellerId(), dto.getProductId()); - return ResponseEntity.onSuccess(ChatRoomCreateResponse.builder() + return ResponseEntity.ok(ChatRoomCreateResponse.builder() .chatRoomId(chatRoomId) .build()); } @@ -127,12 +60,39 @@ public ResponseEntity createChatRoom(@AuthenticationPrin @GetMapping("/find/chat/list/{id}") public Mono>> findMessages(@PathVariable("id") Long id) { Flux response = chatRoomService.findChatMessages(id); - return response.collectList().map(ResponseEntity::onSuccess); + return response.collectList().map(ResponseEntity::ok); + } + + // ======== 테스트 API 작성 ========== + + // 채팅방 조회 + @GetMapping("test/{chatRoomId}") + public Mono> getChatRoomDetailTest( + @PathVariable("chatRoomId") Long chatRoomId) { + + return chatRoomService.getChatRoomDetail(1L, chatRoomId) + .map(ResponseEntity::ok); + } + + @PostMapping("test/create") + public ResponseEntity createChatRoomTest( + @Valid @RequestBody ChatRoomRequestDto dto) { + Long chatRoomId = chatRoomService.saveChatRoom(1L, + dto.getSellerId(), dto.getProductId()); + return ResponseEntity.ok(ChatRoomCreateResponse.builder() + .chatRoomId(chatRoomId) + .build()); + } + + @GetMapping("test/find/chat/list/{id}") + public Mono>> findMessagesTest(@PathVariable("id") Long id) { + Flux response = chatRoomService.findChatMessages(id); + return response.collectList().map(ResponseEntity::ok); } - */ +} -// // 메시지 송신 및 수신 + // 메시지 송신 및 수신 // @MessageMapping("/message") // public Mono> receiveMessage(@RequestBody MessageRequestDto chat) { // return chatRoomService.saveChatMessage(chat).doOnNext(message -> { diff --git a/src/main/java/com/app/treen/chat_room/dto/response/ChatRoomDetailResponse.java b/src/main/java/com/app/treen/chat_room/dto/response/ChatRoomDetailResponse.java index 2e4ea76..450f523 100644 --- a/src/main/java/com/app/treen/chat_room/dto/response/ChatRoomDetailResponse.java +++ b/src/main/java/com/app/treen/chat_room/dto/response/ChatRoomDetailResponse.java @@ -1,6 +1,12 @@ package com.app.treen.chat_room.dto.response; import com.app.treen.message.document.Message; +import com.app.treen.products.entity.TransProduct; +import com.app.treen.products.entity.enumeration.Gender; +import com.app.treen.products.entity.enumeration.Method; +import com.app.treen.products.entity.enumeration.Size; +import com.app.treen.products.entity.enumeration.UsedRank; +import com.app.treen.transactions.entity.Transactions; import com.app.treen.user.entity.User; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; @@ -17,6 +23,8 @@ public class ChatRoomDetailResponse { private List messages; private MemberDto buyer; private MemberDto seller; + private TransProductDto transProduct; + private TransactionsDto transaction; @Data @Builder @@ -52,4 +60,56 @@ public static MessageDto from(Message message) { } } + @Data + @Builder + public static class TransProductDto { + private Long id; + private String name; + private String usedTerm; + private Size size; + private UsedRank usedRank; + private Long point; + private Method method; + private String category; + private String imageUrls; + + public static TransProductDto from(TransProduct transProduct) { + return TransProductDto.builder() + .id(transProduct.getId()) + .name(transProduct.getName()) + .usedTerm(transProduct.getUsedTerm()) + .size(transProduct.getSize()) + .point(transProduct.getPoint()) + .method(transProduct.getMethod()) + .category(transProduct.getCategory().toString()) + //.imageUrls(transProduct.) + .build(); + } + } + + @Data + @Builder + public static class TransactionsDto { + + private Long id; + private LocalDateTime transDate; + private boolean isDirect; + private String place; + private String deliveryAddress; + private String deliveryRequest; + private String deliveryFeeAccount; + + public static TransactionsDto from(Transactions transaction) { + return TransactionsDto.builder() + .id(transaction.getId()) + .transDate(transaction.getTransDate()) + .isDirect(transaction.isDirect()) + .place(transaction.getPlace()) + .deliveryAddress(transaction.getDeliveryAddress()) + .deliveryRequest(transaction.getDeliveryRequest()) + .deliveryFeeAccount(transaction.getDeliveryFeeAccount()) + .build(); + } + } + } diff --git a/src/main/java/com/app/treen/chat_room/entity/ChatRoom.java b/src/main/java/com/app/treen/chat_room/entity/ChatRoom.java index 72fd0f6..d05da89 100644 --- a/src/main/java/com/app/treen/chat_room/entity/ChatRoom.java +++ b/src/main/java/com/app/treen/chat_room/entity/ChatRoom.java @@ -18,6 +18,7 @@ public class ChatRoom extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "chat_room_id") private Long id; private String title; private boolean isReserved; // 거래 예약 여부 diff --git a/src/main/java/com/app/treen/chat_room/service/ChatRoomService.java b/src/main/java/com/app/treen/chat_room/service/ChatRoomService.java index 95bee15..67ac20c 100644 --- a/src/main/java/com/app/treen/chat_room/service/ChatRoomService.java +++ b/src/main/java/com/app/treen/chat_room/service/ChatRoomService.java @@ -1,4 +1,3 @@ -/* package com.app.treen.chat_room.service; import com.app.treen.common.response.code.status.ErrorStatus; @@ -49,8 +48,8 @@ public long saveChatRoom(Long loginMemberId, Long sellerId, Long productId) { .orElseThrow()) .seller(userRepository.findById(sellerId) .orElseThrow()) - .transProduct(transProductRepository.findById(productId) - .orElseThrow()) + //.transProduct(transProductRepository.findById(productId) + //.orElseThrow()) //.isReserved() .build(); @@ -67,7 +66,7 @@ public List getChatRooms(Long loginMemberId) { .unreadCount(getUnreadCount(chatRoom, member)) //.otherMember(getOtherMemberDto(chatRoom.getOtherMember(loginMemberId))) .lastMessage(getLastMessageDto(chatRoom)) - .isReserved().build()) + .isReserved(false).build()) .collect(Collectors.toList()); } @@ -125,6 +124,10 @@ public void leaveChatRoom(Long chatRoomId) { } } +// private ChatRoomResponse.MemberDto getOtherMemberDto(Member otherMember) { +// return ChatRoomResponse.MemberDto.of(otherMember, fileManager.getFullPath(otherMember.getImage())); +// } + // 채팅방 마지막 메시지 조회 // private ChatRoomResponse.MessageDto getLastMessageDto(ChatRoom chatRoom) { // return messageRepository.findFirstByChatRoomOrderByIdDesc(chatRoom) @@ -133,4 +136,4 @@ public void leaveChatRoom(Long chatRoomId) { // // } } -*/ + diff --git a/src/main/java/com/app/treen/common/config/ChattingConfig.java b/src/main/java/com/app/treen/common/config/ChattingConfig.java index 955c152..87fc1c1 100644 --- a/src/main/java/com/app/treen/common/config/ChattingConfig.java +++ b/src/main/java/com/app/treen/common/config/ChattingConfig.java @@ -15,6 +15,7 @@ public class ChattingConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; + /** * 클라이언트에서 WebSocket에 접속할 수 있는 endpoint를 지정 * @param registry @@ -23,7 +24,7 @@ public class ChattingConfig implements WebSocketMessageBrokerConfigurer { public void registerStompEndpoints(StompEndpointRegistry registry) { // stomp 접속 주소 url = ws://localhost:8080/ws, 프로토콜이 http가 아니다! registry.addEndpoint("/ws") // 연결될 엔드포인트 - .setAllowedOrigins("*").withSockJS(); + .setAllowedOrigins("http://localhost:3000").withSockJS(); } /** diff --git a/src/main/java/com/app/treen/common/config/RedisConfig.java b/src/main/java/com/app/treen/common/config/RedisConfig.java index ad65c9b..dc0bb2f 100644 --- a/src/main/java/com/app/treen/common/config/RedisConfig.java +++ b/src/main/java/com/app/treen/common/config/RedisConfig.java @@ -1,14 +1,18 @@ package com.app.treen.common.config; import com.app.treen.common.redis.service.RedisMessageStringSubscriber; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.messaging.simp.SimpMessagingTemplate; @Configuration public class RedisConfig { @@ -36,18 +40,36 @@ public RedisTemplate stringValueRedisTemplate() { * Redis의 channel 로부터 메시지를 수신받아 해당 MessageListenerAdapter 에게 디스패치 */ @Bean - public RedisMessageListenerContainer redisContainer() { + public RedisMessageListenerContainer redisContainer( + RedisConnectionFactory redisConnectionFactory, + MessageListenerAdapter messageStringListener) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(redisConnectionFactory()); - return container; // 초기에는 토픽이 비어있음 + container.setConnectionFactory(redisConnectionFactory); + + // PatternTopic을 사용하여 chatroom/* 형식의 모든 채널을 구독 + container.addMessageListener(messageStringListener, new PatternTopic("chatroom/*")); + + return container; } /** * 메시지 수신 리스너 어댑터 (메시지 핸들러를 설정) */ @Bean - public MessageListenerAdapter messageStringListener() { - return new MessageListenerAdapter(new RedisMessageStringSubscriber(), "onMessage"); + public MessageListenerAdapter messageStringListener(SimpMessagingTemplate simpMessagingTemplate, ObjectMapper objectMapper) { + return new MessageListenerAdapter(new RedisMessageStringSubscriber(simpMessagingTemplate, objectMapper), "onMessage"); + } + + + @Primary + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); // 이 부분을 확인 + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; } // /** diff --git a/src/main/java/com/app/treen/common/config/S3Uploader.java b/src/main/java/com/app/treen/common/config/S3Uploader.java index d45bcc9..294c3ad 100644 --- a/src/main/java/com/app/treen/common/config/S3Uploader.java +++ b/src/main/java/com/app/treen/common/config/S3Uploader.java @@ -32,18 +32,25 @@ public class S3Uploader { @Value("${cloud.aws.region.static}") private String region; // S3 리전 값 + private String defaultImageUrl = "default.url"; + // 단일 파일 업로드 public String upload(MultipartFile multipartFile, String dirName) throws IOException { - File uploadFile = convert(multipartFile) - .orElseThrow(() -> new IOException("Failed to create a file from MultipartFile")); + if (multipartFile == null || multipartFile.isEmpty()) { + log.warn("파일이 제공되지 않음. 기본 URL 반환"); + return defaultImageUrl; // 파일이 없으면 기본 이미지 반환 + } + + File uploadFile = convert(multipartFile); return upload(uploadFile, dirName); } + // 다중 파일 업로드 public List upload(List multipartFiles, String dirName) throws IOException { List fileUrls = new ArrayList<>(); for (MultipartFile file : multipartFiles) { - fileUrls.add(upload(file, dirName)); + fileUrls.add(upload(file, dirName)); // 개별적으로 업로드 수행 } return fileUrls; } @@ -73,14 +80,16 @@ private void removeNewFile(File targetFile) { } } - private Optional convert(MultipartFile multipartFile) throws IOException { - File convertFile = new File(System.getProperty("java.io.tmpdir") + "/" + multipartFile.getOriginalFilename()); - if (convertFile.createNewFile()) { - try (FileOutputStream fos = new FileOutputStream(convertFile)) { - fos.write(multipartFile.getBytes()); - } - return Optional.of(convertFile); + private File convert(MultipartFile multipartFile) throws IOException { + if (multipartFile == null || multipartFile.isEmpty()) { + throw new IOException("파일이 제공되지 않았습니다."); } - return Optional.empty(); + + File convertFile = File.createTempFile(UUID.randomUUID().toString(), "_" + multipartFile.getOriginalFilename()); + try (FileOutputStream fos = new FileOutputStream(convertFile)) { + fos.write(multipartFile.getBytes()); + } + return convertFile; } + } \ No newline at end of file diff --git a/src/main/java/com/app/treen/common/config/WebSecurityConfig.java b/src/main/java/com/app/treen/common/config/WebSecurityConfig.java index ea8f861..47d1a48 100644 --- a/src/main/java/com/app/treen/common/config/WebSecurityConfig.java +++ b/src/main/java/com/app/treen/common/config/WebSecurityConfig.java @@ -61,7 +61,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/swagger-ui/**", // Swagger UI "/v3/api-docs/**", "/swagger-resources/**", - "/webjars/**" + "/webjars/**", + "/ws/**", + "/**" ).permitAll() .requestMatchers("/api/auth/**").permitAll() // 인증이 필요한 경로 (JWT 필요) @@ -78,13 +80,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // CORS 설정 @Bean public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.addAllowedOrigin("*"); + configuration.addAllowedOrigin("http://localhost:3000"); // 특정 도메인 허용 configuration.addAllowedHeader("*"); // 모든 헤더 허용 configuration.addAllowedMethod("*"); // 모든 HTTP 메서드 허용 configuration.setAllowCredentials(true); // 인증 정보 포함 요청 허용 - configuration.addAllowedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); // 모든 경로에 대해 CORS 정책 적용 @@ -92,4 +92,5 @@ public CorsConfigurationSource corsConfigurationSource() { } + } \ No newline at end of file diff --git a/src/main/java/com/app/treen/common/redis/service/RedisMessageStringSubscriber.java b/src/main/java/com/app/treen/common/redis/service/RedisMessageStringSubscriber.java index dc2fdcc..2dddf8a 100644 --- a/src/main/java/com/app/treen/common/redis/service/RedisMessageStringSubscriber.java +++ b/src/main/java/com/app/treen/common/redis/service/RedisMessageStringSubscriber.java @@ -1,7 +1,10 @@ package com.app.treen.common.redis.service; +import com.app.treen.message.dto.MessageRequest; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; /** @@ -11,11 +14,20 @@ @RequiredArgsConstructor @Service public class RedisMessageStringSubscriber { + private final SimpMessagingTemplate simpMessagingTemplate; + private final ObjectMapper objectMapper; // Redis 메시지를 수신했을 때 호출 public void onMessage(String message, String channel) { log.info("Received message: {} from channel: {}", message, channel); + try { + MessageRequest receivedMessage = objectMapper.readValue(message, MessageRequest.class); + String destination = "/sub/chatroom/" + receivedMessage.getRoomId(); + simpMessagingTemplate.convertAndSend(destination, receivedMessage); + } catch (Exception e) { + log.error("Failed to process received message", e); + } // 실시간 거래 데이터 처리 // =========== diff --git a/src/main/java/com/app/treen/common/redis/service/RedisTopicManager.java b/src/main/java/com/app/treen/common/redis/service/RedisTopicManager.java index 97fe61f..f7dcf18 100644 --- a/src/main/java/com/app/treen/common/redis/service/RedisTopicManager.java +++ b/src/main/java/com/app/treen/common/redis/service/RedisTopicManager.java @@ -15,13 +15,13 @@ public class RedisTopicManager { // 채널 추가 및 구독 public void addTopic(Long roomId) { - ChannelTopic channelTopic = new ChannelTopic("ch" + roomId); + ChannelTopic channelTopic = new ChannelTopic("chatroom/" + roomId); redisContainer.addMessageListener(messageListener, channelTopic); } // 채널 제거 및 구독 취소 public void removeTopic(Long roomId) { - ChannelTopic channelTopic = new ChannelTopic("ch" + roomId); + ChannelTopic channelTopic = new ChannelTopic("chatroom/" + roomId); redisContainer.removeMessageListener(messageListener, channelTopic); } diff --git a/src/main/java/com/app/treen/common/response/code/status/ErrorStatus.java b/src/main/java/com/app/treen/common/response/code/status/ErrorStatus.java index 0afdc3d..af7313a 100644 --- a/src/main/java/com/app/treen/common/response/code/status/ErrorStatus.java +++ b/src/main/java/com/app/treen/common/response/code/status/ErrorStatus.java @@ -36,7 +36,6 @@ public enum ErrorStatus implements BaseErrorCode { USER_IS_ALREADY_REGISTERED_KAKAO(HttpStatus.IM_USED, "USER4016", "이미 카카오계정으로 가입된 전화번호입니다. 카카오로 로그인해주세요."), USER_IS_INTEGRATED_KAKAO(HttpStatus.ACCEPTED, "USER4017", "사용자의 계정이 카카오계정과 통합되었습니다. YESOL 계정 혹은 카카오로 로그인해주세요."), - // 관리자 관련 에러 NOT_ADMIN(HttpStatus.UNAUTHORIZED, "ADMIN4001", "관리자의 권한이 없습니다."), @@ -128,9 +127,17 @@ public enum ErrorStatus implements BaseErrorCode { BOARDCOMPLAINT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMPLAINT4001", "신고내역을 찾지 못했습니다."), COMMENTCOMPLAINT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMENT4001", "신고내역을 찾지 못했습니다."), PINCOMPLAINT_NOT_FOUND(HttpStatus.NOT_FOUND, "PIN4001", "신고내역을 찾지 못했습니다."), - NOT_FOUND_CHAT_ROOM(HttpStatus.NOT_FOUND, "CHATROOM4001", "채팅방을 찾지 못했습니다."), + // 채팅방 에러 + NOT_FOUND_CHAT_ROOM(HttpStatus.NOT_FOUND, "CHATROOM4001", "채팅방을 찾지 못했습니다."), + // 이미지 에러 + IMAGE_MUST_BE_UPLOADED(HttpStatus.BAD_REQUEST, "IMAGE4001", "이미지는 1장 이상 업로드해야합니다."), + IMAGE_OVER_UPLOADED(HttpStatus.BAD_REQUEST, "IMAGE4001", "이미지는 5장까지만 업로드할 수 있습니다."), + // 상품거래 예약 에러 + NOT_FOUND_TRANSACTIONS(HttpStatus.NOT_FOUND, "TRANSACTIONS4001", "상품거래 예약을 찾지 못했습니다."), + PERMISSION_DENIED_TRANSACTIONS(HttpStatus.UNAUTHORIZED, "USER4001", "타인의 예약 내역은 수정 및 삭제할 수 없습니다."), + SHOULD_BE_BOOKED(HttpStatus.BAD_REQUEST, "TRANSACTIONS400", "판매완료 및 예약 전 상품은 예약 취소할 수 없습니다."), // 상품 에러 PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "PRODUCT4001", "상품을 찾을 수 없습니다."), diff --git a/src/main/java/com/app/treen/jpa/repository/products/TradePImgRepository.java b/src/main/java/com/app/treen/jpa/repository/products/TradePImgRepository.java index 4ee8879..3791a55 100644 --- a/src/main/java/com/app/treen/jpa/repository/products/TradePImgRepository.java +++ b/src/main/java/com/app/treen/jpa/repository/products/TradePImgRepository.java @@ -4,6 +4,13 @@ import com.app.treen.products.entity.TradeProduct; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + public interface TradePImgRepository extends JpaRepository { void deleteByTradeProduct(TradeProduct existingProduct); + + Optional findByTradeProductAndIsMainTrue(TradeProduct selectedProduct); + + List findAllByTradeProduct(TradeProduct selectedProduct); } diff --git a/src/main/java/com/app/treen/jpa/repository/products/TransPImgRepository.java b/src/main/java/com/app/treen/jpa/repository/products/TransPImgRepository.java index cb3b2c2..9455512 100644 --- a/src/main/java/com/app/treen/jpa/repository/products/TransPImgRepository.java +++ b/src/main/java/com/app/treen/jpa/repository/products/TransPImgRepository.java @@ -4,6 +4,13 @@ import com.app.treen.products.entity.TransProduct; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + public interface TransPImgRepository extends JpaRepository { void deleteByTransProduct(TransProduct existingProduct); + + Optional findFirstByTransProductAndIsMainTrue(TransProduct product); + + List findByTransProduct(TransProduct selectedProduct); } diff --git a/src/main/java/com/app/treen/jpa/repository/products/WishCategoryRepository.java b/src/main/java/com/app/treen/jpa/repository/products/WishCategoryRepository.java index 6d1e7cd..393ac3d 100644 --- a/src/main/java/com/app/treen/jpa/repository/products/WishCategoryRepository.java +++ b/src/main/java/com/app/treen/jpa/repository/products/WishCategoryRepository.java @@ -4,6 +4,10 @@ import com.app.treen.products.entity.WishCategory; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface WishCategoryRepository extends JpaRepository { void deleteByTradeProduct(TradeProduct existingProduct); + + List findAllByTradeProduct(TradeProduct selectedProduct); } diff --git a/src/main/java/com/app/treen/jpa/repository/trade/TradeRepository.java b/src/main/java/com/app/treen/jpa/repository/trade/TradeRepository.java new file mode 100644 index 0000000..57e334d --- /dev/null +++ b/src/main/java/com/app/treen/jpa/repository/trade/TradeRepository.java @@ -0,0 +1,7 @@ +package com.app.treen.jpa.repository.trade; + +import com.app.treen.trade.entity.Trade; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TradeRepository extends JpaRepository { +} diff --git a/src/main/java/com/app/treen/jpa/repository/transactions/TransactionsRepository.java b/src/main/java/com/app/treen/jpa/repository/transactions/TransactionsRepository.java new file mode 100644 index 0000000..6d27984 --- /dev/null +++ b/src/main/java/com/app/treen/jpa/repository/transactions/TransactionsRepository.java @@ -0,0 +1,10 @@ +package com.app.treen.jpa.repository.transactions; + +import com.app.treen.transactions.entity.Transactions; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TransactionsRepository extends JpaRepository { + + + +} diff --git a/src/main/java/com/app/treen/message/controller/MessageController.java b/src/main/java/com/app/treen/message/controller/MessageController.java index e5189f4..803079e 100644 --- a/src/main/java/com/app/treen/message/controller/MessageController.java +++ b/src/main/java/com/app/treen/message/controller/MessageController.java @@ -23,7 +23,7 @@ public class MessageController { private final MessageService messageService; //private final SimpMessagingTemplate template; - private RedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; private final ObjectMapper objectMapper; // 메시지 송신 및 수신 @@ -35,9 +35,9 @@ public Mono> receiveMessage(@Valid MessageRequest messageRe try { // 메시지를 Redis 채널로 퍼블리시 String publishMessage = new ObjectMapper().writeValueAsString(messageRequest); - redisTemplate.convertAndSend("chatroom:" + messageRequest.getRoomId(), publishMessage); + redisTemplate.convertAndSend("chatroom/" + messageRequest.getRoomId(), publishMessage); - log.info("Message published to Redis channel: {}", "chatroom:" + messageRequest.getRoomId()); + log.info("Message published to Redis channel: {}", "chatroom/" + messageRequest.getRoomId()); // 응답 반환 return Mono.just(ResponseEntity.ok().build()); diff --git a/src/main/java/com/app/treen/products/controller/ProductController.java b/src/main/java/com/app/treen/products/controller/ProductController.java index 7ee2ac8..7c1d155 100644 --- a/src/main/java/com/app/treen/products/controller/ProductController.java +++ b/src/main/java/com/app/treen/products/controller/ProductController.java @@ -3,10 +3,7 @@ import com.app.treen.common.response.code.status.SuccessStatus; import com.app.treen.products.dto.ProductQueryHelper; import com.app.treen.products.dto.TradeQueryHelper; -import com.app.treen.products.dto.request.TradeProductSaveDto; -import com.app.treen.products.dto.request.TradeProductUpdateDto; -import com.app.treen.products.dto.request.TransProductSaveDto; -import com.app.treen.products.dto.request.TransProductUpdateDto; +import com.app.treen.products.dto.request.*; import com.app.treen.products.dto.response.TradeProductResponseDto; import com.app.treen.products.dto.response.TradeResponseListDto; import com.app.treen.products.dto.response.TransProductResponseDto; @@ -38,12 +35,10 @@ public class ProductController { @PostMapping(value = "/transaction/save", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) public ResponseEntity saveTransProduct( @RequestPart("images") List images, // 이미지 리스트로 변경 - @RequestPart("product") TransProductSaveDto requestDto, - User user + @RequestPart("product") TransactionRequestDto requestDto, + @AuthenticationPrincipal CustomUserDetails userDetails ) throws IOException { - // User user = new User(); // 현재 사용자의 정보를 가져오는 로직 추가 필요 - TransProductResponseDto responseDto = productService.saveTransProduct(requestDto,images, user); - + TransProductResponseDto responseDto = productService.saveTransProduct(requestDto,images, userDetails.getUser()); return ResponseEntity.status(HttpStatus.OK).body(responseDto); } @@ -141,17 +136,19 @@ public ResponseEntity> getFilteredTradeResults( @Operation(summary = "거래상품 좋아요 및 취소", description = "좋아요 되어있을 경우 취소, 안되어있을 경우 좋아요 등록됩니다.") @GetMapping("/transaction/like/{productId}") - public ResponseEntity registerLikesTransProduct(@PathVariable Long productId, User user){ - boolean isLike = productService.increaseLikeTransaction(productId, user); + public ResponseEntity registerLikesTransProduct(@PathVariable Long productId, @AuthenticationPrincipal CustomUserDetails userDetails){ + boolean isLike = productService.increaseLikeTransaction(productId, userDetails.getUser()); return isLike ? ResponseEntity.status(HttpStatus.OK).body(SuccessStatus.PIN_LIKE) : ResponseEntity.status(HttpStatus.OK).body(SuccessStatus.PIN_UNLIKE); } @Operation(summary = "교환상품 좋아요 및 취소", description = "좋아요 되어있을 경우 취소, 안되어있을 경우 좋아요 등록됩니다.") @GetMapping("/trade/like/{productId}") - public ResponseEntity registerLikesTradeProduct(@PathVariable Long productId, User user){ - boolean isLike = productService.increaseLikeTrade(productId, user); + public ResponseEntity registerLikesTradeProduct(@PathVariable Long productId, @AuthenticationPrincipal CustomUserDetails userDetails){ + boolean isLike = productService.increaseLikeTrade(productId, userDetails.getUser()); return isLike ? ResponseEntity.status(HttpStatus.OK).body(SuccessStatus.PIN_LIKE) : ResponseEntity.status(HttpStatus.OK).body(SuccessStatus.PIN_UNLIKE); } + + } diff --git a/src/main/java/com/app/treen/products/dto/request/ImgRequestDto.java b/src/main/java/com/app/treen/products/dto/request/ImgRequestDto.java index 7ff7eff..ce0a60a 100644 --- a/src/main/java/com/app/treen/products/dto/request/ImgRequestDto.java +++ b/src/main/java/com/app/treen/products/dto/request/ImgRequestDto.java @@ -5,20 +5,17 @@ import com.app.treen.products.entity.TradeProduct; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import java.util.ArrayList; import java.util.List; @Getter +@Setter @NoArgsConstructor public class ImgRequestDto { - private List imageUrls; - public ImgRequestDto(List imageUrls) { - this.imageUrls = imageUrls; - } - - public List toImageEntities(TradeProduct tradeProduct) { + public List toImageEntities(TradeProduct tradeProduct , List imageUrls) { List images = new ArrayList<>(); for (int i = 0; i < imageUrls.size(); i++) { images.add(TradePImg.builder() diff --git a/src/main/java/com/app/treen/products/dto/request/RegionRequestDto.java b/src/main/java/com/app/treen/products/dto/request/RegionRequestDto.java index 7bdb9c0..a25a7ba 100644 --- a/src/main/java/com/app/treen/products/dto/request/RegionRequestDto.java +++ b/src/main/java/com/app/treen/products/dto/request/RegionRequestDto.java @@ -22,10 +22,6 @@ public List toRegionEntities(TradeProduct tradeProduct, List tradeRegions = new ArrayList<>(); for (Region region : regions) { tradeRegions.add(TradeRegion.builder() diff --git a/src/main/java/com/app/treen/products/dto/request/TradeProductSaveDto.java b/src/main/java/com/app/treen/products/dto/request/TradeProductSaveDto.java index 4b3e8ed..48cab2c 100644 --- a/src/main/java/com/app/treen/products/dto/request/TradeProductSaveDto.java +++ b/src/main/java/com/app/treen/products/dto/request/TradeProductSaveDto.java @@ -25,21 +25,7 @@ public class TradeProductSaveDto { private TradeType tradeType; private Long categoryId; - private List imageUrls = new ArrayList<>(); // 이미지 리스트 - private List wishCategoryIds = new ArrayList<>(); // 희망 카테고리 리스트 - private List regionIds = new ArrayList<>(); // 거래 가능 지역 리스트 - public void setImageUrls(List imageUrls) { - this.imageUrls = (imageUrls != null) ? imageUrls : new ArrayList<>(); - } - - public void setWishCategoryIds(List wishCategoryIds) { - this.wishCategoryIds = (wishCategoryIds != null) ? wishCategoryIds : new ArrayList<>(); - } - - public void setRegionIds(List regionIds) { - this.regionIds = (regionIds != null) ? regionIds : new ArrayList<>(); - } public TradeProduct toEntity(User user, Category category) { return TradeProduct.builder() @@ -58,46 +44,5 @@ public TradeProduct toEntity(User user, Category category) { .build(); } - public List toImageEntities(TradeProduct tradeProduct) { - List images = new ArrayList<>(); - for (int i = 0; i < imageUrls.size(); i++) { - images.add(TradePImg.builder() - .tradeProduct(tradeProduct) - .imgUrl(imageUrls.get(i)) - .sortOrder(i) - .isMain(i == 0) // 첫 번째 이미지를 대표 이미지로 설정 - .build()); - } - return images; - } - - public List toWishCategoryEntities(TradeProduct tradeProduct, List wishCategories) { - List wishCategoryEntities = new ArrayList<>(); - for (Category category : wishCategories) { - wishCategoryEntities.add(WishCategory.builder() - .tradeProduct(tradeProduct) - .category(category) - .build()); - } - return wishCategoryEntities; - } - - public List toRegionEntities(TradeProduct tradeProduct, List regions) { - if (tradeProduct == null) { - throw new IllegalArgumentException("TradeProduct cannot be null"); - } - if (regions == null || regions.isEmpty()) { - throw new IllegalArgumentException("Regions cannot be null or empty"); - } - - List tradeRegions = new ArrayList<>(); - for (Region region : regions) { - tradeRegions.add(TradeRegion.builder() - .tradeProduct(tradeProduct) - .region(region) - .build()); - } - return tradeRegions; - } } diff --git a/src/main/java/com/app/treen/products/dto/request/TradeRequestDto.java b/src/main/java/com/app/treen/products/dto/request/TradeRequestDto.java new file mode 100644 index 0000000..60c5478 --- /dev/null +++ b/src/main/java/com/app/treen/products/dto/request/TradeRequestDto.java @@ -0,0 +1,22 @@ +package com.app.treen.products.dto.request; + + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class TradeRequestDto { + + private RegionRequestDto regionRequest; + private ImgRequestDto imgRequest; + private TradeProductSaveDto tradeProduct; + private WishCategoryRequestDto wishCategoryRequest; + + public TradeRequestDto(TradeProductSaveDto tradeProduct, ImgRequestDto imgRequest, RegionRequestDto regionRequest, WishCategoryRequestDto wishCategoryRequest){ + this.regionRequest = regionRequest; + this.tradeProduct = tradeProduct; + this.imgRequest = imgRequest; + this.wishCategoryRequest = wishCategoryRequest; + } +} diff --git a/src/main/java/com/app/treen/products/dto/request/TransImgRequestDto.java b/src/main/java/com/app/treen/products/dto/request/TransImgRequestDto.java index 181042c..913da59 100644 --- a/src/main/java/com/app/treen/products/dto/request/TransImgRequestDto.java +++ b/src/main/java/com/app/treen/products/dto/request/TransImgRequestDto.java @@ -1,4 +1,29 @@ package com.app.treen.products.dto.request; + +import com.app.treen.products.entity.TransPImg; +import com.app.treen.products.entity.TransProduct; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor public class TransImgRequestDto { + + public List toImageEntities(TransProduct transProduct, List imageUrls) { + List images = new ArrayList<>(); + for (int i = 0; i imageUrls = new ArrayList<>(); - private List regionIds = new ArrayList<>(); - public void setImageUrls(List imageUrls) { - this.imageUrls = imageUrls; - } public TransProduct toEntity(User user, Category category) { return TransProduct.builder() @@ -47,28 +44,22 @@ public TransProduct toEntity(User user, Category category) { .build(); } - public List toImageEntities(TransProduct transProduct) { - List images = new ArrayList<>(); - for (int i = 0; i < imageUrls.size(); i++) { - images.add(TransPImg.builder() - .transProduct(transProduct) - .imgUrl(imageUrls.get(i)) - .sortOrder(i) - .isMain(i == 0) // 첫 번째 이미지를 대표 이미지로 설정 - .build()); - } - return images; - } - public List toRegionEntities(TransProduct transProduct, List regions) { - List transRegions = new ArrayList<>(); - for (Region region : regions) { - transRegions.add(TransRegion.builder() - .transProduct(transProduct) - .region(region) - .build()); + public static String formatRelativeTime(LocalDateTime createdDate) { + Duration duration = Duration.between(createdDate, LocalDateTime.now()); + long seconds = duration.getSeconds(); + long minutes = seconds / 60; + long hours = minutes / 60; + long days = hours / 24; + + if (seconds < 60) { + return seconds + "초 전"; + } else if (minutes < 60) { + return minutes + "분 전"; + } else if (hours < 24) { + return hours + "시간 전"; + } else { + return days + "일 전"; } - return transRegions; } - } diff --git a/src/main/java/com/app/treen/products/dto/request/TransRegionRequestDto.java b/src/main/java/com/app/treen/products/dto/request/TransRegionRequestDto.java new file mode 100644 index 0000000..079fff8 --- /dev/null +++ b/src/main/java/com/app/treen/products/dto/request/TransRegionRequestDto.java @@ -0,0 +1,33 @@ +package com.app.treen.products.dto.request; + +import com.app.treen.products.entity.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor +public class TransRegionRequestDto { + private List regionIds; + + public TransRegionRequestDto(List regionIds) { + this.regionIds = regionIds; + } + + public List toRegionEntities(TransProduct transProduct, List regions) { + if (transProduct == null) { + throw new IllegalArgumentException("TradeProduct cannot be null"); + } + + List transRegions = new ArrayList<>(); + for (Region region : regions) { + transRegions.add(TransRegion.builder() + .transProduct(transProduct) + .region(region) + .build()); + } + return transRegions; + } +} diff --git a/src/main/java/com/app/treen/products/dto/request/TransactionRequestDto.java b/src/main/java/com/app/treen/products/dto/request/TransactionRequestDto.java new file mode 100644 index 0000000..fe2e27c --- /dev/null +++ b/src/main/java/com/app/treen/products/dto/request/TransactionRequestDto.java @@ -0,0 +1,21 @@ +package com.app.treen.products.dto.request; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class TransactionRequestDto { + + private TransProductSaveDto productRequest; + private TransRegionRequestDto regionRequest; + private TransImgRequestDto imageRequest; + + public TransactionRequestDto(TransProductSaveDto productRequest, TransRegionRequestDto regionRequest, TransImgRequestDto imageRequest) { + this.productRequest = productRequest; + this.regionRequest = regionRequest; + this.imageRequest = imageRequest; + } +} \ No newline at end of file diff --git a/src/main/java/com/app/treen/products/dto/response/TradeProductResponseDto.java b/src/main/java/com/app/treen/products/dto/response/TradeProductResponseDto.java index 1af5996..f3de168 100644 --- a/src/main/java/com/app/treen/products/dto/response/TradeProductResponseDto.java +++ b/src/main/java/com/app/treen/products/dto/response/TradeProductResponseDto.java @@ -8,6 +8,7 @@ import java.time.Duration; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -42,7 +43,7 @@ public class TradeProductResponseDto { private Long writerId; - public TradeProductResponseDto(TradeProduct tradeProduct, List regions, User user) { + public TradeProductResponseDto(TradeProduct tradeProduct, List regions, List images,List wishCategories,User user) { this.id = tradeProduct.getId(); this.name = tradeProduct.getName(); this.usedTerm = tradeProduct.getUsedTerm(); @@ -56,15 +57,24 @@ public TradeProductResponseDto(TradeProduct tradeProduct, List regi this.likedCount = tradeProduct.getLikedCount(); this.wishSize = tradeProduct.getWishSize(); this.wishColor = tradeProduct.getWishColor(); - this.imageUrls = tradeProduct.getImages().stream() + this.imageUrls = images.stream() .map(TradePImg::getImgUrl) .collect(Collectors.toList()); - this.wishCategories = tradeProduct.getWishCategories().stream() - .map(wishCategory -> wishCategory.getCategory().getName()) - .collect(Collectors.toList()); - this.regions = regions.stream() - .map(tradeRegion -> tradeRegion.getRegion().getName()) - .collect(Collectors.toList()); + if (wishCategories != null) { + this.wishCategories = wishCategories.stream() + .map(wishCategory -> wishCategory.getCategory().getName()) + .collect(Collectors.toList()); + } else { + this.wishCategories = Collections.emptyList(); // 빈 리스트로 초기화 + } + + if (regions != null) { + this.regions = regions.stream() + .map(tradeRegion -> tradeRegion.getRegion().getName()) + .collect(Collectors.toList()); + } else { + this.regions = Collections.emptyList(); // 빈 리스트로 초기화 + } this.status = tradeProduct.getTransactionStatus(); this.tradeType = tradeProduct.getTradeType(); diff --git a/src/main/java/com/app/treen/products/dto/response/TradeResponseListDto.java b/src/main/java/com/app/treen/products/dto/response/TradeResponseListDto.java index d54cec7..8364aba 100644 --- a/src/main/java/com/app/treen/products/dto/response/TradeResponseListDto.java +++ b/src/main/java/com/app/treen/products/dto/response/TradeResponseListDto.java @@ -27,14 +27,9 @@ public class TradeResponseListDto { private Method method; // 거래 방법 private UsedRank usedRank; // 사용 - public TradeResponseListDto(TradeProduct tradeProduct) { + public TradeResponseListDto(TradeProduct tradeProduct, TradePImg tradePImg) { this.id = tradeProduct.getId(); - // 상품 대표 사진 (첫 번째 이미지를 대표 사진으로 설정) - this.representativeImage = tradeProduct.getImages().stream() - .map(TradePImg::getImgUrl) - .findFirst() - .orElse(null); - + this.representativeImage = tradePImg.getImgUrl(); this.name = tradeProduct.getName(); // 상품명 this.category = tradeProduct.getCategory().getName(); // 카테고리 this.likedCount = tradeProduct.getLikedCount(); // 좋아요 수 diff --git a/src/main/java/com/app/treen/products/dto/response/TransProductResponseDto.java b/src/main/java/com/app/treen/products/dto/response/TransProductResponseDto.java index 959c5fc..37b5335 100644 --- a/src/main/java/com/app/treen/products/dto/response/TransProductResponseDto.java +++ b/src/main/java/com/app/treen/products/dto/response/TransProductResponseDto.java @@ -1,5 +1,6 @@ package com.app.treen.products.dto.response; +import com.fasterxml.jackson.annotation.JsonInclude; import com.app.treen.products.entity.*; import com.app.treen.products.entity.enumeration.*; import com.app.treen.user.entity.User; @@ -11,6 +12,7 @@ import java.util.stream.Collectors; @Getter +@JsonInclude(JsonInclude.Include.NON_NULL) // ✅ NULL인 필드는 JSON에서 제외 public class TransProductResponseDto { private Long id; @@ -34,7 +36,7 @@ public class TransProductResponseDto { private Long writerId; - public TransProductResponseDto(TransProduct transProduct, List transRegions, User user) { + public TransProductResponseDto(TransProduct transProduct, List transRegions, List images, User user) { this.id = transProduct.getId(); this.name = transProduct.getName(); this.usedTerm = transProduct.getUsedTerm(); @@ -48,20 +50,24 @@ public TransProductResponseDto(TransProduct transProduct, List tran this.viewCount = transProduct.getViewCount(); this.likedCount = transProduct.getLikedCount(); - this.imageUrls = transProduct.getImages().stream() + // ✅ 이미지 URL 리스트 설정 + this.imageUrls = images.stream() .map(TransPImg::getImgUrl) .collect(Collectors.toList()); - this.regions = transRegions.stream() - .map(transRegion -> transRegion.getRegion().getName()) - .collect(Collectors.toList()); + // ✅ 지역 리스트 설정 (null이면 설정 안 함) + if (transRegions != null && !transRegions.isEmpty()) { + this.regions = transRegions.stream() + .map(transRegion -> transRegion.getRegion().getName()) + .collect(Collectors.toList()); + } - // 유저 정보 반환 + // ✅ 유저 정보 설정 this.userName = user.getUserName(); this.userImage = user.getProfileImgUrl(); this.writerId = user.getId(); - // createdDate 포맷팅 + // ✅ 생성 시간 포맷팅 this.createdDate = formatRelativeTime(transProduct.getCreatedDate()); } @@ -69,8 +75,6 @@ public TransProductResponseDto(TransProduct transProduct, List tran * 상대적인 시간 표현으로 변환 * @param createdDate 생성 시간 * @return 상대적 시간 - * / - */ private String formatRelativeTime(LocalDateTime createdDate) { Duration duration = Duration.between(createdDate, LocalDateTime.now()); diff --git a/src/main/java/com/app/treen/products/dto/response/TransResponseListDto.java b/src/main/java/com/app/treen/products/dto/response/TransResponseListDto.java index d95cc3b..f12da61 100644 --- a/src/main/java/com/app/treen/products/dto/response/TransResponseListDto.java +++ b/src/main/java/com/app/treen/products/dto/response/TransResponseListDto.java @@ -28,15 +28,11 @@ public class TransResponseListDto { private Method method; // 거래 방법 private UsedRank usedRank; // 사용 - public TransResponseListDto(TransProduct transProduct) { + public TransResponseListDto(TransProduct transProduct, TransPImg transPImg) { this.id = transProduct.getId(); // 상품 ID // 대표 사진 (첫 번째 이미지를 대표 사진으로 설정) - this.representativeImage = transProduct.getImages().stream() - .map(TransPImg::getImgUrl) - .findFirst() - .orElse(null); - + this.representativeImage = transPImg.getImgUrl(); this.name = transProduct.getName(); // 상품명 this.category = transProduct.getCategory().getName(); // 카테고리 this.likedCount = transProduct.getLikedCount(); // 좋아요 수 diff --git a/src/main/java/com/app/treen/products/entity/CategoryInitializer.java b/src/main/java/com/app/treen/products/entity/CategoryInitializer.java new file mode 100644 index 0000000..fb413a1 --- /dev/null +++ b/src/main/java/com/app/treen/products/entity/CategoryInitializer.java @@ -0,0 +1,24 @@ +package com.app.treen.products.entity; + +import com.app.treen.jpa.repository.products.CategoryRepository; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import java.util.List; + +@Configuration +public class CategoryInitializer { + + @Bean + public ApplicationRunner initCategories(CategoryRepository categoryRepository) { + return args -> { + if (categoryRepository.count() == 0) { // 중복 방지 + List categories = List.of( + "아우터", "상의", "하의", "원피스", + "신발", "잡화", "키즈" + ); + categories.forEach(name -> categoryRepository.save(new Category(null, name))); + } + }; + } +} diff --git a/src/main/java/com/app/treen/products/entity/TradeProduct.java b/src/main/java/com/app/treen/products/entity/TradeProduct.java index 82139c2..a0b065c 100644 --- a/src/main/java/com/app/treen/products/entity/TradeProduct.java +++ b/src/main/java/com/app/treen/products/entity/TradeProduct.java @@ -67,12 +67,6 @@ public class TradeProduct extends BaseTimeEntity { @Column(name = "liked_count", nullable = false) private Long likedCount = 0L; - @OneToMany(mappedBy = "tradeProduct", cascade = CascadeType.ALL) - private List images; - - @Builder.Default - @OneToMany(mappedBy = "tradeProduct", cascade = CascadeType.ALL, orphanRemoval = true) - private List wishCategories = new ArrayList<>(); @Enumerated(EnumType.STRING) @Column(name = "trade_type", nullable = false) @@ -96,25 +90,8 @@ public void updateDetail(TradeProductUpdateDto dto, Category category) { this.wishSize = dto.getWishSize(); this.tradeType = dto.getTradeType(); this.category = category; - // 이미지 업데이트 - updateImages(dto.getImageUrls()); } - private void updateImages(List imageUrls) { - this.images.clear(); - - // 새로운 이미지 목록 추가 - for (int i = 0; i < imageUrls.size(); i++) { - this.images.add(TradePImg.builder() - .tradeProduct(this) - .imgUrl(imageUrls.get(i)) - .sortOrder(i) - .isMain(i == 0) // 첫 번째 이미지를 대표 이미지로 설정 - .build()); - } - } - - } diff --git a/src/main/java/com/app/treen/products/entity/TransPImg.java b/src/main/java/com/app/treen/products/entity/TransPImg.java index 974b7eb..a2980d5 100644 --- a/src/main/java/com/app/treen/products/entity/TransPImg.java +++ b/src/main/java/com/app/treen/products/entity/TransPImg.java @@ -10,7 +10,7 @@ @AllArgsConstructor @Entity @Getter -@Table(name = "trade_product_img") +@Table(name = "trans_product_img") public class TransPImg { @Id @@ -27,7 +27,7 @@ public class TransPImg { @Column(name = "img_url", nullable = false) private String imgUrl; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "trans_product_id") private TransProduct transProduct; diff --git a/src/main/java/com/app/treen/products/entity/TransProduct.java b/src/main/java/com/app/treen/products/entity/TransProduct.java index a1f4080..c235adc 100644 --- a/src/main/java/com/app/treen/products/entity/TransProduct.java +++ b/src/main/java/com/app/treen/products/entity/TransProduct.java @@ -1,17 +1,11 @@ package com.app.treen.products.entity; import com.app.treen.BaseTimeEntity; -import com.app.treen.products.dto.request.TransProductUpdateDto; import com.app.treen.products.entity.enumeration.*; import com.app.treen.user.entity.User; import jakarta.persistence.*; import lombok.*; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - @Getter @NoArgsConstructor @AllArgsConstructor @@ -20,16 +14,19 @@ @Table(name="trans_products") public class TransProduct extends BaseTimeEntity { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "trans_product_id") private Long id; - @ManyToOne @JoinColumn(name = "user_id") + @ManyToOne + @JoinColumn(name = "user_id") private User user; private String name; - @OneToOne @JoinColumn(name = "category_id") + @OneToOne + @JoinColumn(name = "category_id") private Category category; @Column(name = "used_term", nullable = true) @@ -56,7 +53,6 @@ public class TransProduct extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Method method = Method.ALL; - @Builder.Default @Column(name = "view_count") private Long viewCount = 0L; @@ -68,13 +64,24 @@ public class TransProduct extends BaseTimeEntity { @Builder.Default @Enumerated(EnumType.STRING) @Column(name = "transaction_status") - Status transactionStatus = Status.BEFORE; - - - @OneToMany(mappedBy = "transProduct", cascade = CascadeType.ALL, orphanRemoval = true) - private List images; - + private Status transactionStatus = Status.BEFORE; + @Builder + public TransProduct(User user, String name, Category category, String usedTerm, + String detail, Gender gender, Size size, UsedRank usedRank, + Long point, Method method, Status transactionStatus) { + this.user = user; + this.name = name; + this.category = category; + this.usedTerm = usedTerm; + this.detail = detail; + this.gender = gender; + this.size = size; + this.usedRank = usedRank; + this.point = point != null ? point : 0L; + this.method = method != null ? method : Method.ALL; + this.transactionStatus = transactionStatus != null ? transactionStatus : Status.BEFORE; + } public void updateDetails( String name, @@ -85,8 +92,7 @@ public void updateDetails( UsedRank usedRank, Long point, Method method, - Category category, - List newImageUrls) { + Category category) { this.name = name; this.usedTerm = usedTerm; @@ -97,38 +103,17 @@ public void updateDetails( this.point = point; this.method = method; this.category = category; - - // 이미지 업데이트 - updateImages(newImageUrls); } - @Builder - public TransProduct(User user, String name, Category category, String usedTerm, - String detail, Gender gender, Size size, UsedRank usedRank, - Long point, Method method, Status transactionStatus) { - this.user = user; - this.name = name; - this.category = category; - this.usedTerm = usedTerm; - this.detail = detail; - this.gender = gender; - this.size = size; - this.usedRank = usedRank; - this.point = point != null ? point : 0L; - this.method = method != null ? method : Method.ALL; - this.transactionStatus = transactionStatus != null ? transactionStatus : Status.BEFORE; + public void bookTransaction() { + this.transactionStatus = Status.BOOKED; } - /** - * 기존 이미지 삭제 후 새로운 이미지 리스트로 업데이트 - */ - - private void updateImages(List imageUrls) { - this.images.clear(); - - for (int i = 0; i < imageUrls.size(); i++) { - this.images.add(new TransPImg(i, i == 0, imageUrls.get(i), this)); - } + public void cancelTransaction() { + this.transactionStatus = Status.BEFORE; } -} + public void completeTransaction() { + this.transactionStatus = Status.COMPLETED; + } +} \ No newline at end of file diff --git a/src/main/java/com/app/treen/products/service/ProductService.java b/src/main/java/com/app/treen/products/service/ProductService.java index 1d4ac5c..3a7fe46 100644 --- a/src/main/java/com/app/treen/products/service/ProductService.java +++ b/src/main/java/com/app/treen/products/service/ProductService.java @@ -2,13 +2,11 @@ import com.app.treen.common.config.S3Uploader; import com.app.treen.common.response.code.status.ErrorStatus; +import com.app.treen.common.response.exception.CustomException; import com.app.treen.jpa.repository.products.*; import com.app.treen.products.dto.ProductQueryHelper; import com.app.treen.products.dto.TradeQueryHelper; -import com.app.treen.products.dto.request.TradeProductSaveDto; -import com.app.treen.products.dto.request.TradeProductUpdateDto; -import com.app.treen.products.dto.request.TransProductSaveDto; -import com.app.treen.products.dto.request.TransProductUpdateDto; +import com.app.treen.products.dto.request.*; import com.app.treen.products.dto.response.TradeProductResponseDto; import com.app.treen.products.dto.response.TradeResponseListDto; import com.app.treen.products.dto.response.TransProductResponseDto; @@ -29,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; @RequiredArgsConstructor @Service @@ -58,88 +57,113 @@ public class ProductService { // 1 . 상품 거래 등록 @Transactional - public TransProductResponseDto saveTransProduct(TransProductSaveDto dto, List files, User user) throws IOException { - // S3에 파일 업로드 - List uploadedUrls = s3Uploader.upload(files, "trans-product-images"); - dto.setImageUrls(uploadedUrls); + public TransProductResponseDto saveTransProduct(TransactionRequestDto dto, List files, User user) throws IOException { + TransProductSaveDto transProductDto = dto.getProductRequest(); + TransImgRequestDto transImgRequestDto = dto.getImageRequest(); + TransRegionRequestDto regionRequestDto = dto.getRegionRequest(); // Category 조회 - Category category = categoryRepository.findById(dto.getCategoryId()) + Category category = categoryRepository.findById(transProductDto.getCategoryId()) .orElseThrow(() -> new IllegalArgumentException("Invalid category ID")); // TransProduct 저장 - TransProduct transProduct = transProductRepository.save(dto.toEntity(user, category)); + TransProduct transProduct = transProductRepository.save(transProductDto.toEntity(user, category)); + tradeProductRepository.flush(); + if (transProduct.getId() == null) { + throw new IllegalStateException("TradeProduct 저장 후 ID가 생성되지 않았습니다."); + } + + // S3에 파일 업로드 개수 검증 + if (files == null || files.isEmpty()) { + throw new CustomException(ErrorStatus.IMAGE_MUST_BE_UPLOADED); + } else if (files.size() > 5) { + throw new CustomException(ErrorStatus.IMAGE_OVER_UPLOADED); + } + + List uploadedUrls = s3Uploader.upload(files, "trans-product-images"); // 이미지 저장 - List images = dto.toImageEntities(transProduct); + List images = transImgRequestDto.toImageEntities(transProduct,uploadedUrls); transPImgRepository.saveAll(images); // Region 리스트 조회 - List regions = regionRepository.findAllById(dto.getRegionIds()); - - // regions가 비어있으면 null로 처리 - List transRegions = null; - if (regions.isEmpty()) { - // null을 저장 - transRegionRepository.saveAll(Collections.singletonList(null)); // null 저장 예시 - } else { - // TransRegion 객체 생성 - transRegions = dto.toRegionEntities(transProduct, regions); + List regions = regionRepository.findAllById(regionRequestDto.getRegionIds()); - // TransRegion 저장 + // TransRegion 저장 (regions가 비어있지 않은 경우에만 저장) + List transRegions = (regions.isEmpty()) ? null : regionRequestDto.toRegionEntities(transProduct, regions); + if (transRegions != null) { transRegionRepository.saveAll(transRegions); } - return new TransProductResponseDto(transProduct, transRegions, user); + return new TransProductResponseDto(transProduct, transRegions, images,user); } + // 2. 상품 교환 등록 @Transactional - public TradeProductResponseDto saveTradeProduct(TradeProductSaveDto dto, List files, User user) throws IOException { - // 1. S3에 파일 업로드 및 URL 변환 - List storedFiles = s3Uploader.upload(files, "trade-product-images"); - dto.setImageUrls(storedFiles); + public TradeProductResponseDto saveTradeProduct(TradeRequestDto dto, List files, User user) throws IOException { + TradeProductSaveDto tradeProductDto = dto.getTradeProduct(); + ImgRequestDto tradeImgRequestDto = dto.getImgRequest(); + RegionRequestDto regionRequestDto = dto.getRegionRequest(); + WishCategoryRequestDto wishCategoryRequestDto = dto.getWishCategoryRequest(); - // 2. Category 조회 - Category category = categoryRepository.findById(dto.getCategoryId()) + // Category 조회 + Category category = categoryRepository.findById(tradeProductDto.getCategoryId()) .orElseThrow(() -> new IllegalArgumentException("Invalid category ID")); - // 3. TradeProduct 저장 - TradeProduct tradeProduct = tradeProductRepository.save(dto.toEntity(user, category)); + // TradeProduct 저장 + TradeProduct tradeProduct = tradeProductRepository.save(tradeProductDto.toEntity(user, category)); + tradeProductRepository.flush(); + + if (tradeProduct.getId() == null) { + throw new IllegalStateException("TradeProduct 저장 후 ID가 생성되지 않았습니다."); + } + + // S3 파일 업로드 개수 검증 (검증 후 업로드 진행) + if (files == null || files.isEmpty()) { + throw new CustomException(ErrorStatus.IMAGE_MUST_BE_UPLOADED); + } else if (files.size() > 5) { + throw new CustomException(ErrorStatus.IMAGE_OVER_UPLOADED); + } + + // S3에 파일 업로드 및 URL 변환 + List uploadedUrls = s3Uploader.upload(files, "trade-product-images"); + tradeImgRequestDto.toImageEntities(tradeProduct,uploadedUrls); - // 4. 이미지 저장 - List images = dto.toImageEntities(tradeProduct); + // 이미지 저장 + List images = tradeImgRequestDto.toImageEntities(tradeProduct, uploadedUrls); tradePImgRepository.saveAll(images); + List wishCategoryIds = wishCategoryRequestDto.getWishCategoryIds(); + + List wishCategoryEntities = new ArrayList<>(); + // 교환 타입에 따라 설정 + if (tradeProductDto.getTradeType() == TradeType.ANYTHING){ + if (wishCategoryIds != null && !wishCategoryIds.isEmpty()) { + List wishCategories = categoryRepository.findAllById(wishCategoryIds); + wishCategoryEntities = wishCategoryRequestDto.toWishCategoryEntities(tradeProduct, wishCategories); + wishCategoryRepository.saveAll(wishCategoryEntities); + } - // 5. 희망 카테고리 저장 - if (dto.getWishCategoryIds() != null && !dto.getWishCategoryIds().isEmpty()) { - List wishCategories = categoryRepository.findAllById(dto.getWishCategoryIds()); - List wishCategoryEntities = dto.toWishCategoryEntities(tradeProduct, wishCategories); - wishCategoryRepository.saveAll(wishCategoryEntities); } - // 6. 지역 정보 저장 + // 지역 정보 저장 List tradeRegions = new ArrayList<>(); - List regions = new ArrayList<>(); - if (dto.getRegionIds() != null && !dto.getRegionIds().isEmpty()) { - regions = regionRepository.findAllById(dto.getRegionIds()); + if (regionRequestDto.getRegionIds() != null && !regionRequestDto.getRegionIds().isEmpty()) { + List regions = regionRepository.findAllById(regionRequestDto.getRegionIds()); - // regions가 비어있지 않으면 TransRegion 생성 + // regions가 비어있지 않으면 TradeRegion 생성 후 저장 if (!regions.isEmpty()) { - tradeRegions = dto.toRegionEntities(tradeProduct, regions); + tradeRegions = regionRequestDto.toRegionEntities(tradeProduct, regions); tradeRegionRepository.saveAll(tradeRegions); } - } else { - // 만약 dto.getRegionIds()가 null이거나 비어 있으면, tradeRegions를 null로 설정하거나 적절한 값으로 처리 - tradeRegions = null; // 또는 빈 리스트를 설정: tradeRegions = new ArrayList<>(); } - // 7. 응답 DTO 생성 및 반환 - return new TradeProductResponseDto(tradeProduct, tradeRegions, user); - + // 응답 DTO 생성 및 반환 + return new TradeProductResponseDto(tradeProduct, tradeRegions, images,wishCategoryEntities, user); } + // 3. 거래 상품 수정 @Transactional public void updateTransProduct(Long productId, TransProductUpdateDto dto, List files) throws IOException @@ -170,8 +194,7 @@ public void updateTransProduct(Long productId, TransProductUpdateDto dto, List new RuntimeException(ErrorStatus.PRODUCT_NOT_FOUND.getMessage())); + .orElseThrow(() -> new CustomException(ErrorStatus.PRODUCT_NOT_FOUND)); List transRegions = transRegionRepository.findByTransProduct(selectedProduct); + // 이미지 조회 + List transPImgs = transPImgRepository.findByTransProduct(selectedProduct); // 상품에 연결된 유저 정보 조회 User user = transProductRepository.findUserById(id); // DTO 생성 및 반환 - return new TransProductResponseDto(selectedProduct, transRegions, user); + return new TransProductResponseDto(selectedProduct, transRegions,transPImgs,user); } // 8. 교환상품 상세 조회 @@ -247,16 +272,22 @@ public TransProductResponseDto findTransProductById(Long id) { public TradeProductResponseDto findTradeProductById(Long id) { // 교환 상품 조회 TradeProduct selectedProduct = tradeProductRepository.findById(id) - .orElseThrow(() -> new RuntimeException(ErrorStatus.PRODUCT_NOT_FOUND.getMessage())); + .orElseThrow(() -> new CustomException(ErrorStatus.PRODUCT_NOT_FOUND)); // 연결된 지역 정보 조회 List regions = tradeRegionRepository.findByTradeProduct(selectedProduct); + // 희망 카테고리 조회 + List wishCategories = wishCategoryRepository.findAllByTradeProduct(selectedProduct); + + // 이미지 조회 + List tradePImgs = tradePImgRepository.findAllByTradeProduct(selectedProduct); + // 상품에 연결된 유저 정보 조회 User user = selectedProduct.getUser(); // DTO 생성 및 반환 - return new TradeProductResponseDto(selectedProduct, regions, user); + return new TradeProductResponseDto(selectedProduct, regions, tradePImgs, wishCategories,user); } @@ -277,20 +308,23 @@ public List getFilteredResults( // 정렬 조건 생성 OrderSpecifier orderSpecifier = ProductQueryHelper.getOrderSpecifier(condition, trans); - // 쿼리 실행 및 결과 반환 - return queryFactory.select( - Projections.constructor( - TransResponseListDto.class, - trans.id, - trans.name, - trans.point - )) - .from(trans) + // ✅ TransProduct 리스트 조회 + List products = queryFactory + .selectFrom(trans) .where(filterBuilder) .orderBy(orderSpecifier) .offset((long) page * size) .limit(size) .fetch(); + + // ✅ 각 상품에 대해 대표 이미지 조회 + return products.stream().map(product -> { + TransPImg mainImage = (TransPImg) transPImgRepository + .findFirstByTransProductAndIsMainTrue(product) + .orElse(null); // 대표 이미지가 없으면 null + + return new TransResponseListDto(product, mainImage); + }).collect(Collectors.toList()); } @@ -313,22 +347,23 @@ public List getFilteredTradeResults( // 정렬 조건 생성 OrderSpecifier orderSpecifier = TradeQueryHelper.getOrderSpecifier(trade); - // 쿼리 실행 및 결과 반환 - return queryFactory.select( - Projections.constructor( - TradeResponseListDto.class, - trade.id, - trade.name - )) - .from(trade) + // ✅ TradeProduct 리스트 조회 + List products = queryFactory + .selectFrom(trade) .where(filterBuilder) .orderBy(orderSpecifier) .offset((long) page * size) .limit(size) .fetch(); - } - + // ✅ 각 상품에 대해 대표 이미지 조회 + return products.stream().map(product -> { + TradePImg mainImage = tradePImgRepository + .findByTradeProductAndIsMainTrue(product) + .orElse(null); + return new TradeResponseListDto(product, mainImage); + }).collect(Collectors.toList()); + } // 11. 거래 상품 좋아요 취소 @Transactional public boolean increaseLikeTransaction(Long productId, User user) { diff --git a/src/main/java/com/app/treen/trade/controller/TradeController.java b/src/main/java/com/app/treen/trade/controller/TradeController.java new file mode 100644 index 0000000..bb1218e --- /dev/null +++ b/src/main/java/com/app/treen/trade/controller/TradeController.java @@ -0,0 +1,13 @@ +package com.app.treen.trade.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/trade") +public class TradeController { + + +} diff --git a/src/main/java/com/app/treen/trade/entity/Trade.java b/src/main/java/com/app/treen/trade/entity/Trade.java new file mode 100644 index 0000000..e352937 --- /dev/null +++ b/src/main/java/com/app/treen/trade/entity/Trade.java @@ -0,0 +1,25 @@ +package com.app.treen.trade.entity; + +import com.app.treen.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Trade extends BaseTimeEntity { // 승인 완료된 자유교환 + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "trade_id") + private Long id; + + + + +} diff --git a/src/main/java/com/app/treen/trade/service/TradeService.java b/src/main/java/com/app/treen/trade/service/TradeService.java new file mode 100644 index 0000000..b21599f --- /dev/null +++ b/src/main/java/com/app/treen/trade/service/TradeService.java @@ -0,0 +1,20 @@ +package com.app.treen.trade.service; + +import com.app.treen.jpa.repository.trade.TradeRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class TradeService { + + private final TradeRepository tradeRepository; + + // 자유교환 신청 생성 + @Transactional + public void saveTradeRequest() { + + } +} diff --git a/src/main/java/com/app/treen/transactions/controller/TransactionsController.java b/src/main/java/com/app/treen/transactions/controller/TransactionsController.java new file mode 100644 index 0000000..28845a1 --- /dev/null +++ b/src/main/java/com/app/treen/transactions/controller/TransactionsController.java @@ -0,0 +1,42 @@ +package com.app.treen.transactions.controller; + +import com.app.treen.transactions.dto.request.TransactionsSaveRequestDto; +import com.app.treen.transactions.dto.response.TransactionsResponseDto; +import com.app.treen.transactions.service.TransactionsService; +import com.app.treen.user.service.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/transactions") +public class TransactionsController { + + private final TransactionsService transactionsService; + + // 상품거래 예약 생성 + @PostMapping("/auth/save") + public ResponseEntity saveTransactions(@AuthenticationPrincipal CustomUserDetails userDetails, + TransactionsSaveRequestDto transactionsSaveRequestDto) { + + Long transactionsId = transactionsService.createTransactions(userDetails.getUser(), transactionsSaveRequestDto); + TransactionsResponseDto responseDto = transactionsService.getTransactions(transactionsId); + + return ResponseEntity.status(HttpStatus.OK).body(responseDto); + } + + // 상품거래 예약 취소 + @DeleteMapping("/auth/delete/{transactionsId}") + public ResponseEntity deleteTransactions(@AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable("transactionsId") Long transactionsId) { + + transactionsService.cancelTransactions(userDetails.getUser(), transactionsId); + TransactionsResponseDto responseDto = transactionsService.getTransactions(transactionsId); + + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/com/app/treen/transactions/dto/request/TransactionsSaveRequestDto.java b/src/main/java/com/app/treen/transactions/dto/request/TransactionsSaveRequestDto.java new file mode 100644 index 0000000..d142317 --- /dev/null +++ b/src/main/java/com/app/treen/transactions/dto/request/TransactionsSaveRequestDto.java @@ -0,0 +1,25 @@ +package com.app.treen.transactions.dto.request; + +import com.app.treen.chat_room.entity.ChatRoom; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TransactionsSaveRequestDto { + + private Long transChatRoomId; + private LocalDateTime transDate; + private boolean isDirect; + private String place; + private String deliveryAddress; + private String deliveryRequest; + private String deliveryFeeAccount; + +} diff --git a/src/main/java/com/app/treen/transactions/dto/response/TransactionsResponseDto.java b/src/main/java/com/app/treen/transactions/dto/response/TransactionsResponseDto.java new file mode 100644 index 0000000..97c0402 --- /dev/null +++ b/src/main/java/com/app/treen/transactions/dto/response/TransactionsResponseDto.java @@ -0,0 +1,23 @@ +package com.app.treen.transactions.dto.response; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TransactionsResponseDto { + + private Long id; + private Long transChatRoomId; + private LocalDateTime transDate; + private boolean isDirect; + private String place; + private String deliveryAddress; + private String deliveryRequest; + private String deliveryFeeAccount; + +} diff --git a/src/main/java/com/app/treen/transactions/entity/Transactions.java b/src/main/java/com/app/treen/transactions/entity/Transactions.java new file mode 100644 index 0000000..2627fa4 --- /dev/null +++ b/src/main/java/com/app/treen/transactions/entity/Transactions.java @@ -0,0 +1,48 @@ +package com.app.treen.transactions.entity; + +import com.app.treen.BaseTimeEntity; +import com.app.treen.chat_room.entity.ChatRoom; +import com.app.treen.products.entity.TransProduct; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Transactions extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "transactions_id") + private Long id; + + // 대상테이블에 외래키 방식 + @OneToOne + @JoinColumn(name = "chat_room_id") + private ChatRoom transChatRoom; + + private LocalDateTime transDate; // 거래일자 + private boolean isDirect; // 직거래 여부 + + private String place; // 거래 장소(직거래시) + + private String deliveryAddress; // 택배 배송지 + private String deliveryRequest; // 택배 요청사항 + private String deliveryFeeAccount; // 택배비 송금계좌 + + + public void setDeliveryInfo(String deliveryAddress, String deliveryRequest, String deliveryFeeAccount) { + this.deliveryAddress = deliveryAddress; + this.deliveryRequest = deliveryRequest; + this.deliveryFeeAccount = deliveryFeeAccount; + } +} diff --git a/src/main/java/com/app/treen/transactions/service/TransactionsService.java b/src/main/java/com/app/treen/transactions/service/TransactionsService.java new file mode 100644 index 0000000..61791d4 --- /dev/null +++ b/src/main/java/com/app/treen/transactions/service/TransactionsService.java @@ -0,0 +1,102 @@ +package com.app.treen.transactions.service; + +import com.app.treen.chat_room.entity.ChatRoom; +import com.app.treen.common.response.code.status.ErrorStatus; +import com.app.treen.common.response.exception.CustomException; +import com.app.treen.jpa.repository.ChatRoomRepository; +import com.app.treen.jpa.repository.transactions.TransactionsRepository; +import com.app.treen.products.entity.enumeration.Status; +import com.app.treen.transactions.dto.request.TransactionsSaveRequestDto; +import com.app.treen.transactions.dto.response.TransactionsResponseDto; +import com.app.treen.transactions.entity.Transactions; +import com.app.treen.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class TransactionsService { + + private final TransactionsRepository transactionsRepository; + private final ChatRoomRepository chatRoomRepository; + + // 상품거래 예약 생성 + @Transactional + public long createTransactions(User user, TransactionsSaveRequestDto dto) { + + ChatRoom findChatRoom = chatRoomRepository.findById(dto.getTransChatRoomId()) + .orElseThrow(() -> new CustomException(ErrorStatus.NOT_FOUND_CHAT_ROOM)); + + Transactions transactions = Transactions.builder() + .transChatRoom(findChatRoom) + .transDate(dto.getTransDate()) + .isDirect(dto.isDirect()) + .place(dto.getPlace()) + .build(); + + if (!dto.isDirect()) { + transactions.setDeliveryInfo(dto.getDeliveryAddress(), dto.getDeliveryRequest(), dto.getDeliveryFeeAccount()); + } + + // 상품 거래 상태 변경 + transactions.getTransChatRoom().getTransProduct().bookTransaction(); + + return transactionsRepository.save(transactions).getId(); + } + + // 상품거래 예약 조회 + public TransactionsResponseDto getTransactions(Long transactionsId) { + Transactions transactions = transactionsRepository.findById(transactionsId) + .orElseThrow(() -> new CustomException(ErrorStatus.NOT_FOUND_TRANSACTIONS)); + + return TransactionsResponseDto.builder() + .id(transactions.getId()) + .transDate(transactions.getTransDate()) + .isDirect(transactions.isDirect()) + .place(transactions.getPlace()) + .deliveryAddress(transactions.getDeliveryAddress()) + .deliveryRequest(transactions.getDeliveryRequest()) + .deliveryFeeAccount(transactions.getDeliveryFeeAccount()) + .build(); + } + + // 상품거래 예약 취소 + @Transactional + public void cancelTransactions(User user, Long transactionsId) { + + Transactions transactions = transactionsRepository.findById(transactionsId) + .orElseThrow(() -> new CustomException(ErrorStatus.NOT_FOUND_TRANSACTIONS)); + + checkEditTransactions(user, transactions); + + // 상품 상태 변경 + transactions.getTransChatRoom().getTransProduct().cancelTransaction(); + + } + + // 거래 완료 + @Transactional + public void completeTransactions(Long transactionsId) { + + Transactions transactions = transactionsRepository.findById(transactionsId) + .orElseThrow(() -> new CustomException(ErrorStatus.NOT_FOUND_TRANSACTIONS)); + + // 상품 상태 변경 + transactions.getTransChatRoom().getTransProduct().completeTransaction(); + } + + private void checkEditTransactions(User user, Transactions transactions) { + + // 예약한 거래 상품의 판매자만 예약 취소 가능 + if (!transactions.getTransChatRoom().getSeller().getId().equals(user.getId())) { + throw new CustomException(ErrorStatus.PERMISSION_DENIED_TRANSACTIONS); + } + + if (!transactions.getTransChatRoom().getTransProduct().getTransactionStatus().equals(Status.BOOKED)) { + throw new CustomException(ErrorStatus.SHOULD_BE_BOOKED); + } + } + +} diff --git a/src/main/java/com/app/treen/user/controller/UserController.java b/src/main/java/com/app/treen/user/controller/UserController.java index e821d1f..67036d0 100644 --- a/src/main/java/com/app/treen/user/controller/UserController.java +++ b/src/main/java/com/app/treen/user/controller/UserController.java @@ -42,4 +42,12 @@ public ResponseEntity getMember(@AuthenticationPrincipal CustomUserDetails us // 회원탈퇴 + + // 핸드폰 인증 + + // 카카오 로그인 + + // 비밀번호 재설정 + + } \ No newline at end of file diff --git a/src/main/java/com/app/treen/user/service/CustomUserDetailService.java b/src/main/java/com/app/treen/user/service/CustomUserDetailService.java index 734e209..364b972 100644 --- a/src/main/java/com/app/treen/user/service/CustomUserDetailService.java +++ b/src/main/java/com/app/treen/user/service/CustomUserDetailService.java @@ -17,14 +17,14 @@ public class CustomUserDetailService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - log.info("user Name = {}", username); + public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException { + log.info("user loginId = {}", loginId); // 유저가 존재하지 않을 경우 예외 발생 - User user = userRepository.findByUserName(username) + User user = userRepository.findByLoginId(loginId) .orElseThrow(() -> { - log.error("사용자를 찾을 수 없습니다: {}", username); - return new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + username); + log.error("사용자를 찾을 수 없습니다: {}", loginId); + return new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + loginId); }); return new CustomUserDetails(user); diff --git a/src/main/java/com/app/treen/user/service/JwtProvider.java b/src/main/java/com/app/treen/user/service/JwtProvider.java index 185732b..40e20ad 100644 --- a/src/main/java/com/app/treen/user/service/JwtProvider.java +++ b/src/main/java/com/app/treen/user/service/JwtProvider.java @@ -1,5 +1,8 @@ package com.app.treen.user.service; +import com.app.treen.common.response.code.status.ErrorStatus; +import com.app.treen.common.response.exception.CustomException; +import com.app.treen.jpa.repository.RefreshTokenRepository; import com.app.treen.user.dto.request.CustomUserInfoDto; import com.app.treen.user.dto.response.TokenResponseDto; import com.app.treen.user.entity.RefreshToken; @@ -8,6 +11,7 @@ import io.jsonwebtoken.security.Keys; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -38,6 +42,8 @@ public class JwtProvider { private static final Set blacklistedTokens = new HashSet<>(); + private final RefreshTokenRepository refreshTokenRepository; + @Value("${jwt.secret.key}") private String secretKey; @@ -231,5 +237,32 @@ public String validateRefreshToken(RefreshToken refreshTokenObj){ } + @Transactional + public void saveRefreshToken(TokenResponseDto tokenDto) { + RefreshToken refreshToken = RefreshToken.builder().keyUserId(tokenDto.getKey()).refreshToken(tokenDto.getRefreshToken()).build(); + String userId = refreshToken.getKeyUserId(); + + if (refreshTokenRepository.existsByKeyUserId(userId)) { + refreshTokenRepository.deleteByKeyUserId(userId); + } + refreshTokenRepository.save(refreshToken); + } + + public RefreshToken getRefreshToken(String refreshToken) { + return refreshTokenRepository.findByRefreshToken(refreshToken) + .orElseThrow(() -> new CustomException(ErrorStatus.JWT_INVALID)); + } + + public String validateRefreshToken(String refreshToken) { + RefreshToken getRefreshToken = getRefreshToken(refreshToken); + String createdAccessToken = validateRefreshToken(getRefreshToken); + + if (createdAccessToken == null) { + throw new CustomException(ErrorStatus.JWT_EXPIRED); + } + + return createdAccessToken; + } + } \ No newline at end of file diff --git a/src/main/java/com/app/treen/user/service/UserService.java b/src/main/java/com/app/treen/user/service/UserService.java index 7694292..31e45bf 100644 --- a/src/main/java/com/app/treen/user/service/UserService.java +++ b/src/main/java/com/app/treen/user/service/UserService.java @@ -2,7 +2,6 @@ import com.app.treen.common.response.code.status.ErrorStatus; import com.app.treen.common.response.exception.CustomException; -import com.app.treen.jpa.repository.RefreshTokenRepository; import com.app.treen.jpa.repository.user.UserRepository; import com.app.treen.user.dto.request.CustomUserInfoDto; import com.app.treen.user.dto.request.JoinRequestDto; @@ -10,7 +9,6 @@ import com.app.treen.user.dto.response.LoginResponseDto; import com.app.treen.user.dto.response.MemberResponseDto; import com.app.treen.user.dto.response.TokenResponseDto; -import com.app.treen.user.entity.RefreshToken; import com.app.treen.user.entity.RoleType; import com.app.treen.user.entity.User; import jakarta.transaction.Transactional; @@ -27,34 +25,6 @@ public class UserService { private final PasswordEncoder passwordEncoder; private final JwtProvider jwtProvider; - private final RefreshTokenRepository refreshTokenRepository; - - @Transactional - public void saveRefreshToken(TokenResponseDto tokenDto) { - RefreshToken refreshToken = RefreshToken.builder().keyUserId(tokenDto.getKey()).refreshToken(tokenDto.getRefreshToken()).build(); - String userId = refreshToken.getKeyUserId(); - - if (refreshTokenRepository.existsByKeyUserId(userId)) { - refreshTokenRepository.deleteByKeyUserId(userId); - } - refreshTokenRepository.save(refreshToken); - } - - public RefreshToken getRefreshToken(String refreshToken) { - return refreshTokenRepository.findByRefreshToken(refreshToken) - .orElseThrow(() -> new CustomException(ErrorStatus.JWT_INVALID)); - } - - public String validateRefreshToken(String refreshToken) { - RefreshToken getRefreshToken = getRefreshToken(refreshToken); - String createdAccessToken = jwtProvider.validateRefreshToken(getRefreshToken); - - if (createdAccessToken == null) { - throw new CustomException(ErrorStatus.JWT_EXPIRED); - } - - return createdAccessToken; - } // 회원가입 public MemberResponseDto signUp(@Valid JoinRequestDto joinRequest) throws CustomException { @@ -78,6 +48,10 @@ public MemberResponseDto signUp(@Valid JoinRequestDto joinRequest) throws Custom throw new CustomException(ErrorStatus.USER_PHONE_IS_USED); } + if (password.equals(joinRequest.getPassword2())) { + throw new CustomException(ErrorStatus.PASSWORD_NOT_MATCHED); + } + RoleType role = RoleType.USER; User member = joinRequest.toEntity(role, password); @@ -104,7 +78,7 @@ public LoginResponseDto login(LoginRequestDto loginRequest) { CustomUserInfoDto userInfoDto = new CustomUserInfoDto(member.getId(), member.getLoginId(), member.getRoles()); TokenResponseDto tokenDto = jwtProvider.createTokenByLogin(userInfoDto); - saveRefreshToken(tokenDto); + //saveRefreshToken(tokenDto); return new LoginResponseDto(member,tokenDto); } @@ -114,4 +88,8 @@ public MemberResponseDto getMember(User user) { MemberResponseDto memberResponse = new MemberResponseDto(user); return memberResponse; } + + // 비밀번호 재설정 + // 회원 탈퇴 + // 전화번호 인증 } diff --git a/src/main/java/com/app/treen/user/service/filter/JwtAuthFilter.java b/src/main/java/com/app/treen/user/service/filter/JwtAuthFilter.java index 7f7ede2..af4868b 100644 --- a/src/main/java/com/app/treen/user/service/filter/JwtAuthFilter.java +++ b/src/main/java/com/app/treen/user/service/filter/JwtAuthFilter.java @@ -32,12 +32,11 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha String token = jwtProvider.resolveToken(httpRequest); if (token != null && jwtProvider.validateToken(token)) { - // JWT 토큰에서 이메일 추출 Claims claims = jwtProvider.getUserInfoFromToken(token); - String email = claims.get("email", String.class); + String loginId = claims.get("loginId", String.class); - if (email != null) { - Authentication authentication = jwtProvider.createUserAuthentication(email); + if (loginId != null) { + Authentication authentication = jwtProvider.createUserAuthentication(loginId); SecurityContextHolder.getContext().setAuthentication(authentication); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 344d8cc..fdfc689 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,6 +5,9 @@ # username: ${DB_USERNAME:root} # password: ${DB_PASSWORD:password} spring: + redis: + host: localhost + port: 6379 config: import: optional:file:.env[.properties] # .env 파일을 직접 로드 datasource: @@ -13,15 +16,15 @@ spring: username: sa # 기본 사용자명 password: jpa: - hibernate.ddl-auto: create + hibernate.ddl-auto: update show-sql: true properties: hibernate: format_sql: true data: mongodb: - uri: ${MONGO_DB_URL} - database: treen_dev + uri: ${MONGO_DB_URI} + database: ${MONGO_DB_DATABASE} # database-platform: org.hibernate.dialect.MySQL8Dialect cloud: aws: @@ -37,11 +40,6 @@ jwt: secret: key: ${JWT_SECRET_KEY} refresh: ${JWT_REFRESH_KEY} - redis: - host: localhost - port: 6379 - repositories: - enabled: false coolsms: apiKey: ${COOL_SMS_KEY} apiSecret: ${COOL_SMS_SECRET_KEY}