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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/com/beat/BeatApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
@ImportAutoConfiguration({FeignAutoConfiguration.class})
public class BeatApplication {

/**
* Main entry point for the BeatApplication Spring Boot application.
*
* <p>This method bootstraps the application by invoking SpringApplication.run
* with the BeatApplication class and the provided command-line arguments.</p>
*
* @param args command-line arguments passed to the application
*/
public static void main(String[] args) {
SpringApplication.run(BeatApplication.class, args);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public class MemberRegistrationService {
private final UserRepository userRepository;
private final MemberRepository memberRepository;

/**
* Registers a new member and its associated user account.
*
* <p>This method creates a user with the MEMBER role and a corresponding member record using the
* provided member information. It saves both entities transactionally and publishes a registration event
* with the member's nickname.
*
* @param memberInfoResponse the data required for member registration, including nickname, email, social ID, and social type
* @return the identifier of the newly registered member
*/
@Transactional
public Long registerMemberWithUserInfo(final MemberInfoResponse memberInfoResponse) {
Users users = Users.createWithRole(Role.MEMBER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,33 @@ public boolean checkMemberExistsBySocialIdAndSocialType(final Long socialId, fin
return memberRepository.findBySocialTypeAndSocialId(socialId, socialType).isPresent();
}

/**
* Retrieves a member using the specified social ID and social type.
* <p>
* Searches the member repository for a member matching the provided social ID and social type.
* If no such member exists, a NotFoundException with error code MEMBER_NOT_FOUND is thrown.
*
* @param socialId the unique social identifier of the member
* @param socialType the social platform type associated with the member
* @return the member corresponding to the given social credentials
* @throws NotFoundException if no matching member is found
*/
@Override
@Transactional(readOnly = true)
public Member findMemberBySocialIdAndSocialType(final Long socialId, final SocialType socialType) {
return memberRepository.findBySocialTypeAndSocialId(socialId, socialType)
.orElseThrow(() -> new NotFoundException(MemberErrorCode.MEMBER_NOT_FOUND));
}

/**
* Deletes a user identified by the specified ID.
*
* <p>This method retrieves the user from the repository and removes it. If no user is found with the
* given ID, a {@link NotFoundException} is thrown with the error code {@link MemberErrorCode#MEMBER_NOT_FOUND}.
*
* @param id the unique identifier of the user to delete
* @throws NotFoundException if no user with the specified ID is found
*/
@Override
@Transactional
public void deleteUser(final Long id) {
Expand All @@ -52,6 +72,11 @@ public void deleteUser(final Long id) {
userRepository.delete(users);
}

/**
* Retrieves the total count of members.
*
* @return the total number of members
*/
@Override
@Transactional(readOnly = true)
public long countMembers() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,14 @@ private LoginSuccessResponse generateLoginResponseFromMemberInfo(final MemberInf
}

/**
* 사용자 정보(Social ID와 Social Type)를 통해 기존 회원을 찾거나,
* 없으면 새로운 회원을 등록하는 메서드.
* Finds an existing member by their social ID and social type, or registers a new member if none exists.
*
* @param memberInfoResponse 소셜 서비스에서 가져온 사용자 정보
* @return 등록된 회원 또는 기존 회원의 ID
* <p>This method checks whether a member with the specified social details (contained in
* {@code memberInfoResponse}) already exists. If found, the member's ID is returned after logging
* their role; otherwise, the method registers a new member using the provided information and returns the new member's ID.
*
* @param memberInfoResponse user information retrieved from the social service, including social ID and type
* @return the ID of the existing or newly registered member
*/
private Long findOrRegisterMember(final MemberInfoResponse memberInfoResponse) {
boolean memberExists = memberUseCase.checkMemberExistsBySocialIdAndSocialType(memberInfoResponse.socialId(),
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/com/beat/domain/member/port/in/MemberUseCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ public interface MemberUseCase {

Member findMemberBySocialIdAndSocialType(Long socialId, SocialType socialType);

void deleteUser(Long id);
/**
* Deletes a user identified by the given ID.
*
* @param id the unique identifier of the user to delete
*/
void deleteUser(Long id);

long countMembers();
/**
* Returns the total number of members.
*
* @return the total member count
*/
long countMembers();
}
19 changes: 19 additions & 0 deletions src/main/java/com/beat/global/common/config/AsyncThreadConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
public class AsyncThreadConfig implements AsyncConfigurer {
private final ThreadPoolProperties threadPoolProperties;

/**
* Creates and returns an Executor for asynchronous task execution.
*
* <p>The executor is configured with a core pool size and thread name prefix sourced from
* the thread pool properties. It employs a CallerRunsPolicy to handle task rejections,
* ensuring that when the pool is saturated, tasks execute in the calling thread.
* The ThreadPoolTaskExecutor is wrapped in a DelegatingSecurityContextExecutor to preserve
* the Spring Security context across asynchronous execution.
*
* @return the security-aware executor for asynchronous tasks
*/
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
Expand All @@ -32,6 +43,14 @@ public Executor getAsyncExecutor() {
return new DelegatingSecurityContextExecutor(executor.getThreadPoolExecutor());
}

/**
* Returns the asynchronous uncaught exception handler.
*
* <p>This bean provides a global handler for uncaught exceptions thrown by asynchronous
* methods, using an instance of {@link GlobalAsyncExceptionHandler}.</p>
*
* @return an instance of {@link GlobalAsyncExceptionHandler} for handling uncaught async exceptions
*/
@Override
@Bean
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@
@Slf4j
public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

/**
* Handles uncaught exceptions thrown during asynchronous method execution.
*
* This method logs an error message with the method name and exception details. If the provided
* parameters array is null, it logs that no parameters were provided; otherwise, it logs the string
* representation of the parameters.
*
* @param ex the exception that occurred (non-null)
* @param method the method where the exception was thrown (non-null)
* @param params the parameters passed to the method; may be null
*/
@Override
public void handleUncaughtException(@NonNull Throwable ex, @NonNull Method method, @Nullable Object... params) {
if (params == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
@FeignClient(name = "slackClient", url = "${slack.webhook.url}")
public interface SlackClient {

/**
* Sends a JSON payload as a Slack message to the configured webhook.
*
* <p>This method posts the provided payload to the Slack webhook specified in the application properties.
* The payload should contain key-value pairs representing the Slack message details.
*
* @param payload JSON payload containing details of the Slack message
*/
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
void sendMessage(@RequestBody Map<String, String> payload);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ public class SlackService {

private final SlackClient slackClient;

/**
* Sends a Slack message using the provided payload.
*
* This method forwards the message payload to the Slack client.
*
* @param payload a map containing key-value pairs representing the details of the Slack message
*/
public void sendMessage(Map<String, String> payload) {
slackClient.sendMessage(payload);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ public class MemberRegisteredEventListener {
private final MemberUseCase memberUseCase;
private final SlackService slackService;

/**
* Asynchronously sends a Slack notification when a new member registers.
*
* <p>This method is triggered after a transaction commits and builds a payload with a welcome
* message that includes the current number of members and the new member's nickname. It then tries
* to send this payload to Slack. If the message fails to send, a RuntimeException is thrown.
*
* @param event the member registration event containing details about the new member
*/
@Async("taskExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendSlackNotification(MemberRegisteredEvent event) {
Expand Down