From 9395460ebdbd6683c12e0e1978093129f9d3140e Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 01:39:13 +0900 Subject: [PATCH 01/12] build(gradle): add expo-server-sdk dependency --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 1e41edf8..5d86ede4 100644 --- a/build.gradle +++ b/build.gradle @@ -84,6 +84,9 @@ dependencies { // Component that test code convert to Swagger file and include restDocs Api testImplementation('com.epages:restdocs-api-spec-mockmvc:0.18.2') //2.2 + + // expo push notification + implementation('io.github.jav:expo-server-sdk:1.1.0') } // Querydsl From 5f149065eb0e975923420e2e4dd9a75254ef508b Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:16:53 +0900 Subject: [PATCH 02/12] feat(notification): add push notification sending service --- .../notification/NotificationConfig.java | 15 ++++++++ .../NotificationHandleService.java | 18 +++++++++ .../NotificationHandleServiceImpl.java | 38 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/com/e2i/wemeet/config/notification/NotificationConfig.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/NotificationHandleService.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/NotificationHandleServiceImpl.java diff --git a/src/main/java/com/e2i/wemeet/config/notification/NotificationConfig.java b/src/main/java/com/e2i/wemeet/config/notification/NotificationConfig.java new file mode 100644 index 00000000..70233744 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/config/notification/NotificationConfig.java @@ -0,0 +1,15 @@ +package com.e2i.wemeet.config.notification; + +import io.github.jav.exposerversdk.PushClient; +import io.github.jav.exposerversdk.PushClientException; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class NotificationConfig { + + @Bean + public PushClient pushClient() throws PushClientException { + return new PushClient(); + } +} \ No newline at end of file diff --git a/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleService.java b/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleService.java new file mode 100644 index 00000000..602e8478 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleService.java @@ -0,0 +1,18 @@ +package com.e2i.wemeet.service.notification; + + +import java.util.List; + +public interface NotificationHandleService { + + /* + * Body 미포함 푸시 알림 전송 + */ + void sendPushNotification(List tokens, String title); + + /* + * Body 포함 푸시 알림 전송 + */ + void sendPushNotification(List tokens, String title, String body); + +} diff --git a/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleServiceImpl.java b/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleServiceImpl.java new file mode 100644 index 00000000..45b5da40 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/NotificationHandleServiceImpl.java @@ -0,0 +1,38 @@ +package com.e2i.wemeet.service.notification; + +import io.github.jav.exposerversdk.ExpoPushMessage; +import io.github.jav.exposerversdk.PushClient; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + + +@RequiredArgsConstructor +@Service +public class NotificationHandleServiceImpl implements NotificationHandleService { + + private final PushClient client; + + @Override + public void sendPushNotification(List tokens, String title) { + sendPushNotification(tokens, title, null); + } + + @Override + public void sendPushNotification(List tokens, String title, String body) { + ExpoPushMessage expoPushMessage = createExpoPushMessage(tokens, title, body); + client.sendPushNotificationsAsync(List.of(expoPushMessage)); + } + + private ExpoPushMessage createExpoPushMessage(List tokens, String title, String body) { + ExpoPushMessage expoPushMessage = new ExpoPushMessage(); + expoPushMessage.setTo(tokens); + expoPushMessage.setTitle(title); + + if (body != null && !body.isBlank()) { + expoPushMessage.setBody(body); + } + + return expoPushMessage; + } +} From db012f215c6d017336b2cd4ca541b3ec09d9a224 Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:18:53 +0900 Subject: [PATCH 03/12] feat(notification): modify meeting events to include push notification events --- .../domain/meeting/MeetingRepository.java | 14 ++++++++- .../wemeet/service/meeting/MeetingEvent.java | 11 +++++-- .../meeting/MeetingHandleServiceImpl.java | 11 +++++-- .../event/NotificatioEventService.java | 9 ++++++ .../notification/event/NotificationEvent.java | 8 +++++ .../event/NotificationEventServiceImpl.java | 31 +++++++++++++++++++ 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/e2i/wemeet/service/notification/event/NotificatioEventService.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/event/NotificationEvent.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/event/NotificationEventServiceImpl.java diff --git a/src/main/java/com/e2i/wemeet/domain/meeting/MeetingRepository.java b/src/main/java/com/e2i/wemeet/domain/meeting/MeetingRepository.java index eec8d47c..9fcd0c75 100644 --- a/src/main/java/com/e2i/wemeet/domain/meeting/MeetingRepository.java +++ b/src/main/java/com/e2i/wemeet/domain/meeting/MeetingRepository.java @@ -42,6 +42,18 @@ public interface MeetingRepository extends JpaRepository, Meeting join MeetingRequest mr on mr.partnerTeam = pt and mr.team = t where mr.meetingRequestId = :meetingRequestId """) - List findCreatedAtByMeetingRequestId(@Param("meetingRequestId") final Long meetingRequestId); + List findCreatedAtByMeetingRequestId( + @Param("meetingRequestId") final Long meetingRequestId); + + /* + * 팀장의 pushToken 조회 + * */ + @Query(""" + select p.token + from PushToken p + join Team t on t.teamLeader.memberId = p.member.memberId and t.deletedAt is null + where t.teamId = :teamId + """) + Optional findLeaderPushTokenById(@Param("teamId") final Long teamId); } diff --git a/src/main/java/com/e2i/wemeet/service/meeting/MeetingEvent.java b/src/main/java/com/e2i/wemeet/service/meeting/MeetingEvent.java index 275f2c9e..1f477953 100644 --- a/src/main/java/com/e2i/wemeet/service/meeting/MeetingEvent.java +++ b/src/main/java/com/e2i/wemeet/service/meeting/MeetingEvent.java @@ -2,17 +2,22 @@ import com.e2i.wemeet.domain.cost.Spent; import com.e2i.wemeet.service.cost.SpendEvent; +import com.e2i.wemeet.service.notification.event.NotificationEvent; import com.e2i.wemeet.service.sns.SnsEvent; public record MeetingEvent( SnsEvent snsEvent, - SpendEvent spendEvent + SpendEvent spendEvent, + NotificationEvent notificationEvent + ) { - public static MeetingEvent of(String receivePhoneNumber, String message, Spent type, Long memberId) { + public static MeetingEvent of(String receivePhoneNumber, String token, String message, + Spent type, Long memberId) { SnsEvent snsEvent = new SnsEvent(receivePhoneNumber, message); SpendEvent spendEvent = new SpendEvent(type, memberId); - return new MeetingEvent(snsEvent, spendEvent); + NotificationEvent notificationEvent = new NotificationEvent(token, message); + return new MeetingEvent(snsEvent, spendEvent, notificationEvent); } } diff --git a/src/main/java/com/e2i/wemeet/service/meeting/MeetingHandleServiceImpl.java b/src/main/java/com/e2i/wemeet/service/meeting/MeetingHandleServiceImpl.java index 6bdb6c3c..263df09f 100644 --- a/src/main/java/com/e2i/wemeet/service/meeting/MeetingHandleServiceImpl.java +++ b/src/main/java/com/e2i/wemeet/service/meeting/MeetingHandleServiceImpl.java @@ -66,7 +66,8 @@ public Long sendRequest(final SendMeetingRequestDto requestDto, final Long membe MeetingRequest request = meetingRequestRepository.save(meetingRequest); // 이벤트 발행 - publishMeetingEvent(getMeetingRequestMessage(), memberLeaderId, partnerTeam, MEETING_REQUEST); + publishMeetingEvent(getMeetingRequestMessage(), memberLeaderId, partnerTeam, + MEETING_REQUEST); return request.getMeetingRequestId(); } @@ -110,7 +111,8 @@ public Long acceptRequest(final Long memberLeaderId, final Long meetingRequestId // 미팅 성사 이벤트 발행 Team myTeam = meetingRequest.getTeam(); String leaderNickname = meetingRequest.getPartnerTeam().getTeamLeader().getNickname(); - publishMeetingEvent(getMeetingAcceptMessage(leaderNickname), memberLeaderId, myTeam, MEETING_ACCEPT); + publishMeetingEvent(getMeetingAcceptMessage(leaderNickname), memberLeaderId, myTeam, + MEETING_ACCEPT); return saveMeeting(meetingRequest).getMeetingId(); } @@ -181,8 +183,11 @@ private void publishMeetingEvent(final String message, final Long memberLeaderId final Team targetTeam, final Spent spent) { String leaderPhoneNumber = meetingRepository.findLeaderPhoneNumberById( targetTeam.getTeamId()); + String leaderPushToken = meetingRepository.findLeaderPushTokenById( + targetTeam.getTeamId()).orElse(null); + eventPublisher.publishEvent( - MeetingEvent.of(leaderPhoneNumber, message, spent, memberLeaderId) + MeetingEvent.of(leaderPhoneNumber, leaderPushToken, message, spent, memberLeaderId) ); } diff --git a/src/main/java/com/e2i/wemeet/service/notification/event/NotificatioEventService.java b/src/main/java/com/e2i/wemeet/service/notification/event/NotificatioEventService.java new file mode 100644 index 00000000..070cd327 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/event/NotificatioEventService.java @@ -0,0 +1,9 @@ +package com.e2i.wemeet.service.notification.event; + +public interface NotificatioEventService { + + /* + * 이벤트 전용 푸시 알림 전송 + */ + void sendForNotificationEvent(NotificationEvent notificationEvent); +} diff --git a/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEvent.java b/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEvent.java new file mode 100644 index 00000000..013ad24d --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEvent.java @@ -0,0 +1,8 @@ +package com.e2i.wemeet.service.notification.event; + +public record NotificationEvent(String token, String title) { + + public static NotificationEvent of(String token, String title) { + return new NotificationEvent(token, title); + } +} diff --git a/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEventServiceImpl.java b/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEventServiceImpl.java new file mode 100644 index 00000000..5e91001f --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/event/NotificationEventServiceImpl.java @@ -0,0 +1,31 @@ +package com.e2i.wemeet.service.notification.event; + +import com.e2i.wemeet.service.meeting.MeetingEvent; +import com.e2i.wemeet.service.notification.NotificationHandleService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class NotificationEventServiceImpl implements NotificatioEventService { + + private final NotificationHandleService notificationHandleService; + + @EventListener(classes = MeetingEvent.class) + public void sendForNotificationEvent(final MeetingEvent event) { + sendForNotificationEvent(event.notificationEvent()); + } + + @EventListener(classes = NotificationEvent.class) + @Override + public void sendForNotificationEvent(final NotificationEvent notificationEvent) { + if (notificationEvent.token() == null) { + return; + } + + notificationHandleService.sendPushNotification(List.of(notificationEvent.token()), + notificationEvent.title()); + } +} From b27c070c0cf833cbb5b8308c3ea0e2a3f3d39989 Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:20:07 +0900 Subject: [PATCH 04/12] feat(notification): add scheduler for recommended push notifications --- .../wemeet/config/common/SchedulerConfig.java | 10 +++++++ .../notification/PushTokenRepository.java | 13 ++++++++++ .../notification/NotificationScheduler.java | 22 ++++++++++++++++ .../notification/NotificationService.java | 14 ++++++++++ .../notification/NotificationServiceImpl.java | 26 +++++++++++++++++++ 5 files changed, 85 insertions(+) create mode 100644 src/main/java/com/e2i/wemeet/config/common/SchedulerConfig.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/NotificationScheduler.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/NotificationService.java create mode 100644 src/main/java/com/e2i/wemeet/service/notification/NotificationServiceImpl.java diff --git a/src/main/java/com/e2i/wemeet/config/common/SchedulerConfig.java b/src/main/java/com/e2i/wemeet/config/common/SchedulerConfig.java new file mode 100644 index 00000000..d66eae20 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/config/common/SchedulerConfig.java @@ -0,0 +1,10 @@ +package com.e2i.wemeet.config.common; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulerConfig { + +} \ No newline at end of file diff --git a/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java b/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java index ddffc74f..0c1576a0 100644 --- a/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java +++ b/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java @@ -1,7 +1,20 @@ package com.e2i.wemeet.domain.notification; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface PushTokenRepository extends JpaRepository { + @Query("select p.token from PushToken p where p.member.memberId is not null") + List findAllMemberTokens(); + + @Query(""" + select p.token + from PushToken p + join Member m on m.memberId = p.member.memberId + where p.member.memberId is not null + and m.role = 'ROLE_MEMBER' + """) + List findTokensOfMemberWithoutTeam(); } \ No newline at end of file diff --git a/src/main/java/com/e2i/wemeet/service/notification/NotificationScheduler.java b/src/main/java/com/e2i/wemeet/service/notification/NotificationScheduler.java new file mode 100644 index 00000000..e92a1658 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/NotificationScheduler.java @@ -0,0 +1,22 @@ +package com.e2i.wemeet.service.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class NotificationScheduler { + + private final NotificationService notificationService; + private static final String TITLE = "기다리고 기다리던 11시 11분이야!"; + private static final String BODY = "오늘의 추천 친구들을 확인해 봐! 🤩"; + + // 매일 23시 11분에 실행 + @Scheduled(cron = "0 11 23 * * ?") + public void sendPushNotificationForSuggestion() { + notificationService.sendToAllMembers(TITLE, BODY); + } +} + + diff --git a/src/main/java/com/e2i/wemeet/service/notification/NotificationService.java b/src/main/java/com/e2i/wemeet/service/notification/NotificationService.java new file mode 100644 index 00000000..5ddf8cae --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/NotificationService.java @@ -0,0 +1,14 @@ +package com.e2i.wemeet.service.notification; + +public interface NotificationService { + + /* + * 전체 사용자 푸시 알림 전송 + */ + void sendToAllMembers(String title, String body); + + /* + * 팀이 없는 사용자 푸시 알림 전송 + */ + void sendToMembersWithoutTeam(String title, String body); +} diff --git a/src/main/java/com/e2i/wemeet/service/notification/NotificationServiceImpl.java b/src/main/java/com/e2i/wemeet/service/notification/NotificationServiceImpl.java new file mode 100644 index 00000000..6dfa8a01 --- /dev/null +++ b/src/main/java/com/e2i/wemeet/service/notification/NotificationServiceImpl.java @@ -0,0 +1,26 @@ +package com.e2i.wemeet.service.notification; + +import com.e2i.wemeet.domain.notification.PushTokenRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class NotificationServiceImpl implements NotificationService { + + private final NotificationHandleService notificationHandleService; + private final PushTokenRepository pushTokenRepository; + + @Override + public void sendToAllMembers(String title, String body) { + List tokens = pushTokenRepository.findAllMemberTokens(); + notificationHandleService.sendPushNotification(tokens, title, body); + } + + @Override + public void sendToMembersWithoutTeam(String title, String body) { + List tokens = pushTokenRepository.findTokensOfMemberWithoutTeam(); + notificationHandleService.sendPushNotification(tokens, title, body); + } +} From d1d9855f4e9fe6ce5dccfdf49b192df70e42ea1b Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:20:31 +0900 Subject: [PATCH 05/12] test(notification): add notification scheduler test --- .../NotificationSchedulerTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/com/e2i/wemeet/service/notification/NotificationSchedulerTest.java diff --git a/src/test/java/com/e2i/wemeet/service/notification/NotificationSchedulerTest.java b/src/test/java/com/e2i/wemeet/service/notification/NotificationSchedulerTest.java new file mode 100644 index 00000000..b7289bf1 --- /dev/null +++ b/src/test/java/com/e2i/wemeet/service/notification/NotificationSchedulerTest.java @@ -0,0 +1,33 @@ +package com.e2i.wemeet.service.notification; + +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NotificationSchedulerTest { + + @Mock + private NotificationService notificationService; + + @InjectMocks + private NotificationScheduler notificationScheduler; + + private static final String TITLE = "기다리고 기다리던 11시 11분이야!"; + private static final String BODY = "오늘의 추천 친구들을 확인해 봐! 🤩"; + + @DisplayName("sendPushNotificationForSuggestion 메소드가 실행되면 sendToAllMembers 메소드가 실행된다.") + @Test + void sendPushNotificationForSuggestionTest() { + // when + notificationScheduler.sendPushNotificationForSuggestion(); + + // then + verify(notificationService).sendToAllMembers(TITLE, BODY); + } +} From 0de458ea0a2dae481d7974e60f700b86f9752d86 Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:25:15 +0900 Subject: [PATCH 06/12] test(notification): add NotificationHandleServiceTest --- .../NotificationHandleServiceTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/test/java/com/e2i/wemeet/service/notification/NotificationHandleServiceTest.java diff --git a/src/test/java/com/e2i/wemeet/service/notification/NotificationHandleServiceTest.java b/src/test/java/com/e2i/wemeet/service/notification/NotificationHandleServiceTest.java new file mode 100644 index 00000000..e50b54c3 --- /dev/null +++ b/src/test/java/com/e2i/wemeet/service/notification/NotificationHandleServiceTest.java @@ -0,0 +1,75 @@ +package com.e2i.wemeet.service.notification; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import io.github.jav.exposerversdk.ExpoPushMessage; +import io.github.jav.exposerversdk.PushClient; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NotificationHandleServiceTest { + + @Mock + private PushClient mockPushClient; + + @InjectMocks + private NotificationHandleServiceImpl notificationHandleService; + + @Captor + ArgumentCaptor> messageCaptor; + + + @DisplayName("푸시 알림을 전송할 수 있다.") + @Test + void sendPushNotificationWithBody() { + // given + List tokens = List.of("token1", "token2"); + String title = "Test Title"; + String body = "Test Body"; + + // when + notificationHandleService.sendPushNotification(tokens, title, body); + + // then + verify(mockPushClient).sendPushNotificationsAsync(messageCaptor.capture()); + + List capturedMessages = messageCaptor.getValue(); + assertThat(capturedMessages).hasSize(1); + ExpoPushMessage capturedMessage = capturedMessages.get(0); + + assertThat(capturedMessage.getTo()).isEqualTo(tokens); + assertThat(capturedMessage.getTitle()).isEqualTo(title); + assertThat(capturedMessage.getBody()).isEqualTo(body); + } + + @DisplayName("Body가 없는 푸시 알림을 전송할 수 있다.") + @Test + void sendPushNotificationWithoutBody() { + // given + List tokens = List.of("token1", "token2"); + String title = "Test Title"; + + // when + notificationHandleService.sendPushNotification(tokens, title); + + // then + verify(mockPushClient).sendPushNotificationsAsync(messageCaptor.capture()); + + List capturedMessages = messageCaptor.getValue(); + assertThat(capturedMessages).hasSize(1); + ExpoPushMessage capturedMessage = capturedMessages.get(0); + + assertThat(capturedMessage.getTo()).isEqualTo(tokens); + assertThat(capturedMessage.getTitle()).isEqualTo(title); + assertThat(capturedMessage.getBody()).isNull(); + } +} From 68366d6d308c1545b50a533adf0a23801d2dc2bb Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:32:42 +0900 Subject: [PATCH 07/12] test(notification): add NotificationServiceTest --- .../notification/NotificationServiceTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java diff --git a/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java b/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java new file mode 100644 index 00000000..8763f69e --- /dev/null +++ b/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java @@ -0,0 +1,57 @@ +package com.e2i.wemeet.service.notification; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import com.e2i.wemeet.domain.notification.PushTokenRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NotificationServiceTest { + + @Mock + private NotificationHandleService notificationHandleService; + + @Mock + private PushTokenRepository pushTokenRepository; + + @InjectMocks + private NotificationServiceImpl notificationService; + + private final List tokens = List.of("token1", "token2", "token3"); + private final String title = "Test Title"; + private final String body = "Test Body"; + + + @DisplayName("모든 사용자에게 푸시 알림을 보낼 수 있다.") + @Test + void sendToAllMembers_shouldCallNotificationHandleServiceWithCorrectParameters() { + // given + given(pushTokenRepository.findAllMemberTokens()).willReturn(tokens); + + // when + notificationService.sendToAllMembers(title, body); + + // then + verify(notificationHandleService).sendPushNotification(tokens, title, body); + } + + @DisplayName("팀이 없는 사용자들에게 푸시 알림을 보낼 수 있다.") + @Test + void sendToMembersWithoutTeam_shouldCallNotificationHandleServiceWithCorrectParameters() { + // given + given(pushTokenRepository.findTokensOfMemberWithoutTeam()).willReturn(tokens); + + // when + notificationService.sendToMembersWithoutTeam(title, body); + + // then + verify(notificationHandleService).sendPushNotification(tokens, title, body); + } +} \ No newline at end of file From 145fd0d1f88b587264d3dccc595366b09e676317 Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 02:51:13 +0900 Subject: [PATCH 08/12] test(notification): rename NotificationServiceTest functions --- .../wemeet/service/notification/NotificationServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java b/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java index 8763f69e..bd1a1a86 100644 --- a/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java +++ b/src/test/java/com/e2i/wemeet/service/notification/NotificationServiceTest.java @@ -31,7 +31,7 @@ class NotificationServiceTest { @DisplayName("모든 사용자에게 푸시 알림을 보낼 수 있다.") @Test - void sendToAllMembers_shouldCallNotificationHandleServiceWithCorrectParameters() { + void sendToAllMembers() { // given given(pushTokenRepository.findAllMemberTokens()).willReturn(tokens); @@ -44,7 +44,7 @@ void sendToAllMembers_shouldCallNotificationHandleServiceWithCorrectParameters() @DisplayName("팀이 없는 사용자들에게 푸시 알림을 보낼 수 있다.") @Test - void sendToMembersWithoutTeam_shouldCallNotificationHandleServiceWithCorrectParameters() { + void sendToMembersWithoutTeam() { // given given(pushTokenRepository.findTokensOfMemberWithoutTeam()).willReturn(tokens); From 9773638bcc1cc6c273a717fb919927968e77d03c Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 03:00:07 +0900 Subject: [PATCH 09/12] fix(notification): modify query's where condition --- .../e2i/wemeet/domain/notification/PushTokenRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java b/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java index c21a0f4e..ecb59571 100644 --- a/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java +++ b/src/main/java/com/e2i/wemeet/domain/notification/PushTokenRepository.java @@ -1,7 +1,7 @@ package com.e2i.wemeet.domain.notification; -import java.util.Optional; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -19,7 +19,7 @@ public interface PushTokenRepository extends JpaRepository { from PushToken p join Member m on m.memberId = p.member.memberId where p.member.memberId is not null - and m.role = 'ROLE_MEMBER' + and m.role = 'USER' """) List findTokensOfMemberWithoutTeam(); } From 1a380095ef3459aec8ecae39a2dd0b05c727fa57 Mon Sep 17 00:00:00 2001 From: chaerim Date: Fri, 10 Nov 2023 03:17:15 +0900 Subject: [PATCH 10/12] test(notification): add PushTokenRepositoryTest --- .../notification/PushTokenRepositoryTest.java | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/e2i/wemeet/domain/notification/PushTokenRepositoryTest.java b/src/test/java/com/e2i/wemeet/domain/notification/PushTokenRepositoryTest.java index dff2b519..866db4d2 100644 --- a/src/test/java/com/e2i/wemeet/domain/notification/PushTokenRepositoryTest.java +++ b/src/test/java/com/e2i/wemeet/domain/notification/PushTokenRepositoryTest.java @@ -1,19 +1,33 @@ package com.e2i.wemeet.domain.notification; +import static com.e2i.wemeet.support.fixture.MemberFixture.KAI; +import static com.e2i.wemeet.support.fixture.MemberFixture.RIM; +import static com.e2i.wemeet.support.fixture.MemberFixture.SEYUN; import static org.assertj.core.api.Assertions.assertThat; +import com.e2i.wemeet.domain.member.Member; +import com.e2i.wemeet.domain.member.MemberRepository; +import com.e2i.wemeet.domain.team.TeamRepository; +import com.e2i.wemeet.support.fixture.TeamFixture; +import com.e2i.wemeet.support.fixture.TeamMemberFixture; import com.e2i.wemeet.support.module.AbstractRepositoryUnitTest; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; - class PushTokenRepositoryTest extends AbstractRepositoryUnitTest { @Autowired private PushTokenRepository pushTokenRepository; + @Autowired + private MemberRepository memberRepository; + + @Autowired + private TeamRepository teamRepository; + @DisplayName("토큰 이름으로 PushToken을 조회할 수 있다.") @Test void findByToken() { @@ -24,6 +38,9 @@ void findByToken() { .build(); pushTokenRepository.save(token); + entityManager.flush(); + entityManager.clear(); + // when Optional findToken = pushTokenRepository.findByToken(tokenName); @@ -31,4 +48,62 @@ void findByToken() { assertThat(findToken).isPresent(); } -} \ No newline at end of file + @DisplayName("모든 사용자의 push token을 조회할 수 있다.") + @Test + void findAllMemberTokens() { + // given + Member kai = memberRepository.save(KAI.create(ANYANG_CODE)); + Member rim = memberRepository.save(RIM.create(WOMANS_CODE)); + + pushTokenRepository.save(PushToken.builder().token("token1").member(kai).build()); + pushTokenRepository.save(PushToken.builder().token("token2").member(rim).build()); + + // when + List pushTokenList = pushTokenRepository.findAllMemberTokens(); + + // then + assertThat(pushTokenList).hasSize(2); + } + + @DisplayName("모든 사용자의 push token을 조회할 때 회원가입 전의 사용자는 조회되지 않는다.") + @Test + void findAllMemberTokens_PreRegisteredMembersShouldBeExcluded() { + // given + Member kai = memberRepository.save(KAI.create(ANYANG_CODE)); + Member rim = memberRepository.save(RIM.create(WOMANS_CODE)); + + pushTokenRepository.save(PushToken.builder().token("token1").member(kai).build()); + pushTokenRepository.save(PushToken.builder().token("token2").member(rim).build()); + + pushTokenRepository.save(PushToken.builder().token("token3").build()); + + // when + List pushTokenList = pushTokenRepository.findAllMemberTokens(); + + // then + assertThat(pushTokenList).hasSize(2); + } + + @DisplayName("팀이 없는 사용자들의 push token을 조회할 수 있다.") + @Test + void findTokensOfMemberWithoutTeam() { + // given + Member kai = memberRepository.save(KAI.create(ANYANG_CODE)); + Member rim = memberRepository.save(RIM.create(WOMANS_CODE)); + Member seyun = memberRepository.save(SEYUN.create(KOREA_CODE)); + + teamRepository.save( + TeamFixture.HONGDAE_TEAM_1.create(kai, TeamMemberFixture.create_3_man())); + teamRepository.save(TeamFixture.WOMAN_TEAM.create(rim, TeamMemberFixture.create_1_woman())); + + pushTokenRepository.save(PushToken.builder().token("token1").member(kai).build()); + pushTokenRepository.save(PushToken.builder().token("token2").member(rim).build()); + pushTokenRepository.save(PushToken.builder().token("token3").member(seyun).build()); + + // when + List pushTokenList = pushTokenRepository.findTokensOfMemberWithoutTeam(); + + // then + assertThat(pushTokenList).hasSize(1); + } +} From ca060dcb5f7210f16a78ad8ed0b9b5d54843198e Mon Sep 17 00:00:00 2001 From: KAispread Date: Tue, 14 Nov 2023 17:30:03 +0900 Subject: [PATCH 11/12] chore: add jackson dependencies --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index a51aead7..fbc5531b 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,13 @@ dependencies { // Component that test code convert to Swagger file and include restDocs Api testImplementation('com.epages:restdocs-api-spec-mockmvc:0.18.2') //2.2 + +// Json Serializer + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' + implementation 'com.fasterxml.jackson.core:jackson-core:2.15.0' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.15.0' + // expo push notification implementation('io.github.jav:expo-server-sdk:1.1.0') } From f1dcb5c940b461a034b91b30d73ec75724524868 Mon Sep 17 00:00:00 2001 From: chaerim Date: Tue, 14 Nov 2023 17:38:53 +0900 Subject: [PATCH 12/12] docs: add swagger api --- .../resources/static/swagger-ui/openapi3.yaml | 1950 +++++++++++++++++ 1 file changed, 1950 insertions(+) create mode 100644 src/main/resources/static/swagger-ui/openapi3.yaml diff --git a/src/main/resources/static/swagger-ui/openapi3.yaml b/src/main/resources/static/swagger-ui/openapi3.yaml new file mode 100644 index 00000000..956d251f --- /dev/null +++ b/src/main/resources/static/swagger-ui/openapi3.yaml @@ -0,0 +1,1950 @@ +openapi: 3.0.1 +info: + title: We:meet + description: We:meet API Documentation + version: 0.1.0 +servers: +- url: http://localhost:8080 + description: LOCAL server +- url: http://ec2-52-78-215-171.ap-northeast-2.compute.amazonaws.com:8080 + description: Develop Server +- url: http://wemeet-elb-1696815651.ap-northeast-2.elb.amazonaws.com + description: Production Server +tags: [] +paths: + /v1/credit: + get: + tags: + - 크레딧 관련 API + summary: 로그인한 사용자의 크레딧을 조회합니다 + description: |2 + 로그인한 사용자의 크레딧을 조회합니다. + operationId: 크레딧 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-credit1610375099' + examples: + 크레딧 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Getting User's Credit\ + \ Success\",\"data\":30}" + /v1/meeting: + post: + tags: + - 미팅 관련 API + summary: 상대 이성 팀에게 미팅을 신청합니다. + description: |2 + 상대 이성 팀에게 미팅을 신청합니다. + 지정된 개수 만큼의 크레딧이 소모됩니다. (10) + operationId: 미팅 신청 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-meeting-374462745' + examples: + 미팅 신청: + value: "{\"partnerTeamId\":1}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 미팅 신청: + value: "{\"status\":\"SUCCESS\",\"message\":\"Send meeting request\ + \ success\",\"data\":null}" + /v1/member: + get: + tags: + - 회원 관련 API + summary: 로그인된 사용자의 정보를 조회합니다 + description: |2 + AccessToken 을 통해 로그인된 사용자의 상세 정보를 조회합니다. + operationId: 회원 상세 정보 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-995118165' + examples: + 회원 상세 정보 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Member-detail\ + \ Success\",\"data\":{\"nickname\":\"카이\",\"gender\":\"MAN\",\"\ + mbti\":\"INFJ\",\"college\":\"안양대\",\"collegeType\":\"인문사회\",\"\ + admissionYear\":\"17\",\"profileImage\":{\"basicUrl\":\"/v1/asdf\"\ + ,\"lowUrl\":\"/v1/kai\"},\"emailAuthenticated\":true}}" + post: + tags: + - 회원 관련 API + summary: 회원가입 API 입니다. + description: |2 + 회원 정보를 통해 회원가입을 진행합니다. + operationId: 회원가입 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member1362466055' + examples: + 회원가입: + value: "{\"nickname\":\"카이\",\"gender\":\"MAN\",\"phoneNumber\":\"\ + +821011112222\",\"collegeInfo\":{\"collegeCode\":\"CE-081\",\"collegeType\"\ + :\"SOCIAL\",\"admissionYear\":\"17\"},\"mbti\":\"INFJ\",\"allowMarketing\"\ + :true}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 회원가입: + value: "{\"status\":\"SUCCESS\",\"message\":\"Create Member Success\"\ + ,\"data\":null}" + delete: + tags: + - 회원 관련 API + summary: 회원 탈퇴 API 입니다. + description: |2 + 회원 탈퇴를 수행합니다. (deleteAt 컬럼에 현재 시간을 기록합니다.) + operationId: 회원 탈퇴 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 회원 탈퇴: + value: "{\"status\":\"SUCCESS\",\"message\":\"Delete Member Success\"\ + ,\"data\":null}" + patch: + tags: + - 회원 관련 API + summary: 회원 상세 정보를 수정합니다 + description: |2 + 회원의 상세 정보를 수정합니다. + nickname 과 mbti 를 수정할 수 있습니다. + operationId: 회원 상세 정보 수정 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-88903508' + examples: + 회원 상세 정보 수정: + value: "{\"nickname\":\"기우미우\",\"mbti\":\"ESTJ\"}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 회원 상세 정보 수정: + value: "{\"status\":\"SUCCESS\",\"message\":\"Update Member Success\"\ + ,\"data\":null}" + /v1/push: + post: + tags: + - 토큰 관련 API + summary: PUSH TOKEN을 저장합니다. + description: |2 + PUSH 토큰을 저장합니다. AccessToken 헤더를 전달하지 않으면 memberId가 null 로 저장됩니다. + 이미 DB 저장되어있는 토큰에 memberId를 업데이트하려면 + body에 token, AccessToken 헤더에 해당 User에 AccessToken을 전달하면 됩니다. + operationId: PUSH TOKEN 저장 요청 + parameters: + - name: AccessToken + in: header + description: Access Token + required: true + schema: + type: string + example: Bearer token + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-push-1516412703' + examples: + PUSH TOKEN 저장 요청: + value: "{\"pushToken\":\"token\"}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + PUSH TOKEN 저장 요청: + value: "{\"status\":\"SUCCESS\",\"message\":\"Push Token Save Success\"\ + ,\"data\":null}" + /v1/recommend: + post: + tags: + - 회원 관련 API + summary: 추천인을 등록합니다. + description: |2 + 위밋을 추천해준 추천인의 전화번호를 입력하여 추천인으로 등록합니다. + 계정당 1번만 가능하고 추천인 상대에게 20코인을 지급합니다. + operationId: 추천인 등록 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-recommend-330443167' + examples: + 추천인 등록: + value: "{\"phoneNumber\":\"+821012345678\"}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 추천인 등록: + value: "{\"status\":\"SUCCESS\",\"message\":\"Recommend Success\"\ + ,\"data\":null}" + /v1/suggestion: + get: + tags: + - 추천 관련 API + summary: 오늘의 추천 받기 API 입니다. + description: |2 + 오늘의 추천 정보를 반환합니다. + 하루 3개의 추천 팀 정보를 받아올 수 있습니다. + (오후 11:11 갱신) + operationId: 오늘의 추천 받기 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-suggestion1159884028' + examples: + 오늘의 추천 받기: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Suggestion Success\"\ + ,\"data\":[{\"teamId\":1,\"memberNum\":2,\"region\":\"홍대\",\"\ + profileImageURL\":\"testImageUrl.jpg\",\"mainImageURL\":\"testImageUrl.jpg\"\ + ,\"leader\":{\"nickname\":\"짱구\",\"mbti\":\"ENFP\",\"college\"\ + :\"서울대학교\",\"admissionYear\":\"19\",\"emailAuthenticated\":true}}]}" + /v1/team: + get: + tags: + - 팀 관련 API + summary: 내 팀 정보를 조회합니다. + description: |2 + 내 팀 정보를 조회합니다. + operationId: 마이 팀 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team1017300237' + examples: + 마이 팀 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get My Team Detail\ + \ Success\",\"data\":{\"hasTeam\":true,\"team\":{\"memberNum\"\ + :2,\"region\":\"신촌\",\"drinkRate\":\"술은 기분 좋을 정도로만\",\"drinkWithGame\"\ + :\"상관 없어\",\"additionalActivity\":\"카페/맛집\",\"introduction\":\"\ + 안녕하세요! 반가워요! 여자 2인 팀입니다!\",\"chatLink\":\"https://open.kakao.com/o/djsi23di\"\ + ,\"profileImageURL\":\"/v1/rim\",\"images\":[{\"url\":\"testUrl\"\ + }],\"members\":[{\"college\":\"인하대\",\"collegeType\":\"자연공학\"\ + ,\"admissionYear\":\"22\",\"mbti\":\"INTJ\"}],\"leader\":{\"nickname\"\ + :\"째림\",\"mbti\":\"ISFJ\",\"college\":\"서울여대\",\"admissionYear\"\ + :\"23\",\"emailAuthenticated\":true}}}}" + put: + tags: + - 팀 관련 API + summary: 팀 수정 API 입니다. + description: " 나의 팀 정보를 수정합니다.\n multipart/form-data 데이터로 보내주어야함 (data는\ + \ json!)\n part - 'images': 사진 파일\n part - 'data': 팀 수정 요청 데이터\n\n \ + \ :: 팀 생성과 동일!\n\n \"data\" : {\n \t\"region\" : String, // HONGDAE,\ + \ GANGNAM, SINCHON, GUNDAE 중 하나\n \t\"drinkRate\" : String, // ZERO, LOW,\ + \ MIDDLE, HIGH\n \t\"drinkWithGame\" : String, // ANY, MASTER, BEGINNER,\ + \ HATER 중 하나\n \t\"additionalActivity\" : String, // nullable // SHOW,\ + \ SPORTS, UNIQUE_EXPERIENCE, OUTDOOR_ACTIVITY, CAFE 중 하나\n \t\"introduction\"\ + \ : String, // 150 제한 \n \t\"members\" : [ // 사이즈 최소 1, 최대 3\n \t\t\t\ + \t\t\t\t{\n \t\t\t\t\t\t\t\t\"collegeInfo\": {\n \t\t\t\t\t\t\t\t\t\"\ + collegeCode\": String, // College CODE 값을 전달 ex) CE-001 \n \t\t\t\t\t\t\ + \t\t\t\"collegeType\": String, // ETC, SOCIAL, ENGINEERING, ARTS, EDUCATION,\ + \ MEDICINE 중 하나\n \t\t\t\t\t\t\t\t\t\"admissionYear\" : String, \n \t\ + \t\t\t\t\t\t\t},\n \t\t\t\t\t\t\t\t\"mbti\" : String // 잘 모를 경우 \"XXXX\"\ + \n \t\t\t\t\t\t\t}, \n \t\t\t\t\t\t\t{\n \t\t\t\t\t\t\t\t\"collegeInfo\"\ + : {\n \t\t\t\t\t\t\t\t\t\"collegeCode\": String, // College CODE 값을 전달\ + \ ex) CE-001 \n \t\t\t\t\t\t\t\t\t\"collegeType\": String, // ETC, SOCIAL,\ + \ ENGINEERING, ARTS, EDUCATION, MEDICINE 중 하나\n \t\t\t\t\t\t\t\t\t\"admissionYear\"\ + \ : String, \n \t\t\t\t\t\t\t\t},\n \t\t\t\t\t\t\t\t\"mbti\" : String\ + \ // 잘 모를 경우 \"XXXX\"\n \t\t\t\t\t\t\t}\n \t\t\t\t\t],\n \t\"chatLink\"\ + \ : String, //not null\n },\n\n \"images\": File[], // 최소 1장, 최대 10장\ + \n" + operationId: 팀 수정 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 팀 수정: + value: "{\"status\":\"SUCCESS\",\"message\":\"Update Team Success\"\ + ,\"data\":null}" + post: + tags: + - 팀 관련 API + summary: 팀 생성 API 입니다. + description: " 팀을 생성합니다.\n multipart/form-data 데이터로 보내주어야함 (data는 json!)\n\ + \ part - 'images': 사진 파일\n part - 'data': 팀 수정 요청 데이터\n\n 성공 시 AccessToken\ + \ & RefreshToken 재발급되어 Header에 함께 전송됨\n 사용자 권한 변경 USER -> MANAGER\n\n \ + \ \"data\" : {\n \t\"region\" : String, // HONGDAE, GANGNAM, SINCHON,\ + \ GUNDAE 중 하나\n \t\"drinkRate\" : String, // ZERO, LOW, MIDDLE, HIGH\n\ + \ \t\"drinkWithGame\" : String, // ANY, MASTER, BEGINNER, HATER 중 하나\n\ + \ \t\"additionalActivity\" : String, // nullable // SHOW, SPORTS, UNIQUE_EXPERIENCE,\ + \ OUTDOOR_ACTIVITY, CAFE 중 하나\n \t\"introduction\" : String, // 150 제한\ + \ \n \t\"members\" : [ // 사이즈 최소 1, 최대 3\n \t\t\t\t\t\t\t{\n \t\t\ + \t\t\t\t\t\t\"collegeInfo\": {\n \t\t\t\t\t\t\t\t\t\"collegeCode\": String,\ + \ // College CODE 값을 전달 ex) CE-001 \n \t\t\t\t\t\t\t\t\t\"collegeType\"\ + : String, // ETC, SOCIAL, ENGINEERING, ARTS, EDUCATION, MEDICINE 중 하나\n \ + \ \t\t\t\t\t\t\t\t\t\"admissionYear\" : String, \n \t\t\t\t\t\t\t\t},\n\ + \ \t\t\t\t\t\t\t\t\"mbti\" : String // 잘 모를 경우 \"XXXX\"\n \t\t\t\t\t\ + \t\t}, \n \t\t\t\t\t\t\t{\n \t\t\t\t\t\t\t\t\"collegeInfo\": {\n \ + \ \t\t\t\t\t\t\t\t\t\"collegeCode\": String, // College CODE 값을 전달 ex) CE-001\ + \ \n \t\t\t\t\t\t\t\t\t\"collegeType\": String, // ETC, SOCIAL, ENGINEERING,\ + \ ARTS, EDUCATION, MEDICINE 중 하나\n \t\t\t\t\t\t\t\t\t\"admissionYear\"\ + \ : String, \n \t\t\t\t\t\t\t\t},\n \t\t\t\t\t\t\t\t\"mbti\" : String\ + \ // 잘 모를 경우 \"XXXX\"\n \t\t\t\t\t\t\t}\n \t\t\t\t\t],\n \t\"chatLink\"\ + \ : String, //not null\n },\n\n \"images\": File[], // 최소 1장, 최대 10장\ + \n" + operationId: 팀 생성 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 팀 생성: + value: "{\"status\":\"SUCCESS\",\"message\":\"Create Team Success\"\ + ,\"data\":null}" + delete: + tags: + - 팀 관련 API + summary: 팀 삭제 API 입니다. + description: |2 + 현재 속한 팀을 삭제합니다. + 팀과 관련된 모든 정보가 삭제됩니다. + 성공 시 AccessToken & RefershToken 재발급되어 Header에 함께 전송됨 + 사용자 권한 변경 MANAGER -> USER + operationId: 팀 삭제 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 팀 삭제: + value: "{\"status\":\"SUCCESS\",\"message\":\"Delete Team Success\"\ + ,\"data\":null}" + /v1/auth/persist: + get: + tags: + - 토큰 관련 API + summary: AccessToken 을 통해 유저의 상태 정보를 반환합니다. + description: |2 + AccessToken 을 통해 유저의 상태 정보를 반환합니다. + operationId: PERSIST LOGIN 요청 + parameters: + - name: AccessToken + in: header + description: Access Token + required: true + schema: + type: string + example: Bearer token + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-auth-persist20428854' + examples: + PERSIST LOGIN 요청: + value: "{\"status\":\"SUCCESS\",\"message\":\"Persist Login Success\"\ + ,\"data\":{\"nickname\":\"닉네임\",\"emailAuthenticated\":true,\"\ + hasMainProfileImage\":true,\"basicProfileImage\":\"basicUrl\"\ + ,\"lowProfileImage\":\"lowUrl\",\"profileImageAuthenticated\"\ + :true,\"hasTeam\":true,\"pushTokens\":[\"expo token1\",\"expo\ + \ token2\"]}}" + /v1/auth/refresh: + post: + tags: + - 토큰 관련 API + summary: "RefreshToken 을 사용하여 Access, Refresh Token을 갱신합니다." + description: " Access Token & RefreshToken 을 사용하여 Access, Refresh Token을\ + \ 갱신합니다. \n\n Access Token과 Refresh Token 을 Header 에 넘겨주어야합니다.\n Access\ + \ Token 은 유저의 값을 받아오는 용도이기 때문에 만료된 상태여도 상관 없습니다.\n" + operationId: Refresh Token + parameters: + - name: AccessToken + in: header + description: Access Token + required: true + schema: + type: string + example: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJBQ0NFU1MiLCJpc3MiOiJXRTpNRUVUIiwiZXhwIjoxNjk5OTUyNzQ4LCJpZCI6MTAwLCJyb2xlIjoiVVNFUiJ9.4aTyxZI8wlHxYaoEocyZNDFQrhTIZP7FswsJkzdaQYOTtsEch22Y31sZVwQTSOfMv4V-46xCPzdMK8zEzrV8uQ + - name: RefreshToken + in: header + description: Refresh Token + required: true + schema: + type: string + example: eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJSRUZSRVNIIiwiaXNzIjoiV0U6TUVFVCIsImV4cCI6MTcwMjU0Mjk0OH0.YIvB5vy0CsJVwI67wU6vkVunYMRg-cpKLSx7TyNRu0ewDPrhPpxNWruaBZIjmZN46EjFRSaw6E5BRVBokTAZLg + responses: + "200": + description: "200" + headers: + AccessToken: + description: 새로 생성된 Access Token + schema: + type: string + RefreshToken: + description: 새로 생성된 Refresh Token + schema: + type: string + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + Refresh Token: + value: "{\"status\":\"SUCCESS\",\"message\":\"RefreshToken 을 재발급\ + 하는데 성공했습니다.\",\"data\":null}" + /v1/heart/received: + get: + tags: + - 좋아요 관련 API + summary: 받은 좋아요 내역을 조회합니다. + description: |2 + 나의 팀이 받은 좋아요 내역을 조회합니다. + 팀이 없는 경우 조회되지 않습니다. + operationId: 받은 좋아요 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-heart-received2038359125' + examples: + 받은 좋아요 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Received Heart\ + \ Detail Success\",\"data\":[{\"teamId\":1,\"memberNum\":3,\"\ + region\":\"신촌\",\"profileImageURL\":\"https://test.image.com\"\ + ,\"mainImageURL\":\"https://test.image.com\",\"receivedTime\"\ + :\"2023-11-14T17:36:07.654838\",\"leader\":{\"nickname\":\"팀장님\ + \",\"mbti\":\"ENFP\",\"college\":\"서울대\",\"admissionYear\":\"\ + 19\",\"emailAuthenticated\":true}}]}" + /v1/heart/sent: + get: + tags: + - 좋아요 관련 API + summary: 보낸 좋아요 내역을 조회합니다. + description: |2 + 내가 보낸 좋아요 내역을 조회합니다. + 팀이 없는 경우 조회되지 않습니다. + operationId: 보낸 좋아요 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-heart-sent1644510391' + examples: + 보낸 좋아요 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Sent Heart Detail\ + \ Success\",\"data\":[{\"teamId\":1,\"memberNum\":3,\"region\"\ + :\"신촌\",\"profileImageURL\":\"https://test.image.com\",\"mainImageURL\"\ + :\"https://test.image.com\",\"sentTime\":\"2023-11-14T17:36:07.445422\"\ + ,\"leader\":{\"nickname\":\"팀장님\",\"mbti\":\"ENFP\",\"college\"\ + :\"서울대\",\"admissionYear\":\"19\",\"emailAuthenticated\":true}}]}" + /v1/heart/{partnerTeamId}: + post: + tags: + - 좋아요 관련 API + summary: 상대 팀에게 좋아요를 보냅니다. + description: |2+ + 상대 팀에게 좋아요를 보냅니다. + 하루에 한 번 가능하며, 본인 팀에게는 보낼 수 없습니다. + + operationId: 좋아요 보내기 + parameters: + - name: partnerTeamId + in: path + description: "" + required: true + schema: + type: string + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 좋아요 보내기: + value: "{\"status\":\"SUCCESS\",\"message\":\"Send Heart Success\"\ + ,\"data\":null}" + /v1/meeting/accepted: + get: + tags: + - 미팅 관련 API + summary: 성사된 미팅 리스트를 조회합니다. + description: |2 + 성사된 미팅 리스트를 조회합니다. + 상대 팀이 삭제되었거나, 미팅 유효기간이 만료된 경우에는 리스트에 포함되지 않습니다. + 최신순으로 정렬되어 반환됩니다. + operationId: 성사된 미팅 리스트 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-meeting-accepted499303008' + examples: + 성사된 미팅 리스트 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get accepted meeting\ + \ list success\",\"data\":[{\"meetingId\":1,\"teamId\":1,\"memberCount\"\ + :2,\"region\":\"HONGDAE\",\"meetingAcceptTime\":\"2023-11-14T17:36:08.178798\"\ + ,\"teamProfileImageUrl\":[\"https://profile1.com\"],\"leader\"\ + :{\"leaderId\":1,\"nickname\":\"채원\",\"mbti\":\"INFP\",\"collegeName\"\ + :\"고려대학교\",\"collegeType\":\"ENGINEERING\",\"admissionYear\":\"\ + 2022\",\"leaderLowProfileImageUrl\":\"https://profile.com\",\"\ + imageAuth\":true,\"emailAuthenticated\":false},\"deleted\":false,\"\ + expired\":false},{\"meetingId\":2,\"teamId\":2,\"memberCount\"\ + :2,\"region\":\"HONGDAE\",\"meetingAcceptTime\":\"2023-11-14T17:36:08.178959\"\ + ,\"teamProfileImageUrl\":[\"https://profile2.com\"],\"leader\"\ + :{\"leaderId\":2,\"nickname\":\"째림\",\"mbti\":\"INFJ\",\"collegeName\"\ + :\"서울대학교\",\"collegeType\":\"ARTS\",\"admissionYear\":\"2019\"\ + ,\"leaderLowProfileImageUrl\":\"https://profile.com\",\"imageAuth\"\ + :true,\"emailAuthenticated\":false},\"deleted\":false,\"expired\"\ + :false}]}" + /v1/meeting/message: + post: + tags: + - 미팅 관련 API + summary: 상대 이성 팀에게 쪽지와 함께 미팅을 신청합니다. + description: |2 + 상대 이성 팀에게 쪽지와 함께 미팅을 신청합니다. + 지정된 개수 만큼의 크레딧이 소모됩니다. (12) + 메시지는 최대 50자까지 입력할 수 있습니다. + operationId: 쪽지와 미팅 신청 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-meeting-message-738424585' + examples: + 쪽지와 미팅 신청: + value: "{\"partnerTeamId\":1,\"message\":\"안녕하세요!! 재밌게 놀아봐요ㅎㅎ\"}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 쪽지와 미팅 신청: + value: "{\"status\":\"SUCCESS\",\"message\":\"Send meeting request\ + \ with message success\",\"data\":null}" + /v1/meeting/received: + get: + tags: + - 미팅 관련 API + summary: 받은 미팅 신청 리스트를 조회합니다. + description: |2 + 우리 팀이 받은 미팅 신청 리스트를 조회합니다. + 상대 팀이 삭제되었거나, 미팅 유효기간이 만료된 경우에는 리스트에 포함되지 않습니다. + 최신순으로 정렬되어 반환됩니다. + operationId: 받은 미팅 신청 리스트 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-meeting-sent792022336' + examples: + 받은 미팅 신청 리스트 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get receive meeting\ + \ request list success\",\"data\":[{\"meetingRequestId\":1,\"\ + acceptStatus\":\"PENDING\",\"requestTime\":\"2023-11-14T17:36:08.298026\"\ + ,\"partnerTeamDeleted\":false,\"teamId\":1,\"memberCount\":4,\"\ + region\":\"HONGDAE\",\"message\":\"재미있게 놀아요!\",\"teamProfileImageUrl\"\ + :[\"https://profile1.com\"],\"leader\":{\"leaderId\":1,\"nickname\"\ + :\"채원\",\"mbti\":\"INFP\",\"collegeName\":\"고려대학교\",\"collegeType\"\ + :\"ENGINEERING\",\"admissionYear\":\"2022\",\"leaderLowProfileImageUrl\"\ + :\"https://profile.com\",\"imageAuth\":true,\"emailAuthenticated\"\ + :true},\"deleted\":false,\"pending\":true},{\"meetingRequestId\"\ + :2,\"acceptStatus\":\"PENDING\",\"requestTime\":\"2023-11-14T17:36:08.298032\"\ + ,\"partnerTeamDeleted\":false,\"teamId\":2,\"memberCount\":4,\"\ + region\":\"HONGDAE\",\"message\":\"재미있게 놀아요!\",\"teamProfileImageUrl\"\ + :[\"https://profile2.com\"],\"leader\":{\"leaderId\":2,\"nickname\"\ + :\"쨰림\",\"mbti\":\"INFJ\",\"collegeName\":\"서울대학교\",\"collegeType\"\ + :\"ARTS\",\"admissionYear\":\"2019\",\"leaderLowProfileImageUrl\"\ + :\"https://profile.com\",\"imageAuth\":true,\"emailAuthenticated\"\ + :true},\"deleted\":false,\"pending\":true}]}" + /v1/meeting/sent: + get: + tags: + - 미팅 관련 API + summary: 보낸 미팅 신청 리스트를 조회합니다. + description: |2 + 보낸 미팅 신청 리스트를 조회합니다. + 상대 팀이 삭제되었거나, 미팅 유효기간이 만료된 경우에는 리스트에 포함되지 않습니다. + 최신순으로 정렬되어 반환됩니다. + operationId: 보낸 신청 리스트 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-meeting-sent792022336' + examples: + 보낸 신청 리스트 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get sent meeting request\ + \ list success\",\"data\":[{\"meetingRequestId\":1,\"acceptStatus\"\ + :\"PENDING\",\"requestTime\":\"2023-11-14T17:36:07.933014\",\"\ + partnerTeamDeleted\":false,\"teamId\":1,\"memberCount\":4,\"region\"\ + :\"HONGDAE\",\"message\":\"재미있게 놀아요!\",\"teamProfileImageUrl\"\ + :[\"https://profile1.com\"],\"leader\":{\"leaderId\":1,\"nickname\"\ + :\"채원\",\"mbti\":\"INFP\",\"collegeName\":\"고려대학교\",\"collegeType\"\ + :\"ENGINEERING\",\"admissionYear\":\"2022\",\"leaderLowProfileImageUrl\"\ + :\"https://profile.com\",\"imageAuth\":true,\"emailAuthenticated\"\ + :true},\"deleted\":false,\"pending\":true},{\"meetingRequestId\"\ + :2,\"acceptStatus\":\"PENDING\",\"requestTime\":\"2023-11-14T17:36:07.933116\"\ + ,\"partnerTeamDeleted\":false,\"teamId\":2,\"memberCount\":4,\"\ + region\":\"HONGDAE\",\"message\":\"재미있게 놀아요!\",\"teamProfileImageUrl\"\ + :[\"https://profile2.com\"],\"leader\":{\"leaderId\":2,\"nickname\"\ + :\"쨰림\",\"mbti\":\"INFJ\",\"collegeName\":\"서울대학교\",\"collegeType\"\ + :\"ARTS\",\"admissionYear\":\"2019\",\"leaderLowProfileImageUrl\"\ + :\"https://profile.com\",\"imageAuth\":true,\"emailAuthenticated\"\ + :true},\"deleted\":false,\"pending\":true}]}" + /v1/member/block: + get: + tags: + - 차단 관련 API + summary: 차단한 사용자들의 ID를 조회합니다. + description: |2 + 차단한 사용자들의 ID를 조회합니다. + operationId: 차단된 사용자의 목록을 조회합니다. + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-block-61521594' + examples: + 차단된 사용자의 목록을 조회합니다.: + value: "{\"status\":\"SUCCESS\",\"message\":\"Read Block Member\ + \ Success\",\"data\":[2,3]}" + /v1/member/profile-image: + post: + tags: + - 회원 관련 API + summary: 회원 프로필 등록 API 입니다. + description: |2 + 회원의 프로필 사진을 등록합니다. + multipart/form-data 형식으로 요청해야 합니다. + 예시) + { + "file": "test" + } + operationId: 회원 프로필 사진 등록 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/member-profile-image-upload' + examples: + 회원 프로필 사진 등록: + value: "{\"status\":\"SUCCESS\",\"message\":\"Upload Profile Image\ + \ Success\",\"data\":null}" + /v1/member/role: + get: + tags: + - 회원 관련 API + summary: 회원 Role 정보 조회 API 입니다. + description: |2 + 사용자의 팀장 여부와 팀 소속 여부를 조회할 수 있습니다. + operationId: 회원 Role 조회 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-role-1164682728' + examples: + 회원 Role 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Member Role Success\"\ + ,\"data\":{\"isManager\":true,\"hasTeam\":true}}" + /v1/suggestion/check: + get: + tags: + - 추천 관련 API + summary: 오늘의 추천 받기 여부 확인 API 입니다. + description: |2 + 오늘의 추천을 받았는지에 대한 정보를 확인할 수 있습니다. + 추천을 받았다면, isReceivedSuggestion은 true이고 추천 팀 정보가 반환됩니다. + operationId: 오늘의 추천 여부 확인 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-suggestion-check-1067270736' + examples: + 오늘의 추천 여부 확인: + value: "{\"status\":\"SUCCESS\",\"message\":\"Check Suggestion Success\"\ + ,\"data\":{\"isReceivedSuggestion\":true,\"teams\":[{\"teamId\"\ + :1,\"isLiked\":false,\"memberNum\":2,\"region\":\"홍대\",\"profileImageURL\"\ + :\"testImageUrl.jpg\",\"mainImageURL\":\"testImageUrl.jpg\",\"\ + leader\":{\"nickname\":\"짱구\",\"mbti\":\"ENFP\",\"college\":\"\ + 서울대학교\",\"admissionYear\":\"19\",\"emailAuthenticated\":true}}]}}" + /v1/team/image: + put: + tags: + - 팀 관련 API + summary: 팀 이미지를 업데이트합니다. + description: | + 팀 이미지를 업데이트합니다. + 기존 이미지를 모두 삭제한 뒤, 새로운 이미지를 저장합니다. + + multipart/form-data 데이터로 보내주어야함 (data는 json!) + "images": File[], // 최소 1장, 최대 10장 + operationId: 팀 이미지 업데이트 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team-image-453865333' + examples: + 팀 이미지 업데이트: + value: "{\"status\":\"SUCCESS\",\"message\":\"Team Image Update\ + \ Success\",\"data\":[\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/1.jpg\"\ + ,\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/2.jpg\"\ + ,\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/3.jpg\"\ + ]}" + post: + tags: + - 팀 관련 API + summary: 팀 이미지를 업로드합니다. + description: | + 팀 이미지를 업로드 합니다. + 새로 업로드하는 이미지는 기존 이미지 뒤에 위치합니다. + 한 팀당 최대 10개의 이미지를 업로드 할 수 있습니다. + + multipart/form-data 데이터로 보내주어야함 (data는 json!) + "images": File[], // 기존 이미지 + 업로드 이미지 개수가 10개를 넘지 않아야 함 + operationId: 팀 이미지 업로드 + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team-image-453865333' + examples: + 팀 이미지 업로드: + value: "{\"status\":\"SUCCESS\",\"message\":\"Team Image Upload\ + \ Success\",\"data\":[\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/1.jpg\"\ + ,\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/2.jpg\"\ + ,\"https://wemeet-bucket.s3.ap-northeast-2.amazonaws.com/teams/1/3.jpg\"\ + ]}" + delete: + tags: + - 팀 관련 API + summary: 팀 이미지를 삭제합니다. + description: | + 팀 이미지를 삭제합니다. + 삭제할 url을 전달받아, 해당 이미지를 팀에서 삭제합니다. + operationId: 팀 이미지 삭제 + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team-image486549215' + examples: + 팀 이미지 삭제: + value: "{\"deleteImageUrls\":[\"https://image.s3.ap-northeast-2.amazonaws.com/v1/teams/1.png\"\ + ,\"https://image.s3.ap-northeast-2.amazonaws.com/v1/teams/2.png\"\ + ]}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 팀 이미지 삭제: + value: "{\"status\":\"SUCCESS\",\"message\":\"Delete Team Image\ + \ Success\",\"data\":null}" + /v1/team/{teamId}: + get: + tags: + - 팀 관련 API + summary: 팀 상세 정보 조회 API 입니다. + description: |2 + 팀 ID로 상세정보를 조회합니다. + operationId: 팀 상세 정보 조회 + parameters: + - name: teamId + in: path + description: "" + required: true + schema: + type: string + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team-teamId700704606' + examples: + 팀 상세 정보 조회: + value: "{\"status\":\"SUCCESS\",\"message\":\"Get Team Detail Success\"\ + ,\"data\":{\"teamId\":1,\"isDeleted\":false,\"isLiked\":true,\"\ + meetingRequestStatus\":\"PENDING\",\"memberHasTeam\":true,\"memberNum\"\ + :4,\"region\":\"HONGDAE\",\"drinkRate\":\"LOW\",\"drinkWithGame\"\ + :\"ANY\",\"additionalActivity\":\"CAFE\",\"introduction\":\"안녕\ + 하세요! 반가워요! 홍대팀 1입니다!!\",\"chatLink\":\"https://open.kakao.com/o/ajsds1ik\"\ + ,\"teamImageUrls\":[\"/v1/test1\",\"/v1/test2\",\"/v1/test3\"\ + ],\"teamMembers\":[{\"college\":\"고려대\",\"collegeType\":\"자연공학\ + \",\"admissionYear\":\"18\",\"mbti\":\"ENFJ\"},{\"college\":\"\ + 고려대\",\"collegeType\":\"자연공학\",\"admissionYear\":\"18\",\"mbti\"\ + :\"ENFP\"},{\"college\":\"인하대\",\"collegeType\":\"자연공학\",\"admissionYear\"\ + :\"22\",\"mbti\":\"ESFJ\"}],\"leader\":{\"leaderId\":1,\"nickname\"\ + :\"카이\",\"mbti\":\"INFJ\",\"collegeName\":\"안양대\",\"collegeType\"\ + :\"SOCIAL\",\"admissionYear\":\"17\",\"leaderLowProfileImageUrl\"\ + :\"/v1/kai\",\"imageAuth\":false,\"emailAuthenticated\":true}}}" + /v1/auth/mail/request: + post: + tags: + - 인증 관련 API + summary: 이메일 인증번호를 발급하는 API 입니다. + description: |2 + mail 값으로 넘어온 이메일에 인증 번호를 발송합니다. + operationId: 이메일 인증번호 발급 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/v1-auth-mail-request-533811798' + examples: + 이메일 인증번호 발급: + value: "{\"college\":\"서울여자대학교\",\"mail\":\"ghkdalsgus0809@swu.ac.kr\"\ + }" + responses: + "200": + description: "200" + content: + application/json: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 이메일 인증번호 발급: + value: "{\"status\":\"SUCCESS\",\"message\":\"대학 메일 인증 번호 발급 성공\"\ + ,\"data\":null}" + /v1/auth/mail/validate: + post: + tags: + - 인증 관련 API + summary: 이메일 인증번호가 일치하는지 검증합니다 + description: |2 + 이메일로 전송했던 인증 번호가 일치하는지 검증합니다. + operationId: 이메일 인증번호 검증 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/v1-auth-mail-validate521266742' + examples: + 이메일 인증번호 검증: + value: "{\"mail\":\"ghkdalsgus0809@swu.ac.kr\",\"authCode\":\"628410\"\ + }" + responses: + "200": + description: "200" + content: + application/json: + schema: + $ref: '#/components/schemas/v1-auth-mail-validate2112268002' + examples: + 이메일 인증번호 검증: + value: "{\"status\":\"SUCCESS\",\"message\":\"대학 메일 인증 번호 확인 성공\"\ + ,\"data\":true}" + /v1/auth/phone/issue: + post: + tags: + - 인증 관련 API + summary: 휴대폰 인증번호를 발급하는 API 입니다. + description: |2 + target 값으로 넘어온 휴대폰 번호에 SMS 인증 번호를 발송합니다. + operationId: 휴대폰 인증번호 발급 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/v1-auth-phone-issue-817510993' + examples: + 휴대폰 인증번호 발급: + value: "{\"target\":\"+821088990011\"}" + responses: + "200": + description: "200" + content: + application/json: + schema: + $ref: '#/components/schemas/v1-member-1162606117' + examples: + 휴대폰 인증번호 발급: + value: "{\"status\":\"SUCCESS\",\"message\":\"휴대폰 인증 번호 발급 성공\"\ + ,\"data\":null}" + /v1/auth/phone/validate: + post: + tags: + - 인증 관련 API + summary: 휴대폰 인증번호가 일치하는지 검증하는 API 입니다. + description: " SMS 인증 번호를 확인하는 API 입니다 \n\n 인증 번호가 일치하면 AccessToken 과\ + \ RefreshToken을 반환합니다 \n\n 인증 번호가 일치하지만 회원가입 되어있지 않은 사용자라면 Token을 반환하지\ + \ 않습니다\n" + operationId: 휴대폰 인증번호 검증 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/v1-auth-phone-validate1564587931' + examples: + 휴대폰 인증번호 검증: + value: "{\"phone\":\"+821088990011\",\"credential\":\"429954\"}" + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-auth-phone-validate-214253953' + examples: + 휴대폰 인증번호 검증: + value: "{\"status\":\"SUCCESS\",\"message\":\"인증에 성공하였습니다.\",\"\ + data\":{\"memberId\":null,\"isRegistered\":false,\"withdrawal\"\ + :false,\"role\":[{\"authority\":\"ROLE_GUEST\"}]}}" + /v1/meeting/accept/{meetingRequestId}: + post: + tags: + - 미팅 관련 API + summary: 미팅 신청을 수락합니다. + description: |2 + 상대 이성 팀으로부터 받은 미팅 신청을 수락합니다. + 지정된 개수 만큼의 크레딧이 소모됩니다. (5) + data 에는 미팅 ID가 반환됩니다. + operationId: 미팅 신청 수락 + parameters: + - name: meetingRequestId + in: path + description: "" + required: true + schema: + type: string + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/meeting-accepted-response' + examples: + 미팅 신청 수락: + value: "{\"status\":\"SUCCESS\",\"message\":\"Meeting was successfully\ + \ matched\",\"data\":1}" + /v1/meeting/reject/{meetingRequestId}: + post: + tags: + - 미팅 관련 API + summary: 미팅 신청을 거절합니다. + description: |2 + 상대 이성 팀으로부터 받은 미팅 신청을 거절합니다. + operationId: 미팅 신청 거절 + parameters: + - name: meetingRequestId + in: path + description: "" + required: true + schema: + type: string + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-team-image486549215' + examples: + 미팅 신청 거절: + value: "{\"status\":\"SUCCESS\",\"message\":\"Meeting request successfully\ + \ Rejected\",\"data\":\"REJECT\"}" + /v1/member/block/{blockMemberId}: + post: + tags: + - 차단 관련 API + summary: 지정된 사용자를 차단합니다. + description: |2 + url에서 ID로 넘겨받은 사용자를 차단합니다. + 차단된 사용자는 다음의 기능에서 노출되지 않습니다. + - 추천 API + - 받은 좋아요 + - 보낸 좋아요 + - 성사된 매칭 + - 보낸 미팅 신청 + - 받은 미팅 신청 + - 팀 상세 조회 불가 + operationId: 차단하기 + parameters: + - name: blockMemberId + in: path + description: "" + required: true + schema: + type: string + responses: + "200": + description: "200" + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/v1-member-block-blockMemberId-356556804' + examples: + 차단하기: + value: "{\"status\":\"SUCCESS\",\"message\":\"Block Member Success\"\ + ,\"data\":2}" +components: + schemas: + v1-auth-persist20428854: + type: object + properties: + data: + type: object + properties: + basicProfileImage: + type: string + description: 일반 프로필 이미지 사진 경로를 반환합니다. + emailAuthenticated: + type: boolean + description: 이메일 인증 여부를 반환합니다. + pushTokens: + type: array + description: 사용자의 푸시 토큰들을 반환합니다. + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + nickname: + type: string + description: 사용자의 닉네임을 반환합니다. + hasMainProfileImage: + type: boolean + description: 프로필 이미지 인증 여부를 반환합니다. + profileImageAuthenticated: + type: boolean + description: 선호도 조사 여부를 반환합니다. + hasTeam: + type: boolean + description: 프로필 이미지의 등록 여부를 반환합니다. + lowProfileImage: + type: string + description: 작은 프로필 이미지 사진 경로를 반환합니다. + description: 응답 데이터 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-member-block-blockMemberId-356556804: + type: object + properties: + data: + type: number + description: 차단된 사용자의 ID + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-auth-mail-validate2112268002: + type: object + properties: + data: + type: boolean + description: 인증번호 일치 여부 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-member-995118165: + type: object + properties: + data: + type: object + properties: + college: + type: string + description: 대학교명 + gender: + type: string + description: 성별 + emailAuthenticated: + type: boolean + description: 대학 인증 여부 + collegeType: + type: string + description: 학과 정보 + admissionYear: + type: string + description: 학번 + nickname: + type: string + description: 닉네임 + mbti: + type: string + description: 본인 MBTI + profileImage: + type: object + properties: + basicUrl: + type: string + description: 회원 개인 프로필 사진 원본 + lowUrl: + type: string + description: 회원 개인 프로필 사진 저해상도 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-auth-mail-request-533811798: + type: object + properties: + college: + type: string + description: 대학교 이름 + mail: + type: string + description: 메일 주소 + v1-auth-phone-validate-214253953: + type: object + properties: + data: + type: object + properties: + role: + type: array + items: + type: object + properties: + authority: + type: string + description: 회원 권한 + isRegistered: + type: boolean + description: 회원 가입 여부 + withdrawal: + type: boolean + description: 회원 탈퇴 여부 + description: 회원 가입이 되어있지 않은 사용자의 경우 null로 채워서 반환됨 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-credit1610375099: + type: object + properties: + data: + type: number + description: 사용자의 크레딧 개수가 반환됩니다. + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-meeting-374462745: + type: object + properties: + partnerTeamId: + type: number + description: 상대 이성 팀 ID + v1-meeting-message-738424585: + type: object + properties: + partnerTeamId: + type: number + description: 상대 이성 팀 ID + message: + type: string + description: 쪽지 내용 (최대 50자) + v1-member-role-1164682728: + type: object + properties: + data: + type: object + properties: + isManager: + type: boolean + description: 팀장 여부 + hasTeam: + type: boolean + description: 팀 소속 여부 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-suggestion-check-1067270736: + type: object + properties: + data: + type: object + properties: + teams: + type: array + items: + type: object + properties: + leader: + type: object + properties: + college: + type: string + description: 추천 팀 팀장 대학교 + emailAuthenticated: + type: boolean + description: 추천 팀 팀장 대학 인증 여부 + admissionYear: + type: string + description: 추천 팀 팀장 학번 + nickname: + type: string + description: 추천 팀 팀장 닉네임 + mbti: + type: string + description: 추천 팀 팀장 MBTI + mainImageURL: + type: string + description: 추천 팀 대표 이미지 + teamId: + type: number + description: 추천 팀 아이디 + isLiked: + type: boolean + description: 좋아요 여부 + region: + type: string + description: 추천 팀 선호 지역 + memberNum: + type: number + description: 추천 팀 인원수 + profileImageURL: + type: string + description: 추천 팀 팀장 프로필 이미지 + isReceivedSuggestion: + type: boolean + description: 오늘의 추천 여부 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-member-block-61521594: + type: object + properties: + data: + type: array + description: 차단된 사용자의 ID + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + meeting-accepted-response: + title: meeting-accepted-response + type: object + v1-auth-phone-validate1564587931: + type: object + properties: + credential: + type: string + description: 인증 번호 + phone: + type: string + description: 휴대폰 번호 + v1-team-teamId700704606: + type: object + properties: + data: + type: object + properties: + leader: + type: object + properties: + collegeName: + type: string + description: 팀장 대학교 + leaderLowProfileImageUrl: + type: string + description: 팀장 프로필 사진 + emailAuthenticated: + type: boolean + description: 팀장 프로필 사진 인증 여부 + collegeType: + type: string + description: 팀장 학과 + admissionYear: + type: string + description: 팀장 학번 + nickname: + type: string + description: 팀장 닉네임 + mbti: + type: string + description: 팀장 MBTI + imageAuth: + type: boolean + description: 팀장 프로필 사진 인증 여부 + leaderId: + type: number + description: 팀장 ID + isLiked: + type: boolean + description: 팀 좋아요 여부 + additionalActivity: + type: string + description: 취미 및 관심사 + teamMembers: + type: array + items: + type: object + properties: + college: + type: string + description: 대학교 명 + collegeType: + type: string + description: 학과 + admissionYear: + type: string + description: 학번 + mbti: + type: string + description: MBTI + memberNum: + type: number + description: 팀 인원수 + drinkWithGame: + type: string + description: 술게임 여부 + isDeleted: + type: boolean + description: 팀 삭제 여부 + drinkRate: + type: string + description: 음주 수치 + teamId: + type: number + description: 팀 ID + chatLink: + type: string + description: 카카오톡 오픈 채팅방 링크 + memberHasTeam: + type: boolean + description: 요청을 한 유저가 팀에 속해있는지 여부를 반환합니다. + meetingRequestStatus: + type: string + description: |2 + 미팅 요청 상태 + ACCEPT (미팅 성사됨), + PENDING (요청 대기중), + REJECT (요청 거절당함), + EXPIRED (요청 만료됨), + null (요청 내역 없음) 중 하나 + region: + type: string + description: 선호 지역 + introduction: + type: string + description: 팀 소개 + teamImageUrls: + type: array + description: 팀 사진 URL + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-suggestion1159884028: + type: object + properties: + data: + type: array + items: + type: object + properties: + leader: + type: object + properties: + college: + type: string + description: 추천 팀 팀장 대학교 + emailAuthenticated: + type: boolean + description: 추천 팀 팀장 대학 인증 여부 + admissionYear: + type: string + description: 추천 팀 팀장 학번 + nickname: + type: string + description: 추천 팀 팀장 닉네임 + mbti: + type: string + description: 추천 팀 팀장 MBTI + mainImageURL: + type: string + description: 추천 팀 대표 이미지 + teamId: + type: number + description: 추천 팀 아이디 + region: + type: string + description: 추천 팀 선호 지역 + memberNum: + type: number + description: 추천 팀 인원수 + profileImageURL: + type: string + description: 추천 팀 팀장 프로필 이미지 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-team-image486549215: + type: object + v1-member-88903508: + type: object + properties: + nickname: + type: string + description: 닉네임 + mbti: + type: string + description: 본인 MBTI + v1-meeting-accepted499303008: + type: object + properties: + data: + type: array + items: + type: object + properties: + leader: + type: object + properties: + collegeName: + type: string + description: 팀장 대학교 이름 + leaderLowProfileImageUrl: + type: string + description: 팀장 저화질 프로필 이미지 URL + emailAuthenticated: + type: boolean + description: 팀장 대학 인증 여부 + collegeType: + type: string + description: 팀장 학과 + admissionYear: + type: string + description: 팀장 학번 + nickname: + type: string + description: 팀장 닉네임 + mbti: + type: string + description: 팀장 MBTI + imageAuth: + type: boolean + description: 팀장 프로필 인증 여부 + leaderId: + type: number + description: 팀장 ID + expired: + type: boolean + description: 미팅 만료 여부 + deleted: + type: boolean + description: 미팅 삭제 여부 + meetingAcceptTime: + type: string + description: 미팅 수락 시간 + memberCount: + type: number + description: 팀 인원 수 + teamId: + type: number + description: 팀 ID + meetingId: + type: number + description: 미팅 ID + region: + type: string + description: 지역 + teamProfileImageUrl: + type: array + description: 팀 프로필 이미지 URL + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-auth-mail-validate521266742: + type: object + properties: + authCode: + type: string + description: 인증 번호 + mail: + type: string + description: 메일 주소 + v1-auth-phone-issue-817510993: + type: object + properties: + target: + type: string + description: 휴대폰 번호 + v1-heart-received2038359125: + type: object + properties: + data: + type: array + items: + type: object + properties: + leader: + type: object + properties: + college: + type: string + description: 상대 팀 팀장 대학교 + emailAuthenticated: + type: boolean + description: 상대 팀 팀장 대학 인증 여부 + admissionYear: + type: string + description: 상대 팀 팀장 학번 + nickname: + type: string + description: 상대 팀 팀장 닉네임 + mbti: + type: string + description: 상대 팀 팀장 MBTI + mainImageURL: + type: string + description: 상대 팀 메인 이미지 URL + receivedTime: + type: string + description: 좋아요 받은 시간 + teamId: + type: number + description: 상대 팀 아이디 + memberNum: + type: number + description: 상대 팀 인원 수 + region: + type: string + description: 상대 팀 선호 지역 + profileImageURL: + type: string + description: 상대 팀 팀장 프로필 이미지 URL + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-recommend-330443167: + type: object + properties: + phoneNumber: + type: string + description: 위밋을 추천해준 추천인 전화번호 (+82101234xxxx) + v1-member-1162606117: + type: object + properties: + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-team1017300237: + type: object + properties: + data: + type: object + properties: + team: + type: object + properties: + leader: + type: object + properties: + college: + type: string + description: 팀장 대학교 정보 + emailAuthenticated: + type: boolean + description: 팀장 대학 인증 여부 + admissionYear: + type: string + description: 팀장 학번 + nickname: + type: string + description: 팀장 닉네임 + mbti: + type: string + description: 팀장 MBTI + images: + type: array + description: 팀 사진 정보 + items: + type: object + properties: + url: + type: string + description: 팀 사진 URL + drinkWithGame: + type: string + description: 술게임 여부 + members: + type: array + description: 팀원 정보 + items: + type: object + properties: + college: + type: string + description: 팀원 대학교 + collegeType: + type: string + description: 팀원 학과 타입 + admissionYear: + type: string + description: 팀원 학번 + mbti: + type: string + description: 팀원 MBTI + drinkRate: + type: string + description: 음주 수치 + additionalActivity: + type: string + description: 추가 활동 + chatLink: + type: string + description: 카카오톡 오픈 채팅방 링크 + region: + type: string + description: 선호 지역 + memberNum: + type: number + description: 팀 인원수 + profileImageURL: + type: string + description: 팀장 프로필 사진 + introduction: + type: string + description: 팀 소개 + hasTeam: + type: boolean + description: 팀 소속 여부 + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-member1362466055: + type: object + properties: + phoneNumber: + type: string + description: 핸드폰 번호 + collegeInfo: + type: object + properties: + collegeType: + type: string + description: 학과 (ENGINEERING) + admissionYear: + type: string + description: 학번 (17) + collegeCode: + type: string + description: 대학교 코드 (CE-001) + gender: + type: string + description: 성별 + allowMarketing: + type: boolean + description: 마케팅 수신 동의 여부 + nickname: + type: string + description: 닉네임 + mbti: + type: string + description: 본인 MBTI + v1-heart-sent1644510391: + type: object + properties: + data: + type: array + items: + type: object + properties: + leader: + type: object + properties: + college: + type: string + description: 상대 팀 팀장 대학교 + emailAuthenticated: + type: boolean + description: 상대 팀 팀장 대학 인증 여부 + admissionYear: + type: string + description: 상대 팀 팀장 학번 + nickname: + type: string + description: 상대 팀 팀장 닉네임 + mbti: + type: string + description: 상대 팀 팀장 MBTI + mainImageURL: + type: string + description: 상대 팀 메인 이미지 URL + teamId: + type: number + description: 상대 팀 아이디 + sentTime: + type: string + description: 좋아요 보낸 시간 + memberNum: + type: number + description: 상대 팀 인원 수 + region: + type: string + description: 상대 팀 선호 지역 + profileImageURL: + type: string + description: 상대 팀 팀장 프로필 이미지 URL + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + member-profile-image-upload: + title: member-profile-image-upload + type: object + properties: + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-meeting-sent792022336: + type: object + properties: + data: + type: array + description: "보낸 미팅 신청 목록 [배열로 반환]" + items: + type: object + properties: + requestTime: + type: string + description: 미팅 신청 시간 + leader: + type: object + properties: + collegeName: + type: string + description: 팀장 대학교 이름 + leaderLowProfileImageUrl: + type: string + description: 팀장 프로필 이미지 URL + emailAuthenticated: + type: boolean + description: 팀장 대학 인증 여부 + collegeType: + type: string + description: 팀장 학과 + admissionYear: + type: string + description: 팀장 학번 + nickname: + type: string + description: 팀장 닉네임 + mbti: + type: string + description: 팀장 MBTI + imageAuth: + type: boolean + description: 팀장 프로필 인증 여부 + leaderId: + type: number + description: 팀장 ID + deleted: + type: boolean + description: 미팅 신청 삭제 여부 + partnerTeamDeleted: + type: boolean + description: 상대 팀 삭제 여부 + pending: + type: boolean + description: 미팅 신청 대기 여부 + memberCount: + type: number + description: 팀 인원 수 + teamId: + type: number + description: 팀 ID + meetingRequestId: + type: number + description: 미팅 신청 ID + acceptStatus: + type: string + description: 응답 상태 + message: + type: string + description: 쪽지 내용 + region: + type: string + description: 지역 + teamProfileImageUrl: + type: array + description: 팀 프로필 이미지 URL + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-team-image-453865333: + type: object + properties: + data: + type: array + description: 업로드된 이미지 URL 목록을 반환합니다. + items: + oneOf: + - type: object + - type: boolean + - type: string + - type: number + message: + type: string + description: 응답 메시지 + status: + type: string + description: 응답 상태 + v1-push-1516412703: + type: object + properties: + pushToken: + type: string + description: 푸시 토큰