diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/config/WebSocketConfig.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/config/WebSocketConfig.kt index 66cd19bf..bb14edc3 100644 --- a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/config/WebSocketConfig.kt +++ b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/config/WebSocketConfig.kt @@ -1,7 +1,7 @@ package com.asyncgate.chat_server.config import com.asyncgate.chat_server.filter.FilterChannelInterceptor -import com.asyncgate.chat_server.filter.JwtHandshakeInterceptor +import com.asyncgate.chat_server.filter.WebSocketHandshakeInterceptor import org.springframework.context.annotation.Configuration import org.springframework.messaging.simp.config.ChannelRegistration import org.springframework.messaging.simp.config.MessageBrokerRegistry @@ -13,13 +13,13 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo @EnableWebSocketMessageBroker class WebSocketConfig( private val filterChannelInterceptor: FilterChannelInterceptor, - private val jwtHandshakeInterceptor: JwtHandshakeInterceptor, + private val webSocketHandshakeInterceptor: WebSocketHandshakeInterceptor, ) : WebSocketMessageBrokerConfigurer { override fun registerStompEndpoints(registry: StompEndpointRegistry) { registry.addEndpoint("/asyncgate-chat") .setAllowedOriginPatterns("*") - .addInterceptors(jwtHandshakeInterceptor) + .addInterceptors(webSocketHandshakeInterceptor) } override fun configureMessageBroker(registry: MessageBrokerRegistry) { diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/controller/HealthCheckController.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/controller/HealthCheckController.kt new file mode 100644 index 00000000..7a96d295 --- /dev/null +++ b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/controller/HealthCheckController.kt @@ -0,0 +1,15 @@ +package com.asyncgate.chat_server.controller + +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/health") +class HealthCheckController { + + @GetMapping + fun healthCheck(): Map { + return mapOf("status" to "UP") + } +} diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/ChatServerErrorHandler.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/ChatServerErrorHandler.kt new file mode 100644 index 00000000..913d6469 --- /dev/null +++ b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/ChatServerErrorHandler.kt @@ -0,0 +1,34 @@ +package com.asyncgate.chat_server.exception + +import com.asyncgate.chat_server.support.response.FailResponse +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.servlet.resource.NoResourceFoundException + +@ControllerAdvice +class ChatServerErrorHandler { + private val log: Logger = LoggerFactory.getLogger(ChatServerErrorHandler::class.java) + + @ExceptionHandler(ChatServerException::class) + fun handleApiException(e: ChatServerException): ResponseEntity { + val errorType: FailType = e.failType + val response: FailResponse = FailResponse.of( + errorType.errorCode, + errorType.message, + errorType.status.value() + ) + return ResponseEntity.status(errorType.status).body(response) + } + + @ExceptionHandler(Exception::class) + fun handleException(exception: Exception) { + log.error("🚨 [Global Error] ${exception.message}", exception) + } + + @ExceptionHandler(NoResourceFoundException::class) + fun handleResourceException(exception: NoResourceFoundException) { + } +} diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/WebSocketErrorHandler.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/WebSocketErrorHandler.kt deleted file mode 100644 index e4fccef9..00000000 --- a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/exception/WebSocketErrorHandler.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.asyncgate.chat_server.exception - -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.web.bind.annotation.ControllerAdvice -import org.springframework.web.bind.annotation.ExceptionHandler - -@ControllerAdvice -class WebSocketErrorHandler { - private val log: Logger = LoggerFactory.getLogger(WebSocketErrorHandler::class.java) - - @ExceptionHandler(Exception::class) - fun handleException(exception: Exception) { - log.error("🚨 [WebSocket Error] ${exception.message}", exception) - } -} diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/FilterChannelInterceptor.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/StompInterceptor.kt similarity index 100% rename from src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/FilterChannelInterceptor.kt rename to src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/StompInterceptor.kt diff --git a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/JwtHandshakeInterceptor.kt b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/WebSocketHandshakeInterceptor.kt similarity index 95% rename from src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/JwtHandshakeInterceptor.kt rename to src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/WebSocketHandshakeInterceptor.kt index 6904ad9d..855ab606 100644 --- a/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/JwtHandshakeInterceptor.kt +++ b/src/backend/chat-server/src/main/kotlin/com/asyncgate/chat_server/filter/WebSocketHandshakeInterceptor.kt @@ -10,12 +10,12 @@ import org.springframework.web.socket.WebSocketHandler import org.springframework.web.socket.server.HandshakeInterceptor @Component -class JwtHandshakeInterceptor( +class WebSocketHandshakeInterceptor( private val jwtTokenProvider: JwtTokenProvider, ) : HandshakeInterceptor { companion object { - private val log: Logger = LoggerFactory.getLogger(JwtHandshakeInterceptor::class.java) + private val log: Logger = LoggerFactory.getLogger(WebSocketHandshakeInterceptor::class.java) } /** diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClient.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClient.java new file mode 100644 index 00000000..81e45238 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClient.java @@ -0,0 +1,15 @@ +package com.asyncgate.guild_server.client; + +import com.asyncgate.guild_server.support.response.SuccessResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@FeignClient(name = "user-server") +public interface UserClient { + + @GetMapping("/users") + SuccessResponse getUsersInfo(@RequestParam(required = false) List memberIds); +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClientInfoResponses.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClientInfoResponses.java new file mode 100644 index 00000000..4c9cc03f --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/client/UserClientInfoResponses.java @@ -0,0 +1,8 @@ +package com.asyncgate.guild_server.client; + +import java.util.List; + +public record UserClientInfoResponses(List responses) { + public record UserClientInfoResponse(String userId, String name, String nickname, String profileImageUrl) { + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/FeignClientConfig.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/FeignClientConfig.java index 9301db99..6f321723 100644 --- a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/FeignClientConfig.java +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/config/FeignClientConfig.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@EnableFeignClients +@EnableFeignClients(basePackages = "com.asyncgate.guild_server.client") public class FeignClientConfig { @Bean diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/controller/DirectController.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/controller/DirectController.java new file mode 100644 index 00000000..af21209c --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/controller/DirectController.java @@ -0,0 +1,30 @@ +package com.asyncgate.guild_server.controller; + +import com.asyncgate.guild_server.dto.request.DirectChannelCreateRequest; +import com.asyncgate.guild_server.dto.response.DirectResponse; +import com.asyncgate.guild_server.service.DirectService; +import com.asyncgate.guild_server.support.response.SuccessResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/direct") +public class DirectController { + + private final DirectService directService; + + @PostMapping + public SuccessResponse create( + final @AuthenticationPrincipal String currentUserId, + final @RequestBody DirectChannelCreateRequest request + ) { + return SuccessResponse.created( + directService.create(currentUserId, request) + ); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/Direct.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/Direct.java new file mode 100644 index 00000000..c95fef57 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/Direct.java @@ -0,0 +1,24 @@ +package com.asyncgate.guild_server.domain; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; +import java.util.UUID; + +@Getter +public class Direct { + + private final String id; + + @Builder + private Direct(String id, String name) { + this.id = id; + } + + public static Direct create() { + return Direct.builder() + .id(UUID.randomUUID().toString()) + .build(); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/DirectMember.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/DirectMember.java new file mode 100644 index 00000000..4e16d5dc --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/domain/DirectMember.java @@ -0,0 +1,27 @@ +package com.asyncgate.guild_server.domain; + +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +@Getter +public class DirectMember { + private final String id; + private final String directId; + private final String memberId; + private final String memberName; + + @Builder + private DirectMember(String id, String directId, String memberId, String memberName) { + this.id = id; + this.directId = directId; + this.memberId = memberId; + this.memberName = memberName; + } + + public static DirectMember create(final String directId, final String memberId, final String memberName) { + return new DirectMember(UUID.randomUUID().toString(), directId, memberId, memberName); + } + +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/request/DirectChannelCreateRequest.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/request/DirectChannelCreateRequest.java new file mode 100644 index 00000000..7e0f32ce --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/request/DirectChannelCreateRequest.java @@ -0,0 +1,12 @@ +package com.asyncgate.guild_server.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@Getter +@AllArgsConstructor +public class DirectChannelCreateRequest { + private List memberIds; +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/response/DirectResponse.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/response/DirectResponse.java new file mode 100644 index 00000000..fbe10c42 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/dto/response/DirectResponse.java @@ -0,0 +1,9 @@ +package com.asyncgate.guild_server.dto.response; + +import com.asyncgate.guild_server.client.UserClientInfoResponses; + +public record DirectResponse(String directId,UserClientInfoResponses members) { + public static DirectResponse of(final String directId, final UserClientInfoResponses members) { + return new DirectResponse(directId, members); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectEntity.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectEntity.java new file mode 100644 index 00000000..6d79674e --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectEntity.java @@ -0,0 +1,24 @@ +package com.asyncgate.guild_server.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "direct") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DirectEntity { + + @Id + private String id; + + @Builder + private DirectEntity(String id) { + this.id = id; + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectMemberEntity.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectMemberEntity.java new file mode 100644 index 00000000..44cfcc3b --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/entity/DirectMemberEntity.java @@ -0,0 +1,28 @@ +package com.asyncgate.guild_server.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "direct_member") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DirectMemberEntity { + + @Id + private String id; + private String memberId; + private String memberName; + + @Builder + private DirectMemberEntity(String id, String memberId, String memberName) { + this.id = id; + this.memberId = memberId; + this.memberName = memberName; + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectJpaRepository.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectJpaRepository.java new file mode 100644 index 00000000..adad9f73 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectJpaRepository.java @@ -0,0 +1,7 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.entity.DirectEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DirectJpaRepository extends JpaRepository { +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberJpaRepository.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberJpaRepository.java new file mode 100644 index 00000000..93e933b9 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberJpaRepository.java @@ -0,0 +1,7 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.entity.DirectMemberEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DirectMemberJpaRepository extends JpaRepository { +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepository.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepository.java new file mode 100644 index 00000000..d808003d --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepository.java @@ -0,0 +1,9 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.domain.DirectMember; + +import java.util.List; + +public interface DirectMemberRepository { + void saveAll(List directMembers); +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepositoryImpl.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepositoryImpl.java new file mode 100644 index 00000000..81e00ca1 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectMemberRepositoryImpl.java @@ -0,0 +1,24 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.domain.DirectMember; +import com.asyncgate.guild_server.support.utility.DomainUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class DirectMemberRepositoryImpl implements DirectMemberRepository { + + private final DirectMemberJpaRepository jpaRepository; + + @Override + public void saveAll(List directMembers) { + jpaRepository.saveAll( + directMembers.stream() + .map(DomainUtil.DirectMemberMapper::toEntity) + .toList() + ); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepository.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepository.java new file mode 100644 index 00000000..eb6d48f0 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepository.java @@ -0,0 +1,7 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.domain.Direct; + +public interface DirectRepository { + void save(Direct direct); +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepositoryImpl.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepositoryImpl.java new file mode 100644 index 00000000..b4d402c4 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/repository/DirectRepositoryImpl.java @@ -0,0 +1,19 @@ +package com.asyncgate.guild_server.repository; + +import com.asyncgate.guild_server.domain.Direct; +import com.asyncgate.guild_server.support.utility.DomainUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class DirectRepositoryImpl implements DirectRepository { + private final DirectJpaRepository jpaRepository; + + @Override + public void save(Direct direct) { + jpaRepository.save( + DomainUtil.DirectMapper.toEntity(direct) + ); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/SecurityConstants.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/SecurityConstants.java index 7f69f0e2..678ef946 100644 --- a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/SecurityConstants.java +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/SecurityConstants.java @@ -18,6 +18,7 @@ public class SecurityConstants { "/swagger-ui/index.css", "/v3/api-docs/swagger-config", "/error", - "/health" + "/health", + "/actuator/info" }; } diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/filter/JwtFilter.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/filter/JwtFilter.java index 140ee61f..1308630a 100644 --- a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/filter/JwtFilter.java +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/security/filter/JwtFilter.java @@ -41,6 +41,9 @@ protected void doFilterInternal( } String jwtToken = request.getHeader(HttpHeaders.AUTHORIZATION); + + System.out.println("jwtToken = " + jwtToken); + jwtService.authenticate(jwtToken); filterChain.doFilter(request, response); } diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectService.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectService.java new file mode 100644 index 00000000..fdd951bb --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectService.java @@ -0,0 +1,8 @@ +package com.asyncgate.guild_server.service; + +import com.asyncgate.guild_server.dto.request.DirectChannelCreateRequest; +import com.asyncgate.guild_server.dto.response.DirectResponse; + +public interface DirectService { + DirectResponse create(String currentUserId, DirectChannelCreateRequest request); +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectServiceImpl.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectServiceImpl.java new file mode 100644 index 00000000..fe2ba512 --- /dev/null +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/service/DirectServiceImpl.java @@ -0,0 +1,49 @@ +package com.asyncgate.guild_server.service; + +import com.asyncgate.guild_server.client.UserClient; +import com.asyncgate.guild_server.client.UserClientInfoResponses; +import com.asyncgate.guild_server.domain.Direct; +import com.asyncgate.guild_server.domain.DirectMember; +import com.asyncgate.guild_server.dto.request.DirectChannelCreateRequest; +import com.asyncgate.guild_server.dto.response.DirectResponse; +import com.asyncgate.guild_server.repository.DirectMemberRepository; +import com.asyncgate.guild_server.repository.DirectRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class DirectServiceImpl implements DirectService { + + private final UserClient userClient; + private final DirectRepository directRepository; + private final DirectMemberRepository directMemberRepository; + + @Override + @Transactional + public DirectResponse create(final String currentUserId, final DirectChannelCreateRequest request) { + List memberIds = request.getMemberIds(); + + UserClientInfoResponses usersInfo = userClient + .getUsersInfo(memberIds) + .getResult(); + + Direct direct = Direct.create(); + + // ToDo memberName λ³€κ²½μ‹œ 이벀트 κ΅¬λ…ν•˜κ³  μˆ˜μ •ν•΄μ•Όν•¨ + List directMembers = usersInfo.responses().stream() + .map(userInfo -> + DirectMember.create(direct.getId(), userInfo.userId(), userInfo.name()) + ) + .toList(); + + directRepository.save(direct); + directMemberRepository.saveAll(directMembers); + + return DirectResponse.of(direct.getId(), usersInfo); + } +} diff --git a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/support/utility/DomainUtil.java b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/support/utility/DomainUtil.java index 2f35d682..24c6e27a 100644 --- a/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/support/utility/DomainUtil.java +++ b/src/backend/guild-server/src/main/java/com/asyncgate/guild_server/support/utility/DomainUtil.java @@ -1,13 +1,7 @@ package com.asyncgate.guild_server.support.utility; -import com.asyncgate.guild_server.domain.Category; -import com.asyncgate.guild_server.domain.Channel; -import com.asyncgate.guild_server.domain.Guild; -import com.asyncgate.guild_server.domain.GuildMember; -import com.asyncgate.guild_server.entity.CategoryEntity; -import com.asyncgate.guild_server.entity.ChannelEntity; -import com.asyncgate.guild_server.entity.GuildEntity; -import com.asyncgate.guild_server.entity.GuildMemberEntity; +import com.asyncgate.guild_server.domain.*; +import com.asyncgate.guild_server.entity.*; public class DomainUtil { @@ -96,4 +90,36 @@ public static Channel toDomain(final ChannelEntity entity) { .build(); } } + + public static class DirectMapper { + public static DirectEntity toEntity(final Direct domain) { + return DirectEntity.builder() + .id(domain.getId()) + .build(); + } + + public static Direct toDomain(final DirectEntity entity) { + return Direct.builder() + .id(entity.getId()) + .build(); + } + } + + public static class DirectMemberMapper { + public static DirectMemberEntity toEntity(final DirectMember directMember) { + return DirectMemberEntity.builder() + .id(directMember.getId()) + .memberId(directMember.getMemberId()) + .memberName(directMember.getMemberName()) + .build(); + } + + public static DirectMember toDomain(final DirectMemberEntity entity) { + return DirectMember.builder() + .id(entity.getId()) + .memberId(entity.getMemberId()) + .memberName(entity.getMemberName()) + .build(); + } + } } diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/controller/MemberCommandController.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/controller/MemberCommandController.java index 4c5b07a8..3258ad4c 100644 --- a/src/backend/user-server/src/main/java/com/asyncgate/user_server/controller/MemberCommandController.java +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/controller/MemberCommandController.java @@ -4,6 +4,7 @@ import com.asyncgate.user_server.dto.request.*; import com.asyncgate.user_server.dto.response.CheckEmailDuplicateResponse; import com.asyncgate.user_server.dto.response.DefaultJsonWebTokenResponse; +import com.asyncgate.user_server.dto.response.UserClientInfoResponses; import com.asyncgate.user_server.security.annotation.MemberID; import com.asyncgate.user_server.support.response.SuccessResponse; import com.asyncgate.user_server.usecase.*; @@ -11,6 +12,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + @RestController @RequiredArgsConstructor public class MemberCommandController implements MemberControllerDocs { @@ -22,8 +25,10 @@ public class MemberCommandController implements MemberControllerDocs { private final UpdateUserInfoUseCase UpdateUserInfoUseCase; private final DeleteUserUseCase DeleteUserUseCase; private final UpdateDeviceTokenUseCase UpdateDeviceTokenUseCase; + private final FindUserInfoUseCase findUserInfoUseCase; // μ‚¬μ†Œν•œ 주석 μΆ”κ°€ ν…ŒμŠ€νŠΈν…ŒμŠ€νŠΈμš© μš©μš©μš©κ°€λ¦¬ + /** * 1.0 μž„μ‹œ νšŒμ›κ°€μž… */ @@ -111,4 +116,16 @@ public SuccessResponse deleteUser(@MemberID final String userId) { DeleteUserUseCase.execute(userId); return SuccessResponse.ok("νšŒμ›νƒˆν‡΄ μ™„λ£Œ"); } + + @GetMapping("/users") + public SuccessResponse getMembers( + @RequestParam(required = false) List memberIds + ) { + + UserClientInfoResponses byUserIds = findUserInfoUseCase.getByUserIds(memberIds); + System.out.println("byUserIds = " + byUserIds); + return SuccessResponse.ok( + byUserIds + ); + } } \ No newline at end of file diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/dto/response/UserClientInfoResponses.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/dto/response/UserClientInfoResponses.java new file mode 100644 index 00000000..974d6596 --- /dev/null +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/dto/response/UserClientInfoResponses.java @@ -0,0 +1,23 @@ +package com.asyncgate.user_server.dto.response; + +import com.asyncgate.user_server.domain.Member; + +import java.util.List; + +public record UserClientInfoResponses(List responses) { + public static UserClientInfoResponses from(List members) { + return new UserClientInfoResponses( + members.stream() + .map(UserClientInfoResponse::from) + .toList() + ); + } + + private record UserClientInfoResponse(String userId, String name, String nickname, String profileImageUrl) { + private static UserClientInfoResponse from(final Member member) { + return new UserClientInfoResponse( + member.getId(), member.getName(), member.getNickname(), member.getProfileImgUrl() + ); + } + } +} diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberQueryDslRepository.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberQueryDslRepository.java new file mode 100644 index 00000000..c19ee8f2 --- /dev/null +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberQueryDslRepository.java @@ -0,0 +1,29 @@ +package com.asyncgate.user_server.repository; + +import com.asyncgate.user_server.domain.Member; +import com.asyncgate.user_server.entity.MemberEntity; +import com.asyncgate.user_server.entity.QMemberEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class MemberQueryDslRepository { + + private final JPAQueryFactory jpaQueryFactory; + private final QMemberEntity memberEntity = QMemberEntity.memberEntity; + + public List getByMemberIds(final List memberIds) { + return jpaQueryFactory + .select(this.memberEntity) + .from(this.memberEntity) + .where( + this.memberEntity.id.in(memberIds), + this.memberEntity.deleted.isFalse() + ) + .fetch(); + } +} diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepository.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepository.java index 509b81a9..155be8a7 100644 --- a/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepository.java +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepository.java @@ -1,7 +1,9 @@ package com.asyncgate.user_server.repository; import com.asyncgate.user_server.domain.Member; +import com.asyncgate.user_server.entity.MemberEntity; +import java.util.List; import java.util.Optional; public interface MemberRepository { @@ -16,4 +18,6 @@ public interface MemberRepository { void save(Member member); void softDeleteById(String id); + + List getByMemberIds(List memberIds); } diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepositoryImpl.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepositoryImpl.java index 74ece7c9..2b77fb57 100644 --- a/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepositoryImpl.java +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/repository/MemberRepositoryImpl.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Slf4j @@ -16,6 +17,7 @@ @RequiredArgsConstructor public class MemberRepositoryImpl implements MemberRepository { private final MemberJpaRepository memberJpaRepository; + private final MemberQueryDslRepository queryDslRepository; @Override public void save(final Member member) { @@ -30,6 +32,13 @@ public void softDeleteById(final String id) { memberJpaRepository.softDeleteById(id); } + @Override + public List getByMemberIds(List memberIds) { + return queryDslRepository.getByMemberIds(memberIds).stream() + .map(DomainUtil.MemberMapper::toDomain) + .toList(); + } + @Override public Optional findByEmail(final String email) { return memberJpaRepository.findByNotDeletedEmail(email) diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/constant/Constants.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/constant/Constants.java index 8f1f840e..203e7cd8 100644 --- a/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/constant/Constants.java +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/security/constant/Constants.java @@ -32,7 +32,8 @@ public class Constants { "/api-docs.html", "/api-docs/**", "/swagger-ui/**", - "/v3/**" + "/v3/**", + "/users" ); /** diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/service/FindUserInfoService.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/service/FindUserInfoService.java new file mode 100644 index 00000000..a9e13c68 --- /dev/null +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/service/FindUserInfoService.java @@ -0,0 +1,25 @@ +package com.asyncgate.user_server.service; + +import com.asyncgate.user_server.dto.response.UserClientInfoResponses; +import com.asyncgate.user_server.repository.MemberRepository; +import com.asyncgate.user_server.usecase.FindUserInfoUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class FindUserInfoService implements FindUserInfoUseCase { + + private final MemberRepository memberRepository; + + @Override + public UserClientInfoResponses getByUserIds(final List memberIds) { + return UserClientInfoResponses.from( + memberRepository.getByMemberIds(memberIds) + ); + } +} diff --git a/src/backend/user-server/src/main/java/com/asyncgate/user_server/usecase/FindUserInfoUseCase.java b/src/backend/user-server/src/main/java/com/asyncgate/user_server/usecase/FindUserInfoUseCase.java new file mode 100644 index 00000000..5ffa26f0 --- /dev/null +++ b/src/backend/user-server/src/main/java/com/asyncgate/user_server/usecase/FindUserInfoUseCase.java @@ -0,0 +1,10 @@ +package com.asyncgate.user_server.usecase; + +import com.asyncgate.user_server.dto.response.UserClientInfoResponses; + +import java.util.List; + +public interface FindUserInfoUseCase { + + UserClientInfoResponses getByUserIds(List memberIds); +}