├── settings.gradle ├── src ├── main │ ├── http │ │ ├── address │ │ │ └── address.http │ │ ├── position │ │ │ └── position.http │ │ ├── ranking │ │ │ └── ranking.http │ │ ├── alarm │ │ │ ├── crew-alarm.http │ │ │ ├── game-alarm.http │ │ │ └── alarm.http │ │ ├── auth │ │ │ └── auth.http │ │ ├── chat │ │ │ └── chat.http │ │ ├── crew │ │ │ └── crew.http │ │ └── member │ │ │ └── member.http │ └── java │ │ └── kr │ │ └── pickple │ │ └── back │ │ ├── alarm │ │ ├── service │ │ │ ├── AlarmPublisher.java │ │ │ └── RedisAlarmPublisher.java │ │ ├── dto │ │ │ ├── response │ │ │ │ ├── AlarmResponse.java │ │ │ │ └── AlarmExistStatusResponse.java │ │ │ └── request │ │ │ │ ├── CrewAlarmUpdateStatusRequest.java │ │ │ │ └── GameAlarmUpdateStatusRequest.java │ │ ├── event │ │ │ ├── crew │ │ │ │ ├── CrewMemberJoinedEvent.java │ │ │ │ ├── CrewMemberRejectedEvent.java │ │ │ │ └── CrewJoinRequestNotificationEvent.java │ │ │ └── game │ │ │ │ ├── GameMemberJoinedEvent.java │ │ │ │ ├── GameMemberRejectedEvent.java │ │ │ │ └── GameJoinRequestNotificationEvent.java │ │ ├── exception │ │ │ ├── AlarmException.java │ │ │ └── AlarmExceptionCode.java │ │ ├── repository │ │ │ ├── SseEmitterRepository.java │ │ │ ├── SseEmitterLocalRepository.java │ │ │ ├── CrewAlarmRepository.java │ │ │ └── GameAlarmRepository.java │ │ ├── util │ │ │ ├── CursorResult.java │ │ │ ├── CrewAlarmTypeConverter.java │ │ │ └── GameAlarmTypeConverter.java │ │ ├── domain │ │ │ ├── CrewAlarmType.java │ │ │ ├── GameAlarmType.java │ │ │ └── AlarmExistsStatus.java │ │ ├── handler │ │ │ ├── GameAlarmEventHandler.java │ │ │ └── CrewAlarmEventHandler.java │ │ └── controller │ │ │ ├── CrewAlarmController.java │ │ │ └── GameAlarmController.java │ │ ├── address │ │ ├── repository │ │ │ ├── entity │ │ │ │ ├── AddressEntity.java │ │ │ │ ├── AddressDepth1Entity.java │ │ │ │ └── AddressDepth2Entity.java │ │ │ ├── AddressDepth1Repository.java │ │ │ └── AddressDepth2Repository.java │ │ ├── domain │ │ │ ├── Address.java │ │ │ ├── AllAddress.java │ │ │ └── MainAddress.java │ │ ├── exception │ │ │ ├── AddressException.java │ │ │ └── AddressExceptionCode.java │ │ ├── dto │ │ │ ├── response │ │ │ │ └── AllAddressResponse.java │ │ │ └── kakao │ │ │ │ └── KakaoAddressResponse.java │ │ ├── service │ │ │ ├── kakao │ │ │ │ ├── KakaoAddressSearchApiClient.java │ │ │ │ └── KakaoAddressSearchClient.java │ │ │ └── AddressService.java │ │ ├── controller │ │ │ └── AddressController.java │ │ ├── util │ │ │ └── AddressParser.java │ │ ├── implement │ │ │ └── AddressMapper.java │ │ └── config │ │ │ └── KakaoAddressSearchHttpInterfaceConfig.java │ │ ├── common │ │ ├── exception │ │ │ ├── ExceptionCode.java │ │ │ ├── CommonException.java │ │ │ ├── BusinessException.java │ │ │ └── CommonExceptionCode.java │ │ ├── dto │ │ │ └── ExceptionResponse.java │ │ ├── util │ │ │ ├── RandomUtil.java │ │ │ ├── RegistrationStatusConverter.java │ │ │ ├── DateTimeUtil.java │ │ │ └── RegistrationStatusAttributeConverter.java │ │ ├── config │ │ │ ├── property │ │ │ │ ├── S3Properties.java │ │ │ │ ├── RedisProperties.java │ │ │ │ └── AsyncProperties.java │ │ │ ├── CommonConfig.java │ │ │ ├── CacheType.java │ │ │ └── JpaConfig.java │ │ └── domain │ │ │ ├── BaseEntity.java │ │ │ └── RegistrationStatus.java │ │ ├── auth │ │ ├── domain │ │ │ ├── oauth │ │ │ │ ├── OauthProvider.java │ │ │ │ └── OauthMember.java │ │ │ └── token │ │ │ │ ├── AuthTokens.java │ │ │ │ └── RefreshToken.java │ │ ├── dto │ │ │ ├── response │ │ │ │ └── AccessTokenResponse.java │ │ │ └── kakao │ │ │ │ ├── KakaoTokenResponse.java │ │ │ │ └── KakaoMemberResponse.java │ │ ├── service │ │ │ ├── authcode │ │ │ │ ├── AuthCodeRequestUrlProvider.java │ │ │ │ ├── KakaoAuthCodeRequestUrlProvider.java │ │ │ │ └── AuthCodeRequestUrlProviderComposite.java │ │ │ └── memberclient │ │ │ │ ├── OauthMemberClient.java │ │ │ │ ├── KakaoAuthApiClient.java │ │ │ │ ├── KakaoMemberApiClient.java │ │ │ │ └── OauthMemberClientComposite.java │ │ ├── config │ │ │ ├── resolver │ │ │ │ ├── Login.java │ │ │ │ ├── SignUp.java │ │ │ │ ├── TokenExtractor.java │ │ │ │ ├── LoginTokenArgumentResolver.java │ │ │ │ └── RegisterTokenArgumentResolver.java │ │ │ ├── property │ │ │ │ ├── CorsProperties.java │ │ │ │ ├── JwtProperties.java │ │ │ │ └── KakaoOauthProperties.java │ │ │ ├── OauthProviderConverter.java │ │ │ ├── AuthConfig.java │ │ │ ├── WebClientConfig.java │ │ │ └── http │ │ │ │ └── KakaoMemberHttpInterfaceConfig.java │ │ ├── exception │ │ │ ├── AuthException.java │ │ │ └── AuthExceptionCode.java │ │ ├── repository │ │ │ └── RedisRepository.java │ │ └── implement │ │ │ └── TokenManager.java │ │ ├── crew │ │ ├── dto │ │ │ ├── response │ │ │ │ ├── CrewIdResponse.java │ │ │ │ ├── CrewMemberRegistrationStatusResponse.java │ │ │ │ ├── CrewResponse.java │ │ │ │ └── CrewProfileResponse.java │ │ │ ├── request │ │ │ │ ├── CrewMemberUpdateStatusRequest.java │ │ │ │ └── CrewCreateRequest.java │ │ │ └── mapper │ │ │ │ └── CrewRequestMapper.java │ │ ├── exception │ │ │ └── CrewException.java │ │ ├── util │ │ │ └── CrewStatusConverter.java │ │ ├── domain │ │ │ ├── CrewProfile.java │ │ │ ├── CrewMember.java │ │ │ ├── CrewStatus.java │ │ │ └── NewCrew.java │ │ └── repository │ │ │ ├── CrewMemberRepository.java │ │ │ ├── CrewRepository.java │ │ │ └── entity │ │ │ └── CrewMemberEntity.java │ │ ├── game │ │ ├── dto │ │ │ ├── response │ │ │ │ ├── GameIdResponse.java │ │ │ │ ├── GameMemberRegistrationStatusResponse.java │ │ │ │ ├── GameResponse.java │ │ │ │ └── MemberGameResponse.java │ │ │ ├── request │ │ │ │ ├── MannerScoreReviewsRequest.java │ │ │ │ ├── GameMemberRegistrationStatusUpdateRequest.java │ │ │ │ └── MannerScoreReview.java │ │ │ └── mapper │ │ │ │ └── GameRequestMapper.java │ │ ├── exception │ │ │ └── GameException.java │ │ ├── repository │ │ │ ├── GamePositionRepository.java │ │ │ ├── GameSearchRepository.java │ │ │ ├── entity │ │ │ │ └── GamePositionEntity.java │ │ │ ├── GamePositionJdbcRepository.java │ │ │ ├── GameRepository.java │ │ │ └── GameMemberRepository.java │ │ ├── util │ │ │ ├── CategoryConverter.java │ │ │ └── GameStatusConverter.java │ │ ├── domain │ │ │ ├── Category.java │ │ │ ├── GameStatus.java │ │ │ └── GameMember.java │ │ ├── service │ │ │ └── RedisExpirationListener.java │ │ └── implement │ │ │ └── GameMemberMapper.java │ │ ├── member │ │ ├── controller │ │ │ ├── Identification.java │ │ │ └── IdentificationAspect.java │ │ ├── exception │ │ │ ├── MemberException.java │ │ │ └── MemberExceptionCode.java │ │ ├── repository │ │ │ ├── MemberPositionRepository.java │ │ │ ├── MemberRepository.java │ │ │ ├── entity │ │ │ │ └── MemberPositionEntity.java │ │ │ └── MemberPositionJdbcRepository.java │ │ ├── util │ │ │ └── MemberStatusConverter.java │ │ ├── dto │ │ │ ├── response │ │ │ │ ├── MemberResponse.java │ │ │ │ └── MemberProfileResponse.java │ │ │ ├── mapper │ │ │ │ └── MemberRequestMapper.java │ │ │ └── request │ │ │ │ └── MemberCreateRequest.java │ │ └── domain │ │ │ ├── MemberProfile.java │ │ │ ├── NewMember.java │ │ │ ├── MemberStatus.java │ │ │ └── Member.java │ │ ├── PickpleBackApplication.java │ │ ├── chat │ │ ├── domain │ │ │ ├── PersonalChatRoomStatus.java │ │ │ ├── ChatMessage.java │ │ │ ├── RoomType.java │ │ │ └── ChatRoom.java │ │ ├── exception │ │ │ └── ChatException.java │ │ ├── dto │ │ │ ├── response │ │ │ │ ├── PersonalChatRoomStatusResponse.java │ │ │ │ ├── ChatMemberResponse.java │ │ │ │ ├── ChatMessageResponse.java │ │ │ │ ├── ChatRoomResponse.java │ │ │ │ └── ChatRoomDetailResponse.java │ │ │ └── request │ │ │ │ ├── PersonalChatRoomCreateRequest.java │ │ │ │ └── ChatMessageCreateRequest.java │ │ ├── util │ │ │ ├── RoomTypeConverter.java │ │ │ ├── RoomTypeAttributeConverter.java │ │ │ └── MessageTypeAttributeConverter.java │ │ ├── repository │ │ │ ├── ChatRoomRepository.java │ │ │ ├── ChatMessageRepository.java │ │ │ ├── entity │ │ │ │ ├── ChatRoomEntity.java │ │ │ │ ├── ChatRoomMemberEntity.java │ │ │ │ └── ChatMessageEntity.java │ │ │ └── ChatRoomMemberRepository.java │ │ ├── config │ │ │ └── ChatConfig.java │ │ └── implement │ │ │ └── ChatMapper.java │ │ ├── map │ │ ├── dto │ │ │ └── response │ │ │ │ ├── Location.java │ │ │ │ └── PolygonLocation.java │ │ ├── repository │ │ │ └── MapPolygonRepository.java │ │ ├── service │ │ │ └── MapService.java │ │ └── domain │ │ │ └── MapPolygon.java │ │ ├── position │ │ ├── exception │ │ │ ├── PositionException.java │ │ │ └── PositionExceptionCode.java │ │ ├── service │ │ │ └── PositionService.java │ │ ├── util │ │ │ └── PositionConverter.java │ │ ├── dto │ │ │ └── PositionResponse.java │ │ └── controller │ │ │ └── PositionController.java │ │ └── ranking │ │ ├── dto │ │ └── CrewRankingResponse.java │ │ ├── service │ │ ├── RankingScheduler.java │ │ └── RankingService.java │ │ └── controller │ │ └── RankingController.java ├── docs │ └── asciidoc │ │ ├── address.adoc │ │ ├── position.adoc │ │ ├── crew.adoc │ │ ├── index.adoc │ │ ├── chat.adoc │ │ ├── auth.adoc │ │ ├── member.adoc │ │ └── game.adoc └── test │ └── java │ └── kr │ └── pickple │ └── back │ ├── fixture │ ├── dto │ │ ├── AuthDtoFixtures.java │ │ ├── ChatDtoFixtures.java │ │ └── CrewDtoFixtures.java │ ├── domain │ │ ├── CrewAlarmFixtures.java │ │ ├── GameAlarmFixtures.java │ │ ├── AddressFixtures.java │ │ └── AuthFixtures.java │ └── setup │ │ ├── AddressSetup.java │ │ └── MemberSetup.java │ ├── auth │ ├── IntegrationAuthTest.java │ └── domain │ │ └── JwtProviderTest.java │ ├── alarm │ └── IntegrationAlarmTest.java │ ├── crew │ └── IntegrationCrewTest.java │ ├── game │ └── IntegrationGameTest.java │ ├── member │ └── IntegrationMemberTest.java │ └── chat │ └── IntegrationChatTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── Dockerfile └── .github ├── ISSUE_TEMPLATE └── pickple-back-이슈-템플릿.md ├── pull_request_template.md └── workflows ├── ci-push.yml └── ci-pr.yml /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pickple-back' 2 | -------------------------------------------------------------------------------- /src/main/http/address/address.http: -------------------------------------------------------------------------------- 1 | ### 지역 목록 조회 2 | GET http://localhost:8080/address 3 | -------------------------------------------------------------------------------- /src/main/http/position/position.http: -------------------------------------------------------------------------------- 1 | ### 포지션 목록 조회 2 | GET http://localhost:8080/positions 3 | -------------------------------------------------------------------------------- /src/main/http/ranking/ranking.http: -------------------------------------------------------------------------------- 1 | ### 크루 랭킹 조회 2 | GET http://localhost:8080/ranking/crews 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Java-and-Script/pickple-back/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "secret"] 2 | path = secret 3 | url = git@github.com:Java-and-Script/pickple-back-env.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /src/docs/asciidoc/address.adoc: -------------------------------------------------------------------------------- 1 | == 5. 주소(Address) 2 | 3 | === 5.1. 지역 목록 조회 4 | operation::find-all-address[snippets='http-request,http-response'] 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17-jdk-jammy 2 | ARG JAR_FILE=build/libs/*.jar 3 | COPY ${JAR_FILE} app.jar 4 | ENTRYPOINT ["java", "-jar", "/app.jar"] 5 | -------------------------------------------------------------------------------- /src/docs/asciidoc/position.adoc: -------------------------------------------------------------------------------- 1 | == 6. 포지션(Position) 2 | 3 | === 6.1. 포지션 목록 조회 4 | operation::find-all-positions[snippets='http-request,http-response'] 5 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/service/AlarmPublisher.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.service; 2 | 3 | public interface AlarmPublisher { 4 | 5 | void publish(final Long memberId, final String alarm); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/http/alarm/crew-alarm.http: -------------------------------------------------------------------------------- 1 | 2 | ### 해당 사용자 크루 관련 알람 상태 변경 3 | PATCH http://localhost:8080/crew-alarms/{crewAlarmId} 4 | Authorization: 5 | Content-Type: application/json 6 | 7 | { 8 | "isRead": true 9 | } 10 | -------------------------------------------------------------------------------- /src/main/http/alarm/game-alarm.http: -------------------------------------------------------------------------------- 1 | 2 | ### 게임 관련 알람 상태 수정 3 | PATCH http://localhost:8080/gameEntity-alarms/{gameAlarmId} 4 | Authorization: 5 | Content-Type: application/json 6 | 7 | { 8 | "isRead": true 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/repository/entity/AddressEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.repository.entity; 2 | 3 | public interface AddressEntity { 4 | 5 | Long getId(); 6 | 7 | String getName(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/dto/response/AlarmResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.dto.response; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public interface AlarmResponse { 6 | 7 | LocalDateTime getCreatedAt(); 8 | 9 | Long getAlarmId(); 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/exception/ExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public interface ExceptionCode { 6 | 7 | HttpStatus getStatus(); 8 | 9 | String getCode(); 10 | 11 | String getMessage(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/domain/oauth/OauthProvider.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.domain.oauth; 2 | 3 | public enum OauthProvider { 4 | KAKAO, 5 | ; 6 | 7 | public static OauthProvider from(final String type) { 8 | return OauthProvider.valueOf(type.toUpperCase()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/response/CrewIdResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor(staticName = "from") 8 | public class CrewIdResponse { 9 | 10 | private Long crewId; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/response/GameIdResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor(staticName = "from") 8 | public class GameIdResponse { 9 | 10 | private Long gameId; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/dto/ExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor(staticName = "from") 8 | public class ExceptionResponse { 9 | 10 | private final String code; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/dto/response/AccessTokenResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor(staticName = "of") 8 | public class AccessTokenResponse { 9 | 10 | private String accessToken; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/authcode/AuthCodeRequestUrlProvider.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.authcode; 2 | 3 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 4 | 5 | public interface AuthCodeRequestUrlProvider { 6 | 7 | OauthProvider oauthprovider(); 8 | 9 | String provideUrl(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/exception/CommonException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.exception; 2 | 3 | public class CommonException extends BusinessException { 4 | 5 | public CommonException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 6 | super(exceptionCode, rejectedValues); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/docs/asciidoc/crew.adoc: -------------------------------------------------------------------------------- 1 | == 3. 크루(Crew) 2 | 3 | === 3.1. 크루 생성 4 | 5 | --- 6 | 7 | === 3.2. 크루 상세 조회 8 | 9 | --- 10 | 11 | === 3.3. 크루 가입 신청 12 | 13 | --- 14 | 15 | === 3.4. 크루원 가입 신청자 목록 조회 16 | 17 | --- 18 | 19 | === 3.5. 크루원 가입 신청 수락 20 | 21 | --- 22 | 23 | === 3.6. 크루원 가입 신청 거절/취소 24 | 25 | --- 26 | 27 | === 3.7. 사용자 근처 크루 목록 조회 28 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/dto/AuthDtoFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.dto; 2 | 3 | import kr.pickple.back.auth.dto.response.AccessTokenResponse; 4 | 5 | public class AuthDtoFixtures { 6 | 7 | public static AccessTokenResponse accessTokenResponseBuild() { 8 | return AccessTokenResponse.of("accessToken"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/http/auth/auth.http: -------------------------------------------------------------------------------- 1 | ### Oauth 로그인 페이지 리다이렉트 2 | GET http://localhost:8080/auth/kakao 3 | 4 | ### Oauth 로그인 후 서버 로그인 과정 5 | GET http://localhost:8080/auth/login/kakao?authCode={authCode} 6 | 7 | ### AccessToken 갱신 8 | POST http://localhost:8080/auth/refresh 9 | Authorization: 10 | 11 | ### 로그아웃 12 | DELETE http://localhost:8080/auth/logout 13 | Authorization: 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/util/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.util; 2 | 3 | import java.util.Random; 4 | 5 | public final class RandomUtil { 6 | 7 | public static Integer getRandomNumber(final int start, final int end) { 8 | final Random random = new Random(); 9 | 10 | return random.nextInt(end) + start; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/crew/CrewMemberJoinedEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.crew; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class CrewMemberJoinedEvent { 11 | 12 | private final Long crewId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/game/GameMemberJoinedEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.game; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class GameMemberJoinedEvent { 11 | 12 | private final Long gameId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/memberclient/OauthMemberClient.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.memberclient; 2 | 3 | import kr.pickple.back.auth.domain.oauth.OauthMember; 4 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 5 | 6 | public interface OauthMemberClient { 7 | 8 | OauthProvider oauthProvider(); 9 | 10 | OauthMember fetch(final String code); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/domain/Address.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.domain; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class Address { 12 | 13 | private Long id; 14 | private String name; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/crew/CrewMemberRejectedEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.crew; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class CrewMemberRejectedEvent { 11 | 12 | private final Long crewId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/game/GameMemberRejectedEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.game; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class GameMemberRejectedEvent { 11 | 12 | private final Long gameId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/resolver/Login.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.resolver; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.PARAMETER) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Login { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/resolver/SignUp.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.resolver; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.PARAMETER) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SignUp { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/controller/Identification.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.controller; 2 | 3 | import static java.lang.annotation.RetentionPolicy.*; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | @Retention(RUNTIME) 10 | @Target({ElementType.METHOD}) 11 | public @interface Identification { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/crew/CrewJoinRequestNotificationEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.crew; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class CrewJoinRequestNotificationEvent { 11 | 12 | private final Long crewId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/event/game/GameJoinRequestNotificationEvent.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.event.game; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @Builder 9 | @RequiredArgsConstructor 10 | public class GameJoinRequestNotificationEvent { 11 | 12 | private final Long gameId; 13 | private final Long memberId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/PickpleBackApplication.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class PickpleBackApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(PickpleBackApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/domain/token/AuthTokens.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.domain.token; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class AuthTokens { 12 | 13 | private String accessToken; 14 | private String refreshToken; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/domain/PersonalChatRoomStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.domain; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class PersonalChatRoomStatus { 12 | 13 | private Long roomId; 14 | private Boolean isSenderActive; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/property/CorsProperties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | @ConfigurationProperties(prefix = "cors") 11 | public class CorsProperties { 12 | 13 | private final String[] urls; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/response/CrewMemberRegistrationStatusResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.response; 2 | 3 | import kr.pickple.back.common.domain.RegistrationStatus; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @AllArgsConstructor(staticName = "from") 9 | public class CrewMemberRegistrationStatusResponse { 10 | 11 | private final RegistrationStatus memberRegistrationStatus; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/exception/AuthException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class AuthException extends BusinessException { 7 | 8 | public AuthException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/exception/ChatException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class ChatException extends BusinessException { 7 | 8 | public ChatException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/exception/CrewException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class CrewException extends BusinessException { 7 | 8 | public CrewException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/exception/GameException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class GameException extends BusinessException { 7 | 8 | public GameException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/exception/AlarmException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class AlarmException extends BusinessException { 7 | 8 | public AlarmException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/dto/kakao/KakaoTokenResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.dto.kakao; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Getter 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public class KakaoTokenResponse { 10 | 11 | private String refreshToken; 12 | private String tokenType; 13 | private String accessToken; 14 | private String expiresIn; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/response/PersonalChatRoomStatusResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.response; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class PersonalChatRoomStatusResponse { 12 | 13 | private Long roomId; 14 | private Boolean isSenderActive; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/map/dto/response/Location.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.map.dto.response; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class Location { 14 | 15 | private BigDecimal latitude; 16 | private BigDecimal longitude; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/OauthProviderConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | 5 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 6 | 7 | public class OauthProviderConverter implements Converter { 8 | 9 | @Override 10 | public OauthProvider convert(final String type) { 11 | return OauthProvider.from(type); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/response/ChatMemberResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.response; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class ChatMemberResponse { 12 | 13 | private Long id; 14 | private String nickname; 15 | private String profileImageUrl; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/map/dto/response/PolygonLocation.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.map.dto.response; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class PolygonLocation { 14 | 15 | private BigDecimal lat; 16 | private BigDecimal lng; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/exception/MemberException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class MemberException extends BusinessException { 7 | 8 | public MemberException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/domain/AllAddress.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.domain; 2 | 3 | import java.util.List; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class AllAddress { 14 | 15 | private String addressDepth1Name; 16 | private List addressDepth2Names; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/exception/AddressException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class AddressException extends BusinessException { 7 | 8 | public AddressException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/GamePositionRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import kr.pickple.back.game.repository.entity.GamePositionEntity; 8 | 9 | public interface GamePositionRepository extends JpaRepository { 10 | 11 | List findAllByGameId(final Long gameId); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/exception/PositionException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.exception; 2 | 3 | import kr.pickple.back.common.exception.BusinessException; 4 | import kr.pickple.back.common.exception.ExceptionCode; 5 | 6 | public class PositionException extends BusinessException { 7 | 8 | public PositionException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 9 | super(exceptionCode, rejectedValues); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/dto/ChatDtoFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.dto; 2 | 3 | import kr.pickple.back.chat.dto.request.PersonalChatRoomCreateRequest; 4 | 5 | public class ChatDtoFixtures { 6 | 7 | public static PersonalChatRoomCreateRequest personalChatRoomCreateRequestBuild(final Long receiverId) { 8 | return PersonalChatRoomCreateRequest.builder() 9 | .receiverId(receiverId) 10 | .build(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/repository/AddressDepth1Repository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.repository; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 8 | 9 | public interface AddressDepth1Repository extends JpaRepository { 10 | 11 | Optional findByName(final String name); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/repository/SseEmitterRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.repository; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 6 | 7 | public interface SseEmitterRepository { 8 | 9 | SseEmitter save(final String emitterId, final SseEmitter sseEmitter); 10 | 11 | Optional findById(final Long emitterId); 12 | 13 | void deleteById(final Long emitterId); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/repository/MemberPositionRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import kr.pickple.back.member.repository.entity.MemberPositionEntity; 8 | 9 | public interface MemberPositionRepository extends JpaRepository { 10 | 11 | List findAllByMemberId(final Long memberId); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/util/CursorResult.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.util; 2 | 3 | import java.util.List; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Getter 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CursorResult { 15 | 16 | private List alarmResponse; 17 | private Boolean hasNext; 18 | private Long cursorId; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/map/repository/MapPolygonRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.map.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import kr.pickple.back.map.domain.MapPolygon; 6 | 7 | public interface MapPolygonRepository extends JpaRepository { 8 | 9 | MapPolygon findByAddressDepth1IdAndAddressDepth2Id( 10 | final Long addressDepth1Id, 11 | final Long addressDepth2Id 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/dto/response/AllAddressResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.dto.response; 2 | 3 | import java.util.List; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class AllAddressResponse { 14 | 15 | private String addressDepth1; 16 | private List addressDepth2List; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/property/S3Properties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | @ConfigurationProperties(prefix = "s3.default") 11 | public class S3Properties { 12 | 13 | private final String crewProfile; 14 | private final String crewBackground; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/util/CategoryConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.util; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.stereotype.Component; 5 | 6 | import kr.pickple.back.game.domain.Category; 7 | 8 | @Component 9 | public class CategoryConverter implements Converter { 10 | 11 | @Override 12 | public Category convert(final String source) { 13 | return Category.from(source); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/util/RoomTypeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.util; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.stereotype.Component; 5 | 6 | import kr.pickple.back.chat.domain.RoomType; 7 | 8 | @Component 9 | public class RoomTypeConverter implements Converter { 10 | 11 | @Override 12 | public RoomType convert(final String description) { 13 | return RoomType.from(description); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pickple-back-이슈-템플릿.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: pickple-back 이슈 템플릿 3 | about: 새 기능 추가, 버그 수정 등에 대한 이슈 생성 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | ### 📝 개요 12 | - 개요1 13 | 14 | --- 15 | 16 | 17 | ### ✅ 작업 예정 목록 18 | - [ ] 할일1 19 | 20 | --- 21 | 22 | 23 | ### 📀 관련 데이터 (화면, 테이블, API 등) 24 | #### 화면 25 | 26 | 27 | #### 테이블 명 28 | - 테이블 명 1 29 | 30 | 31 | #### API endpoint 32 | - endpoint 1 33 | -------------------------------------------------------------------------------- /src/main/http/alarm/alarm.http: -------------------------------------------------------------------------------- 1 | ### 해당 사용자 알람 sse 연결 2 | GET http://localhost:8080/alarms/subscribe 3 | Content-Type: text/event-stream 4 | Authorization: 5 | 6 | ### 해당 사용자 읽지 않은 알림 확인 7 | GET http://localhost:8080/alarms/unread 8 | Authorization: 9 | Content-Type: application/json 10 | 11 | ### 해당 사용자의 모든 알람 목록 조회 12 | GET http://localhost:8080/alarms?size=6 13 | Authorization: 14 | Content-Type: application/json 15 | 16 | ### 해당 사용자의 모든 알람 삭제 17 | DELETE http://localhost:8080/alarms 18 | Authorization: 19 | Content-Type: application/json 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/domain/oauth/OauthMember.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.domain.oauth; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class OauthMember { 12 | 13 | private Long oauthId; 14 | private String email; 15 | private String profileImageUrl; 16 | private String nickname; 17 | private OauthProvider oauthProvider; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/property/RedisProperties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | @ConfigurationProperties(prefix = "spring.data.redis") 11 | public class RedisProperties { 12 | 13 | private final String host; 14 | private final Integer port; 15 | private final String password; 16 | } 17 | -------------------------------------------------------------------------------- /src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = Pickple REST API Document 2 | :doctype: book 3 | :source-highlighter: highlightjs 4 | :toc: left 5 | :toclevels: 2 6 | :sectlinks: 7 | 8 | |=== 9 | |환경|url 10 | 11 | |로컬(Local) 12 | |`http://localhost:8080` 13 | 14 | |개발(Dev) 15 | |`https://dev.pickple.kr` 16 | 17 | |운영(Prod) 18 | |`https://prod.pickple.kr` 19 | |=== 20 | 21 | include::auth.adoc[] 22 | include::member.adoc[] 23 | include::crew.adoc[] 24 | include::gameEntity.adoc[] 25 | include::address.adoc[] 26 | include::position.adoc[] 27 | include::chat.adoc[] 28 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/memberclient/KakaoAuthApiClient.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.memberclient; 2 | 3 | import org.springframework.util.MultiValueMap; 4 | import org.springframework.web.bind.annotation.RequestParam; 5 | import org.springframework.web.service.annotation.PostExchange; 6 | 7 | import kr.pickple.back.auth.dto.kakao.KakaoTokenResponse; 8 | 9 | public interface KakaoAuthApiClient { 10 | 11 | @PostExchange 12 | KakaoTokenResponse fetchToken(@RequestParam final MultiValueMap params); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/dto/request/CrewAlarmUpdateStatusRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.dto.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @AllArgsConstructor(staticName = "from") 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class CrewAlarmUpdateStatusRequest { 13 | 14 | @NotNull(message = "크루 알림 읽음 여부는 필수입니다.") 15 | private Boolean isRead; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/dto/request/GameAlarmUpdateStatusRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.dto.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @AllArgsConstructor(staticName = "from") 11 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 12 | public class GameAlarmUpdateStatusRequest { 13 | 14 | @NotNull(message = "게임 알림 읽음 여부는 필수입니다.") 15 | private Boolean isRead; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/util/RegistrationStatusConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.util; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.stereotype.Component; 5 | 6 | import kr.pickple.back.common.domain.RegistrationStatus; 7 | 8 | @Component 9 | public class RegistrationStatusConverter implements Converter { 10 | 11 | @Override 12 | public RegistrationStatus convert(final String source) { 13 | return RegistrationStatus.from(source); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/response/GameMemberRegistrationStatusResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.response; 2 | 3 | import kr.pickple.back.common.domain.RegistrationStatus; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @Builder 11 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class GameMemberRegistrationStatusResponse { 13 | 14 | private final RegistrationStatus memberRegistrationStatus; 15 | private final Boolean isReviewDone; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/memberclient/KakaoMemberApiClient.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.memberclient; 2 | 3 | import static org.springframework.http.HttpHeaders.*; 4 | 5 | import org.springframework.web.bind.annotation.RequestHeader; 6 | import org.springframework.web.service.annotation.GetExchange; 7 | 8 | import kr.pickple.back.auth.dto.kakao.KakaoMemberResponse; 9 | 10 | public interface KakaoMemberApiClient { 11 | 12 | @GetExchange 13 | KakaoMemberResponse fetchMember(@RequestHeader(name = AUTHORIZATION) final String bearerToken); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/dto/response/AlarmExistStatusResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.dto.response; 2 | 3 | import kr.pickple.back.alarm.domain.AlarmExistsStatus; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @Builder 9 | public class AlarmExistStatusResponse { 10 | 11 | private final boolean unread; 12 | 13 | public static AlarmExistStatusResponse of(final AlarmExistsStatus status) { 14 | return AlarmExistStatusResponse.builder() 15 | .unread(status.getBooleanValue()) 16 | .build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/domain/token/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.domain.token; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | public class RefreshToken { 16 | 17 | private String token; 18 | private Long memberId; 19 | private LocalDateTime createdAt; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public abstract class BusinessException extends RuntimeException { 7 | 8 | private final ExceptionCode exceptionCode; 9 | private final Object[] rejectedValues; 10 | 11 | protected BusinessException(final ExceptionCode exceptionCode, final Object... rejectedValues) { 12 | super(exceptionCode.getMessage()); 13 | this.exceptionCode = exceptionCode; 14 | this.rejectedValues = rejectedValues; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/service/PositionService.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.service; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Service; 7 | 8 | import kr.pickple.back.position.domain.Position; 9 | import kr.pickple.back.position.dto.PositionResponse; 10 | 11 | @Service 12 | public class PositionService { 13 | 14 | public List findAllPositions() { 15 | return Arrays.stream(Position.values()) 16 | .map(PositionResponse::from) 17 | .toList(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/property/JwtProperties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | @ConfigurationProperties(prefix = "jwt") 11 | public class JwtProperties { 12 | 13 | private final String secretKey; 14 | private final Long accessTokenExpirationTime; 15 | private final Long refreshTokenExpirationTime; 16 | private final Long registerTokenExpirationTime; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/AuthConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import kr.pickple.back.auth.config.property.CorsProperties; 7 | import kr.pickple.back.auth.config.property.JwtProperties; 8 | import kr.pickple.back.auth.config.property.KakaoOauthProperties; 9 | 10 | @Configuration 11 | @EnableConfigurationProperties(value = {KakaoOauthProperties.class, CorsProperties.class, JwtProperties.class}) 12 | public class AuthConfig { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/exception/PositionExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import kr.pickple.back.common.exception.ExceptionCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public enum PositionExceptionCode implements ExceptionCode { 12 | 13 | POSITION_NOT_FOUND(HttpStatus.NOT_FOUND, "POS-001", "포지션을 찾을 수 없음"), 14 | ; 15 | 16 | private final HttpStatus status; 17 | private final String code; 18 | private final String message; 19 | } 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 👨‍💻 작업 사항 2 | 3 | ### 📑 PR 개요 4 | 5 | 6 | --- 7 | 8 | 9 | ### ✅ 작업 목록 10 | - [ ] 작업1 11 | - [ ] 작업2 12 | - [ ] 작업3 13 | 14 | --- 15 | 16 | ### 🙏 리뷰어에게 17 | 18 | - 설명1 19 | - 설명2 20 | 21 | --- 22 | 23 | ### 기타 24 | 25 | 26 | --- 27 | 28 | ### Prefix 29 | > PR 코멘트를 작성할 때 항상 Prefix를 붙여주세요. 30 | - `P1`: 꼭 반영해주세요 (Request changes) 31 | - `P2`: 적극적으로 고려해주세요 (Request changes) 32 | - `P3`: 웬만하면 반영해 주세요 (Comment) 33 | - `P4`: 반영해도 좋고 넘어가도 좋습니다 (Approve) 34 | - `P5`: 그냥 사소한 의견입니다 (Approve) 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/exception/AddressExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import kr.pickple.back.common.exception.ExceptionCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public enum AddressExceptionCode implements ExceptionCode { 12 | 13 | ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "ADD-001", "입력한 주소에 해당하는 리소스를 찾을 수 없음"), 14 | ; 15 | 16 | private final HttpStatus status; 17 | private final String code; 18 | private final String message; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import kr.pickple.back.common.config.property.AsyncProperties; 7 | import kr.pickple.back.common.config.property.RedisProperties; 8 | import kr.pickple.back.common.config.property.S3Properties; 9 | 10 | @Configuration 11 | @EnableConfigurationProperties(value = {RedisProperties.class, S3Properties.class, AsyncProperties.class}) 12 | public class CommonConfig { 13 | 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/docs/asciidoc/chat.adoc: -------------------------------------------------------------------------------- 1 | == 7. 채팅(Chat) 2 | 3 | === 7.1. 새 1:1 채팅방 생성 4 | operation::create-personal-room[snippets='http-request,http-response'] 5 | 6 | === 7.2. 특정 사용자와의 1:1 채팅방 존재 여부 조회 7 | operation::find-active-personal-chatroom-with-receiver[snippets='http-request,http-response'] 8 | 9 | === 7.3. 채팅방 타입에 따른 참여중인 모든 채팅방 목록 조회 10 | operation::find-all-active-chatrooms-by-type[snippets='http-request,http-response'] 11 | 12 | === 7.4. 단일 채팅방 정보 상세 조회 13 | operation::find-chatroom-by-id[snippets='http-request,http-response'] 14 | 15 | === 7.5. 특정 채팅방의 모든 메시지 목록 조회 16 | operation::find-all-messages-in-room[snippets='http-request,http-response'] 17 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/repository/AddressDepth2Repository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 9 | 10 | public interface AddressDepth2Repository extends JpaRepository { 11 | 12 | List findAllByAddressDepth1Id(final Long addressDepth1Id); 13 | 14 | Optional findByNameAndAddressDepth1Id(final String name, final Long addressDepth1Id); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/response/ChatMessageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.response; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import kr.pickple.back.chat.domain.MessageType; 6 | import lombok.AccessLevel; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 14 | public class ChatMessageResponse { 15 | 16 | private MessageType type; 17 | private String content; 18 | private ChatMemberResponse sender; 19 | private Long roomId; 20 | private LocalDateTime createdAt; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/util/CrewStatusConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Convert; 5 | import kr.pickple.back.crew.domain.CrewStatus; 6 | 7 | @Convert 8 | public final class CrewStatusConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final CrewStatus status) { 12 | return status.getDescription(); 13 | } 14 | 15 | @Override 16 | public CrewStatus convertToEntityAttribute(final String status) { 17 | return CrewStatus.from(status); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/docs/asciidoc/auth.adoc: -------------------------------------------------------------------------------- 1 | == 1. 인증(Auth) 2 | 3 | === 1.1. OAuth 로그인 페이지 리다이렉트 4 | operation::redirect-oauth-login-page[snippets='http-request,http-response'] 5 | 6 | --- 7 | 8 | === 1.2. OAuth 로그인 (Access Token, Refresh Token 발급) 9 | operation::oauth-login-authenticated[snippets='http-request,http-response'] 10 | 11 | --- 12 | 13 | === 1.3. Register Token 발급 14 | operation::oauth-login-registration[snippets='http-request,http-response'] 15 | 16 | --- 17 | 18 | === 1.4. Access Token 갱신 19 | operation::oauth-access-token-refresh[snippets='http-request,http-response'] 20 | 21 | --- 22 | 23 | === 1.5. 로그아웃 24 | operation::auth-logout[snippets='http-request,http-response'] 25 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/service/RedisAlarmPublisher.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.service; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | import org.springframework.stereotype.Service; 5 | 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Service 9 | @RequiredArgsConstructor 10 | public class RedisAlarmPublisher implements AlarmPublisher { 11 | 12 | private final RedisTemplate redisTemplate; 13 | 14 | public void publish(final Long memberId, final String alarm) { 15 | final String fullMessage = memberId + ":" + alarm; 16 | redisTemplate.convertAndSend("pubsub:queue", fullMessage); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/util/CrewAlarmTypeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Convert; 5 | import kr.pickple.back.alarm.domain.CrewAlarmType; 6 | 7 | @Convert 8 | public class CrewAlarmTypeConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final CrewAlarmType Type) { 12 | return Type.getDescription(); 13 | } 14 | 15 | @Override 16 | public CrewAlarmType convertToEntityAttribute(final String status) { 17 | return CrewAlarmType.from(status); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/util/GameAlarmTypeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Convert; 5 | import kr.pickple.back.alarm.domain.GameAlarmType; 6 | 7 | @Convert 8 | public class GameAlarmTypeConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final GameAlarmType Type) { 12 | return Type.getDescription(); 13 | } 14 | 15 | @Override 16 | public GameAlarmType convertToEntityAttribute(final String status) { 17 | return GameAlarmType.from(status); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/util/PositionConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Converter; 5 | import kr.pickple.back.position.domain.Position; 6 | 7 | @Converter 8 | public final class PositionConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final Position position) { 12 | return position.getAcronym(); 13 | } 14 | 15 | @Override 16 | public Position convertToEntityAttribute(final String acronym) { 17 | return Position.fromGamePositions(acronym); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/request/CrewMemberUpdateStatusRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import kr.pickple.back.common.domain.RegistrationStatus; 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @Builder 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class CrewMemberUpdateStatusRequest { 16 | 17 | @NotNull(message = "크루원 가입 상태는 필수입니다.") 18 | private RegistrationStatus status; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/util/GameStatusConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Converter; 5 | import kr.pickple.back.game.domain.GameStatus; 6 | 7 | @Converter 8 | public final class GameStatusConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final GameStatus gameStatus) { 12 | return gameStatus.getDescription(); 13 | } 14 | 15 | @Override 16 | public GameStatus convertToEntityAttribute(final String description) { 17 | return GameStatus.from(description); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/util/DateTimeUtil.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.util; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public final class DateTimeUtil { 10 | 11 | public static boolean isAfterThanNow(final LocalDateTime datetime) { 12 | return datetime.isAfter(LocalDateTime.now()); 13 | } 14 | 15 | public static boolean isEqualOrAfter(final LocalDateTime baseDateTime, final LocalDateTime targetDateTime) { 16 | return targetDateTime.isEqual(baseDateTime) || targetDateTime.isAfter(baseDateTime); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/util/RoomTypeAttributeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.util; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import jakarta.persistence.AttributeConverter; 6 | import kr.pickple.back.chat.domain.RoomType; 7 | 8 | @Component 9 | public class RoomTypeAttributeConverter implements AttributeConverter { 10 | 11 | @Override 12 | public String convertToDatabaseColumn(final RoomType roomType) { 13 | return roomType.getDescription(); 14 | } 15 | 16 | @Override 17 | public RoomType convertToEntityAttribute(final String description) { 18 | return RoomType.from(description); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/ChatRoomRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Modifying; 5 | import org.springframework.data.jpa.repository.Query; 6 | 7 | import kr.pickple.back.chat.repository.entity.ChatRoomEntity; 8 | 9 | public interface ChatRoomRepository extends JpaRepository { 10 | 11 | @Modifying(clearAutomatically = true) 12 | @Query("update ChatRoomEntity cr set cr.memberCount = :memberCount where cr.id = :chatRoomId") 13 | void updateMemberCount(final Long chatRoomId, final Integer memberCount); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/CacheType.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum CacheType { 9 | 10 | POLYGON("polygon", Integer.MAX_VALUE, 1000), 11 | ADDRESS("address", Integer.MAX_VALUE, 1000), 12 | RANKING("ranking", Constants.SIX_HOURS_IN_SECONDS, 10), 13 | ; 14 | 15 | private final String cacheName; 16 | private final int expireAfterWrite; 17 | private final int maximumSize; 18 | 19 | private static class Constants { 20 | 21 | public static final int SIX_HOURS_IN_SECONDS = 60 * 60 * 6; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/property/AsyncProperties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import lombok.AccessLevel; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | 11 | @Getter 12 | @Setter 13 | @Configuration 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @ConfigurationProperties(prefix = "async") 16 | public class AsyncProperties { 17 | 18 | private Integer corePoolSize; 19 | private Integer maxPoolSize; 20 | private Integer queueCapacity; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/util/MemberStatusConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Converter; 5 | import kr.pickple.back.member.domain.MemberStatus; 6 | 7 | @Converter 8 | public final class MemberStatusConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final MemberStatus memberStatus) { 12 | return memberStatus.getDescription(); 13 | } 14 | 15 | @Override 16 | public MemberStatus convertToEntityAttribute(final String description) { 17 | return MemberStatus.from(description); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/ranking/dto/CrewRankingResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.ranking.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class CrewRankingResponse { 12 | 13 | private Long id; 14 | private String name; 15 | private Integer memberCount; 16 | private Integer maxMemberCount; 17 | private String profileImageUrl; 18 | private String addressDepth1; 19 | private String addressDepth2; 20 | private Integer mannerScore; 21 | private Integer totalScore; 22 | private Integer rank; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/request/PersonalChatRoomCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Positive; 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @Builder 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class PersonalChatRoomCreateRequest { 16 | 17 | @NotNull(message = "수신자 ID가 입력되지 않음") 18 | @Positive(message = "수신자 ID는 1이상의 자연수로 입력해야함") 19 | private Long receiverId; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/util/MessageTypeAttributeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.util; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import jakarta.persistence.AttributeConverter; 6 | import kr.pickple.back.chat.domain.MessageType; 7 | 8 | @Component 9 | public class MessageTypeAttributeConverter implements AttributeConverter { 10 | 11 | @Override 12 | public String convertToDatabaseColumn(final MessageType messageType) { 13 | return messageType.getDescription(); 14 | } 15 | 16 | @Override 17 | public MessageType convertToEntityAttribute(final String description) { 18 | return MessageType.from(description); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/request/MannerScoreReviewsRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.request; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.validation.Valid; 6 | import jakarta.validation.constraints.NotNull; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | 13 | @Getter 14 | @Builder 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 17 | public class MannerScoreReviewsRequest { 18 | 19 | @Valid 20 | @NotNull(message = "매너 스코어 리뷰 목록이 입력되지 않음") 21 | private List mannerScoreReviews; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/GameSearchRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 6 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 7 | import kr.pickple.back.game.repository.entity.GameEntity; 8 | 9 | public interface GameSearchRepository { 10 | 11 | List findGamesWithInDistance(final Double latitude, final Double longitude, final Double distance); 12 | 13 | List findGamesWithInAddress( 14 | final AddressDepth1Entity addressDepth1Entity, 15 | final AddressDepth2Entity addressDepth2Entity 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/request/GameMemberRegistrationStatusUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.request; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import kr.pickple.back.common.domain.RegistrationStatus; 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Getter 12 | @Builder 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class GameMemberRegistrationStatusUpdateRequest { 16 | 17 | @NotNull(message = "게스트 모집글에 참여 신청한 사용자에 대한 상태가 입력되지 않음") 18 | private RegistrationStatus status; 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/domain/CrewAlarmFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.domain; 2 | 3 | import kr.pickple.back.alarm.domain.CrewAlarm; 4 | import kr.pickple.back.crew.repository.entity.CrewEntity; 5 | import kr.pickple.back.member.repository.entity.MemberEntity; 6 | 7 | import static kr.pickple.back.alarm.domain.CrewAlarmType.CREW_LEADER_WAITING; 8 | 9 | public class CrewAlarmFixtures { 10 | 11 | public static CrewAlarm crewAlarmBuild(final MemberEntity member,final CrewEntity crew) { 12 | return CrewAlarm.builder() 13 | .crew(crew) 14 | .member(member) 15 | .crewAlarmType(CREW_LEADER_WAITING) 16 | .build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/domain/GameAlarmFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.domain; 2 | 3 | import kr.pickple.back.alarm.domain.GameAlarm; 4 | import kr.pickple.back.game.repository.entity.GameEntity; 5 | import kr.pickple.back.member.repository.entity.MemberEntity; 6 | 7 | import static kr.pickple.back.alarm.domain.GameAlarmType.HOST_WAITING; 8 | 9 | public class GameAlarmFixtures { 10 | 11 | public static GameAlarm gameAlarmBuild(final MemberEntity member, final GameEntity gameEntity) { 12 | return GameAlarm.builder() 13 | .game(gameEntity) 14 | .member(member) 15 | .gameAlarmType(HOST_WAITING) 16 | .build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/domain/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.domain; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import kr.pickple.back.member.domain.Member; 6 | import lombok.AccessLevel; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Getter 13 | @Builder 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class ChatMessage { 17 | 18 | private Long chatMessageId; 19 | private MessageType type; 20 | private String content; 21 | private Member sender; 22 | private ChatRoom chatRoom; 23 | private LocalDateTime createdAt; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/domain/AddressFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.domain; 2 | 3 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 4 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 5 | 6 | public class AddressFixtures { 7 | 8 | public static AddressDepth1Entity addressDepth1Build() { 9 | return AddressDepth1Entity.builder() 10 | .name("서울시") 11 | .build(); 12 | } 13 | 14 | public static AddressDepth2Entity addressDepth2Build() { 15 | return AddressDepth2Entity.builder() 16 | .name("영등포구") 17 | .addressDepth1Id(addressDepth1Build()) 18 | .build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/property/KakaoOauthProperties.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.property; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | @ConfigurationProperties(prefix = "oauth.kakao") 11 | public class KakaoOauthProperties { 12 | 13 | private final String clientId; 14 | private final String clientSecret; 15 | private final String[] scope; 16 | private final String authUrl; 17 | private final String redirectUrl; 18 | private final String memberUrl; 19 | private final String addressUrl; 20 | private final String providerUrl; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/util/RegistrationStatusAttributeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.util; 2 | 3 | import jakarta.persistence.AttributeConverter; 4 | import jakarta.persistence.Converter; 5 | import kr.pickple.back.common.domain.RegistrationStatus; 6 | 7 | @Converter 8 | public final class RegistrationStatusAttributeConverter implements AttributeConverter { 9 | 10 | @Override 11 | public String convertToDatabaseColumn(final RegistrationStatus registrationStatus) { 12 | return registrationStatus.getDescription(); 13 | } 14 | 15 | @Override 16 | public RegistrationStatus convertToEntityAttribute(final String description) { 17 | return RegistrationStatus.from(description); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/config/JpaConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | import com.querydsl.jpa.impl.JPAQueryFactory; 8 | 9 | import jakarta.persistence.EntityManager; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | @Configuration 13 | @EnableJpaAuditing 14 | @RequiredArgsConstructor 15 | public class JpaConfig { 16 | 17 | private final EntityManager entityManager; 18 | 19 | @Bean 20 | public JPAQueryFactory jpaQueryFactory(final EntityManager entityManager) { 21 | return new JPAQueryFactory(entityManager); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/http/chat/chat.http: -------------------------------------------------------------------------------- 1 | ### 새 1:1 채팅방 생성 2 | POST http://localhost:8080/rooms/personal 3 | Authorization: 4 | Content-Type: application/json 5 | 6 | { 7 | "receiverId": 3 8 | } 9 | 10 | ### 특정 사용자와의 1:1 채팅방 존재 여부 조회 11 | GET http://localhost:8080/rooms/personal?receiver=1 12 | Authorization: 13 | 14 | ### 참여중인 모든 채팅방 목록 조회(개인) 15 | GET http://localhost:8080/rooms?type=개인 16 | Authorization: 17 | 18 | ### 참여중인 모든 채팅방 목록 조회(게스트 모집) 19 | GET http://localhost:8080/rooms?type=게스트 20 | Authorization: 21 | 22 | ### 참여중인 모든 채팅방 목록 조회(크루) 23 | GET http://localhost:8080/rooms?type=크루 24 | Authorization: 25 | 26 | ### 단일 채팅방 정보 상세 조회 27 | GET http://localhost:8080/rooms/1 28 | Authorization: 29 | 30 | ### 특정 채팅방의 모든 메시지 목록 조회 31 | GET http://localhost:8080/messages/rooms/1 32 | Authorization: 33 | -------------------------------------------------------------------------------- /src/docs/asciidoc/member.adoc: -------------------------------------------------------------------------------- 1 | == 2. 사용자(Member) 2 | 3 | === 2.1. 사용자 회원가입 4 | operation::create-member[snippets='http-request,http-response'] 5 | 6 | --- 7 | 8 | === 2.2. 사용자 프로필 조회 9 | operation::find-member[snippets='http-request,http-response'] 10 | 11 | --- 12 | 13 | === 2.3. 사용자의 참여 확정 게스트 모집글 목록 조회 14 | operation::find-all-member-gameEntities[snippets='http-request,http-response'] 15 | 16 | --- 17 | 18 | === 2.4. 사용자가 만든 게스트 모집글 목록 조회 19 | operation::find-all-created-gameEntities[snippets='http-request,http-response'] 20 | 21 | --- 22 | 23 | === 2.5. 사용자가 가입한 크루 목록 조회 24 | operation::find-all-crews-by-member-id[snippets='http-request,http-response'] 25 | 26 | --- 27 | 28 | === 2.6. 사용자가 만든 크루 목록 조회 29 | operation::find-created-crews-by-member-id[snippets='http-request,http-response'] 30 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/service/kakao/KakaoAddressSearchApiClient.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.service.kakao; 2 | 3 | import static org.springframework.http.HttpHeaders.*; 4 | 5 | import org.springframework.util.MultiValueMap; 6 | import org.springframework.web.bind.annotation.RequestHeader; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.service.annotation.GetExchange; 9 | 10 | import kr.pickple.back.address.dto.kakao.KakaoAddressResponse; 11 | 12 | public interface KakaoAddressSearchApiClient { 13 | 14 | @GetExchange 15 | KakaoAddressResponse fetchAddress( 16 | @RequestHeader(name = AUTHORIZATION) final String kakaoAK, 17 | @RequestParam final MultiValueMap params 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/controller/AddressController.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import kr.pickple.back.address.dto.response.AllAddressResponse; 8 | import kr.pickple.back.address.service.AddressService; 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @RestController 12 | @RequiredArgsConstructor 13 | @RequestMapping("/address") 14 | public class AddressController { 15 | 16 | private final AddressService addressService; 17 | 18 | @GetMapping 19 | public AllAddressResponse findAllAddress() { 20 | return addressService.findAllAddress(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/dto/response/MemberResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.dto.response; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.position.domain.Position; 6 | import lombok.AccessLevel; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 14 | public class MemberResponse { 15 | 16 | private Long id; 17 | private String email; 18 | private String nickname; 19 | private String introduction; 20 | private String profileImageUrl; 21 | private Integer mannerScore; 22 | private Integer mannerScoreCount; 23 | private String addressDepth1; 24 | private String addressDepth2; 25 | private List positions; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.domain; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.LastModifiedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | 9 | import jakarta.persistence.Column; 10 | import jakarta.persistence.EntityListeners; 11 | import jakarta.persistence.MappedSuperclass; 12 | import lombok.Getter; 13 | 14 | @Getter 15 | @MappedSuperclass 16 | @EntityListeners(AuditingEntityListener.class) 17 | public abstract class BaseEntity { 18 | 19 | @CreatedDate 20 | @Column(updatable = false) 21 | protected LocalDateTime createdAt; 22 | 23 | @LastModifiedDate 24 | protected LocalDateTime updatedAt; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/domain/MainAddress.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.domain; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | 7 | @Builder 8 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 9 | public class MainAddress { 10 | 11 | private Address addressDepth1; 12 | private Address addressDepth2; 13 | 14 | public Long getAddressDepth1Id() { 15 | return this.addressDepth1.getId(); 16 | } 17 | 18 | public String getAddressDepth1Name() { 19 | return this.addressDepth1.getName(); 20 | } 21 | 22 | public Long getAddressDepth2Id() { 23 | return this.addressDepth2.getId(); 24 | } 25 | 26 | public String getAddressDepth2Name() { 27 | return this.addressDepth2.getName(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/dto/PositionResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.dto; 2 | 3 | import kr.pickple.back.position.domain.Position; 4 | import lombok.AccessLevel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | @Builder 11 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class PositionResponse { 13 | 14 | private String name; 15 | private String acronym; 16 | private String description; 17 | 18 | public static PositionResponse from(final Position position) { 19 | return PositionResponse.builder() 20 | .name(position.getName()) 21 | .acronym(position.getAcronym()) 22 | .description(position.getDescription()) 23 | .build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/request/ChatMessageCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Positive; 6 | import lombok.AccessLevel; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | 12 | @Getter 13 | @Builder 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class ChatMessageCreateRequest { 17 | 18 | @NotNull(message = "송신자 ID가 입력되지 않음") 19 | @Positive(message = "송신자 ID는 1이상의 자연수로 입력해야함") 20 | private Long senderId; 21 | 22 | @NotBlank(message = "채팅 메시지의 내용이 입력되지 않음") 23 | private String content; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/util/AddressParser.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public final class AddressParser { 7 | 8 | public static final int ADDRESS_DEPTH_SIZE = 2; 9 | private static final String ADDRESS_DELIMITER_REGEX = "[\\s+]"; 10 | 11 | public static List splitToAddressDepth1And2(final String mainAddressName) { 12 | return Arrays.stream(mainAddressName.split(ADDRESS_DELIMITER_REGEX)) 13 | .limit(ADDRESS_DEPTH_SIZE) 14 | .map(AddressParser::addCitySuffixToFirstAddress) 15 | .toList(); 16 | } 17 | 18 | private static String addCitySuffixToFirstAddress(final String firstAddressName) { 19 | return firstAddressName.equals("서울") ? "서울시" : firstAddressName; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/exception/CommonExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | public enum CommonExceptionCode implements ExceptionCode { 11 | 12 | COMMON_NOT_FOUND(HttpStatus.NOT_FOUND, "COM-001", "요청한 URL에 해당하는 리소스를 찾을 수 없음"), 13 | COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST, "COM-002", "사용자 입력 유효성 검사 실패"), 14 | COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "COM-003", "허용되지 않은 HTTP Method 요청 발생"), 15 | COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COM-004", "기타 서버 내부 에러 발생"), 16 | ; 17 | 18 | private final HttpStatus status; 19 | private final String code; 20 | private final String message; 21 | } 22 | -------------------------------------------------------------------------------- /src/docs/asciidoc/game.adoc: -------------------------------------------------------------------------------- 1 | == 4. 게스트 모집(Game) 2 | 3 | === 4.1. 게스트 모집글 작성 4 | 5 | --- 6 | 7 | === 4.2. 조건별 게스트 모집글 조회 8 | 9 | --- 10 | 11 | === 4.3. 게스트 모집글 상세 조회 12 | operation::find-gameEntity[snippets='http-request,http-response'] 13 | 14 | --- 15 | 16 | === 4.4. 게스트 모집 참여 신청 17 | operation::register-gameMemberEntity[snippets='http-request,http-response'] 18 | 19 | --- 20 | 21 | === 4.5. 게스트 모집에 참여 신청된 혹은 확정된 사용자 정보 목록 조회 22 | operation::find-all-waiting-or-confirmed-gameMemberEntities[snippets='http-request,http-response'] 23 | 24 | --- 25 | 26 | === 4.6. 게스트 모집 참여 신청 수락 27 | operation::confirm-gameMemberEntity[snippets='http-request,http-response'] 28 | 29 | --- 30 | 31 | === 4.7. 게스트 모집 참여 신청 거절/취소 32 | operation::delete-gameMemberEntity[snippets='http-request,http-response'] 33 | 34 | --- 35 | 36 | === 4.8. 다른 사용자 매너 스코어 리뷰 37 | operation::review-mannerScores[snippets='http-request,http-response'] 38 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/mapper/CrewRequestMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.mapper; 2 | 3 | import kr.pickple.back.crew.domain.NewCrew; 4 | import kr.pickple.back.crew.dto.request.CrewCreateRequest; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public final class CrewRequestMapper { 10 | 11 | public static NewCrew mapToNewCrewDomain(final CrewCreateRequest crewCreateRequest) { 12 | return NewCrew.builder() 13 | .name(crewCreateRequest.getName()) 14 | .content(crewCreateRequest.getContent()) 15 | .maxMemberCount(crewCreateRequest.getMaxMemberCount()) 16 | .addressDepth1Name(crewCreateRequest.getAddressDepth1()) 17 | .addressDepth2Name(crewCreateRequest.getAddressDepth2()) 18 | .build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/response/ChatRoomResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.response; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.LocalTime; 5 | 6 | import kr.pickple.back.chat.domain.RoomType; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | @Builder 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class ChatRoomResponse { 16 | 17 | private Long id; 18 | private String roomName; 19 | private String roomIconImageUrl; 20 | private RoomType type; 21 | private Integer memberCount; 22 | private Integer maxMemberCount; 23 | private LocalTime playStartTime; 24 | private Integer playTimeMinutes; 25 | private String lastMessageContent; 26 | private LocalDateTime lastMessageCreatedAt; 27 | private LocalDateTime createdAt; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/controller/IdentificationAspect.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.controller; 2 | 3 | import static kr.pickple.back.member.exception.MemberExceptionCode.*; 4 | 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.annotation.Before; 7 | import org.springframework.stereotype.Component; 8 | 9 | import kr.pickple.back.member.exception.MemberException; 10 | 11 | @Aspect 12 | @Component 13 | public class IdentificationAspect { 14 | 15 | @Before(value = "@annotation(Identification) && args(loggedInMemberId, memberId, ..)", argNames = "loggedInMemberId, memberId") 16 | public void checkIdentification( 17 | final Long loggedInMemberId, 18 | final Long memberId 19 | ) { 20 | if (!loggedInMemberId.equals(memberId)) { 21 | throw new MemberException(MEMBER_MISMATCH, loggedInMemberId, memberId); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/dto/response/MemberProfileResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.dto.response; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.crew.dto.response.CrewResponse; 6 | import kr.pickple.back.position.domain.Position; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | @Builder 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class MemberProfileResponse { 16 | 17 | private Long id; 18 | private String email; 19 | private String nickname; 20 | private String introduction; 21 | private String profileImageUrl; 22 | private Integer mannerScore; 23 | private Integer mannerScoreCount; 24 | private String addressDepth1; 25 | private String addressDepth2; 26 | private List positions; 27 | private List crews; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/domain/CrewProfile.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.domain; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.member.domain.Member; 6 | import lombok.AccessLevel; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 14 | public class CrewProfile { 15 | 16 | private Long crewId; 17 | private String name; 18 | private String content; 19 | private Integer memberCount; 20 | private Integer maxMemberCount; 21 | private String profileImageUrl; 22 | private String backgroundImageUrl; 23 | private CrewStatus status; 24 | private Integer likeCount; 25 | private Integer competitionPoint; 26 | private Member leader; 27 | private String addressDepth1; 28 | private String addressDepth2; 29 | private List members; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/dto/response/ChatRoomDetailResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.dto.response; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.LocalTime; 5 | import java.util.List; 6 | 7 | import kr.pickple.back.chat.domain.RoomType; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | 13 | @Getter 14 | @Builder 15 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class ChatRoomDetailResponse { 17 | 18 | private Long id; 19 | private String roomName; 20 | private String roomIconImageUrl; 21 | private RoomType type; 22 | private Long domainId; 23 | private Integer memberCount; 24 | private Integer maxMemberCount; 25 | private LocalTime playStartTime; 26 | private Integer playTimeMinutes; 27 | private List members; 28 | private LocalDateTime createdAt; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/response/CrewResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.response; 2 | 3 | import kr.pickple.back.crew.domain.CrewStatus; 4 | import kr.pickple.back.member.dto.response.MemberResponse; 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Getter; 9 | 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class CrewResponse { 14 | 15 | private Long id; 16 | private String name; 17 | private String content; 18 | private Integer memberCount; 19 | private Integer maxMemberCount; 20 | private String profileImageUrl; 21 | private String backgroundImageUrl; 22 | private CrewStatus status; 23 | private Integer likeCount; 24 | private Integer competitionPoint; 25 | private MemberResponse leader; 26 | private String addressDepth1; 27 | private String addressDepth2; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/ranking/service/RankingScheduler.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.ranking.service; 2 | 3 | import org.redisson.api.RLock; 4 | import org.redisson.api.RedissonClient; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Component 11 | @RequiredArgsConstructor 12 | public class RankingScheduler { 13 | 14 | private final RankingService rankingService; 15 | private final RedissonClient redissonClient; 16 | 17 | @Scheduled(cron = "0 0 */6 * * *") 18 | public void refreshRankingCache() { 19 | RLock lock = redissonClient.getLock("refreshCrewRankingCacheLock"); 20 | if (lock.tryLock()) { 21 | try { 22 | rankingService.putCrewRankingCache(); 23 | } finally { 24 | lock.unlock(); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/auth/IntegrationAuthTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.mock.mockito.MockBean; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import kr.pickple.back.auth.domain.token.JwtProvider; 11 | import kr.pickple.back.auth.service.OauthService; 12 | 13 | @SpringBootTest 14 | @AutoConfigureMockMvc 15 | @AutoConfigureRestDocs 16 | public abstract class IntegrationAuthTest { 17 | 18 | @Autowired 19 | protected JwtProvider jwtProvider; 20 | 21 | @Autowired 22 | protected MockMvc mockMvc; 23 | 24 | @MockBean 25 | protected OauthService oauthService; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/exception/AlarmExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import kr.pickple.back.common.exception.ExceptionCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public enum AlarmExceptionCode implements ExceptionCode { 12 | 13 | ALARM_STATUS_NOT_FOUND(HttpStatus.NOT_FOUND, "ALA-001", "알람 읽음 여부를 찾을 수 없음"), 14 | ALARM_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "ALA-002", "알림 타입을 찾을 수 없음"), 15 | ALARM_EXISTS_STATUS_NOT_FOUND(HttpStatus.NOT_FOUND, "ALA-003", "사용자의 알람 상태 여부를 찾을 수 없음"), 16 | ALARM_NOT_FOUND(HttpStatus.NOT_FOUND, "ALA-004", "해당 알람을 찾을 수 없음"), 17 | ALARM_CONVERT_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "ALA-005", "알람 타입 변환 중 오류 발생"), 18 | ; 19 | 20 | private final HttpStatus status; 21 | private final String code; 22 | private final String message; 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/dto/CrewDtoFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.dto; 2 | 3 | import kr.pickple.back.common.domain.RegistrationStatus; 4 | import kr.pickple.back.crew.dto.request.CrewCreateRequest; 5 | import kr.pickple.back.crew.dto.request.CrewMemberUpdateStatusRequest; 6 | 7 | public class CrewDtoFixtures { 8 | 9 | public static CrewCreateRequest crewCreateRequestBuild() { 10 | return CrewCreateRequest.builder() 11 | .name("백둥크루") 12 | .content("안녕하세요. 백둥크루 입니다~!") 13 | .maxMemberCount(15) 14 | .addressDepth1("서울시") 15 | .addressDepth2("영등포구") 16 | .build(); 17 | } 18 | 19 | public static CrewMemberUpdateStatusRequest crewMemberUpdateStatusRequest( 20 | final RegistrationStatus status 21 | ) { 22 | return CrewMemberUpdateStatusRequest.builder() 23 | .status(status) 24 | .build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/request/MannerScoreReview.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.request; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Positive; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | import lombok.NoArgsConstructor; 12 | 13 | @Getter 14 | @Builder 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 17 | public class MannerScoreReview { 18 | 19 | @NotNull(message = "회원 ID가 입력되지 않음") 20 | @Positive(message = "회원 ID는 1이상의 자연수로 입력") 21 | private Long memberId; 22 | 23 | @NotNull(message = "리뷰에 사용되는 매너 스코어가 입력되지 않음") 24 | @Min(value = -1, message = "리뷰에 사용되는 매너 스코어는 -1보다 작을 수 없음") 25 | @Max(value = 1, message = "리뷰에 사용되는 매너 스코어는 1보다 클 수 없음") 26 | private Integer mannerScore; 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci-push.yml: -------------------------------------------------------------------------------- 1 | name: ci-push 2 | 3 | on: 4 | push: 5 | branches: [ "main","dev" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Github Repository Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | submodules: true 16 | token: ${{ secrets.ACCESS_TOKEN }} 17 | 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: '17' 22 | distribution: 'temurin' 23 | 24 | - name: SoanrCloud 설정 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.sonar/cache 28 | key: ${{ runner.os }}-sonar 29 | restore-keys: ${{ runner.os }}-sonar 30 | 31 | - name: SonarCloud Build 32 | uses: gradle/gradle-build-action@v2 33 | with: 34 | arguments: test sonar 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 38 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/alarm/IntegrationAlarmTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import kr.pickple.back.auth.domain.token.JwtProvider; 5 | import kr.pickple.back.fixture.setup.MemberSetup; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | 12 | @SpringBootTest 13 | @AutoConfigureMockMvc 14 | @AutoConfigureRestDocs 15 | public abstract class IntegrationAlarmTest { 16 | 17 | @Autowired 18 | protected MockMvc mockMvc; 19 | 20 | @Autowired 21 | protected JwtProvider jwtProvider; 22 | 23 | @Autowired 24 | protected MemberSetup memberSetup; 25 | 26 | @Autowired 27 | protected ObjectMapper objectMapper; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/domain/MemberProfile.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.domain; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.crew.domain.Crew; 6 | import kr.pickple.back.position.domain.Position; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | @Builder 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class MemberProfile { 16 | 17 | private Long memberId; 18 | private String email; 19 | private String nickname; 20 | private String introduction; 21 | private String profileImageUrl; 22 | private Integer mannerScore; 23 | private Integer mannerScoreCount; 24 | private String addressDepth1Name; 25 | private String addressDepth2Name; 26 | private List positions; 27 | private List joinedCrews; 28 | 29 | public void updateJoinedCrews(final List joinedCrews) { 30 | this.joinedCrews = joinedCrews; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/response/CrewProfileResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.response; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.crew.domain.CrewStatus; 6 | import kr.pickple.back.member.dto.response.MemberResponse; 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | 12 | @Getter 13 | @Builder 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class CrewProfileResponse { 16 | 17 | private Long id; 18 | private String name; 19 | private String content; 20 | private Integer memberCount; 21 | private Integer maxMemberCount; 22 | private String profileImageUrl; 23 | private String backgroundImageUrl; 24 | private CrewStatus status; 25 | private Integer likeCount; 26 | private Integer competitionPoint; 27 | private MemberResponse leader; 28 | private String addressDepth1; 29 | private String addressDepth2; 30 | private List members; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/repository/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.repository; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Modifying; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import kr.pickple.back.member.repository.entity.MemberEntity; 10 | 11 | public interface MemberRepository extends JpaRepository { 12 | 13 | Boolean existsByEmailOrNicknameOrOauthId(final String email, final String nickname, final Long oauthId); 14 | 15 | Optional findByOauthId(final Long oauthId); 16 | 17 | @Modifying(clearAutomatically = true) 18 | @Query(""" 19 | update MemberEntity m 20 | set m.mannerScore = :mannerScore, m.mannerScoreCount = :mannerScoreCount 21 | where m.id = :memberId""") 22 | void updateMannerScore(final Long memberId, final Integer mannerScore, final Integer mannerScoreCount); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/position/controller/PositionController.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.position.controller; 2 | 3 | import static org.springframework.http.HttpStatus.*; 4 | 5 | import java.util.List; 6 | 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import kr.pickple.back.position.dto.PositionResponse; 13 | import kr.pickple.back.position.service.PositionService; 14 | import lombok.RequiredArgsConstructor; 15 | 16 | @RestController 17 | @RequestMapping("/positions") 18 | @RequiredArgsConstructor 19 | public class PositionController { 20 | 21 | private final PositionService positionService; 22 | 23 | @GetMapping 24 | public ResponseEntity> findAllPositions() { 25 | return ResponseEntity.status(OK) 26 | .body(positionService.findAllPositions()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/ranking/controller/RankingController.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.ranking.controller; 2 | 3 | import static org.springframework.http.HttpStatus.*; 4 | 5 | import java.util.List; 6 | 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import kr.pickple.back.ranking.dto.CrewRankingResponse; 13 | import kr.pickple.back.ranking.service.RankingService; 14 | import lombok.RequiredArgsConstructor; 15 | 16 | @RestController 17 | @RequiredArgsConstructor 18 | @RequestMapping("/ranking") 19 | public class RankingController { 20 | 21 | private final RankingService rankingService; 22 | 23 | @GetMapping("/crews") 24 | public ResponseEntity> findCrewRanking() { 25 | return ResponseEntity.status(OK) 26 | .body(rankingService.findCrewRanking()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/service/AddressService.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.springframework.transaction.annotation.Transactional; 5 | 6 | import kr.pickple.back.address.domain.AllAddress; 7 | import kr.pickple.back.address.dto.response.AllAddressResponse; 8 | import kr.pickple.back.address.implement.AddressReader; 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | @Transactional(readOnly = true) 14 | public class AddressService { 15 | 16 | private final AddressReader addressReader; 17 | 18 | /** 19 | * 지역 목록 조회 20 | */ 21 | public AllAddressResponse findAllAddress() { 22 | final AllAddress allAddress = addressReader.readAllAddress(); 23 | 24 | return AllAddressResponse.builder() 25 | .addressDepth1(allAddress.getAddressDepth1Name()) 26 | .addressDepth2List(allAddress.getAddressDepth2Names()) 27 | .build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/http/crew/crew.http: -------------------------------------------------------------------------------- 1 | ### 크루 생성 2 | POST http://localhost:8080/crews 3 | Authorization: 4 | Content-Type: application/json 5 | 6 | { 7 | "name": "밥솥 크루", 8 | "content": "안녕하세요, 밭솥 크루입니다. 백둥체육관 201호에서 진행합니다.", 9 | "maxMemberCount": 15, 10 | "addressDepth1": "서울시", 11 | "addressDepth2": "강남구" 12 | } 13 | 14 | ### 크루 상세 조회 15 | GET http://localhost:8080/crews/15 16 | 17 | ### 크루가입 신청 18 | POST http://localhost:8080/crews/11/members 19 | Authorization: 20 | Content-Type: application/json 21 | 22 | 23 | ### 크루원 가입 신청자 목록 24 | GET http://localhost:8080/crews/15/members?status=대기 25 | Authorization: Bearer {{token1}} 26 | 27 | ### 크루원 가입 신청 수락 28 | PATCH http://localhost:8080/crews/15/members/2 29 | Content-Type: application/json 30 | Authorization: Bearer {{token1}} 31 | 32 | { 33 | "status": "확정" 34 | } 35 | 36 | ### 크루원 가입 신청 거절/취소 37 | DELETE http://localhost:8080/crews/15/members/1 38 | Authorization: Bearer {{token1}} 39 | Content-Type: application/json 40 | 41 | ### 사용자 근처 크루 목록 조회 42 | GET http://localhost:8080/crews?addressDepth1=서울시&addressDepth2=영등포구&page=0&size=10 43 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/dto/kakao/KakaoAddressResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.dto.kakao; 2 | 3 | import java.util.List; 4 | 5 | import org.locationtech.jts.geom.Coordinate; 6 | import org.locationtech.jts.geom.GeometryFactory; 7 | import org.locationtech.jts.geom.Point; 8 | import org.locationtech.jts.geom.PrecisionModel; 9 | 10 | import lombok.AccessLevel; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Getter 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class KakaoAddressResponse { 17 | 18 | private List documents; 19 | 20 | public Point toPoint() { 21 | final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326); 22 | 23 | return geometryFactory.createPoint(new Coordinate(documents.get(0).x, documents.get(0).y)); 24 | } 25 | 26 | @Getter 27 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 28 | static class Document { 29 | 30 | private String addressName; 31 | private Double x; 32 | private Double y; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/config/ChatConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class ChatConfig implements WebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void registerStompEndpoints(final StompEndpointRegistry registry) { 15 | registry.addEndpoint("/chat") 16 | .setAllowedOriginPatterns("*") 17 | .withSockJS(); 18 | } 19 | 20 | @Override 21 | public void configureMessageBroker(final MessageBrokerRegistry registry) { 22 | registry.enableSimpleBroker("/receive"); 23 | registry.setApplicationDestinationPrefixes("/send"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/map/service/MapService.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.map.service; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import kr.pickple.back.address.domain.MainAddress; 8 | import kr.pickple.back.map.domain.MapPolygon; 9 | import kr.pickple.back.map.repository.MapPolygonRepository; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | public class MapService { 15 | 16 | private final MapPolygonRepository mapPolygonRepository; 17 | 18 | @Transactional(readOnly = true) 19 | @Cacheable(cacheManager = "caffeineCacheManager", cacheNames = "polygon", key = "#mainAddress.addressDepth2Id") 20 | public MapPolygon findMapPolygonByMainAddress(final MainAddress mainAddress) { 21 | 22 | return mapPolygonRepository.findByAddressDepth1IdAndAddressDepth2Id( 23 | mainAddress.getAddressDepth1Id(), 24 | mainAddress.getAddressDepth2Id() 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/resolver/TokenExtractor.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.resolver; 2 | 3 | import static kr.pickple.back.auth.exception.AuthExceptionCode.*; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import kr.pickple.back.auth.exception.AuthException; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Component 11 | @RequiredArgsConstructor 12 | public class TokenExtractor { 13 | 14 | private static final String BEARER_TYPE = "Bearer "; 15 | 16 | public String extractRegisterToken(final String header) { 17 | if (header != null && header.startsWith(BEARER_TYPE)) { 18 | return header.substring(BEARER_TYPE.length()).trim(); 19 | } 20 | 21 | throw new AuthException(AUTH_INVALID_REGISTER_TOKEN, header); 22 | } 23 | 24 | public String extractAccessToken(final String header) { 25 | if (header != null && header.startsWith(BEARER_TYPE)) { 26 | return header.substring(BEARER_TYPE.length()).trim(); 27 | } 28 | 29 | throw new AuthException(AUTH_INVALID_ACCESS_TOKEN, header); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/repository/entity/AddressDepth1Entity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.Table; 9 | import jakarta.validation.constraints.NotNull; 10 | import kr.pickple.back.common.domain.BaseEntity; 11 | import lombok.AccessLevel; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | @Entity 17 | @Getter 18 | @Table(name = "address_depth1") 19 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 20 | public class AddressDepth1Entity extends BaseEntity implements AddressEntity { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | @NotNull 27 | @Column(unique = true, length = 10) 28 | private String name; 29 | 30 | @Builder 31 | private AddressDepth1Entity(final String name) { 32 | this.name = name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/crew/IntegrationCrewTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import kr.pickple.back.auth.domain.token.JwtProvider; 5 | import kr.pickple.back.fixture.setup.CrewSetup; 6 | import kr.pickple.back.fixture.setup.MemberSetup; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | 13 | @SpringBootTest 14 | @AutoConfigureMockMvc 15 | @AutoConfigureRestDocs 16 | public abstract class IntegrationCrewTest { 17 | 18 | @Autowired 19 | protected MockMvc mockMvc; 20 | 21 | @Autowired 22 | protected JwtProvider jwtProvider; 23 | 24 | @Autowired 25 | protected CrewSetup crewSetup; 26 | 27 | @Autowired 28 | protected MemberSetup memberSetup; 29 | 30 | @Autowired 31 | protected ObjectMapper objectMapper; 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/game/IntegrationGameTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.web.servlet.MockMvc; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | import kr.pickple.back.auth.domain.token.JwtProvider; 12 | import kr.pickple.back.fixture.setup.GameSetup; 13 | import kr.pickple.back.fixture.setup.MemberSetup; 14 | 15 | @SpringBootTest 16 | @AutoConfigureMockMvc 17 | @AutoConfigureRestDocs 18 | public abstract class IntegrationGameTest { 19 | 20 | @Autowired 21 | protected MockMvc mockMvc; 22 | 23 | @Autowired 24 | protected JwtProvider jwtProvider; 25 | 26 | @Autowired 27 | protected GameSetup gameSetup; 28 | 29 | @Autowired 30 | protected MemberSetup memberSetup; 31 | 32 | @Autowired 33 | protected ObjectMapper objectMapper; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/dto/mapper/MemberRequestMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.dto.mapper; 2 | 3 | import kr.pickple.back.member.domain.NewMember; 4 | import kr.pickple.back.member.dto.request.MemberCreateRequest; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public final class MemberRequestMapper { 10 | 11 | public static NewMember mapToNewMemberDomain(final MemberCreateRequest memberCreateRequest) { 12 | return NewMember.builder() 13 | .email(memberCreateRequest.getEmail()) 14 | .nickname(memberCreateRequest.getNickname()) 15 | .profileImageUrl(memberCreateRequest.getProfileImageUrl()) 16 | .positions(memberCreateRequest.getPositions()) 17 | .oauthId(memberCreateRequest.getOauthId()) 18 | .oauthProvider(memberCreateRequest.getOauthProvider()) 19 | .addressDepth1Name(memberCreateRequest.getAddressDepth1()) 20 | .addressDepth2Name(memberCreateRequest.getAddressDepth2()) 21 | .build(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/domain/NewMember.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.domain; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 6 | import kr.pickple.back.auth.domain.token.AuthTokens; 7 | import kr.pickple.back.position.domain.Position; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | 13 | @Getter 14 | @Builder 15 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 16 | public class NewMember { 17 | 18 | private Long memberId; 19 | private AuthTokens authTokens; 20 | private String nickname; 21 | private String profileImageUrl; 22 | private String email; 23 | private List positions; 24 | private Long oauthId; 25 | private OauthProvider oauthProvider; 26 | private String addressDepth1Name; 27 | private String addressDepth2Name; 28 | 29 | public void updateMemberId(final Long memberId) { 30 | this.memberId = memberId; 31 | } 32 | 33 | public void updateAuthTokens(final AuthTokens authTokens) { 34 | this.authTokens = authTokens; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/repository/SseEmitterLocalRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.repository; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | import org.springframework.stereotype.Repository; 8 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 9 | 10 | import lombok.NoArgsConstructor; 11 | import lombok.extern.log4j.Log4j2; 12 | 13 | @Log4j2 14 | @Repository 15 | @NoArgsConstructor 16 | public class SseEmitterLocalRepository implements SseEmitterRepository { 17 | 18 | private final Map emitters = new ConcurrentHashMap<>(); 19 | 20 | @Override 21 | public SseEmitter save(final String emitterId, final SseEmitter sseEmitter) { 22 | emitters.put(Long.parseLong(emitterId), sseEmitter); 23 | return sseEmitter; 24 | } 25 | 26 | @Override 27 | public Optional findById(final Long emitterId) { 28 | return Optional.ofNullable(emitters.get(emitterId)); 29 | } 30 | 31 | @Override 32 | public void deleteById(final Long emitterId) { 33 | emitters.remove(emitterId); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr.yml: -------------------------------------------------------------------------------- 1 | name: ci-pr 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main", "dev" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Github Repository Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | submodules: true 16 | token: ${{ secrets.ACCESS_TOKEN }} 17 | 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: '17' 22 | distribution: 'temurin' 23 | 24 | - name: Build with Gradle 25 | uses: gradle/gradle-build-action@v2 26 | with: 27 | arguments: build 28 | 29 | - name: SoanrCloud 설정 30 | uses: actions/cache@v3 31 | with: 32 | path: ~/.sonar/cache 33 | key: ${{ runner.os }}-sonar 34 | restore-keys: ${{ runner.os }}-sonar 35 | 36 | - name: SonarCloud Build and analyze 37 | uses: gradle/gradle-build-action@v2 38 | with: 39 | arguments: sonar 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 43 | if: always() 44 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/domain/CrewMember.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.domain; 2 | 3 | import static kr.pickple.back.crew.exception.CrewExceptionCode.*; 4 | 5 | import kr.pickple.back.common.domain.RegistrationStatus; 6 | import kr.pickple.back.crew.exception.CrewException; 7 | import kr.pickple.back.member.domain.Member; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Getter 15 | @Builder 16 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class CrewMember { 19 | 20 | private Long crewMemberId; 21 | private RegistrationStatus status; 22 | private Member member; 23 | private Crew crew; 24 | 25 | public void updateCrewMemberId(final Long crewMemberId) { 26 | this.crewMemberId = crewMemberId; 27 | } 28 | 29 | public void updateRegistrationStatus(final RegistrationStatus status) { 30 | if (this.status == status) { 31 | throw new CrewException(CREW_MEMBER_ALREADY_IN_THAT_REGISTRATION_STATUS, status); 32 | } 33 | 34 | this.status = status; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/ChatMessageRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository; 2 | 3 | import java.time.LocalDateTime; 4 | import java.util.List; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import kr.pickple.back.chat.repository.entity.ChatMessageEntity; 10 | 11 | public interface ChatMessageRepository extends JpaRepository { 12 | 13 | List findAllByChatRoomIdAndCreatedAtGreaterThanEqual( 14 | final Long chatRoomId, 15 | final LocalDateTime createdAt 16 | ); 17 | 18 | ChatMessageEntity findTopByChatRoomIdOrderByCreatedAtDesc(final Long chatRoomId); 19 | 20 | @Query(""" 21 | select cm.createdAt 22 | from ChatMessageEntity cm 23 | where cm.senderId= :memberId 24 | and cm.chatRoomId = :chatRoomId 25 | and cm.type = kr.pickple.back.chat.domain.MessageType.ENTER 26 | order by cm.createdAt desc 27 | limit 1 28 | """) 29 | LocalDateTime findChatRoomLastEntranceMessageCreatedAt(final Long memberId, final Long chatRoomId); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/domain/Category.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.domain; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.function.Function; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import com.fasterxml.jackson.annotation.JsonCreator; 10 | import com.fasterxml.jackson.annotation.JsonValue; 11 | 12 | import kr.pickple.back.game.exception.GameException; 13 | import kr.pickple.back.game.exception.GameExceptionCode; 14 | import lombok.Getter; 15 | import lombok.RequiredArgsConstructor; 16 | 17 | @Getter 18 | @RequiredArgsConstructor 19 | public enum Category { 20 | ADDRESS("location"); 21 | 22 | private static final Map categoryMap = Collections.unmodifiableMap( 23 | Stream.of(values()).collect(Collectors.toMap(Category::getValue, Function.identity()))); 24 | 25 | @JsonValue 26 | private final String value; 27 | 28 | @JsonCreator 29 | public static Category from(final String value) { 30 | if (categoryMap.containsKey(value)) { 31 | return categoryMap.get(value); 32 | } 33 | 34 | throw new GameException(GameExceptionCode.GAME_SEARCH_CATEGORY_IS_INVALID, value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/member/IntegrationMemberTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.web.servlet.MockMvc; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | import kr.pickple.back.auth.domain.token.JwtProvider; 12 | import kr.pickple.back.fixture.setup.CrewSetup; 13 | import kr.pickple.back.fixture.setup.GameSetup; 14 | import kr.pickple.back.fixture.setup.MemberSetup; 15 | 16 | @SpringBootTest 17 | @AutoConfigureRestDocs 18 | @AutoConfigureMockMvc 19 | public abstract class IntegrationMemberTest { 20 | 21 | @Autowired 22 | protected MockMvc mockMvc; 23 | 24 | @Autowired 25 | protected MemberSetup memberSetup; 26 | 27 | @Autowired 28 | protected CrewSetup crewSetup; 29 | 30 | @Autowired 31 | protected GameSetup gameSetup; 32 | 33 | @Autowired 34 | protected ObjectMapper objectMapper; 35 | 36 | @Autowired 37 | protected JwtProvider jwtProvider; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/ranking/service/RankingService.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.ranking.service; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.cache.annotation.CachePut; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import kr.pickple.back.ranking.dto.CrewRankingResponse; 11 | import kr.pickple.back.ranking.repository.RankingJdbcRepository; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | @Transactional(readOnly = true) 17 | public class RankingService { 18 | 19 | private final RankingJdbcRepository rankingJdbcRepository; 20 | 21 | @Cacheable(cacheManager = "redisCacheManager", cacheNames = "ranking", key = "'crew'") 22 | public List findCrewRanking() { 23 | return putCrewRankingCache(); 24 | } 25 | 26 | @CachePut(cacheManager = "redisCacheManager", cacheNames = "ranking", key = "'crew'") 27 | public List putCrewRankingCache() { 28 | final List crewRankings = rankingJdbcRepository.getCrewRankings(); 29 | 30 | return crewRankings; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/chat/IntegrationChatTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.mock.mockito.MockBean; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | 12 | import kr.pickple.back.auth.domain.token.JwtProvider; 13 | import kr.pickple.back.auth.service.OauthService; 14 | import kr.pickple.back.chat.service.ChatRoomService; 15 | import kr.pickple.back.fixture.setup.MemberSetup; 16 | 17 | @SpringBootTest 18 | @AutoConfigureMockMvc 19 | @AutoConfigureRestDocs 20 | public abstract class IntegrationChatTest { 21 | 22 | @Autowired 23 | protected JwtProvider jwtProvider; 24 | 25 | @Autowired 26 | protected MockMvc mockMvc; 27 | 28 | @Autowired 29 | protected ObjectMapper objectMapper; 30 | 31 | @Autowired 32 | protected MemberSetup memberSetup; 33 | 34 | @Autowired 35 | protected ChatRoomService chatRoomService; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/authcode/KakaoAuthCodeRequestUrlProvider.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.authcode; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.util.UriComponentsBuilder; 5 | 6 | import kr.pickple.back.auth.config.property.KakaoOauthProperties; 7 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | @Component 11 | @RequiredArgsConstructor 12 | public class KakaoAuthCodeRequestUrlProvider implements AuthCodeRequestUrlProvider { 13 | 14 | private final KakaoOauthProperties kakaoOauthProperties; 15 | 16 | @Override 17 | public OauthProvider oauthprovider() { 18 | return OauthProvider.KAKAO; 19 | } 20 | 21 | @Override 22 | public String provideUrl() { 23 | return UriComponentsBuilder 24 | .fromUriString(kakaoOauthProperties.getProviderUrl()) 25 | .queryParam("response_type", "code") 26 | .queryParam("client_id", kakaoOauthProperties.getClientId()) 27 | .queryParam("redirect_uri", kakaoOauthProperties.getRedirectUrl()) 28 | .queryParam("scope", String.join(",", kakaoOauthProperties.getScope())) 29 | .toUriString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/service/RedisExpirationListener.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.service; 2 | 3 | import java.util.StringTokenizer; 4 | 5 | import org.springframework.data.redis.connection.Message; 6 | import org.springframework.data.redis.connection.MessageListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | import kr.pickple.back.game.domain.GameStatus; 10 | import kr.pickple.back.game.implement.GameWriter; 11 | import lombok.RequiredArgsConstructor; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | @Slf4j 15 | @Component 16 | @RequiredArgsConstructor 17 | public class RedisExpirationListener implements MessageListener { 18 | 19 | private final GameWriter gameWriter; 20 | 21 | @Override 22 | public void onMessage(final Message message, final byte[] pattern) { 23 | final StringTokenizer stringTokenizer = new StringTokenizer(message.toString(), ":"); 24 | final String keyName = stringTokenizer.nextToken(); 25 | 26 | if (keyName.equals("game")) { 27 | final GameStatus gameStatus = GameStatus.valueOf(stringTokenizer.nextToken()); 28 | final Long gameId = Long.parseLong(stringTokenizer.nextToken()); 29 | 30 | gameWriter.updateMemberRegistrationStatus(gameStatus, gameId); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/repository/entity/AddressDepth2Entity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.Table; 9 | import jakarta.validation.constraints.NotNull; 10 | import kr.pickple.back.common.domain.BaseEntity; 11 | import lombok.AccessLevel; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | @Entity 17 | @Getter 18 | @Table(name = "address_depth2") 19 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 20 | public class AddressDepth2Entity extends BaseEntity implements AddressEntity { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | @NotNull 27 | @Column(unique = true, length = 10) 28 | private String name; 29 | 30 | @NotNull 31 | @Column(name = "address_depth1_id") 32 | private Long addressDepth1Id; 33 | 34 | @Builder 35 | private AddressDepth2Entity(final String name, final Long addressDepth1Id) { 36 | this.name = name; 37 | this.addressDepth1Id = addressDepth1Id; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/dto/kakao/KakaoMemberResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.dto.kakao; 2 | 3 | import kr.pickple.back.auth.domain.oauth.OauthMember; 4 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Getter 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class KakaoMemberResponse { 12 | 13 | private Long id; 14 | private KakaoAccount kakaoAccount; 15 | 16 | public OauthMember toOauthMember() { 17 | return OauthMember.builder() 18 | .oauthId(id) 19 | .email(kakaoAccount.email) 20 | .nickname(kakaoAccount.profile.nickname) 21 | .profileImageUrl(kakaoAccount.profile.profileImageUrl) 22 | .oauthProvider(OauthProvider.KAKAO) 23 | .build(); 24 | } 25 | 26 | @Getter 27 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 28 | static class KakaoAccount { 29 | 30 | private String email; 31 | private Profile profile; 32 | } 33 | 34 | @Getter 35 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 36 | static class Profile { 37 | 38 | private String nickname; 39 | private String profileImageUrl; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/domain/AuthFixtures.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.domain; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import kr.pickple.back.auth.domain.oauth.OauthMember; 6 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 7 | import kr.pickple.back.auth.domain.token.AuthTokens; 8 | import kr.pickple.back.auth.domain.token.RefreshToken; 9 | 10 | public class AuthFixtures { 11 | 12 | public static OauthMember oauthMemberBuild() { 13 | return OauthMember.builder() 14 | .oauthId(1L) 15 | .oauthProvider(OauthProvider.KAKAO) 16 | .email("pickple@pickple.kr") 17 | .profileImageUrl("https://amazon.com/pickple/1") 18 | .nickname("pickple") 19 | .build(); 20 | } 21 | 22 | public static AuthTokens authTokensBuild() { 23 | return AuthTokens.builder() 24 | .accessToken("accessToken") 25 | .refreshToken("refreshToken") 26 | .build(); 27 | } 28 | 29 | public static RefreshToken refreshTokenBuild() { 30 | return RefreshToken.builder() 31 | .token("refreshToken") 32 | .memberId(1L) 33 | .createdAt(LocalDateTime.now()) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/domain/CrewStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.domain; 2 | 3 | import static kr.pickple.back.crew.exception.CrewExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.crew.exception.CrewException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum CrewStatus { 21 | 22 | OPEN("모집 중"), 23 | CLOSED("모집 마감"), 24 | ; 25 | 26 | private static final Map crewStatusMap = Collections.unmodifiableMap(Stream.of(values()) 27 | .collect(Collectors.toMap(CrewStatus::getDescription, Function.identity()))); 28 | 29 | @JsonValue 30 | private final String description; 31 | 32 | @JsonCreator 33 | public static CrewStatus from(final String description) { 34 | if (crewStatusMap.containsKey(description)) { 35 | return crewStatusMap.get(description); 36 | } 37 | 38 | throw new CrewException(CREW_STATUS_NOT_FOUND, description); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/exception/MemberExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import kr.pickple.back.common.exception.ExceptionCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public enum MemberExceptionCode implements ExceptionCode { 12 | 13 | MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEM-001", "사용자를 찾을 수 없음"), 14 | MEMBER_IS_EXISTED(HttpStatus.BAD_REQUEST, "MEM-002", "이미 존재하는 사용자 정보"), 15 | MEMBER_STATUS_NOT_FOUND(HttpStatus.NOT_FOUND, "MEM-003", "사용자 상태는 활동이거나 탈퇴만 가능"), 16 | MEMBER_SIGNUP_OAUTH_SUBJECT_INVALID(HttpStatus.BAD_REQUEST, "MEM-004", 17 | "회원 가입 시, 사용자의 OAuth ID와 Provider의 정보가 유효하지 않음"), 18 | MEMBER_UPDATING_MANNER_SCORE_POINT_OUT_OF_RANGE(HttpStatus.BAD_REQUEST, "MEM-005", "매너 스코어 변경 포인트가 범위를 벗어남"), 19 | MEMBER_POSITIONS_IS_DUPLICATED(HttpStatus.BAD_REQUEST, "MEM-006", "사용자의 포지션 목록에 중복이 존재함"), 20 | MEMBER_MISMATCH(HttpStatus.FORBIDDEN, "MEM-007", "로그인 된 사용자와 찾는 리소스 사용자가 달라 접근이 불가함"), 21 | MEMBER_REGISTRATION_STATUS_NOT_FOUND(HttpStatus.NOT_FOUND, "MEM-008", "사용자 신청 상태는 없음, 대기, 확정만 가능"); 22 | 23 | private final HttpStatus status; 24 | private final String code; 25 | private final String message; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/domain/RoomType.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.domain; 2 | 3 | import static kr.pickple.back.chat.exception.ChatExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.chat.exception.ChatException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @RequiredArgsConstructor 19 | public enum RoomType { 20 | 21 | PERSONAL("개인"), 22 | GAME("게스트"), 23 | CREW("크루"), 24 | ; 25 | 26 | private static final Map roomTypeMap = Collections.unmodifiableMap( 27 | Stream.of(values()).collect(Collectors.toMap(RoomType::getDescription, Function.identity()))); 28 | 29 | @Getter 30 | @JsonValue 31 | private final String description; 32 | 33 | @JsonCreator 34 | public static RoomType from(final String description) { 35 | if (roomTypeMap.containsKey(description)) { 36 | return roomTypeMap.get(description); 37 | } 38 | 39 | throw new ChatException(CHAT_ROOM_TYPE_NOT_FOUND, description); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/exception/AuthExceptionCode.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import kr.pickple.back.common.exception.ExceptionCode; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @RequiredArgsConstructor 11 | public enum AuthExceptionCode implements ExceptionCode { 12 | 13 | AUTH_NOT_FOUND_OAUTH_PROVIDER(HttpStatus.NOT_FOUND, "AUT-001", "지원하지 않는 소셜 로그인 타입"), 14 | AUTH_EXPIRED_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, "AUT-002", "AccessToken 토큰 만료"), 15 | AUTH_EXPIRED_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "AUT-003", "RefreshToken 토큰 만료"), 16 | AUTH_EXPIRED_REGISTER_TOKEN(HttpStatus.BAD_REQUEST, "AUT-004", "RegisterToken 토큰 만료"), 17 | AUTH_INVALID_ACCESS_TOKEN(HttpStatus.BAD_REQUEST, "AUT-005", "유효하지 않은 AccessToken"), 18 | AUTH_INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "AUT-006", "유효하지 않은 RefreshToken"), 19 | AUTH_INVALID_REGISTER_TOKEN(HttpStatus.BAD_REQUEST, "AUT-007", "유효하지 않은 RegisterToken"), 20 | AUTH_NOT_FOUND_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "AUT-008", "RefreshToken이 존재하지 않음"), 21 | AUTH_FAIL_TO_VALIDATE_TOKEN(HttpStatus.BAD_REQUEST, "AUT-009", "토큰을 검증할 수 없음"); 22 | 23 | private final HttpStatus status; 24 | private final String code; 25 | private final String message; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/domain/GameStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.domain; 2 | 3 | import static kr.pickple.back.game.exception.GameExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.game.exception.GameException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum GameStatus { 21 | 22 | OPEN("모집 중"), 23 | CLOSED("모집 마감"), 24 | ENDED("경기 종료"), 25 | ; 26 | 27 | private static final Map gameStatusMap = Collections.unmodifiableMap(Stream.of(values()) 28 | .collect(Collectors.toMap(GameStatus::getDescription, Function.identity()))); 29 | 30 | @JsonValue 31 | private final String description; 32 | 33 | @JsonCreator 34 | public static GameStatus from(final String description) { 35 | if (gameStatusMap.containsKey(description)) { 36 | return gameStatusMap.get(description); 37 | } 38 | 39 | throw new GameException(GAME_STATUS_NOT_FOUND, description); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/response/GameResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.response; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalTime; 5 | import java.util.List; 6 | 7 | import kr.pickple.back.game.domain.GameStatus; 8 | import kr.pickple.back.member.dto.response.MemberResponse; 9 | import kr.pickple.back.position.domain.Position; 10 | import lombok.AccessLevel; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | 15 | @Getter 16 | @Builder 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class GameResponse { 19 | 20 | private Long id; 21 | private String content; 22 | private LocalDate playDate; 23 | private LocalTime playStartTime; 24 | private LocalTime playEndTime; 25 | private Integer playTimeMinutes; 26 | private String mainAddress; 27 | private String detailAddress; 28 | private Double latitude; 29 | private Double longitude; 30 | private GameStatus status; 31 | private Integer viewCount; 32 | private Integer cost; 33 | private Integer memberCount; 34 | private Integer maxMemberCount; 35 | private MemberResponse host; 36 | private String addressDepth1; 37 | private String addressDepth2; 38 | private List positions; 39 | private List members; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/domain/MemberStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.domain; 2 | 3 | import static kr.pickple.back.member.exception.MemberExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.member.exception.MemberException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum MemberStatus { 21 | 22 | ACTIVE("활동"), 23 | WITHDRAWN("탈퇴"), 24 | ; 25 | 26 | private static final Map memberStatusMap = Collections.unmodifiableMap(Stream.of(values()) 27 | .collect(Collectors.toMap(MemberStatus::getDescription, Function.identity()))); 28 | 29 | @JsonValue 30 | private final String description; 31 | 32 | @JsonCreator 33 | public static MemberStatus from(final String description) { 34 | if (memberStatusMap.containsKey(description)) { 35 | return memberStatusMap.get(description); 36 | } 37 | 38 | throw new MemberException(MEMBER_STATUS_NOT_FOUND, description); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/dto/request/CrewCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.dto.request; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Getter 15 | @Builder 16 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | public class CrewCreateRequest { 19 | 20 | @NotBlank(message = "크루 이름은 필수입니다.") 21 | @Size(min = 1, max = 20, message = "크루 이름은 20자 이내로 작성해주세요.") 22 | private String name; 23 | 24 | @Size(max = 1000, message = "크루 소개글은 1,000자를 넘길 수 없습니다.") 25 | private String content; 26 | 27 | @NotNull(message = "크루의 최대 인원은 null값이 들어올 수 없습니다.") 28 | @Min(value = 1, message = "크루의 인원은 최소 1명 이상이어야 합니다.") 29 | @Max(value = 30, message = "크루의 인원은 최대 30명까지만 가능합니다.") 30 | private Integer maxMemberCount; 31 | 32 | @NotBlank(message = "해당 크루의 활동 장소(도,시) 정보는 필수입니다.") 33 | private String addressDepth1; 34 | 35 | @NotBlank(message = "해당 크루의 활동 장소(구) 정보는 필수입니다.") 36 | private String addressDepth2; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/repository/CrewMemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | import kr.pickple.back.common.domain.RegistrationStatus; 11 | import kr.pickple.back.crew.repository.entity.CrewMemberEntity; 12 | 13 | public interface CrewMemberRepository extends JpaRepository { 14 | 15 | Optional findByMemberIdAndCrewId(final Long memberId, final Long crewId); 16 | 17 | List findAllByCrewIdAndStatus(final Long crewId, final RegistrationStatus status); 18 | 19 | List findAllByMemberIdAndStatus(final Long memberId, final RegistrationStatus status); 20 | 21 | Boolean existsByCrewIdAndMemberId(final Long crewId, final Long memberId); 22 | 23 | Boolean existsByCrewIdAndMemberIdAndStatus(final Long crewId, final Long memberId, final RegistrationStatus status); 24 | 25 | @Modifying(clearAutomatically = true) 26 | @Query("update CrewMemberEntity cm set cm.status = :status where cm.id = :crewMemberId") 27 | void updateRegistrationStatus(final Long crewMemberId, final RegistrationStatus status); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/domain/GameMember.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.domain; 2 | 3 | import static java.lang.Boolean.*; 4 | import static kr.pickple.back.common.domain.RegistrationStatus.*; 5 | 6 | import kr.pickple.back.common.domain.RegistrationStatus; 7 | import kr.pickple.back.member.domain.Member; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Getter 15 | @Builder 16 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class GameMember { 19 | 20 | private Long gameMemberId; 21 | private RegistrationStatus status; 22 | private Member member; 23 | private Game game; 24 | private Boolean isReview = FALSE; 25 | 26 | public void updateGameMemberId(final Long gameMemberId) { 27 | this.gameMemberId = gameMemberId; 28 | } 29 | 30 | public void updateRegistrationStatus(final RegistrationStatus status) { 31 | this.status = status; 32 | } 33 | 34 | public Boolean isReviewDone() { 35 | return this.isReview; 36 | } 37 | 38 | public Boolean isStatusChangedFromWaitingToConfirmed(final RegistrationStatus newRegistrationStatus) { 39 | return this.status == WAITING && newRegistrationStatus == CONFIRMED; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/service/kakao/KakaoAddressSearchClient.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.service.kakao; 2 | 3 | import org.locationtech.jts.geom.Point; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.util.LinkedMultiValueMap; 6 | import org.springframework.util.MultiValueMap; 7 | 8 | import kr.pickple.back.address.dto.kakao.KakaoAddressResponse; 9 | import kr.pickple.back.auth.config.property.KakaoOauthProperties; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | @Component 13 | @RequiredArgsConstructor 14 | public class KakaoAddressSearchClient { 15 | 16 | private final KakaoAddressSearchApiClient kakaoAddressSearchApiClient; 17 | private final KakaoOauthProperties kakaoOauthProperties; 18 | 19 | public Point fetchAddress(final String address) { 20 | final KakaoAddressResponse kakaoAddressResponse = kakaoAddressSearchApiClient.fetchAddress( 21 | "KakaoAK " + kakaoOauthProperties.getClientId(), 22 | addressRequestParams(address) 23 | ); 24 | 25 | return kakaoAddressResponse.toPoint(); 26 | } 27 | 28 | private MultiValueMap addressRequestParams(final String address) { 29 | final MultiValueMap params = new LinkedMultiValueMap<>(); 30 | params.add("query", address); 31 | 32 | return params; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/implement/GameMemberMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.implement; 2 | 3 | import kr.pickple.back.game.domain.Game; 4 | import kr.pickple.back.game.domain.GameMember; 5 | import kr.pickple.back.game.repository.entity.GameMemberEntity; 6 | import kr.pickple.back.member.domain.Member; 7 | import lombok.AccessLevel; 8 | import lombok.NoArgsConstructor; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public final class GameMemberMapper { 12 | 13 | public static GameMemberEntity mapGameMemberDomainToEntity(final GameMember gameMember) { 14 | return GameMemberEntity.builder() 15 | .status(gameMember.getStatus()) 16 | .memberId(gameMember.getMember().getMemberId()) 17 | .gameId(gameMember.getGame().getGameId()) 18 | .build(); 19 | } 20 | 21 | public static GameMember mapGameMemberEntityToDomain( 22 | final GameMemberEntity gameMemberEntity, 23 | final Member member, 24 | final Game game 25 | ) { 26 | return GameMember.builder() 27 | .gameMemberId(gameMemberEntity.getId()) 28 | .status(gameMemberEntity.getStatus()) 29 | .member(member) 30 | .game(game) 31 | .isReview(gameMemberEntity.isReviewDone()) 32 | .build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/common/domain/RegistrationStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.common.domain; 2 | 3 | import static kr.pickple.back.common.exception.CommonExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.common.exception.CommonException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum RegistrationStatus { 21 | 22 | WAITING("대기"), 23 | CONFIRMED("확정"), 24 | ; 25 | 26 | private static final Map registrationStatusMap = Collections.unmodifiableMap( 27 | Stream.of(values()).collect(Collectors.toMap(RegistrationStatus::getDescription, Function.identity()))); 28 | 29 | @JsonValue 30 | private final String description; 31 | 32 | @JsonCreator 33 | public static RegistrationStatus from(final String description) { 34 | if (registrationStatusMap.containsKey(description)) { 35 | return registrationStatusMap.get(description); 36 | } 37 | 38 | throw new CommonException(COMMON_BAD_REQUEST, description); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/setup/AddressSetup.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.setup; 2 | 3 | import static kr.pickple.back.address.exception.AddressExceptionCode.*; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import kr.pickple.back.address.exception.AddressException; 9 | import kr.pickple.back.address.repository.AddressDepth1Repository; 10 | import kr.pickple.back.address.repository.AddressDepth2Repository; 11 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 12 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 13 | 14 | @Component 15 | public class AddressSetup { 16 | 17 | @Autowired 18 | private AddressDepth1Repository addressDepth1Repository; 19 | 20 | @Autowired 21 | private AddressDepth2Repository addressDepth2Repository; 22 | 23 | public AddressDepth1Entity findAddressDepth1(String name) { 24 | return addressDepth1Repository.findByName(name) 25 | .orElseThrow(() -> new AddressException(ADDRESS_NOT_FOUND, name)); 26 | } 27 | 28 | public AddressDepth2Entity findAddressDepth2(String name) { 29 | return addressDepth2Repository.findByNameAndAddressDepth1Id(name, findAddressDepth1("서울시").getId()) 30 | .orElseThrow(() -> new AddressException(ADDRESS_NOT_FOUND, name)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/repository/RedisRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.repository; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.springframework.data.redis.core.HashOperations; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public class RedisRepository { 11 | 12 | private final RedisTemplate redisTemplate; 13 | private final HashOperations hashOperations; 14 | 15 | public RedisRepository(final RedisTemplate redisTemplate) { 16 | this.redisTemplate = redisTemplate; 17 | this.hashOperations = redisTemplate.opsForHash(); 18 | } 19 | 20 | public void saveHash(final String key, final String field, final T value, final Long duration) { 21 | hashOperations.put(key, field, value); 22 | redisTemplate.expire(key, duration, TimeUnit.SECONDS); 23 | } 24 | 25 | public T findHash(final String key, final String field) { 26 | return (T)hashOperations.get(key, field); 27 | } 28 | 29 | public Boolean existsHash(final String key, final String field) { 30 | return hashOperations.hasKey(key, field); 31 | } 32 | 33 | public void deleteHash(final String key, final String field) { 34 | hashOperations.delete(key, field); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/response/MemberGameResponse.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.response; 2 | 3 | import java.time.LocalDate; 4 | import java.time.LocalTime; 5 | import java.util.List; 6 | 7 | import kr.pickple.back.game.domain.GameStatus; 8 | import kr.pickple.back.member.dto.response.MemberResponse; 9 | import kr.pickple.back.position.domain.Position; 10 | import lombok.AccessLevel; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | 15 | @Getter 16 | @Builder 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class MemberGameResponse { 19 | 20 | private Long id; 21 | private String content; 22 | private LocalDate playDate; 23 | private LocalTime playStartTime; 24 | private LocalTime playEndTime; 25 | private Integer playTimeMinutes; 26 | private String mainAddress; 27 | private String detailAddress; 28 | private Double latitude; 29 | private Double longitude; 30 | private GameStatus status; 31 | private Boolean isReviewDone; 32 | private Integer viewCount; 33 | private Integer cost; 34 | private Integer memberCount; 35 | private Integer maxMemberCount; 36 | private MemberResponse host; 37 | private String addressDepth1; 38 | private String addressDepth2; 39 | private List positions; 40 | private List members; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/entity/GamePositionEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Convert; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import jakarta.validation.constraints.NotNull; 11 | import kr.pickple.back.common.domain.BaseEntity; 12 | import kr.pickple.back.position.domain.Position; 13 | import kr.pickple.back.position.util.PositionConverter; 14 | import lombok.AccessLevel; 15 | import lombok.Builder; 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | 19 | @Entity 20 | @Table(name = "game_position") 21 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 22 | public class GamePositionEntity extends BaseEntity { 23 | 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private Long id; 27 | 28 | @Getter 29 | @NotNull 30 | @Convert(converter = PositionConverter.class) 31 | @Column(length = 2) 32 | private Position position; 33 | 34 | @NotNull 35 | private Long gameId; 36 | 37 | @Builder 38 | private GamePositionEntity(final Position position, final Long gameId) { 39 | this.position = position; 40 | this.gameId = gameId; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/http/member/member.http: -------------------------------------------------------------------------------- 1 | ### 회원 생성 2 | POST http://localhost:8080/members 3 | Content-Type: application/json 4 | Authorization: 5 | 6 | { 7 | "email": "test2@test.com", 8 | "nickname": "테스트2", 9 | "profileImageUrl": "http://k.kakaocdn.net/dn/dpk9l1/btqmGhA2lKL/Oz0wDuJn1YV2DIn92f6DVK/img_640x640.jpg", 10 | "positions": [ 11 | "C","PG" 12 | ], 13 | "addressDepth1": "서울시", 14 | "addressDepth2": "구로구", 15 | "oauthId": 32014120, 16 | "oauthProvider": "KAKAO" 17 | } 18 | 19 | ### 사용자 프로필 조회 20 | GET http://localhost:8080/members/1 21 | 22 | ### 사용자가 가입한 크루 목록 23 | GET http://localhost:8080/members/3/crews?status=확정 24 | Authorization: 25 | 26 | ### 사용자가 만든 크루 목록 조회 27 | GET http://localhost:8080/members/1/created-crews 28 | Authorization: 29 | 30 | ### 사용자의 참여 확정 게스트 모집글 목록 조회 31 | GET http://localhost:8080/members/2/games?status=대기 32 | Authorization: 33 | 34 | ### 사용자가 만든 게스트 모집글 목록 조회 35 | GET http://localhost:8080/members/2/created-games 36 | Authorization: 37 | 38 | 39 | ### 사용자의 게스트 모집 참여 여부 조회 40 | GET http://localhost:8080/members/2/games/4/registration-status 41 | Authorization: 42 | 43 | ### 사용자의 크루 가입 여부 조회 44 | GET http://localhost:8080/members/1/crews/2/registration-status 45 | Authorization: 46 | 47 | ### AccessToken 재발급 48 | POST http://localhost:8080/auth/refresh 49 | Authorization: 50 | 51 | ### AccessToken 재발급 52 | DELETE http://localhost:8080/auth/logout 53 | Authorization: 54 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/authcode/AuthCodeRequestUrlProviderComposite.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.authcode; 2 | 3 | import static java.util.function.Function.*; 4 | import static java.util.stream.Collectors.*; 5 | import static kr.pickple.back.auth.exception.AuthExceptionCode.*; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | 11 | import org.springframework.stereotype.Component; 12 | 13 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 14 | import kr.pickple.back.auth.exception.AuthException; 15 | 16 | @Component 17 | public class AuthCodeRequestUrlProviderComposite { 18 | 19 | private final Map mapping; 20 | 21 | public AuthCodeRequestUrlProviderComposite(final Set providers) { 22 | mapping = providers.stream() 23 | .collect(toMap(AuthCodeRequestUrlProvider::oauthprovider, identity())); 24 | } 25 | 26 | public String provide(final OauthProvider oauthProvider) { 27 | return getProvider(oauthProvider).provideUrl(); 28 | } 29 | 30 | private AuthCodeRequestUrlProvider getProvider(final OauthProvider oauthProvider) { 31 | return Optional.ofNullable(mapping.get(oauthProvider)) 32 | .orElseThrow(() -> new AuthException(AUTH_NOT_FOUND_OAUTH_PROVIDER, oauthProvider.toString())); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/auth/domain/JwtProviderTest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.domain; 2 | 3 | import static org.assertj.core.api.Assertions.*; 4 | 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import kr.pickple.back.auth.IntegrationAuthTest; 9 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 10 | import kr.pickple.back.auth.domain.token.AuthTokens; 11 | 12 | class JwtProviderTest extends IntegrationAuthTest { 13 | 14 | private static final Long MEMBER_ID = 1L; 15 | 16 | @Test 17 | @DisplayName("memberId로 accessToken과 refreshToken을 만들 수 있다.") 18 | void create_AuthTokens() { 19 | final AuthTokens authTokens = jwtProvider.createLoginToken(String.valueOf(MEMBER_ID)); 20 | 21 | assertThat(authTokens.getAccessToken()).isNotNull(); 22 | assertThat(authTokens.getRefreshToken()).isNotNull(); 23 | } 24 | 25 | @Test 26 | @DisplayName("oauthId와 oauthProvider로 registerToken을 만들 수 있다.") 27 | void create_RegisterToken() { 28 | final Long oauthId = 1L; 29 | final OauthProvider oauthProvider = OauthProvider.KAKAO; 30 | final AuthTokens registerToken = jwtProvider.createRegisterToken( 31 | oauthProvider.name() + oauthId); 32 | 33 | assertThat(registerToken.getAccessToken()).isNotNull(); 34 | assertThat(registerToken.getRefreshToken()).isNull(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/repository/entity/MemberPositionEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Convert; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import jakarta.validation.constraints.NotNull; 11 | import kr.pickple.back.common.domain.BaseEntity; 12 | import kr.pickple.back.position.domain.Position; 13 | import kr.pickple.back.position.util.PositionConverter; 14 | import lombok.AccessLevel; 15 | import lombok.Builder; 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | 19 | @Entity 20 | @Getter 21 | @Table(name = "member_position") 22 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 23 | public class MemberPositionEntity extends BaseEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @NotNull 30 | @Convert(converter = PositionConverter.class) 31 | @Column(length = 2) 32 | private Position position; 33 | 34 | @NotNull 35 | private Long memberId; 36 | 37 | @Builder 38 | private MemberPositionEntity(final Position position, final Long memberId) { 39 | this.position = position; 40 | this.memberId = memberId; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/service/memberclient/OauthMemberClientComposite.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.service.memberclient; 2 | 3 | import static java.util.function.Function.*; 4 | import static java.util.stream.Collectors.*; 5 | import static kr.pickple.back.auth.exception.AuthExceptionCode.*; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | 11 | import org.springframework.stereotype.Component; 12 | 13 | import kr.pickple.back.auth.domain.oauth.OauthMember; 14 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 15 | import kr.pickple.back.auth.exception.AuthException; 16 | 17 | @Component 18 | public class OauthMemberClientComposite { 19 | 20 | private final Map mapping; 21 | 22 | public OauthMemberClientComposite(final Set clients) { 23 | mapping = clients.stream() 24 | .collect(toMap(OauthMemberClient::oauthProvider, identity())); 25 | } 26 | 27 | public OauthMember fetch(final OauthProvider oauthProvider, final String authCode) { 28 | return getClient(oauthProvider).fetch(authCode); 29 | } 30 | 31 | private OauthMemberClient getClient(final OauthProvider oauthProvider) { 32 | return Optional.ofNullable(mapping.get(oauthProvider)) 33 | .orElseThrow(() -> new AuthException(AUTH_NOT_FOUND_OAUTH_PROVIDER, oauthProvider.toString())); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/domain/ChatRoom.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.domain; 2 | 3 | import static kr.pickple.back.chat.exception.ChatExceptionCode.*; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | import kr.pickple.back.chat.exception.ChatException; 8 | import lombok.AccessLevel; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Getter 15 | @Builder 16 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | public class ChatRoom { 19 | 20 | private Long chatRoomId; 21 | private String name; 22 | private RoomType type; 23 | private Integer memberCount; 24 | private Integer maxMemberCount; 25 | private LocalDateTime createdAt; 26 | 27 | public void increaseMemberCount() { 28 | if (memberCount.equals(maxMemberCount)) { 29 | throw new ChatException(CHAT_ROOM_IS_FULL, memberCount); 30 | } 31 | 32 | memberCount += 1; 33 | } 34 | 35 | public void decreaseMemberCount() { 36 | if (isEmpty()) { 37 | throw new ChatException(CHAT_ROOM_IS_EMPTY, memberCount); 38 | } 39 | 40 | memberCount -= 1; 41 | } 42 | 43 | public Boolean isEmpty() { 44 | return memberCount == 0; 45 | } 46 | 47 | public Boolean isMatchedRoomType(final RoomType type) { 48 | return this.type == type; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/implement/AddressMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.implement; 2 | 3 | import java.util.List; 4 | 5 | import kr.pickple.back.address.domain.Address; 6 | import kr.pickple.back.address.domain.AllAddress; 7 | import kr.pickple.back.address.domain.MainAddress; 8 | import kr.pickple.back.address.repository.entity.AddressEntity; 9 | import lombok.AccessLevel; 10 | import lombok.NoArgsConstructor; 11 | 12 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 13 | public final class AddressMapper { 14 | 15 | public static AllAddress mapToAllAddressDomain( 16 | final String addressDepth1Name, 17 | final List addressDepth2Names 18 | ) { 19 | return AllAddress.builder() 20 | .addressDepth1Name(addressDepth1Name) 21 | .addressDepth2Names(addressDepth2Names) 22 | .build(); 23 | } 24 | 25 | public static MainAddress mapToMainAddressDomain(final Address addressDepth1, final Address addressDepth2) { 26 | return MainAddress.builder() 27 | .addressDepth1(addressDepth1) 28 | .addressDepth2(addressDepth2) 29 | .build(); 30 | } 31 | 32 | public static Address mapAddressEntityToDomain(final AddressEntity addressEntity) { 33 | return Address.builder() 34 | .id(addressEntity.getId()) 35 | .name(addressEntity.getName()) 36 | .build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/map/domain/MapPolygon.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.map.domain; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import org.locationtech.jts.geom.Polygon; 6 | 7 | import jakarta.persistence.Entity; 8 | import jakarta.persistence.FetchType; 9 | import jakarta.persistence.GeneratedValue; 10 | import jakarta.persistence.GenerationType; 11 | import jakarta.persistence.Id; 12 | import jakarta.persistence.JoinColumn; 13 | import jakarta.persistence.ManyToOne; 14 | import jakarta.validation.constraints.NotNull; 15 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 16 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 17 | import lombok.AccessLevel; 18 | import lombok.Getter; 19 | import lombok.NoArgsConstructor; 20 | 21 | @Entity 22 | @Getter 23 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 24 | public class MapPolygon { 25 | 26 | @Id 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) 28 | private Long id; 29 | 30 | @NotNull 31 | @ManyToOne(fetch = FetchType.LAZY) 32 | @JoinColumn(name = "address_depth1_id") 33 | private AddressDepth1Entity addressDepth1; 34 | 35 | @NotNull 36 | @ManyToOne(fetch = FetchType.LAZY) 37 | @JoinColumn(name = "address_depth2_id") 38 | private AddressDepth2Entity addressDepth2; 39 | 40 | @NotNull 41 | private BigDecimal latitude; 42 | 43 | @NotNull 44 | private BigDecimal longitude; 45 | 46 | @NotNull 47 | private Polygon polygon; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/domain/CrewAlarmType.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.domain; 2 | 3 | import static kr.pickple.back.alarm.exception.AlarmExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.alarm.exception.AlarmException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum CrewAlarmType { 21 | 22 | CREW_LEADER_WAITING("크루 가입 수락을 기다리고 있어요"), 23 | CREW_ACCEPT("크루 가입이 수락되었어요"), 24 | CREW_DENIED("크루 가입이 거절되었어요"), 25 | ; 26 | 27 | private static final Map crewAlarmTypeMap = Collections.unmodifiableMap(Stream.of(values()) 28 | .collect(Collectors.toMap(CrewAlarmType::getDescription, Function.identity()))); 29 | 30 | private final String description; 31 | 32 | @JsonCreator 33 | public static CrewAlarmType from(final String description) { 34 | if (crewAlarmTypeMap.containsKey(description)) { 35 | return crewAlarmTypeMap.get(description); 36 | } 37 | throw new AlarmException(ALARM_TYPE_NOT_FOUND, description); 38 | } 39 | 40 | @JsonValue 41 | public String getDescription() { 42 | return description; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/domain/GameAlarmType.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.domain; 2 | 3 | import static kr.pickple.back.alarm.exception.AlarmExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.alarm.exception.AlarmException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum GameAlarmType { 21 | 22 | HOST_WAITING("게스트 모집 참여 수락을 기다리고 있어요"), 23 | GUEST_ACCEPT("게스트 참여가 수락되었어요"), 24 | GUEST_DENIED("게스트 참여가 거절되었어요"), 25 | ; 26 | 27 | private static final Map gameAlarmTypeMap = Collections.unmodifiableMap(Stream.of(values()) 28 | .collect(Collectors.toMap(GameAlarmType::getDescription, Function.identity()))); 29 | 30 | private final String description; 31 | 32 | @JsonCreator 33 | public static GameAlarmType from(final String description) { 34 | if (gameAlarmTypeMap.containsKey(description)) { 35 | return gameAlarmTypeMap.get(description); 36 | } 37 | throw new AlarmException(ALARM_TYPE_NOT_FOUND, description); 38 | } 39 | 40 | @JsonValue 41 | public String getDescription() { 42 | return description; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/dto/mapper/GameRequestMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.dto.mapper; 2 | 3 | import java.time.LocalTime; 4 | 5 | import kr.pickple.back.address.domain.MainAddress; 6 | import kr.pickple.back.game.domain.NewGame; 7 | import kr.pickple.back.game.dto.request.GameCreateRequest; 8 | 9 | public class GameRequestMapper { 10 | 11 | public static NewGame mapToNewGameDomain(final GameCreateRequest gameCreateRequest, final MainAddress mainAddress) { 12 | final LocalTime playEndTime = gameCreateRequest.getPlayStartTime() 13 | .plusMinutes(gameCreateRequest.getPlayTimeMinutes()); 14 | 15 | return NewGame.builder() 16 | .content(gameCreateRequest.getContent()) 17 | .playDate(gameCreateRequest.getPlayDate()) 18 | .playStartTime(gameCreateRequest.getPlayStartTime()) 19 | .playEndTime(playEndTime) 20 | .playTimeMinutes(gameCreateRequest.getPlayTimeMinutes()) 21 | .mainAddress(gameCreateRequest.getMainAddress()) 22 | .detailAddress(gameCreateRequest.getDetailAddress()) 23 | .cost(gameCreateRequest.getCost()) 24 | .maxMemberCount(gameCreateRequest.getMaxMemberCount()) 25 | .positions(gameCreateRequest.getPositions()) 26 | .addressDepth1Name(mainAddress.getAddressDepth1Name()) 27 | .addressDepth2Name(mainAddress.getAddressDepth2Name()) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/handler/GameAlarmEventHandler.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.handler; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.transaction.event.TransactionalEventListener; 6 | 7 | import kr.pickple.back.alarm.event.game.GameJoinRequestNotificationEvent; 8 | import kr.pickple.back.alarm.event.game.GameMemberJoinedEvent; 9 | import kr.pickple.back.alarm.event.game.GameMemberRejectedEvent; 10 | import kr.pickple.back.alarm.service.GameAlarmService; 11 | import lombok.RequiredArgsConstructor; 12 | 13 | @Component 14 | @RequiredArgsConstructor 15 | public class GameAlarmEventHandler { 16 | 17 | private final GameAlarmService gameAlarmService; 18 | 19 | @Async 20 | @TransactionalEventListener 21 | public void sendAlarmToGameHost(final GameJoinRequestNotificationEvent gameJoinRequestNotificationEvent) { 22 | gameAlarmService.createGameJoinAlarm(gameJoinRequestNotificationEvent); 23 | } 24 | 25 | @Async 26 | @TransactionalEventListener 27 | public void sendAlarmToGameMemberOnJoin(final GameMemberJoinedEvent gameMemberJoinedEvent) { 28 | gameAlarmService.createGuestApproveAlarm(gameMemberJoinedEvent); 29 | } 30 | 31 | @Async 32 | @TransactionalEventListener 33 | public void sendAlarmToGameMemberOnRejection(final GameMemberRejectedEvent gameMemberRejectedEvent) { 34 | gameAlarmService.createGuestDeniedAlarm(gameMemberRejectedEvent); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/repository/CrewRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | import org.springframework.data.jpa.repository.JpaRepository; 9 | import org.springframework.data.jpa.repository.Modifying; 10 | import org.springframework.data.jpa.repository.Query; 11 | 12 | import kr.pickple.back.crew.domain.CrewStatus; 13 | import kr.pickple.back.crew.repository.entity.CrewEntity; 14 | 15 | public interface CrewRepository extends JpaRepository { 16 | 17 | Boolean existsByName(final String name); 18 | 19 | Page findByAddressDepth1IdAndAddressDepth2Id( 20 | final Long addressDepth1Id, 21 | final Long addressDepth2Id, 22 | final Pageable pageable 23 | ); 24 | 25 | Optional findByChatRoomId(final Long chatRoomId); 26 | 27 | List findAllByLeaderId(final Long leaderId); 28 | 29 | Integer countByLeaderId(final Long leaderId); 30 | 31 | @Modifying(clearAutomatically = true) 32 | @Query("update CrewEntity c set c.memberCount = :memberCount, c.status = :status where c.id = :crewId") 33 | void updateMemberCountAndStatus(final Long crewId, final Integer memberCount, final CrewStatus status); 34 | 35 | @Query("select c.chatRoomId from CrewEntity c where c.id = :crewId") 36 | Long findChatRoomId(final Long crewId); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/controller/CrewAlarmController.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.controller; 2 | 3 | import static org.springframework.http.HttpStatus.*; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PatchMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import jakarta.validation.Valid; 13 | import kr.pickple.back.alarm.dto.request.CrewAlarmUpdateStatusRequest; 14 | import kr.pickple.back.alarm.service.CrewAlarmService; 15 | import kr.pickple.back.auth.config.resolver.Login; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @RestController 19 | @RequiredArgsConstructor 20 | @RequestMapping("/crew-alarms") 21 | public class CrewAlarmController { 22 | 23 | private final CrewAlarmService crewAlarmService; 24 | 25 | @PatchMapping("/{crewAlarmId}") 26 | public ResponseEntity updateCrewAlarmStatus( 27 | @Login final Long loggedInMemberId, 28 | @PathVariable final Long crewAlarmId, 29 | @Valid @RequestBody final CrewAlarmUpdateStatusRequest crewAlarmUpdateStatusRequest 30 | ) { 31 | crewAlarmService.updateCrewAlarmById(loggedInMemberId, crewAlarmId, crewAlarmUpdateStatusRequest); 32 | 33 | return ResponseEntity.status(NO_CONTENT) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/controller/GameAlarmController.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.controller; 2 | 3 | import static org.springframework.http.HttpStatus.*; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PatchMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import jakarta.validation.Valid; 13 | import kr.pickple.back.alarm.dto.request.GameAlarmUpdateStatusRequest; 14 | import kr.pickple.back.alarm.service.GameAlarmService; 15 | import kr.pickple.back.auth.config.resolver.Login; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @RestController 19 | @RequiredArgsConstructor 20 | @RequestMapping("/game-alarms") 21 | public class GameAlarmController { 22 | 23 | private final GameAlarmService gameAlarmService; 24 | 25 | @PatchMapping("/{gameAlarmId}") 26 | public ResponseEntity updateGameAlarmStatus( 27 | @Login final Long loggedInMemberId, 28 | @PathVariable final Long gameAlarmId, 29 | @Valid @RequestBody final GameAlarmUpdateStatusRequest gameAlarmUpdateStatusRequest 30 | ) { 31 | gameAlarmService.updateGameAlarmById(loggedInMemberId, gameAlarmId, gameAlarmUpdateStatusRequest); 32 | 33 | return ResponseEntity.status(NO_CONTENT) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/handler/CrewAlarmEventHandler.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.handler; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.transaction.event.TransactionalEventListener; 6 | 7 | import kr.pickple.back.alarm.event.crew.CrewJoinRequestNotificationEvent; 8 | import kr.pickple.back.alarm.event.crew.CrewMemberJoinedEvent; 9 | import kr.pickple.back.alarm.event.crew.CrewMemberRejectedEvent; 10 | import kr.pickple.back.alarm.service.CrewAlarmService; 11 | import lombok.RequiredArgsConstructor; 12 | 13 | @Component 14 | @RequiredArgsConstructor 15 | public class CrewAlarmEventHandler { 16 | 17 | private final CrewAlarmService crewAlarmService; 18 | 19 | @Async 20 | @TransactionalEventListener 21 | public void sendAlarmToCrewLeader(final CrewJoinRequestNotificationEvent crewJoinRequestNotificationEvent) { 22 | crewAlarmService.createCrewJoinAlarm(crewJoinRequestNotificationEvent); 23 | } 24 | 25 | @Async 26 | @TransactionalEventListener 27 | public void sendAlarmToCrewMemberOnJoin(final CrewMemberJoinedEvent crewMemberJoinedEvent) { 28 | crewAlarmService.createCrewMemberApproveAlarm(crewMemberJoinedEvent); 29 | } 30 | 31 | @Async 32 | @TransactionalEventListener 33 | public void sendAlarmToCrewMemberOnRejection(final CrewMemberRejectedEvent crewMemberRejectedEvent) { 34 | crewAlarmService.createCrewMemberDeniedAlarm(crewMemberRejectedEvent); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/WebClientConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config; 2 | 3 | import static com.fasterxml.jackson.databind.PropertyNamingStrategies.*; 4 | import static org.springframework.http.MediaType.*; 5 | 6 | import org.springframework.http.codec.json.Jackson2JsonDecoder; 7 | import org.springframework.web.reactive.function.client.ExchangeStrategies; 8 | 9 | import com.fasterxml.jackson.databind.DeserializationFeature; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | 12 | import lombok.AccessLevel; 13 | import lombok.NoArgsConstructor; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | public final class WebClientConfig { 17 | 18 | /** 19 | * WebClient로 외부 API 요청시 받아오는 json의 property가 SNAKE_CASE인 경우 CAMEL_CASE로 변경하기 위해 설정하는 설정 값입니다. 20 | * ObjectMapper를 이용하여 SNAKE_CASE 전략을 설정하고, FAIL_ON_UNKNOWN_PROPERTIES를 이용하여 Response로 받는 객체에서 없는 필드는 21 | * 받지 않도록 합니다. 해당 설정 값을 WebClient Builder의 exchangeStrategis에 설정하여 변환될 수 있도록 합니다. 22 | * @author 황창현 23 | */ 24 | public static ExchangeStrategies getExchangeStrategies() { 25 | ObjectMapper objectMapper = new ObjectMapper() 26 | .setPropertyNamingStrategy(SNAKE_CASE) 27 | .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 28 | 29 | return ExchangeStrategies.builder() 30 | .codecs(configurer -> configurer.defaultCodecs() 31 | .jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, APPLICATION_JSON))) 32 | .build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/implement/TokenManager.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.implement; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import kr.pickple.back.auth.config.property.JwtProperties; 8 | import kr.pickple.back.auth.domain.token.AuthTokens; 9 | import kr.pickple.back.auth.domain.token.JwtProvider; 10 | import kr.pickple.back.auth.domain.token.RefreshToken; 11 | import kr.pickple.back.auth.repository.RedisRepository; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Component 15 | @RequiredArgsConstructor 16 | public class TokenManager { 17 | 18 | private static final String REFRESH_TOKEN_KEY = "refresh_token"; 19 | 20 | private final JwtProvider jwtProvider; 21 | private final JwtProperties jwtProperties; 22 | private final RedisRepository redisRepository; 23 | 24 | public AuthTokens create(final Long memberId) { 25 | final AuthTokens loginTokens = jwtProvider.createLoginToken(String.valueOf(memberId)); 26 | 27 | final RefreshToken refreshToken = RefreshToken.builder() 28 | .token(loginTokens.getRefreshToken()) 29 | .memberId(memberId) 30 | .createdAt(LocalDateTime.now()) 31 | .build(); 32 | 33 | redisRepository.saveHash( 34 | REFRESH_TOKEN_KEY, 35 | refreshToken.getToken(), 36 | refreshToken, 37 | jwtProperties.getRefreshTokenExpirationTime() 38 | ); 39 | 40 | return loginTokens; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/domain/AlarmExistsStatus.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.domain; 2 | 3 | import static kr.pickple.back.alarm.exception.AlarmExceptionCode.*; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | import com.fasterxml.jackson.annotation.JsonCreator; 12 | import com.fasterxml.jackson.annotation.JsonValue; 13 | 14 | import kr.pickple.back.crew.exception.CrewException; 15 | import lombok.Getter; 16 | import lombok.RequiredArgsConstructor; 17 | 18 | @Getter 19 | @RequiredArgsConstructor 20 | public enum AlarmExistsStatus { 21 | 22 | EXISTS("읽지 않은 알람이 있음", true), 23 | NOT_EXISTS("읽지 않은 알람이 없음", false); 24 | 25 | private static final Map alarmExistsStatusMap = Collections.unmodifiableMap( 26 | Stream.of(values()) 27 | .collect(Collectors.toMap(AlarmExistsStatus::getDescription, Function.identity()))); 28 | 29 | private final String description; 30 | private final Boolean booleanValue; 31 | 32 | @JsonCreator 33 | public static AlarmExistsStatus from(final String description) { 34 | if (alarmExistsStatusMap.containsKey(description)) { 35 | return alarmExistsStatusMap.get(description); 36 | } 37 | throw new CrewException(ALARM_EXISTS_STATUS_NOT_FOUND, description); 38 | } 39 | 40 | @JsonValue 41 | public Boolean getBooleanValue() { 42 | return booleanValue; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/http/KakaoMemberHttpInterfaceConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.http; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import org.springframework.web.reactive.function.client.support.WebClientAdapter; 7 | import org.springframework.web.service.invoker.HttpServiceProxyFactory; 8 | 9 | import kr.pickple.back.auth.config.WebClientConfig; 10 | import kr.pickple.back.auth.config.property.KakaoOauthProperties; 11 | import kr.pickple.back.auth.service.memberclient.KakaoMemberApiClient; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Configuration 15 | @RequiredArgsConstructor 16 | public class KakaoMemberHttpInterfaceConfig { 17 | 18 | private final KakaoOauthProperties kakaoOauthProperties; 19 | 20 | @Bean 21 | public KakaoMemberApiClient kakaoMemberApiClient() { 22 | return createHttpInterface(KakaoMemberApiClient.class); 23 | } 24 | 25 | private T createHttpInterface(final Class clazz) { 26 | final WebClient webClient = WebClient.builder() 27 | .baseUrl(kakaoOauthProperties.getMemberUrl()) 28 | .exchangeStrategies(WebClientConfig.getExchangeStrategies()) 29 | .build(); 30 | final HttpServiceProxyFactory build = HttpServiceProxyFactory 31 | .builder(WebClientAdapter.forClient(webClient)) 32 | .build(); 33 | 34 | return build.createClient(clazz); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/entity/ChatRoomEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Convert; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import jakarta.validation.constraints.NotNull; 11 | import kr.pickple.back.chat.domain.RoomType; 12 | import kr.pickple.back.chat.util.RoomTypeAttributeConverter; 13 | import kr.pickple.back.common.domain.BaseEntity; 14 | import lombok.AccessLevel; 15 | import lombok.Builder; 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | 19 | @Getter 20 | @Entity 21 | @Table(name = "chat_room") 22 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 23 | public class ChatRoomEntity extends BaseEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @NotNull 30 | @Column(length = 20) 31 | private String name; 32 | 33 | @NotNull 34 | @Convert(converter = RoomTypeAttributeConverter.class) 35 | private RoomType type; 36 | 37 | @NotNull 38 | private Integer memberCount = 0; 39 | 40 | @NotNull 41 | private Integer maxMemberCount = 2; 42 | 43 | @Builder 44 | private ChatRoomEntity(final String name, final RoomType type, final Integer maxMemberCount) { 45 | this.name = name; 46 | this.type = type; 47 | this.maxMemberCount = maxMemberCount; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/GamePositionJdbcRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | import java.util.List; 6 | 7 | import org.springframework.jdbc.core.BatchPreparedStatementSetter; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import kr.pickple.back.position.domain.Position; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Repository 15 | @RequiredArgsConstructor 16 | public class GamePositionJdbcRepository { 17 | 18 | private static final String GAME_POSITION_INSERT_SQL = "INSERT INTO game_position (position, game_id) VALUES(?, ?)"; 19 | 20 | private final JdbcTemplate jdbcTemplate; 21 | 22 | public void creatGamePositions(final List positions, final Long gameId) { 23 | jdbcTemplate.batchUpdate( 24 | GAME_POSITION_INSERT_SQL, 25 | new BatchPreparedStatementSetter() { 26 | @Override 27 | public void setValues(PreparedStatement ps, int i) throws SQLException { 28 | Position position = positions.get(i); 29 | ps.setString(1, position.getAcronym()); 30 | ps.setLong(2, gameId); 31 | } 32 | 33 | @Override 34 | public int getBatchSize() { 35 | return positions.size(); 36 | } 37 | } 38 | ); 39 | } 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/repository/MemberPositionJdbcRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.repository; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.SQLException; 5 | import java.util.List; 6 | 7 | import org.springframework.jdbc.core.BatchPreparedStatementSetter; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import kr.pickple.back.position.domain.Position; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Repository 15 | @RequiredArgsConstructor 16 | public class MemberPositionJdbcRepository { 17 | 18 | private static final String MEMBER_POSITION_INSERT_SQL = "INSERT INTO member_position (position, member_id) VALUES(?, ?)"; 19 | 20 | private final JdbcTemplate jdbcTemplate; 21 | 22 | public void creatMemberPositions(final List positions, final Long memberId) { 23 | jdbcTemplate.batchUpdate( 24 | MEMBER_POSITION_INSERT_SQL, 25 | new BatchPreparedStatementSetter() { 26 | @Override 27 | public void setValues(PreparedStatement ps, int i) throws SQLException { 28 | Position position = positions.get(i); 29 | ps.setString(1, position.getAcronym()); 30 | ps.setLong(2, memberId); 31 | } 32 | 33 | @Override 34 | public int getBatchSize() { 35 | return positions.size(); 36 | } 37 | } 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/repository/CrewAlarmRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.domain.PageRequest; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.data.repository.query.Param; 10 | 11 | import kr.pickple.back.alarm.domain.CrewAlarm; 12 | 13 | public interface CrewAlarmRepository extends JpaRepository { 14 | 15 | boolean existsByMemberIdAndIsRead(final Long memberId, final Boolean isRead); 16 | 17 | void deleteByMemberId(final Long memberId); 18 | 19 | Optional findByMemberIdAndId(final Long memberId, final Long crewAlarmId); 20 | 21 | @Query("SELECT ca " + 22 | "FROM CrewAlarm ca LEFT JOIN FETCH ca.crew " + 23 | "WHERE ca.member.id = :memberId AND ca.id < :cursorId " + 24 | "ORDER BY ca.createdAt DESC") 25 | List findByMemberIdAndIdLessThanOrderByCreatedAtDesc( 26 | @Param("memberId") final Long loggedInMemberId, 27 | final Long cursorId, 28 | final PageRequest of 29 | ); 30 | 31 | @Query("SELECT ca " + 32 | "FROM CrewAlarm ca LEFT JOIN FETCH ca.crew " + 33 | "WHERE ca.member.id = :memberId " + 34 | "ORDER BY ca.createdAt DESC") 35 | List findByMemberIdOrderByCreatedAtDesc( 36 | @Param("memberId") final Long loggedInMemberId, 37 | final PageRequest of 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/alarm/repository/GameAlarmRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.alarm.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.domain.PageRequest; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.data.repository.query.Param; 10 | 11 | import kr.pickple.back.alarm.domain.GameAlarm; 12 | 13 | public interface GameAlarmRepository extends JpaRepository { 14 | 15 | boolean existsByMemberIdAndIsRead(final Long memberId, final Boolean isRead); 16 | 17 | void deleteByMemberId(final Long memberId); 18 | 19 | Optional findByMemberIdAndId(final Long memberId, final Long gameAlarmId); 20 | 21 | @Query("SELECT ga " + 22 | "FROM GameAlarm ga LEFT JOIN FETCH ga.game " + 23 | "WHERE ga.member.id = :memberId " + 24 | "ORDER BY ga.createdAt DESC") 25 | List findByMemberIdOrderByCreatedAtDesc( 26 | @Param("memberId") final Long loggedInMemberId, 27 | final PageRequest of 28 | ); 29 | 30 | @Query("SELECT ga " + 31 | "FROM GameAlarm ga LEFT JOIN FETCH ga.game " + 32 | "WHERE ga.member.id = :memberId AND ga.id < :cursorId " + 33 | "ORDER BY ga.createdAt DESC") 34 | List findByMemberIdAndIdLessThanOrderByCreatedAtDesc( 35 | @Param("memberId") final Long loggedInMemberId, 36 | final Long cursorId, 37 | final PageRequest of 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/ChatRoomMemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | import kr.pickple.back.chat.repository.entity.ChatRoomMemberEntity; 11 | 12 | public interface ChatRoomMemberRepository extends JpaRepository { 13 | 14 | Boolean existsByChatRoomIdAndMemberId(final Long chatRoomId, final Long memberId); 15 | 16 | Boolean existsByActiveTrueAndChatRoomIdAndMemberId(final Long chatRoomId, final Long memberId); 17 | 18 | List findAllByMemberId(final Long memberId); 19 | 20 | List findAllByActiveTrueAndMemberId(final Long memberId); 21 | 22 | List findAllByActiveTrueAndChatRoomId(final Long chatRoomId); 23 | 24 | // 개인 채팅방에서 상대방에 대한 ChatRoomMember 데이터 조회용 25 | Optional findByChatRoomIdAndMemberIdNot(final Long chatRoomId, final Long memberId); 26 | 27 | @Modifying(clearAutomatically = true) 28 | @Query(""" 29 | update ChatRoomMemberEntity crm 30 | set crm.active = :activeStatus 31 | where crm.chatRoomId = :chatRoomId and crm.memberId = :memberId""") 32 | void updateChatRoomMemberActiveStatus( 33 | final Long chatRoomId, 34 | final Long memberId, 35 | final Boolean activeStatus 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/entity/ChatRoomMemberEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository.entity; 2 | 3 | import static java.lang.Boolean.*; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.Entity; 7 | import jakarta.persistence.GeneratedValue; 8 | import jakarta.persistence.GenerationType; 9 | import jakarta.persistence.Id; 10 | import jakarta.persistence.Table; 11 | import jakarta.persistence.UniqueConstraint; 12 | import jakarta.validation.constraints.NotNull; 13 | import kr.pickple.back.common.domain.BaseEntity; 14 | import lombok.AccessLevel; 15 | import lombok.Builder; 16 | import lombok.EqualsAndHashCode; 17 | import lombok.Getter; 18 | import lombok.NoArgsConstructor; 19 | 20 | @Entity 21 | @Getter 22 | @Table(name = "chat_room_member", uniqueConstraints = @UniqueConstraint(columnNames = {"member_id", "chat_room_id"})) 23 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 24 | @EqualsAndHashCode(of = {"memberId", "chatRoomId"}, callSuper = false) 25 | public class ChatRoomMemberEntity extends BaseEntity { 26 | 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Long id; 30 | 31 | @NotNull 32 | private Boolean active = TRUE; 33 | 34 | @NotNull 35 | @Column(name = "member_id") 36 | private Long memberId; 37 | 38 | @NotNull 39 | @Column(name = "chat_room_id") 40 | private Long chatRoomId; 41 | 42 | @Builder 43 | private ChatRoomMemberEntity(final Long memberId, final Long chatRoomId) { 44 | this.memberId = memberId; 45 | this.chatRoomId = chatRoomId; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/address/config/KakaoAddressSearchHttpInterfaceConfig.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.address.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | import org.springframework.web.reactive.function.client.support.WebClientAdapter; 7 | import org.springframework.web.service.invoker.HttpServiceProxyFactory; 8 | 9 | import kr.pickple.back.address.service.kakao.KakaoAddressSearchApiClient; 10 | import kr.pickple.back.auth.config.WebClientConfig; 11 | import kr.pickple.back.auth.config.property.KakaoOauthProperties; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Configuration 15 | @RequiredArgsConstructor 16 | public class KakaoAddressSearchHttpInterfaceConfig { 17 | 18 | private final KakaoOauthProperties kakaoOauthProperties; 19 | 20 | @Bean 21 | public KakaoAddressSearchApiClient kakaoAddressSearchApiClient() { 22 | return createHttpInterface(KakaoAddressSearchApiClient.class); 23 | } 24 | 25 | private T createHttpInterface(final Class clazz) { 26 | final WebClient webClient = WebClient.builder() 27 | .baseUrl(kakaoOauthProperties.getAddressUrl()) 28 | .exchangeStrategies(WebClientConfig.getExchangeStrategies()) 29 | .build(); 30 | final HttpServiceProxyFactory build = HttpServiceProxyFactory 31 | .builder(WebClientAdapter.forClient(webClient)) 32 | .build(); 33 | 34 | return build.createClient(clazz); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/dto/request/MemberCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.dto.request; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Positive; 8 | import kr.pickple.back.auth.domain.oauth.OauthProvider; 9 | import kr.pickple.back.position.domain.Position; 10 | import lombok.AccessLevel; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | @Getter 17 | @Builder 18 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 19 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 20 | public class MemberCreateRequest { 21 | 22 | @NotBlank(message = "닉네임은 null이거나 빈 문자열이거나 공백일 수 없음") 23 | private String nickname; 24 | 25 | @NotBlank(message = "프로필 이미지 URL은 null이거나 빈 문자열이거나 공백일 수 없음") 26 | private String profileImageUrl; 27 | 28 | @NotBlank(message = "이메일은 null이거나 빈 문자열이거나 공백일 수 없음") 29 | private String email; 30 | 31 | @NotNull(message = "포지션은 null일 수 없음") 32 | private List positions; 33 | 34 | @NotNull(message = "oauth id는 null일 수 없음") 35 | @Positive(message = "oauth id는 값이 없거나 음수일 수 없음") 36 | private Long oauthId; 37 | 38 | @NotNull(message = "oauth 제공자는 null이거나 빈 문자열이거나 공백일 수 없음") 39 | private OauthProvider oauthProvider; 40 | 41 | @NotBlank(message = "주소1은 null이거나 빈 문자열이거나 공백일 수 없음") 42 | private String addressDepth1; 43 | 44 | @NotBlank(message = "주소2는 null이거나 빈 문자열이거나 공백일 수 없음") 45 | private String addressDepth2; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/member/domain/Member.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.member.domain; 2 | 3 | import static kr.pickple.back.member.exception.MemberExceptionCode.*; 4 | 5 | import java.util.List; 6 | 7 | import kr.pickple.back.member.exception.MemberException; 8 | import kr.pickple.back.position.domain.Position; 9 | import lombok.AccessLevel; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Builder; 12 | import lombok.EqualsAndHashCode; 13 | import lombok.Getter; 14 | 15 | @Getter 16 | @Builder 17 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 18 | @EqualsAndHashCode(of = "memberId") 19 | public class Member { 20 | 21 | public static final List MANNER_SCORE_POINT_RANGE = List.of(-1, 0, 1); 22 | 23 | private Long memberId; 24 | private String email; 25 | private String nickname; 26 | private String introduction; 27 | private String profileImageUrl; 28 | private Integer mannerScore; 29 | private Integer mannerScoreCount; 30 | private String addressDepth1Name; 31 | private String addressDepth2Name; 32 | private List positions; 33 | 34 | public Boolean isIdMatched(final Long memberId) { 35 | return this.memberId.equals(memberId); 36 | } 37 | 38 | public void updateMannerScore(final Integer mannerScorePoint) { 39 | if (MANNER_SCORE_POINT_RANGE.contains(mannerScorePoint)) { 40 | this.mannerScore += mannerScorePoint; 41 | this.mannerScoreCount += 1; 42 | 43 | return; 44 | } 45 | 46 | throw new MemberException(MEMBER_UPDATING_MANNER_SCORE_POINT_OUT_OF_RANGE, mannerScorePoint); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/repository/entity/CrewMemberEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.repository.entity; 2 | 3 | import static kr.pickple.back.common.domain.RegistrationStatus.*; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.Convert; 7 | import jakarta.persistence.Entity; 8 | import jakarta.persistence.GeneratedValue; 9 | import jakarta.persistence.GenerationType; 10 | import jakarta.persistence.Id; 11 | import jakarta.persistence.Table; 12 | import jakarta.validation.constraints.NotNull; 13 | import kr.pickple.back.common.domain.BaseEntity; 14 | import kr.pickple.back.common.domain.RegistrationStatus; 15 | import kr.pickple.back.common.util.RegistrationStatusAttributeConverter; 16 | import lombok.AccessLevel; 17 | import lombok.Builder; 18 | import lombok.Getter; 19 | import lombok.NoArgsConstructor; 20 | 21 | @Getter 22 | @Entity 23 | @Table(name = "crew_member") 24 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 25 | public class CrewMemberEntity extends BaseEntity { 26 | 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Long id; 30 | 31 | @NotNull 32 | @Convert(converter = RegistrationStatusAttributeConverter.class) 33 | @Column(length = 10) 34 | private RegistrationStatus status = WAITING; 35 | 36 | @NotNull 37 | private Long memberId; 38 | 39 | @NotNull 40 | private Long crewId; 41 | 42 | @Builder 43 | private CrewMemberEntity(final RegistrationStatus status, final Long memberId, final Long crewId) { 44 | this.status = status; 45 | this.memberId = memberId; 46 | this.crewId = crewId; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/repository/entity/ChatMessageEntity.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.repository.entity; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Convert; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import jakarta.validation.constraints.NotNull; 11 | import kr.pickple.back.chat.domain.MessageType; 12 | import kr.pickple.back.chat.util.MessageTypeAttributeConverter; 13 | import kr.pickple.back.common.domain.BaseEntity; 14 | import lombok.AccessLevel; 15 | import lombok.Builder; 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | 19 | @Getter 20 | @Entity 21 | @Table(name = "chat_message") 22 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 23 | public class ChatMessageEntity extends BaseEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | private Long id; 28 | 29 | @NotNull 30 | @Convert(converter = MessageTypeAttributeConverter.class) 31 | private MessageType type; 32 | 33 | @NotNull 34 | @Column(length = 500) 35 | private String content; 36 | 37 | @NotNull 38 | private Long senderId; 39 | 40 | @NotNull 41 | private Long chatRoomId; 42 | 43 | @Builder 44 | private ChatMessageEntity(final MessageType type, final String content, final Long senderId, 45 | final Long chatRoomId) { 46 | this.type = type; 47 | this.content = content; 48 | this.senderId = senderId; 49 | this.chatRoomId = chatRoomId; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/GameRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository; 2 | 3 | import static kr.pickple.back.game.exception.GameExceptionCode.*; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | import org.springframework.data.jpa.repository.Query; 12 | 13 | import kr.pickple.back.game.domain.GameStatus; 14 | import kr.pickple.back.game.exception.GameException; 15 | import kr.pickple.back.game.repository.entity.GameEntity; 16 | 17 | public interface GameRepository extends JpaRepository, GameSearchRepository { 18 | 19 | Page findByAddressDepth1IdAndAddressDepth2IdAndStatusNot( 20 | final Long addressDepth1Id, 21 | final Long addressDepth2Id, 22 | final GameStatus status, 23 | final Pageable pageable 24 | ); 25 | 26 | Optional findByChatRoomId(final Long chatRoomId); 27 | 28 | List findAllByHostId(final Long hostId); 29 | 30 | @Query("update GameEntity g set g.memberCount = :memberCount, g.status = :status where g.id = :gameId") 31 | void updateMemberCountAndStatus(final Long gameId, final Integer memberCount, final GameStatus status); 32 | 33 | @Query("update GameEntity g set g.status = :status where g.id = :gameId") 34 | void updateRegistrationStatus(final GameStatus status, final Long gameId); 35 | 36 | @Query("select g.chatRoomId from GameEntity g where g.id = :gameId") 37 | Long findChatRoomId(final Long gameId); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/game/repository/GameMemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.game.repository; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | 10 | import kr.pickple.back.common.domain.RegistrationStatus; 11 | import kr.pickple.back.game.repository.entity.GameMemberEntity; 12 | 13 | public interface GameMemberRepository extends JpaRepository { 14 | 15 | Optional findByMemberIdAndGameId(final Long memberId, final Long gameId); 16 | 17 | Optional findByMemberIdAndGameIdAndStatus( 18 | final Long memberId, 19 | final Long gameId, 20 | final RegistrationStatus status 21 | ); 22 | 23 | List findAllByMemberIdAndStatus(final Long memberId, final RegistrationStatus memberStatus); 24 | 25 | List findAllByGameIdAndStatus(final Long gameId, final RegistrationStatus status); 26 | 27 | Boolean existsByGameIdAndMemberId(final Long gameId, final Long memberId); 28 | 29 | @Modifying(clearAutomatically = true) 30 | @Query("update GameMemberEntity gm set gm.status = :status where gm.id = :gameMemberId") 31 | void updateRegistrationStatus(final Long gameMemberId, final RegistrationStatus status); 32 | 33 | @Modifying(clearAutomatically = true) 34 | @Query("update GameMemberEntity gm set gm.isReview = :isReview where gm.id = :gameMemberId") 35 | void updateReviewDone(Long gameMemberId, Boolean isReview); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/chat/implement/ChatMapper.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.chat.implement; 2 | 3 | import kr.pickple.back.chat.repository.entity.ChatMessageEntity; 4 | import kr.pickple.back.chat.domain.ChatMessage; 5 | import kr.pickple.back.chat.repository.entity.ChatRoomEntity; 6 | import kr.pickple.back.chat.domain.ChatRoom; 7 | import kr.pickple.back.member.domain.Member; 8 | import lombok.AccessLevel; 9 | import lombok.NoArgsConstructor; 10 | 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public final class ChatMapper { 13 | 14 | public static ChatRoom mapChatRoomEntityToDomain(final ChatRoomEntity chatRoomEntity) { 15 | return ChatRoom.builder() 16 | .chatRoomId(chatRoomEntity.getId()) 17 | .type(chatRoomEntity.getType()) 18 | .name(chatRoomEntity.getName()) 19 | .memberCount(chatRoomEntity.getMemberCount()) 20 | .maxMemberCount(chatRoomEntity.getMaxMemberCount()) 21 | .createdAt(chatRoomEntity.getCreatedAt()) 22 | .build(); 23 | } 24 | 25 | public static ChatMessage mapChatMessageEntityToDomain( 26 | final ChatMessageEntity chatMessageEntity, 27 | final Member sender, 28 | final ChatRoom chatRoom 29 | ) { 30 | return ChatMessage.builder() 31 | .chatMessageId(chatMessageEntity.getId()) 32 | .type(chatMessageEntity.getType()) 33 | .content(chatMessageEntity.getContent()) 34 | .sender(sender) 35 | .chatRoom(chatRoom) 36 | .createdAt(chatMessageEntity.getCreatedAt()) 37 | .build(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/resolver/LoginTokenArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.resolver; 2 | 3 | import static org.springframework.http.HttpHeaders.*; 4 | 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.bind.support.WebDataBinderFactory; 8 | import org.springframework.web.context.request.NativeWebRequest; 9 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 10 | import org.springframework.web.method.support.ModelAndViewContainer; 11 | 12 | import kr.pickple.back.auth.domain.token.JwtProvider; 13 | import lombok.RequiredArgsConstructor; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | public class LoginTokenArgumentResolver implements HandlerMethodArgumentResolver { 18 | 19 | private final JwtProvider jwtProvider; 20 | private final TokenExtractor tokenExtractor; 21 | 22 | @Override 23 | public boolean supportsParameter(final MethodParameter parameter) { 24 | return parameter.withContainingClass(Long.class) 25 | .hasParameterAnnotation(Login.class); 26 | } 27 | 28 | @Override 29 | public Long resolveArgument( 30 | final MethodParameter parameter, 31 | final ModelAndViewContainer mavContainer, 32 | final NativeWebRequest webRequest, 33 | final WebDataBinderFactory binderFactory 34 | ) { 35 | final String accessToken = tokenExtractor.extractAccessToken(webRequest.getHeader(AUTHORIZATION)); 36 | jwtProvider.validateAccessToken(accessToken); 37 | 38 | return Long.parseLong(jwtProvider.getSubject(accessToken)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/auth/config/resolver/RegisterTokenArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.auth.config.resolver; 2 | 3 | import static org.springframework.http.HttpHeaders.*; 4 | 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.bind.support.WebDataBinderFactory; 8 | import org.springframework.web.context.request.NativeWebRequest; 9 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 10 | import org.springframework.web.method.support.ModelAndViewContainer; 11 | 12 | import kr.pickple.back.auth.domain.token.JwtProvider; 13 | import lombok.RequiredArgsConstructor; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | public class RegisterTokenArgumentResolver implements HandlerMethodArgumentResolver { 18 | 19 | private final JwtProvider jwtProvider; 20 | private final TokenExtractor tokenExtractor; 21 | 22 | @Override 23 | public boolean supportsParameter(final MethodParameter parameter) { 24 | return parameter.withContainingClass(String.class) 25 | .hasParameterAnnotation(SignUp.class); 26 | } 27 | 28 | @Override 29 | public String resolveArgument( 30 | final MethodParameter parameter, 31 | final ModelAndViewContainer mavContainer, 32 | final NativeWebRequest webRequest, 33 | final WebDataBinderFactory binderFactory 34 | ) { 35 | final String registerToken = tokenExtractor.extractRegisterToken(webRequest.getHeader(AUTHORIZATION)); 36 | jwtProvider.validateRegisterToken(registerToken); 37 | 38 | return jwtProvider.getSubject(registerToken); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/kr/pickple/back/crew/domain/NewCrew.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.crew.domain; 2 | 3 | import kr.pickple.back.chat.domain.ChatRoom; 4 | import kr.pickple.back.member.domain.Member; 5 | import lombok.AccessLevel; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class NewCrew { 13 | 14 | private String name; 15 | private String content; 16 | private Integer maxMemberCount; 17 | private String addressDepth1Name; 18 | private String addressDepth2Name; 19 | private String profileImageUrl; 20 | private String backgroundImageUrl; 21 | private Member leader; 22 | private ChatRoom chatRoom; 23 | 24 | @Builder 25 | private NewCrew( 26 | final String name, 27 | final String content, 28 | final Integer maxMemberCount, 29 | final String addressDepth1Name, 30 | final String addressDepth2Name 31 | ) { 32 | this.name = name; 33 | this.content = content; 34 | this.maxMemberCount = maxMemberCount; 35 | this.addressDepth1Name = addressDepth1Name; 36 | this.addressDepth2Name = addressDepth2Name; 37 | } 38 | 39 | public void assignImageUrls(final String profileImageUrl, final String backgroundImageUrl) { 40 | this.profileImageUrl = profileImageUrl; 41 | this.backgroundImageUrl = backgroundImageUrl; 42 | } 43 | 44 | public void assignLeader(final Member leader) { 45 | this.leader = leader; 46 | } 47 | 48 | public void assignChatRoom(final ChatRoom chatRoom) { 49 | this.chatRoom = chatRoom; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/kr/pickple/back/fixture/setup/MemberSetup.java: -------------------------------------------------------------------------------- 1 | package kr.pickple.back.fixture.setup; 2 | 3 | import kr.pickple.back.address.repository.entity.AddressDepth1Entity; 4 | import kr.pickple.back.address.repository.entity.AddressDepth2Entity; 5 | import kr.pickple.back.fixture.domain.MemberFixtures; 6 | import kr.pickple.back.member.repository.entity.MemberEntity; 7 | import kr.pickple.back.member.repository.MemberRepository; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.List; 13 | 14 | @Component 15 | public class MemberSetup { 16 | 17 | @Autowired 18 | private MemberRepository memberRepository; 19 | 20 | @Autowired 21 | private AddressSetup addressSetup; 22 | 23 | public MemberEntity save() { 24 | final AddressDepth1Entity addressDepth1 = addressSetup.findAddressDepth1("서울시"); 25 | final AddressDepth2Entity addressDepth2 = addressSetup.findAddressDepth2("영등포구"); 26 | 27 | final MemberEntity member = MemberFixtures.memberBuild( 28 | addressDepth1, 29 | addressDepth2 30 | ); 31 | return memberRepository.save(member); 32 | } 33 | 34 | public List save(final int count) { 35 | final AddressDepth1Entity addressDepth1 = addressSetup.findAddressDepth1("서울시"); 36 | final AddressDepth2Entity addressDepth2 = addressSetup.findAddressDepth2("영등포구"); 37 | 38 | final List members = MemberFixtures.membersBuild(count, addressDepth1, addressDepth2); 39 | 40 | return members.stream() 41 | .map(memberRepository::save) 42 | .toList(); 43 | } 44 | } 45 | --------------------------------------------------------------------------------