From 3a7f49a82257c3592d30dc019c78095826294cc4 Mon Sep 17 00:00:00 2001 From: Dev Uni Date: Mon, 4 Dec 2023 02:23:52 +0900 Subject: [PATCH] =?UTF-8?q?v1.0.0=20=EC=A0=95=EC=8B=9D=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20(#244)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: sonarcloud 및 jacoco 세팅 * chore: checkstyle 세팅 * chore: gitignore 수정 * docs: PR 템플릿 수정 * feat: 공통된 클래스 추가 작업 (#4) * feat: Exception 관련 클래스 추가 * feat: Config 관련 클래스 추가 * feat: Entity 관련 클래스 추가 * fix: intellij-formatter line-separator (#10) * feat: 회원 Authorization Grant와 페이지 반환 기능 구현 (#18) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: 방 생성, 수정 기능 구현 (#20) * feat: Room, Participant, Routine, Certification 엔티티 생성 * feat: Room 엔티티 인증 시간 검증 로직 추가 * test: Room 엔티티 테스트 코드 작성 * refactor: Room 관련 엔티티 수정 * feat: 방 생성 기능 구현 * chore: DynamicQuery Jacoco 예외 추가 * test: 방 생성 테스트 코드 작성 * feat: 방 수정 기능 구현 * test: 방 수정 통합 테스트 작성 * refactor: Member 관련 파일 이동 * refactor: checkStyle에 맞춰서 변경 * test: 추가 테스트 코드 작성 * refactor: 코드 리뷰 반영 * refactor: 불필요한 메서드 삭제 * feat: 벌레 조회 기능 구현 (#21) * feat: Bug 임베디드 타입 생성 * feat: 벌레 조회 API 구현 * docs: PR merge 시, Issue 자동 close로 수정 * refactor: 엔티티 생성자 id 포함으로 변경 * feat: 벌레 개수 검증 추가 * test: 벌레 조회 서비스 테스트 * style: dto 내 bug 패키지 제거 * test: Bug 도메인 테스트 * style: 테스트 메서드 네이밍 수정 * test: 벌레 조회 controller 테스트 * refactor: private 생성자 추가 * test: 멤버 fixture 생성 및 적용 * test: 벌레 fixture 생성 및 적용 * test: 멤버 엔티티 테스트에 Bug 추가 * fix: code smell 제거 * style: BugMapper 메서드 네이밍 수정 * style: return 전 줄바꿈 추가 * refactor: ResponseStatus + DTO 방식으로 변경 * test: 벌레 개수 검증 테스트에 ParameterizedTest 적용 * feat: 상품 목록 조회 기능 구현 (#22) * fix: SQL syntax 오류 수정 * feat: 상품 엔티티 생성 * feat: 상품 목록 조회 API 구현 * test: 상품 목록 조회 테스트 * style: return 전 줄바꿈 추가 * feat: Authorization Server로 부터 토큰 발급 기능 추가 (#24) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 콕 찌르기 알림 서비스 기능 구현 (#26) * feat: Redis 초기 설정 * feat: FCM 초기 설정 및 예외처리 * feat: 콕 찌르기 기능 서비스 및 레포지토리 구현 * fix: Redis Reposi 추상 클래스 제거 및 테스트 Profile 변경 * test: StringRedisRepository 테스트 * test: NotificationRepository 테스트 * feat: NullPointerException 예외 핸들링 처리 * test: NotificationService 테스트 * refacotr: PostConstruct를 Bean으로 변경 * refactor : 테스트 코드 Profile 변경 * fix: redis 테스트 삭제 * fix : Redis 테스트 클래스 삭제 * fix : Member Role Default 문제 해결 * fix: firebase config 경로 변경 * fix: 에러 찾기 위한 로그 설정 * fix: CI가 서브모듈 경로를 못찾는 에러 해결 * test: Redis Repository 테스트 및 로그 삭제 * style: 메서드명 및 줄바꿈 설정 * refactor: 콕 찌르기 알림 저장 시, 키값 및 만료시간 변경 * refactor: 리뷰 코드 수정 * feat: 방 참여, 나가기 기능 구현 (#28) * feat: Room, Participant, Routine, Certification 엔티티 생성 * feat: Room 엔티티 인증 시간 검증 로직 추가 * test: Room 엔티티 테스트 코드 작성 * refactor: Room 관련 엔티티 수정 * feat: 방 생성 기능 구현 * chore: DynamicQuery Jacoco 예외 추가 * test: 방 생성 테스트 코드 작성 * feat: 방 수정 기능 구현 * test: 방 수정 통합 테스트 작성 * refactor: Member 관련 파일 이동 * refactor: checkStyle에 맞춰서 변경 * test: 추가 테스트 코드 작성 * chore: Apache Commons Lang 의존성 추가 * feat: 방 참여 기능 구현 * test: 방 참여 기능 테스트 작성 * feat: 방 나가기 기능 구현 * chore: test yml JPA 로그 추가 * test: 방 참여, 나가기 일부 테스트 작성 * feat: 방 나가기 구현 마무리 * fix: Morning -> Night 수정 * test: 방 나가기 추가 테스트 코드 작성 * test: 방 나가기 추가 테스트 작성 * feat: 방 ID로 존재 확인 로직 추가 * refactor: 오타 수정 * fix: 테스트 실행 불가 해결 * fix: CI 오류 해결 * refactor: 코드 리뷰 반영 * feat: 방 수정에 필요한 필드 추가 (#36) * feat: ec2 dev 서버 배포 구현 (#40) * chore: submodule 업데이트 * feat: docker-compose 파일 세팅 * feat: nginx 템플릿 설정 * feat: Dockerfile 설정 * feat: 쉘 스크립트 파일 작성 * feat: HealthCheckController 구현 * chore: build.gradle 커버리지 항목 제외 추가 * feat: github actions ci, cd 작성 * style: ci 파일 오타 수정 * hotfix: submodule mysql 수정 * feat: social 회원 토큰 조회 기능 추가 (#37) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 토큰 정보 조회 기능 및 테스트 추가 * feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경 * fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성 * fix: 코드 리뷰 반영 * feat: 아이템 목록 조회 기능 구현 (#41) * refactor: ResponseStatus + DTO 방식으로 변경 * feat: 아이템, 인벤토리 Entity 생성 * feat: 아이템 목록 조회 API 구현 * test: containsExactly 검증으로 수정 * test: 아이템 목록 조회 Service 테스트 * test: 인벤토리 아이템 목록 조회 Repository 테스트 * feat: Stream 유틸 클래스 생성 및 적용 * fix: ItemFixture를 통한 아이템 생성 시 build() 추가 * test: 구매하지 않은 아이템 목록 조회 Repository 테스트 * feat: MethodArgumentTypeMismatchException handler 추가 * test: 아이템 목록 조회 Controller 테스트 * fix: Mapper 생성자 접근 레벨 private으로 변경 * feat: ItemType 생성 및 적용 * refactor: 잘못된 요청 타입 에러 메시지 상수화 * feat: 콕 찌르기 API 구현 (feat. RestDoc, Embedded Redis) (#43) * feat: RestDoc 기본 설정 * feat: Embedded Redis 환경 구축 * style: 에러 메시지 변경 및 추가 * feat: 콕 찌르기 API 구현 * refactor: 콕 찌르기 키 생성 메서드 분리 * chore: redis docker 주석 처리 * chore: dump.rdb 삭제 * chore: develop-cd Redis 주석처리 * style: 주석 삭제 * style: Constant 분리 * refacotr: String.format을 활용해 Knock Key 생성 * feat: 방 상세 정보 조회 기능 구현 (#44) * refactor: Mapper 클래스 선언 통일 * refactor: service, mapper 수정 * fix: Room nullable로 수정 * chore: highlight sql 설정 추가 * feat: 방과 각 방에서 사용자의 인증 여부 저장을 위한 Entity 추가 * feat: 방 상세 정보 조회에 필요한 DTO, Mapper 추가 * feat: 방장 정보 부르는 querydsl * feat: 인증에 대한 정보 Mapper, querydsl 추가 * feat: Participant, Routine 수정, Search querydsl 작성 * feat: 방 상세 정보 조회 service, controller * test: 방 상세 정보 조회 통합 테스트 작성 * refactor: 코드 리뷰 반영 * refactor: checkstyle 수정 * feat: 아이템 적용 기능 구현 (#45) * feat: 아이템 적용 API 구현 * test: 아이템 적용 Service 테스트 * test: Controller 테스트 @WebMvcTest로 변경 * test: 아이템 적용 Controller 테스트 * style: support 패키지 생성 * test: RepositoryTest 어노테이션 생성 및 적용 * test: 동일 메서드 테스트 Nested로 처리 * feat: 현재 적용된 인벤토리 조회 시 아이템 타입 정보 추가 * test: 인벤토리 조회 Repository 테스트 * fix: merge conflict 해결 * test: given-willReturn 으로 변경 * refactor: 메서드 네이밍 수정 * refactor: 어노테이션 네이밍 수정 * refactor: 방 관련 기능 리팩터링 (#49) * refactor: 방 상세 정보 조회 부분 리팩터링 * refactor: Mapper 위치 변경 * refactor: 방 관련 기능 수정 * refactor: createRoom roomId 반환하도록 refactor * feat: 회원 생성 및 로그인 응답 기능 구현 (#47) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 토큰 정보 조회 기능 및 테스트 추가 * feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경 * fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성 * feat: JWT 토큰 제공 서비스 및 테스트 코드 추가 * feat: 토큰 인증 코드 및 테스트 코드 작성 * feat: 로그인 및 회원가입 기능 추가 - 회원의 socialId string -> long으로 변경 * feat: 회원 로그인 테스트 코드 추가 * chore: 코드 포메팅 재 설정 * feat: config 파일 업데이트 * feat: Window용 포트 redis 포트 변경 추가 * refacotr: develop 업데이트 사항 merge * refactor: develop 업데이트 부분 merge * fix: TimeConfig 삭제 및 코드 스멜 변경 * refactor: 코르리뷰 반영 * feat: 인증 타임에 따른 알림 기능 구현 (#50) * feat: 인증 타임에 따른 주기적 알림 기능 도입 * test: 인증타임에 따른 주기적 알림 기능 테스트 * test: Restdoc 파일 * refactor: 코드 리뷰 반영 * refactor: 코드 리뷰 반영 * fix: checkstyle 수정 * refactor: 코드 리뷰 반영 * refactor: 리뷰 반영 * chore: config update (#51) * feat: 콕 찌르기 여부를 확인하는 기능 구현 및 테스트 (#53) * feat: 콕 찌르기 여부를 확인하는 기능 구현 * test: 콕 찌르기 여부를 확인하는 기능 테스트 * test: 콕 찌르기 여부를 확인하는 기능 테스트 * feat: 아이템 구매 기능 구현 (#54) * feat: 벌레 내역 관련 Entity 생성 * feat: 아이템 구매 API 구현 * refactor: Bug -> Wallet 네이밍 수정 * refactor: Bug로 네이밍 재수정 * refactor: Entity 생성 로직 Mapper로 이동 * fix: isDefault nullable 하도록 수정 * fix: 레벨 1부터 시작하도록 수정 * test: 아이템 구매 Service 테스트 * test: 아이템 Entity 테스트 * test: 벌레 Entity 테스트 * test: 아이템 구매 Controller 테스트 * style: decrease로 메서드 네이밍 수정 * feat: 해당 벌레 타입의 개수 증가 메서드 추가 * chore: Table 어노테이션 추가 * test: 벌레 개수 증가 테스트 * feat: 쿠폰 발행 기능 구현 및 테스트 (#57) * feat: 쿠폰 엔티티 설계 * test: Coupon Entity 테스트 * refactor: 초기값 0에서 1로 지정 * feat: 쿠폰 종류에 대한 조회 처리 구현 및 테스트 * refactor: 쿠폰 컬럼으로 관리자 아이디 추가 * feat: 관리자의 쿠폰 생성 기능 구현 * test: 쿠폰 발행 기능 테스트 * test: 쿠폰 엔티티 테스트 추가 * style: test 메서드 변경 * fix: CheckStyle 수정 * feat: 쿠폰 삭제 기능 구현 (#58) * feat: 쿠폰 삭제 기능 구현 * test: 쿠폰 삭제 기능 테스트 * test: 테스트 Display 및 Adoc 수정 * test: RestDoc 문서 결과 * feat: 특정 쿠폰 및 상태에 따른 쿠폰 조회 기능 구현 및 테스트 (#60) * feat: 쿠폰 삭제 기능 구현 * test: 쿠폰 삭제 기능 테스트 * test: 테스트 Display 및 Adoc 수정 * test: RestDoc 문서 결과 * refactor: type -> couponType으로 변경 * feat: 쿠폰 상태에 따른 조회 및 특정 쿠폰 조회 기능 구현 * fix: 쿼리 에러 해결 및 CouponResponse 위치 변경 * fix: 상태에 따른 잘못된 쿼리 수정 * test: 특정 쿠폰 및 상태에 따른 쿠폰 조회 기능 테스트 * test: 리뷰 반영 * feat: 회원 annotation 기능 추가 및 테스트 인터페이스 제공 (#62) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 토큰 정보 조회 기능 및 테스트 추가 * feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경 * fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성 * feat: JWT 토큰 제공 서비스 및 테스트 코드 추가 * feat: 토큰 인증 코드 및 테스트 코드 작성 * feat: 로그인 및 회원가입 기능 추가 - 회원의 socialId string -> long으로 변경 * feat: 회원 로그인 테스트 코드 추가 * chore: 코드 포메팅 재 설정 * feat: config 파일 업데이트 * feat: Window용 포트 redis 포트 변경 추가 * refacotr: develop 업데이트 사항 merge * refactor: develop 업데이트 부분 merge * fix: TimeConfig 삭제 및 코드 스멜 변경 * refactor: 코르리뷰 반영 * chore: submodule update * feat: 메서드 파싱 customizing 및 @CurrentMember AuthorizationMember 를 파라미터로 감지하는 조건 추가 * feat: 인가회원에 대한 객체 ThreadLocalMap에 저장하는 기능 추가 * fix: 회원 정보 Optional 정보 조회 버그 fix, socialId requiredNotNull추가 등 에러 수정 * feat: API요청 Path 및 인증에 따른 filter 추가 - PathFilter: PathResolver, WebConfig - AuthorizationFilter:AuthorizationService, JwtAuthenticationService, JwtProviderService, MemberService - Member info: CurrentMember, AuthorizationMember, LoginResponse, MemberMapper, CurrentMember, PublicClaim, CurrentMemberArgumentResolver * test: CurrentMember 테스트 support 추가 * test: authorizationfilter 및 pathfilter 테스트 추가 * test: 회원 repostiory 및 fixture 추가 * test: filter support 클랠스 추가 * test: filter support 클래스 적용 * refactor: PublicClaim 변환 책임 변경 * test: PathResolver, CurrentMemberArgumentResovler테스트 코드 추가 * fix: 모든 쿠키 secure 적용되도록 변경 * refactor: 클래스 명 변경 * refactor: webConfig Path 매핑 클래스 추가 * feat: 루틴 인증 및 이미지 업로드 기능 구현 (#63) * feat: 서버 시간 체크 컨트롤러 구현 * feat: 루틴 인증 기능 및 ClockHolder 구현 * feat: UrlSubstringParser 구현 * test: 루틴 인증 관련 테스트 구현 * refactor: 방 공지 길이 수정 * feat: constant 및 error 작성 * feat: s3 이미지 업로드 기능 구현 * test: s3 이미지 업로드 테스트 * chore: build.gradle s3 추가 * Merge branch 'develop' into feature/#8-upload-image * refactor: build 오류 수정 * test: CertificationsSearchRepository 테스트 * chore: s3Manager 커버리지 제외 * refactor: UrlParser 코드스멜 제거 * refactor: 코드 리뷰 반영 --------- Co-authored-by: ymkim97 Co-authored-by: Youngmyung Kim <83266154+ymkim97@users.noreply.github.com> * feat: healthCheck path 추가 (#66) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 토큰 정보 조회 기능 및 테스트 추가 * feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경 * fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성 * feat: JWT 토큰 제공 서비스 및 테스트 코드 추가 * feat: 토큰 인증 코드 및 테스트 코드 작성 * feat: 로그인 및 회원가입 기능 추가 - 회원의 socialId string -> long으로 변경 * feat: 회원 로그인 테스트 코드 추가 * chore: 코드 포메팅 재 설정 * feat: config 파일 업데이트 * feat: Window용 포트 redis 포트 변경 추가 * refacotr: develop 업데이트 사항 merge * refactor: develop 업데이트 부분 merge * fix: TimeConfig 삭제 및 코드 스멜 변경 * refactor: 코르리뷰 반영 * chore: submodule update * feat: 메서드 파싱 customizing 및 @CurrentMember AuthorizationMember 를 파라미터로 감지하는 조건 추가 * feat: 인가회원에 대한 객체 ThreadLocalMap에 저장하는 기능 추가 * fix: 회원 정보 Optional 정보 조회 버그 fix, socialId requiredNotNull추가 등 에러 수정 * feat: API요청 Path 및 인증에 따른 filter 추가 - PathFilter: PathResolver, WebConfig - AuthorizationFilter:AuthorizationService, JwtAuthenticationService, JwtProviderService, MemberService - Member info: CurrentMember, AuthorizationMember, LoginResponse, MemberMapper, CurrentMember, PublicClaim, CurrentMemberArgumentResolver * test: CurrentMember 테스트 support 추가 * test: authorizationfilter 및 pathfilter 테스트 추가 * test: 회원 repostiory 및 fixture 추가 * test: filter support 클랠스 추가 * test: filter support 클래스 적용 * refactor: PublicClaim 변환 책임 변경 * test: PathResolver, CurrentMemberArgumentResovler테스트 코드 추가 * fix: 모든 쿠키 secure 적용되도록 변경 * refactor: 클래스 명 변경 * refactor: webConfig Path 매핑 클래스 추가 * feat: healthcheck path 추가 * Revert "feat: healthCheck path 추가 (#66)" (#71) This reverts commit baf4703c010e0494032d6e62e4c3f44906cfccd6. * fix: config 최신화 (#72) * feat: 회원 엔티티 생성 및 테스트코드 추가 * feat: 카카오 OAuth 환경변수 추가 및 클래스 바인딩 * feat: authorization code를 받기 위한 queryString generator 추가 * feat: Authorization code의 parameter 만드는 로직 분리 및 테스트 코드 추가 * feat: 회원 가입/로그인 요청 api 및 소셜 로그인 페이지 반환 * refactor: member관련 클래스 네이밍과 폴더 위치 변경 * refactor: 로그인 페이지 요청 방식 Resttemplate -> response (redirect)하도록 변경 * style: 코드 포맷 재적용 및 사용하지 않는 클래스 삭제 * chore: config 파일 업데이트 * refactor: 테스트 코드 추가 및 코드 포맷 재적용 * refactor: 사용하지 않는 코드 제거 * refactor: CRLF -> LF로 변경 * fix: config 커밋, config 최근 커밋으로 변경 * feat: 테스트 코드 추가 및 패키지 구조 변경 * refactor: revert merge * fix: merge confilt해결 및 예외처리 추가 * test: oauth properties가 없을 때의 테스트코드 추가 * feat: 코드리뷰에 따른 기능 분리 및 테스트 코드 변경 * fix: 테스트코드 관련 code smell 제거 * feat: Authorization grant 받기 예외 코드 및 테스트 코드 추가 * feat: Authorization Token 요청 및 반환 코드, 에러 반환 테스트 코드 추가 * refactor: AuthenticationService에서 서버에 요청보내는 로직 OAuth2AuthorizationServerRequestService로 분리 * test: 로그인 요청 테스트 코드 추가 * feat: 토큰 발급 요청 기능 테스트 코드 추가 및 RestTemplate 필드변수로 변경 * test: restTemplate 및 서비스 테스트 추가 * refactor: 에러 메세지 이름 변경 * refacotr: 변수명 및 entity default 명 변경 * feat: 토큰 정보 조회 기능 및 테스트 추가 * feat: 사용자 토큰 정보 조회 및 테스트 코드 & Resttemplate 테크트 코드 변경 * fix: encoding, formatting, tab 문제로 인한 파일 삭제 후 다시 작성 * feat: JWT 토큰 제공 서비스 및 테스트 코드 추가 * feat: 토큰 인증 코드 및 테스트 코드 작성 * feat: 로그인 및 회원가입 기능 추가 - 회원의 socialId string -> long으로 변경 * feat: 회원 로그인 테스트 코드 추가 * chore: 코드 포메팅 재 설정 * feat: config 파일 업데이트 * feat: Window용 포트 redis 포트 변경 추가 * refacotr: develop 업데이트 사항 merge * refactor: develop 업데이트 부분 merge * fix: TimeConfig 삭제 및 코드 스멜 변경 * refactor: 코르리뷰 반영 * chore: submodule update * feat: 메서드 파싱 customizing 및 @CurrentMember AuthorizationMember 를 파라미터로 감지하는 조건 추가 * feat: 인가회원에 대한 객체 ThreadLocalMap에 저장하는 기능 추가 * fix: 회원 정보 Optional 정보 조회 버그 fix, socialId requiredNotNull추가 등 에러 수정 * feat: API요청 Path 및 인증에 따른 filter 추가 - PathFilter: PathResolver, WebConfig - AuthorizationFilter:AuthorizationService, JwtAuthenticationService, JwtProviderService, MemberService - Member info: CurrentMember, AuthorizationMember, LoginResponse, MemberMapper, CurrentMember, PublicClaim, CurrentMemberArgumentResolver * test: CurrentMember 테스트 support 추가 * test: authorizationfilter 및 pathfilter 테스트 추가 * test: 회원 repostiory 및 fixture 추가 * test: filter support 클랠스 추가 * test: filter support 클래스 적용 * refactor: PublicClaim 변환 책임 변경 * test: PathResolver, CurrentMemberArgumentResovler테스트 코드 추가 * fix: 모든 쿠키 secure 적용되도록 변경 * refactor: 클래스 명 변경 * refactor: webConfig Path 매핑 클래스 추가 * feat: healthcheck path 추가 * fix: config 변경 * refactor: merge 변경 * refactor: 패키지 분리 (#73) * refactor: 방 관련 서비스 분리 (#79) * chore: gitignore 추가 * refactor: Room 관련 Service 분리 * feat: 방장 위임 기능 구현 (#81) * feat: 방장 위임 기능 구현 * test: 방장 위임 기능 테스트 작성 * test: 방장이 아닌 유저의 요청인 경우 추가 * feat: 오늘 보상 벌레 조회 기능 구현 (#80) * feat: 오늘 얻은 벌레 조회 API 구현 * refactor: 쿼리 1번으로 수정 * feat: @CurrentMember 적용 * test: 벌레 조회 Controller 통합 테스트 * chore: 주석 제거 * test: 오늘 보상 벌레 조회 Controller 테스트 * test: memberService mock 처리 * chore: enum 비교 equals로 변경 * refactor: 쿠폰 및 알림 Authorization Member 적용 리팩터링 (#82) * refactor: Coupon에 Authorization Member 적용 * test: Authorization Member 적용된 코드 테스트 * fix: 인프라 ci/cd 버그 해결 (#84) * feat: nginx conf 수정 및 분리 * feat: 쉘 스크립트 파일 추가 * feat: docker-compose nginx volume 수정 * feat: ci, cd 파일 수정 * feat: dev 서버 프론트 * chore: config 업데이트 * chore: code smell 제거 * hotfix: env 파일 생성 코드 수정 * hotfix: env 파일 생성 코드 수정 * hotfix: 파일 cd 코드 수정 * hotfix: 파일 cd 코드 수정 * feat: 방 추방 기능 구현 (#86) * feat: 방장 위임 기능 구현 * test: 방장 위임 기능 테스트 작성 * test: 방장이 아닌 유저의 요청인 경우 추가 * feat: participant deletedAt null일때 찾도록 추가 * feat: 방 추방 기능 구현 * test: 방 추방 통합 테스트 구현 * refactor: nginx conf 수정 * refactor: nginx conf 추가 수정 * refactor: nginx conf * chore: config 업데이트 * refactor: 아이템/상품 컨트롤러 테스트 리팩토링 및 @CurrentMember 적용 (#87) * refactor: 아이템 컨트롤러에 @CurrentMember 적용 * refactor: 아이템 컨트롤러 통합 테스트로 변경 * refactor: 상품 컨트롤러 통합 테스트로 변경 * test: 성공 테스트 추가 * hotfix: kakao path 제외 추가 * refactor: 방 컨트롤러 @CurrentMember 적용, 통합 테스트 리팩토링 (#89) * feat: 방장 위임 기능 구현 * test: 방장 위임 기능 테스트 작성 * test: 방장이 아닌 유저의 요청인 경우 추가 * feat: participant deletedAt null일때 찾도록 추가 * feat: 방 추방 기능 구현 * test: 방 추방 통합 테스트 구현 * refactor: nginx conf 수정 * refactor: nginx conf 추가 수정 * refactor: BugSearchRepository 위치 변경 * refactor: RoomController @CurrentMember 적용 * refactor: 메서드명 변경 * refactor: 테스트 코드 리팩토링 * refactor: 테스트 패키지 구조 변경 * style: 쿠폰 및 알림 테스트 패키지 분리 (#90) * feat: 토큰 redis 저장 (#91) * feat: 토큰 redis 저장을 위한 dto 및 config 추가 * feat: webConfig 파일 추가 * feat: redis 토큰 저장 서비스 및 테스트 코드 추가 * feat: 에러시 모든 토큰 제거 추가 * refactor: config update * feat: config 추가 * refactor: code smell 제거 * feat: 로그아웃 기능 추가 (#94) * feat: 토큰 redis 저장을 위한 dto 및 config 추가 * feat: webConfig 파일 추가 * feat: redis 토큰 저장 서비스 및 테스트 코드 추가 * feat: 에러시 모든 토큰 제거 추가 * refactor: config update * feat: config 추가 * refactor: code smell 제거 * feat: logout 기능 추가 * refactor: null 예외 처리 변경 * refactor: config 수정 * refactor: merge confilt 수정 * refacotr: code smell 로직 변경 * fix: config 수정 (#98) * feat: 참여중인 방 목록 조회 (#95) * feat: 참여중인 방 목록 조회 기능 구현 * feat: 관련 Repository 구현 * test: 참여중인 방 목록 조회 테스트 작성 * hotfix: redis config 파일 수정 * refactor: 벌레 컨트롤러 및 테스트 패키지 구조 변경 (#97) * refactor: 벌레 상품 조회 API URL 변경 * style: 테스트 패키지 구조 변경 * feat: 아이템 목록 조회 Response에 현재 적용된 아이템 속성 추가 (#100) * feat: 아이템 목록 조회 시 defaultItemId 속성 추가 * test: default 아이템 속성 추가 반영 * style: TodayBugResponse 패키지 위치 변경 * feat: 방 참여 기록 조회 기능 구현 (#101) * feat: 참여중인 방 목록 조회 기능 구현 * feat: 관련 Repository 구현 * test: 참여중인 방 목록 조회 테스트 작성 * refactor: 방 나가기 flush() 수정 * feat: 방 참여 기록 조회 컨트롤러 dto 구현 * feat: 방 참여 기록 조회 기능 구현 * test: 방 참여 기록 조회 서비스 테스트 * test: 방 참여 기록 조회 통합 테스트 * chore: 사용하지 않는 코드 제거 --------- Co-authored-by: ymkim97 * feat: profile 환경에 따른 cookie 설정 분리 및 config 업데이트 (#102) * feat: profile 환경에 따른 cookie 설정 분리 및 config 업데이트 * test: profile에 따른 쿠키 생성 테스트 * hotfix: config update * refactor: 쿠폰, 알림 및 토큰 패키지 및 클래스명 변경 (#105) * refactor: 쿠폰 및 토큰 패키지 및 클래스명 변경 * refactor: 알림 패키지 및 클래스명 변경, Fcm 로직 분리 * feat: 벌레 상품 구매 기능 구현 (#107) * feat: 결제 엔티티 생성 * feat: 벌레 상품 구매 API 구현 * test: 벌레 상품 구매 통합 테스트 * test: 벌레 상품 구매 서비스 테스트 * test: 결제 쿠폰 적용 테스트 * test: 주문 생성 및 금액 할인 테스트 * test: 벌레 사용 및 증가 로직 검증 방식 수정 * chore: config 업데이트 * fix: 상품 구매 Response에 주문 id 제거 * feat: 상품 구매 Response에 결제 id 추가 * fix: Transactional 적용 * feat: 방 전체 목록 조회 기능 구현 (#109) * feat: 방 전체 목록 조회 컨트롤러 추가 * refactor: 방장 member 반환 기능 삭제 * feat: 방 검색 dto 추가 * feat: 방 전체 조회 기능 구현 * fix: 서비스, 컨트롤러 수정 * test: 서비스 단위 테스트 작성 * test: 통합 테스트 작성 * fix: 피연산자 Long으로 수정 * feat: 결제 요청 기능 구현 (#113) * style: 메서드 네이밍 수정 * feat: 결제 요청 전 대기 상태 추가 * feat: 결제 요청 API 구현 * fix: Valid 어노테이션 추가 * test: 결제 요청 통합 테스트 * test: 결제 요청 서비스 테스트 * test: 결제/주문 유닛 테스트 * feat: 쿠폰 발급 요청 기능 구현 (#114) * refactor: 쿠폰 및 토큰 패키지 및 클래스명 변경 * refactor: 알림 패키지 및 클래스명 변경, Fcm 로직 분리 * feat: 쿠폰 발급 요청 기능 구현 * test: 쿠폰 발급 요청 기능 테스트 * test: Syntax 에러로 쿠폰 발급 관련 테스트 임시 Disabled 처리 * fix: Redis Yaml 추가 설정 * test: 중복 저장에 대한 테스트 코드 추가 * refactor: SystemClockHolder -> ClockHolder 변경 * feat: 방 상세 정보 조회 기능 추가 변경 (#117) * feat: 방 전체 목록 조회 컨트롤러 추가 * refactor: 방장 member 반환 기능 삭제 * feat: 방 검색 dto 추가 * feat: 방 전체 조회 기능 구현 * fix: 서비스, 컨트롤러 수정 * test: 서비스 단위 테스트 작성 * test: 통합 테스트 작성 * fix: 피연산자 Long으로 수정 * feat: 방 상세 목록 조회 날짜별 조회로 기능 추가 - 방이 인증된 날짜들은 조회하는 유저의 날짜에서 일주일 전까지 가져옴 * refactor: 사용자의 찌르기 확인 기능 수정 * feat: 사용자별 콕찌르기 여부 확인 추가 * feat: Response에 요청자의 memberId 추가 * feat: 카카오 로그인 API(/members/login/kakao/oauth) Get -> Post 변경 (#118) * feat: profile 환경에 따른 cookie 설정 분리 및 config 업데이트 * test: profile에 따른 쿠키 생성 테스트 * feat: Get에서 Post로 변경 * refactor: CookieUtils 변경 * feat: config 변경 * fix: merge confilt 해결 * feat: Cookie secure 추가 * feat: 방 검색 기능 구현 (#121) * feat: 검색 Native Query 작성 * feat: 방 검색 서비스 기능 구현 * test: 방 검색 서비스 테스트 * feat: 방 검색 컨트롤러 구현 * test: 방 컨트롤러 통합 테스트 구현 * refactor: 파라미터 타입 통일화 * refactor: controller 타입 수정 * style: 쿠폰 및 노션 메서드, 변수, 클래스명 변경 (#122) * feat: 방 수정 전 정보 불러오기 기능 구현 (#128) * fix: 사용자의 인증 후 인증 수 증가 추가 * feat: 컨트롤러 추가 * feat: 서비스, Dto, Mapper 작성 * test: 통합 테스트 작성 * refactor: 사용하지 않는 API 제거 및 코드 스타일 수정 (#129) * refactor: 결제 금액 컬럼 Payment로 이동 * chore: 사용하지 않는 API 제거 * feat : 회원 삭제 기능 추가 (#131) * feat: 토큰 redis 저장을 위한 dto 및 config 추가 * feat: webConfig 파일 추가 * feat: redis 토큰 저장 서비스 및 테스트 코드 추가 * feat: 에러시 모든 토큰 제거 추가 * refactor: config update * feat: config 추가 * refactor: code smell 제거 * feat: logout 기능 추가 * refactor: 사용자 nickname 생성 및 랜덤 삭제 ID부여 제공 * refacotr: @Transaction제거, redis를 사용하기 때문에 트랜잭션 전파 불필요 * feat: 삭제 요청 추가 * refactor: member mapper 메서드 위치 변경 AuthMapper -> MemberMapper * refacotr: 패키지 위치 변경 및 socialId long->String * feat: 회원탈퇴 요청 기능 추가 * fix: restTemplate 요청 반환 값 변경 * feat: 회원 탈퇴 요청에 대한 api 추가 * test: 회원 삭제 테스트 추가 * test: 회원 탈퇴 테스트 코드 및 Auth테스트와 member테스트 분리 * feat: 회원 탈퇴 서비스 기능 구현 및 restTemplate요청 테스트 추가 * feat: 사용하지 않는 메서드 및 회원 조회 쿼리 생성 * test: 테스트 코드 수정 및 test config 변경 * feat: WebConfig path 수정 * feat: 삭제할 회원 조건 변경 * refacotr: 테스트 로그인 get 메서드 uri변경 및 AuthorizationMember -> AuthMember / CurrentMember -> Auth * refactor: merge develop * fix: findMemberWithNotManager 메서드 명 findMemberNotManager 변경 * feat: Fcm Token 저장 기능 구현 (#132) * feat: FCM Token 저장 기능 구현 * feat: FCM Token 저장 기능 테스트 * refactor: Knock, Fcm 분리 및 메서드명 변경 * style: Coupon 메서드명 변경 * refactor: Fcm Token null, blank 처리 * docs: 쿠폰 저장에 대한 RestDoc 추가 * refactor: AuthMember 적용 및 테스트 코드 수정 * fix: submodule config 변경 * feat: CouponWallet 엔티티 & 레포지토리 구현 (#134) * refactor : 쿠폰 발행 기간 하루로 변경 및 쿠폰 정보 오픈 날짜 컬럼 추가 (#136) * style : Schedule 어노테이션 위치 변경 * refactor: 쿠폰 발행 기간 하루로 통일 및 쿠폰 정보 오픈 날짜 추가 * refactor: Sub module Update * refactor: 회원 탈퇴 로직 변경 (#139) * feat: 토큰 redis 저장을 위한 dto 및 config 추가 * feat: webConfig 파일 추가 * feat: redis 토큰 저장 서비스 및 테스트 코드 추가 * feat: 에러시 모든 토큰 제거 추가 * refactor: config update * feat: config 추가 * refactor: code smell 제거 * feat: logout 기능 추가 * refactor: 사용자 nickname 생성 및 랜덤 삭제 ID부여 제공 * refacotr: @Transaction제거, redis를 사용하기 때문에 트랜잭션 전파 불필요 * feat: 삭제 요청 추가 * refactor: member mapper 메서드 위치 변경 AuthMapper -> MemberMapper * refacotr: 패키지 위치 변경 및 socialId long->String * feat: 회원탈퇴 요청 기능 추가 * fix: restTemplate 요청 반환 값 변경 * feat: 회원 탈퇴 요청에 대한 api 추가 * test: 회원 삭제 테스트 추가 * test: 회원 탈퇴 테스트 코드 및 Auth테스트와 member테스트 분리 * feat: 회원 탈퇴 서비스 기능 구현 및 restTemplate요청 테스트 추가 * feat: 사용하지 않는 메서드 및 회원 조회 쿼리 생성 * test: 테스트 코드 수정 및 test config 변경 * feat: WebConfig path 수정 * feat: 삭제할 회원 조건 변경 * refacotr: 테스트 로그인 get 메서드 uri변경 및 AuthorizationMember -> AuthMember / CurrentMember -> Auth * refactor: merge develop * fix: findMemberWithNotManager 메서드 명 findMemberNotManager 변경 * refactor: 회원 탈퇴 로직 변경 * feat: 벌레 상품 구매 시 CouponWallet 적용 (#141) * feat: 벌레 상품 구매 시 couponWallet 검증 로직 적용 * fix: couponWalletId를 받도록 수정 * test: couponWallet 적용 테스트 * chore: 불필요한 fixture 제거 * fix: 보유한 쿠폰 조회 시 fetch join 적용 * test: 쿠폰 지갑 레포지토리 테스트 * chore: 사용하지 않는 메서드 제거 * feature: 회원 정보 조회 기능 추가 (#142) * feat: 새 스킨 조회 기능 및 테스트 코드 추가 * chore: jpa관련 config 설정 - 버전 호환오류로 인한 기본 Template설정 * feat: 기본 새 스킨 조회 query 추가 * feat: 회원과 벌레에 대한 조회 쿼리 및 테스트 코드 추가 * feat: 회원 정보 조회 기능 및 테스트 코드 추가 * refactor: 회원과 Item 서비스의 의존성 순환을 피하기 위해 inventorySearchService 생성 * refactor: 회원과 Item 서비스의 의존성 순환을 피하기 위해 inventorySearchService 생성 * feat: 회원 정보 조회 API 추가 * style: 메서드 접근 제어자에 따른 순서 변경 * refactor: inventorySearchService 제거 후 memberService에서 repository 추가 * refactor: transform에서 stream으로 동작 변경 * style: 리뷰 반영 * refactor: nginx 설정 파일 리포맷팅 * hotfix: CorsFilter 추가 * refactor: 방/루틴 전체 리팩토링 (#143) * feat: ClockHolder LocalDate 추가 * refactor: RoomService 리팩토링 * refactor: SearchService 리팩토링 * refactor: 방 입장, 퇴장 리팩토링 * refactor: CertifiactionService 리팩토링 * refactor: RoomController 리팩토링 * test: InventorySearchRepository 테스트 추가 * refactor: merge 메서드 네이밍 * refactor: ParticipantMapper 코드리뷰 반영 * feat: 쿠폰 발급 요청 및 대기열 사용자 쿠폰 발급 처리 구현 (#146) * style : Schedule 어노테이션 위치 변경 * refactor: 쿠폰 발행 기간 하루로 통일 및 쿠폰 정보 오픈 날짜 추가 * feat: 쿠폰 발행 가능 날짜 중복 체크 기능 추가 * refactor: Builder 삭제 * test: 쿠폰 관련 테스트 수정 * feat: 쿠폰 발행 관련 레포지토리 기능 구현 및 테스트 * test: 쿠폰 발행 관련 문자열 레디스 기능 구현 및 테스트 * feat: 쿠폰 발행 관련 ZSET 레디스 기능 구현 및 테스트 * test: 쿠폰 발행 컨트롤러 기능 테스트 * test: RestDoc 업데이트 * test: Github Actions 시, Redis ZSET 명령어 못찾는 테스트 Disable * refactor: 쿠폰, 알림 테스트 접근 제어자, 메서드명, 클래스명 변경 (#148) * style : Schedule 어노테이션 위치 변경 * refactor: 쿠폰 발행 기간 하루로 통일 및 쿠폰 정보 오픈 날짜 추가 * feat: 쿠폰 발행 가능 날짜 중복 체크 기능 추가 * refactor: Builder 삭제 * test: 쿠폰 관련 테스트 수정 * feat: 쿠폰 발행 관련 레포지토리 기능 구현 및 테스트 * test: 쿠폰 발행 관련 문자열 레디스 기능 구현 및 테스트 * feat: 쿠폰 발행 관련 ZSET 레디스 기능 구현 및 테스트 * test: 쿠폰 발행 컨트롤러 기능 테스트 * test: RestDoc 업데이트 * test: Github Actions 시, Redis ZSET 명령어 못찾는 테스트 Disable * refactor: 알림 및 쿠폰 테스트 코드 메서드명 변경 및 알림 콕 알림 키 변경 * refactor: LocalDate 코드 리뷰 반영 * feat: 쿠폰 보관함 조회 기능 구현 (#149) * style : Schedule 어노테이션 위치 변경 * refactor: 쿠폰 발행 기간 하루로 통일 및 쿠폰 정보 오픈 날짜 추가 * feat: 쿠폰 발행 가능 날짜 중복 체크 기능 추가 * refactor: Builder 삭제 * test: 쿠폰 관련 테스트 수정 * feat: 쿠폰 발행 관련 레포지토리 기능 구현 및 테스트 * test: 쿠폰 발행 관련 문자열 레디스 기능 구현 및 테스트 * feat: 쿠폰 발행 관련 ZSET 레디스 기능 구현 및 테스트 * test: 쿠폰 발행 컨트롤러 기능 테스트 * test: RestDoc 업데이트 * test: Github Actions 시, Redis ZSET 명령어 못찾는 테스트 Disable * refactor: 알림 및 쿠폰 테스트 코드 메서드명 변경 및 알림 콕 알림 키 변경 * feat: 쿠폰함 조회 서비스 기능 구현 및 테스트 * feat: 쿠폰 보관함 저장소 조회 기능 구현 및 테스트 * feat: 쿠폰 보관함 조회 기능 구현 및 테스트 * fix: temporal 에러 해결 * refactor: Stream 코드 리뷰 반영 * feat: 회원 정보 수정 API 추가 (#151) * fix: cors api 요청 위치 변경 * feat: 회원 수정 기능 추가 * feat: 회원 정보 수정 API 및 테스트 코드 추가 * feat: 회원 정보 수정 APi 추가 및 테스트 코드 추가 * refactor: 리뷰 코드 반영 - 일시적 사용하지 않는 코드 제거 - 회원 null값에 대한 예외 Objects로 변경 - ErrorMessage 변경 - 테스트 코드 CsvSource null값 적용 * refactor: null체크 메서드 변경 및 에러 메시지 어순 변경 * feat: 결제 승인 기능 구현 (#154) * feat: order_id 컬럼 인덱스 설정 * chore: webflux 의존성 추가 * feat: 토스 결제 위젯 승인 API 연동 * feat: 결제 승인 API 구현 * feat: 결제 테이블에 couponWalletId 컬럼 추가 * test: 결제 승인 통합 테스트 * feat: 벌레 상품 구매 시 couponWallet 검증 로직 적용 * fix: couponWalletId를 받도록 수정 * test: couponWallet 적용 테스트 * chore: 불필요한 fixture 제거 * feat: 결제 승인 시 쿠폰 차감 및 벌레 충전 로직 추가 * fix: 쿠폰이 적용된 경우 분기 처리 * chore: config 업데이트 * test: 결제 승인 컨트롤러 통합 테스트 * test: 결제 승인 서비스 테스트 * chore: MockWebServer 의존성 추가 * test: 토스 결제 승인 API 테스트 * fix: checkStyle 오류 수정 * chore: config 업데이트 * refactor: 결제 테이블 coupon_id 컬럼을 discount_amount로 변경 * refactor: 공통 메서드 분리 * feat: 벌레 충전 시 벌레 내역 저장 로직 추가 * style: 중복 메서드 제거 * feat: 벌레 내역 조회 기능 구현 (#155) * feat: 벌레 내역 조회 API 구현 * refactor: 결제 테이블 coupon_id 컬럼을 discount_amount로 변경 * test: 벌레 내역 조회 컨트롤러 통합 테스트 * fix: 테스트 오류 수정 * chore: 사용하지 않는 메서드 제거 * refactor: Response 분리 * style: 줄바꿈 제거 * feat: 방 인증, 입장 동시성 처리 (#157) * feat: ClockHolder LocalDate 추가 * refactor: RoomService 리팩토링 * refactor: SearchService 리팩토링 * refactor: 방 입장, 퇴장 리팩토링 * refactor: CertifiactionService 리팩토링 * refactor: RoomController 리팩토링 * test: InventorySearchRepository 테스트 추가 * chore: 테스트 코드 In-memory H2에서 MySQL로 변경 * feat: CertifyRoom Transaction 분리, 비관적 락 적용 * feat: 방 입장 낙관적 락 적용 * refactor: MySQL 변경으로 일부 테스트 수정 * test: 방 인증, 입장 동시성 테스트 작성 * test: 방장 위임 테스트 작성 * fix: 방 입장 낙관적 락 -> 비관적 락으로 변경 * refactor: Room version 삭제 * fix: 코드 수정 * feat: Image Type 추가 --------- Co-authored-by: Dev Uni * hotfix: develop-cd docker 추가 * feat: 방/회원/인증 신고 기능 추가 (#158) * test: 삭제된 회원 조회 테스트 추가 * refactor: 회원 조회 변경 * feat: 신고 기능 추가 및 테스트 코드 추가 * refactor: 신고 기능 로직 수정 및 테스트 코드 추가 * feat: 신고 api 기능 추가 및 테스트 코드 추가 * fix: 통합 테스트간 데이터 중복 및 index 문제 해결 * refactor: CsvSource null 부분 변경 * hotfix: config 업데이트 * feat: 쿠폰 사용 기능 구현 (#160) * Merge branch 'develop' into feature/#75-use-coupon * feat: 쿠폰 지갑에서 특정 회원의 특정 쿠폰 조회 기능 구현 및 테스트 * feat: 쿠폰 지갑에 있는 쿠폰 사용하는 서비스 기능 구현 및 테스트 * feat: 쿠폰 사용 API 기능 구현 및 테스트 * fix: 테스트 코드 에러 수정 * test: RestDoc 업데이트 * refactor : 결제 쿠폰 사용 통합 * Submodule update * test: 테스트 커버리지 추가 * feat: 미참여자의 방 정보 조회 기능 (#161) * feat: ClockHolder LocalDate 추가 * refactor: RoomService 리팩토링 * refactor: SearchService 리팩토링 * refactor: 방 입장, 퇴장 리팩토링 * refactor: CertifiactionService 리팩토링 * refactor: RoomController 리팩토링 * test: InventorySearchRepository 테스트 추가 * chore: 테스트 코드 In-memory H2에서 MySQL로 변경 * feat: CertifyRoom Transaction 분리, 비관적 락 적용 * feat: 방 입장 낙관적 락 적용 * refactor: MySQL 변경으로 일부 테스트 수정 * test: 방 인증, 입장 동시성 테스트 작성 * test: 방장 위임 테스트 작성 * fix: 방 입장 낙관적 락 -> 비관적 락으로 변경 * feat: 방 참여 여부 확인, 참여 중이지 않은 방 정보 부르기 컨트롤러 * feat: 방 참여 여부 확인 서비스 추가 * feat: 참여중이지 않은 방 정보 조회 서비스 * test: 통합 테스트 코드 작성 * test: 테스트 코드 보완 * fix: memberId 가져오기로 변경 * refactor: redirection -> boolean으로 변경 * fix: Search 쿼리 수정 --------- Co-authored-by: Dev Uni * fix: noskin image 변경 (#162) * fix: 회원 로그인 시 기본 부엉이, 오목눈이 생성 기능 추가 및 테스트 코드 변경 * fix: 테스트 코드 변경 * refacotr: config 수정 * feat: 벌레 보상/충전/사용 시 내역 저장 로직 추가 (#165) * refactor: 벌레 사용 + 벌레 내역 저장 로직 하나의 메서드로 분리 * refactor: 벌레 보상 + 벌레 내역 저장 로직 하나의 메서드로 분리 * test: 아이템 서비스 테스트 수정 * test: BugService Mock 추가 * test: 벌레 사용/충전/보상 서비스 테스트 * refactor: 쿠폰 사용 + 벌레 내역 저장 로직 하나의 메서드로 분리 * fix: 불필요한 Mock 제거 * feat: 아이템 이미지 버전 추가 및 방 배경 업데이트 (#167) * refactor: 아이템 테이블에 awakeImage, sleepImage 컬럼 추가 * feat: 방 레벨업 시 이미지 업데이트 로직 추가 * chore: 코드 제거 * test: 테스트 검증 수정 * chore: 이미지 URL에 작은 따옴표 제거 * fix: no skin image 버그 해결 (#168) * fix: 회원 로그인 시 기본 부엉이, 오목눈이 생성 기능 추가 및 테스트 코드 변경 * fix: 테스트 코드 변경 * refacotr: config 수정 * test: @BeforeAll Transaction적용 실패로 인한 merge 테스트 추가 * feat: 서비스 추가 * test: 기본 URL 변경 및 테스트 코드 수정 * style: 중복 코드 제거 * hotfix: schema, item 등록 * hotfix: config 수정 * hotfix: sql 업데이트 * hotfix: item inventory 쿼리 수정 * hotfix: config admin key 업데이트 * hotfix: config sql init none * hotfix: config sql init never * refactor: 실시간 선착순 쿠폰 발급 기능 리팩터링 (#169) * refactor: ZSET popMin -> range로 변경 * refactor: 쿠폰 관리 저장소 popMin -> range로 변경 * feat: 쿠폰 발급 결과 FCM 알림 전송 기능 구현 및 테스트 * feat: ZSET size 반환 기능 구현 및 테스트 * feat: 쿠폰 대기열 사이즈를 반환하는 기능 구현 및 테스트 * test: 테스트 코드 체크 스타일 수정 * fix: Import 에러 해결 * refactor: 쿠폰 발급 현재 위치 기록 변경 * refactor: 쿠폰 대기열 크기 조회 기능 삭제 * refactor: addIfAbsent 기능 수정 * test: 레디스 SORTED SET 명령어 테스트 Disabled * refactor: 쿠폰 발급 및 발행 기능 수정 * test: 쿠폰 랭킹 조회 기능 테스트 추가 * fix: Base64관련 디코딩 코드 변경 -> Base64Url (#173) * fix: Base64관련 디코딩 코드 변경 -> Base64Url * refactor: 쿠폰 스케쥴 업데이트 및 config 수정 * style: 문자열 checkstyle 수정 * hotfix: sql init 방식 변경 * hotfix: docker-compose mysql * hotfix: docker-compose mysql * fix: 방장 자신에 대한 추방 버그 (#177) * fix: 방장 자신 추방 못하도록 validate 추가 * feature: 방 수정 전 정보 불러오기에 방장 ID 추가 * test: 테스트 코드 작성 * fix: 방 참여 기록 조회 최신순으로 변경 * Fix/#175 fix member delete error (#178) * fix: Base64관련 디코딩 코드 변경 -> Base64Url * refactor: 쿠폰 스케쥴 업데이트 및 config 수정 * style: 문자열 checkstyle 수정 * fix: 회원 탈퇴시 방 참여에 대한 문제 해결 * refactor: config update * test: 신고 실패에 대한 테스트 코드 변경 * refactor: 쿠폰, 알림 코드 개선 (#180) * refactor: coupon 발행 및 삭제 스타일 변경 * refactor: My Coupon 조회 코드 개선 * refactor: 쿠폰 등록, 사용 코드 개선 * refactor: FCM 및 알림 코드 개선 * fix: 아이템 선택 시 멤버 기본 스킨 이미지 업데이트 (#182) * style: FCM Token Log 생성 (#183) * refactor: coupon 발행 및 삭제 스타일 변경 * refactor: My Coupon 조회 코드 개선 * refactor: 쿠폰 등록, 사용 코드 개선 * refactor: FCM 및 알림 코드 개선 * style: fcm token log * fix: 방장 방 나가기 코드 수정 (#184) * hotfix: 인증 방식 변경 * hotfix: 방장 방 삭제 버그 * fix: fcm 토큰 삭제 기능 추가 (#185) * fix: Base64관련 디코딩 코드 변경 -> Base64Url * refactor: 쿠폰 스케쥴 업데이트 및 config 수정 * style: 문자열 checkstyle 수정 * fix: 회원 탈퇴시 방 참여에 대한 문제 해결 * refactor: config update * test: 신고 실패에 대한 테스트 코드 변경 * feat: fcm 토큰 제거 기능 추가 * style: 필요없는 로그 제거 * fix: 참여자 업데이트 * fix: 토스 결제 승인 실패 시 예외 처리 (#188) * fix: 토스 결제 승인 실패 시 예외 throw * test: 결제 승인 로직 변경에 따른 테스트 수정 * fix: 토스 승인 API 요청 시 Basic 인증 헤더로 변경 * fix: ModelAttribute로 방식 변경 (#193) * fix: 토스 결제 승인 성공/실패 시 결과 반영 안되는 이슈 해결 (#194) * fix: 결제 정보 검증 및 토스 결제 승인 API 로직 트랜잭션 분리 * test: 로직 변경에 따른 테스트 수정 * feat: ranking system 구현 (#189) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 --------- Co-authored-by: Kim Heebin * fix: record를 class로 바꿔서 바인딩 해결 (#195) * fix: ModelAttribute로 방식 변경 * fix: record를 class로 바꾸고 바인딩 해결 * fix: approvedAt 제거 (#197) * fix: 벌레 0마리인 경우 내역 저장되지 않도록 수정 (#199) * chore: 결제 실패 처리 로직에 Transactional 적용 * refactor: 방 상세 정보에 방 생성 날짜시간 추가 (#201) * fix: ObjectMapper 수정 (#202) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 --------- Co-authored-by: Kim Heebin * refactor: 알림 메시지 형식 변경 (#203) * refactor: 푸시 알림 메시지 Body 변경 * refactor: FCM 알림 형식 변경 * fix: ObjectMapper 삭제 실패 수정 (#204) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 * fix: objectMapper 삭제 추가 --------- Co-authored-by: Kim Heebin * hotfix: 알림 메시지 내용 변경 및 item-data 쿼리 추가 * refactor: infra 디렉토리 생성 및 리팩터링 (#206) * refactor: infra 디렉토리 생성 및 리팩터링 * fix: 초기 아이템 데이터 이미지 링크 수정 * refactor: infra 디렉토리 생성 및 리팩터링 (#207) * refactor: infra 디렉토리 생성 및 리팩터링 * fix: 초기 아이템 데이터 이미지 링크 수정 * fix: DockerFile 경로 수정 * refactor: infra 디렉토리 생성 및 리팩터링 (#208) * refactor: infra 디렉토리 생성 및 리팩터링 * fix: 초기 아이템 데이터 이미지 링크 수정 * fix: DockerFile 경로 수정 * fix: 쉘 스크립트 경로 수정 * feat: nginx 로깅 추가 * feat: actuator 외부 차단 * hotfix: Dockerfile copy 수정 * hotfix: deploy-cd Dockerfile 경로 수정 * hotfix: deploy-cd 쉘 스크립트 수정 * hotfix: nginx 로깅 docker-compose 연결 * hotfix: String to Long Error 수정 * fix: MaxUploadSizeExceededException 예외 던지기 (#212) * fix: 0시 인증 방에서 인증이 안되는 버그 수정 (#213) * fix: 0시 인증타임 예외처리 수정 * test: 테스트 수정 * hotfix: 쿠폰 발급이 안되는 버그 수정 * hotfix: nginx client 파일 크기 제한 수정 * hotfix: 쿠폰큐 비어있을 시, 발생하는 버그 수정 * hotfix: 쿠폰 발급 횟수 버그 수정 * hotfix: 스웨거 도입 * hotfix: 스웨거 버그 수정 * feat: 예외 발생 시 슬랙 연동 구현 (#215) * chore: 기본 상점 상품 쿼리 수정 * chore: slack api client 의존성 추가 * feat: 예외 발생 시 슬랙 연동 구현 * chore: slack webhook url config 추가 * fix: build 오류 해결 * fix: 방 수정에서 루틴 수정 제외 (#217) * feat: admin login (#216) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 * fix: objectMapper 삭제 추가 * feat: 어드민 서비스 로그인 기능 추가 * refactor: 어드민 config 업데이트 * fix: test application.yml 수정 * test: stub에서의 타입 오류 해결 * style: 변수면 변경 --------- Co-authored-by: Kim Heebin * hotfix: mysql 테이블 init 업데이트 * hotfix: config 업데이트 * hotfix: 00시 인증 타임 수정 * refactor: 방 인증 기획 관련 수정 (#219) * refactor: 방 인증 시간 정각부터 10분까지로 수정 * refactor: 참여자 중 한명 이상이 인증 했을 시 방 시간 수정 못하게 변경 * test: 테스트 코드 작성 * fix: 인증된 참여자의 방 나가기 후 방 정보 불러오기 안되는 버그 해결 (#221) * fix: 인증하고 나간 참여자 정보 불러오기 * fix: 인증된 방이 삭제되지 않는 버그 수정 * hotfix: 쿠폰 메시지 수정 * fix: 방의 인증 시간에는 입장하지 못하도록 수정 (#223) * fix: Room soft delete로 변경 (#226) * fix: Room soft delete로 변경 * docs: mysql 수정 * fix: checkstyle * fix: 참여자 목록이 복사되는 버그 해결 (#228) * hotfix: distinct 추가 * fix: 기여도 버그 해결 (#230) * fix: admin token (#231) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 * fix: objectMapper 삭제 추가 * feat: 어드민 서비스 로그인 기능 추가 * refactor: 어드민 config 업데이트 * fix: test application.yml 수정 * test: stub에서의 타입 오류 해결 * style: 변수면 변경 * feat: 어드민과 일반 유저간 토큰 생성, 검증 분리 및 로그인 분리 * feat: 회원 인증시 뱃지 생성기능 추가 * refactor: config 수정 * refactor: 코딩 스타일 재적용 --------- Co-authored-by: Kim Heebin * fix: 인증율 하락 버그 수정 (#233) * refactor: 코드 정리 * fix: 인증율 하락 수정 * fix: admin token fix (#234) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 * fix: objectMapper 삭제 추가 * feat: 어드민 서비스 로그인 기능 추가 * refactor: 어드민 config 업데이트 * fix: test application.yml 수정 * test: stub에서의 타입 오류 해결 * style: 변수면 변경 * feat: 어드민과 일반 유저간 토큰 생성, 검증 분리 및 로그인 분리 * feat: 회원 인증시 뱃지 생성기능 추가 * refactor: config 수정 * refactor: 코딩 스타일 재적용 * fix: 도메인 변경 --------- Co-authored-by: Kim Heebin * hotfix: 서브 도메인 설정 오류 변경 * hotfix: 서브 도메인 관련 기능 rollback * feat: 에러 로그 슬랙 연동 구현 (#237) * chore: logback slack appender 라이브러리 의존성 추가 * feat: 로그 파일 작성 * fix: admin token fix (#235) * feat: 회원의 랭킹 redis에 추가 및 삭제, 업데이트 기능 추가 * test: 회원 정보 변경 및 삭제 추가에 따른 랭킹 참여, 제외 테스트 코드 추가 * feat: 랭킹시스템 API 추가 및 랭킹 조회 기능 추가 * feat: 랭킹 조회 테스트 코드 추가 및 랭킹 업데이트 로직 각 업데이트 -> 스케쥴러 * style: checkstyle 에러 fix * refactor: 응답 객체명 변경 TopRankingInfoResponse -> TopRankingInfo * fix: 랭킹 업데이트 시간 15분 매초마다 동작하는 방식 -> 15분에 한 번만 실행되도록 변경 * refactor: 랭킹 응답 반환 객체 변수면 s 제거 Co-authored-by: Kim Heebin * refactor: ToprankingResponses 응답 객체 반환명 TopRankingResponse로 변경 * fix: ObjectMapper에러 수정 * fix: objectMapper 삭제 추가 * feat: 어드민 서비스 로그인 기능 추가 * refactor: 어드민 config 업데이트 * fix: test application.yml 수정 * test: stub에서의 타입 오류 해결 * style: 변수면 변경 * feat: 어드민과 일반 유저간 토큰 생성, 검증 분리 및 로그인 분리 * feat: 회원 인증시 뱃지 생성기능 추가 * refactor: config 수정 * refactor: 코딩 스타일 재적용 * fix: 도메인 변경 * hotfix: 서버 도메인 변경 * feat: 로그인 쿠키 도메인 관련 SameSite를 None으로 변경 --------- Co-authored-by: Kim Heebin * hotfix: 서브 도메인 변경 * fix: date equals 적용 (#239) * feat: exception AOP 로그 추가 (#241) * feat: ExceptionHandler AOP 적용 * refactor: 수정 * refactor: checkstyle 적용 * refactor: 방, filter, aop 수정 (#243) * fix: 방 상세 페이지 버그 수정 * refactor: 필터, AOP 수정 * hotfix: date equals 적용 * fix: 회원 삭제에 대한 참여자 목록 조회 기능 변경 * feat: sql 수정 * refactor: 벳지 init sql 수정 * refactor: 방 이미지 정보 변경 * fix: 방의 exp 보내는 방법 변경 * hotfix: 회원 삭제 로직 변경 * hotfix: 랭킹 조회 쿼리 및 방 조회 수정 * feat: 운영서버 배포 구현 * fix: 운영서버 배포 cd 수정 * fix: log AOP 제거 및 SlackExceptionHandler 수정 * chore: config 업데이트 --------- Co-authored-by: Kim Heebin Co-authored-by: Youngmyung Kim <83266154+ymkim97@users.noreply.github.com> Co-authored-by: Park Seyeon Co-authored-by: 홍혁준 <31675711+HyuckJuneHong@users.noreply.github.com> Co-authored-by: ymkim97 Co-authored-by: HyuckJuneHong --- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/ci.yml | 88 +- .github/workflows/develop-cd.yml | 175 ++ .github/workflows/prod-cd.yml | 173 ++ .gitignore | 3 + build.gradle | 120 +- config/naver-intellij-formatter-custom.xml | 143 +- infra/Dockerfile | 8 + infra/docker-compose-dev.yml | 77 + infra/docker-compose-prod.yml | 56 + infra/mysql/initdb.d/init.sql | 259 +++ infra/mysql/initdb.d/item-data.sql | 48 + infra/nginx/conf.d/header.conf | 9 + infra/nginx/mime.types | 96 + infra/nginx/nginx.conf | 32 + infra/nginx/templates/http-server.template | 13 + infra/nginx/templates/ssl-server.template | 19 + infra/nginx/templates/upstream.template | 4 + infra/scripts/deploy-dev.sh | 119 ++ infra/scripts/deploy-prod.sh | 107 ++ infra/scripts/init-letsencrypt.sh | 86 + infra/scripts/init-nginx-converter.sh | 14 + src/docs/asciidoc/coupon.adoc | 112 ++ src/docs/asciidoc/index.adoc | 76 + src/docs/asciidoc/notification.adoc | 34 + .../com/moabam/MoabamServerApplication.java | 3 +- .../admin/application/admin/AdminMapper.java | 16 + .../admin/application/admin/AdminService.java | 57 + .../com/moabam/admin/domain/admin/Admin.java | 53 + .../admin/domain/admin/AdminRepository.java | 10 + .../presentation/admin/AdminController.java | 41 + .../auth/AuthorizationService.java | 239 +++ .../auth/JwtAuthenticationService.java | 59 + .../application/auth/JwtProviderService.java | 63 + ...uth2AuthorizationServerRequestService.java | 71 + .../application/auth/mapper/AuthMapper.java | 43 + .../auth/mapper/AuthorizationMapper.java | 48 + .../application/auth/mapper/PathMapper.java | 43 + .../moabam/api/application/bug/BugMapper.java | 83 + .../api/application/bug/BugService.java | 139 ++ .../coupon/CouponManageService.java | 101 ++ .../api/application/coupon/CouponMapper.java | 67 + .../api/application/coupon/CouponService.java | 147 ++ .../api/application/image/ImageService.java | 73 + .../api/application/item/ItemMapper.java | 44 + .../api/application/item/ItemService.java | 92 + .../api/application/member/BadgeService.java | 32 + .../api/application/member/MemberMapper.java | 118 ++ .../api/application/member/MemberService.java | 206 +++ .../notification/NotificationService.java | 99 + .../application/payment/PaymentMapper.java | 49 + .../application/payment/PaymentService.java | 74 + .../application/product/ProductMapper.java | 41 + .../application/ranking/RankingMapper.java | 39 + .../application/ranking/RankingService.java | 86 + .../api/application/report/ReportMapper.java | 23 + .../api/application/report/ReportService.java | 61 + .../room/CertificationService.java | 208 +++ .../api/application/room/RoomService.java | 240 +++ .../api/application/room/SearchService.java | 416 +++++ .../room/mapper/CertificationsMapper.java | 87 + .../room/mapper/ParticipantMapper.java | 29 + .../application/room/mapper/RoomMapper.java | 176 ++ .../room/mapper/RoutineMapper.java | 32 + .../auth/repository/TokenRepository.java | 43 + .../java/com/moabam/api/domain/bug/Bug.java | 88 + .../moabam/api/domain/bug/BugActionType.java | 10 + .../com/moabam/api/domain/bug/BugHistory.java | 71 + .../com/moabam/api/domain/bug/BugType.java | 12 + .../bug/repository/BugHistoryRepository.java | 9 + .../BugHistorySearchRepository.java | 38 + .../com/moabam/api/domain/coupon/Coupon.java | 100 ++ .../moabam/api/domain/coupon/CouponType.java | 60 + .../api/domain/coupon/CouponWallet.java | 44 + .../repository/CouponManageRepository.java | 83 + .../coupon/repository/CouponRepository.java | 21 + .../repository/CouponSearchRepository.java | 49 + .../repository/CouponWalletRepository.java | 12 + .../CouponWalletSearchRepository.java | 44 + .../moabam/api/domain/image/ImageName.java | 35 + .../moabam/api/domain/image/ImageResizer.java | 122 ++ .../moabam/api/domain/image/ImageSize.java | 17 + .../moabam/api/domain/image/ImageType.java | 9 + .../com/moabam/api/domain/image/NewImage.java | 67 + .../com/moabam/api/domain/item/Inventory.java | 66 + .../java/com/moabam/api/domain/item/Item.java | 119 ++ .../moabam/api/domain/item/ItemCategory.java | 6 + .../com/moabam/api/domain/item/ItemImage.java | 28 + .../com/moabam/api/domain/item/ItemType.java | 21 + .../item/repository/InventoryRepository.java | 9 + .../repository/InventorySearchRepository.java | 77 + .../item/repository/ItemRepository.java | 9 + .../item/repository/ItemSearchRepository.java | 39 + .../com/moabam/api/domain/member/Badge.java | 48 + .../moabam/api/domain/member/BadgeType.java | 41 + .../com/moabam/api/domain/member/Member.java | 178 ++ .../com/moabam/api/domain/member/Role.java | 8 + .../member/repository/BadgeRepository.java | 12 + .../member/repository/MemberRepository.java | 14 + .../repository/MemberSearchRepository.java | 88 + .../repository/NotificationRepository.java | 42 + .../com/moabam/api/domain/payment/Order.java | 31 + .../moabam/api/domain/payment/Payment.java | 146 ++ .../api/domain/payment/PaymentStatus.java | 19 + .../payment/repository/PaymentRepository.java | 9 + .../repository/PaymentSearchRepository.java | 27 + .../moabam/api/domain/product/Product.java | 73 + .../api/domain/product/ProductType.java | 6 + .../product/repository/ProductRepository.java | 13 + .../com/moabam/api/domain/report/Report.java | 56 + .../report/repository/ReportRepository.java | 9 + .../moabam/api/domain/room/Certification.java | 53 + .../domain/room/DailyMemberCertification.java | 49 + .../domain/room/DailyRoomCertification.java | 41 + .../moabam/api/domain/room/Participant.java | 80 + .../java/com/moabam/api/domain/room/Room.java | 196 ++ .../com/moabam/api/domain/room/RoomExp.java | 43 + .../com/moabam/api/domain/room/RoomType.java | 7 + .../com/moabam/api/domain/room/Routine.java | 61 + .../repository/CertificationRepository.java | 9 + .../CertificationsSearchRepository.java | 84 + .../DailyMemberCertificationRepository.java | 15 + .../DailyRoomCertificationRepository.java | 12 + .../repository/ParticipantRepository.java | 12 + .../ParticipantSearchRepository.java | 121 ++ .../room/repository/RoomRepository.java | 57 + .../room/repository/RoomSearchRepository.java | 34 + .../room/repository/RoutineRepository.java | 14 + .../dto/auth/AuthorizationCodeRequest.java | 26 + .../dto/auth/AuthorizationCodeResponse.java | 10 + .../auth/AuthorizationTokenInfoResponse.java | 11 + .../dto/auth/AuthorizationTokenRequest.java | 24 + .../dto/auth/AuthorizationTokenResponse.java | 15 + .../moabam/api/dto/auth/LoginResponse.java | 14 + .../moabam/api/dto/auth/TokenSaveValue.java | 11 + .../api/dto/bug/BugHistoryItemResponse.java | 22 + .../api/dto/bug/BugHistoryResponse.java | 12 + .../api/dto/bug/BugHistoryWithPayment.java | 21 + .../com/moabam/api/dto/bug/BugResponse.java | 12 + .../moabam/api/dto/coupon/CouponResponse.java | 25 + .../api/dto/coupon/CouponStatusRequest.java | 11 + .../api/dto/coupon/CreateCouponRequest.java | 27 + .../api/dto/coupon/MyCouponResponse.java | 17 + .../com/moabam/api/dto/item/ItemResponse.java | 17 + .../moabam/api/dto/item/ItemsResponse.java | 14 + .../api/dto/item/PurchaseItemRequest.java | 11 + .../moabam/api/dto/member/BadgeResponse.java | 13 + .../api/dto/member/DeleteMemberResponse.java | 11 + .../com/moabam/api/dto/member/MemberInfo.java | 23 + .../api/dto/member/MemberInfoResponse.java | 26 + .../dto/member/MemberInfoSearchResponse.java | 23 + .../api/dto/member/ModifyMemberRequest.java | 8 + .../dto/payment/ConfirmPaymentRequest.java | 15 + .../payment/ConfirmTossPaymentResponse.java | 16 + .../api/dto/payment/PaymentRequest.java | 9 + .../api/dto/payment/PaymentResponse.java | 13 + .../RequestConfirmPaymentResponse.java | 13 + .../api/dto/product/ProductResponse.java | 14 + .../api/dto/product/ProductsResponse.java | 12 + .../dto/product/PurchaseProductRequest.java | 9 + .../dto/product/PurchaseProductResponse.java | 12 + .../moabam/api/dto/ranking/RankingInfo.java | 12 + .../api/dto/ranking/TopRankingInfo.java | 14 + .../api/dto/ranking/TopRankingResponse.java | 13 + .../moabam/api/dto/ranking/UpdateRanking.java | 11 + .../moabam/api/dto/report/ReportRequest.java | 12 + .../dto/room/CertificationImageResponse.java | 11 + .../dto/room/CertificationImagesResponse.java | 12 + .../api/dto/room/CertifiedMemberInfo.java | 19 + .../api/dto/room/CertifyRoomRequest.java | 14 + .../api/dto/room/CertifyRoomsRequest.java | 13 + .../api/dto/room/CreateRoomRequest.java | 24 + .../moabam/api/dto/room/EnterRoomRequest.java | 9 + .../api/dto/room/GetAllRoomResponse.java | 24 + .../api/dto/room/GetAllRoomsResponse.java | 13 + .../api/dto/room/ManageRoomResponse.java | 23 + .../api/dto/room/ModifyRoomRequest.java | 17 + .../moabam/api/dto/room/MyRoomResponse.java | 20 + .../moabam/api/dto/room/MyRoomsResponse.java | 12 + .../api/dto/room/ParticipantResponse.java | 13 + .../api/dto/room/RoomDetailsResponse.java | 33 + .../api/dto/room/RoomHistoryResponse.java | 15 + .../api/dto/room/RoomsHistoryResponse.java | 12 + .../moabam/api/dto/room/RoutineResponse.java | 11 + .../room/TodayCertificateRankResponse.java | 18 + .../UnJoinedRoomCertificateRankResponse.java | 14 + .../dto/room/UnJoinedRoomDetailsResponse.java | 27 + .../api/infrastructure/fcm/FcmMapper.java | 25 + .../api/infrastructure/fcm/FcmRepository.java | 37 + .../api/infrastructure/fcm/FcmService.java | 44 + .../payment/TossPaymentService.java | 55 + .../redis/HashRedisRepository.java | 47 + .../redis/ValueRedisRepository.java | 41 + .../redis/ZSetRedisRepository.java | 90 + .../api/infrastructure/s3/S3Manager.java | 47 + .../slack/SlackMessageFactory.java | 74 + .../infrastructure/slack/SlackService.java | 26 + .../api/presentation/BugController.java | 55 + .../api/presentation/CouponController.java | 78 + .../presentation/HealthCheckController.java | 24 + .../api/presentation/ItemController.java | 48 + .../api/presentation/MemberController.java | 92 + .../presentation/NotificationController.java | 42 + .../api/presentation/PaymentController.java | 41 + .../api/presentation/RankingController.java | 33 + .../api/presentation/ReportController.java | 30 + .../api/presentation/RoomController.java | 155 ++ .../moabam/global/auth/annotation/Auth.java | 12 + .../auth/filter/AuthorizationFilter.java | 130 ++ .../moabam/global/auth/filter/CorsFilter.java | 79 + .../moabam/global/auth/filter/PathFilter.java | 48 + .../auth/handler/AuthArgumentResolver.java | 31 + .../global/auth/handler/PathResolver.java | 85 + .../moabam/global/auth/model/AuthMember.java | 11 + .../auth/model/AuthorizationThreadLocal.java | 26 + .../moabam/global/auth/model/PublicClaim.java | 15 + .../global/common/entity/BaseTimeEntity.java | 29 + .../global/common/util/BaseDataCode.java | 11 + .../global/common/util/BaseImageUrl.java | 20 + .../global/common/util/ClockHolder.java | 11 + .../global/common/util/CookieUtils.java | 39 + .../moabam/global/common/util/DateUtils.java | 17 + .../global/common/util/DynamicQuery.java | 34 + .../global/common/util/GlobalConstant.java | 21 + .../global/common/util/RandomUtils.java | 19 + .../global/common/util/StreamUtils.java | 17 + .../global/common/util/SystemClockHolder.java | 22 + .../common/util/UrlSubstringParser.java | 25 + .../common/util/cookie/CookieDevUtils.java | 22 + .../common/util/cookie/CookieProdUtils.java | 22 + .../common/util/cookie/CookieUtils.java | 22 + .../global/config/AllowOriginConfig.java | 13 + .../global/config/EmbeddedRedisConfig.java | 227 +++ .../com/moabam/global/config/FcmConfig.java | 44 + .../com/moabam/global/config/JpaConfig.java | 24 + .../com/moabam/global/config/OAuthConfig.java | 34 + .../com/moabam/global/config/RedisConfig.java | 38 + .../com/moabam/global/config/SlackConfig.java | 19 + .../com/moabam/global/config/TokenConfig.java | 32 + .../global/config/TossPaymentConfig.java | 11 + .../com/moabam/global/config/WebConfig.java | 53 + .../error/exception/BadRequestException.java | 10 + .../error/exception/ConflictException.java | 10 + .../global/error/exception/FcmException.java | 10 + .../error/exception/ForbiddenException.java | 10 + .../error/exception/MoabamException.java | 18 + .../error/exception/NotFoundException.java | 13 + .../error/exception/TossPaymentException.java | 8 + .../exception/UnauthorizedException.java | 10 + .../error/handler/GlobalExceptionHandler.java | 114 ++ .../handler/RestTemplateResponseHandler.java | 62 + .../error/handler/SlackExceptionHandler.java | 29 + .../global/error/model/ErrorMessage.java | 105 ++ .../global/error/model/ErrorResponse.java | 11 + .../resources/binary/redis/redis-server-arm64 | Bin 0 -> 2338192 bytes src/main/resources/config | 2 +- src/main/resources/logback-spring.xml | 35 + src/main/resources/static/docs/coupon.html | 723 ++++++++ src/main/resources/static/docs/index.html | 630 +++++++ .../resources/static/docs/notification.html | 520 ++++++ .../moabam/MoabamServerApplicationTests.java | 5 - .../auth/AuthorizationServiceTest.java | 352 ++++ .../auth/JwtAuthenticationServiceTest.java | 145 ++ .../auth/JwtProviderServiceTest.java | 168 ++ ...AuthorizationServerRequestServiceTest.java | 266 +++ .../api/application/bug/BugServiceTest.java | 183 ++ .../coupon/CouponManageServiceTest.java | 172 ++ .../application/coupon/CouponServiceTest.java | 367 ++++ .../application/image/ImageServiceTest.java | 62 + .../api/application/item/ItemServiceTest.java | 189 ++ .../application/member/MemberServiceTest.java | 249 +++ .../notification/NotificationServiceTest.java | 234 +++ .../payment/PaymentServiceTest.java | 118 ++ .../ranking/RankingServiceTest.java | 234 +++ .../application/report/ReportServiceTest.java | 93 + .../CertificationServiceConcurrencyTest.java | 128 ++ .../room/CertificationServiceTest.java | 199 +++ .../room/RoomServiceConcurrencyTest.java | 111 ++ .../api/application/room/RoomServiceTest.java | 163 ++ .../application/room/SearchServiceTest.java | 417 +++++ .../com/moabam/api/domain/bug/BugTest.java | 79 + .../moabam/api/domain/coupon/CouponTest.java | 62 + .../api/domain/coupon/CouponTypeTest.java | 52 + .../api/domain/coupon/CouponWalletTest.java | 25 + .../CouponManageRepositoryTest.java | 119 ++ .../CouponSearchRepositoryTest.java | 104 ++ .../CouponWalletSearchRepositoryTest.java | 126 ++ .../moabam/api/domain/entity/MemberTest.java | 111 ++ .../com/moabam/api/domain/item/ItemTest.java | 117 ++ .../InventorySearchRepositoryTest.java | 169 ++ .../repository/ItemSearchRepositoryTest.java | 100 ++ .../domain/member/BadgeRepositoryTest.java | 84 + .../domain/member/MemberRepositoryTest.java | 152 ++ .../NotificationRepositoryTest.java | 93 + .../moabam/api/domain/payment/OrderTest.java | 24 + .../api/domain/payment/PaymentTest.java | 93 + .../api/domain/product/ProductTest.java | 36 + .../api/domain/room/CertificationTest.java | 39 + .../com/moabam/api/domain/room/RoomTest.java | 111 ++ .../CertificationsSearchRepositoryTest.java | 109 ++ .../ParticipantSearchRepositoryTest.java | 48 + .../dto/coupon/CreateCouponRequestTest.java | 31 + .../infrastructure/fcm/FcmRepositoryTest.java | 88 + .../infrastructure/fcm/FcmServiceTest.java | 97 + .../payment/TossPaymentServiceTest.java | 92 + .../redis/HashRedisRepositoryTest.java | 77 + .../redis/TokenRepostiroyTest.java | 66 + .../redis/ValueRedisRepositoryTest.java | 86 + .../redis/ZSetRedisRepositoryTest.java | 111 ++ .../api/presentation/BugControllerTest.java | 190 ++ .../presentation/CouponControllerTest.java | 518 ++++++ .../api/presentation/ItemControllerTest.java | 165 ++ .../MemberAuthorizeControllerTest.java | 219 +++ .../presentation/MemberControllerTest.java | 528 ++++++ .../NotificationControllerTest.java | 174 ++ .../presentation/PaymentControllerTest.java | 179 ++ .../presentation/RankingControllerTest.java | 88 + .../presentation/ReportControllerTest.java | 165 ++ .../api/presentation/RoomControllerTest.java | 1592 +++++++++++++++++ .../handler/AuthArgumentResolverTest.java | 116 ++ .../common/handler/PathResolverTest.java | 65 + .../global/common/util/CookieMakeTest.java | 59 + .../common/util/UrlSubstringParserTest.java | 41 + .../filter/AuthorizationFilterTest.java | 175 ++ .../moabam/global/filter/PathFilterTest.java | 77 + .../annotation/QuerydslRepositoryTest.java | 22 + .../moabam/support/annotation/WithMember.java | 19 + .../support/common/ClearDataExtension.java | 15 + .../support/common/DataCleanResolver.java | 54 + .../common/FilterProcessExtension.java | 61 + .../support/common/RestDocsFactory.java | 31 + .../support/common/TestClockHolder.java | 24 + .../support/common/WithFilterSupporter.java | 55 + .../common/WithoutFilterSupporter.java | 47 + .../support/config/TestQuerydslConfig.java | 53 + .../support/fixture/AuthMemberFixture.java | 11 + .../fixture/AuthorizationResponseFixture.java | 29 + .../moabam/support/fixture/BadgeFixture.java | 14 + .../moabam/support/fixture/BugFixture.java | 52 + .../support/fixture/BugHistoryFixture.java | 26 + .../moabam/support/fixture/CouponFixture.java | 184 ++ .../support/fixture/CouponWalletFixture.java | 38 + .../support/fixture/DeleteMemberFixture.java | 13 + .../support/fixture/InventoryFixture.java | 14 + .../moabam/support/fixture/ItemFixture.java | 58 + .../support/fixture/JwtProviderFixture.java | 21 + .../moabam/support/fixture/MemberFixture.java | 39 + .../fixture/MemberInfoSearchFixture.java | 40 + .../support/fixture/ModifyImageFixture.java | 28 + .../support/fixture/ParticipantFixture.java | 44 + .../support/fixture/PaymentFixture.java | 60 + .../support/fixture/ProductFixture.java | 20 + .../support/fixture/PublicClaimFixture.java | 15 + .../support/fixture/RankingInfoFixture.java | 5 + .../moabam/support/fixture/ReportFixture.java | 30 + .../moabam/support/fixture/RoomFixture.java | 170 ++ .../fixture/TokenSaveValueFixture.java | 24 + .../moabam/support/snippet/CouponSnippet.java | 50 + .../support/snippet/CouponWalletSnippet.java | 18 + .../moabam/support/snippet/ErrorSnippet.java | 13 + src/test/resources/application.yml | 93 + src/test/resources/image.png | Bin 0 -> 52270 bytes 362 files changed, 26066 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/develop-cd.yml create mode 100644 .github/workflows/prod-cd.yml create mode 100644 infra/Dockerfile create mode 100644 infra/docker-compose-dev.yml create mode 100644 infra/docker-compose-prod.yml create mode 100644 infra/mysql/initdb.d/init.sql create mode 100644 infra/mysql/initdb.d/item-data.sql create mode 100644 infra/nginx/conf.d/header.conf create mode 100644 infra/nginx/mime.types create mode 100644 infra/nginx/nginx.conf create mode 100644 infra/nginx/templates/http-server.template create mode 100644 infra/nginx/templates/ssl-server.template create mode 100644 infra/nginx/templates/upstream.template create mode 100644 infra/scripts/deploy-dev.sh create mode 100644 infra/scripts/deploy-prod.sh create mode 100644 infra/scripts/init-letsencrypt.sh create mode 100644 infra/scripts/init-nginx-converter.sh create mode 100644 src/docs/asciidoc/coupon.adoc create mode 100644 src/docs/asciidoc/index.adoc create mode 100644 src/docs/asciidoc/notification.adoc create mode 100644 src/main/java/com/moabam/admin/application/admin/AdminMapper.java create mode 100644 src/main/java/com/moabam/admin/application/admin/AdminService.java create mode 100644 src/main/java/com/moabam/admin/domain/admin/Admin.java create mode 100644 src/main/java/com/moabam/admin/domain/admin/AdminRepository.java create mode 100644 src/main/java/com/moabam/admin/presentation/admin/AdminController.java create mode 100644 src/main/java/com/moabam/api/application/auth/AuthorizationService.java create mode 100644 src/main/java/com/moabam/api/application/auth/JwtAuthenticationService.java create mode 100644 src/main/java/com/moabam/api/application/auth/JwtProviderService.java create mode 100644 src/main/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestService.java create mode 100644 src/main/java/com/moabam/api/application/auth/mapper/AuthMapper.java create mode 100644 src/main/java/com/moabam/api/application/auth/mapper/AuthorizationMapper.java create mode 100644 src/main/java/com/moabam/api/application/auth/mapper/PathMapper.java create mode 100644 src/main/java/com/moabam/api/application/bug/BugMapper.java create mode 100644 src/main/java/com/moabam/api/application/bug/BugService.java create mode 100644 src/main/java/com/moabam/api/application/coupon/CouponManageService.java create mode 100644 src/main/java/com/moabam/api/application/coupon/CouponMapper.java create mode 100644 src/main/java/com/moabam/api/application/coupon/CouponService.java create mode 100644 src/main/java/com/moabam/api/application/image/ImageService.java create mode 100644 src/main/java/com/moabam/api/application/item/ItemMapper.java create mode 100644 src/main/java/com/moabam/api/application/item/ItemService.java create mode 100644 src/main/java/com/moabam/api/application/member/BadgeService.java create mode 100644 src/main/java/com/moabam/api/application/member/MemberMapper.java create mode 100644 src/main/java/com/moabam/api/application/member/MemberService.java create mode 100644 src/main/java/com/moabam/api/application/notification/NotificationService.java create mode 100644 src/main/java/com/moabam/api/application/payment/PaymentMapper.java create mode 100644 src/main/java/com/moabam/api/application/payment/PaymentService.java create mode 100644 src/main/java/com/moabam/api/application/product/ProductMapper.java create mode 100644 src/main/java/com/moabam/api/application/ranking/RankingMapper.java create mode 100644 src/main/java/com/moabam/api/application/ranking/RankingService.java create mode 100644 src/main/java/com/moabam/api/application/report/ReportMapper.java create mode 100644 src/main/java/com/moabam/api/application/report/ReportService.java create mode 100644 src/main/java/com/moabam/api/application/room/CertificationService.java create mode 100644 src/main/java/com/moabam/api/application/room/RoomService.java create mode 100644 src/main/java/com/moabam/api/application/room/SearchService.java create mode 100644 src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java create mode 100644 src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java create mode 100644 src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java create mode 100644 src/main/java/com/moabam/api/application/room/mapper/RoutineMapper.java create mode 100644 src/main/java/com/moabam/api/domain/auth/repository/TokenRepository.java create mode 100644 src/main/java/com/moabam/api/domain/bug/Bug.java create mode 100644 src/main/java/com/moabam/api/domain/bug/BugActionType.java create mode 100644 src/main/java/com/moabam/api/domain/bug/BugHistory.java create mode 100644 src/main/java/com/moabam/api/domain/bug/BugType.java create mode 100644 src/main/java/com/moabam/api/domain/bug/repository/BugHistoryRepository.java create mode 100644 src/main/java/com/moabam/api/domain/bug/repository/BugHistorySearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/Coupon.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/CouponType.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/CouponWallet.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/repository/CouponSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletRepository.java create mode 100644 src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/image/ImageName.java create mode 100644 src/main/java/com/moabam/api/domain/image/ImageResizer.java create mode 100644 src/main/java/com/moabam/api/domain/image/ImageSize.java create mode 100644 src/main/java/com/moabam/api/domain/image/ImageType.java create mode 100644 src/main/java/com/moabam/api/domain/image/NewImage.java create mode 100644 src/main/java/com/moabam/api/domain/item/Inventory.java create mode 100644 src/main/java/com/moabam/api/domain/item/Item.java create mode 100644 src/main/java/com/moabam/api/domain/item/ItemCategory.java create mode 100644 src/main/java/com/moabam/api/domain/item/ItemImage.java create mode 100644 src/main/java/com/moabam/api/domain/item/ItemType.java create mode 100644 src/main/java/com/moabam/api/domain/item/repository/InventoryRepository.java create mode 100644 src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/item/repository/ItemRepository.java create mode 100644 src/main/java/com/moabam/api/domain/item/repository/ItemSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/member/Badge.java create mode 100644 src/main/java/com/moabam/api/domain/member/BadgeType.java create mode 100644 src/main/java/com/moabam/api/domain/member/Member.java create mode 100644 src/main/java/com/moabam/api/domain/member/Role.java create mode 100644 src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java create mode 100644 src/main/java/com/moabam/api/domain/member/repository/MemberRepository.java create mode 100644 src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java create mode 100644 src/main/java/com/moabam/api/domain/payment/Order.java create mode 100644 src/main/java/com/moabam/api/domain/payment/Payment.java create mode 100644 src/main/java/com/moabam/api/domain/payment/PaymentStatus.java create mode 100644 src/main/java/com/moabam/api/domain/payment/repository/PaymentRepository.java create mode 100644 src/main/java/com/moabam/api/domain/payment/repository/PaymentSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/product/Product.java create mode 100644 src/main/java/com/moabam/api/domain/product/ProductType.java create mode 100644 src/main/java/com/moabam/api/domain/product/repository/ProductRepository.java create mode 100644 src/main/java/com/moabam/api/domain/report/Report.java create mode 100644 src/main/java/com/moabam/api/domain/report/repository/ReportRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/Certification.java create mode 100644 src/main/java/com/moabam/api/domain/room/DailyMemberCertification.java create mode 100644 src/main/java/com/moabam/api/domain/room/DailyRoomCertification.java create mode 100644 src/main/java/com/moabam/api/domain/room/Participant.java create mode 100644 src/main/java/com/moabam/api/domain/room/Room.java create mode 100644 src/main/java/com/moabam/api/domain/room/RoomExp.java create mode 100644 src/main/java/com/moabam/api/domain/room/RoomType.java create mode 100644 src/main/java/com/moabam/api/domain/room/Routine.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/CertificationRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/CertificationsSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/DailyMemberCertificationRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/DailyRoomCertificationRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/ParticipantRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/ParticipantSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/RoomRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java create mode 100644 src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java create mode 100644 src/main/java/com/moabam/api/dto/auth/AuthorizationCodeRequest.java create mode 100644 src/main/java/com/moabam/api/dto/auth/AuthorizationCodeResponse.java create mode 100644 src/main/java/com/moabam/api/dto/auth/AuthorizationTokenInfoResponse.java create mode 100644 src/main/java/com/moabam/api/dto/auth/AuthorizationTokenRequest.java create mode 100644 src/main/java/com/moabam/api/dto/auth/AuthorizationTokenResponse.java create mode 100644 src/main/java/com/moabam/api/dto/auth/LoginResponse.java create mode 100644 src/main/java/com/moabam/api/dto/auth/TokenSaveValue.java create mode 100644 src/main/java/com/moabam/api/dto/bug/BugHistoryItemResponse.java create mode 100644 src/main/java/com/moabam/api/dto/bug/BugHistoryResponse.java create mode 100644 src/main/java/com/moabam/api/dto/bug/BugHistoryWithPayment.java create mode 100644 src/main/java/com/moabam/api/dto/bug/BugResponse.java create mode 100644 src/main/java/com/moabam/api/dto/coupon/CouponResponse.java create mode 100644 src/main/java/com/moabam/api/dto/coupon/CouponStatusRequest.java create mode 100644 src/main/java/com/moabam/api/dto/coupon/CreateCouponRequest.java create mode 100644 src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java create mode 100644 src/main/java/com/moabam/api/dto/item/ItemResponse.java create mode 100644 src/main/java/com/moabam/api/dto/item/ItemsResponse.java create mode 100644 src/main/java/com/moabam/api/dto/item/PurchaseItemRequest.java create mode 100644 src/main/java/com/moabam/api/dto/member/BadgeResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/DeleteMemberResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfo.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java create mode 100644 src/main/java/com/moabam/api/dto/member/ModifyMemberRequest.java create mode 100644 src/main/java/com/moabam/api/dto/payment/ConfirmPaymentRequest.java create mode 100644 src/main/java/com/moabam/api/dto/payment/ConfirmTossPaymentResponse.java create mode 100644 src/main/java/com/moabam/api/dto/payment/PaymentRequest.java create mode 100644 src/main/java/com/moabam/api/dto/payment/PaymentResponse.java create mode 100644 src/main/java/com/moabam/api/dto/payment/RequestConfirmPaymentResponse.java create mode 100644 src/main/java/com/moabam/api/dto/product/ProductResponse.java create mode 100644 src/main/java/com/moabam/api/dto/product/ProductsResponse.java create mode 100644 src/main/java/com/moabam/api/dto/product/PurchaseProductRequest.java create mode 100644 src/main/java/com/moabam/api/dto/product/PurchaseProductResponse.java create mode 100644 src/main/java/com/moabam/api/dto/ranking/RankingInfo.java create mode 100644 src/main/java/com/moabam/api/dto/ranking/TopRankingInfo.java create mode 100644 src/main/java/com/moabam/api/dto/ranking/TopRankingResponse.java create mode 100644 src/main/java/com/moabam/api/dto/ranking/UpdateRanking.java create mode 100644 src/main/java/com/moabam/api/dto/report/ReportRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/CertificationImageResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/CertifiedMemberInfo.java create mode 100644 src/main/java/com/moabam/api/dto/room/CertifyRoomRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/CertifyRoomsRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/CreateRoomRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/EnterRoomRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/ManageRoomResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/ModifyRoomRequest.java create mode 100644 src/main/java/com/moabam/api/dto/room/MyRoomResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/MyRoomsResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/ParticipantResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/RoomDetailsResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/RoomHistoryResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/RoomsHistoryResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/RoutineResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/UnJoinedRoomCertificateRankResponse.java create mode 100644 src/main/java/com/moabam/api/dto/room/UnJoinedRoomDetailsResponse.java create mode 100644 src/main/java/com/moabam/api/infrastructure/fcm/FcmMapper.java create mode 100644 src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java create mode 100644 src/main/java/com/moabam/api/infrastructure/fcm/FcmService.java create mode 100644 src/main/java/com/moabam/api/infrastructure/payment/TossPaymentService.java create mode 100644 src/main/java/com/moabam/api/infrastructure/redis/HashRedisRepository.java create mode 100644 src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java create mode 100644 src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java create mode 100644 src/main/java/com/moabam/api/infrastructure/s3/S3Manager.java create mode 100644 src/main/java/com/moabam/api/infrastructure/slack/SlackMessageFactory.java create mode 100644 src/main/java/com/moabam/api/infrastructure/slack/SlackService.java create mode 100644 src/main/java/com/moabam/api/presentation/BugController.java create mode 100644 src/main/java/com/moabam/api/presentation/CouponController.java create mode 100644 src/main/java/com/moabam/api/presentation/HealthCheckController.java create mode 100644 src/main/java/com/moabam/api/presentation/ItemController.java create mode 100644 src/main/java/com/moabam/api/presentation/MemberController.java create mode 100644 src/main/java/com/moabam/api/presentation/NotificationController.java create mode 100644 src/main/java/com/moabam/api/presentation/PaymentController.java create mode 100644 src/main/java/com/moabam/api/presentation/RankingController.java create mode 100644 src/main/java/com/moabam/api/presentation/ReportController.java create mode 100644 src/main/java/com/moabam/api/presentation/RoomController.java create mode 100644 src/main/java/com/moabam/global/auth/annotation/Auth.java create mode 100644 src/main/java/com/moabam/global/auth/filter/AuthorizationFilter.java create mode 100644 src/main/java/com/moabam/global/auth/filter/CorsFilter.java create mode 100644 src/main/java/com/moabam/global/auth/filter/PathFilter.java create mode 100644 src/main/java/com/moabam/global/auth/handler/AuthArgumentResolver.java create mode 100644 src/main/java/com/moabam/global/auth/handler/PathResolver.java create mode 100644 src/main/java/com/moabam/global/auth/model/AuthMember.java create mode 100644 src/main/java/com/moabam/global/auth/model/AuthorizationThreadLocal.java create mode 100644 src/main/java/com/moabam/global/auth/model/PublicClaim.java create mode 100644 src/main/java/com/moabam/global/common/entity/BaseTimeEntity.java create mode 100644 src/main/java/com/moabam/global/common/util/BaseDataCode.java create mode 100644 src/main/java/com/moabam/global/common/util/BaseImageUrl.java create mode 100644 src/main/java/com/moabam/global/common/util/ClockHolder.java create mode 100644 src/main/java/com/moabam/global/common/util/CookieUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/DateUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/DynamicQuery.java create mode 100644 src/main/java/com/moabam/global/common/util/GlobalConstant.java create mode 100644 src/main/java/com/moabam/global/common/util/RandomUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/StreamUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/SystemClockHolder.java create mode 100644 src/main/java/com/moabam/global/common/util/UrlSubstringParser.java create mode 100644 src/main/java/com/moabam/global/common/util/cookie/CookieDevUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/cookie/CookieProdUtils.java create mode 100644 src/main/java/com/moabam/global/common/util/cookie/CookieUtils.java create mode 100644 src/main/java/com/moabam/global/config/AllowOriginConfig.java create mode 100644 src/main/java/com/moabam/global/config/EmbeddedRedisConfig.java create mode 100644 src/main/java/com/moabam/global/config/FcmConfig.java create mode 100644 src/main/java/com/moabam/global/config/JpaConfig.java create mode 100644 src/main/java/com/moabam/global/config/OAuthConfig.java create mode 100644 src/main/java/com/moabam/global/config/RedisConfig.java create mode 100644 src/main/java/com/moabam/global/config/SlackConfig.java create mode 100644 src/main/java/com/moabam/global/config/TokenConfig.java create mode 100644 src/main/java/com/moabam/global/config/TossPaymentConfig.java create mode 100644 src/main/java/com/moabam/global/config/WebConfig.java create mode 100644 src/main/java/com/moabam/global/error/exception/BadRequestException.java create mode 100644 src/main/java/com/moabam/global/error/exception/ConflictException.java create mode 100644 src/main/java/com/moabam/global/error/exception/FcmException.java create mode 100644 src/main/java/com/moabam/global/error/exception/ForbiddenException.java create mode 100644 src/main/java/com/moabam/global/error/exception/MoabamException.java create mode 100644 src/main/java/com/moabam/global/error/exception/NotFoundException.java create mode 100644 src/main/java/com/moabam/global/error/exception/TossPaymentException.java create mode 100644 src/main/java/com/moabam/global/error/exception/UnauthorizedException.java create mode 100644 src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/com/moabam/global/error/handler/RestTemplateResponseHandler.java create mode 100644 src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java create mode 100644 src/main/java/com/moabam/global/error/model/ErrorMessage.java create mode 100644 src/main/java/com/moabam/global/error/model/ErrorResponse.java create mode 100755 src/main/resources/binary/redis/redis-server-arm64 create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/main/resources/static/docs/coupon.html create mode 100644 src/main/resources/static/docs/index.html create mode 100644 src/main/resources/static/docs/notification.html create mode 100644 src/test/java/com/moabam/api/application/auth/AuthorizationServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/auth/JwtAuthenticationServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/auth/JwtProviderServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/bug/BugServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/image/ImageServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/item/ItemServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/member/MemberServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/payment/PaymentServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/ranking/RankingServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/report/ReportServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/room/CertificationServiceConcurrencyTest.java create mode 100644 src/test/java/com/moabam/api/application/room/CertificationServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/room/RoomServiceConcurrencyTest.java create mode 100644 src/test/java/com/moabam/api/application/room/RoomServiceTest.java create mode 100644 src/test/java/com/moabam/api/application/room/SearchServiceTest.java create mode 100644 src/test/java/com/moabam/api/domain/bug/BugTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/CouponTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/entity/MemberTest.java create mode 100644 src/test/java/com/moabam/api/domain/item/ItemTest.java create mode 100644 src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/item/repository/ItemSearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/member/BadgeRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/payment/OrderTest.java create mode 100644 src/test/java/com/moabam/api/domain/payment/PaymentTest.java create mode 100644 src/test/java/com/moabam/api/domain/product/ProductTest.java create mode 100644 src/test/java/com/moabam/api/domain/room/CertificationTest.java create mode 100644 src/test/java/com/moabam/api/domain/room/RoomTest.java create mode 100644 src/test/java/com/moabam/api/domain/room/repository/CertificationsSearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/domain/room/repository/ParticipantSearchRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/payment/TossPaymentServiceTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/redis/HashRedisRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/redis/TokenRepostiroyTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java create mode 100644 src/test/java/com/moabam/api/presentation/BugControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/CouponControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/ItemControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/MemberControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/NotificationControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/PaymentControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/RankingControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/ReportControllerTest.java create mode 100644 src/test/java/com/moabam/api/presentation/RoomControllerTest.java create mode 100644 src/test/java/com/moabam/global/common/handler/AuthArgumentResolverTest.java create mode 100644 src/test/java/com/moabam/global/common/handler/PathResolverTest.java create mode 100644 src/test/java/com/moabam/global/common/util/CookieMakeTest.java create mode 100644 src/test/java/com/moabam/global/common/util/UrlSubstringParserTest.java create mode 100644 src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java create mode 100644 src/test/java/com/moabam/global/filter/PathFilterTest.java create mode 100644 src/test/java/com/moabam/support/annotation/QuerydslRepositoryTest.java create mode 100644 src/test/java/com/moabam/support/annotation/WithMember.java create mode 100644 src/test/java/com/moabam/support/common/ClearDataExtension.java create mode 100644 src/test/java/com/moabam/support/common/DataCleanResolver.java create mode 100644 src/test/java/com/moabam/support/common/FilterProcessExtension.java create mode 100644 src/test/java/com/moabam/support/common/RestDocsFactory.java create mode 100644 src/test/java/com/moabam/support/common/TestClockHolder.java create mode 100644 src/test/java/com/moabam/support/common/WithFilterSupporter.java create mode 100644 src/test/java/com/moabam/support/common/WithoutFilterSupporter.java create mode 100644 src/test/java/com/moabam/support/config/TestQuerydslConfig.java create mode 100644 src/test/java/com/moabam/support/fixture/AuthMemberFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/AuthorizationResponseFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/BadgeFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/BugFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/BugHistoryFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/CouponFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/CouponWalletFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/DeleteMemberFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/InventoryFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/ItemFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/JwtProviderFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/MemberFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/ModifyImageFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/ParticipantFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/PaymentFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/ProductFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/PublicClaimFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/RankingInfoFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/ReportFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/RoomFixture.java create mode 100644 src/test/java/com/moabam/support/fixture/TokenSaveValueFixture.java create mode 100644 src/test/java/com/moabam/support/snippet/CouponSnippet.java create mode 100644 src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java create mode 100644 src/test/java/com/moabam/support/snippet/ErrorSnippet.java create mode 100644 src/test/resources/application.yml create mode 100755 src/test/resources/image.png diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a2d24f4..a5e5b65d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,11 @@ - +## 📋 Checklist -## 🧩 이슈 번호 - -- #이슈번호 +- [ ] 🔀 PR 제목의 형식을 잘 작성했나요? (e.g. `feat: 유저 조회 기능 구현`) +- [ ] 🏷️ 라벨, 프로젝트, 마일스톤은 등록했나요? +- [ ] 🧹 코드 스멜은 해결했나요? -## ✅ 작업 사항 +## 🧩 이슈 번호 -- [ ] 작업 내용 +- close #이슈번호 ## 👩‍💻 공유 포인트 및 논의 사항 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3109497d..17e57edd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,46 +1,52 @@ name: ci on: - pull_request: - branches: [ "main", "develop" ] + pull_request: + branches: [ "main", "develop" ] jobs: - build: - name: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: JDK 17 셋업 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'corretto' - - - name: Gradle 캐싱 - uses: actions/cache@v3 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Gradle Grant 권한 부여 - run: chmod +x gradlew - - - name: SonarCloud 캐싱 - uses: actions/cache@v3 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: 빌드 및 분석 - run: ./gradlew build jacocoTestReport sonar --info --stacktrace - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_CLOUD_TOKEN }} + build: + name: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + token: ${{ secrets.MOABAM_SUBMODULE_KEY }} + + - name: JDK 17 셋업 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: Gradle 캐싱 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Gradle Grant 권한 부여 + run: chmod +x gradlew + + - name: 테스트용 MySQL 도커 컨테이너 실행 + run: | + sudo docker run -d -p 3305:3306 --env MYSQL_DATABASE=moabam --env MYSQL_ROOT_PASSWORD=1234 mysql:8.0.33 + + - name: SonarCloud 캐싱 + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: 빌드 및 분석 + run: ./gradlew build jacocoTestReport sonar --info --stacktrace + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_CLOUD_TOKEN }} diff --git a/.github/workflows/develop-cd.yml b/.github/workflows/develop-cd.yml new file mode 100644 index 00000000..cd5449bf --- /dev/null +++ b/.github/workflows/develop-cd.yml @@ -0,0 +1,175 @@ +name: develop-CD + +on: + push: + branches: [ "develop" ] + +permissions: + contents: write + +jobs: + move-files: + name: move-files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.MOABAM_SUBMODULE_KEY }} + + - name: Github Actions IP 획득 + id: ip + uses: haythem/public-ip@v1.3 + + - name: AWS Credentials 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Github Actions IP 보안그룹 추가 + run: | + aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_DEV_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - name: 디렉토리 생성 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_DEV_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_DEV_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_DEV_INSTANCE_PRIVATE_KEY }} + script: | + mkdir -p /home/ubuntu/moabam/ + + - name: Docker env 파일 생성 + run: + cp src/main/resources/config/dev.env ./infra/.env + + - name: 서버로 전송 기본 파일들 전송 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_DEV_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_DEV_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_DEV_INSTANCE_PRIVATE_KEY }} + source: "infra/mysql/*, infra/nginx/*, infra/scripts/*.sh, !infra/scripts/deploy-prod.sh, infra/docker-compose-dev.yml" + target: "/home/ubuntu/moabam" + + - name: 파일 세팅 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_DEV_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_DEV_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_DEV_INSTANCE_PRIVATE_KEY }} + script: | + cd /home/ubuntu/moabam/infra + mv docker-compose-dev.yml docker-compose.yml + chmod +x ./scripts/deploy-dev.sh + chmod +x ./scripts/init-letsencrypt.sh + chmod +x ./scripts/init-nginx-converter.sh + chmod +x ./mysql/initdb.d/init.sql + chmod +x ./mysql/initdb.d/item-data.sql + + - name: Github Actions IP 보안그룹에서 삭제 + if: always() + run: | + aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_DEV_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + deploy: + name: deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.MOABAM_SUBMODULE_KEY }} + + - name: JDK 17 셋업 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: Gradle 캐싱 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Gradle Grant 권한 부여 + run: chmod +x gradlew + + - name: 테스트용 MySQL 도커 컨테이너 실행 + run: | + sudo docker run -d -p 3305:3306 --env MYSQL_DATABASE=moabam --env MYSQL_ROOT_PASSWORD=1234 mysql:8.0.33 + + - name: Gradle 빌드 + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build + + - name: 멀티플랫폼 위한 Docker Buildx 설정 + uses: docker/setup-buildx-action@v2 + + - name: Docker Hub 로그인 + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Docker Hub 빌드하고 푸시 + uses: docker/build-push-action@v4 + with: + context: . + file: ./infra/Dockerfile + push: true + tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:${{ secrets.DOCKER_HUB_DEV_TAG }} + build-args: | + "SPRING_ACTIVE_PROFILES=dev" + platforms: | + linux/amd64 + linux/arm64 + + - name: Github Actions IP 획득 + id: ip + uses: haythem/public-ip@v1.3 + + - name: AWS Credentials 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Github Actions IP 보안그룹 추가 + run: | + aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_DEV_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - name: EC2 서버에 배포 + uses: appleboy/ssh-action@master + id: deploy-dev + if: contains(github.ref, 'dev') + with: + host: ${{ secrets.EC2_DEV_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_DEV_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_DEV_INSTANCE_PRIVATE_KEY }} + source: "./infra/docker-compose-dev.yml" + script: | + cd /home/ubuntu/moabam/infra + echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + ./scripts/deploy-dev.sh + docker rm `docker ps -a -q` + docker rmi $(docker images -aq) + echo "### 배포 완료 ###" + + - name: Github Actions IP 보안그룹에서 삭제 + if: always() + run: | + aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_DEV_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml new file mode 100644 index 00000000..d00dca7c --- /dev/null +++ b/.github/workflows/prod-cd.yml @@ -0,0 +1,173 @@ +name: prod-CD + +on: + push: + branches: [ "main" ] + +permissions: + contents: write + +jobs: + move-files: + name: move-files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.MOABAM_SUBMODULE_KEY }} + + - name: Github Actions IP 획득 + id: ip + uses: haythem/public-ip@v1.3 + + - name: AWS Credentials 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Github Actions IP 보안그룹 추가 + run: | + aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_PROD_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - name: 디렉토리 생성 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_PROD_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_PROD_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_PROD_INSTANCE_PRIVATE_KEY }} + script: | + mkdir -p /home/ubuntu/moabam/ + + - name: Docker env 파일 생성 + run: + cp src/main/resources/config/prod.env ./infra/.env + + - name: 서버로 전송 기본 파일들 전송 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_PROD_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_PROD_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_PROD_INSTANCE_PRIVATE_KEY }} + source: "infra/mysql/*, infra/nginx/*, infra/scripts/*.sh, !infra/scripts/deploy-dev.sh, infra/docker-compose-prod.yml" + target: "/home/ubuntu/moabam" + + - name: 파일 세팅 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_PROD_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_PROD_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_PROD_INSTANCE_PRIVATE_KEY }} + script: | + cd /home/ubuntu/moabam/infra + mv docker-compose-prod.yml docker-compose.yml + chmod +x ./scripts/deploy-prod.sh + chmod +x ./scripts/init-letsencrypt.sh + chmod +x ./scripts/init-nginx-converter.sh + + - name: Github Actions IP 보안그룹에서 삭제 + if: always() + run: | + aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_PROD_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + deploy: + name: deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + token: ${{ secrets.MOABAM_SUBMODULE_KEY }} + + - name: JDK 17 셋업 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: Gradle 캐싱 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Gradle Grant 권한 부여 + run: chmod +x gradlew + + - name: 테스트용 MySQL 도커 컨테이너 실행 + run: | + sudo docker run -d -p 3305:3306 --env MYSQL_DATABASE=moabam --env MYSQL_ROOT_PASSWORD=1234 mysql:8.0.33 + + - name: Gradle 빌드 + uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 + with: + arguments: build + + - name: 멀티플랫폼 위한 Docker Buildx 설정 + uses: docker/setup-buildx-action@v2 + + - name: Docker Hub 로그인 + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Docker Hub 빌드하고 푸시 + uses: docker/build-push-action@v4 + with: + context: . + file: ./infra/Dockerfile + push: true + tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:${{ secrets.DOCKER_HUB_DEV_TAG }} + build-args: | + "SPRING_ACTIVE_PROFILES=prod" + platforms: | + linux/amd64 + linux/arm64 + + - name: Github Actions IP 획득 + id: ip + uses: haythem/public-ip@v1.3 + + - name: AWS Credentials 설정 + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Github Actions IP 보안그룹 추가 + run: | + aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_PROD_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 + + - name: EC2 서버에 배포 + uses: appleboy/ssh-action@master + id: deploy-prod + if: contains(github.ref, 'main') + with: + host: ${{ secrets.EC2_PROD_INSTANCE_HOST }} + port: 22 + username: ${{ secrets.EC2_PROD_INSTANCE_USERNAME }} + key: ${{ secrets.EC2_PROD_INSTANCE_PRIVATE_KEY }} + source: "./infra/docker-compose-prod.yml" + script: | + cd /home/ubuntu/moabam/infra + echo ${{ secrets.DOCKER_HUB_TOKEN }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin + ./scripts/deploy-prod.sh + docker rm `docker ps -a -q` + docker rmi $(docker images -aq) + echo "### 배포 완료 ###" + + - name: Github Actions IP 보안그룹에서 삭제 + if: always() + run: | + aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_PROD_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32 diff --git a/.gitignore b/.gitignore index df8fb3c3..1bd49ffd 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,6 @@ gradle-app.setting logs/ application-*.yml src/main/resources/config +!application-test.yml +src/main/generated +dump.rdb diff --git a/build.gradle b/build.gradle index 1fc75afe..c7f8c37e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ plugins { id 'org.sonarqube' version '4.4.1.3373' id 'jacoco' id 'checkstyle' + id 'org.asciidoctor.jvm.convert' version '3.3.2' } group = 'com.moabam' @@ -14,7 +15,21 @@ java { sourceCompatibility = '17' } +ext { + snippetsDir = file('build/generated-snippets') +} + +def querydslSrcDir = 'src/main/generated' +clean { + delete file(querydslSrcDir) +} +tasks.withType(JavaCompile) { + options.generatedSourceOutputDirectory = file(querydslSrcDir) +} + configurations { + asciidoctorExtensions + compileOnly { extendsFrom annotationProcessor } @@ -43,6 +58,7 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0' // Querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' @@ -52,6 +68,51 @@ dependencies { // H2 implementation 'com.h2database:h2' + + // Configuration Binding + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" + + // Apache Commons Lang 3 + implementation 'org.apache.commons:commons-lang3:3.13.0' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // Embedded-Redis + implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2' + + // Firebase Admin + implementation 'com.google.firebase:firebase-admin:9.2.0' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // JSON parser + implementation 'org.json:json:20230618' + + // Asciidoctor + asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' + + // RestDocs + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + + // S3 + implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2") + implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3' + + // Webflux + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + // Slack Webhook + implementation 'net.gpedro.integrations.slack:slack-webhook:1.4.0' + + // Logback Slack Appender + implementation 'com.github.maricn:logback-slack-appender:1.6.1' + + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' } tasks.named('test') { @@ -73,17 +134,21 @@ jacocoTestReport { afterEvaluate { classDirectories.setFrom( - files(classDirectories.files.collect { - fileTree(dir: it, excludes: [ - "**/*Application*", - "**/*Config*", - "**/*Request*", - "**/*Response*", - "**/*Exception*", - "**/*Mapper*", - "**/*ErrorMessage*", - ] + Qdomains) - }) + files(classDirectories.files.collect { + fileTree(dir: it, excludes: [ + "**/*Application*", + "**/*Config*", + "**/*Request*", + "**/*Response*", + "**/*Exception*", + "**/*Mapper*", + "**/*ErrorMessage*", + "**/*DynamicQuery*", + "**/*BaseTimeEntity*", + "**/*HealthCheckController*", + "**/*S3Manager*", + ] + Qdomains) + }) ) } } @@ -112,8 +177,37 @@ sonar { property "sonar.host.url", "https://sonarcloud.io" property 'sonar.coverage.jacoco.xmlReportPaths', 'build/reports/jacoco/test/jacocoTestReport.xml' property 'sonar.coverage.exclusions', '**/test/**, **/Q*.java, **/*Doc*.java, **/resources/** ' + - ',**/*Application*.java , **/*Config*.java, **/*Request*.java, **/*Response*.java ,**/*Exception*.java ' + - ',**/*ErrorMessage*.java, **/*Mapper*.java' + ',**/*Application*.java , **/*Config*.java, **/*Request*.java, **/*Response*.java ,**/*Exception*.java ' + + ',**/*ErrorMessage*.java, **/*Mapper*.java, **/*DynamicQuery*, **/*BaseTimeEntity*, **/*HealthCheckController* ' + + ', **/*S3Manager*.java' property 'sonar.java.checkstyle.reportPaths', 'build/reports/checkstyle/main.xml' } } + +test { + outputs.dir snippetsDir +} + +asciidoctor { + configurations 'asciidoctorExtensions' + inputs.dir snippetsDir + dependsOn test +} + +asciidoctor.doFirst { + delete file('src/main/resources/static/docs') +} + +tasks.register('copyDocument', Copy) { + dependsOn asciidoctor + from file("build/docs/asciidoc") + into file("src/main/resources/static/docs") +} + +bootJar { + dependsOn copyDocument +} + +build { + dependsOn copyDocument +} diff --git a/config/naver-intellij-formatter-custom.xml b/config/naver-intellij-formatter-custom.xml index 26f28954..dc4c4a2e 100644 --- a/config/naver-intellij-formatter-custom.xml +++ b/config/naver-intellij-formatter-custom.xml @@ -1,74 +1,75 @@ - - - \ No newline at end of file diff --git a/infra/Dockerfile b/infra/Dockerfile new file mode 100644 index 00000000..c7fb704b --- /dev/null +++ b/infra/Dockerfile @@ -0,0 +1,8 @@ +FROM amazoncorretto:17 + +ARG SPRING_ACTIVE_PROFILES +ENV SPRING_ACTIVE_PROFILES ${SPRING_ACTIVE_PROFILES} + +COPY build/libs/moabam-server-0.0.1-SNAPSHOT.jar moabam.jar + +ENTRYPOINT ["java", "-jar", "-Duser.timezone=Asia/Seoul", "-Dspring.profiles.active=${SPRING_ACTIVE_PROFILES}", "/moabam.jar"] diff --git a/infra/docker-compose-dev.yml b/infra/docker-compose-dev.yml new file mode 100644 index 00000000..86ec3530 --- /dev/null +++ b/infra/docker-compose-dev.yml @@ -0,0 +1,77 @@ +version: '3.7' + +services: + nginx: + image: nginx:latest + container_name: nginx + platform: linux/arm64/v8 + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d:/etc/nginx/conf.d + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + - ../logs/nginx:/var/log/nginx + certbot: + image: certbot/certbot:latest + container_name: certbot + platform: linux/arm64 + restart: unless-stopped + volumes: + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + moabam-blue: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${BLUE_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + - mysql + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + moabam-green: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${GREEN_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + - mysql + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + redis: + image: redis:alpine + container_name: redis + platform: linux/arm64 + restart: always + command: redis-server + ports: + - "6379:6379" + volumes: + - ./data/redis:/data + mysql: + image: mysql:8.0.33 + container_name: mysql + platform: linux/arm64/v8 + restart: always + ports: + - "3306:3306" + environment: + MYSQL_DATABASE: ${DEV_MYSQL_DATABASE} + MYSQL_USERNAME: ${DEV_MYSQL_USERNAME} + MYSQL_ROOT_PASSWORD: ${DEV_MYSQL_PASSWORD} + TZ: Asia/Seoul + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + volumes: + - ./data/mysql:/var/lib/mysql + - ./mysql/initdb.d:/docker-entrypoint-initdb.d diff --git a/infra/docker-compose-prod.yml b/infra/docker-compose-prod.yml new file mode 100644 index 00000000..8cf816fa --- /dev/null +++ b/infra/docker-compose-prod.yml @@ -0,0 +1,56 @@ +version: '3.7' + +services: + nginx: + image: nginx:latest + container_name: nginx + platform: linux/arm64/v8 + restart: always + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/conf.d:/etc/nginx/conf.d + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + - ../logs/nginx:/var/log/nginx + certbot: + image: certbot/certbot:latest + container_name: certbot + platform: linux/arm64 + restart: unless-stopped + volumes: + - ./nginx/certbot/conf:/etc/letsencrypt + - ./nginx/certbot/www:/var/www/certbot + entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + moabam-blue: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${BLUE_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + moabam-green: + image: ${DOCKER_HUB_USERNAME}/${DOCKER_HUB_REPOSITORY}:${DOCKER_HUB_TAG} + container_name: ${GREEN_CONTAINER} + restart: unless-stopped + expose: + - ${SERVER_PORT} + depends_on: + - redis + environment: + SPRING_ACTIVE_PROFILES: ${SPRING_ACTIVE_PROFILES} + redis: + image: redis:alpine + container_name: redis + platform: linux/arm64 + restart: always + command: redis-server + ports: + - "6379:6379" + volumes: + - ./data/redis:/data diff --git a/infra/mysql/initdb.d/init.sql b/infra/mysql/initdb.d/init.sql new file mode 100644 index 00000000..a6edd500 --- /dev/null +++ b/infra/mysql/initdb.d/init.sql @@ -0,0 +1,259 @@ +use moabam_dev; + +create table admin +( + id bigint not null auto_increment, + nickname varchar(255) not null unique, + social_id varchar(255) unique, + role enum ('ADMIN','BLACK','USER') default 'ADMIN' not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table badge +( + id bigint not null auto_increment, + member_id bigint not null, + type enum ('BIRTH','LEVEL10','LEVEL50') not null, + created_at datetime(6) not null, + primary key (id) +); + +create table bug_history +( + id bigint not null auto_increment, + member_id bigint not null, + payment_id bigint, + bug_type enum ('GOLDEN','MORNING','NIGHT') not null, + action_type enum ('CHARGE','COUPON','REFUND','REWARD','USE') not null, + quantity integer not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table certification +( + id bigint not null auto_increment, + routine_id bigint not null, + member_id bigint not null, + image varchar(255) not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table coupon +( + id bigint not null auto_increment, + name varchar(20) not null unique, + point integer default 1 not null, + description varchar(50) default '', + type enum ('DISCOUNT','GOLDEN','MORNING','NIGHT') not null, + max_count integer default 1 not null, + start_at date not null unique, + open_at date not null, + admin_id bigint not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table coupon_wallet +( + id bigint not null auto_increment, + member_id bigint not null, + coupon_id bigint not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table daily_member_certification +( + id bigint not null auto_increment, + member_id bigint not null, + room_id bigint not null, + participant_id bigint, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table daily_room_certification +( + id bigint not null auto_increment, + room_id bigint not null, + certified_at date not null, + primary key (id) +); + +create table inventory +( + id bigint not null auto_increment, + member_id bigint not null, + item_id bigint not null, + is_default bit default false not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id), + index idx_member_id (member_id) +); + +create table item +( + id bigint not null auto_increment, + type enum ('MORNING','NIGHT') not null, + category enum ('SKIN') not null, + name varchar(255) not null, + awake_image varchar(255) not null, + sleep_image varchar(255) not null, + bug_price integer default 0 not null, + golden_bug_price integer default 0 not null, + unlock_level integer default 1 not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table member +( + id bigint not null auto_increment, + social_id varchar(255) not null unique, + nickname varchar(255) unique, + intro varchar(30), + profile_image varchar(255) not null, + morning_image varchar(255) not null, + night_image varchar(255) not null, + total_certify_count bigint default 0 not null, + report_count integer default 0 not null, + current_morning_count integer default 0 not null, + current_night_count integer default 0 not null, + morning_bug integer default 0 not null, + night_bug integer default 0 not null, + golden_bug integer default 0 not null, + role enum ('ADMIN','BLACK','USER') default 'USER' not null, + deleted_at datetime(6), + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table participant +( + id bigint not null auto_increment, + room_id bigint, + member_id bigint not null, + is_manager bit, + certify_count integer, + deleted_at datetime(6), + deleted_room_title varchar(30), + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table payment +( + id bigint not null auto_increment, + member_id bigint not null, + product_id bigint not null, + coupon_wallet_id bigint, + order_id varchar(255), + order_name varchar(255) not null, + total_amount integer not null, + discount_amount integer not null, + payment_key varchar(255), + status enum ('ABORTED','CANCELED','DONE','EXPIRED','IN_PROGRESS','READY') not null, + created_at datetime(6) not null, + requested_at datetime(6), + approved_at datetime(6), + primary key (id), + index idx_order_id (order_id) +); + +create table product +( + id bigint not null auto_increment, + type enum ('BUG') default 'BUG' not null, + name varchar(255) not null, + price integer not null, + quantity integer default 1 not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table report +( + id bigint not null auto_increment, + reporter_id bigint not null, + reported_member_id bigint not null, + room_id bigint, + certification_id bigint, + description varchar(255), + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table room +( + id bigint not null auto_increment, + title varchar(20) not null, + password varchar(8), + level integer default 0 not null, + exp integer default 0 not null, + room_type enum ('MORNING','NIGHT'), + certify_time integer not null, + current_user_count integer not null, + max_user_count integer not null, + announcement varchar(100), + room_image varchar(500), + manager_nickname varchar(30), + deleted_at datetime(6), + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +create table routine +( + id bigint not null auto_increment, + room_id bigint, + content varchar(20) not null, + created_at datetime(6) not null, + updated_at datetime(6), + primary key (id) +); + +alter table bug_history + add foreign key (payment_id) references payment (id); + +alter table certification + add foreign key (routine_id) references routine (id); + +alter table coupon_wallet + add foreign key (coupon_id) references coupon (id); + +alter table daily_member_certification + add foreign key (participant_id) references participant (id); + +alter table inventory + add foreign key (item_id) references item (id); + +alter table participant + add foreign key (room_id) references room (id); + +alter table payment + add foreign key (product_id) references product (id); + +alter table report + add foreign key (certification_id) references certification (id); + +alter table report + add foreign key (room_id) references room (id); + +alter table routine + add foreign key (room_id) references room (id); diff --git a/infra/mysql/initdb.d/item-data.sql b/infra/mysql/initdb.d/item-data.sql new file mode 100644 index 00000000..568e8f0f --- /dev/null +++ b/infra/mysql/initdb.d/item-data.sql @@ -0,0 +1,48 @@ +insert into item (type, category, name, awake_image, sleep_image, unlock_level, created_at) +values ('MORNING', 'SKIN', '오목눈이 알', 'https://image.moabam.com/moabam/skins/omok/default/egg.png', + 'https://image.moabam.com/moabam/skins/omok/default/egg.png', 0, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, unlock_level, created_at) +values ('NIGHT', 'SKIN', '부엉이 알', 'https://image.moabam.com/moabam/skins/owl/default/egg.png', + 'https://image.moabam.com/moabam/skins/owl/default/egg.png', 0, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, unlock_level, created_at) +values ('MORNING', 'SKIN', '오목눈이', 'https://image.moabam.com/moabam/skins/omok/default/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/omok/default/eyes-closed.png', 1, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, unlock_level, created_at) +values ('NIGHT', 'SKIN', '부엉이', 'https://image.moabam.com/moabam/skins/owl/default/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/owl/default/eyes-closed.png', 1, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('MORNING', 'SKIN', '안경 오목눈이', 'https://image.moabam.com/moabam/skins/omok/glasses/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/omok/glasses/eyes-closed.png', 10, 5, 5, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('NIGHT', 'SKIN', '안경 부엉이', 'https://image.moabam.com/moabam/skins/owl/glasses/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/owl/glasses/eyes-closed.png', 10, 5, 5, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('MORNING', 'SKIN', '목도리 오목눈이', 'https://image.moabam.com/moabam/skins/omok/scarf/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/omok/scarf/eyes-closed.png', 20, 10, 10, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('NIGHT', 'SKIN', '목도리 부엉이', 'https://image.moabam.com/moabam/skins/owl/scarf/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/owl/scarf/eyes-closed.png', 20, 10, 10, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('MORNING', 'SKIN', '산타 오목눈이', 'https://image.moabam.com/moabam/skins/omok/santa/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/omok/santa/eyes-closed.png', 30, 15, 15, current_time()); + +insert into item (type, category, name, awake_image, sleep_image, bug_price, golden_bug_price, unlock_level, created_at) +values ('NIGHT', 'SKIN', '산타 부엉이', 'https://image.moabam.com/moabam/skins/owl/santa/eyes-opened.png', + 'https://image.moabam.com/moabam/skins/owl/santa/eyes-closed.png', 30, 15, 15, current_time()); + +insert into product (type, name, price, quantity, created_at) +values ('BUG', '황금벌레 5', 3000, 5, current_time()); + +insert into product (type, name, price, quantity, created_at) +values ('BUG', '황금벌레 15', 7000, 15, current_time()); + +insert into product (type, name, price, quantity, created_at) +values ('BUG', '황금벌레 25', 9900, 25, current_time()); diff --git a/infra/nginx/conf.d/header.conf b/infra/nginx/conf.d/header.conf new file mode 100644 index 00000000..59deea39 --- /dev/null +++ b/infra/nginx/conf.d/header.conf @@ -0,0 +1,9 @@ +proxy_pass_header Server; +proxy_http_version 1.1; +proxy_set_header Host $http_host; +proxy_set_header Connection $connection_upgrade; +proxy_set_header Upgrade $http_upgrade; + +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; diff --git a/infra/nginx/mime.types b/infra/nginx/mime.types new file mode 100644 index 00000000..7c7cdef2 --- /dev/null +++ b/infra/nginx/mime.types @@ -0,0 +1,96 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/infra/nginx/nginx.conf b/infra/nginx/nginx.conf new file mode 100644 index 00000000..73220f14 --- /dev/null +++ b/infra/nginx/nginx.conf @@ -0,0 +1,32 @@ +worker_processes auto; + +events { + use epoll; + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + client_max_body_size 10M; + + send_timeout 15s; + resolver_timeout 5s; + + server_tokens off; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + include conf.d/header.conf; + + log_format main '$remote_addr $remote_user "$request" ' + '$status $body_bytes_sent "$http_referer" "$request_time" ' + '"$http_user_agent" '; + + include conf.d/upstream.conf; + include conf.d/http-server.conf; + include conf.d/ssl-server.conf; +} diff --git a/infra/nginx/templates/http-server.template b/infra/nginx/templates/http-server.template new file mode 100644 index 00000000..f4c91d91 --- /dev/null +++ b/infra/nginx/templates/http-server.template @@ -0,0 +1,13 @@ +server { + listen 80; + server_name ${SERVER_DOMAIN}; + + location / { + return 301 https://$http_host$request_uri; + } + + location /.well-known/acme-challenge/ { + allow all; + root /var/www/certbot; + } +} diff --git a/infra/nginx/templates/ssl-server.template b/infra/nginx/templates/ssl-server.template new file mode 100644 index 00000000..46db85fb --- /dev/null +++ b/infra/nginx/templates/ssl-server.template @@ -0,0 +1,19 @@ +server { + listen 443 ssl; + server_name ${SERVER_DOMAIN}; + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log error; + + location ^~ /actuator { + return 404; + } + + ssl_certificate /etc/letsencrypt/live/${SERVER_DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/${SERVER_DOMAIN}/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + location / { + proxy_pass http://backend; + } +} diff --git a/infra/nginx/templates/upstream.template b/infra/nginx/templates/upstream.template new file mode 100644 index 00000000..d32d6266 --- /dev/null +++ b/infra/nginx/templates/upstream.template @@ -0,0 +1,4 @@ +upstream backend { + server ${BLUE_CONTAINER}:${SERVER_PORT}; + keepalive 1024; +} diff --git a/infra/scripts/deploy-dev.sh b/infra/scripts/deploy-dev.sh new file mode 100644 index 00000000..cc6debda --- /dev/null +++ b/infra/scripts/deploy-dev.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# .env 파일 로드 +if [ -f /home/ubuntu/moabam/infra/.env ]; then + source /home/ubuntu/moabam/infra/.env +fi + +if [ $(docker ps | grep -c "nginx") -eq 0 ]; then + echo "### nginx 시작 ###" + docker-compose up -d nginx +else + echo "-------------------------------------------" + echo "nginx 이미 실행 중 입니다." + echo "-------------------------------------------" +fi + +echo +echo + +if [ $(docker ps | grep -c "redis") -eq 0 ]; then + echo "### redis 시작 ###" + docker-compose up -d redis +else + echo "-------------------------------------------" + echo "redis 이미 실행 중 입니다." + echo "-------------------------------------------" +fi + +echo +echo + +if [ $(docker ps | grep -c "mysql") -eq 0 ]; then + echo "### mysql 시작 ###" + docker-compose up -d mysql +else + echo "-------------------------------------------" + echo "mysql 이미 실행 중 입니다." + echo "-------------------------------------------" +fi + +echo +echo + +echo +echo "### springboot blue-green 무중단 배포 시작 ###" +echo + +IS_BLUE=$(docker ps | grep ${BLUE_CONTAINER}) +NGINX_CONF="/home/ubuntu/moabam/infra/nginx/nginx.conf" +UPSTREAM_CONF="/home/ubuntu/moabam/infra/nginx/conf.d/upstream.conf" + +if [ -n "$IS_BLUE" ]; then + echo "### BLUE => GREEN ###" + echo "1. ${GREEN_CONTAINER} 이미지 가져오고 실행" + docker-compose pull moabam-green + docker-compose up -d moabam-green + + attempt=1 + while [ $attempt -le 24 ]; do + echo "2. ${GREEN_CONTAINER} health check (Attempt: $attempt)" + sleep 5 + REQUEST=$(docker exec nginx curl http://${GREEN_CONTAINER}:${SERVER_PORT}) + + if [ -n "$REQUEST" ]; then + echo "${GREEN_CONTAINER} health check 성공" + sed -i "s/${BLUE_CONTAINER}/${GREEN_CONTAINER}/g" $UPSTREAM_CONF + echo "3. nginx 설정파일 reload" + docker exec nginx service nginx reload + echo "4. ${BLUE_CONTAINER} 컨테이너 종료" + docker-compose stop moabam-blue + + echo "5. ${GREEN_CONTAINER} 배포 성공" + break; + fi + + if [ $attempt -eq 24 ]; then + echo "${GREEN_CONTAINER} 배포 실패 !!" + + docker-compose stop moabam-green + + exit 1; + fi + + attempt=$((attempt+1)) + done; +else + echo "### GREEN => BLUE ###" + echo "1. ${BLUE_CONTAINER} 이미지 가져오고 실행" + docker-compose pull moabam-blue + docker-compose up -d moabam-blue + + attempt=1 + while [ $attempt -le 24 ]; do + echo "2. ${BLUE_CONTAINER} health check (Attempt: $attempt)" + sleep 5 + REQUEST=$(docker exec nginx curl http://${BLUE_CONTAINER}:${SERVER_PORT}) + + if [ -n "$REQUEST" ]; then + echo "${BLUE_CONTAINER} health check 성공" + sed -i "s/${GREEN_CONTAINER}/${BLUE_CONTAINER}/g" $UPSTREAM_CONF + echo "3. nginx 설정파일 reload" + docker exec nginx service nginx reload + echo "4. ${GREEN_CONTAINER} 컨테이너 종료" + docker-compose stop moabam-green + + echo "5. ${BLUE_CONTAINER} 배포 성공" + break; + fi + + if [ $attempt -eq 24 ]; then + echo "${BLUE_CONTAINER} 배포 실패 !!" + + docker-compose stop moabam-blue + exit 1; + fi + + attempt=$((attempt+1)) + done; +fi diff --git a/infra/scripts/deploy-prod.sh b/infra/scripts/deploy-prod.sh new file mode 100644 index 00000000..b9933fc0 --- /dev/null +++ b/infra/scripts/deploy-prod.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# .env 파일 로드 +if [ -f /home/ubuntu/moabam/infra/.env ]; then + source /home/ubuntu/moabam/infra/.env +fi + +if [ $(docker ps | grep -c "nginx") -eq 0 ]; then + echo "### nginx 시작 ###" + docker-compose up -d nginx +else + echo "-------------------------------------------" + echo "nginx 이미 실행 중 입니다." + echo "-------------------------------------------" +fi + +echo +echo + +if [ $(docker ps | grep -c "redis") -eq 0 ]; then + echo "### redis 시작 ###" + docker-compose up -d redis +else + echo "-------------------------------------------" + echo "redis 이미 실행 중 입니다." + echo "-------------------------------------------" +fi + +echo +echo + +echo +echo "### springboot blue-green 무중단 배포 시작 ###" +echo + +IS_BLUE=$(docker ps | grep ${BLUE_CONTAINER}) +NGINX_CONF="/home/ubuntu/moabam/infra/nginx/nginx.conf" +UPSTREAM_CONF="/home/ubuntu/moabam/infra/nginx/conf.d/upstream.conf" + +if [ -n "$IS_BLUE" ]; then + echo "### BLUE => GREEN ###" + echo "1. ${GREEN_CONTAINER} 이미지 가져오고 실행" + docker-compose pull moabam-green + docker-compose up -d moabam-green + + attempt=1 + while [ $attempt -le 24 ]; do + echo "2. ${GREEN_CONTAINER} health check (Attempt: $attempt)" + sleep 5 + REQUEST=$(docker exec nginx curl http://${GREEN_CONTAINER}:${SERVER_PORT}) + + if [ -n "$REQUEST" ]; then + echo "${GREEN_CONTAINER} health check 성공" + sed -i "s/${BLUE_CONTAINER}/${GREEN_CONTAINER}/g" $UPSTREAM_CONF + echo "3. nginx 설정파일 reload" + docker exec nginx service nginx reload + echo "4. ${BLUE_CONTAINER} 컨테이너 종료" + docker-compose stop moabam-blue + + echo "5. ${GREEN_CONTAINER} 배포 성공" + break; + fi + + if [ $attempt -eq 24 ]; then + echo "${GREEN_CONTAINER} 배포 실패 !!" + + docker-compose stop moabam-green + + exit 1; + fi + + attempt=$((attempt+1)) + done; +else + echo "### GREEN => BLUE ###" + echo "1. ${BLUE_CONTAINER} 이미지 가져오고 실행" + docker-compose pull moabam-blue + docker-compose up -d moabam-blue + + attempt=1 + while [ $attempt -le 24 ]; do + echo "2. ${BLUE_CONTAINER} health check (Attempt: $attempt)" + sleep 5 + REQUEST=$(docker exec nginx curl http://${BLUE_CONTAINER}:${SERVER_PORT}) + + if [ -n "$REQUEST" ]; then + echo "${BLUE_CONTAINER} health check 성공" + sed -i "s/${GREEN_CONTAINER}/${BLUE_CONTAINER}/g" $UPSTREAM_CONF + echo "3. nginx 설정파일 reload" + docker exec nginx service nginx reload + echo "4. ${GREEN_CONTAINER} 컨테이너 종료" + docker-compose stop moabam-green + + echo "5. ${BLUE_CONTAINER} 배포 성공" + break; + fi + + if [ $attempt -eq 24 ]; then + echo "${BLUE_CONTAINER} 배포 실패 !!" + + docker-compose stop moabam-blue + exit 1; + fi + + attempt=$((attempt+1)) + done; +fi diff --git a/infra/scripts/init-letsencrypt.sh b/infra/scripts/init-letsencrypt.sh new file mode 100644 index 00000000..1031fd25 --- /dev/null +++ b/infra/scripts/init-letsencrypt.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# .env 파일 로드 +if [ -f /home/ubuntu/moabam/infra/.env ]; then + source /home/ubuntu/moabam/infra/.env +fi + +if ! [ -x "$(command -v docker-compose)" ]; then + echo 'Error: docker-compose is not installed.' >&2 + exit 1 +fi + +domains="${SERVER_DOMAIN}" +rsa_key_size=4096 +data_path="/home/ubuntu/moabam/infra/nginx/certbot" +email="${MY_EMAIL}" # Adding a valid address is strongly recommended +staging=1 # Set to 1 if you're testing your setup to avoid hitting request limits + +if [ -d "$data_path" ]; then + read -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision + if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then + exit + fi +fi + + +if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then + echo "### Downloading recommended TLS parameters ..." + mkdir -p "$data_path/conf" + sudo chmod 777 "$data_path/conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" + curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem" + echo +fi + +echo "### Creating dummy certificate for $domains ..." +path="/etc/letsencrypt/live/$domains" +sudo mkdir -p "$data_path/conf/live/$domains" +docker-compose run --rm --entrypoint "\ + openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\ + -keyout '$path/privkey.pem' \ + -out '$path/fullchain.pem' \ + -subj '/CN=localhost'" certbot +echo + + +echo "### Starting nginx ..." +docker-compose up --force-recreate -d nginx +echo + +echo "### Deleting dummy certificate for $domains ..." +docker-compose run --rm --entrypoint "\ + rm -Rf /etc/letsencrypt/live/$domains && \ + rm -Rf /etc/letsencrypt/archive/$domains && \ + rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot +echo + + +echo "### Requesting Let's Encrypt certificate for $domains ..." +#Join $domains to -d args +domain_args="" +for domain in "${domains[@]}"; do + domain_args="$domain_args -d $domain" +done + +# Select appropriate email arg +case "$email" in + "") email_arg="--register-unsafely-without-email" ;; + *) email_arg="--email $email" ;; +esac + +# Enable staging mode if needed +if [ $staging != "0" ]; then staging_arg="--staging"; fi + +docker-compose run --rm --entrypoint "\ + certbot certonly --webroot -w /var/www/certbot \ + $staging_arg \ + $email_arg \ + $domain_args \ + --rsa-key-size $rsa_key_size \ + --agree-tos \ + --force-renewal" certbot +echo + +echo "### Reloading nginx ..." +docker-compose exec nginx nginx -s reload diff --git a/infra/scripts/init-nginx-converter.sh b/infra/scripts/init-nginx-converter.sh new file mode 100644 index 00000000..861b2aeb --- /dev/null +++ b/infra/scripts/init-nginx-converter.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# .env 파일 로드 +if [ -f /home/ubuntu/moabam/infra/.env ]; then + source /home/ubuntu/moabam/infra/.env +fi + +export SERVER_DOMAIN=${SERVER_DOMAIN} +export SERVER_PORT=${SERVER_PORT} +export BLUE_CONTAINER=${BLUE_CONTAINER} + +envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/http-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/http-server.conf +envsubst '$SERVER_DOMAIN' < /home/ubuntu/moabam/infra/nginx/templates/ssl-server.template > /home/ubuntu/moabam/infra/nginx/conf.d/ssl-server.conf +envsubst '$BLUE_CONTAINER $SERVER_PORT' < /home/ubuntu/moabam/infra/nginx/templates/upstream.template > /home/ubuntu/moabam/infra/nginx/conf.d/upstream.conf diff --git a/src/docs/asciidoc/coupon.adoc b/src/docs/asciidoc/coupon.adoc new file mode 100644 index 00000000..222be81b --- /dev/null +++ b/src/docs/asciidoc/coupon.adoc @@ -0,0 +1,112 @@ +== 쿠폰(Coupon) + + 쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다. + +--- + +=== 쿠폰 생성 + + 관리자가 쿠폰을 생성합니다. + +[discrete] +==== 요청 + +include::{snippets}/admins/coupons/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/admins/coupons/http-response.adoc[] + +--- + +=== 쿠폰 삭제 + + 관리자가 쿠폰 ID와 일치하는 쿠폰을 삭제합니다. + +[discrete] +==== 요청 + +include::{snippets}/admins/coupons/couponId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/admins/coupons/couponId/http-response.adoc[] + +--- + +=== 특정 쿠폰 조회 + + 관리자 혹은 사용자가 특정 ID와 일치하는 쿠폰을 조회합니다. + +==== 요청 + +include::{snippets}/coupons/couponId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/coupons/couponId/http-response.adoc[] + +--- + +=== 상태에 따른 쿠폰들을 조회 + + 관리자 혹은 사용자가 날짜 상태에 따라 쿠폰들을 조회합니다. + +==== 요청 + +include::{snippets}/coupons/search/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/coupons/search/http-response.adoc[] + +--- + +=== 특정 쿠폰에 대해 발급 + + 사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다. + +==== 요청 + +include::{snippets}/coupons/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/coupons/http-response.adoc[] + +--- + +=== 특정 사용자의 쿠폰 보관함을 조회 + + 사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다. + +==== 요청 + +include::{snippets}/my-coupons/couponId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/my-coupons/couponId/http-response.adoc[] + +--- + +=== 쿠폰을 사용 + + 사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다. + +==== 요청 + +include::{snippets}/my-coupons/couponWalletId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/my-coupons/couponWalletId/http-response.adoc[] + +--- diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..ea559aeb --- /dev/null +++ b/src/docs/asciidoc/index.adoc @@ -0,0 +1,76 @@ += MOABAM API 문서 +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toc-title: 목차 +:toclevels: 3 +:sectlinks: +:sectnums: + +== 개요 + +이 API 문서는 'MOABAM' 프로젝트의 산출물입니다. + +=== API 서버 경로 + +[cols="2,5,3"] +|==== +|환경 |DNS |비고 +|개발(dev) | link:[dev.moabam.com] | +|운영(prod) | link:[www.moabam.com] | +|==== + +[NOTE] +==== +해당 프로젝트 API 문서는 [특이사항]입니다. +==== + +[CAUTION] +==== +해당 프로젝트 API 문서는 [주의사항]입니다. +==== + +=== 응답형식 + +프로젝트는 다음과 같은 응답형식을 제공합니다. + +==== 정상(2XX) + +|==== +|응답데이터가 없는 경우|응답데이터가 있는 경우 + +a| +[source,json] +---- +{ + +} +---- + +a| +[source,json] +---- +{ + "name": "Hong-Dosan" +} +---- +|==== + +==== 상태코드(HttpStatus) + +응답시 다음과 같은 응답상태 헤더, 응답코드 및 응답메시지를 제공합니다. + +[cols="5,5"] +|==== +|HttpStatus |설명 + +|`OK(200)` |정상 응답 +|`CREATED(201)` |새로운 리소스 생성 +|`BAD_REQUEST(400)`|요청값 누락, 잘못된 기입 +|`UNAUTHORIZED(401)`|비인증 요청 +|`NOT_FOUND(404)`|요청값 누락, 잘못된 기입, 비인가 접속 등 +|`CONFLICT(409)`|요청값 중복 +|`INTERNAL_SERVER_ERROR(500)`|알 수 없는 서버 에러가 발생했습니다. 관리자에게 문의하세요. + +|==== diff --git a/src/docs/asciidoc/notification.adoc b/src/docs/asciidoc/notification.adoc new file mode 100644 index 00000000..2a2af666 --- /dev/null +++ b/src/docs/asciidoc/notification.adoc @@ -0,0 +1,34 @@ +== 알림(Notification) + + 콕 찌르기 알림, FCM Token 저장 기능을 제공합니다. + +=== 콕 찌르기 알림 + + 1) 특정 방의 사용자가 다른 사용자를 콕 찌릅니다. + 2) 서버에서 콕 찌를 대상의 FCM Token 여부를 검증합니다. + 3) Firebase 서버에 FCM Push Messaing 알림을 비동기로 요청합니다. + 4) Firebase 서버에서 FCM Token으로 식별된 기기에 알림을 보냅니다. + +[discrete] +==== 요청 + +include::{snippets}/notifications/rooms/roomId/members/memberId/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/notifications/rooms/roomId/members/memberId/http-response.adoc[] + +=== FCM TOKEN 저장 + + 1) 특정 사용자의 FCM-TOKEN을 받아서 REDIS DB에 저장합니다. + +[discrete] +==== 요청 + +include::{snippets}/notifications/http-request.adoc[] + +[discrete] +==== 응답 + +include::{snippets}/notifications/http-response.adoc[] diff --git a/src/main/java/com/moabam/MoabamServerApplication.java b/src/main/java/com/moabam/MoabamServerApplication.java index 5390acc3..e2dbce1d 100644 --- a/src/main/java/com/moabam/MoabamServerApplication.java +++ b/src/main/java/com/moabam/MoabamServerApplication.java @@ -2,12 +2,13 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +@ConfigurationPropertiesScan @SpringBootApplication public class MoabamServerApplication { public static void main(String[] args) { SpringApplication.run(MoabamServerApplication.class, args); } - } diff --git a/src/main/java/com/moabam/admin/application/admin/AdminMapper.java b/src/main/java/com/moabam/admin/application/admin/AdminMapper.java new file mode 100644 index 00000000..648c2e4d --- /dev/null +++ b/src/main/java/com/moabam/admin/application/admin/AdminMapper.java @@ -0,0 +1,16 @@ +package com.moabam.admin.application.admin; + +import com.moabam.admin.domain.admin.Admin; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AdminMapper { + + public static Admin toAdmin(Long socialId) { + return Admin.builder() + .socialId(String.valueOf(socialId)) + .build(); + } +} diff --git a/src/main/java/com/moabam/admin/application/admin/AdminService.java b/src/main/java/com/moabam/admin/application/admin/AdminService.java new file mode 100644 index 00000000..01243bd7 --- /dev/null +++ b/src/main/java/com/moabam/admin/application/admin/AdminService.java @@ -0,0 +1,57 @@ +package com.moabam.admin.application.admin; + +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.admin.domain.admin.Admin; +import com.moabam.admin.domain.admin.AdminRepository; +import com.moabam.api.application.auth.mapper.AuthMapper; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminService { + + @Value("${admin}") + private String adminLoginKey; + + private final AdminRepository adminRepository; + + public void validate(String state) { + if (!adminLoginKey.equals(state)) { + throw new BadRequestException(ErrorMessage.LOGIN_FAILED_ADMIN_KEY); + } + } + + @Transactional + public LoginResponse signUpOrLogin(AuthorizationTokenInfoResponse authorizationTokenInfoResponse) { + return login(authorizationTokenInfoResponse); + } + + private LoginResponse login(AuthorizationTokenInfoResponse authorizationTokenInfoResponse) { + Optional admin = adminRepository.findBySocialId(String.valueOf(authorizationTokenInfoResponse.id())); + Admin loginMember = admin.orElseGet(() -> signUp(authorizationTokenInfoResponse.id())); + + return AuthMapper.toLoginResponse(loginMember, admin.isEmpty()); + } + + private Admin signUp(Long socialId) { + Admin admin = AdminMapper.toAdmin(socialId); + + return adminRepository.save(admin); + } + + public Admin findMember(Long id) { + return adminRepository.findById(id).orElseThrow(() -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moabam/admin/domain/admin/Admin.java b/src/main/java/com/moabam/admin/domain/admin/Admin.java new file mode 100644 index 00000000..eeff1bc3 --- /dev/null +++ b/src/main/java/com/moabam/admin/domain/admin/Admin.java @@ -0,0 +1,53 @@ +package com.moabam.admin.domain.admin; + +import static com.moabam.global.common.util.RandomUtils.*; +import static java.util.Objects.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Admin extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "nickname", unique = true) + private String nickname; + + @Column(name = "social_id", nullable = false, unique = true) + private String socialId; + + @Enumerated(EnumType.STRING) + @Column(name = "role", nullable = false) + @ColumnDefault("'ADMIN'") + private Role role; + + @Builder + private Admin(String socialId) { + this.socialId = requireNonNull(socialId); + this.nickname = createNickName(); + this.role = Role.ADMIN; + } + + private String createNickName() { + return "오목눈이#" + randomStringValues(); + } +} diff --git a/src/main/java/com/moabam/admin/domain/admin/AdminRepository.java b/src/main/java/com/moabam/admin/domain/admin/AdminRepository.java new file mode 100644 index 00000000..e4878786 --- /dev/null +++ b/src/main/java/com/moabam/admin/domain/admin/AdminRepository.java @@ -0,0 +1,10 @@ +package com.moabam.admin.domain.admin; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminRepository extends JpaRepository { + + Optional findBySocialId(String socialId); +} diff --git a/src/main/java/com/moabam/admin/presentation/admin/AdminController.java b/src/main/java/com/moabam/admin/presentation/admin/AdminController.java new file mode 100644 index 00000000..cea5bb99 --- /dev/null +++ b/src/main/java/com/moabam/admin/presentation/admin/AdminController.java @@ -0,0 +1,41 @@ +package com.moabam.admin.presentation.admin; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.admin.application.admin.AdminService; +import com.moabam.api.application.auth.AuthorizationService; +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.api.dto.auth.LoginResponse; + +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/admins") +@RequiredArgsConstructor +public class AdminController { + + private final AuthorizationService authorizationService; + private final AdminService adminService; + + @PostMapping("/login/kakao/oauth") + @ResponseStatus(HttpStatus.OK) + public LoginResponse authorizationTokenIssue(@RequestBody AuthorizationCodeResponse authorizationCodeResponse, + HttpServletResponse httpServletResponse) { + adminService.validate(authorizationCodeResponse.state()); + AuthorizationTokenResponse tokenResponse = authorizationService.requestAdminToken(authorizationCodeResponse); + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = + authorizationService.requestTokenInfo(tokenResponse); + LoginResponse loginResponse = adminService.signUpOrLogin(authorizationTokenInfoResponse); + authorizationService.issueServiceToken(httpServletResponse, loginResponse.publicClaim()); + + return loginResponse; + } +} diff --git a/src/main/java/com/moabam/api/application/auth/AuthorizationService.java b/src/main/java/com/moabam/api/application/auth/AuthorizationService.java new file mode 100644 index 00000000..ed2d0840 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/AuthorizationService.java @@ -0,0 +1,239 @@ +package com.moabam.api.application.auth; + +import java.util.Arrays; + +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +import com.moabam.admin.application.admin.AdminService; +import com.moabam.api.application.auth.mapper.AuthMapper; +import com.moabam.api.application.auth.mapper.AuthorizationMapper; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.auth.repository.TokenRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.auth.AuthorizationCodeRequest; +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenRequest; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.common.util.CookieUtils; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.config.AllowOriginConfig; +import com.moabam.global.config.OAuthConfig; +import com.moabam.global.config.TokenConfig; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AuthorizationService { + + private final FcmService fcmService; + private final OAuthConfig oAuthConfig; + private final TokenConfig tokenConfig; + private final OAuth2AuthorizationServerRequestService oauth2AuthorizationServerRequestService; + private final MemberService memberService; + private final AdminService adminService; + private final JwtProviderService jwtProviderService; + private final TokenRepository tokenRepository; + private final AllowOriginConfig allowOriginsConfig; + + public void redirectToLoginPage(HttpServletResponse httpServletResponse) { + String authorizationCodeUri = getAuthorizationCodeUri(); + oauth2AuthorizationServerRequestService.loginRequest(httpServletResponse, authorizationCodeUri); + } + + public AuthorizationTokenResponse requestAdminToken(AuthorizationCodeResponse authorizationCodeResponse) { + validAuthorizationGrant(authorizationCodeResponse.code()); + + return issueTokenToAuthorizationServer(authorizationCodeResponse.code(), + oAuthConfig.provider().adminRedirectUri()); + } + + public AuthorizationTokenResponse requestToken(AuthorizationCodeResponse authorizationCodeResponse) { + validAuthorizationGrant(authorizationCodeResponse.code()); + + return issueTokenToAuthorizationServer(authorizationCodeResponse.code(), oAuthConfig.provider().redirectUri()); + } + + public AuthorizationTokenInfoResponse requestTokenInfo(AuthorizationTokenResponse authorizationTokenResponse) { + String tokenValue = generateTokenValue(authorizationTokenResponse.accessToken()); + ResponseEntity authorizationTokenInfoResponse = + oauth2AuthorizationServerRequestService + .tokenInfoRequest(oAuthConfig.provider().tokenInfo(), tokenValue); + + return authorizationTokenInfoResponse.getBody(); + } + + public LoginResponse signUpOrLogin(HttpServletResponse httpServletResponse, + AuthorizationTokenInfoResponse authorizationTokenInfoResponse) { + LoginResponse loginResponse = memberService.login(authorizationTokenInfoResponse); + issueServiceToken(httpServletResponse, loginResponse.publicClaim()); + + return loginResponse; + } + + public void issueServiceToken(HttpServletResponse response, PublicClaim publicClaim) { + String accessToken = jwtProviderService.provideAccessToken(publicClaim); + String refreshToken = jwtProviderService.provideRefreshToken(publicClaim.role()); + TokenSaveValue tokenSaveRequest = AuthMapper.toTokenSaveValue(refreshToken, null); + + tokenRepository.saveToken(publicClaim.id(), tokenSaveRequest, publicClaim.role()); + + String domain = getDomain(publicClaim.role()); + + response.addCookie(CookieUtils.typeCookie("Bearer", tokenConfig.getRefreshExpire(), domain)); + response.addCookie(CookieUtils + .tokenCookie("access_token", accessToken, tokenConfig.getRefreshExpire(), domain)); + response.addCookie(CookieUtils + .tokenCookie("refresh_token", refreshToken, tokenConfig.getRefreshExpire(), domain)); + } + + public void validTokenPair(Long id, String oldRefreshToken, Role role) { + TokenSaveValue tokenSaveValue = tokenRepository.getTokenSaveValue(id, role); + + if (!tokenSaveValue.refreshToken().equals(oldRefreshToken)) { + tokenRepository.delete(id, role); + + throw new UnauthorizedException(ErrorMessage.AUTHENTICATE_FAIL); + } + } + + public void logout(AuthMember authMember, + HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + removeToken(httpServletRequest, httpServletResponse); + tokenRepository.delete(authMember.id(), authMember.role()); + fcmService.deleteTokenByMemberId(authMember.id()); + } + + public void removeToken(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + if (httpServletRequest.getCookies() == null) { + return; + } + + Arrays.stream(httpServletRequest.getCookies()).forEach(cookie -> { + if (cookie.getName().contains("token")) { + httpServletResponse.addCookie(CookieUtils.deleteCookie(cookie)); + } + }); + } + + @Transactional + public void unLinkMember(AuthMember authMember) { + memberService.validateMemberToDelete(authMember.id()); + Member member = memberService.findMember(authMember.id()); + unlinkRequest(member.getSocialId()); + memberService.delete(member); + } + + private String getDomain(Role role) { + if (role.equals(Role.ADMIN)) { + return allowOriginsConfig.adminDomain(); + } + + return allowOriginsConfig.domain(); + } + + private void unlinkRequest(String socialId) { + try { + oauth2AuthorizationServerRequestService.unlinkMemberRequest(oAuthConfig.provider().unlink(), + oAuthConfig.client().adminKey(), unlinkRequestParam(socialId)); + log.info("회원 탈퇴 성공 : [socialId={}]", socialId); + } catch (BadRequestException badRequestException) { + log.warn("회원 탈퇴요청 실패 : 카카오 연결 오류"); + throw new BadRequestException(ErrorMessage.UNLINK_REQUEST_FAIL_ROLLBACK_SUCCESS); + } + } + + private MultiValueMap unlinkRequestParam(String socialId) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("target_id_type", "user_id"); + params.add("target_id", socialId); + + return params; + } + + private String getAuthorizationCodeUri() { + AuthorizationCodeRequest authorizationCodeRequest = AuthorizationMapper.toAuthorizationCodeRequest(oAuthConfig); + return generateQueryParamsWith(authorizationCodeRequest); + } + + private String generateTokenValue(String token) { + return "Bearer" + GlobalConstant.SPACE + token; + } + + private String generateQueryParamsWith(AuthorizationCodeRequest authorizationCodeRequest) { + UriComponentsBuilder authorizationCodeUri = + UriComponentsBuilder.fromUriString( + oAuthConfig.provider() + .authorizationUri()) + .queryParam("response_type", "code") + .queryParam("client_id", authorizationCodeRequest.clientId()) + .queryParam("redirect_uri", authorizationCodeRequest.redirectUri()); + + if (authorizationCodeRequest.scope() != null && !authorizationCodeRequest.scope().isEmpty()) { + String scopes = String.join(",", authorizationCodeRequest.scope()); + authorizationCodeUri.queryParam("scope", scopes); + } + + return authorizationCodeUri.toUriString(); + } + + private void validAuthorizationGrant(String code) { + if (code == null) { + throw new BadRequestException(ErrorMessage.GRANT_FAILED); + } + } + + private AuthorizationTokenResponse issueTokenToAuthorizationServer(String code, String redirectUri) { + AuthorizationTokenRequest authorizationTokenRequest = + AuthorizationMapper.toAuthorizationTokenRequest(oAuthConfig, code, redirectUri); + MultiValueMap uriParams = generateTokenRequest(authorizationTokenRequest); + ResponseEntity authorizationTokenResponse = + oauth2AuthorizationServerRequestService + .requestAuthorizationServer(oAuthConfig.provider().tokenUri(), uriParams); + + return authorizationTokenResponse.getBody(); + } + + private MultiValueMap generateTokenRequest(AuthorizationTokenRequest authorizationTokenRequest) { + MultiValueMap contents = new LinkedMultiValueMap<>(); + contents.add("grant_type", authorizationTokenRequest.grantType()); + contents.add("client_id", authorizationTokenRequest.clientId()); + contents.add("redirect_uri", authorizationTokenRequest.redirectUri()); + contents.add("code", authorizationTokenRequest.code()); + + if (authorizationTokenRequest.clientSecret() != null) { + contents.add("client_secret", authorizationTokenRequest.clientSecret()); + } + + return contents; + } + + public void validMemberExist(Long id, Role role) { + if (role.equals(Role.ADMIN)) { + adminService.findMember(id); + + return; + } + + memberService.findMember(id); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/JwtAuthenticationService.java b/src/main/java/com/moabam/api/application/auth/JwtAuthenticationService.java new file mode 100644 index 00000000..52c652f9 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/JwtAuthenticationService.java @@ -0,0 +1,59 @@ +package com.moabam.api.application.auth; + +import java.nio.charset.StandardCharsets; +import java.security.Key; + +import org.json.JSONObject; +import org.springframework.stereotype.Service; + +import com.moabam.api.application.auth.mapper.AuthorizationMapper; +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.config.TokenConfig; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class JwtAuthenticationService { + + private final TokenConfig tokenConfig; + + public boolean isTokenExpire(String token, Role role) { + try { + Key key = getSecret(role); + + Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + return false; + } catch (ExpiredJwtException expiredJwtException) { + return true; + } catch (Exception exception) { + throw new UnauthorizedException(ErrorMessage.AUTHENTICATE_FAIL); + } + } + + private Key getSecret(Role role) { + if (role.equals(Role.ADMIN)) { + return tokenConfig.getAdminKey(); + } + + return tokenConfig.getKey(); + } + + public PublicClaim parseClaim(String token) { + String claims = token.split("\\.")[1]; + byte[] claimsBytes = Decoders.BASE64URL.decode(claims); + String decodedClaims = new String(claimsBytes, StandardCharsets.UTF_8); + JSONObject jsonObject = new JSONObject(decodedClaims); + + return AuthorizationMapper.toPublicClaim(jsonObject); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/JwtProviderService.java b/src/main/java/com/moabam/api/application/auth/JwtProviderService.java new file mode 100644 index 00000000..816985f2 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/JwtProviderService.java @@ -0,0 +1,63 @@ +package com.moabam.api.application.auth; + +import java.security.Key; +import java.util.Date; + +import org.springframework.stereotype.Service; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.config.TokenConfig; + +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class JwtProviderService { + + private final TokenConfig tokenConfig; + + public String provideAccessToken(PublicClaim publicClaim) { + return generateIdToken(publicClaim, tokenConfig.getAccessExpire()); + } + + public String provideRefreshToken(Role role) { + return generateCommonInfo(tokenConfig.getRefreshExpire(), role); + } + + private String generateIdToken(PublicClaim publicClaim, long expireTime) { + return commonInfo(expireTime, publicClaim.role()) + .claim("id", publicClaim.id()) + .claim("nickname", publicClaim.nickname()) + .claim("role", publicClaim.role()) + .compact(); + } + + private String generateCommonInfo(long expireTime, Role role) { + return commonInfo(expireTime, role).compact(); + } + + private JwtBuilder commonInfo(long expireTime, Role role) { + Date issueDate = new Date(); + Date expireDate = new Date(issueDate.getTime() + expireTime); + + return Jwts.builder() + .setHeaderParam("alg", "HS256") + .setHeaderParam("typ", "JWT") + .setIssuer(tokenConfig.getIss()) + .setIssuedAt(issueDate) + .setExpiration(expireDate) + .signWith(getSecretKey(role), SignatureAlgorithm.HS256); + } + + private Key getSecretKey(Role role) { + if (role.equals(Role.ADMIN)) { + return tokenConfig.getAdminKey(); + } + + return tokenConfig.getKey(); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestService.java b/src/main/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestService.java new file mode 100644 index 00000000..44db540c --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestService.java @@ -0,0 +1,71 @@ +package com.moabam.api.application.auth; + +import java.io.IOException; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.handler.RestTemplateResponseHandler; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletResponse; + +@Service +public class OAuth2AuthorizationServerRequestService { + + private final RestTemplate restTemplate; + + public OAuth2AuthorizationServerRequestService() { + restTemplate = new RestTemplateBuilder() + .errorHandler(new RestTemplateResponseHandler()) + .build(); + } + + public void loginRequest(HttpServletResponse httpServletResponse, String authorizationCodeUri) { + try { + httpServletResponse.setContentType(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + httpServletResponse.sendRedirect(authorizationCodeUri); + } catch (IOException e) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + } + + public ResponseEntity requestAuthorizationServer(String tokenUri, + MultiValueMap uriParams) { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8); + HttpEntity> httpEntity = new HttpEntity<>(uriParams, headers); + + return restTemplate.exchange(tokenUri, HttpMethod.POST, httpEntity, AuthorizationTokenResponse.class); + } + + public ResponseEntity tokenInfoRequest(String tokenInfoUri, String tokenValue) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", tokenValue); + HttpEntity httpEntity = new HttpEntity<>(headers); + + return restTemplate.exchange(tokenInfoUri, HttpMethod.GET, httpEntity, AuthorizationTokenInfoResponse.class); + } + + public void unlinkMemberRequest(String unlinkUri, String adminKey, MultiValueMap params) { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8); + headers.add("Authorization", "KakaoAK " + adminKey); + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + restTemplate.exchange(unlinkUri, HttpMethod.POST, httpEntity, Void.class); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/mapper/AuthMapper.java b/src/main/java/com/moabam/api/application/auth/mapper/AuthMapper.java new file mode 100644 index 00000000..0fe48a56 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/mapper/AuthMapper.java @@ -0,0 +1,43 @@ +package com.moabam.api.application.auth.mapper; + +import com.moabam.admin.domain.admin.Admin; +import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.global.auth.model.PublicClaim; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class AuthMapper { + + public static LoginResponse toLoginResponse(Member member, boolean isSignUp) { + return LoginResponse.builder() + .publicClaim(PublicClaim.builder() + .id(member.getId()) + .nickname(member.getNickname()) + .role(member.getRole()) + .build()) + .isSignUp(isSignUp) + .build(); + } + + public static LoginResponse toLoginResponse(Admin admin, boolean isSignUp) { + return LoginResponse.builder() + .publicClaim(PublicClaim.builder() + .id(admin.getId()) + .nickname(admin.getNickname()) + .role(admin.getRole()) + .build()) + .isSignUp(isSignUp) + .build(); + } + + public static TokenSaveValue toTokenSaveValue(String refreshToken, String ip) { + return TokenSaveValue.builder() + .refreshToken(refreshToken) + .loginIp(ip) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/mapper/AuthorizationMapper.java b/src/main/java/com/moabam/api/application/auth/mapper/AuthorizationMapper.java new file mode 100644 index 00000000..3e566153 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/mapper/AuthorizationMapper.java @@ -0,0 +1,48 @@ +package com.moabam.api.application.auth.mapper; + +import org.json.JSONObject; + +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.auth.AuthorizationCodeRequest; +import com.moabam.api.dto.auth.AuthorizationTokenRequest; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.config.OAuthConfig; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class AuthorizationMapper { + + public static AuthorizationCodeRequest toAuthorizationCodeRequest(OAuthConfig oAuthConfig) { + return AuthorizationCodeRequest.builder() + .clientId(oAuthConfig.client().clientId()) + .redirectUri(oAuthConfig.provider().redirectUri()) + .scope(oAuthConfig.client().scope()) + .build(); + } + + public static AuthorizationTokenRequest toAuthorizationTokenRequest(OAuthConfig oAuthConfig, String code, + String redirectUri) { + return AuthorizationTokenRequest.builder() + .grantType(oAuthConfig.client().authorizationGrantType()) + .clientId(oAuthConfig.client().clientId()) + .redirectUri(redirectUri) + .code(code) + .clientSecret(oAuthConfig.client().clientSecret()) + .build(); + } + + public static PublicClaim toPublicClaim(JSONObject jsonObject) { + return PublicClaim.builder() + .id(Long.valueOf(jsonObject.get("id").toString())) + .nickname(jsonObject.getString("nickname")) + .role(jsonObject.getEnum(Role.class, "role")) + .build(); + } + + public static AuthMember toAuthMember(PublicClaim publicClaim) { + return new AuthMember(publicClaim.id(), publicClaim.nickname(), publicClaim.role()); + } +} diff --git a/src/main/java/com/moabam/api/application/auth/mapper/PathMapper.java b/src/main/java/com/moabam/api/application/auth/mapper/PathMapper.java new file mode 100644 index 00000000..4ef9db71 --- /dev/null +++ b/src/main/java/com/moabam/api/application/auth/mapper/PathMapper.java @@ -0,0 +1,43 @@ +package com.moabam.api.application.auth.mapper; + +import static java.util.Objects.*; + +import java.util.List; + +import org.springframework.http.HttpMethod; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.handler.PathResolver; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PathMapper { + + public static PathResolver.Path parsePath(String uri) { + return parsePath(uri, null, null); + } + + public static PathResolver.Path pathWithRole(String uri, List params) { + return parsePath(uri, params, null); + } + + public static PathResolver.Path pathWithMethod(String uri, List params) { + return parsePath(uri, null, params); + } + + private static PathResolver.Path parsePath(String uri, List roles, List methods) { + PathResolver.Path.PathBuilder pathBuilder = PathResolver.Path.builder().uri(uri); + + if (nonNull(roles)) { + pathBuilder.roles(roles); + } + + if (nonNull(methods)) { + pathBuilder.httpMethods(methods); + } + + return pathBuilder.build(); + } +} diff --git a/src/main/java/com/moabam/api/application/bug/BugMapper.java b/src/main/java/com/moabam/api/application/bug/BugMapper.java new file mode 100644 index 00000000..af91b206 --- /dev/null +++ b/src/main/java/com/moabam/api/application/bug/BugMapper.java @@ -0,0 +1,83 @@ +package com.moabam.api.application.bug; + +import java.util.List; + +import com.moabam.api.application.payment.PaymentMapper; +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugHistory; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.dto.bug.BugHistoryItemResponse; +import com.moabam.api.dto.bug.BugHistoryResponse; +import com.moabam.api.dto.bug.BugHistoryWithPayment; +import com.moabam.api.dto.bug.BugResponse; +import com.moabam.global.common.util.DateUtils; +import com.moabam.global.common.util.StreamUtils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class BugMapper { + + public static BugResponse toBugResponse(Bug bug) { + return BugResponse.builder() + .morningBug(bug.getMorningBug()) + .nightBug(bug.getNightBug()) + .goldenBug(bug.getGoldenBug()) + .build(); + } + + public static BugHistoryItemResponse toBugHistoryItemResponse(BugHistoryWithPayment dto) { + return BugHistoryItemResponse.builder() + .id(dto.id()) + .bugType(dto.bugType()) + .actionType(dto.actionType()) + .quantity(dto.quantity()) + .date(DateUtils.format(dto.createdAt())) + .payment(PaymentMapper.toPaymentResponse(dto.payment())) + .build(); + } + + public static BugHistoryResponse toBugHistoryResponse(List dtoList) { + return BugHistoryResponse.builder() + .history(StreamUtils.map(dtoList, BugMapper::toBugHistoryItemResponse)) + .build(); + } + + public static BugHistory toUseBugHistory(Long memberId, BugType bugType, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(bugType) + .actionType(BugActionType.USE) + .quantity(quantity) + .build(); + } + + public static BugHistory toChargeBugHistory(Long memberId, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(BugType.GOLDEN) + .actionType(BugActionType.CHARGE) + .quantity(quantity) + .build(); + } + + public static BugHistory toRewardBugHistory(Long memberId, BugType bugType, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(bugType) + .actionType(BugActionType.REWARD) + .quantity(quantity) + .build(); + } + + public static BugHistory toCouponBugHistory(Long memberId, BugType bugType, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(bugType) + .actionType(BugActionType.COUPON) + .quantity(quantity) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/bug/BugService.java b/src/main/java/com/moabam/api/application/bug/BugService.java new file mode 100644 index 00000000..f1246d45 --- /dev/null +++ b/src/main/java/com/moabam/api/application/bug/BugService.java @@ -0,0 +1,139 @@ +package com.moabam.api.application.bug; + +import static com.moabam.api.domain.product.ProductType.*; +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.payment.PaymentMapper; +import com.moabam.api.application.product.ProductMapper; +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.bug.repository.BugHistoryRepository; +import com.moabam.api.domain.bug.repository.BugHistorySearchRepository; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.repository.ProductRepository; +import com.moabam.api.dto.bug.BugHistoryResponse; +import com.moabam.api.dto.bug.BugHistoryWithPayment; +import com.moabam.api.dto.bug.BugResponse; +import com.moabam.api.dto.product.ProductsResponse; +import com.moabam.api.dto.product.PurchaseProductRequest; +import com.moabam.api.dto.product.PurchaseProductResponse; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class BugService { + + private final MemberService memberService; + private final BugHistoryRepository bugHistoryRepository; + private final BugHistorySearchRepository bugHistorySearchRepository; + private final ProductRepository productRepository; + private final PaymentRepository paymentRepository; + private final CouponWalletSearchRepository couponWalletSearchRepository; + + public BugResponse getBug(Long memberId) { + Bug bug = getByMemberId(memberId); + + return BugMapper.toBugResponse(bug); + } + + public BugHistoryResponse getBugHistory(Long memberId) { + List history = bugHistorySearchRepository.findByMemberIdWithPayment(memberId); + + return BugMapper.toBugHistoryResponse(history); + } + + public ProductsResponse getBugProducts() { + List products = productRepository.findAllByType(BUG); + + return ProductMapper.toProductsResponse(products); + } + + @Transactional + public PurchaseProductResponse purchaseBugProduct(Long memberId, Long productId, PurchaseProductRequest request) { + Product product = getProductById(productId); + Payment payment = PaymentMapper.toPayment(memberId, product); + + if (!isNull(request.couponWalletId())) { + CouponWallet couponWallet = getCouponWallet(request.couponWalletId(), memberId); + payment.applyCoupon(couponWallet); + } + paymentRepository.save(payment); + + return ProductMapper.toPurchaseProductResponse(payment); + } + + @Transactional + public void use(Member member, BugType bugType, int count) { + if (count == 0) { + return; + } + + Bug bug = member.getBug(); + + bug.use(bugType, count); + bugHistoryRepository.save(BugMapper.toUseBugHistory(member.getId(), bugType, count)); + } + + @Transactional + public void reward(Member member, BugType bugType, int count) { + if (count == 0) { + return; + } + + Bug bug = member.getBug(); + + bug.increase(bugType, count); + bugHistoryRepository.save(BugMapper.toRewardBugHistory(member.getId(), bugType, count)); + } + + @Transactional + public void charge(Long memberId, Product bugProduct) { + Bug bug = getByMemberId(memberId); + + bug.charge(bugProduct.getQuantity()); + bugHistoryRepository.save(BugMapper.toChargeBugHistory(memberId, bugProduct.getQuantity())); + } + + @Transactional + public void applyCoupon(Long memberId, BugType bugType, int count) { + if (count == 0) { + return; + } + + Bug bug = getByMemberId(memberId); + + bug.increase(bugType, count); + bugHistoryRepository.save(BugMapper.toCouponBugHistory(memberId, bugType, count)); + } + + private Bug getByMemberId(Long memberId) { + return memberService.findMember(memberId) + .getBug(); + } + + private Product getProductById(Long productId) { + return productRepository.findById(productId) + .orElseThrow(() -> new NotFoundException(PRODUCT_NOT_FOUND)); + } + + private CouponWallet getCouponWallet(Long couponWalletId, Long memberId) { + return couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)); + } +} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponManageService.java b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java new file mode 100644 index 00000000..3cda6f9a --- /dev/null +++ b/src/main/java/com/moabam/api/application/coupon/CouponManageService.java @@ -0,0 +1,101 @@ +package com.moabam.api.application.coupon; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.Set; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.moabam.api.application.notification.NotificationService; +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponManageRepository; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CouponManageService { + + private static final String SUCCESS_ISSUE_BODY = "%s 쿠폰 발행을 성공했습니다. 축하드립니다!"; + private static final String FAIL_ISSUE_BODY = "%s 쿠폰 발행을 실패했습니다. 다음 기회에!"; + private static final long ISSUE_SIZE = 10; + + private final ClockHolder clockHolder; + private final NotificationService notificationService; + + private final CouponRepository couponRepository; + private final CouponManageRepository couponManageRepository; + private final CouponWalletRepository couponWalletRepository; + + @Scheduled(fixedDelay = 1000) + public void issue() { + LocalDate now = clockHolder.date(); + Optional optionalCoupon = couponRepository.findByStartAt(now); + + if (optionalCoupon.isEmpty()) { + return; + } + + Coupon coupon = optionalCoupon.get(); + String couponName = coupon.getName(); + int maxCount = coupon.getMaxCount(); + int currentCount = couponManageRepository.getCount(couponName); + + if (maxCount <= currentCount) { + return; + } + + Set membersId = couponManageRepository.rangeQueue(couponName, currentCount, currentCount + ISSUE_SIZE); + + if (membersId.isEmpty()) { + return; + } + + for (Long memberId : membersId) { + couponWalletRepository.save(CouponWallet.create(memberId, coupon)); + notificationService.sendCouponIssueResult(memberId, couponName, SUCCESS_ISSUE_BODY); + } + + couponManageRepository.increase(couponName, membersId.size()); + } + + public void registerQueue(String couponName, Long memberId) { + double registerTime = System.currentTimeMillis(); + validateRegisterQueue(couponName, memberId); + couponManageRepository.addIfAbsentQueue(couponName, memberId, registerTime); + } + + public void delete(String couponName) { + couponManageRepository.deleteQueue(couponName); + couponManageRepository.deleteCount(couponName); + } + + private void validateRegisterQueue(String couponName, Long memberId) { + LocalDate now = clockHolder.date(); + Coupon coupon = couponRepository.findByNameAndStartAt(couponName, now) + .orElseThrow(() -> new NotFoundException(ErrorMessage.INVALID_COUPON_PERIOD)); + + if (couponManageRepository.hasValue(couponName, memberId)) { + throw new ConflictException(ErrorMessage.CONFLICT_COUPON_ISSUE); + } + + int maxCount = coupon.getMaxCount(); + int sizeQueue = couponManageRepository.sizeQueue(couponName); + + if (maxCount <= sizeQueue) { + notificationService.sendCouponIssueResult(memberId, couponName, FAIL_ISSUE_BODY); + throw new BadRequestException(ErrorMessage.INVALID_COUPON_STOCK_END); + } + } +} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponMapper.java b/src/main/java/com/moabam/api/application/coupon/CouponMapper.java new file mode 100644 index 00000000..a3cbe1c3 --- /dev/null +++ b/src/main/java/com/moabam/api/application/coupon/CouponMapper.java @@ -0,0 +1,67 @@ +package com.moabam.api.application.coupon; + +import java.util.List; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.dto.coupon.CouponResponse; +import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; +import com.moabam.global.common.util.StreamUtils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CouponMapper { + + public static Coupon toEntity(Long adminId, CreateCouponRequest coupon) { + return Coupon.builder() + .name(coupon.name()) + .description(coupon.description()) + .type(CouponType.from(coupon.type())) + .point(coupon.point()) + .maxCount(coupon.maxCount()) + .startAt(coupon.startAt()) + .openAt(coupon.openAt()) + .adminId(adminId) + .build(); + } + + // TODO : Admin Table 생성 시, 관리자 명 추가할 예정 + public static CouponResponse toResponse(Coupon coupon) { + return CouponResponse.builder() + .id(coupon.getId()) + .adminId(coupon.getAdminId()) + .name(coupon.getName()) + .description(coupon.getDescription()) + .point(coupon.getPoint()) + .maxCount(coupon.getMaxCount()) + .type(coupon.getType()) + .startAt(coupon.getStartAt()) + .openAt(coupon.getOpenAt()) + .build(); + } + + public static List toResponses(List coupons) { + return StreamUtils.map(coupons, CouponMapper::toResponse); + } + + public static MyCouponResponse toMyResponse(CouponWallet couponWallet) { + Coupon coupon = couponWallet.getCoupon(); + + return MyCouponResponse.builder() + .walletId(couponWallet.getId()) + .id(coupon.getId()) + .name(coupon.getName()) + .description(coupon.getDescription()) + .point(coupon.getPoint()) + .type(coupon.getType()) + .build(); + } + + public static List toMyResponses(List couponWallets) { + return StreamUtils.map(couponWallets, CouponMapper::toMyResponse); + } +} diff --git a/src/main/java/com/moabam/api/application/coupon/CouponService.java b/src/main/java/com/moabam/api/application/coupon/CouponService.java new file mode 100644 index 00000000..00ec0fa3 --- /dev/null +++ b/src/main/java/com/moabam/api/application/coupon/CouponService.java @@ -0,0 +1,147 @@ +package com.moabam.api.application.coupon; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.admin.application.admin.AdminService; +import com.moabam.api.application.bug.BugService; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponSearchRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.coupon.CouponResponse; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CouponService { + + private final ClockHolder clockHolder; + private final BugService bugService; + private final AdminService adminService; + private final CouponManageService couponManageService; + + private final CouponRepository couponRepository; + private final CouponSearchRepository couponSearchRepository; + private final CouponWalletRepository couponWalletRepository; + private final CouponWalletSearchRepository couponWalletSearchRepository; + + @Transactional + public void create(CreateCouponRequest request, Long adminId, Role role) { + validateAdminRole(role); + validateConflictName(request.name()); + validateConflictStartAt(request.startAt()); + validatePeriod(request.startAt(), request.openAt()); + + Coupon coupon = CouponMapper.toEntity(adminId, request); + + couponRepository.save(coupon); + } + + @Transactional + public void delete(Long couponId, Role role) { + validateAdminRole(role); + + Coupon coupon = couponRepository.findById(couponId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON)); + + couponRepository.delete(coupon); + couponManageService.delete(coupon.getName()); + } + + @Transactional + public void use(Long couponWalletId, Long memberId) { + CouponWallet couponWallet = couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)); + Coupon coupon = couponWallet.getCoupon(); + BugType bugType = coupon.getType().getBugType(); + + if (coupon.getType().isDiscount()) { + throw new BadRequestException(ErrorMessage.INVALID_DISCOUNT_COUPON); + } + + bugService.applyCoupon(memberId, bugType, coupon.getPoint()); + couponWalletRepository.delete(couponWallet); + } + + @Transactional + public void discount(Long couponWalletId, Long memberId) { + CouponWallet couponWallet = couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)); + Coupon coupon = couponWallet.getCoupon(); + + if (!coupon.getType().isDiscount()) { + throw new BadRequestException(ErrorMessage.INVALID_BUG_COUPON); + } + + couponWalletRepository.delete(couponWallet); + } + + public CouponResponse getById(Long couponId) { + Coupon coupon = couponRepository.findById(couponId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON)); + + return CouponMapper.toResponse(coupon); + } + + public List getAllByStatus(CouponStatusRequest request) { + LocalDate now = clockHolder.date(); + List coupons = couponSearchRepository.findAllByStatus(now, request); + + return CouponMapper.toResponses(coupons); + } + + public List getAllByWalletIdAndMemberId(Long couponWalletId, Long memberId) { + List couponWallets = + couponWalletSearchRepository.findAllByIdAndMemberId(couponWalletId, memberId); + + return CouponMapper.toMyResponses(couponWallets); + } + + private void validatePeriod(LocalDate startAt, LocalDate openAt) { + LocalDate now = clockHolder.date(); + + if (!now.isBefore(startAt)) { + throw new BadRequestException(ErrorMessage.INVALID_COUPON_START_AT_PERIOD); + } + + if (!openAt.isBefore(startAt)) { + throw new BadRequestException(ErrorMessage.INVALID_COUPON_OPEN_AT_PERIOD); + } + } + + private void validateAdminRole(Role role) { + if (!role.equals(Role.ADMIN)) { + throw new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND); + } + } + + private void validateConflictName(String couponName) { + if (couponRepository.existsByName(couponName)) { + throw new ConflictException(ErrorMessage.CONFLICT_COUPON_NAME); + } + } + + private void validateConflictStartAt(LocalDate startAt) { + if (couponRepository.existsByStartAt(startAt)) { + throw new ConflictException(ErrorMessage.CONFLICT_COUPON_START_AT); + } + } +} diff --git a/src/main/java/com/moabam/api/application/image/ImageService.java b/src/main/java/com/moabam/api/application/image/ImageService.java new file mode 100644 index 00000000..a70a783f --- /dev/null +++ b/src/main/java/com/moabam/api/application/image/ImageService.java @@ -0,0 +1,73 @@ +package com.moabam.api.application.image; + +import static com.moabam.global.error.model.ErrorMessage.IMAGE_CONVERT_FAIL; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import com.moabam.api.domain.image.ImageName; +import com.moabam.api.domain.image.ImageResizer; +import com.moabam.api.domain.image.ImageType; +import com.moabam.api.domain.image.NewImage; +import com.moabam.api.dto.room.CertifyRoomsRequest; +import com.moabam.api.infrastructure.s3.S3Manager; +import com.moabam.global.error.exception.BadRequestException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ImageService { + + private final S3Manager s3Manager; + + @Transactional + public List uploadImages(List multipartFiles, ImageType imageType) { + + List result = new ArrayList<>(); + + List imageResizers = multipartFiles.stream() + .map(multipartFile -> this.toImageResizer(multipartFile, imageType)) + .toList(); + + imageResizers.forEach(resizer -> { + resizer.resizeImageToFixedSize(imageType); + result.add(s3Manager.uploadImage(resizer.getResizedImage().getName(), resizer.getResizedImage())); + }); + + return result; + } + + public List getNewImages(CertifyRoomsRequest request) { + return request.getCertifyRoomsRequest().stream() + .map(certifyRoomRequest -> { + try { + return NewImage.of(String.valueOf(certifyRoomRequest.getRoutineId()), + certifyRoomRequest.getImage().getContentType(), certifyRoomRequest.getImage().getBytes()); + } catch (IOException e) { + throw new BadRequestException(IMAGE_CONVERT_FAIL); + } + }) + .toList(); + } + + @Transactional + public void deleteImage(String imageUrl) { + s3Manager.deleteImage(imageUrl); + } + + private ImageResizer toImageResizer(MultipartFile multipartFile, ImageType imageType) { + ImageName imageName = ImageName.of(multipartFile, imageType); + + return ImageResizer.builder() + .image(multipartFile) + .fileName(imageName.getFileName()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/item/ItemMapper.java b/src/main/java/com/moabam/api/application/item/ItemMapper.java new file mode 100644 index 00000000..f2cabfff --- /dev/null +++ b/src/main/java/com/moabam/api/application/item/ItemMapper.java @@ -0,0 +1,44 @@ +package com.moabam.api.application.item; + +import java.util.List; + +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.dto.item.ItemResponse; +import com.moabam.api.dto.item.ItemsResponse; +import com.moabam.global.common.util.StreamUtils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ItemMapper { + + public static ItemResponse toItemResponse(Item item) { + return ItemResponse.builder() + .id(item.getId()) + .type(item.getType().name()) + .category(item.getCategory().name()) + .name(item.getName()) + .image(item.getAwakeImage()) + .level(item.getUnlockLevel()) + .bugPrice(item.getBugPrice()) + .goldenBugPrice(item.getGoldenBugPrice()) + .build(); + } + + public static ItemsResponse toItemsResponse(Long itemId, List purchasedItems, List notPurchasedItems) { + return ItemsResponse.builder() + .defaultItemId(itemId) + .purchasedItems(StreamUtils.map(purchasedItems, ItemMapper::toItemResponse)) + .notPurchasedItems(StreamUtils.map(notPurchasedItems, ItemMapper::toItemResponse)) + .build(); + } + + public static Inventory toInventory(Long memberId, Item item) { + return Inventory.builder() + .memberId(memberId) + .item(item) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/item/ItemService.java b/src/main/java/com/moabam/api/application/item/ItemService.java new file mode 100644 index 00000000..b0f7f0f9 --- /dev/null +++ b/src/main/java/com/moabam/api/application/item/ItemService.java @@ -0,0 +1,92 @@ +package com.moabam.api.application.item; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.InventorySearchRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.item.repository.ItemSearchRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.item.ItemsResponse; +import com.moabam.api.dto.item.PurchaseItemRequest; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ItemService { + + private final MemberService memberService; + private final BugService bugService; + private final ItemRepository itemRepository; + private final ItemSearchRepository itemSearchRepository; + private final InventoryRepository inventoryRepository; + private final InventorySearchRepository inventorySearchRepository; + + public ItemsResponse getItems(Long memberId, ItemType type) { + Item defaultItem = getDefaultInventory(memberId, type).getItem(); + List purchasedItems = inventorySearchRepository.findItems(memberId, type); + List notPurchasedItems = itemSearchRepository.findNotPurchasedItems(memberId, type); + + return ItemMapper.toItemsResponse(defaultItem.getId(), purchasedItems, notPurchasedItems); + } + + @Transactional + public void purchaseItem(Long memberId, Long itemId, PurchaseItemRequest request) { + Item item = getItem(itemId); + Member member = memberService.findMember(memberId); + + validateAlreadyPurchased(memberId, itemId); + item.validatePurchasable(request.bugType(), member.getLevel()); + + int price = item.getPrice(request.bugType()); + + bugService.use(member, request.bugType(), price); + inventoryRepository.save(ItemMapper.toInventory(memberId, item)); + } + + @Transactional + public void selectItem(Long memberId, Long itemId) { + Member member = memberService.findMember(memberId); + Inventory inventory = getInventory(memberId, itemId); + + inventorySearchRepository.findDefault(memberId, inventory.getItemType()) + .ifPresent(Inventory::deselect); + inventory.select(member); + } + + private Item getItem(Long itemId) { + return itemRepository.findById(itemId) + .orElseThrow(() -> new NotFoundException(ITEM_NOT_FOUND)); + } + + private Inventory getInventory(Long memberId, Long itemId) { + return inventorySearchRepository.findOne(memberId, itemId) + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); + } + + private Inventory getDefaultInventory(Long memberId, ItemType type) { + return inventorySearchRepository.findDefault(memberId, type) + .orElseThrow(() -> new NotFoundException(DEFAULT_INVENTORY_NOT_FOUND)); + } + + private void validateAlreadyPurchased(Long memberId, Long itemId) { + inventorySearchRepository.findOne(memberId, itemId) + .ifPresent(inventory -> { + throw new ConflictException(INVENTORY_CONFLICT); + }); + } +} diff --git a/src/main/java/com/moabam/api/application/member/BadgeService.java b/src/main/java/com/moabam/api/application/member/BadgeService.java new file mode 100644 index 00000000..e89dc5dd --- /dev/null +++ b/src/main/java/com/moabam/api/application/member/BadgeService.java @@ -0,0 +1,32 @@ +package com.moabam.api.application.member; + +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; +import com.moabam.api.domain.member.repository.BadgeRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class BadgeService { + + private final BadgeRepository badgeRepository; + + public void createBadge(Long memberId, long certifyCount) { + Optional badgeType = BadgeType.getBadgeFrom(certifyCount); + + if (badgeType.isEmpty() + || badgeRepository.existsByMemberIdAndType(memberId, badgeType.get())) { + return; + } + + Badge badge = MemberMapper.toBadge(memberId, badgeType.get()); + badgeRepository.save(badge); + } +} diff --git a/src/main/java/com/moabam/api/application/member/MemberMapper.java b/src/main/java/com/moabam/api/application/member/MemberMapper.java new file mode 100644 index 00000000..7d80c808 --- /dev/null +++ b/src/main/java/com/moabam/api/application/member/MemberMapper.java @@ -0,0 +1,118 @@ +package com.moabam.api.application.member; + +import static com.moabam.global.common.util.GlobalConstant.*; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.member.BadgeResponse; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.MemberInfoSearchResponse; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.UpdateRanking; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class MemberMapper { + + public static Member toMember(Long socialId) { + return Member.builder() + .socialId(String.valueOf(socialId)) + .bug(Bug.builder().build()) + .build(); + } + + public static UpdateRanking toUpdateRanking(Member member) { + return UpdateRanking.builder() + .rankingInfo(toRankingInfo(member)) + .score(member.getTotalCertifyCount()) + .build(); + } + + public static MemberInfoSearchResponse toMemberInfoSearchResponse(List memberInfos) { + MemberInfo infos = memberInfos.get(0); + List badgeTypes = memberInfos.stream() + .map(MemberInfo::badges) + .filter(Objects::nonNull) + .toList(); + + return MemberInfoSearchResponse.builder() + .nickname(infos.nickname()) + .profileImage(infos.profileImage()) + .morningImage(infos.morningImage()) + .nightImage(infos.nightImage()) + .intro(infos.intro()) + .totalCertifyCount(infos.totalCertifyCount()) + .badges(new HashSet<>(badgeTypes)) + .goldenBug(infos.goldenBug()) + .morningBug(infos.morningBug()) + .nightBug(infos.nightBug()) + .build(); + } + + public static MemberInfoResponse toMemberInfoResponse(MemberInfoSearchResponse memberInfoSearchResponse) { + long certifyCount = memberInfoSearchResponse.totalCertifyCount(); + + return MemberInfoResponse.builder() + .nickname(memberInfoSearchResponse.nickname()) + .profileImage(memberInfoSearchResponse.profileImage()) + .intro(memberInfoSearchResponse.intro()) + .level(certifyCount / LEVEL_DIVISOR) + .exp(certifyCount % LEVEL_DIVISOR) + .birds(defaultSkins(memberInfoSearchResponse.morningImage(), memberInfoSearchResponse.nightImage())) + .badges(badgedNames(memberInfoSearchResponse.badges())) + .goldenBug(memberInfoSearchResponse.goldenBug()) + .morningBug(memberInfoSearchResponse.morningBug()) + .nightBug(memberInfoSearchResponse.nightBug()) + .build(); + } + + public static Inventory toInventory(Long memberId, Item item) { + return Inventory.builder() + .memberId(memberId) + .item(item) + .isDefault(true) + .build(); + } + + public static RankingInfo toRankingInfo(Member member) { + return RankingInfo.builder() + .memberId(member.getId()) + .nickname(member.getNickname()) + .image(member.getProfileImage()) + .build(); + } + + public static Badge toBadge(Long memberId, BadgeType badgeType) { + return Badge.builder() + .type(badgeType) + .memberId(memberId) + .build(); + } + + private static List badgedNames(Set badgeTypes) { + return BadgeType.memberBadgeMap(badgeTypes); + } + + private static Map defaultSkins(String morningImage, String nightImage) { + Map birdsSkin = new HashMap<>(); + birdsSkin.put(ItemType.MORNING.name(), morningImage); + birdsSkin.put(ItemType.NIGHT.name(), nightImage); + + return birdsSkin; + } +} diff --git a/src/main/java/com/moabam/api/application/member/MemberService.java b/src/main/java/com/moabam/api/application/member/MemberService.java new file mode 100644 index 00000000..ef3a761c --- /dev/null +++ b/src/main/java/com/moabam/api/application/member/MemberService.java @@ -0,0 +1,206 @@ +package com.moabam.api.application.member; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.auth.mapper.AuthMapper; +import com.moabam.api.application.ranking.RankingService; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.MemberInfoSearchResponse; +import com.moabam.api.dto.member.ModifyMemberRequest; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.UpdateRanking; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.common.util.BaseDataCode; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; + +import io.micrometer.common.util.StringUtils; +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MemberService { + + private final RankingService rankingService; + private final FcmService fcmService; + private final MemberRepository memberRepository; + private final InventoryRepository inventoryRepository; + private final ItemRepository itemRepository; + private final MemberSearchRepository memberSearchRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final ParticipantRepository participantRepository; + private final ClockHolder clockHolder; + + public Member findMember(Long memberId) { + return memberSearchRepository.findMember(memberId) + .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); + } + + @Transactional + public LoginResponse login(AuthorizationTokenInfoResponse authorizationTokenInfoResponse) { + Optional member = memberRepository.findBySocialId(String.valueOf(authorizationTokenInfoResponse.id())); + Member loginMember = member.orElseGet(() -> signUp(authorizationTokenInfoResponse.id())); + + return AuthMapper.toLoginResponse(loginMember, member.isEmpty()); + } + + public List getRoomMembers(List memberIds) { + return memberRepository.findAllById(memberIds); + } + + public void validateMemberToDelete(Long memberId) { + List participants = memberSearchRepository.findParticipantByMemberId(memberId); + + if (!participants.isEmpty()) { + throw new NotFoundException(MEMBER_NOT_FOUND); + } + } + + @Transactional + public void delete(Member member) { + List participants = participantSearchRepository.findAllByMemberIdParticipant(member.getId()); + + if (!participants.isEmpty()) { + throw new BadRequestException(NEED_TO_EXIT_ALL_ROOMS); + } + + member.delete(clockHolder.times()); + memberRepository.flush(); + memberRepository.delete(member); + rankingService.removeRanking(MemberMapper.toRankingInfo(member)); + fcmService.deleteTokenByMemberId(member.getId()); + } + + public MemberInfoResponse searchInfo(AuthMember authMember, Long memberId) { + Long searchId = authMember.id(); + boolean isMe = confirmMe(searchId, memberId); + + if (!isMe) { + searchId = memberId; + } + MemberInfoSearchResponse memberInfoSearchResponse = findMemberInfo(searchId, isMe); + + return MemberMapper.toMemberInfoResponse(memberInfoSearchResponse); + } + + @Transactional + public void modifyInfo(AuthMember authMember, ModifyMemberRequest modifyMemberRequest, String newProfileUri) { + validateNickname(modifyMemberRequest.nickname()); + Member member = memberSearchRepository.findMember(authMember.id()) + .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); + + RankingInfo beforeInfo = MemberMapper.toRankingInfo(member); + member.changeNickName(modifyMemberRequest.nickname()); + + boolean nickNameChanged = member.changeNickName(modifyMemberRequest.nickname()); + member.changeIntro(modifyMemberRequest.intro()); + member.changeProfileUri(newProfileUri); + memberRepository.save(member); + + RankingInfo afterInfo = MemberMapper.toRankingInfo(member); + rankingService.changeInfos(beforeInfo, afterInfo); + + if (nickNameChanged) { + changeNickname(authMember.id(), modifyMemberRequest.nickname()); + } + } + + public UpdateRanking getRankingInfo(AuthMember authMember) { + Member member = findMember(authMember.id()); + + return MemberMapper.toUpdateRanking(member); + } + + @Scheduled(cron = "0 11 * * * *") + public void updateAllRanking() { + List members = memberSearchRepository.findAllMembers(); + List updateRankings = members.stream() + .map(MemberMapper::toUpdateRanking) + .toList(); + + rankingService.updateScores(updateRankings); + } + + private void changeNickname(Long memberId, String changedName) { + List participants = participantSearchRepository.findAllRoomMangerByMemberId(memberId); + + for (Participant participant : participants) { + participant.getRoom().changeManagerNickname(changedName); + } + } + + private void validateNickname(String nickname) { + if (Objects.isNull(nickname)) { + return; + } + if (StringUtils.isEmpty(nickname) && memberRepository.existsByNickname(nickname)) { + throw new ConflictException(NICKNAME_CONFLICT); + } + } + + private Member signUp(Long socialId) { + Member member = MemberMapper.toMember(socialId); + Member savedMember = memberRepository.save(member); + saveMyEgg(savedMember); + rankingService.addRanking(MemberMapper.toRankingInfo(member), member.getTotalCertifyCount()); + + return savedMember; + } + + private void saveMyEgg(Member member) { + List items = getBasicEggs(); + List inventories = items.stream() + .map(item -> MemberMapper.toInventory(member.getId(), item)) + .toList(); + inventoryRepository.saveAll(inventories); + } + + private List getBasicEggs() { + List items = itemRepository.findAllById(List.of(BaseDataCode.MORNING_EGG, BaseDataCode.NIGHT_EGG)); + + if (items.isEmpty()) { + throw new BadRequestException(BASIC_SKIN_NOT_FOUND); + } + + return items; + } + + private MemberInfoSearchResponse findMemberInfo(Long searchId, boolean isMe) { + List memberInfos = memberSearchRepository.findMemberAndBadges(searchId, isMe); + + if (memberInfos.isEmpty()) { + throw new BadRequestException(MEMBER_NOT_FOUND); + } + + return MemberMapper.toMemberInfoSearchResponse(memberInfos); + } + + private boolean confirmMe(Long myId, Long memberId) { + return Objects.isNull(memberId) || myId.equals(memberId); + } +} diff --git a/src/main/java/com/moabam/api/application/notification/NotificationService.java b/src/main/java/com/moabam/api/application/notification/NotificationService.java new file mode 100644 index 00000000..3d8c859b --- /dev/null +++ b/src/main/java/com/moabam/api/application/notification/NotificationService.java @@ -0,0 +1,99 @@ +package com.moabam.api.application.notification; + +import static com.moabam.global.common.util.GlobalConstant.*; + +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.RoomService; +import com.moabam.api.domain.notification.repository.NotificationRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NotificationService { + + private static final String COMMON_TITLE = "모아밤"; + private static final String KNOCK_BODY = "[%s] - [%s]님이 콕콕콕!"; + private static final String CERTIFY_TIME_BODY = "[%s] - 5분 후 인증 시간입니다!"; + + private final ClockHolder clockHolder; + private final FcmService fcmService; + private final RoomService roomService; + private final MemberService memberService; + + private final NotificationRepository notificationRepository; + private final ParticipantSearchRepository participantSearchRepository; + + @Transactional + public void sendKnock(Long roomId, Long targetId, Long memberId) { + validateConflictKnock(roomId, targetId, memberId); + String fcmToken = fcmService.findTokenByMemberId(targetId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_FCM_TOKEN)); + + String roomTitle = roomService.findRoom(roomId).getTitle(); + String memberNickname = memberService.findMember(memberId).getNickname(); + String notificationTitle = roomId.toString(); + + String notificationBody = String.format(KNOCK_BODY, roomTitle, memberNickname); + fcmService.sendAsync(fcmToken, notificationTitle, notificationBody); + notificationRepository.saveKnock(roomId, targetId, memberId); + } + + public void sendCouponIssueResult(Long memberId, String couponName, String body) { + String fcmToken = fcmService.findTokenByMemberId(memberId).orElse(null); + String notificationBody = String.format(body, couponName); + fcmService.sendAsync(fcmToken, COMMON_TITLE, notificationBody); + } + + @Scheduled(cron = "0 55 * * * *") + public void sendCertificationTime() { + int certificationTime = (clockHolder.times().getHour() + ONE_HOUR) % HOURS_IN_A_DAY; + List participants = participantSearchRepository.findAllByRoomCertifyTime(certificationTime); + + participants.parallelStream().forEach(participant -> { + String roomTitle = participant.getRoom().getTitle(); + String notificationTitle = participant.getRoom().getId().toString(); + String notificationBody = String.format(CERTIFY_TIME_BODY, roomTitle); + String fcmToken = fcmService.findTokenByMemberId(participant.getMemberId()).orElse(null); + fcmService.sendAsync(fcmToken, notificationTitle, notificationBody); + }); + } + + public List getMyKnockStatusInRoom(Long memberId, Long roomId, List participants) { + List filteredParticipants = participants.stream() + .filter(participant -> !participant.getMemberId().equals(memberId)) + .toList(); + + Predicate knockPredicate = targetId -> + notificationRepository.existsKnockByKey(roomId, targetId, memberId); + + Map> knockStatus = filteredParticipants.stream() + .map(Participant::getMemberId) + .collect(Collectors.partitioningBy(knockPredicate)); + + return knockStatus.get(true); + } + + private void validateConflictKnock(Long roomId, Long targetId, Long memberId) { + if (notificationRepository.existsKnockByKey(roomId, targetId, memberId)) { + throw new ConflictException(ErrorMessage.CONFLICT_KNOCK); + } + } +} diff --git a/src/main/java/com/moabam/api/application/payment/PaymentMapper.java b/src/main/java/com/moabam/api/application/payment/PaymentMapper.java new file mode 100644 index 00000000..e98b64eb --- /dev/null +++ b/src/main/java/com/moabam/api/application/payment/PaymentMapper.java @@ -0,0 +1,49 @@ +package com.moabam.api.application.payment; + +import java.util.Optional; + +import com.moabam.api.domain.payment.Order; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.product.Product; +import com.moabam.api.dto.payment.ConfirmTossPaymentResponse; +import com.moabam.api.dto.payment.PaymentResponse; +import com.moabam.api.dto.payment.RequestConfirmPaymentResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class PaymentMapper { + + public static Payment toPayment(Long memberId, Product product) { + Order order = Order.builder() + .name(product.getName()) + .build(); + + return Payment.builder() + .memberId(memberId) + .product(product) + .order(order) + .totalAmount(product.getPrice()) + .build(); + } + + public static PaymentResponse toPaymentResponse(Payment payment) { + return Optional.ofNullable(payment) + .map(p -> PaymentResponse.builder() + .id(p.getId()) + .orderName(p.getOrder().getName()) + .discountAmount(p.getDiscountAmount()) + .totalAmount(p.getTotalAmount()) + .build()) + .orElse(null); + } + + public static RequestConfirmPaymentResponse toRequestConfirmPaymentResponse(Payment payment, + ConfirmTossPaymentResponse response) { + return RequestConfirmPaymentResponse.builder() + .payment(payment) + .paymentKey(response.paymentKey()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/payment/PaymentService.java b/src/main/java/com/moabam/api/application/payment/PaymentService.java new file mode 100644 index 00000000..80b388a8 --- /dev/null +++ b/src/main/java/com/moabam/api/application/payment/PaymentService.java @@ -0,0 +1,74 @@ +package com.moabam.api.application.payment; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.coupon.CouponService; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.payment.repository.PaymentSearchRepository; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.ConfirmTossPaymentResponse; +import com.moabam.api.dto.payment.PaymentRequest; +import com.moabam.api.dto.payment.RequestConfirmPaymentResponse; +import com.moabam.api.infrastructure.payment.TossPaymentService; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.exception.TossPaymentException; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class PaymentService { + + private final BugService bugService; + private final CouponService couponService; + private final TossPaymentService tossPaymentService; + private final PaymentRepository paymentRepository; + private final PaymentSearchRepository paymentSearchRepository; + + @Transactional + public void request(Long memberId, Long paymentId, PaymentRequest request) { + Payment payment = getById(paymentId); + payment.validateByMember(memberId); + payment.request(request.orderId()); + } + + @Transactional + public RequestConfirmPaymentResponse requestConfirm(Long memberId, ConfirmPaymentRequest request) { + Payment payment = getByOrderId(request.orderId()); + payment.validateInfo(memberId, request.amount()); + + try { + ConfirmTossPaymentResponse response = tossPaymentService.confirm(request); + return PaymentMapper.toRequestConfirmPaymentResponse(payment, response); + } catch (TossPaymentException exception) { + payment.fail(request.paymentKey()); + throw exception; + } + } + + @Transactional + public void confirm(Long memberId, Payment payment, String paymentKey) { + payment.confirm(paymentKey); + + if (payment.isCouponApplied()) { + couponService.discount(payment.getCouponWalletId(), memberId); + } + bugService.charge(memberId, payment.getProduct()); + } + + private Payment getById(Long paymentId) { + return paymentRepository.findById(paymentId) + .orElseThrow(() -> new NotFoundException(PAYMENT_NOT_FOUND)); + } + + private Payment getByOrderId(String orderId) { + return paymentSearchRepository.findByOrderId(orderId) + .orElseThrow(() -> new NotFoundException(PAYMENT_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moabam/api/application/product/ProductMapper.java b/src/main/java/com/moabam/api/application/product/ProductMapper.java new file mode 100644 index 00000000..b847d9c1 --- /dev/null +++ b/src/main/java/com/moabam/api/application/product/ProductMapper.java @@ -0,0 +1,41 @@ +package com.moabam.api.application.product; + +import java.util.List; + +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.product.Product; +import com.moabam.api.dto.product.ProductResponse; +import com.moabam.api.dto.product.ProductsResponse; +import com.moabam.api.dto.product.PurchaseProductResponse; +import com.moabam.global.common.util.StreamUtils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ProductMapper { + + public static ProductResponse toProductResponse(Product product) { + return ProductResponse.builder() + .id(product.getId()) + .type(product.getType().name()) + .name(product.getName()) + .price(product.getPrice()) + .quantity(product.getQuantity()) + .build(); + } + + public static ProductsResponse toProductsResponse(List products) { + return ProductsResponse.builder() + .products(StreamUtils.map(products, ProductMapper::toProductResponse)) + .build(); + } + + public static PurchaseProductResponse toPurchaseProductResponse(Payment payment) { + return PurchaseProductResponse.builder() + .paymentId(payment.getId()) + .orderName(payment.getOrder().getName()) + .price(payment.getTotalAmount()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/ranking/RankingMapper.java b/src/main/java/com/moabam/api/application/ranking/RankingMapper.java new file mode 100644 index 00000000..817ccdc8 --- /dev/null +++ b/src/main/java/com/moabam/api/application/ranking/RankingMapper.java @@ -0,0 +1,39 @@ +package com.moabam.api.application.ranking; + +import java.util.List; + +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.TopRankingInfo; +import com.moabam.api.dto.ranking.TopRankingResponse; +import com.moabam.api.dto.ranking.UpdateRanking; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RankingMapper { + + public static TopRankingInfo topRankingResponse(int rank, long score, RankingInfo rankInfo) { + return TopRankingInfo.builder() + .rank(rank) + .score(score) + .nickname(rankInfo.nickname()) + .image(rankInfo.image()) + .memberId(rankInfo.memberId()) + .build(); + } + + public static TopRankingInfo topRankingResponse(int rank, UpdateRanking updateRanking) { + return TopRankingInfo.builder() + .rank(rank + 1) + .score(updateRanking.score()) + .nickname(updateRanking.rankingInfo().nickname()) + .image(updateRanking.rankingInfo().image()) + .memberId(updateRanking.rankingInfo().memberId()) + .build(); + } + + public static TopRankingResponse topRankingResponses(TopRankingInfo myRanking, List topRankings) { + return TopRankingResponse.builder().topRankings(topRankings).myRanking(myRanking).build(); + } +} diff --git a/src/main/java/com/moabam/api/application/ranking/RankingService.java b/src/main/java/com/moabam/api/application/ranking/RankingService.java new file mode 100644 index 00000000..651093f4 --- /dev/null +++ b/src/main/java/com/moabam/api/application/ranking/RankingService.java @@ -0,0 +1,86 @@ +package com.moabam.api.application.ranking; + +import static java.util.Objects.*; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.TopRankingInfo; +import com.moabam.api.dto.ranking.TopRankingResponse; +import com.moabam.api.dto.ranking.UpdateRanking; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class RankingService { + + private static final String RANKING = "Ranking"; + private static final int START_INDEX = 0; + private static final int LIMIT_INDEX = 9; + + private final ObjectMapper objectMapper; + private final ZSetRedisRepository zSetRedisRepository; + + public void addRanking(RankingInfo rankingInfo, Long totalCertifyCount) { + zSetRedisRepository.add(RANKING, rankingInfo, totalCertifyCount); + } + + public void updateScores(List updateRankings) { + updateRankings.forEach( + updateRanking -> zSetRedisRepository.add(RANKING, updateRanking.rankingInfo(), updateRanking.score())); + } + + public void changeInfos(RankingInfo before, RankingInfo after) { + zSetRedisRepository.changeMember(RANKING, before, after); + } + + public void removeRanking(RankingInfo rankingInfo) { + zSetRedisRepository.delete(RANKING, rankingInfo); + } + + public TopRankingResponse getMemberRanking(UpdateRanking myRankingInfo) { + List topRankings = getTopRankings(); + Long myRanking = zSetRedisRepository.reverseRank(RANKING, myRankingInfo.rankingInfo()); + + Optional myTopRanking = topRankings.stream() + .filter(topRankingInfo -> Objects.equals(topRankingInfo.memberId(), myRankingInfo.rankingInfo().memberId())) + .findFirst(); + + if (myTopRanking.isPresent()) { + myRanking = (long)myTopRanking.get().rank(); + } + + TopRankingInfo myRankingInfoResponse = RankingMapper.topRankingResponse(myRanking.intValue(), myRankingInfo); + + return RankingMapper.topRankingResponses(myRankingInfoResponse, topRankings); + } + + private List getTopRankings() { + Set> topRankings = zSetRedisRepository.rangeJson(RANKING, START_INDEX, + LIMIT_INDEX); + + Set scoreSet = new HashSet<>(); + List topRankingInfo = new ArrayList<>(); + + for (ZSetOperations.TypedTuple topRanking : topRankings) { + long score = requireNonNull(topRanking.getScore()).longValue(); + scoreSet.add(score); + + RankingInfo rankingInfo = objectMapper.convertValue(topRanking.getValue(), RankingInfo.class); + topRankingInfo.add(RankingMapper.topRankingResponse(scoreSet.size(), score, rankingInfo)); + } + + return topRankingInfo; + } +} diff --git a/src/main/java/com/moabam/api/application/report/ReportMapper.java b/src/main/java/com/moabam/api/application/report/ReportMapper.java new file mode 100644 index 00000000..7cada261 --- /dev/null +++ b/src/main/java/com/moabam/api/application/report/ReportMapper.java @@ -0,0 +1,23 @@ +package com.moabam.api.application.report; + +import com.moabam.api.domain.report.Report; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ReportMapper { + + public static Report toReport(Long reporterId, Long reportedMemberId, + Room room, Certification certification, String description) { + return Report.builder() + .reporterId(reporterId) + .reportedMemberId(reportedMemberId) + .certification(certification) + .room(room) + .description(description) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/report/ReportService.java b/src/main/java/com/moabam/api/application/report/ReportService.java new file mode 100644 index 00000000..5735c63a --- /dev/null +++ b/src/main/java/com/moabam/api/application/report/ReportService.java @@ -0,0 +1,61 @@ +package com.moabam.api.application.report; + +import static java.util.Objects.*; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.CertificationService; +import com.moabam.api.application.room.RoomService; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.report.Report; +import com.moabam.api.domain.report.repository.ReportRepository; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; +import com.moabam.api.dto.report.ReportRequest; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ReportService { + + private final MemberService memberService; + private final RoomService roomService; + private final CertificationService certificationService; + private final ReportRepository reportRepository; + + @Transactional + public void report(AuthMember authMember, ReportRequest reportRequest) { + validateNoReportSubject(reportRequest.reportedId()); + Report report = createReport(authMember.id(), reportRequest); + reportRepository.save(report); + } + + private Report createReport(Long reporterId, ReportRequest reportRequest) { + Member reportedMember = memberService.findMember(reportRequest.reportedId()); + + Certification certification = null; + if (nonNull(reportRequest.certificationId())) { + certification = certificationService.findCertification(reportRequest.certificationId()); + } + + Room room = null; + if (nonNull(reportRequest.roomId())) { + room = roomService.findRoom(reportRequest.roomId()); + } + + return ReportMapper.toReport(reporterId, reportedMember.getId(), + room, certification, reportRequest.description()); + } + + private void validateNoReportSubject(Long reportedId) { + if (isNull(reportedId)) { + throw new BadRequestException(ErrorMessage.REPORT_REQUEST_ERROR); + } + } +} diff --git a/src/main/java/com/moabam/api/application/room/CertificationService.java b/src/main/java/com/moabam/api/application/room/CertificationService.java new file mode 100644 index 00000000..c4e86434 --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/CertificationService.java @@ -0,0 +1,208 @@ +package com.moabam.api.application.room; + +import static com.moabam.global.common.util.GlobalConstant.*; +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.member.BadgeService; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.mapper.CertificationsMapper; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomExp; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationRepository; +import com.moabam.api.domain.room.repository.CertificationsSearchRepository; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.DailyRoomCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CertifiedMemberInfo; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.common.util.UrlSubstringParser; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.NotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CertificationService { + + private static final int REQUIRED_ROOM_CERTIFICATION = 75; + + private final RoutineRepository routineRepository; + private final CertificationRepository certificationRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final CertificationsSearchRepository certificationsSearchRepository; + private final DailyRoomCertificationRepository dailyRoomCertificationRepository; + private final DailyMemberCertificationRepository dailyMemberCertificationRepository; + private final MemberService memberService; + private final BadgeService badgeService; + private final BugService bugService; + private final ClockHolder clockHolder; + + @Transactional + public CertifiedMemberInfo getCertifiedMemberInfo(Long memberId, Long roomId, List imageUrls) { + LocalDate today = clockHolder.date(); + Participant participant = participantSearchRepository.findOne(memberId, roomId) + .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); + Room room = participant.getRoom(); + Member member = memberService.findMember(memberId); + BugType bugType = switch (room.getRoomType()) { + case MORNING -> BugType.MORNING; + case NIGHT -> BugType.NIGHT; + }; + + validateCertifyTime(clockHolder.times(), room.getCertifyTime()); + validateAlreadyCertified(memberId, roomId, today); + + certifyMember(memberId, roomId, participant, member, imageUrls); + + return CertificationsMapper.toCertifiedMemberInfo(today, bugType, room, member); + } + + @Transactional + public void certifyRoom(CertifiedMemberInfo certifyInfo) { + LocalDate date = certifyInfo.date(); + BugType bugType = certifyInfo.bugType(); + Room room = certifyInfo.room(); + Member member = certifyInfo.member(); + + Optional dailyRoomCertification = + certificationsSearchRepository.findDailyRoomCertification(room.getId(), date); + + if (dailyRoomCertification.isEmpty()) { + certifyRoomIfAvailable(room.getId(), date, room, bugType, room.getLevel()); + return; + } + + bugService.reward(member, bugType, room.getLevel()); + } + + public boolean existsMemberCertification(Long memberId, Long roomId, LocalDate date) { + return dailyMemberCertificationRepository.existsByMemberIdAndRoomIdAndCreatedAtBetween(memberId, roomId, + date.atStartOfDay(), date.atTime(LocalTime.MAX)); + } + + public boolean existsRoomCertification(Long roomId, LocalDate date) { + return dailyRoomCertificationRepository.existsByRoomIdAndCertifiedAt(roomId, date); + } + + public boolean existsAnyMemberCertification(Long roomId, LocalDate date) { + return dailyMemberCertificationRepository.existsByRoomIdAndCreatedAtBetween(roomId, date.atStartOfDay(), + date.atTime(LocalTime.MAX)); + } + + public Certification findCertification(Long certificationId) { + return certificationRepository.findById(certificationId) + .orElseThrow(() -> new NotFoundException(CERTIFICATION_NOT_FOUND)); + } + + private void validateCertifyTime(LocalDateTime now, int certifyTime) { + LocalTime targetTime = LocalTime.of(certifyTime, 0); + LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); + + if (certifyTime == MIDNIGHT_HOUR && now.getHour() != MIDNIGHT_HOUR) { + targetDateTime = targetDateTime.plusDays(1); + } + + LocalDateTime plusTenMinutes = targetDateTime.plusMinutes(10); + + if (now.isBefore(targetDateTime) || now.isAfter(plusTenMinutes)) { + throw new BadRequestException(INVALID_CERTIFY_TIME); + } + } + + private void validateAlreadyCertified(Long memberId, Long roomId, LocalDate today) { + if (certificationsSearchRepository.findDailyMemberCertification(memberId, roomId, today).isPresent()) { + throw new BadRequestException(DUPLICATED_DAILY_MEMBER_CERTIFICATION); + } + } + + private void certifyMember(Long memberId, Long roomId, Participant participant, Member member, List urls) { + DailyMemberCertification dailyMemberCertification = CertificationsMapper.toDailyMemberCertification(memberId, + roomId, participant); + dailyMemberCertificationRepository.save(dailyMemberCertification); + member.increaseTotalCertifyCount(); + badgeService.createBadge(member.getId(), member.getTotalCertifyCount()); + participant.updateCertifyCount(); + + saveNewCertifications(memberId, urls); + } + + private void saveNewCertifications(Long memberId, List imageUrls) { + List certifications = new ArrayList<>(); + + for (String imageUrl : imageUrls) { + Long routineId = Long.parseLong(UrlSubstringParser.parseUrl(imageUrl, "_")); + Routine routine = routineRepository.findById(routineId).orElseThrow(() -> new NotFoundException( + ROUTINE_NOT_FOUND)); + + Certification certification = CertificationsMapper.toCertification(routine, memberId, imageUrl); + certifications.add(certification); + } + + certificationRepository.saveAll(certifications); + } + + private void certifyRoomIfAvailable(Long roomId, LocalDate today, Room room, BugType bugType, int roomLevel) { + List dailyMemberCertifications = + certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, today); + double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(), + room.getCurrentUserCount()); + + if (completePercentage >= REQUIRED_ROOM_CERTIFICATION) { + DailyRoomCertification createDailyRoomCertification = CertificationsMapper.toDailyRoomCertification( + roomId, today); + + dailyRoomCertificationRepository.save(createDailyRoomCertification); + int expAppliedRoomLevel = getRoomLevelAfterExpApply(roomLevel, room); + + provideBugToCompletedMembers(bugType, dailyMemberCertifications, expAppliedRoomLevel); + } + } + + private double calculateCompletePercentage(int certifiedMembersCount, int currentsMembersCount) { + double completePercentage = ((double)certifiedMembersCount / currentsMembersCount) * 100; + + return Math.round(completePercentage * 100) / 100.0; + } + + private int getRoomLevelAfterExpApply(int roomLevel, Room room) { + int requireExp = RoomExp.of(roomLevel).getTotalExp(); + room.gainExp(); + + if (room.getExp() == requireExp) { + room.levelUp(); + } + + return room.getLevel(); + } + + private void provideBugToCompletedMembers(BugType bugType, List dailyMemberCertifications, + int expAppliedRoomLevel) { + List memberIds = dailyMemberCertifications.stream() + .map(DailyMemberCertification::getMemberId) + .toList(); + + memberService.getRoomMembers(memberIds) + .forEach(completedMember -> bugService.reward(completedMember, bugType, expAppliedRoomLevel)); + } +} diff --git a/src/main/java/com/moabam/api/application/room/RoomService.java b/src/main/java/com/moabam/api/application/room/RoomService.java new file mode 100644 index 00000000..1f2ccdce --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/RoomService.java @@ -0,0 +1,240 @@ +package com.moabam.api.application.room; + +import static com.moabam.api.domain.room.RoomType.*; +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.mapper.ParticipantMapper; +import com.moabam.api.application.room.mapper.RoomMapper; +import com.moabam.api.application.room.mapper.RoutineMapper; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.api.dto.room.EnterRoomRequest; +import com.moabam.api.dto.room.ModifyRoomRequest; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ForbiddenException; +import com.moabam.global.error.exception.NotFoundException; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class RoomService { + + private final RoomRepository roomRepository; + private final RoutineRepository routineRepository; + private final ParticipantRepository participantRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final DailyMemberCertificationRepository dailyMemberCertificationRepository; + private final CertificationService certificationService; + private final MemberService memberService; + private final ClockHolder clockHolder; + + @Transactional + public Long createRoom(Long memberId, CreateRoomRequest createRoomRequest) { + Room room = RoomMapper.toRoomEntity(createRoomRequest); + List routines = RoutineMapper.toRoutineEntities(room, createRoomRequest.routines()); + Participant participant = ParticipantMapper.toParticipant(room, memberId); + + validateEnteredRoomCount(memberId, room.getRoomType()); + + Member member = memberService.findMember(memberId); + member.enterRoom(room.getRoomType()); + participant.enableManager(); + room.changeManagerNickname(member.getNickname()); + + Room savedRoom = roomRepository.save(room); + routineRepository.saveAll(routines); + participantRepository.save(participant); + + return savedRoom.getId(); + } + + @Transactional + public void modifyRoom(Long memberId, Long roomId, ModifyRoomRequest modifyRoomRequest) { + Participant participant = getParticipant(memberId, roomId); + validateManagerAuthorization(participant); + + Room room = participant.getRoom(); + room.changeTitle(modifyRoomRequest.title()); + room.changeAnnouncement(modifyRoomRequest.announcement()); + room.changePassword(modifyRoomRequest.password()); + room.changeMaxCount(modifyRoomRequest.maxUserCount()); + + if (room.getCertifyTime() != modifyRoomRequest.certifyTime()) { + validateChangeCertifyTime(roomId); + } + room.changeCertifyTime(modifyRoomRequest.certifyTime()); + } + + @Transactional + public void enterRoom(Long memberId, Long roomId, EnterRoomRequest enterRoomRequest) { + Room room = roomRepository.findWithPessimisticLockByIdAndDeletedAtIsNull(roomId).orElseThrow( + () -> new NotFoundException(ROOM_NOT_FOUND)); + validateRoomEnter(memberId, enterRoomRequest.password(), room); + + Member member = memberService.findMember(memberId); + member.enterRoom(room.getRoomType()); + room.increaseCurrentUserCount(); + + Participant participant = ParticipantMapper.toParticipant(room, memberId); + participantRepository.save(participant); + } + + @Transactional + public void exitRoom(Long memberId, Long roomId) { + Participant participant = getParticipant(memberId, roomId); + Room room = participant.getRoom(); + + validateRoomExit(participant, room); + + Member member = memberService.findMember(memberId); + member.exitRoom(room.getRoomType()); + + participant.removeRoom(); + participantRepository.flush(); + participantRepository.delete(participant); + + if (!participant.isManager()) { + room.decreaseCurrentUserCount(); + return; + } + + roomRepository.delete(room); + } + + @Transactional + public void mandateManager(Long managerId, Long roomId, Long memberId) { + Participant managerParticipant = getParticipant(managerId, roomId); + Participant memberParticipant = getParticipant(memberId, roomId); + validateManagerAuthorization(managerParticipant); + + Room room = managerParticipant.getRoom(); + Member member = memberService.findMember(memberParticipant.getMemberId()); + room.changeManagerNickname(member.getNickname()); + + managerParticipant.disableManager(); + memberParticipant.enableManager(); + } + + @Transactional + public void deportParticipant(Long managerId, Long roomId, Long memberId) { + validateDeportParticipant(managerId, memberId); + Participant managerParticipant = getParticipant(managerId, roomId); + Participant memberParticipant = getParticipant(memberId, roomId); + validateManagerAuthorization(managerParticipant); + + Room room = managerParticipant.getRoom(); + memberParticipant.removeRoom(); + participantRepository.flush(); + participantRepository.delete(memberParticipant); + room.decreaseCurrentUserCount(); + + Member member = memberService.findMember(memberId); + member.exitRoom(room.getRoomType()); + } + + public boolean checkIfParticipant(Long memberId, Long roomId) { + try { + getParticipant(memberId, roomId); + return true; + } catch (NotFoundException e) { + return false; + } + } + + public Room findRoom(Long roomId) { + return roomRepository.findById(roomId) + .orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND)); + } + + private void validateChangeCertifyTime(Long roomId) { + if (certificationService.existsAnyMemberCertification(roomId, clockHolder.date())) { + throw new BadRequestException(UNAVAILABLE_TO_CHANGE_CERTIFY_TIME); + } + } + + private Participant getParticipant(Long memberId, Long roomId) { + return participantSearchRepository.findOne(memberId, roomId) + .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); + } + + private void validateDeportParticipant(Long managerId, Long memberId) { + if (managerId.equals(memberId)) { + throw new BadRequestException(PARTICIPANT_DEPORT_ERROR); + } + } + + private void validateManagerAuthorization(Participant participant) { + if (!participant.isManager()) { + throw new ForbiddenException(ROOM_MODIFY_UNAUTHORIZED_REQUEST); + } + } + + private void validateRoomEnter(Long memberId, String requestPassword, Room room) { + validateEnteredRoomCount(memberId, room.getRoomType()); + validateCertifyTime(room); + + if (!StringUtils.isEmpty(requestPassword) && !room.getPassword().equals(requestPassword)) { + throw new BadRequestException(WRONG_ROOM_PASSWORD); + } + if (room.getCurrentUserCount() == room.getMaxUserCount()) { + throw new BadRequestException(ROOM_MAX_USER_REACHED); + } + } + + private void validateEnteredRoomCount(Long memberId, RoomType roomType) { + Member member = memberService.findMember(memberId); + + if (roomType.equals(MORNING) && member.getCurrentMorningCount() >= 3) { + throw new BadRequestException(MEMBER_ROOM_EXCEED); + } + if (roomType.equals(NIGHT) && member.getCurrentNightCount() >= 3) { + throw new BadRequestException(MEMBER_ROOM_EXCEED); + } + } + + private void validateCertifyTime(Room room) { + LocalDateTime now = clockHolder.times(); + LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); + LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); + + LocalDateTime plusTenMinutes = targetDateTime.plusMinutes(10); + + if (now.isAfter(targetDateTime) && now.isBefore(plusTenMinutes)) { + throw new BadRequestException(ROOM_ENTER_FAILED); + } + } + + private void validateRoomExit(Participant participant, Room room) { + if (participant.isManager() && room.getCurrentUserCount() != 1) { + throw new BadRequestException(ROOM_EXIT_MANAGER_FAIL); + } + + if (dailyMemberCertificationRepository.existsByMemberIdAndRoomIdAndCreatedAtBetween(participant.getMemberId(), + room.getId(), clockHolder.date().atStartOfDay(), clockHolder.date().atTime(LocalTime.MAX))) { + throw new BadRequestException(CERTIFIED_ROOM_EXIT_FAILED); + } + } +} diff --git a/src/main/java/com/moabam/api/application/room/SearchService.java b/src/main/java/com/moabam/api/application/room/SearchService.java new file mode 100644 index 00000000..f489d966 --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/SearchService.java @@ -0,0 +1,416 @@ +package com.moabam.api.application.room; + +import static com.moabam.global.common.util.GlobalConstant.*; +import static com.moabam.global.error.model.ErrorMessage.*; +import static org.apache.commons.lang3.StringUtils.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.notification.NotificationService; +import com.moabam.api.application.room.mapper.CertificationsMapper; +import com.moabam.api.application.room.mapper.ParticipantMapper; +import com.moabam.api.application.room.mapper.RoomMapper; +import com.moabam.api.application.room.mapper.RoutineMapper; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.repository.InventorySearchRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationsSearchRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoomSearchRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CertificationImageResponse; +import com.moabam.api.dto.room.CertificationImagesResponse; +import com.moabam.api.dto.room.GetAllRoomResponse; +import com.moabam.api.dto.room.GetAllRoomsResponse; +import com.moabam.api.dto.room.ManageRoomResponse; +import com.moabam.api.dto.room.MyRoomResponse; +import com.moabam.api.dto.room.MyRoomsResponse; +import com.moabam.api.dto.room.ParticipantResponse; +import com.moabam.api.dto.room.RoomDetailsResponse; +import com.moabam.api.dto.room.RoomHistoryResponse; +import com.moabam.api.dto.room.RoomsHistoryResponse; +import com.moabam.api.dto.room.RoutineResponse; +import com.moabam.api.dto.room.TodayCertificateRankResponse; +import com.moabam.api.dto.room.UnJoinedRoomCertificateRankResponse; +import com.moabam.api.dto.room.UnJoinedRoomDetailsResponse; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.ForbiddenException; +import com.moabam.global.error.exception.NotFoundException; + +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class SearchService { + + private final RoomRepository roomRepository; + private final RoomSearchRepository roomSearchRepository; + private final RoutineRepository routineRepository; + private final ParticipantSearchRepository participantSearchRepository; + private final CertificationsSearchRepository certificationsSearchRepository; + private final InventorySearchRepository inventorySearchRepository; + private final CertificationService certificationService; + private final MemberService memberService; + private final NotificationService notificationService; + private final ClockHolder clockHolder; + + public RoomDetailsResponse getRoomDetails(Long memberId, Long roomId, LocalDate date) { + Participant participant = participantSearchRepository.findOne(memberId, roomId) + .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); + Room room = participant.getRoom(); + + String managerNickname = room.getManagerNickname(); + List dailyMemberCertifications = + certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, date); + List routineResponses = getRoutineResponses(roomId); + List todayCertificateRankResponses = getTodayCertificateRankResponses(memberId, + roomId, dailyMemberCertifications, date, room.getRoomType()); + List certifiedDates = getCertifiedDatesBeforeWeek(roomId); + double completePercentage = calculateCompletePercentage(dailyMemberCertifications.size(), + room, date); + + return RoomMapper.toRoomDetailsResponse(memberId, room, managerNickname, routineResponses, certifiedDates, + todayCertificateRankResponses, completePercentage); + } + + public MyRoomsResponse getMyRooms(Long memberId) { + LocalDate today = clockHolder.date(); + List myRoomResponses = new ArrayList<>(); + List participants = participantSearchRepository.findNotDeletedAllByMemberId(memberId); + + for (Participant participant : participants) { + Room room = participant.getRoom(); + boolean isMemberCertified = certificationService.existsMemberCertification(memberId, room.getId(), today); + boolean isRoomCertified = certificationService.existsRoomCertification(room.getId(), today); + + myRoomResponses.add(RoomMapper.toMyRoomResponse(room, isMemberCertified, isRoomCertified)); + } + + return RoomMapper.toMyRoomsResponse(myRoomResponses); + } + + public RoomsHistoryResponse getJoinHistory(Long memberId) { + List participants = participantSearchRepository.findAllByMemberId(memberId); + List roomHistoryResponses = participants.stream() + .map(participant -> { + if (participant.getRoom() == null) { + return RoomMapper.toRoomHistoryResponse(null, participant.getDeletedRoomTitle(), participant); + } + + Room room = participant.getRoom(); + + return RoomMapper.toRoomHistoryResponse(room.getId(), room.getTitle(), participant); + }) + .toList(); + + return RoomMapper.toRoomsHistoryResponse(roomHistoryResponses); + } + + public ManageRoomResponse getRoomForModification(Long memberId, Long roomId) { + Participant participant = participantSearchRepository.findOne(memberId, roomId) + .orElseThrow(() -> new NotFoundException(PARTICIPANT_NOT_FOUND)); + + if (!participant.isManager()) { + throw new ForbiddenException(ROOM_MODIFY_UNAUTHORIZED_REQUEST); + } + + Room room = participant.getRoom(); + List routineResponses = getRoutineResponses(roomId); + List participants = participantSearchRepository.findAllByRoomId(roomId); + List memberIds = participants.stream() + .map(Participant::getMemberId) + .toList(); + List members = memberService.getRoomMembers(memberIds); + List participantResponses = new ArrayList<>(); + + for (Member member : members) { + int contributionPoint = calculateContributionPoint(member.getId(), participants, clockHolder.date()); + + participantResponses.add(ParticipantMapper.toParticipantResponse(member, contributionPoint)); + } + + return RoomMapper.toManageRoomResponse(room, memberId, routineResponses, participantResponses); + } + + public GetAllRoomsResponse getAllRooms(@Nullable RoomType roomType, @Nullable Long roomId) { + List getAllRoomResponse = new ArrayList<>(); + List rooms = new ArrayList<>(roomSearchRepository.findAllWithNoOffset(roomType, roomId)); + boolean hasNext = isHasNext(getAllRoomResponse, rooms); + + return RoomMapper.toSearchAllRoomsResponse(hasNext, getAllRoomResponse); + } + + public GetAllRoomsResponse searchRooms(String keyword, @Nullable RoomType roomType, @Nullable Long roomId) { + List getAllRoomResponse = new ArrayList<>(); + List rooms = new ArrayList<>(); + + if (roomId == null && roomType == null) { + rooms = new ArrayList<>(roomRepository.searchByKeyword(keyword)); + } + + if (roomId == null && roomType != null) { + rooms = new ArrayList<>(roomRepository.searchByKeywordAndRoomType(keyword, roomType.name())); + } + + if (roomId != null && roomType == null) { + rooms = new ArrayList<>(roomRepository.searchByKeywordAndRoomId(keyword, roomId)); + } + + if (roomId != null && roomType != null) { + rooms = new ArrayList<>( + roomRepository.searchByKeywordAndRoomIdAndRoomType(keyword, roomType.name(), roomId)); + } + + boolean hasNext = isHasNext(getAllRoomResponse, rooms); + + return RoomMapper.toSearchAllRoomsResponse(hasNext, getAllRoomResponse); + } + + public UnJoinedRoomDetailsResponse getUnJoinedRoomDetails(Long roomId) { + Room room = roomRepository.findById(roomId) + .orElseThrow(() -> new NotFoundException(ROOM_NOT_FOUND)); + + List routines = routineRepository.findAllByRoomId(roomId); + List routineResponses = RoutineMapper.toRoutineResponses(routines); + List sortedDailyMemberCertifications = + certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, clockHolder.date()); + List memberIds = sortedDailyMemberCertifications.stream() + .map(DailyMemberCertification::getMemberId) + .toList(); + List members = memberService.getRoomMembers(memberIds); + List inventories = inventorySearchRepository.findDefaultInventories(memberIds, + room.getRoomType().name()); + List unJoinedRoomCertificateRankResponses = new ArrayList<>(); + + int rank = 1; + for (DailyMemberCertification certification : sortedDailyMemberCertifications) { + Member member = members.stream() + .filter(m -> m.getId().equals(certification.getMemberId())) + .findAny() + .orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND)); + + Inventory inventory = inventories.stream() + .filter(i -> i.getMemberId().equals(member.getId())) + .findAny() + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); + + UnJoinedRoomCertificateRankResponse response = RoomMapper.toUnJoinedRoomCertificateRankResponse(member, + rank, inventory); + + unJoinedRoomCertificateRankResponses.add(response); + rank += 1; + } + + return RoomMapper.toUnJoinedRoomDetails(room, routineResponses, unJoinedRoomCertificateRankResponses); + } + + private boolean isHasNext(List getAllRoomResponse, List rooms) { + boolean hasNext = false; + + if (rooms.size() > ROOM_FIXED_SEARCH_SIZE) { + hasNext = true; + rooms.remove(ROOM_FIXED_SEARCH_SIZE); + } + + List roomIds = rooms.stream() + .map(Room::getId) + .toList(); + List routines = routineRepository.findAllByRoomIdIn(roomIds); + + for (Room room : rooms) { + List filteredRoutines = routines.stream() + .filter(routine -> routine.getRoom().getId().equals(room.getId())) + .toList(); + List filteredResponses = RoutineMapper.toRoutineResponses(filteredRoutines); + boolean isPassword = !isEmpty(room.getPassword()); + + getAllRoomResponse.add(RoomMapper.toSearchAllRoomResponse(room, filteredResponses, isPassword)); + } + + return hasNext; + } + + private List getRoutineResponses(Long roomId) { + List roomRoutines = routineRepository.findAllByRoomId(roomId); + + return RoutineMapper.toRoutineResponses(roomRoutines); + } + + private List getTodayCertificateRankResponses(Long memberId, Long roomId, + List dailyMemberCertifications, LocalDate date, RoomType roomType) { + + List certifications = certificationsSearchRepository.findCertifications(roomId, date); + List participants = participantSearchRepository.findAllWithDeletedByRoomId(roomId); + List members = memberService.getRoomMembers(participants.stream() + .map(Participant::getMemberId) + .distinct() + .toList()); + + List knocks = notificationService.getMyKnockStatusInRoom(memberId, roomId, participants); + + List memberIds = members.stream() + .map(Member::getId) + .toList(); + List inventories = inventorySearchRepository.findDefaultInventories(memberIds, roomType.name()); + + List responses = new ArrayList<>( + completedMembers(dailyMemberCertifications, members, certifications, participants, date, knocks, + inventories)); + + if (clockHolder.date().equals(date)) { + responses.addAll(uncompletedMembers(dailyMemberCertifications, members, participants, date, knocks, + inventories)); + } + + return responses; + } + + private List completedMembers( + List dailyMemberCertifications, List members, + List certifications, List participants, LocalDate date, List knocks, + List inventories) { + + List responses = new ArrayList<>(); + int rank = 1; + + for (DailyMemberCertification certification : dailyMemberCertifications) { + Member member = members.stream() + .filter(m -> m.getId().equals(certification.getMemberId())) + .findAny() + .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); + + Inventory inventory = inventories.stream() + .filter(i -> i.getMemberId().equals(member.getId())) + .findAny() + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); + + String awakeImage = inventory.getItem().getAwakeImage(); + String sleepImage = inventory.getItem().getSleepImage(); + + int contributionPoint = calculateContributionPoint(member.getId(), participants, date); + CertificationImagesResponse certificationImages = getCertificationImages(member.getId(), certifications); + boolean isNotificationSent = knocks.contains(member.getId()); + + TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse(rank, member, + contributionPoint, awakeImage, sleepImage, certificationImages, isNotificationSent); + + rank += 1; + responses.add(response); + } + + return responses; + } + + private List uncompletedMembers( + List dailyMemberCertifications, List members, List participants, + LocalDate date, List knocks, List inventories) { + + List responses = new ArrayList<>(); + + List allMemberIds = participants.stream() + .filter(p -> p.getDeletedAt() == null) + .map(Participant::getMemberId) + .distinct() + .collect(Collectors.toList()); + + List certifiedMemberIds = dailyMemberCertifications.stream() + .map(DailyMemberCertification::getMemberId) + .toList(); + + allMemberIds.removeAll(certifiedMemberIds); + + for (Long memberId : allMemberIds) { + Member member = members.stream() + .filter(m -> m.getId().equals(memberId)) + .findAny() + .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); + + Inventory inventory = inventories.stream() + .filter(i -> i.getMemberId().equals(member.getId())) + .findAny() + .orElseThrow(() -> new NotFoundException(INVENTORY_NOT_FOUND)); + + String awakeImage = inventory.getItem().getAwakeImage(); + String sleepImage = inventory.getItem().getSleepImage(); + + int contributionPoint = calculateContributionPoint(memberId, participants, date); + boolean isNotificationSent = knocks.contains(member.getId()); + + TodayCertificateRankResponse response = CertificationsMapper.toTodayCertificateRankResponse( + NOT_COMPLETED_RANK, member, contributionPoint, awakeImage, sleepImage, null, + isNotificationSent); + + responses.add(response); + } + + return responses; + } + + private CertificationImagesResponse getCertificationImages(Long memberId, List certifications) { + List certificationImageResponses = certifications.stream() + .filter(certification -> certification.getMemberId().equals(memberId)) + .map(certification -> CertificationsMapper.toCertificateImageResponse(certification.getRoutine().getId(), + certification.getImage())) + .toList(); + + return CertificationsMapper.toCertificateImagesResponse(certificationImageResponses); + } + + private int calculateContributionPoint(Long memberId, List participants, LocalDate date) { + Participant participant = participants.stream() + .filter(p -> p.getMemberId().equals(memberId)) + .filter(p -> p.getDeletedAt() == null) + .findAny() + .orElseThrow(() -> new NotFoundException(ROOM_DETAILS_ERROR)); + + int participatedDays = Period.between(participant.getCreatedAt().toLocalDate(), date).getDays() + 1; + + return (int)(((double)participant.getCertifyCount() / participatedDays) * 100); + } + + private List getCertifiedDatesBeforeWeek(Long roomId) { + List certifications = certificationsSearchRepository.findDailyRoomCertifications( + roomId, clockHolder.date()); + + return certifications.stream() + .map(DailyRoomCertification::getCertifiedAt) + .toList(); + } + + private double calculateCompletePercentage(int certifiedMembersCount, Room room, LocalDate date) { + if (!date.equals(clockHolder.date())) { + return 0; + } + + LocalDateTime now = clockHolder.times(); + LocalTime targetTime = LocalTime.of(room.getCertifyTime(), 0); + LocalDateTime targetDateTime = LocalDateTime.of(now.toLocalDate(), targetTime); + + List participants = participantSearchRepository.findAllByRoomIdBeforeDate(room.getId(), + targetDateTime); + + double completePercentage = ((double)certifiedMembersCount / participants.size()) * 100; + + return Math.round(completePercentage * 100) / 100.0; + } +} diff --git a/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java b/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java new file mode 100644 index 00000000..797f18db --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/mapper/CertificationsMapper.java @@ -0,0 +1,87 @@ +package com.moabam.api.application.room.mapper; + +import java.time.LocalDate; +import java.util.List; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.dto.room.CertificationImageResponse; +import com.moabam.api.dto.room.CertificationImagesResponse; +import com.moabam.api.dto.room.CertifiedMemberInfo; +import com.moabam.api.dto.room.TodayCertificateRankResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class CertificationsMapper { + + public static CertificationImageResponse toCertificateImageResponse(Long routineId, String image) { + return CertificationImageResponse.builder() + .routineId(routineId) + .image(image) + .build(); + } + + public static CertificationImagesResponse toCertificateImagesResponse(List images) { + return CertificationImagesResponse.builder() + .images(images) + .build(); + } + + public static TodayCertificateRankResponse toTodayCertificateRankResponse(int rank, Member member, + int contributionPoint, String awakeImage, String sleepImage, + CertificationImagesResponse certificationImagesResponses, boolean isNotificationSent) { + + return TodayCertificateRankResponse.builder() + .rank(rank) + .memberId(member.getId()) + .nickname(member.getNickname()) + .isNotificationSent(isNotificationSent) + .profileImage(member.getProfileImage()) + .contributionPoint(contributionPoint) + .awakeImage(awakeImage) + .sleepImage(sleepImage) + .certificationImage(certificationImagesResponses) + .build(); + } + + public static DailyMemberCertification toDailyMemberCertification(Long memberId, Long roomId, + Participant participant) { + return DailyMemberCertification.builder() + .memberId(memberId) + .roomId(roomId) + .participant(participant) + .build(); + } + + public static DailyRoomCertification toDailyRoomCertification(Long roomId, LocalDate today) { + return DailyRoomCertification.builder() + .roomId(roomId) + .certifiedAt(today) + .build(); + } + + public static Certification toCertification(Routine routine, Long memberId, String image) { + return Certification.builder() + .routine(routine) + .memberId(memberId) + .image(image) + .build(); + } + + public static CertifiedMemberInfo toCertifiedMemberInfo(LocalDate date, BugType bugType, Room room, Member member) { + return CertifiedMemberInfo.builder() + .date(date) + .bugType(bugType) + .room(room) + .member(member) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java b/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java new file mode 100644 index 00000000..3a4ec1da --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/mapper/ParticipantMapper.java @@ -0,0 +1,29 @@ +package com.moabam.api.application.room.mapper; + +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.dto.room.ParticipantResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ParticipantMapper { + + public static Participant toParticipant(Room room, Long memberId) { + return Participant.builder() + .room(room) + .memberId(memberId) + .build(); + } + + public static ParticipantResponse toParticipantResponse(Member member, int contributionPoint) { + return ParticipantResponse.builder() + .memberId(member.getId()) + .nickname(member.getNickname()) + .contributionPoint(contributionPoint) + .profileImage(member.getProfileImage()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java b/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java new file mode 100644 index 00000000..143fac74 --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/mapper/RoomMapper.java @@ -0,0 +1,176 @@ +package com.moabam.api.application.room.mapper; + +import static org.apache.commons.lang3.StringUtils.*; + +import java.time.LocalDate; +import java.util.List; + +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomExp; +import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.api.dto.room.GetAllRoomResponse; +import com.moabam.api.dto.room.GetAllRoomsResponse; +import com.moabam.api.dto.room.ManageRoomResponse; +import com.moabam.api.dto.room.MyRoomResponse; +import com.moabam.api.dto.room.MyRoomsResponse; +import com.moabam.api.dto.room.ParticipantResponse; +import com.moabam.api.dto.room.RoomDetailsResponse; +import com.moabam.api.dto.room.RoomHistoryResponse; +import com.moabam.api.dto.room.RoomsHistoryResponse; +import com.moabam.api.dto.room.RoutineResponse; +import com.moabam.api.dto.room.TodayCertificateRankResponse; +import com.moabam.api.dto.room.UnJoinedRoomCertificateRankResponse; +import com.moabam.api.dto.room.UnJoinedRoomDetailsResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RoomMapper { + + public static Room toRoomEntity(CreateRoomRequest createRoomRequest) { + return Room.builder() + .title(createRoomRequest.title()) + .password(createRoomRequest.password()) + .roomType(createRoomRequest.roomType()) + .certifyTime(createRoomRequest.certifyTime()) + .maxUserCount(createRoomRequest.maxUserCount()) + .build(); + } + + public static RoomDetailsResponse toRoomDetailsResponse(Long memberId, Room room, String managerNickname, + List routineResponses, List certifiedDates, + List todayCertificateRankResponses, double completePercentage) { + return RoomDetailsResponse.builder() + .roomId(room.getId()) + .roomCreatedAt(room.getCreatedAt()) + .myMemberId(memberId) + .title(room.getTitle()) + .managerNickName(managerNickname) + .roomImage(room.getRoomImage()) + .level(room.getLevel()) + .currentExp(room.getExp()) + .totalExp(RoomExp.of(room.getLevel()).getTotalExp()) + .roomType(room.getRoomType()) + .certifyTime(room.getCertifyTime()) + .currentUserCount(room.getCurrentUserCount()) + .maxUserCount(room.getMaxUserCount()) + .announcement(room.getAnnouncement()) + .completePercentage(completePercentage) + .certifiedDates(certifiedDates) + .routines(routineResponses) + .todayCertificateRank(todayCertificateRankResponses) + .build(); + } + + public static MyRoomResponse toMyRoomResponse(Room room, boolean isMemberCertifiedToday, + boolean isRoomCertifiedToday) { + return MyRoomResponse.builder() + .roomId(room.getId()) + .title(room.getTitle()) + .roomType(room.getRoomType()) + .certifyTime(room.getCertifyTime()) + .currentUserCount(room.getCurrentUserCount()) + .maxUserCount(room.getMaxUserCount()) + .obtainedBugs(room.getLevel()) + .isMemberCertifiedToday(isMemberCertifiedToday) + .isRoomCertifiedToday(isRoomCertifiedToday) + .build(); + } + + public static MyRoomsResponse toMyRoomsResponse(List myRoomResponses) { + return MyRoomsResponse.builder() + .participatingRooms(myRoomResponses) + .build(); + } + + public static RoomHistoryResponse toRoomHistoryResponse(Long roomId, String title, Participant participant) { + return RoomHistoryResponse.builder() + .roomId(roomId) + .title(title) + .createdAt(participant.getCreatedAt()) + .deletedAt(participant.getDeletedAt()) + .build(); + } + + public static RoomsHistoryResponse toRoomsHistoryResponse(List roomHistoryResponses) { + return RoomsHistoryResponse.builder() + .roomHistory(roomHistoryResponses) + .build(); + } + + public static ManageRoomResponse toManageRoomResponse(Room room, Long managerId, List routines, + List participantResponses) { + return ManageRoomResponse.builder() + .roomId(room.getId()) + .title(room.getTitle()) + .managerId(managerId) + .announcement(room.getAnnouncement()) + .roomType(room.getRoomType()) + .certifyTime(room.getCertifyTime()) + .maxUserCount(room.getMaxUserCount()) + .password(room.getPassword()) + .routines(routines) + .participants(participantResponses) + .build(); + } + + public static GetAllRoomResponse toSearchAllRoomResponse(Room room, List routineResponses, + boolean isPassword) { + return GetAllRoomResponse.builder() + .id(room.getId()) + .title(room.getTitle()) + .image(room.getRoomImage()) + .isPassword(isPassword) + .managerNickname(room.getManagerNickname()) + .level(room.getLevel()) + .roomType(room.getRoomType()) + .certifyTime(room.getCertifyTime()) + .currentUserCount(room.getCurrentUserCount()) + .maxUserCount(room.getMaxUserCount()) + .routines(routineResponses) + .build(); + } + + public static GetAllRoomsResponse toSearchAllRoomsResponse(boolean hasNext, + List getAllRoomResponse) { + return GetAllRoomsResponse.builder() + .hasNext(hasNext) + .rooms(getAllRoomResponse) + .build(); + } + + public static UnJoinedRoomDetailsResponse toUnJoinedRoomDetails(Room room, List routines, + List responses) { + return UnJoinedRoomDetailsResponse.builder() + .roomId(room.getId()) + .isPassword(!isEmpty(room.getPassword())) + .title(room.getTitle()) + .roomImage(room.getRoomImage()) + .level(room.getLevel()) + .currentExp(room.getExp()) + .totalExp(RoomExp.of(room.getLevel()).getTotalExp()) + .roomType(room.getRoomType()) + .certifyTime(room.getCertifyTime()) + .currentUserCount(room.getCurrentUserCount()) + .maxUserCount(room.getMaxUserCount()) + .announcement(room.getAnnouncement()) + .routines(routines) + .certifiedRanks(responses) + .build(); + } + + public static UnJoinedRoomCertificateRankResponse toUnJoinedRoomCertificateRankResponse(Member member, int rank, + Inventory inventory) { + return UnJoinedRoomCertificateRankResponse.builder() + .rank(rank) + .memberId(member.getId()) + .nickname(member.getNickname()) + .awakeImage(inventory.getItem().getAwakeImage()) + .sleepImage(inventory.getItem().getSleepImage()) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/application/room/mapper/RoutineMapper.java b/src/main/java/com/moabam/api/application/room/mapper/RoutineMapper.java new file mode 100644 index 00000000..b2c362c6 --- /dev/null +++ b/src/main/java/com/moabam/api/application/room/mapper/RoutineMapper.java @@ -0,0 +1,32 @@ +package com.moabam.api.application.room.mapper; + +import java.util.List; + +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.dto.room.RoutineResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RoutineMapper { + + public static List toRoutineEntities(Room room, List routinesRequest) { + return routinesRequest.stream() + .map(routine -> Routine.builder() + .room(room) + .content(routine) + .build()) + .toList(); + } + + public static List toRoutineResponses(List routines) { + return routines.stream() + .map(routine -> RoutineResponse.builder() + .routineId(routine.getId()) + .content(routine.getContent()) + .build()) + .toList(); + } +} diff --git a/src/main/java/com/moabam/api/domain/auth/repository/TokenRepository.java b/src/main/java/com/moabam/api/domain/auth/repository/TokenRepository.java new file mode 100644 index 00000000..e104dad0 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/auth/repository/TokenRepository.java @@ -0,0 +1,43 @@ +package com.moabam.api.domain.auth.repository; + +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.api.infrastructure.redis.HashRedisRepository; + +@Repository +public class TokenRepository { + + private static final int EXPIRE_DAYS = 14; + + private final HashRedisRepository hashRedisRepository; + + @Autowired + public TokenRepository(HashRedisRepository hashRedisRepository) { + this.hashRedisRepository = hashRedisRepository; + } + + public void saveToken(Long memberId, TokenSaveValue tokenSaveRequest, Role role) { + String tokenKey = parseTokenKey(memberId, role); + + hashRedisRepository.save(tokenKey, tokenSaveRequest, Duration.ofDays(EXPIRE_DAYS)); + } + + public TokenSaveValue getTokenSaveValue(Long memberId, Role role) { + String tokenKey = parseTokenKey(memberId, role); + return (TokenSaveValue)hashRedisRepository.get(tokenKey); + } + + public void delete(Long memberId, Role role) { + String tokenKey = parseTokenKey(memberId, role); + hashRedisRepository.delete(tokenKey); + } + + private String parseTokenKey(Long memberId, Role role) { + return role.name() + "_" + memberId; + } +} diff --git a/src/main/java/com/moabam/api/domain/bug/Bug.java b/src/main/java/com/moabam/api/domain/bug/Bug.java new file mode 100644 index 00000000..f581c870 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/Bug.java @@ -0,0 +1,88 @@ +package com.moabam.api.domain.bug; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Bug { + + @Column(name = "morning_bug", nullable = false) + @ColumnDefault("0") + private int morningBug; + + @Column(name = "night_bug", nullable = false) + @ColumnDefault("0") + private int nightBug; + + @Column(name = "golden_bug", nullable = false) + @ColumnDefault("0") + private int goldenBug; + + @Builder + private Bug(int morningBug, int nightBug, int goldenBug) { + this.morningBug = validateBugCount(morningBug); + this.nightBug = validateBugCount(nightBug); + this.goldenBug = validateBugCount(goldenBug); + } + + private int validateBugCount(int bug) { + if (bug < 0) { + throw new BadRequestException(INVALID_BUG_COUNT); + } + + return bug; + } + + public void use(BugType bugType, int count) { + int currentBug = getBug(bugType); + + validateEnoughBug(currentBug, count); + decrease(bugType, count); + } + + private int getBug(BugType bugType) { + return switch (bugType) { + case MORNING -> this.morningBug; + case NIGHT -> this.nightBug; + case GOLDEN -> this.goldenBug; + }; + } + + private void validateEnoughBug(int currentBug, int count) { + if (currentBug < count) { + throw new BadRequestException(BUG_NOT_ENOUGH); + } + } + + private void decrease(BugType bugType, int count) { + switch (bugType) { + case MORNING -> this.morningBug -= count; + case NIGHT -> this.nightBug -= count; + case GOLDEN -> this.goldenBug -= count; + } + } + + public void increase(BugType bugType, int count) { + switch (bugType) { + case MORNING -> this.morningBug += count; + case NIGHT -> this.nightBug += count; + case GOLDEN -> this.goldenBug += count; + } + } + + public void charge(int count) { + this.goldenBug += count; + } +} diff --git a/src/main/java/com/moabam/api/domain/bug/BugActionType.java b/src/main/java/com/moabam/api/domain/bug/BugActionType.java new file mode 100644 index 00000000..24dcba45 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/BugActionType.java @@ -0,0 +1,10 @@ +package com.moabam.api.domain.bug; + +public enum BugActionType { + + REWARD, + CHARGE, + USE, + REFUND, + COUPON; +} diff --git a/src/main/java/com/moabam/api/domain/bug/BugHistory.java b/src/main/java/com/moabam/api/domain/bug/BugHistory.java new file mode 100644 index 00000000..9c08f4b4 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/BugHistory.java @@ -0,0 +1,71 @@ +package com.moabam.api.domain.bug; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import com.moabam.api.domain.payment.Payment; +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "bug_history") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BugHistory extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", updatable = false, nullable = false) + private Long memberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "payment_id") + private Payment payment; + + @Enumerated(value = EnumType.STRING) + @Column(name = "bug_type", nullable = false) + private BugType bugType; + + @Enumerated(value = EnumType.STRING) + @Column(name = "action_type", nullable = false) + private BugActionType actionType; + + @Column(name = "quantity", nullable = false) + private int quantity; + + @Builder + private BugHistory(Long memberId, Payment payment, BugType bugType, BugActionType actionType, int quantity) { + this.memberId = requireNonNull(memberId); + this.payment = payment; + this.bugType = requireNonNull(bugType); + this.actionType = requireNonNull(actionType); + this.quantity = validateQuantity(quantity); + } + + private int validateQuantity(int quantity) { + if (quantity < 0) { + throw new BadRequestException(INVALID_QUANTITY); + } + + return quantity; + } +} diff --git a/src/main/java/com/moabam/api/domain/bug/BugType.java b/src/main/java/com/moabam/api/domain/bug/BugType.java new file mode 100644 index 00000000..9aa0535b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/BugType.java @@ -0,0 +1,12 @@ +package com.moabam.api.domain.bug; + +public enum BugType { + + MORNING, + NIGHT, + GOLDEN; + + public boolean isGoldenBug() { + return this == GOLDEN; + } +} diff --git a/src/main/java/com/moabam/api/domain/bug/repository/BugHistoryRepository.java b/src/main/java/com/moabam/api/domain/bug/repository/BugHistoryRepository.java new file mode 100644 index 00000000..191d486e --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/repository/BugHistoryRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.bug.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.bug.BugHistory; + +public interface BugHistoryRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/bug/repository/BugHistorySearchRepository.java b/src/main/java/com/moabam/api/domain/bug/repository/BugHistorySearchRepository.java new file mode 100644 index 00000000..8c5f569a --- /dev/null +++ b/src/main/java/com/moabam/api/domain/bug/repository/BugHistorySearchRepository.java @@ -0,0 +1,38 @@ +package com.moabam.api.domain.bug.repository; + +import static com.moabam.api.domain.bug.QBugHistory.*; +import static com.moabam.api.domain.payment.QPayment.*; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.dto.bug.BugHistoryWithPayment; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class BugHistorySearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findByMemberIdWithPayment(Long memberId) { + return jpaQueryFactory.select(Projections.constructor( + BugHistoryWithPayment.class, + bugHistory.id, + bugHistory.bugType, + bugHistory.actionType, + bugHistory.quantity, + bugHistory.createdAt, + payment) + ) + .from(bugHistory) + .leftJoin(bugHistory.payment, payment) + .where(bugHistory.memberId.eq(memberId)) + .orderBy(bugHistory.createdAt.desc()) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/Coupon.java b/src/main/java/com/moabam/api/domain/coupon/Coupon.java new file mode 100644 index 00000000..a10a11a4 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/Coupon.java @@ -0,0 +1,100 @@ +package com.moabam.api.domain.coupon; + +import static com.moabam.global.common.util.GlobalConstant.*; +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import java.time.LocalDate; +import java.util.Optional; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "coupon") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Coupon extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name", nullable = false, unique = true, length = 20) + private String name; + + @ColumnDefault("1") + @Column(name = "point", nullable = false) + private int point; + + @ColumnDefault("1") + @Column(name = "max_count", nullable = false) + private int maxCount; + + @ColumnDefault("''") + @Column(name = "description", length = 50) + private String description; + + @Enumerated(value = EnumType.STRING) + @Column(name = "type", nullable = false) + private CouponType type; + + @Column(name = "start_at", unique = true, nullable = false) + private LocalDate startAt; + + @Column(name = "open_at", nullable = false) + private LocalDate openAt; + + @Column(name = "admin_id", updatable = false, nullable = false) + private Long adminId; + + @Builder + private Coupon(String name, String description, int point, int maxCount, CouponType type, LocalDate startAt, + LocalDate openAt, Long adminId) { + this.name = requireNonNull(name); + this.point = validatePoint(point); + this.maxCount = validateStock(maxCount); + this.description = Optional.ofNullable(description).orElse(BLANK); + this.type = requireNonNull(type); + this.startAt = requireNonNull(startAt); + this.openAt = requireNonNull(openAt); + this.adminId = requireNonNull(adminId); + } + + @Override + public String toString() { + return String.format("Coupon{startAt=%s, openAt=%s}", startAt, openAt); + } + + private int validatePoint(int point) { + if (point < 1) { + throw new BadRequestException(INVALID_COUPON_POINT); + } + + return point; + } + + private int validateStock(int stock) { + if (stock < 1) { + throw new BadRequestException(INVALID_COUPON_STOCK); + } + + return stock; + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/CouponType.java b/src/main/java/com/moabam/api/domain/coupon/CouponType.java new file mode 100644 index 00000000..ddcfe57a --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/CouponType.java @@ -0,0 +1,60 @@ +package com.moabam.api.domain.coupon; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum CouponType { + + MORNING("아침"), + NIGHT("저녁"), + GOLDEN("황금"), + DISCOUNT("할인"); + + private final String name; + private static final Map COUPON_TYPE_MAP; + + static { + COUPON_TYPE_MAP = Collections.unmodifiableMap(Arrays.stream(values()) + .collect(Collectors.toMap(CouponType::getName, Function.identity()))); + } + + public static CouponType from(String name) { + return Optional.ofNullable(COUPON_TYPE_MAP.get(name)) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_TYPE)); + } + + public boolean isDiscount() { + return this == CouponType.DISCOUNT; + } + + public BugType getBugType() { + if (this == CouponType.MORNING) { + return BugType.MORNING; + } + + if (this == CouponType.NIGHT) { + return BugType.NIGHT; + } + + if (this == CouponType.GOLDEN) { + return BugType.GOLDEN; + } + + throw new BadRequestException(ErrorMessage.INVALID_DISCOUNT_COUPON); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java b/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java new file mode 100644 index 00000000..80994081 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/CouponWallet.java @@ -0,0 +1,44 @@ +package com.moabam.api.domain.coupon; + +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "coupon_wallet") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CouponWallet extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", updatable = false, nullable = false) + private Long memberId; + + @JoinColumn(name = "coupon_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Coupon coupon; + + private CouponWallet(Long memberId, Coupon coupon) { + this.memberId = memberId; + this.coupon = coupon; + } + + public static CouponWallet create(Long memberId, Coupon coupon) { + return new CouponWallet(memberId, coupon); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java new file mode 100644 index 00000000..0381f236 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponManageRepository.java @@ -0,0 +1,83 @@ +package com.moabam.api.domain.coupon.repository; + +import static java.util.Objects.*; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CouponManageRepository { + + private static final String COUPON_COUNT_KEY = "%s_COUPON_COUNT_KEY"; + private static final int EXPIRE_DAYS = 2; + + private final ZSetRedisRepository zSetRedisRepository; + private final ValueRedisRepository valueRedisRepository; + + public void addIfAbsentQueue(String couponName, Long memberId, double registerTime) { + zSetRedisRepository.addIfAbsent( + requireNonNull(couponName), + requireNonNull(memberId), + registerTime, + EXPIRE_DAYS + ); + } + + public Set rangeQueue(String couponName, long start, long end) { + return zSetRedisRepository + .range(requireNonNull(couponName), start, end) + .stream() + .map(memberId -> Long.parseLong(String.valueOf(memberId))) + .collect(Collectors.toSet()); + } + + public boolean hasValue(String couponName, Long memberId) { + return Objects.nonNull(zSetRedisRepository.score(requireNonNull(couponName), memberId)); + } + + public int sizeQueue(String couponName) { + return zSetRedisRepository + .size(requireNonNull(couponName)) + .intValue(); + } + + public int rankQueue(String couponName, Long memberId) { + return zSetRedisRepository + .rank(requireNonNull(couponName), requireNonNull(memberId)) + .intValue(); + } + + public int getCount(String couponName) { + String couponCountKey = String.format(COUPON_COUNT_KEY, requireNonNull(couponName)); + String count = valueRedisRepository.get(couponCountKey); + + if (isNull(count)) { + return 0; + } + + return Integer.parseInt(count); + } + + public void increase(String couponName, long count) { + String couponCountKey = String.format(COUPON_COUNT_KEY, requireNonNull(couponName)); + valueRedisRepository.increment(couponCountKey, count); + } + + public void deleteQueue(String couponName) { + valueRedisRepository.delete(requireNonNull(couponName)); + } + + public void deleteCount(String couponName) { + String couponCountKey = String.format(COUPON_COUNT_KEY, requireNonNull(couponName)); + valueRedisRepository.delete(couponCountKey); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java new file mode 100644 index 00000000..f236858d --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponRepository.java @@ -0,0 +1,21 @@ +package com.moabam.api.domain.coupon.repository; + +import java.time.LocalDate; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.coupon.Coupon; + +public interface CouponRepository extends JpaRepository { + + boolean existsByName(String couponName); + + boolean existsByStartAt(LocalDate startAt); + + Optional findByStartAt(LocalDate startAt); + + Optional findByNameAndStartAt(String couponName, LocalDate startAt); + + boolean existsByNameAndStartAt(String couponName, LocalDate startAt); +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponSearchRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponSearchRepository.java new file mode 100644 index 00000000..703847eb --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponSearchRepository.java @@ -0,0 +1,49 @@ +package com.moabam.api.domain.coupon.repository; + +import static com.moabam.api.domain.coupon.QCoupon.*; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CouponSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findAllByStatus(LocalDate now, CouponStatusRequest couponStatus) { + return jpaQueryFactory.selectFrom(coupon) + .where(filterStatus(now, couponStatus)) + .orderBy(coupon.startAt.asc()) + .fetch(); + } + + private BooleanExpression filterStatus(LocalDate now, CouponStatusRequest couponStatus) { + // 모든 쿠폰 (금일 발급 가능한 쿠폰 포함) + if (couponStatus.opened() && couponStatus.ended()) { + return null; + } + + // 쿠폰 정보 오픈 중인 쿠폰들 (금일 발급 가능한 쿠폰 포함) + if (couponStatus.opened()) { + return coupon.openAt.loe(now).and(coupon.startAt.goe(now)); + } + + // 종료된 쿠폰들 + if (couponStatus.ended()) { + return coupon.startAt.lt(now); + } + + // 금일 발급 가능한 쿠폰 + return coupon.startAt.eq(now); + } +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletRepository.java new file mode 100644 index 00000000..48da8ca1 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletRepository.java @@ -0,0 +1,12 @@ +package com.moabam.api.domain.coupon.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.coupon.CouponWallet; + +public interface CouponWalletRepository extends JpaRepository { + + Optional findByIdAndMemberId(Long id, Long memberId); +} diff --git a/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java new file mode 100644 index 00000000..9a931864 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepository.java @@ -0,0 +1,44 @@ +package com.moabam.api.domain.coupon.repository; + +import static com.moabam.api.domain.coupon.QCoupon.*; +import static com.moabam.api.domain.coupon.QCouponWallet.*; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CouponWalletSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findAllByIdAndMemberId(Long couponWalletId, Long memberId) { + return jpaQueryFactory + .selectFrom(couponWallet) + .join(couponWallet.coupon, coupon).fetchJoin() + .where( + DynamicQuery.generateEq(couponWalletId, couponWallet.id::eq), + DynamicQuery.generateEq(memberId, couponWallet.memberId::eq) + ) + .fetch(); + } + + public Optional findByIdAndMemberId(Long couponWalletId, Long memberId) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(couponWallet) + .join(couponWallet.coupon, coupon).fetchJoin() + .where( + couponWallet.id.eq(couponWalletId), + couponWallet.memberId.eq(memberId)) + .fetchOne() + ); + } +} diff --git a/src/main/java/com/moabam/api/domain/image/ImageName.java b/src/main/java/com/moabam/api/domain/image/ImageName.java new file mode 100644 index 00000000..6f2a34db --- /dev/null +++ b/src/main/java/com/moabam/api/domain/image/ImageName.java @@ -0,0 +1,35 @@ +package com.moabam.api.domain.image; + +import static com.moabam.global.common.util.GlobalConstant.*; + +import java.time.LocalDate; +import java.util.UUID; + +import org.springframework.web.multipart.MultipartFile; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class ImageName { + + private static final String CERTIFICATION_PATH = "certifications" + DELIMITER + LocalDate.now() + DELIMITER; + private static final String PROFILE_IMAGE = "members/profile" + DELIMITER; + private static final String BIRD_SKIN = "moabam/skins" + DELIMITER; + private static final String DEFAULT = "moabam/default" + DELIMITER; + + private final String fileName; + + public static ImageName of(MultipartFile file, ImageType imageType) { + return switch (imageType) { + case CERTIFICATION -> + new ImageName(CERTIFICATION_PATH + file.getName() + "_" + UUID.randomUUID() + IMAGE_EXTENSION); + case PROFILE_IMAGE -> + new ImageName(PROFILE_IMAGE + file.getName() + "_" + UUID.randomUUID() + IMAGE_EXTENSION); + case BIRD_SKIN -> new ImageName(BIRD_SKIN + file.getName() + IMAGE_EXTENSION); + case DEFAULT -> new ImageName(DEFAULT + file.getName() + IMAGE_EXTENSION); + }; + } +} diff --git a/src/main/java/com/moabam/api/domain/image/ImageResizer.java b/src/main/java/com/moabam/api/domain/image/ImageResizer.java new file mode 100644 index 00000000..82b2f1d4 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/image/ImageResizer.java @@ -0,0 +1,122 @@ +package com.moabam.api.domain.image; + +import static com.moabam.global.common.util.GlobalConstant.DELIMITER; +import static com.moabam.global.error.model.ErrorMessage.S3_INVALID_IMAGE; +import static com.moabam.global.error.model.ErrorMessage.S3_INVALID_IMAGE_SIZE; +import static com.moabam.global.error.model.ErrorMessage.S3_RESIZE_ERROR; +import static java.util.Objects.requireNonNull; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.springframework.web.multipart.MultipartFile; + +import com.moabam.global.error.exception.BadRequestException; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Getter +@Slf4j +public class ImageResizer { + + private static final int MAX_IMAGE_SIZE = 1024 * 1024 * 10; + private static final String IMAGE_FORMAT_PREFIX = "image/"; + private static final int FORMAT_INDEX = 1; + + private final MultipartFile image; + private final String fileName; + private MultipartFile resizedImage; + + @Builder + public ImageResizer(MultipartFile image, String fileName) { + this.image = validate(image); + this.fileName = fileName; + } + + public MultipartFile validate(MultipartFile image) { + if (isNotImage(image)) { + throw new BadRequestException(S3_INVALID_IMAGE); + } + if (image.getSize() > MAX_IMAGE_SIZE) { + throw new BadRequestException(S3_INVALID_IMAGE_SIZE); + } + + return image; + } + + private boolean isNotImage(MultipartFile image) { + String contentType = requireNonNull(image.getContentType()); + + return !contentType.startsWith(IMAGE_FORMAT_PREFIX); + } + + public void resizeImageToFixedSize(ImageType imageType) { + ImageSize imageSize = switch (imageType) { + case PROFILE_IMAGE -> ImageSize.PROFILE_IMAGE; + case CERTIFICATION -> ImageSize.CERTIFICATION_IMAGE; + case BIRD_SKIN -> ImageSize.BIRD_SKIN; + case DEFAULT -> ImageSize.CAGE; + }; + + BufferedImage bufferedImage = getBufferedImage(); + + int width = imageSize.getWidth(); + int height = getResizedHeight(width, bufferedImage); + BufferedImage scaledImage = resize(bufferedImage, width, height); + + byte[] bytes = toByteArray(scaledImage); + this.resizedImage = toMultipartFile(bytes); + } + + private int getResizedHeight(int width, BufferedImage bufferedImage) { + double ratio = (double)width / bufferedImage.getWidth(); + + return (int)(bufferedImage.getHeight() * ratio); + } + + private BufferedImage resize(BufferedImage image, int width, int height) { + BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + Graphics graphics = canvas.getGraphics(); + graphics.drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); + graphics.dispose(); + + return canvas; + } + + private byte[] toByteArray(final BufferedImage result) { + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(result, getFormat(), byteArrayOutputStream); + + return byteArrayOutputStream.toByteArray(); + } catch (IOException e) { + log.error("이미지 리사이징 에러", e); + throw new BadRequestException(S3_RESIZE_ERROR); + } + } + + private String getFormat() { + return requireNonNull(image.getContentType()).split(DELIMITER)[FORMAT_INDEX]; + } + + private BufferedImage getBufferedImage() { + try { + return ImageIO.read(image.getInputStream()); + } catch (IOException e) { + log.error("이미지 리사이징 에러", e); + throw new BadRequestException(S3_RESIZE_ERROR); + } + } + + private NewImage toMultipartFile(byte[] bytes) { + return NewImage.of(fileName, image.getContentType(), bytes); + } +} diff --git a/src/main/java/com/moabam/api/domain/image/ImageSize.java b/src/main/java/com/moabam/api/domain/image/ImageSize.java new file mode 100644 index 00000000..9f311251 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/image/ImageSize.java @@ -0,0 +1,17 @@ +package com.moabam.api.domain.image; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ImageSize { + + CAGE(450), + BIRD_SKIN(150), + COUPON_EVENT(420), + PROFILE_IMAGE(150), + CERTIFICATION_IMAGE(220); + + private final int width; +} diff --git a/src/main/java/com/moabam/api/domain/image/ImageType.java b/src/main/java/com/moabam/api/domain/image/ImageType.java new file mode 100644 index 00000000..dc4dc358 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/image/ImageType.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.image; + +public enum ImageType { + + PROFILE_IMAGE, + CERTIFICATION, + BIRD_SKIN, + DEFAULT +} diff --git a/src/main/java/com/moabam/api/domain/image/NewImage.java b/src/main/java/com/moabam/api/domain/image/NewImage.java new file mode 100644 index 00000000..6367d63c --- /dev/null +++ b/src/main/java/com/moabam/api/domain/image/NewImage.java @@ -0,0 +1,67 @@ +package com.moabam.api.domain.image; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.web.multipart.MultipartFile; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class NewImage implements MultipartFile { + + private final String name; + private final String contentType; + private final long size; + private final byte[] bytes; + + public static NewImage of(String name, String contentType, byte[] bytes) { + return new NewImage(name, contentType, bytes.length, bytes); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getOriginalFilename() { + return name; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public long getSize() { + return size; + } + + @Override + public byte[] getBytes() throws IOException { + return bytes; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(bytes); + } + + @Override + public void transferTo(File dest) throws IOException, IllegalStateException { + try (FileOutputStream fileOutputStream = new FileOutputStream(dest)) { + fileOutputStream.write(this.getBytes()); + } + } +} diff --git a/src/main/java/com/moabam/api/domain/item/Inventory.java b/src/main/java/com/moabam/api/domain/item/Inventory.java new file mode 100644 index 00000000..44c6ab26 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/Inventory.java @@ -0,0 +1,66 @@ +package com.moabam.api.domain.item; + +import static java.util.Objects.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.api.domain.member.Member; +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "inventory", indexes = @Index(name = "idx_member_id", columnList = "member_id")) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Inventory extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", updatable = false, nullable = false) + private Long memberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "item_id", updatable = false, nullable = false) + private Item item; + + @Column(name = "is_default", nullable = false) + @ColumnDefault("false") + private boolean isDefault; + + @Builder + private Inventory(Long memberId, Item item, boolean isDefault) { + this.memberId = requireNonNull(memberId); + this.item = requireNonNull(item); + this.isDefault = isDefault; + } + + public ItemType getItemType() { + return this.item.getType(); + } + + public void select(Member member) { + this.isDefault = true; + member.changeDefaultSkintUrl(this.item); + } + + public void deselect() { + this.isDefault = false; + } +} diff --git a/src/main/java/com/moabam/api/domain/item/Item.java b/src/main/java/com/moabam/api/domain/item/Item.java new file mode 100644 index 00000000..6fefd03e --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/Item.java @@ -0,0 +1,119 @@ +package com.moabam.api.domain.item; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "item") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Item extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Enumerated(value = EnumType.STRING) + @Column(name = "type", nullable = false) + private ItemType type; + + @Enumerated(value = EnumType.STRING) + @Column(name = "category", nullable = false) + private ItemCategory category; + + @Column(name = "name", nullable = false) + private String name; + + @Embedded + private ItemImage image; + + @Column(name = "bug_price", nullable = false) + @ColumnDefault("0") + private int bugPrice; + + @Column(name = "golden_bug_price", nullable = false) + @ColumnDefault("0") + private int goldenBugPrice; + + @Column(name = "unlock_level", nullable = false) + @ColumnDefault("1") + private int unlockLevel; + + @Builder + private Item(ItemType type, ItemCategory category, String name, ItemImage image, int bugPrice, int goldenBugPrice, + Integer unlockLevel) { + this.type = requireNonNull(type); + this.category = requireNonNull(category); + this.name = requireNonNull(name); + this.image = requireNonNull(image); + this.bugPrice = validatePrice(bugPrice); + this.goldenBugPrice = validatePrice(goldenBugPrice); + this.unlockLevel = validateLevel(requireNonNullElse(unlockLevel, 1)); + } + + private int validatePrice(int price) { + if (price < 0) { + throw new BadRequestException(INVALID_PRICE); + } + + return price; + } + + private int validateLevel(int level) { + if (level < 1) { + throw new BadRequestException(INVALID_LEVEL); + } + + return level; + } + + public void validatePurchasable(BugType bugType, int memberLevel) { + validateUnlocked(memberLevel); + validateBugTypeMatch(bugType); + } + + private void validateUnlocked(int memberLevel) { + if (this.unlockLevel > memberLevel) { + throw new BadRequestException(ITEM_UNLOCK_LEVEL_HIGH); + } + } + + private void validateBugTypeMatch(BugType bugType) { + if (!this.type.isPurchasableBy(bugType)) { + throw new BadRequestException(ITEM_NOT_PURCHASABLE_BY_BUG_TYPE); + } + } + + public int getPrice(BugType bugType) { + return bugType.isGoldenBug() ? this.goldenBugPrice : this.bugPrice; + } + + public String getAwakeImage() { + return this.getImage().getAwake(); + } + + public String getSleepImage() { + return this.getImage().getSleep(); + } +} diff --git a/src/main/java/com/moabam/api/domain/item/ItemCategory.java b/src/main/java/com/moabam/api/domain/item/ItemCategory.java new file mode 100644 index 00000000..5690d5f5 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/ItemCategory.java @@ -0,0 +1,6 @@ +package com.moabam.api.domain.item; + +public enum ItemCategory { + + SKIN; +} diff --git a/src/main/java/com/moabam/api/domain/item/ItemImage.java b/src/main/java/com/moabam/api/domain/item/ItemImage.java new file mode 100644 index 00000000..937a55b1 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/ItemImage.java @@ -0,0 +1,28 @@ +package com.moabam.api.domain.item; + +import static java.util.Objects.*; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ItemImage { + + @Column(name = "awake_image", nullable = false) + private String awake; + + @Column(name = "sleep_image", nullable = false) + private String sleep; + + @Builder + public ItemImage(String awakeImage, String sleepImage) { + this.awake = requireNonNull(awakeImage); + this.sleep = requireNonNull(sleepImage); + } +} diff --git a/src/main/java/com/moabam/api/domain/item/ItemType.java b/src/main/java/com/moabam/api/domain/item/ItemType.java new file mode 100644 index 00000000..4297d567 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/ItemType.java @@ -0,0 +1,21 @@ +package com.moabam.api.domain.item; + +import java.util.List; + +import com.moabam.api.domain.bug.BugType; + +public enum ItemType { + + MORNING(List.of(BugType.MORNING, BugType.GOLDEN)), + NIGHT(List.of(BugType.NIGHT, BugType.GOLDEN)); + + private final List purchasableBugTypes; + + ItemType(List purchasableBugTypes) { + this.purchasableBugTypes = purchasableBugTypes; + } + + public boolean isPurchasableBy(BugType bugType) { + return this.purchasableBugTypes.contains(bugType); + } +} diff --git a/src/main/java/com/moabam/api/domain/item/repository/InventoryRepository.java b/src/main/java/com/moabam/api/domain/item/repository/InventoryRepository.java new file mode 100644 index 00000000..73a044f3 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/repository/InventoryRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.item.Inventory; + +public interface InventoryRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java b/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java new file mode 100644 index 00000000..0141d2e4 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/repository/InventorySearchRepository.java @@ -0,0 +1,77 @@ +package com.moabam.api.domain.item.repository; + +import static com.moabam.api.domain.item.QInventory.*; +import static com.moabam.api.domain.item.QItem.*; + +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class InventorySearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public Optional findOne(Long memberId, Long itemId) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(inventory) + .where( + DynamicQuery.generateEq(memberId, inventory.memberId::eq), + DynamicQuery.generateEq(itemId, inventory.item.id::eq)) + .fetchOne() + ); + } + + public Optional findDefault(Long memberId, ItemType type) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(inventory) + .where( + DynamicQuery.generateEq(memberId, inventory.memberId::eq), + DynamicQuery.generateEq(type, inventory.item.type::eq), + inventory.isDefault.isTrue()) + .fetchOne() + ); + } + + public List findItems(Long memberId, ItemType type) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item, item) + .where( + DynamicQuery.generateEq(memberId, inventory.memberId::eq), + DynamicQuery.generateEq(type, inventory.item.type::eq)) + .orderBy(inventory.createdAt.desc()) + .select(item) + .fetch(); + } + + public List findDefaultSkin(Long memberId) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item) + .on(inventory.item.id.eq(item.id)) + .where( + inventory.memberId.eq(memberId), + inventory.isDefault.isTrue() + ).fetch(); + } + + public List findDefaultInventories(List memberId, String roomType) { + return jpaQueryFactory.selectFrom(inventory) + .join(inventory.item, item).fetchJoin() + .where( + inventory.memberId.in(memberId), + inventory.isDefault.isTrue(), + inventory.item.type.eq(ItemType.valueOf(roomType)) + ) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/item/repository/ItemRepository.java b/src/main/java/com/moabam/api/domain/item/repository/ItemRepository.java new file mode 100644 index 00000000..dd5554b8 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/repository/ItemRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.item.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.item.Item; + +public interface ItemRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/item/repository/ItemSearchRepository.java b/src/main/java/com/moabam/api/domain/item/repository/ItemSearchRepository.java new file mode 100644 index 00000000..14a62b7e --- /dev/null +++ b/src/main/java/com/moabam/api/domain/item/repository/ItemSearchRepository.java @@ -0,0 +1,39 @@ +package com.moabam.api.domain.item.repository; + +import static com.moabam.api.domain.item.QInventory.inventory; +import static com.moabam.api.domain.item.QItem.item; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ItemSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findNotPurchasedItems(Long memberId, ItemType type) { + return jpaQueryFactory.selectFrom(item) + .leftJoin(inventory) + .on(inventory.item.id.eq(item.id) + .and(inventory.memberId.eq(memberId))) + .where( + DynamicQuery.generateEq(type, item.type::eq), + inventory.memberId.isNull() + ) + .orderBy( + item.unlockLevel.asc(), + item.bugPrice.asc(), + item.goldenBugPrice.asc(), + item.name.asc()) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/Badge.java b/src/main/java/com/moabam/api/domain/member/Badge.java new file mode 100644 index 00000000..491a8ebf --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/Badge.java @@ -0,0 +1,48 @@ +package com.moabam.api.domain.member; + +import static java.util.Objects.*; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class Badge { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", nullable = false) + private Long memberId; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private BadgeType type; + + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @Builder + private Badge(Long memberId, BadgeType type) { + this.memberId = requireNonNull(memberId); + this.type = requireNonNull(type); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/BadgeType.java b/src/main/java/com/moabam/api/domain/member/BadgeType.java new file mode 100644 index 00000000..e0819f9a --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/BadgeType.java @@ -0,0 +1,41 @@ +package com.moabam.api.domain.member; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import com.moabam.api.dto.member.BadgeResponse; + +import lombok.Getter; + +@Getter +public enum BadgeType { + + BIRTH(10, "탄생 축하 뱃지"), + LEVEL10(100, "10레벨 뱃지"), + LEVEL50(500, "50레벨 뱃지"); + + private final long certifyCount; + private final String korean; + + BadgeType(long certifyCount, String korean) { + this.certifyCount = certifyCount; + this.korean = korean; + } + + public static List memberBadgeMap(Set badgeTypes) { + return Arrays.stream(BadgeType.values()) + .map(badgeType -> BadgeResponse.builder() + .badge(badgeType.korean) + .unlock(badgeTypes.contains(badgeType)) + .build()) + .toList(); + } + + public static Optional getBadgeFrom(long certifyCount) { + return Arrays.stream(BadgeType.values()) + .filter(badgeType -> badgeType.certifyCount == certifyCount) + .findFirst(); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/Member.java b/src/main/java/com/moabam/api/domain/member/Member.java new file mode 100644 index 00000000..51e8d9c3 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/Member.java @@ -0,0 +1,178 @@ +package com.moabam.api.domain.member; + +import static com.moabam.global.common.util.BaseImageUrl.*; +import static com.moabam.global.common.util.GlobalConstant.*; +import static com.moabam.global.common.util.RandomUtils.*; +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import java.time.LocalDateTime; +import java.util.Objects; + +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; + +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.room.RoomType; +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.NotFoundException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "member") +@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where id = ?") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "social_id", nullable = false, unique = true) + private String socialId; + + @Column(name = "nickname", unique = true) + private String nickname; + + @Column(name = "intro", length = 30) + private String intro; + + @Column(name = "profile_image", nullable = false) + private String profileImage; + + @Column(name = "morning_image", nullable = false) + private String morningImage; + + @Column(name = "night_image", nullable = false) + private String nightImage; + + @Column(name = "total_certify_count", nullable = false) + @ColumnDefault("0") + private long totalCertifyCount; + + @Column(name = "report_count", nullable = false) + @ColumnDefault("0") + private int reportCount; + + @Column(name = "current_night_count", nullable = false) + @ColumnDefault("0") + private int currentNightCount; + + @Column(name = "current_morning_count", nullable = false) + @ColumnDefault("0") + private int currentMorningCount; + + @Embedded + private Bug bug; + + @Enumerated(EnumType.STRING) + @Column(name = "role", nullable = false) + @ColumnDefault("'USER'") + private Role role; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + + @Builder + private Member(Long id, String socialId, Bug bug) { + this.id = id; + this.socialId = requireNonNull(socialId); + this.nickname = createNickName(); + this.intro = ""; + this.profileImage = IMAGE_DOMAIN + MEMBER_PROFILE_URL; + this.morningImage = IMAGE_DOMAIN + DEFAULT_MORNING_EGG_URL; + this.nightImage = IMAGE_DOMAIN + DEFAULT_NIGHT_EGG_URL; + this.bug = requireNonNull(bug); + this.role = Role.USER; + } + + public void enterRoom(RoomType roomType) { + if (roomType.equals(RoomType.MORNING)) { + this.currentMorningCount++; + return; + } + + if (roomType.equals(RoomType.NIGHT)) { + this.currentNightCount++; + } + } + + public void exitRoom(RoomType roomType) { + if (roomType.equals(RoomType.MORNING) && currentMorningCount > 0) { + this.currentMorningCount--; + return; + } + + if (roomType.equals(RoomType.NIGHT) && currentNightCount > 0) { + this.currentNightCount--; + } + } + + public int getLevel() { + return (int)(totalCertifyCount / LEVEL_DIVISOR) + 1; + } + + public void increaseTotalCertifyCount() { + this.totalCertifyCount++; + } + + public void delete(LocalDateTime now) { + socialId = deleteSocialId(now); + nickname = null; + } + + public boolean changeNickName(String nickname) { + if (Objects.isNull(nickname)) { + return false; + } + this.nickname = nickname; + return true; + } + + public void changeIntro(String intro) { + this.intro = requireNonNullElse(intro, this.intro); + } + + public void changeProfileUri(String newProfileUri) { + this.profileImage = requireNonNullElse(newProfileUri, profileImage); + } + + public void changeDefaultSkintUrl(Item item) throws NotFoundException { + if (ItemType.MORNING.equals(item.getType())) { + this.morningImage = item.getAwakeImage(); + return; + } + + if (ItemType.NIGHT.equals(item.getType())) { + this.nightImage = item.getAwakeImage(); + return; + } + + throw new NotFoundException(SKIN_TYPE_NOT_FOUND); + } + + private String createNickName() { + return "오목눈이#" + randomStringValues(); + } + + private String deleteSocialId(LocalDateTime now) { + return "delete_" + now.toString() + randomNumberValues(); + } +} diff --git a/src/main/java/com/moabam/api/domain/member/Role.java b/src/main/java/com/moabam/api/domain/member/Role.java new file mode 100644 index 00000000..b7e80810 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/Role.java @@ -0,0 +1,8 @@ +package com.moabam.api.domain.member; + +public enum Role { + + USER, + BLACK, + ADMIN +} diff --git a/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java b/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java new file mode 100644 index 00000000..ac313e25 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/repository/BadgeRepository.java @@ -0,0 +1,12 @@ +package com.moabam.api.domain.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; + +public interface BadgeRepository extends JpaRepository { + + boolean existsByMemberIdAndType(Long memberId, BadgeType type); + +} diff --git a/src/main/java/com/moabam/api/domain/member/repository/MemberRepository.java b/src/main/java/com/moabam/api/domain/member/repository/MemberRepository.java new file mode 100644 index 00000000..f0cb499b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/repository/MemberRepository.java @@ -0,0 +1,14 @@ +package com.moabam.api.domain.member.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.member.Member; + +public interface MemberRepository extends JpaRepository { + + Optional findBySocialId(String id); + + boolean existsByNickname(String nickname); +} diff --git a/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java b/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java new file mode 100644 index 00000000..6285f9bb --- /dev/null +++ b/src/main/java/com/moabam/api/domain/member/repository/MemberSearchRepository.java @@ -0,0 +1,88 @@ +package com.moabam.api.domain.member.repository; + +import static com.moabam.api.domain.member.QBadge.*; +import static com.moabam.api.domain.member.QMember.*; +import static com.moabam.api.domain.room.QParticipant.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class MemberSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public Optional findMember(Long memberId) { + return findMember(memberId, true); + } + + public List findAllMembers() { + return jpaQueryFactory + .selectFrom(member) + .where( + member.deletedAt.isNull() + ) + .fetch(); + } + + public Optional findMember(Long memberId, boolean isNotDeleted) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(member) + .where( + DynamicQuery.generateIsNull(isNotDeleted, member.deletedAt), + member.id.eq(memberId) + ) + .fetchOne()); + } + + public List findParticipantByMemberId(Long memberId) { + return jpaQueryFactory + .selectFrom(participant) + .where( + participant.memberId.eq(memberId), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findMemberAndBadges(Long searchId, boolean isMe) { + List> selectExpression = new ArrayList<>(List.of( + member.nickname, + member.profileImage, + member.morningImage, + member.nightImage, + member.intro, + member.totalCertifyCount, + badge.type)); + + if (isMe) { + selectExpression.addAll(List.of( + member.bug.goldenBug, + member.bug.morningBug, + member.bug.nightBug)); + } + + return jpaQueryFactory + .select(Projections.constructor(MemberInfo.class, selectExpression.toArray(new Expression[0]))) + .from(member) + .leftJoin(badge).on(member.id.eq(badge.memberId)) + .where( + DynamicQuery.generateIsNull(true, member.deletedAt), + member.id.eq(searchId) + ).fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java b/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java new file mode 100644 index 00000000..ce856cc8 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/notification/repository/NotificationRepository.java @@ -0,0 +1,42 @@ +package com.moabam.api.domain.notification.repository; + +import static com.moabam.global.common.util.GlobalConstant.*; +import static java.util.Objects.*; + +import java.time.Duration; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class NotificationRepository { + + private static final String KNOCK_KEY = "roomId=%s_targetId=%s_memberId=%s"; + private static final long EXPIRE_KNOCK = 12; + + private final ValueRedisRepository valueRedisRepository; + + public void saveKnock(Long roomId, Long targetId, Long memberId) { + String knockKey = String.format( + KNOCK_KEY, + requireNonNull(roomId), + requireNonNull(targetId), + requireNonNull(memberId)); + + valueRedisRepository.save(knockKey, BLANK, Duration.ofHours(EXPIRE_KNOCK)); + } + + public boolean existsKnockByKey(Long roomId, Long targetId, Long memberId) { + String knockKey = String.format( + KNOCK_KEY, + requireNonNull(roomId), + requireNonNull(targetId), + requireNonNull(memberId)); + + return valueRedisRepository.hasKey(requireNonNull(knockKey)); + } +} diff --git a/src/main/java/com/moabam/api/domain/payment/Order.java b/src/main/java/com/moabam/api/domain/payment/Order.java new file mode 100644 index 00000000..c907c46d --- /dev/null +++ b/src/main/java/com/moabam/api/domain/payment/Order.java @@ -0,0 +1,31 @@ +package com.moabam.api.domain.payment; + +import static java.util.Objects.*; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Order { + + @Column(name = "order_id") + private String id; + + @Column(name = "order_name", nullable = false) + private String name; + + @Builder + private Order(String name) { + this.name = requireNonNull(name); + } + + public void updateId(String id) { + this.id = id; + } +} diff --git a/src/main/java/com/moabam/api/domain/payment/Payment.java b/src/main/java/com/moabam/api/domain/payment/Payment.java new file mode 100644 index 00000000..db81105b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/payment/Payment.java @@ -0,0 +1,146 @@ +package com.moabam.api.domain.payment; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.product.Product; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "payment", indexes = @Index(name = "idx_order_id", columnList = "order_id")) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class Payment { + + private static final int MIN_AMOUNT = 0; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", updatable = false, nullable = false) + private Long memberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "product_id", updatable = false, nullable = false) + private Product product; + + @Column(name = "coupon_wallet_id") + private Long couponWalletId; + + @Embedded + private Order order; + + @Column(name = "total_amount", nullable = false) + private int totalAmount; + + @Column(name = "discount_amount", nullable = false) + private int discountAmount; + + @Column(name = "payment_key") + private String paymentKey; + + @Enumerated(value = EnumType.STRING) + @Column(name = "status", nullable = false) + private PaymentStatus status; + + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @Column(name = "requested_at") + private LocalDateTime requestedAt; + + @Column(name = "approved_at") + private LocalDateTime approvedAt; + + @Builder + public Payment(Long memberId, Product product, Long couponWalletId, Order order, int totalAmount, + int discountAmount, PaymentStatus status) { + this.memberId = requireNonNull(memberId); + this.product = requireNonNull(product); + this.couponWalletId = couponWalletId; + this.order = requireNonNull(order); + this.totalAmount = validateAmount(totalAmount); + this.discountAmount = validateAmount(discountAmount); + this.status = requireNonNullElse(status, PaymentStatus.READY); + } + + private int validateAmount(int amount) { + if (amount < MIN_AMOUNT) { + throw new BadRequestException(INVALID_PAYMENT_AMOUNT); + } + + return amount; + } + + public void validateInfo(Long memberId, int amount) { + validateByMember(memberId); + validateByTotalAmount(amount); + } + + public void validateByMember(Long memberId) { + if (!this.memberId.equals(memberId)) { + throw new BadRequestException(INVALID_MEMBER_PAYMENT); + } + } + + private void validateByTotalAmount(int amount) { + if (this.totalAmount != amount) { + throw new BadRequestException(INVALID_PAYMENT_INFO); + } + } + + public boolean isCouponApplied() { + return !isNull(this.couponWalletId); + } + + public void applyCoupon(CouponWallet couponWallet) { + this.couponWalletId = couponWallet.getId(); + this.discountAmount = couponWallet.getCoupon().getPoint(); + this.totalAmount = Math.max(MIN_AMOUNT, this.totalAmount - this.discountAmount); + } + + public void request(String orderId) { + this.order.updateId(orderId); + this.requestedAt = LocalDateTime.now(); + } + + public void confirm(String paymentKey) { + this.paymentKey = paymentKey; + this.approvedAt = LocalDateTime.now(); + this.status = PaymentStatus.DONE; + } + + public void fail(String paymentKey) { + this.paymentKey = paymentKey; + this.status = PaymentStatus.ABORTED; + } +} diff --git a/src/main/java/com/moabam/api/domain/payment/PaymentStatus.java b/src/main/java/com/moabam/api/domain/payment/PaymentStatus.java new file mode 100644 index 00000000..dae76ff6 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/payment/PaymentStatus.java @@ -0,0 +1,19 @@ +package com.moabam.api.domain.payment; + +/** + * READY: 결제 생성 + * IN_PROGRESS: 결제 인증 완료 + * DONE: 결제 승인 완료 + * CANCELED: 승인된 결제 취소 + * ABORTED: 결제 승인 실패 + * EXPIRED: 유효 시간 경과로 거래 취소 + */ +public enum PaymentStatus { + + READY, + IN_PROGRESS, + DONE, + CANCELED, + ABORTED, + EXPIRED; +} diff --git a/src/main/java/com/moabam/api/domain/payment/repository/PaymentRepository.java b/src/main/java/com/moabam/api/domain/payment/repository/PaymentRepository.java new file mode 100644 index 00000000..aca0dba9 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/payment/repository/PaymentRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.payment.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.payment.Payment; + +public interface PaymentRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/payment/repository/PaymentSearchRepository.java b/src/main/java/com/moabam/api/domain/payment/repository/PaymentSearchRepository.java new file mode 100644 index 00000000..dafb6925 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/payment/repository/PaymentSearchRepository.java @@ -0,0 +1,27 @@ +package com.moabam.api.domain.payment.repository; + +import static com.moabam.api.domain.payment.QPayment.*; + +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.payment.Payment; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class PaymentSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public Optional findByOrderId(String orderId) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(payment) + .where(payment.order.id.eq(orderId)) + .fetchOne() + ); + } +} diff --git a/src/main/java/com/moabam/api/domain/product/Product.java b/src/main/java/com/moabam/api/domain/product/Product.java new file mode 100644 index 00000000..d99249e1 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/product/Product.java @@ -0,0 +1,73 @@ +package com.moabam.api.domain.product; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import org.hibernate.annotations.ColumnDefault; + +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "product") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Product extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Enumerated(value = EnumType.STRING) + @Column(name = "type", nullable = false) + @ColumnDefault("'BUG'") + private ProductType type; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "price", nullable = false) + private int price; + + @Column(name = "quantity", nullable = false) + @ColumnDefault("1") + private int quantity; + + @Builder + private Product(ProductType type, String name, int price, Integer quantity) { + this.type = requireNonNullElse(type, ProductType.BUG); + this.name = requireNonNull(name); + this.price = validatePrice(price); + this.quantity = validateQuantity(requireNonNullElse(quantity, 1)); + } + + private int validatePrice(int price) { + if (price < 0) { + throw new BadRequestException(INVALID_PRICE); + } + + return price; + } + + private int validateQuantity(int quantity) { + if (quantity < 1) { + throw new BadRequestException(INVALID_QUANTITY); + } + + return quantity; + } +} diff --git a/src/main/java/com/moabam/api/domain/product/ProductType.java b/src/main/java/com/moabam/api/domain/product/ProductType.java new file mode 100644 index 00000000..1dc5c46b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/product/ProductType.java @@ -0,0 +1,6 @@ +package com.moabam.api.domain.product; + +public enum ProductType { + + BUG; +} diff --git a/src/main/java/com/moabam/api/domain/product/repository/ProductRepository.java b/src/main/java/com/moabam/api/domain/product/repository/ProductRepository.java new file mode 100644 index 00000000..358aa0c0 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/product/repository/ProductRepository.java @@ -0,0 +1,13 @@ +package com.moabam.api.domain.product.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.ProductType; + +public interface ProductRepository extends JpaRepository { + + List findAllByType(ProductType type); +} diff --git a/src/main/java/com/moabam/api/domain/report/Report.java b/src/main/java/com/moabam/api/domain/report/Report.java new file mode 100644 index 00000000..4e120055 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/report/Report.java @@ -0,0 +1,56 @@ +package com.moabam.api.domain.report; + +import static java.util.Objects.*; + +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "report") +@Entity +public class Report extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "reporter_id", nullable = false, updatable = false) + private Long reporterId; + + @Column(name = "reported_member_id", nullable = false, updatable = false) + private Long reportedMemberId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", updatable = false) + private Room room; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "certification_id", updatable = false) + private Certification certification; + + @Column(name = "description") + private String description; + + @Builder + private Report(Long reporterId, Long reportedMemberId, Room room, Certification certification, String description) { + this.reporterId = requireNonNull(reporterId); + this.reportedMemberId = requireNonNull(reportedMemberId); + this.room = room; + this.certification = certification; + this.description = description; + } +} diff --git a/src/main/java/com/moabam/api/domain/report/repository/ReportRepository.java b/src/main/java/com/moabam/api/domain/report/repository/ReportRepository.java new file mode 100644 index 00000000..1655fbbc --- /dev/null +++ b/src/main/java/com/moabam/api/domain/report/repository/ReportRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.report.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.report.Report; + +public interface ReportRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/room/Certification.java b/src/main/java/com/moabam/api/domain/room/Certification.java new file mode 100644 index 00000000..c2ce121b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/Certification.java @@ -0,0 +1,53 @@ +package com.moabam.api.domain.room; + +import static java.util.Objects.*; + +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "certification") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Certification extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "routine_id", nullable = false, updatable = false) + private Routine routine; + + @Column(name = "member_id", nullable = false, updatable = false) + private Long memberId; + + @Column(name = "image", nullable = false) + private String image; + + @Builder + private Certification(Long id, Routine routine, Long memberId, String image) { + this.id = id; + this.routine = requireNonNull(routine); + this.memberId = requireNonNull(memberId); + this.image = requireNonNull(image); + } + + public void changeImage(String image) { + this.image = image; + } +} diff --git a/src/main/java/com/moabam/api/domain/room/DailyMemberCertification.java b/src/main/java/com/moabam/api/domain/room/DailyMemberCertification.java new file mode 100644 index 00000000..afe64cd2 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/DailyMemberCertification.java @@ -0,0 +1,49 @@ +package com.moabam.api.domain.room; + +import static java.util.Objects.*; + +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "daily_member_certification") // 매일 사용자가 방에 인증을 완료했는지 -> createdAt으로 인증 시각 확인 +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyMemberCertification extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "member_id", nullable = false, updatable = false) + private Long memberId; + + @Column(name = "room_id", nullable = false, updatable = false) + private Long roomId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "participant_id") // Participant createdAt으로 방 참여 시작 날짜 확인, certifyCount 가져다가 쓰기 + private Participant participant; + + @Builder + private DailyMemberCertification(Long id, Long memberId, Long roomId, Participant participant) { + this.id = id; + this.memberId = requireNonNull(memberId); + this.roomId = requireNonNull(roomId); + this.participant = requireNonNull(participant); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/DailyRoomCertification.java b/src/main/java/com/moabam/api/domain/room/DailyRoomCertification.java new file mode 100644 index 00000000..573261ba --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/DailyRoomCertification.java @@ -0,0 +1,41 @@ +package com.moabam.api.domain.room; + +import static java.util.Objects.*; + +import java.time.LocalDate; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "daily_room_certification") // 매일 방이 인증을 완료했는지 -> certifiedAt으로 인증 날짜 확인 +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyRoomCertification { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "room_id", nullable = false, updatable = false) + private Long roomId; + + @Column(name = "certified_at", nullable = false, updatable = false) + private LocalDate certifiedAt; + + @Builder + private DailyRoomCertification(Long id, Long roomId, LocalDate certifiedAt) { + this.id = id; + this.roomId = requireNonNull(roomId); + this.certifiedAt = requireNonNull(certifiedAt); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/Participant.java b/src/main/java/com/moabam/api/domain/room/Participant.java new file mode 100644 index 00000000..0a0d3d02 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/Participant.java @@ -0,0 +1,80 @@ +package com.moabam.api.domain.room; + +import static java.util.Objects.*; + +import java.time.LocalDateTime; + +import org.hibernate.annotations.SQLDelete; + +import com.moabam.global.common.entity.BaseTimeEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "participant") +@SQLDelete(sql = "UPDATE participant SET deleted_at = CURRENT_TIMESTAMP where id = ?") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Participant extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id") + private Room room; + + @Column(name = "member_id", updatable = false, nullable = false) + private Long memberId; + + @Column(name = "is_manager") + private boolean isManager; + + @Column(name = "certify_count") + private int certifyCount; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + + @Column(name = "deleted_room_title", length = 30) + private String deletedRoomTitle; + + @Builder + private Participant(Long id, Room room, Long memberId) { + this.id = id; + this.room = requireNonNull(room); + this.memberId = requireNonNull(memberId); + this.isManager = false; + this.certifyCount = 0; + } + + public void disableManager() { + this.isManager = false; + } + + public void enableManager() { + this.isManager = true; + } + + public void updateCertifyCount() { + this.certifyCount += 1; + } + + public void removeRoom() { + this.deletedRoomTitle = this.room.getTitle(); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/Room.java b/src/main/java/com/moabam/api/domain/room/Room.java new file mode 100644 index 00000000..6817cb4d --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/Room.java @@ -0,0 +1,196 @@ +package com.moabam.api.domain.room; + +import static com.moabam.api.domain.room.RoomType.*; +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import java.time.LocalDateTime; + +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.SQLDelete; + +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "room") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@SQLDelete(sql = "UPDATE room SET deleted_at = CURRENT_TIMESTAMP where id = ?") +public class Room extends BaseTimeEntity { + + private static final int LEVEL_0 = 0; + private static final int LEVEL_1 = 1; + private static final int LEVEL_2 = 2; + private static final int LEVEL_3 = 3; + private static final int LEVEL_4 = 4; + private static final int LEVEL_5 = 5; + private static final String ROOM_LEVEL_0_IMAGE = "https://image.moabam.com/moabam/default/room-level-00.png"; + private static final String ROOM_LEVEL_1_IMAGE = "https://image.moabam.com/moabam/default/room-level-01.png"; + private static final String ROOM_LEVEL_2_IMAGE = "https://image.moabam.com/moabam/default/room-level-02.png"; + private static final String ROOM_LEVEL_3_IMAGE = "https://image.moabam.com/moabam/default/room-level-03.png"; + private static final String ROOM_LEVEL_4_IMAGE = "https://image.moabam.com/moabam/default/room-level-04.png"; + private static final String ROOM_LEVEL_5_IMAGE = "https://image.moabam.com/moabam/default/room-level-05.png"; + private static final int MORNING_START_TIME = 4; + private static final int MORNING_END_TIME = 10; + private static final int NIGHT_START_TIME = 20; + private static final int NIGHT_END_TIME = 2; + private static final int CLOCK_ZERO = 0; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "title", nullable = false, length = 20) + private String title; + + @Column(name = "password", length = 8) + private String password; + + @ColumnDefault("0") + @Column(name = "level", nullable = false) + private int level; + + @ColumnDefault("0") + @Column(name = "exp", nullable = false) + private int exp; + + @Enumerated(value = EnumType.STRING) + @Column(name = "room_type") + private RoomType roomType; + + @Column(name = "certify_time", nullable = false) + private int certifyTime; + + @Column(name = "current_user_count", nullable = false) + private int currentUserCount; + + @Column(name = "max_user_count", nullable = false) + private int maxUserCount; + + @Column(name = "announcement", length = 100) + private String announcement; + + @ColumnDefault("'" + ROOM_LEVEL_0_IMAGE + "'") + @Column(name = "room_image", length = 500) + private String roomImage; + + @Column(name = "manager_nickname", length = 30) + private String managerNickname; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + + @Builder + private Room(Long id, String title, String password, RoomType roomType, int certifyTime, int maxUserCount) { + this.id = id; + this.title = requireNonNull(title); + this.password = password; + this.level = 0; + this.exp = 0; + this.roomType = requireNonNull(roomType); + this.certifyTime = validateCertifyTime(roomType, certifyTime); + this.currentUserCount = 1; + this.maxUserCount = maxUserCount; + this.roomImage = ROOM_LEVEL_0_IMAGE; + } + + public void levelUp() { + this.level += 1; + this.exp = 0; + upgradeRoomImage(this.level); + } + + public void upgradeRoomImage(int level) { + if (level == LEVEL_1) { + this.roomImage = ROOM_LEVEL_1_IMAGE; + return; + } + + if (level == LEVEL_2) { + this.roomImage = ROOM_LEVEL_2_IMAGE; + return; + } + + if (level == LEVEL_3) { + this.roomImage = ROOM_LEVEL_3_IMAGE; + return; + } + + if (level == LEVEL_4) { + this.roomImage = ROOM_LEVEL_4_IMAGE; + return; + } + + if (level == LEVEL_5) { + this.roomImage = ROOM_LEVEL_5_IMAGE; + } + } + + public void gainExp() { + this.exp += 1; + } + + public void changeAnnouncement(String announcement) { + this.announcement = announcement; + } + + public void changeTitle(String title) { + this.title = title; + } + + public void changePassword(String password) { + this.password = password; + } + + public void changeManagerNickname(String managerNickname) { + this.managerNickname = managerNickname; + } + + public void changeMaxCount(int maxUserCount) { + if (maxUserCount < this.currentUserCount) { + throw new BadRequestException(ROOM_MAX_USER_COUNT_MODIFY_FAIL); + } + + this.maxUserCount = maxUserCount; + } + + public void increaseCurrentUserCount() { + this.currentUserCount += 1; + } + + public void decreaseCurrentUserCount() { + this.currentUserCount -= 1; + } + + public void changeCertifyTime(int certifyTime) { + this.certifyTime = validateCertifyTime(this.roomType, certifyTime); + } + + private int validateCertifyTime(RoomType roomType, int certifyTime) { + if (roomType.equals(MORNING) && (certifyTime < MORNING_START_TIME || certifyTime > MORNING_END_TIME)) { + throw new BadRequestException(INVALID_REQUEST_FIELD); + } + + if (roomType.equals(NIGHT) + && ((certifyTime < NIGHT_START_TIME && certifyTime > NIGHT_END_TIME) || certifyTime < CLOCK_ZERO)) { + throw new BadRequestException(INVALID_REQUEST_FIELD); + } + + return certifyTime; + } +} diff --git a/src/main/java/com/moabam/api/domain/room/RoomExp.java b/src/main/java/com/moabam/api/domain/room/RoomExp.java new file mode 100644 index 00000000..409b91fb --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/RoomExp.java @@ -0,0 +1,43 @@ +package com.moabam.api.domain.room; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 방 경험치 + * 방 레벨 - 현재 경험치 / 전체 경험치 + * 레벨0 - 0 / 1 + * 레벨1 - 0 / 3 + * 레벨2 - 0 / 5 + * 레벨3 - 0 / 10 + */ + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum RoomExp { + + ROOM_LEVEL_0(0, 1), + ROOM_LEVEL_1(1, 5), + ROOM_LEVEL_2(2, 10), + ROOM_LEVEL_3(3, 20), + ROOM_LEVEL_4(4, 40), + ROOM_LEVEL_5(5, 80); + + private static final Map requireExpMap = Collections.unmodifiableMap( + Stream.of(values()) + .collect(Collectors.toMap(RoomExp::getLevel, RoomExp::name)) + ); + + private final int level; + private final int totalExp; + + public static RoomExp of(int level) { + return RoomExp.valueOf(requireExpMap.get(level)); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/RoomType.java b/src/main/java/com/moabam/api/domain/room/RoomType.java new file mode 100644 index 00000000..fd63618b --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/RoomType.java @@ -0,0 +1,7 @@ +package com.moabam.api.domain.room; + +public enum RoomType { + + MORNING, + NIGHT +} diff --git a/src/main/java/com/moabam/api/domain/room/Routine.java b/src/main/java/com/moabam/api/domain/room/Routine.java new file mode 100644 index 00000000..6b3f0a86 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/Routine.java @@ -0,0 +1,61 @@ +package com.moabam.api.domain.room; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static java.util.Objects.*; + +import org.apache.commons.lang3.StringUtils; + +import com.moabam.global.common.entity.BaseTimeEntity; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "routine") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Routine extends BaseTimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", updatable = false) + private Room room; + + @Column(name = "content", nullable = false, length = 20) + private String content; + + @Builder + private Routine(Long id, Room room, String content) { + this.id = id; + this.room = requireNonNull(room); + this.content = validateContent(content); + } + + public void changeContent(String content) { + this.content = content; + } + + private String validateContent(String content) { + if (StringUtils.isBlank(content) || content.length() > 20) { + throw new BadRequestException(ROUTINE_LENGTH_ERROR); + } + + return content; + } +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/CertificationRepository.java b/src/main/java/com/moabam/api/domain/room/repository/CertificationRepository.java new file mode 100644 index 00000000..c8389591 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/CertificationRepository.java @@ -0,0 +1,9 @@ +package com.moabam.api.domain.room.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.room.Certification; + +public interface CertificationRepository extends JpaRepository { + +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/CertificationsSearchRepository.java b/src/main/java/com/moabam/api/domain/room/repository/CertificationsSearchRepository.java new file mode 100644 index 00000000..8563245c --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/CertificationsSearchRepository.java @@ -0,0 +1,84 @@ +package com.moabam.api.domain.room.repository; + +import static com.moabam.api.domain.room.QCertification.*; +import static com.moabam.api.domain.room.QDailyMemberCertification.*; +import static com.moabam.api.domain.room.QDailyRoomCertification.*; +import static com.moabam.api.domain.room.QParticipant.*; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import jakarta.persistence.LockModeType; +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class CertificationsSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findCertifications(Long roomId, LocalDate date) { + return jpaQueryFactory.selectFrom(certification) + .where( + certification.routine.room.id.eq(roomId), + certification.createdAt.between(date.atStartOfDay(), date.atTime(LocalTime.MAX)) + ) + .fetch(); + } + + public Optional findDailyMemberCertification(Long memberId, Long roomId, LocalDate date) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(dailyMemberCertification) + .where( + dailyMemberCertification.memberId.eq(memberId), + dailyMemberCertification.roomId.eq(roomId), + dailyMemberCertification.createdAt.between(date.atStartOfDay(), date.atTime(LocalTime.MAX)) + ) + .fetchOne() + ); + } + + public List findSortedDailyMemberCertifications(Long roomId, LocalDate date) { + return jpaQueryFactory + .selectFrom(dailyMemberCertification) + .join(dailyMemberCertification.participant, participant).fetchJoin() + .where( + dailyMemberCertification.roomId.eq(roomId), + dailyMemberCertification.createdAt.between(date.atStartOfDay(), date.atTime(LocalTime.MAX)) + ) + .orderBy( + dailyMemberCertification.createdAt.asc() + ) + .fetch(); + } + + public Optional findDailyRoomCertification(Long roomId, LocalDate date) { + return Optional.ofNullable(jpaQueryFactory + .selectFrom(dailyRoomCertification) + .where( + dailyRoomCertification.roomId.eq(roomId), + dailyRoomCertification.certifiedAt.eq(date) + ) + .setLockMode(LockModeType.PESSIMISTIC_WRITE) + .fetchOne()); + } + + public List findDailyRoomCertifications(Long roomId, LocalDate today) { + return jpaQueryFactory + .selectFrom(dailyRoomCertification) + .where( + dailyRoomCertification.roomId.eq(roomId), + dailyRoomCertification.certifiedAt.between(today.minusWeeks(1), today) + ) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/DailyMemberCertificationRepository.java b/src/main/java/com/moabam/api/domain/room/repository/DailyMemberCertificationRepository.java new file mode 100644 index 00000000..ec267010 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/DailyMemberCertificationRepository.java @@ -0,0 +1,15 @@ +package com.moabam.api.domain.room.repository; + +import java.time.LocalDateTime; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.room.DailyMemberCertification; + +public interface DailyMemberCertificationRepository extends JpaRepository { + + boolean existsByMemberIdAndRoomIdAndCreatedAtBetween(Long memberId, Long roomId, LocalDateTime startTime, + LocalDateTime endTime); + + boolean existsByRoomIdAndCreatedAtBetween(Long roomId, LocalDateTime startTime, LocalDateTime endTime); +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/DailyRoomCertificationRepository.java b/src/main/java/com/moabam/api/domain/room/repository/DailyRoomCertificationRepository.java new file mode 100644 index 00000000..47194085 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/DailyRoomCertificationRepository.java @@ -0,0 +1,12 @@ +package com.moabam.api.domain.room.repository; + +import java.time.LocalDate; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.room.DailyRoomCertification; + +public interface DailyRoomCertificationRepository extends JpaRepository { + + boolean existsByRoomIdAndCertifiedAt(Long roomId, LocalDate date); +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/ParticipantRepository.java b/src/main/java/com/moabam/api/domain/room/repository/ParticipantRepository.java new file mode 100644 index 00000000..875e6a03 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/ParticipantRepository.java @@ -0,0 +1,12 @@ +package com.moabam.api.domain.room.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.room.Participant; + +public interface ParticipantRepository extends JpaRepository { + + List findAllByMemberId(Long id); +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/ParticipantSearchRepository.java b/src/main/java/com/moabam/api/domain/room/repository/ParticipantSearchRepository.java new file mode 100644 index 00000000..f391e4f0 --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/ParticipantSearchRepository.java @@ -0,0 +1,121 @@ +package com.moabam.api.domain.room.repository; + +import static com.moabam.api.domain.room.QParticipant.*; +import static com.moabam.api.domain.room.QRoom.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.room.Participant; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ParticipantSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public Optional findOne(Long memberId, Long roomId) { + return Optional.ofNullable( + jpaQueryFactory + .selectFrom(participant) + .join(participant.room, room).fetchJoin() + .where( + DynamicQuery.generateEq(roomId, participant.room.id::eq), + DynamicQuery.generateEq(memberId, participant.memberId::eq), + participant.deletedAt.isNull() + ) + .fetchOne() + ); + } + + public List findAllByRoomId(Long roomId) { + return jpaQueryFactory + .selectFrom(participant) + .where( + participant.room.id.eq(roomId), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findAllByMemberIdParticipant(Long memberId) { + return jpaQueryFactory + .selectFrom(participant) + .where( + participant.memberId.eq(memberId), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findAllWithDeletedByRoomId(Long roomId) { + return jpaQueryFactory + .selectFrom(participant) + .where( + participant.room.id.eq(roomId) + ) + .fetch(); + } + + public List findAllByRoomIdBeforeDate(Long roomId, LocalDateTime date) { + return jpaQueryFactory + .selectFrom(participant) + .where( + participant.room.id.eq(roomId), + participant.createdAt.before(date), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findNotDeletedAllByMemberId(Long memberId) { + return jpaQueryFactory + .selectFrom(participant) + .join(participant.room, room).fetchJoin() + .where( + participant.memberId.eq(memberId), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findAllByMemberId(Long memberId) { + return jpaQueryFactory + .selectFrom(participant) + .leftJoin(participant.room, room).fetchJoin() + .where( + participant.memberId.eq(memberId) + ) + .orderBy(participant.createdAt.desc()) + .fetch(); + } + + public List findAllByRoomCertifyTime(int certifyTime) { + return jpaQueryFactory + .selectFrom(participant) + .join(participant.room, room).fetchJoin() + .where( + participant.room.certifyTime.eq(certifyTime), + participant.deletedAt.isNull() + ) + .fetch(); + } + + public List findAllRoomMangerByMemberId(Long memberId) { + return jpaQueryFactory + .selectFrom(participant) + .join(participant.room, room).fetchJoin() + .where( + participant.memberId.eq(memberId), + participant.isManager.isTrue() + ) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoomRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoomRepository.java new file mode 100644 index 00000000..6994356c --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/RoomRepository.java @@ -0,0 +1,57 @@ +package com.moabam.api.domain.room.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import com.moabam.api.domain.room.Room; + +import jakarta.persistence.LockModeType; + +public interface RoomRepository extends JpaRepository { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + Optional findWithPessimisticLockByIdAndDeletedAtIsNull(Long id); + + @Query(value = "select distinct rm.* from room rm left join routine rt on rm.id = rt.room_id " + + "where (rm.title like %:keyword% " + + "or rm.manager_nickname like %:keyword% " + + "or rt.content like %:keyword%) " + + "and rm.deleted_at is null " + + "order by rm.id desc limit 11", nativeQuery = true) + List searchByKeyword(@Param(value = "keyword") String keyword); + + @Query(value = "select distinct rm.* from room rm left join routine rt on rm.id = rt.room_id " + + "where (rm.title like %:keyword% " + + "or rm.manager_nickname like %:keyword% " + + "or rt.content like %:keyword%) " + + "and rm.room_type = :roomType " + + "and rm.deleted_at is null " + + "order by rm.id desc limit 11", nativeQuery = true) + List searchByKeywordAndRoomType(@Param(value = "keyword") String keyword, + @Param(value = "roomType") String roomType); + + @Query(value = "select distinct rm.* from room rm left join routine rt on rm.id = rt.room_id " + + "where (rm.title like %:keyword% " + + "or rm.manager_nickname like %:keyword% " + + "or rt.content like %:keyword%) " + + "and rm.id < :roomId " + + "and rm.deleted_at is null " + + "order by rm.id desc limit 11", nativeQuery = true) + List searchByKeywordAndRoomId(@Param(value = "keyword") String keyword, @Param(value = "roomId") Long roomId); + + @Query(value = "select distinct rm.* from room rm left join routine rt on rm.id = rt.room_id " + + "where (rm.title like %:keyword% " + + "or rm.manager_nickname like %:keyword% " + + "or rt.content like %:keyword%) " + + "and rm.room_type = :roomType " + + "and rm.id < :roomId " + + "and rm.deleted_at is null " + + "order by rm.id desc limit 11", nativeQuery = true) + List searchByKeywordAndRoomIdAndRoomType(@Param(value = "keyword") String keyword, + @Param(value = "roomType") String roomType, @Param(value = "roomId") Long roomId); +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java new file mode 100644 index 00000000..a815760e --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/RoomSearchRepository.java @@ -0,0 +1,34 @@ +package com.moabam.api.domain.room.repository; + +import static com.moabam.api.domain.room.QRoom.*; +import static com.moabam.global.common.util.GlobalConstant.*; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.global.common.util.DynamicQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class RoomSearchRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findAllWithNoOffset(RoomType roomType, Long roomId) { + return jpaQueryFactory.selectFrom(room) + .where( + DynamicQuery.generateEq(roomType, room.roomType::eq), + DynamicQuery.generateEq(roomId, room.id::lt), + room.deletedAt.isNull() + ) + .orderBy(room.id.desc()) + .limit(ROOM_FIXED_SEARCH_SIZE + 1L) + .fetch(); + } +} diff --git a/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java b/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java new file mode 100644 index 00000000..add3c3be --- /dev/null +++ b/src/main/java/com/moabam/api/domain/room/repository/RoutineRepository.java @@ -0,0 +1,14 @@ +package com.moabam.api.domain.room.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.moabam.api.domain.room.Routine; + +public interface RoutineRepository extends JpaRepository { + + List findAllByRoomId(Long roomId); + + List findAllByRoomIdIn(List roomIds); +} diff --git a/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeRequest.java b/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeRequest.java new file mode 100644 index 00000000..cadd3452 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeRequest.java @@ -0,0 +1,26 @@ +package com.moabam.api.dto.auth; + +import static java.util.Objects.*; + +import java.util.List; + +import lombok.Builder; + +public record AuthorizationCodeRequest( + String clientId, + String redirectUri, + String responseType, + List scope, + String state +) { + + @Builder + public AuthorizationCodeRequest(String clientId, String redirectUri, String responseType, List scope, + String state) { + this.clientId = requireNonNull(clientId); + this.redirectUri = requireNonNull(redirectUri); + this.responseType = responseType; + this.scope = scope; + this.state = state; + } +} diff --git a/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeResponse.java b/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeResponse.java new file mode 100644 index 00000000..5b652696 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/AuthorizationCodeResponse.java @@ -0,0 +1,10 @@ +package com.moabam.api.dto.auth; + +public record AuthorizationCodeResponse( + String code, + String error, + String errorDescription, + String state +) { + +} diff --git a/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenInfoResponse.java b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenInfoResponse.java new file mode 100644 index 00000000..8f7c5ff6 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenInfoResponse.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.auth; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record AuthorizationTokenInfoResponse( + @JsonProperty("id") long id, + @JsonProperty("expires_in") String expiresIn, + @JsonProperty("app_id") String appId +) { + +} diff --git a/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenRequest.java b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenRequest.java new file mode 100644 index 00000000..6254c13e --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenRequest.java @@ -0,0 +1,24 @@ +package com.moabam.api.dto.auth; + +import static java.util.Objects.*; + +import lombok.Builder; + +public record AuthorizationTokenRequest( + String grantType, + String clientId, + String redirectUri, + String code, + String clientSecret +) { + + @Builder + public AuthorizationTokenRequest(String grantType, String clientId, String redirectUri, String code, + String clientSecret) { + this.grantType = requireNonNull(grantType); + this.clientId = requireNonNull(clientId); + this.redirectUri = requireNonNull(redirectUri); + this.code = requireNonNull(code); + this.clientSecret = clientSecret; + } +} diff --git a/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenResponse.java b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenResponse.java new file mode 100644 index 00000000..04609d09 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/AuthorizationTokenResponse.java @@ -0,0 +1,15 @@ +package com.moabam.api.dto.auth; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record AuthorizationTokenResponse( + @JsonProperty("token_type") String tokenType, + @JsonProperty("access_token") String accessToken, + @JsonProperty("id_token") String idToken, + @JsonProperty("expires_in") String expiresIn, + @JsonProperty("refresh_token") String refreshToken, + @JsonProperty("refresh_token_expires_in") String refreshTokenExpiresIn, + @JsonProperty("scope") String scope +) { + +} diff --git a/src/main/java/com/moabam/api/dto/auth/LoginResponse.java b/src/main/java/com/moabam/api/dto/auth/LoginResponse.java new file mode 100644 index 00000000..8c75e8b4 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/LoginResponse.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.auth; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.moabam.global.auth.model.PublicClaim; + +import lombok.Builder; + +@Builder +public record LoginResponse( + boolean isSignUp, + @JsonUnwrapped PublicClaim publicClaim +) { + +} diff --git a/src/main/java/com/moabam/api/dto/auth/TokenSaveValue.java b/src/main/java/com/moabam/api/dto/auth/TokenSaveValue.java new file mode 100644 index 00000000..5ac16a6c --- /dev/null +++ b/src/main/java/com/moabam/api/dto/auth/TokenSaveValue.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.auth; + +import lombok.Builder; + +@Builder +public record TokenSaveValue( + String refreshToken, + String loginIp +) { + +} diff --git a/src/main/java/com/moabam/api/dto/bug/BugHistoryItemResponse.java b/src/main/java/com/moabam/api/dto/bug/BugHistoryItemResponse.java new file mode 100644 index 00000000..8d2703ac --- /dev/null +++ b/src/main/java/com/moabam/api/dto/bug/BugHistoryItemResponse.java @@ -0,0 +1,22 @@ +package com.moabam.api.dto.bug; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.dto.payment.PaymentResponse; + +import lombok.Builder; + +@Builder +public record BugHistoryItemResponse( + Long id, + BugType bugType, + BugActionType actionType, + int quantity, + String date, + @JsonInclude(NON_NULL) PaymentResponse payment +) { + +} diff --git a/src/main/java/com/moabam/api/dto/bug/BugHistoryResponse.java b/src/main/java/com/moabam/api/dto/bug/BugHistoryResponse.java new file mode 100644 index 00000000..efbf3df3 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/bug/BugHistoryResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.bug; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record BugHistoryResponse( + List history +) { + +} diff --git a/src/main/java/com/moabam/api/dto/bug/BugHistoryWithPayment.java b/src/main/java/com/moabam/api/dto/bug/BugHistoryWithPayment.java new file mode 100644 index 00000000..30b2ef78 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/bug/BugHistoryWithPayment.java @@ -0,0 +1,21 @@ +package com.moabam.api.dto.bug; + +import java.time.LocalDateTime; + +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.payment.Payment; + +import lombok.Builder; + +@Builder +public record BugHistoryWithPayment( + Long id, + BugType bugType, + BugActionType actionType, + int quantity, + LocalDateTime createdAt, + Payment payment +) { + +} diff --git a/src/main/java/com/moabam/api/dto/bug/BugResponse.java b/src/main/java/com/moabam/api/dto/bug/BugResponse.java new file mode 100644 index 00000000..9493a76c --- /dev/null +++ b/src/main/java/com/moabam/api/dto/bug/BugResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.bug; + +import lombok.Builder; + +@Builder +public record BugResponse( + int morningBug, + int nightBug, + int goldenBug +) { + +} diff --git a/src/main/java/com/moabam/api/dto/coupon/CouponResponse.java b/src/main/java/com/moabam/api/dto/coupon/CouponResponse.java new file mode 100644 index 00000000..d709490e --- /dev/null +++ b/src/main/java/com/moabam/api/dto/coupon/CouponResponse.java @@ -0,0 +1,25 @@ +package com.moabam.api.dto.coupon; + +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.moabam.api.domain.coupon.CouponType; + +import lombok.Builder; + +@Builder +public record CouponResponse( + Long id, + Long adminId, + String name, + String description, + int point, + int maxCount, + CouponType type, + @JsonFormat(pattern = "yyyy-MM-dd") + LocalDate startAt, + @JsonFormat(pattern = "yyyy-MM-dd") + LocalDate openAt +) { + +} diff --git a/src/main/java/com/moabam/api/dto/coupon/CouponStatusRequest.java b/src/main/java/com/moabam/api/dto/coupon/CouponStatusRequest.java new file mode 100644 index 00000000..0cecaea2 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/coupon/CouponStatusRequest.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.coupon; + +import lombok.Builder; + +@Builder +public record CouponStatusRequest( + boolean opened, + boolean ended +) { + +} diff --git a/src/main/java/com/moabam/api/dto/coupon/CreateCouponRequest.java b/src/main/java/com/moabam/api/dto/coupon/CreateCouponRequest.java new file mode 100644 index 00000000..e3edcdfb --- /dev/null +++ b/src/main/java/com/moabam/api/dto/coupon/CreateCouponRequest.java @@ -0,0 +1,27 @@ +package com.moabam.api.dto.coupon; + +import java.time.LocalDate; + +import org.hibernate.validator.constraints.Length; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record CreateCouponRequest( + @NotBlank(message = "쿠폰명이 입력되지 않았거나 20자를 넘었습니다.") @Length(max = 20) String name, + @Length(max = 50, message = "쿠폰 간단 소개는 최대 50자까지 가능합니다.") String description, + @NotBlank(message = "쿠폰 종류를 입력해주세요.") String type, + @Min(value = 1, message = "벌레 수 혹은 할인 금액은 1 이상이어야 합니다.") int point, + @Min(value = 1, message = "쿠폰 최대 갯수는 1 이상이어야 합니다.") int maxCount, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + @NotNull(message = "쿠폰 발급이 가능한 날짜(년, 월, 일)를 입력해주세요.") LocalDate startAt, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + @NotNull(message = "쿠폰 정보창이 열리는 날짜(년, 월, 일)를 입력해주세요.") LocalDate openAt +) { + +} diff --git a/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java b/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java new file mode 100644 index 00000000..60859b7a --- /dev/null +++ b/src/main/java/com/moabam/api/dto/coupon/MyCouponResponse.java @@ -0,0 +1,17 @@ +package com.moabam.api.dto.coupon; + +import com.moabam.api.domain.coupon.CouponType; + +import lombok.Builder; + +@Builder +public record MyCouponResponse( + Long walletId, + Long id, + String name, + String description, + int point, + CouponType type +) { + +} diff --git a/src/main/java/com/moabam/api/dto/item/ItemResponse.java b/src/main/java/com/moabam/api/dto/item/ItemResponse.java new file mode 100644 index 00000000..1ae4ae79 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/item/ItemResponse.java @@ -0,0 +1,17 @@ +package com.moabam.api.dto.item; + +import lombok.Builder; + +@Builder +public record ItemResponse( + Long id, + String type, + String category, + String name, + String image, + int level, + int bugPrice, + int goldenBugPrice +) { + +} diff --git a/src/main/java/com/moabam/api/dto/item/ItemsResponse.java b/src/main/java/com/moabam/api/dto/item/ItemsResponse.java new file mode 100644 index 00000000..70de47ec --- /dev/null +++ b/src/main/java/com/moabam/api/dto/item/ItemsResponse.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.item; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record ItemsResponse( + Long defaultItemId, + List purchasedItems, + List notPurchasedItems +) { + +} diff --git a/src/main/java/com/moabam/api/dto/item/PurchaseItemRequest.java b/src/main/java/com/moabam/api/dto/item/PurchaseItemRequest.java new file mode 100644 index 00000000..0df65e1f --- /dev/null +++ b/src/main/java/com/moabam/api/dto/item/PurchaseItemRequest.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.item; + +import com.moabam.api.domain.bug.BugType; + +import jakarta.validation.constraints.NotNull; + +public record PurchaseItemRequest( + @NotNull BugType bugType +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/BadgeResponse.java b/src/main/java/com/moabam/api/dto/member/BadgeResponse.java new file mode 100644 index 00000000..a00158ba --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/BadgeResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.member; + +import com.moabam.api.domain.member.BadgeType; + +import lombok.Builder; + +@Builder +public record BadgeResponse( + String badge, + boolean unlock +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/DeleteMemberResponse.java b/src/main/java/com/moabam/api/dto/member/DeleteMemberResponse.java new file mode 100644 index 00000000..04b14d26 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/DeleteMemberResponse.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.member; + +import lombok.Builder; + +@Builder +public record DeleteMemberResponse( + String socialId, + Long id +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfo.java b/src/main/java/com/moabam/api/dto/member/MemberInfo.java new file mode 100644 index 00000000..56ed7c77 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfo.java @@ -0,0 +1,23 @@ +package com.moabam.api.dto.member; + +import com.moabam.api.domain.member.BadgeType; + +public record MemberInfo( + String nickname, + String profileImage, + String morningImage, + String nightImage, + String intro, + long totalCertifyCount, + BadgeType badges, + Integer goldenBug, + Integer morningBug, + Integer nightBug +) { + + public MemberInfo(String nickname, String profileImage, String morningImage, String nightImage, + String intro, long totalCertifyCount, BadgeType badges) { + this(nickname, profileImage, morningImage, nightImage, intro, + totalCertifyCount, badges, null, null, null); + } +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java b/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java new file mode 100644 index 00000000..c7f37b0c --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfoResponse.java @@ -0,0 +1,26 @@ +package com.moabam.api.dto.member; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Builder; + +@Builder +public record MemberInfoResponse( + String nickname, + String profileImage, + String intro, + long level, + long exp, + Map birds, + List badges, + @JsonInclude(NON_NULL) Integer goldenBug, + @JsonInclude(NON_NULL) Integer morningBug, + @JsonInclude(NON_NULL) Integer nightBug +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java b/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java new file mode 100644 index 00000000..1391e383 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/MemberInfoSearchResponse.java @@ -0,0 +1,23 @@ +package com.moabam.api.dto.member; + +import java.util.Set; + +import com.moabam.api.domain.member.BadgeType; + +import lombok.Builder; + +@Builder +public record MemberInfoSearchResponse( + String nickname, + String profileImage, + String morningImage, + String nightImage, + String intro, + long totalCertifyCount, + Set badges, + Integer goldenBug, + Integer morningBug, + Integer nightBug +) { + +} diff --git a/src/main/java/com/moabam/api/dto/member/ModifyMemberRequest.java b/src/main/java/com/moabam/api/dto/member/ModifyMemberRequest.java new file mode 100644 index 00000000..43c9ef31 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/member/ModifyMemberRequest.java @@ -0,0 +1,8 @@ +package com.moabam.api.dto.member; + +public record ModifyMemberRequest( + String intro, + String nickname +) { + +} diff --git a/src/main/java/com/moabam/api/dto/payment/ConfirmPaymentRequest.java b/src/main/java/com/moabam/api/dto/payment/ConfirmPaymentRequest.java new file mode 100644 index 00000000..ee5a4d03 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/payment/ConfirmPaymentRequest.java @@ -0,0 +1,15 @@ +package com.moabam.api.dto.payment; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record ConfirmPaymentRequest( + @NotBlank String paymentKey, + @NotBlank String orderId, + @NotNull @Min(0) int amount +) { + +} diff --git a/src/main/java/com/moabam/api/dto/payment/ConfirmTossPaymentResponse.java b/src/main/java/com/moabam/api/dto/payment/ConfirmTossPaymentResponse.java new file mode 100644 index 00000000..34b5bdb7 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/payment/ConfirmTossPaymentResponse.java @@ -0,0 +1,16 @@ +package com.moabam.api.dto.payment; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import lombok.Builder; + +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public record ConfirmTossPaymentResponse( + String paymentKey, + String orderId, + String orderName, + int totalAmount +) { + +} diff --git a/src/main/java/com/moabam/api/dto/payment/PaymentRequest.java b/src/main/java/com/moabam/api/dto/payment/PaymentRequest.java new file mode 100644 index 00000000..292492a3 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/payment/PaymentRequest.java @@ -0,0 +1,9 @@ +package com.moabam.api.dto.payment; + +import jakarta.validation.constraints.NotBlank; + +public record PaymentRequest( + @NotBlank String orderId +) { + +} diff --git a/src/main/java/com/moabam/api/dto/payment/PaymentResponse.java b/src/main/java/com/moabam/api/dto/payment/PaymentResponse.java new file mode 100644 index 00000000..29e30af5 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/payment/PaymentResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.payment; + +import lombok.Builder; + +@Builder +public record PaymentResponse( + Long id, + String orderName, + int discountAmount, + int totalAmount +) { + +} diff --git a/src/main/java/com/moabam/api/dto/payment/RequestConfirmPaymentResponse.java b/src/main/java/com/moabam/api/dto/payment/RequestConfirmPaymentResponse.java new file mode 100644 index 00000000..6c3b69ac --- /dev/null +++ b/src/main/java/com/moabam/api/dto/payment/RequestConfirmPaymentResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.payment; + +import com.moabam.api.domain.payment.Payment; + +import lombok.Builder; + +@Builder +public record RequestConfirmPaymentResponse( + Payment payment, + String paymentKey +) { + +} diff --git a/src/main/java/com/moabam/api/dto/product/ProductResponse.java b/src/main/java/com/moabam/api/dto/product/ProductResponse.java new file mode 100644 index 00000000..bd18b595 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/product/ProductResponse.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.product; + +import lombok.Builder; + +@Builder +public record ProductResponse( + Long id, + String type, + String name, + int price, + int quantity +) { + +} diff --git a/src/main/java/com/moabam/api/dto/product/ProductsResponse.java b/src/main/java/com/moabam/api/dto/product/ProductsResponse.java new file mode 100644 index 00000000..21b99059 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/product/ProductsResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.product; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record ProductsResponse( + List products +) { + +} diff --git a/src/main/java/com/moabam/api/dto/product/PurchaseProductRequest.java b/src/main/java/com/moabam/api/dto/product/PurchaseProductRequest.java new file mode 100644 index 00000000..6e7eda86 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/product/PurchaseProductRequest.java @@ -0,0 +1,9 @@ +package com.moabam.api.dto.product; + +import jakarta.annotation.Nullable; + +public record PurchaseProductRequest( + @Nullable Long couponWalletId +) { + +} diff --git a/src/main/java/com/moabam/api/dto/product/PurchaseProductResponse.java b/src/main/java/com/moabam/api/dto/product/PurchaseProductResponse.java new file mode 100644 index 00000000..8d74cee1 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/product/PurchaseProductResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.product; + +import lombok.Builder; + +@Builder +public record PurchaseProductResponse( + Long paymentId, + String orderName, + int price +) { + +} diff --git a/src/main/java/com/moabam/api/dto/ranking/RankingInfo.java b/src/main/java/com/moabam/api/dto/ranking/RankingInfo.java new file mode 100644 index 00000000..46645389 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/ranking/RankingInfo.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.ranking; + +import lombok.Builder; + +@Builder +public record RankingInfo( + Long memberId, + String nickname, + String image +) { + +} diff --git a/src/main/java/com/moabam/api/dto/ranking/TopRankingInfo.java b/src/main/java/com/moabam/api/dto/ranking/TopRankingInfo.java new file mode 100644 index 00000000..bcd56ff2 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/ranking/TopRankingInfo.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.ranking; + +import lombok.Builder; + +@Builder +public record TopRankingInfo( + int rank, + Long memberId, + Long score, + String nickname, + String image +) { + +} diff --git a/src/main/java/com/moabam/api/dto/ranking/TopRankingResponse.java b/src/main/java/com/moabam/api/dto/ranking/TopRankingResponse.java new file mode 100644 index 00000000..38663842 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/ranking/TopRankingResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.ranking; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record TopRankingResponse( + List topRankings, + TopRankingInfo myRanking +) { + +} diff --git a/src/main/java/com/moabam/api/dto/ranking/UpdateRanking.java b/src/main/java/com/moabam/api/dto/ranking/UpdateRanking.java new file mode 100644 index 00000000..6b5218ee --- /dev/null +++ b/src/main/java/com/moabam/api/dto/ranking/UpdateRanking.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.ranking; + +import lombok.Builder; + +@Builder +public record UpdateRanking( + RankingInfo rankingInfo, + Long score +) { + +} diff --git a/src/main/java/com/moabam/api/dto/report/ReportRequest.java b/src/main/java/com/moabam/api/dto/report/ReportRequest.java new file mode 100644 index 00000000..fdccf2e3 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/report/ReportRequest.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.report; + +import jakarta.validation.constraints.NotNull; + +public record ReportRequest( + @NotNull Long reportedId, + Long roomId, + Long certificationId, + String description +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/CertificationImageResponse.java b/src/main/java/com/moabam/api/dto/room/CertificationImageResponse.java new file mode 100644 index 00000000..110d6798 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertificationImageResponse.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.room; + +import lombok.Builder; + +@Builder +public record CertificationImageResponse( + Long routineId, + String image +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java b/src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java new file mode 100644 index 00000000..7de93159 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertificationImagesResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record CertificationImagesResponse( + List images +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/CertifiedMemberInfo.java b/src/main/java/com/moabam/api/dto/room/CertifiedMemberInfo.java new file mode 100644 index 00000000..08159788 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertifiedMemberInfo.java @@ -0,0 +1,19 @@ +package com.moabam.api.dto.room; + +import java.time.LocalDate; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Room; + +import lombok.Builder; + +@Builder +public record CertifiedMemberInfo( + LocalDate date, + BugType bugType, + Room room, + Member member +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/CertifyRoomRequest.java b/src/main/java/com/moabam/api/dto/room/CertifyRoomRequest.java new file mode 100644 index 00000000..594942e4 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertifyRoomRequest.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.room; + +import org.springframework.web.multipart.MultipartFile; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CertifyRoomRequest { + + private Long routineId; + private MultipartFile image; +} diff --git a/src/main/java/com/moabam/api/dto/room/CertifyRoomsRequest.java b/src/main/java/com/moabam/api/dto/room/CertifyRoomsRequest.java new file mode 100644 index 00000000..c2cf4110 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CertifyRoomsRequest.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CertifyRoomsRequest { + + private List certifyRoomsRequest; +} diff --git a/src/main/java/com/moabam/api/dto/room/CreateRoomRequest.java b/src/main/java/com/moabam/api/dto/room/CreateRoomRequest.java new file mode 100644 index 00000000..a6eeffeb --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/CreateRoomRequest.java @@ -0,0 +1,24 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; + +import com.moabam.api.domain.room.RoomType; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public record CreateRoomRequest( + @NotBlank @Length(max = 20) String title, + @Pattern(regexp = "^(|[0-9]{4,8})$") String password, + @NotNull @Size(min = 1, max = 4) List routines, + @NotNull RoomType roomType, + @Range(min = 0, max = 23) int certifyTime, + @Range(min = 0, max = 10) int maxUserCount +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/EnterRoomRequest.java b/src/main/java/com/moabam/api/dto/room/EnterRoomRequest.java new file mode 100644 index 00000000..fc3d511b --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/EnterRoomRequest.java @@ -0,0 +1,9 @@ +package com.moabam.api.dto.room; + +import jakarta.validation.constraints.Pattern; + +public record EnterRoomRequest( + @Pattern(regexp = "^(|[0-9]{4,8})$") String password +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java b/src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java new file mode 100644 index 00000000..bff4f0ba --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/GetAllRoomResponse.java @@ -0,0 +1,24 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import com.moabam.api.domain.room.RoomType; + +import lombok.Builder; + +@Builder +public record GetAllRoomResponse( + Long id, + String title, + String image, + boolean isPassword, + String managerNickname, + int level, + RoomType roomType, + int certifyTime, + int currentUserCount, + int maxUserCount, + List routines +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java b/src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java new file mode 100644 index 00000000..bb648e72 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/GetAllRoomsResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record GetAllRoomsResponse( + boolean hasNext, + List rooms +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/ManageRoomResponse.java b/src/main/java/com/moabam/api/dto/room/ManageRoomResponse.java new file mode 100644 index 00000000..cbf9c261 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/ManageRoomResponse.java @@ -0,0 +1,23 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import com.moabam.api.domain.room.RoomType; + +import lombok.Builder; + +@Builder +public record ManageRoomResponse( + Long roomId, + String title, + Long managerId, + String announcement, + RoomType roomType, + int certifyTime, + int maxUserCount, + String password, + List routines, + List participants +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/ModifyRoomRequest.java b/src/main/java/com/moabam/api/dto/room/ModifyRoomRequest.java new file mode 100644 index 00000000..28d5cdb1 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/ModifyRoomRequest.java @@ -0,0 +1,17 @@ +package com.moabam.api.dto.room; + +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.Range; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public record ModifyRoomRequest( + @NotBlank @Length(max = 20) String title, + @Length(max = 100, message = "방 공지의 길이 100자 이하여야 합니다.") String announcement, + @Pattern(regexp = "^(|\\d{4,8})$") String password, + @Range(min = 0, max = 23) int certifyTime, + @Range(min = 0, max = 10) int maxUserCount +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/MyRoomResponse.java b/src/main/java/com/moabam/api/dto/room/MyRoomResponse.java new file mode 100644 index 00000000..c3480d63 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/MyRoomResponse.java @@ -0,0 +1,20 @@ +package com.moabam.api.dto.room; + +import com.moabam.api.domain.room.RoomType; + +import lombok.Builder; + +@Builder +public record MyRoomResponse( + Long roomId, + String title, + RoomType roomType, + int certifyTime, + int currentUserCount, + int maxUserCount, + int obtainedBugs, + boolean isMemberCertifiedToday, + boolean isRoomCertifiedToday +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/MyRoomsResponse.java b/src/main/java/com/moabam/api/dto/room/MyRoomsResponse.java new file mode 100644 index 00000000..8f4a8d14 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/MyRoomsResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record MyRoomsResponse( + List participatingRooms +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/ParticipantResponse.java b/src/main/java/com/moabam/api/dto/room/ParticipantResponse.java new file mode 100644 index 00000000..be3a9bbe --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/ParticipantResponse.java @@ -0,0 +1,13 @@ +package com.moabam.api.dto.room; + +import lombok.Builder; + +@Builder +public record ParticipantResponse( + Long memberId, + String nickname, + int contributionPoint, + String profileImage +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/RoomDetailsResponse.java b/src/main/java/com/moabam/api/dto/room/RoomDetailsResponse.java new file mode 100644 index 00000000..466ceee6 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/RoomDetailsResponse.java @@ -0,0 +1,33 @@ +package com.moabam.api.dto.room; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import com.moabam.api.domain.room.RoomType; + +import lombok.Builder; + +@Builder +public record RoomDetailsResponse( + Long roomId, + LocalDateTime roomCreatedAt, + Long myMemberId, + String title, + String managerNickName, + String roomImage, + int level, + int currentExp, + int totalExp, + RoomType roomType, + int certifyTime, + int currentUserCount, + int maxUserCount, + String announcement, + double completePercentage, + List certifiedDates, + List routines, + List todayCertificateRank +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/RoomHistoryResponse.java b/src/main/java/com/moabam/api/dto/room/RoomHistoryResponse.java new file mode 100644 index 00000000..44763cfd --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/RoomHistoryResponse.java @@ -0,0 +1,15 @@ +package com.moabam.api.dto.room; + +import java.time.LocalDateTime; + +import lombok.Builder; + +@Builder +public record RoomHistoryResponse( + Long roomId, + String title, + LocalDateTime createdAt, + LocalDateTime deletedAt +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/RoomsHistoryResponse.java b/src/main/java/com/moabam/api/dto/room/RoomsHistoryResponse.java new file mode 100644 index 00000000..5ce5e40d --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/RoomsHistoryResponse.java @@ -0,0 +1,12 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import lombok.Builder; + +@Builder +public record RoomsHistoryResponse( + List roomHistory +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/RoutineResponse.java b/src/main/java/com/moabam/api/dto/room/RoutineResponse.java new file mode 100644 index 00000000..37ae2c16 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/RoutineResponse.java @@ -0,0 +1,11 @@ +package com.moabam.api.dto.room; + +import lombok.Builder; + +@Builder +public record RoutineResponse( + Long routineId, + String content +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java b/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java new file mode 100644 index 00000000..c35829d4 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/TodayCertificateRankResponse.java @@ -0,0 +1,18 @@ +package com.moabam.api.dto.room; + +import lombok.Builder; + +@Builder +public record TodayCertificateRankResponse( + int rank, + Long memberId, + String nickname, + boolean isNotificationSent, + String profileImage, + int contributionPoint, + String awakeImage, + String sleepImage, + CertificationImagesResponse certificationImage +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/UnJoinedRoomCertificateRankResponse.java b/src/main/java/com/moabam/api/dto/room/UnJoinedRoomCertificateRankResponse.java new file mode 100644 index 00000000..0dfe7a21 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/UnJoinedRoomCertificateRankResponse.java @@ -0,0 +1,14 @@ +package com.moabam.api.dto.room; + +import lombok.Builder; + +@Builder +public record UnJoinedRoomCertificateRankResponse( + int rank, + Long memberId, + String nickname, + String awakeImage, + String sleepImage +) { + +} diff --git a/src/main/java/com/moabam/api/dto/room/UnJoinedRoomDetailsResponse.java b/src/main/java/com/moabam/api/dto/room/UnJoinedRoomDetailsResponse.java new file mode 100644 index 00000000..3152a163 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/room/UnJoinedRoomDetailsResponse.java @@ -0,0 +1,27 @@ +package com.moabam.api.dto.room; + +import java.util.List; + +import com.moabam.api.domain.room.RoomType; + +import lombok.Builder; + +@Builder +public record UnJoinedRoomDetailsResponse( + Long roomId, + boolean isPassword, + String title, + String roomImage, + int level, + int currentExp, + int totalExp, + RoomType roomType, + int certifyTime, + int currentUserCount, + int maxUserCount, + String announcement, + List routines, + List certifiedRanks +) { + +} diff --git a/src/main/java/com/moabam/api/infrastructure/fcm/FcmMapper.java b/src/main/java/com/moabam/api/infrastructure/fcm/FcmMapper.java new file mode 100644 index 00000000..6b6a33a6 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/fcm/FcmMapper.java @@ -0,0 +1,25 @@ +package com.moabam.api.infrastructure.fcm; + +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FcmMapper { + + public static Notification toNotification(String title, String body) { + return Notification.builder() + .setTitle(title) + .setBody(body) + .build(); + } + + public static Message toMessage(Notification notification, String fcmToken) { + return Message.builder() + .setNotification(notification) + .setToken(fcmToken) + .build(); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java b/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java new file mode 100644 index 00000000..2d57c6b5 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/fcm/FcmRepository.java @@ -0,0 +1,37 @@ +package com.moabam.api.infrastructure.fcm; + +import static java.util.Objects.*; + +import java.time.Duration; + +import org.springframework.stereotype.Repository; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class FcmRepository { + + private static final long EXPIRE_FCM_TOKEN = 60; + + private final ValueRedisRepository valueRedisRepository; + + public void saveToken(String fcmToken, Long memberId) { + String tokenKey = String.valueOf(requireNonNull(memberId)); + + valueRedisRepository.save( + tokenKey, + requireNonNull(fcmToken), + Duration.ofDays(EXPIRE_FCM_TOKEN)); + } + + public void deleteTokenByMemberId(Long memberId) { + valueRedisRepository.delete(String.valueOf(requireNonNull(memberId))); + } + + public String findTokenByMemberId(Long memberId) { + return valueRedisRepository.get(String.valueOf(requireNonNull(memberId))); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/fcm/FcmService.java b/src/main/java/com/moabam/api/infrastructure/fcm/FcmService.java new file mode 100644 index 00000000..6b9fa73b --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/fcm/FcmService.java @@ -0,0 +1,44 @@ +package com.moabam.api.infrastructure.fcm; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FcmService { + + private final FirebaseMessaging firebaseMessaging; + private final FcmRepository fcmRepository; + + public void createToken(String fcmToken, Long memberId) { + if (fcmToken == null || fcmToken.isBlank()) { + return; + } + + fcmRepository.saveToken(fcmToken, memberId); + } + + public void deleteTokenByMemberId(Long memberId) { + fcmRepository.deleteTokenByMemberId(memberId); + } + + public Optional findTokenByMemberId(Long targetId) { + return Optional.ofNullable(fcmRepository.findTokenByMemberId(targetId)); + } + + public void sendAsync(String fcmToken, String notificationTitle, String notificationBody) { + Notification notification = FcmMapper.toNotification(notificationTitle, notificationBody); + + if (fcmToken != null) { + Message message = FcmMapper.toMessage(notification, fcmToken); + firebaseMessaging.sendAsync(message); + } + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/payment/TossPaymentService.java b/src/main/java/com/moabam/api/infrastructure/payment/TossPaymentService.java new file mode 100644 index 00000000..35db1492 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/payment/TossPaymentService.java @@ -0,0 +1,55 @@ +package com.moabam.api.infrastructure.payment; + +import java.util.Base64; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.ConfirmTossPaymentResponse; +import com.moabam.global.config.TossPaymentConfig; +import com.moabam.global.error.exception.TossPaymentException; +import com.moabam.global.error.model.ErrorResponse; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; + +@Service +@Slf4j +@RequiredArgsConstructor +public class TossPaymentService { + + private final TossPaymentConfig config; + private WebClient webClient; + + @PostConstruct + public void init() { + this.webClient = WebClient.builder() + .baseUrl(config.baseUrl()) + .defaultHeaders(headers -> { + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + headers.setBasicAuth(Base64.getEncoder().encodeToString(config.secretKey().getBytes())); + }) + .build(); + } + + public ConfirmTossPaymentResponse confirm(ConfirmPaymentRequest request) { + return webClient.post() + .uri("/v1/payments/confirm") + .body(BodyInserters.fromValue(request)) + .retrieve() + .onStatus(HttpStatusCode::isError, response -> response.bodyToMono(ErrorResponse.class) + .flatMap(error -> { + log.error("======= toss-payment confirmation error =======\n{}", error); + return Mono.error(new TossPaymentException(error.message())); + })) + .bodyToMono(ConfirmTossPaymentResponse.class) + .block(); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/redis/HashRedisRepository.java b/src/main/java/com/moabam/api/infrastructure/redis/HashRedisRepository.java new file mode 100644 index 00000000..0af79bc2 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/redis/HashRedisRepository.java @@ -0,0 +1,47 @@ +package com.moabam.api.infrastructure.redis; + +import java.time.Duration; +import java.util.Date; +import java.util.Map; + +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.hash.Jackson2HashMapper; +import org.springframework.stereotype.Repository; + +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +@Repository +public class HashRedisRepository { + + private final RedisTemplate redisTemplate; + private final HashOperations hashOperations; + private final Jackson2HashMapper hashMapper; + + public HashRedisRepository(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + hashOperations = redisTemplate.opsForHash(); + hashMapper = new Jackson2HashMapper(false); + } + + // redisTemplate.opsForHash().putAll(key, hashMapper.toHash(value)); + public void save(String key, Object value, Duration timeout) { + hashOperations.putAll(key, hashMapper.toHash(value)); + redisTemplate.expire(key, timeout); + } + + public void delete(String key) { + redisTemplate.expireAt(key, new Date()); + } + + public Object get(String key) { + Map memberToken = hashOperations.entries(key); + + if (memberToken.isEmpty()) { + throw new UnauthorizedException(ErrorMessage.AUTHENTICATE_FAIL); + } + + return hashMapper.fromHash(memberToken); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java b/src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java new file mode 100644 index 00000000..8c879bcf --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/redis/ValueRedisRepository.java @@ -0,0 +1,41 @@ +package com.moabam.api.infrastructure.redis; + +import java.time.Duration; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ValueRedisRepository { + + private final RedisTemplate redisTemplate; + + public void save(String key, String value, Duration timeout) { + redisTemplate + .opsForValue() + .set(key, value, timeout); + } + + public Long increment(String key, long delta) { + return redisTemplate + .opsForValue() + .increment(key, delta); + } + + public String get(String key) { + return (String)redisTemplate + .opsForValue() + .get(key); + } + + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + public void delete(String key) { + redisTemplate.delete(key); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java b/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java new file mode 100644 index 00000000..389ff604 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/redis/ZSetRedisRepository.java @@ -0,0 +1,90 @@ +package com.moabam.api.infrastructure.redis; + +import static java.util.Objects.*; + +import java.time.Duration; +import java.util.Set; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.data.redis.core.ZSetOperations.TypedTuple; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ZSetRedisRepository { + + private final RedisTemplate redisTemplate; + + public void addIfAbsent(String key, Object value, double score, int expire) { + redisTemplate + .opsForZSet() + .addIfAbsent(requireNonNull(key), requireNonNull(value), score); + redisTemplate + .expire(key, Duration.ofDays(expire)); + } + + public Set range(String key, long start, long end) { + return redisTemplate + .opsForZSet() + .range(key, start, end); + } + + public Long rank(String key, Object value) { + return redisTemplate + .opsForZSet() + .rank(key, value); + } + + public Double score(String key, Object value) { + return redisTemplate + .opsForZSet() + .score(key, value); + } + + public Long size(String key) { + return redisTemplate + .opsForZSet() + .zCard(key); + } + + public void add(String key, Object value, double score) { + redisTemplate + .opsForZSet() + .add(requireNonNull(key), requireNonNull(value), score); + } + + public void changeMember(String key, Object before, Object after) { + Double score = redisTemplate.opsForZSet().score(key, before); + + if (score == null) { + return; + } + + delete(key, before); + add(key, after, score); + } + + public void delete(String key, Object value) { + redisTemplate.opsForZSet().remove(key, value); + } + + public Set> rangeJson(String key, int startIndex, int limitIndex) { + setSerialize(Object.class); + Set> rankings = redisTemplate.opsForZSet() + .reverseRangeWithScores(key, startIndex, limitIndex); + setSerialize(String.class); + return rankings; + } + + public Long reverseRank(String key, Object myRankingInfo) { + return redisTemplate.opsForZSet().reverseRank(key, myRankingInfo); + } + + private void setSerialize(Class classes) { + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(classes)); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/s3/S3Manager.java b/src/main/java/com/moabam/api/infrastructure/s3/S3Manager.java new file mode 100644 index 00000000..45bbb1b3 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/s3/S3Manager.java @@ -0,0 +1,47 @@ +package com.moabam.api.infrastructure.s3; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import com.moabam.global.error.exception.BadRequestException; + +import io.awspring.cloud.s3.ObjectMetadata; +import io.awspring.cloud.s3.S3Template; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class S3Manager { + + private final S3Template s3Template; + + @Value("${spring.cloud.aws.s3.bucket}") + private String bucket; + + @Value("${spring.cloud.aws.s3.url}") + private String s3BaseUrl; + + @Value("${spring.cloud.aws.cloud-front.url}") + private String cloudFrontUrl; + + public String uploadImage(String key, MultipartFile file) { + try { + s3Template.upload(bucket, key, file.getInputStream(), + ObjectMetadata.builder().contentType("image/png").build()); + + return cloudFrontUrl + key; + } catch (IOException e) { + throw new BadRequestException(S3_UPLOAD_FAIL); + } + } + + public void deleteImage(String objectUrl) { + String s3Url = objectUrl.replace(cloudFrontUrl, s3BaseUrl); + s3Template.deleteObject(s3Url); + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/slack/SlackMessageFactory.java b/src/main/java/com/moabam/api/infrastructure/slack/SlackMessageFactory.java new file mode 100644 index 00000000..82e94548 --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/slack/SlackMessageFactory.java @@ -0,0 +1,74 @@ +package com.moabam.api.infrastructure.slack; + +import static java.util.stream.Collectors.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import net.gpedro.integrations.slack.SlackAttachment; +import net.gpedro.integrations.slack.SlackField; +import net.gpedro.integrations.slack.SlackMessage; + +import com.moabam.global.common.util.DateUtils; + +import jakarta.servlet.http.HttpServletRequest; + +@Component +public class SlackMessageFactory { + + private static final String ERROR_TITLE = "에러가 발생했습니다 🚨"; + + public SlackMessage generateErrorMessage(HttpServletRequest request, Exception exception) throws IOException { + return new SlackMessage() + .setAttachments(generateAttachments(request, exception)) + .setText(ERROR_TITLE); + } + + private List generateAttachments(HttpServletRequest request, Exception exception) throws + IOException { + return List.of(new SlackAttachment() + .setFallback("Error") + .setColor("danger") + .setTitleLink(request.getContextPath()) + .setText(formatException(exception)) + .setColor("danger") + .setFields(generateFields(request))); + } + + private String formatException(Exception exception) { + return String.format("📍 Exception Class%n%s%n📍 Exception Message%n%s%n%s", + exception.getClass().getName(), + exception.getMessage(), + Arrays.toString(exception.getStackTrace())); + } + + private List generateFields(HttpServletRequest request) throws IOException { + return List.of( + new SlackField().setTitle("✅ Request Method").setValue(request.getMethod()), + new SlackField().setTitle("✅ Request URL").setValue(request.getRequestURL().toString()), + new SlackField().setTitle("✅ Request Time").setValue(DateUtils.format(LocalDateTime.now())), + new SlackField().setTitle("✅ Request IP").setValue(request.getRemoteAddr()), + new SlackField().setTitle("✅ Request Headers").setValue(request.toString()), + new SlackField().setTitle("✅ Request Body").setValue(getRequestBody(request)) + ); + } + + private String getRequestBody(HttpServletRequest request) throws IOException { + String body; + + try ( + InputStream inputStream = request.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)) + ) { + body = bufferedReader.lines().collect(joining(System.lineSeparator())); + } + return body; + } +} diff --git a/src/main/java/com/moabam/api/infrastructure/slack/SlackService.java b/src/main/java/com/moabam/api/infrastructure/slack/SlackService.java new file mode 100644 index 00000000..a5295d1a --- /dev/null +++ b/src/main/java/com/moabam/api/infrastructure/slack/SlackService.java @@ -0,0 +1,26 @@ +package com.moabam.api.infrastructure.slack; + +import java.io.IOException; + +import org.springframework.core.task.TaskExecutor; +import org.springframework.stereotype.Service; + +import net.gpedro.integrations.slack.SlackApi; +import net.gpedro.integrations.slack.SlackMessage; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class SlackService { + + private final SlackApi slackApi; + private final SlackMessageFactory slackMessageFactory; + private final TaskExecutor taskExecutor; + + public void send(HttpServletRequest request, Exception exception) throws IOException { + SlackMessage slackMessage = slackMessageFactory.generateErrorMessage(request, exception); + taskExecutor.execute(() -> slackApi.call(slackMessage)); + } +} diff --git a/src/main/java/com/moabam/api/presentation/BugController.java b/src/main/java/com/moabam/api/presentation/BugController.java new file mode 100644 index 00000000..51246958 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/BugController.java @@ -0,0 +1,55 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.dto.bug.BugHistoryResponse; +import com.moabam.api.dto.bug.BugResponse; +import com.moabam.api.dto.product.ProductsResponse; +import com.moabam.api.dto.product.PurchaseProductRequest; +import com.moabam.api.dto.product.PurchaseProductResponse; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/bugs") +@RequiredArgsConstructor +public class BugController { + + private final BugService bugService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public BugResponse getBug(@Auth AuthMember member) { + return bugService.getBug(member.id()); + } + + @GetMapping("/history") + @ResponseStatus(HttpStatus.OK) + public BugHistoryResponse getBugHistory(@Auth AuthMember member) { + return bugService.getBugHistory(member.id()); + } + + @GetMapping("/products") + @ResponseStatus(HttpStatus.OK) + public ProductsResponse getBugProducts() { + return bugService.getBugProducts(); + } + + @PostMapping("/products/{productId}/purchase") + @ResponseStatus(HttpStatus.OK) + public PurchaseProductResponse purchaseBugProduct(@Auth AuthMember member, @PathVariable Long productId, + @Valid @RequestBody PurchaseProductRequest request) { + return bugService.purchaseBugProduct(member.id(), productId, request); + } +} diff --git a/src/main/java/com/moabam/api/presentation/CouponController.java b/src/main/java/com/moabam/api/presentation/CouponController.java new file mode 100644 index 00000000..01931878 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/CouponController.java @@ -0,0 +1,78 @@ +package com.moabam.api.presentation; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.coupon.CouponManageService; +import com.moabam.api.application.coupon.CouponService; +import com.moabam.api.dto.coupon.CouponResponse; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class CouponController { + + private final CouponService couponService; + private final CouponManageService couponManageService; + + @PostMapping("/admins/coupons") + @ResponseStatus(HttpStatus.CREATED) + public void create(@Valid @RequestBody CreateCouponRequest request, @Auth AuthMember admin) { + couponService.create(request, admin.id(), admin.role()); + } + + @DeleteMapping("/admins/coupons/{couponId}") + @ResponseStatus(HttpStatus.OK) + public void delete(@PathVariable("couponId") Long couponId, @Auth AuthMember admin) { + couponService.delete(couponId, admin.role()); + } + + @GetMapping("/coupons/{couponId}") + @ResponseStatus(HttpStatus.OK) + public CouponResponse getById(@PathVariable("couponId") Long couponId) { + return couponService.getById(couponId); + } + + @PostMapping("/coupons/search") + @ResponseStatus(HttpStatus.OK) + public List getAllByStatus(@Valid @RequestBody CouponStatusRequest request) { + return couponService.getAllByStatus(request); + } + + @GetMapping({"/my-coupons", "/my-coupons/{couponWalletId}"}) + @ResponseStatus(HttpStatus.OK) + public List getAllByWalletIdAndMemberId( + @PathVariable(value = "couponWalletId", required = false) Long couponWalletId, + @Auth AuthMember authMember + ) { + return couponService.getAllByWalletIdAndMemberId(couponWalletId, authMember.id()); + } + + @PostMapping("/my-coupons/{couponWalletId}") + @ResponseStatus(HttpStatus.OK) + public void use(@PathVariable("couponWalletId") Long couponWalletId, @Auth AuthMember authMember) { + couponService.use(couponWalletId, authMember.id()); + } + + @PostMapping("/coupons") + @ResponseStatus(HttpStatus.OK) + public void registerQueue(@RequestParam("couponName") String couponName, @Auth AuthMember authMember) { + couponManageService.registerQueue(couponName, authMember.id()); + } +} diff --git a/src/main/java/com/moabam/api/presentation/HealthCheckController.java b/src/main/java/com/moabam/api/presentation/HealthCheckController.java new file mode 100644 index 00000000..5d72ea28 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/HealthCheckController.java @@ -0,0 +1,24 @@ +package com.moabam.api.presentation; + +import java.time.LocalDateTime; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HealthCheckController { + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public String healthCheck() { + return "Health Check Success"; + } + + @GetMapping("/serverTime") + @ResponseStatus(HttpStatus.OK) + public String serverTimeCheck() { + return LocalDateTime.now().toString(); + } +} diff --git a/src/main/java/com/moabam/api/presentation/ItemController.java b/src/main/java/com/moabam/api/presentation/ItemController.java new file mode 100644 index 00000000..7b55c155 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/ItemController.java @@ -0,0 +1,48 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.item.ItemService; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.dto.item.ItemsResponse; +import com.moabam.api.dto.item.PurchaseItemRequest; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/items") +@RequiredArgsConstructor +public class ItemController { + + private final ItemService itemService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public ItemsResponse getItems(@Auth AuthMember member, @RequestParam ItemType type) { + return itemService.getItems(member.id(), type); + } + + @PostMapping("/{itemId}/purchase") + @ResponseStatus(HttpStatus.OK) + public void purchaseItem(@Auth AuthMember member, @PathVariable Long itemId, + @Valid @RequestBody PurchaseItemRequest request) { + itemService.purchaseItem(member.id(), itemId, request); + } + + @PostMapping("/{itemId}/select") + @ResponseStatus(HttpStatus.OK) + public void selectItem(@Auth AuthMember member, @PathVariable Long itemId) { + itemService.selectItem(member.id(), itemId); + } +} diff --git a/src/main/java/com/moabam/api/presentation/MemberController.java b/src/main/java/com/moabam/api/presentation/MemberController.java new file mode 100644 index 00000000..4891f5a3 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/MemberController.java @@ -0,0 +1,92 @@ +package com.moabam.api.presentation; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.moabam.api.application.auth.AuthorizationService; +import com.moabam.api.application.image.ImageService; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.image.ImageType; +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.ModifyMemberRequest; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.error.exception.BadRequestException; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/members") +@RequiredArgsConstructor +public class MemberController { + + private final AuthorizationService authorizationService; + private final MemberService memberService; + private final ImageService imageService; + + @GetMapping("/login/oauth") + public void socialLogin(HttpServletResponse httpServletResponse) { + authorizationService.redirectToLoginPage(httpServletResponse); + } + + @PostMapping("/login/kakao/oauth") + @ResponseStatus(HttpStatus.OK) + public LoginResponse authorizationTokenIssue(@RequestBody AuthorizationCodeResponse authorizationCodeResponse, + HttpServletResponse httpServletResponse) { + AuthorizationTokenResponse tokenResponse = authorizationService.requestToken(authorizationCodeResponse); + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = authorizationService.requestTokenInfo( + tokenResponse); + + return authorizationService.signUpOrLogin(httpServletResponse, authorizationTokenInfoResponse); + } + + @GetMapping("/logout") + @ResponseStatus(HttpStatus.OK) + public void logout(@Auth AuthMember authMember, HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse) { + authorizationService.logout(authMember, httpServletRequest, httpServletResponse); + } + + @DeleteMapping + @ResponseStatus(HttpStatus.OK) + public void deleteMember(@Auth AuthMember authMember) { + authorizationService.unLinkMember(authMember); + } + + @GetMapping(value = {"", "/{memberId}"}) + public MemberInfoResponse searchInfo(@Auth AuthMember authMember, @PathVariable(required = false) Long memberId) { + return memberService.searchInfo(authMember, memberId); + } + + @PostMapping("/modify") + public void modifyMember(@Auth AuthMember authMember, + @RequestPart(required = false) ModifyMemberRequest modifyMemberRequest, + @RequestPart(name = "profileImage", required = false) MultipartFile newProfileImage) { + String newProfileUri = null; + + try { + newProfileUri = imageService.uploadImages(List.of(newProfileImage), ImageType.PROFILE_IMAGE).get(0); + } catch (BadRequestException | NullPointerException e) { + // Do nothing + } + + memberService.modifyInfo(authMember, modifyMemberRequest, newProfileUri); + } +} diff --git a/src/main/java/com/moabam/api/presentation/NotificationController.java b/src/main/java/com/moabam/api/presentation/NotificationController.java new file mode 100644 index 00000000..85434343 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/NotificationController.java @@ -0,0 +1,42 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.notification.NotificationService; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/notifications") +public class NotificationController { + + private final NotificationService notificationService; + private final FcmService fcmService; + + @GetMapping("/rooms/{roomId}/members/{memberId}") + @ResponseStatus(HttpStatus.OK) + public void sendKnock( + @PathVariable("roomId") Long roomId, + @PathVariable("memberId") Long memberId, + @Auth AuthMember authMember + ) { + notificationService.sendKnock(roomId, memberId, authMember.id()); + } + + @PostMapping + @ResponseStatus(HttpStatus.OK) + public void createFcmToken(@RequestParam("fcmToken") String fcmToken, @Auth AuthMember authMember) { + fcmService.createToken(fcmToken, authMember.id()); + } +} diff --git a/src/main/java/com/moabam/api/presentation/PaymentController.java b/src/main/java/com/moabam/api/presentation/PaymentController.java new file mode 100644 index 00000000..ab613b73 --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/PaymentController.java @@ -0,0 +1,41 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.payment.PaymentService; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.PaymentRequest; +import com.moabam.api.dto.payment.RequestConfirmPaymentResponse; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/payments") +@RequiredArgsConstructor +public class PaymentController { + + private final PaymentService paymentService; + + @PostMapping("/{paymentId}") + @ResponseStatus(HttpStatus.OK) + public void request(@Auth AuthMember member, @PathVariable Long paymentId, + @Valid @RequestBody PaymentRequest request) { + paymentService.request(member.id(), paymentId, request); + } + + @PostMapping("/confirm") + @ResponseStatus(HttpStatus.OK) + public void confirm(@Auth AuthMember member, @Valid @RequestBody ConfirmPaymentRequest request) { + RequestConfirmPaymentResponse response = paymentService.requestConfirm(member.id(), request); + paymentService.confirm(member.id(), response.payment(), response.paymentKey()); + } +} diff --git a/src/main/java/com/moabam/api/presentation/RankingController.java b/src/main/java/com/moabam/api/presentation/RankingController.java new file mode 100644 index 00000000..d218ca5d --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/RankingController.java @@ -0,0 +1,33 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.ranking.RankingService; +import com.moabam.api.dto.ranking.TopRankingResponse; +import com.moabam.api.dto.ranking.UpdateRanking; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import lombok.RequiredArgsConstructor; + +@RequestMapping("/rankings") +@RestController +@RequiredArgsConstructor +public class RankingController { + + private final RankingService rankingService; + private final MemberService memberService; + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public TopRankingResponse getRanking(@Auth AuthMember authMember) { + UpdateRanking rankingInfo = memberService.getRankingInfo(authMember); + + return rankingService.getMemberRanking(rankingInfo); + } +} diff --git a/src/main/java/com/moabam/api/presentation/ReportController.java b/src/main/java/com/moabam/api/presentation/ReportController.java new file mode 100644 index 00000000..cd23d3fd --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/ReportController.java @@ -0,0 +1,30 @@ +package com.moabam.api.presentation; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.report.ReportService; +import com.moabam.api.dto.report.ReportRequest; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/reports") +@RequiredArgsConstructor +public class ReportController { + + private final ReportService reportService; + + @PostMapping + @ResponseStatus(HttpStatus.OK) + public void reports(@Auth AuthMember authMember, @Valid @RequestBody ReportRequest reportRequest) { + reportService.report(authMember, reportRequest); + } +} diff --git a/src/main/java/com/moabam/api/presentation/RoomController.java b/src/main/java/com/moabam/api/presentation/RoomController.java new file mode 100644 index 00000000..ffc3c52b --- /dev/null +++ b/src/main/java/com/moabam/api/presentation/RoomController.java @@ -0,0 +1,155 @@ +package com.moabam.api.presentation; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.moabam.api.application.image.ImageService; +import com.moabam.api.application.room.CertificationService; +import com.moabam.api.application.room.RoomService; +import com.moabam.api.application.room.SearchService; +import com.moabam.api.domain.image.ImageType; +import com.moabam.api.domain.image.NewImage; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.dto.room.CertifiedMemberInfo; +import com.moabam.api.dto.room.CertifyRoomsRequest; +import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.api.dto.room.EnterRoomRequest; +import com.moabam.api.dto.room.GetAllRoomsResponse; +import com.moabam.api.dto.room.ManageRoomResponse; +import com.moabam.api.dto.room.ModifyRoomRequest; +import com.moabam.api.dto.room.MyRoomsResponse; +import com.moabam.api.dto.room.RoomDetailsResponse; +import com.moabam.api.dto.room.RoomsHistoryResponse; +import com.moabam.api.dto.room.UnJoinedRoomDetailsResponse; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@RestController +@RequiredArgsConstructor +@Slf4j +@RequestMapping("/rooms") +public class RoomController { + + private final RoomService roomService; + private final SearchService searchService; + private final CertificationService certificationService; + private final ImageService imageService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Long createRoom(@Auth AuthMember authMember, @Valid @RequestBody CreateRoomRequest createRoomRequest) { + return roomService.createRoom(authMember.id(), createRoomRequest); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public GetAllRoomsResponse getAllRooms(@RequestParam(value = "roomType", required = false) RoomType roomType, + @RequestParam(value = "roomId", required = false) Long roomId) { + return searchService.getAllRooms(roomType, roomId); + } + + @GetMapping("/{roomId}") + @ResponseStatus(HttpStatus.OK) + public ManageRoomResponse getRoomForModification(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId) { + return searchService.getRoomForModification(authMember.id(), roomId); + } + + @PutMapping("/{roomId}") + @ResponseStatus(HttpStatus.OK) + public void modifyRoom(@Auth AuthMember authMember, @Valid @RequestBody ModifyRoomRequest modifyRoomRequest, + @PathVariable("roomId") Long roomId) { + roomService.modifyRoom(authMember.id(), roomId, modifyRoomRequest); + } + + @PostMapping("/{roomId}") + @ResponseStatus(HttpStatus.OK) + public void enterRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @Valid @RequestBody EnterRoomRequest enterRoomRequest) { + roomService.enterRoom(authMember.id(), roomId, enterRoomRequest); + } + + @DeleteMapping("/{roomId}") + @ResponseStatus(HttpStatus.OK) + public void exitRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId) { + roomService.exitRoom(authMember.id(), roomId); + } + + @GetMapping("/{roomId}/check") + @ResponseStatus(HttpStatus.OK) + public boolean checkIfParticipant(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId) { + return roomService.checkIfParticipant(authMember.id(), roomId); + } + + @GetMapping("/{roomId}/un-joined") + @ResponseStatus(HttpStatus.OK) + public UnJoinedRoomDetailsResponse getUnJoinedRoomDetails(@PathVariable("roomId") Long roomId) { + return searchService.getUnJoinedRoomDetails(roomId); + } + + @GetMapping("/{roomId}/{date}") + @ResponseStatus(HttpStatus.OK) + public RoomDetailsResponse getRoomDetails(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("date") LocalDate date) { + return searchService.getRoomDetails(authMember.id(), roomId, date); + } + + @PostMapping("/{roomId}/certification") + @ResponseStatus(HttpStatus.CREATED) + public void certifyRoom(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + CertifyRoomsRequest request) { + List images = imageService.getNewImages(request); + List imageUrls = imageService.uploadImages(images, ImageType.CERTIFICATION); + CertifiedMemberInfo info = certificationService.getCertifiedMemberInfo(authMember.id(), roomId, imageUrls); + certificationService.certifyRoom(info); + } + + @PutMapping("/{roomId}/members/{memberId}/mandate") + @ResponseStatus(HttpStatus.OK) + public void mandateManager(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("memberId") Long memberId) { + roomService.mandateManager(authMember.id(), roomId, memberId); + } + + @DeleteMapping("/{roomId}/members/{memberId}") + @ResponseStatus(HttpStatus.OK) + public void deportParticipant(@Auth AuthMember authMember, @PathVariable("roomId") Long roomId, + @PathVariable("memberId") Long memberId) { + roomService.deportParticipant(authMember.id(), roomId, memberId); + } + + @GetMapping("/my-join") + @ResponseStatus(HttpStatus.OK) + public MyRoomsResponse getMyRooms(@Auth AuthMember authMember) { + return searchService.getMyRooms(authMember.id()); + } + + @GetMapping("/join-history") + @ResponseStatus(HttpStatus.OK) + public RoomsHistoryResponse getJoinHistory(@Auth AuthMember authMember) { + return searchService.getJoinHistory(authMember.id()); + } + + @GetMapping("/search") + @ResponseStatus(HttpStatus.OK) + public GetAllRoomsResponse searchRooms(@RequestParam(value = "keyword") String keyword, + @RequestParam(value = "roomType", required = false) RoomType roomType, + @RequestParam(value = "roomId", required = false) Long roomId) { + return searchService.searchRooms(keyword, roomType, roomId); + } +} diff --git a/src/main/java/com/moabam/global/auth/annotation/Auth.java b/src/main/java/com/moabam/global/auth/annotation/Auth.java new file mode 100644 index 00000000..28634b27 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/annotation/Auth.java @@ -0,0 +1,12 @@ +package com.moabam.global.auth.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Auth { + +} diff --git a/src/main/java/com/moabam/global/auth/filter/AuthorizationFilter.java b/src/main/java/com/moabam/global/auth/filter/AuthorizationFilter.java new file mode 100644 index 00000000..d9f1c8d0 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/filter/AuthorizationFilter.java @@ -0,0 +1,130 @@ +package com.moabam.global.auth.filter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import com.moabam.api.application.auth.AuthorizationService; +import com.moabam.api.application.auth.JwtAuthenticationService; +import com.moabam.api.application.auth.mapper.AuthorizationMapper; +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.AuthorizationThreadLocal; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Order(2) +@Component +@RequiredArgsConstructor +public class AuthorizationFilter extends OncePerRequestFilter { + + private final HandlerExceptionResolver handlerExceptionResolver; + private final JwtAuthenticationService authenticationService; + private final AuthorizationService authorizationService; + + @Override + protected void doFilterInternal(@NotNull HttpServletRequest httpServletRequest, + @NotNull HttpServletResponse httpServletResponse, @NotNull FilterChain filterChain) throws + ServletException, + IOException { + + if (isPermit(httpServletRequest)) { + filterChain.doFilter(httpServletRequest, httpServletResponse); + return; + } + + try { + invoke(httpServletRequest, httpServletResponse); + } catch (UnauthorizedException unauthorizedException) { + authorizationService.removeToken(httpServletRequest, httpServletResponse); + handlerExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null, + unauthorizedException); + + return; + } + + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + private boolean isPermit(HttpServletRequest httpServletRequest) { + Boolean isPermit = (Boolean)httpServletRequest.getAttribute("isPermit"); + + return Objects.nonNull(isPermit) && Boolean.TRUE.equals(isPermit); + } + + private void invoke(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + Cookie[] cookies = getCookiesOrThrow(httpServletRequest); + + if (!isTokenTypeBearer(cookies)) { + throw new UnauthorizedException(ErrorMessage.TOKEN_TYPE_FAILED); + } + + handleTokenAuthenticate(cookies, httpServletResponse, httpServletRequest); + } + + private boolean isTokenTypeBearer(Cookie[] cookies) { + return "Bearer".equals(extractTokenFromCookie(cookies, "token_type")); + } + + private void handleTokenAuthenticate(Cookie[] cookies, HttpServletResponse httpServletResponse, + HttpServletRequest httpServletRequest) { + String accessToken = extractTokenFromCookie(cookies, "access_token"); + PublicClaim publicClaim = authenticationService.parseClaim(accessToken); + + if (authenticationService.isTokenExpire(accessToken, publicClaim.role())) { + String refreshToken = extractTokenFromCookie(cookies, "refresh_token"); + + if (authenticationService.isTokenExpire(refreshToken, publicClaim.role())) { + throw new UnauthorizedException(ErrorMessage.TOKEN_EXPIRE); + } + + validInvalidMember(publicClaim, refreshToken, httpServletRequest); + authorizationService.issueServiceToken(httpServletResponse, publicClaim); + } + + AuthorizationThreadLocal.setAuthMember(AuthorizationMapper.toAuthMember(publicClaim)); + } + + private void validInvalidMember(PublicClaim publicClaim, String refreshToken, + HttpServletRequest httpServletRequest) { + boolean isAdminPath = httpServletRequest.getRequestURI().contains("admins"); + + if (!((publicClaim.role().equals(Role.ADMIN) && isAdminPath) || (publicClaim.role().equals(Role.USER) + && !isAdminPath))) { + throw new BadRequestException(ErrorMessage.INVALID_REQUEST_ROLE); + } + + authorizationService.validTokenPair(publicClaim.id(), refreshToken, publicClaim.role()); + authorizationService.validMemberExist(publicClaim.id(), publicClaim.role()); + } + + private Cookie[] getCookiesOrThrow(HttpServletRequest httpServletRequest) { + return Optional.ofNullable(httpServletRequest.getCookies()) + .orElseThrow(() -> new UnauthorizedException(ErrorMessage.COOKIE_NOT_FOUND)); + } + + private String extractTokenFromCookie(Cookie[] cookies, String tokenName) { + return Arrays.stream(cookies) + .filter(cookie -> tokenName.equals(cookie.getName())) + .map(Cookie::getValue) + .findFirst() + .orElseThrow(() -> new UnauthorizedException(ErrorMessage.TOKEN_NOT_FOUND)); + } +} diff --git a/src/main/java/com/moabam/global/auth/filter/CorsFilter.java b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java new file mode 100644 index 00000000..bcd7e4d8 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/filter/CorsFilter.java @@ -0,0 +1,79 @@ +package com.moabam.global.auth.filter; + +import java.io.IOException; +import java.util.Objects; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import com.google.cloud.storage.HttpMethod; +import com.moabam.global.config.AllowOriginConfig; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Order(0) +@Component +@RequiredArgsConstructor +public class CorsFilter extends OncePerRequestFilter { + + private static final String ALLOWED_METHOD_NAMES = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH"; + private static final String ALLOWED_HEADERS = "Origin, Accept, Access-Control-Request-Method, " + + "Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer"; + + private final HandlerExceptionResolver handlerExceptionResolver; + + private final AllowOriginConfig allowOriginsConfig; + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, + FilterChain filterChain) throws ServletException, IOException { + String refer = getReferer(httpServletRequest); + + try { + if (Objects.isNull(refer)) { + throw new UnauthorizedException(ErrorMessage.INVALID_REQUEST_URL); + } + } catch (UnauthorizedException unauthorizedException) { + handlerExceptionResolver.resolveException(httpServletRequest, httpServletResponse, null, + unauthorizedException); + + return; + } + + String origin = secureMatch(refer); + + httpServletResponse.setHeader("Access-Control-Allow-Origin", origin); + httpServletResponse.setHeader("Access-Control-Allow-Methods", ALLOWED_METHOD_NAMES); + httpServletResponse.setHeader("Access-Control-Allow-Headers", ALLOWED_HEADERS); + httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); + httpServletResponse.setHeader("Access-Control-Max-Age", "3600"); + + if (isOption(httpServletRequest.getMethod())) { + httpServletRequest.setAttribute("isPermit", true); + } + + filterChain.doFilter(httpServletRequest, httpServletResponse); + } + + public String getReferer(HttpServletRequest httpServletRequest) { + return httpServletRequest.getHeader("referer"); + } + + public String secureMatch(String refer) { + return allowOriginsConfig.origin().stream().filter(refer::contains).findFirst().orElse(null); + } + + public boolean isOption(String method) { + return HttpMethod.OPTIONS.name().equals(method); + } +} diff --git a/src/main/java/com/moabam/global/auth/filter/PathFilter.java b/src/main/java/com/moabam/global/auth/filter/PathFilter.java new file mode 100644 index 00000000..8c7266ee --- /dev/null +++ b/src/main/java/com/moabam/global/auth/filter/PathFilter.java @@ -0,0 +1,48 @@ +package com.moabam.global.auth.filter; + +import java.io.IOException; +import java.util.Optional; + +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.moabam.global.auth.handler.PathResolver; + +import io.grpc.netty.shaded.io.netty.handler.codec.http.HttpMethod; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; + +@Order(1) +@Component +@RequiredArgsConstructor +public class PathFilter extends OncePerRequestFilter { + + private final PathResolver pathResolver; + + @Override + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + Optional matchedPath = pathResolver.permitPathMatch(request.getRequestURI()); + + matchedPath.ifPresent(path -> { + if (path.httpMethods().stream() + .anyMatch(httpMethod -> httpMethod.matches(request.getMethod()))) { + request.setAttribute("isPermit", true); + } + }); + + if (isOption(request.getMethod())) { + request.setAttribute("isPermit", true); + } + + filterChain.doFilter(request, response); + } + + public boolean isOption(String method) { + return HttpMethod.OPTIONS.name().equals(method); + } +} diff --git a/src/main/java/com/moabam/global/auth/handler/AuthArgumentResolver.java b/src/main/java/com/moabam/global/auth/handler/AuthArgumentResolver.java new file mode 100644 index 00000000..08fd83b2 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/handler/AuthArgumentResolver.java @@ -0,0 +1,31 @@ +package com.moabam.global.auth.handler; + +import static com.moabam.global.auth.model.AuthorizationThreadLocal.*; + +import java.util.Objects; + +import javax.annotation.Nullable; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.model.AuthMember; + +public class AuthArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return Objects.nonNull(parameter.getParameterAnnotation(Auth.class)) + && parameter.getParameterType().equals(AuthMember.class); + } + + @Override + public Object resolveArgument(@Nullable MethodParameter parameter, ModelAndViewContainer mavContainer, + @Nullable NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + return getAuthMember(); + } +} diff --git a/src/main/java/com/moabam/global/auth/handler/PathResolver.java b/src/main/java/com/moabam/global/auth/handler/PathResolver.java new file mode 100644 index 00000000..46d2c32d --- /dev/null +++ b/src/main/java/com/moabam/global/auth/handler/PathResolver.java @@ -0,0 +1,85 @@ +package com.moabam.global.auth.handler; + +import static java.util.Objects.*; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.http.HttpMethod; +import org.springframework.http.server.PathContainer; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; + +import com.moabam.api.domain.member.Role; + +import lombok.Builder; +import lombok.Singular; + +public class PathResolver { + + private final Map permitPatterns; + private final Map authenticationPatterns; + + public PathResolver(Paths paths) { + this.permitPatterns = Paths.pathParser(paths.permitAll); + this.authenticationPatterns = Paths.pathParser(paths.authentications); + } + + public Optional permitPathMatch(String uri) { + return match(permitPatterns, uri); + } + + public Optional authenticationsPatterns(String uri) { + return match(authenticationPatterns, uri); + } + + private Optional match(Map patterns, String uri) { + Set paths = patterns.keySet(); + PathContainer path = PathContainer.parsePath(uri); + PathPattern matchedPattern = paths.stream() + .filter(pathPattern -> pathPattern.matches(path)) + .findAny() + .orElse(null); + + return Optional.ofNullable(patterns.get(matchedPattern)); + } + + @Builder + public record Paths( + @Singular("permitOne") List permitAll, + @Singular("authentication") List authentications + ) { + + static Map pathParser(List uris) { + PathPatternParser parser = new PathPatternParser(); + return uris.stream() + .collect(Collectors.toMap( + path -> parser.parse(path.uri()), Function.identity() + )); + } + } + + public record Path( + String uri, + List httpMethods, + List roles + ) { + + private static final List BASE_METHODS = + List.of(HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE, HttpMethod.PUT, HttpMethod.PATCH); + + @Builder + public Path(String uri, @Singular("httpMethod") List httpMethods, + @Singular("role") List roles) { + this.uri = requireNonNull(uri); + this.roles = Optional.of(roles).filter(role -> !role.isEmpty()).orElse(List.of(Role.USER)); + this.httpMethods = Optional.of(httpMethods) + .filter(httpMethod -> !httpMethod.isEmpty()) + .orElse(BASE_METHODS); + } + } +} diff --git a/src/main/java/com/moabam/global/auth/model/AuthMember.java b/src/main/java/com/moabam/global/auth/model/AuthMember.java new file mode 100644 index 00000000..2e434766 --- /dev/null +++ b/src/main/java/com/moabam/global/auth/model/AuthMember.java @@ -0,0 +1,11 @@ +package com.moabam.global.auth.model; + +import com.moabam.api.domain.member.Role; + +public record AuthMember( + Long id, + String nickname, + Role role +) { + +} diff --git a/src/main/java/com/moabam/global/auth/model/AuthorizationThreadLocal.java b/src/main/java/com/moabam/global/auth/model/AuthorizationThreadLocal.java new file mode 100644 index 00000000..7f1250ab --- /dev/null +++ b/src/main/java/com/moabam/global/auth/model/AuthorizationThreadLocal.java @@ -0,0 +1,26 @@ +package com.moabam.global.auth.model; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class AuthorizationThreadLocal { + + private static final ThreadLocal authMember; + + static { + authMember = new ThreadLocal<>(); + } + + public static void setAuthMember(AuthMember authMember) { + AuthorizationThreadLocal.authMember.set(authMember); + } + + public static AuthMember getAuthMember() { + return authMember.get(); + } + + public static void remove() { + authMember.remove(); + } +} diff --git a/src/main/java/com/moabam/global/auth/model/PublicClaim.java b/src/main/java/com/moabam/global/auth/model/PublicClaim.java new file mode 100644 index 00000000..cc23bdec --- /dev/null +++ b/src/main/java/com/moabam/global/auth/model/PublicClaim.java @@ -0,0 +1,15 @@ +package com.moabam.global.auth.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.moabam.api.domain.member.Role; + +import lombok.Builder; + +@Builder +public record PublicClaim( + Long id, + @JsonIgnore String nickname, + @JsonIgnore Role role +) { + +} diff --git a/src/main/java/com/moabam/global/common/entity/BaseTimeEntity.java b/src/main/java/com/moabam/global/common/entity/BaseTimeEntity.java new file mode 100644 index 00000000..07b008fa --- /dev/null +++ b/src/main/java/com/moabam/global/common/entity/BaseTimeEntity.java @@ -0,0 +1,29 @@ +package com.moabam.global.common.entity; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@MappedSuperclass +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTimeEntity { + + @CreatedDate + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/moabam/global/common/util/BaseDataCode.java b/src/main/java/com/moabam/global/common/util/BaseDataCode.java new file mode 100644 index 00000000..59098690 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/BaseDataCode.java @@ -0,0 +1,11 @@ +package com.moabam.global.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaseDataCode { + + public static final Long MORNING_EGG = 1L; + public static final Long NIGHT_EGG = 2L; +} diff --git a/src/main/java/com/moabam/global/common/util/BaseImageUrl.java b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java new file mode 100644 index 00000000..c430933a --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/BaseImageUrl.java @@ -0,0 +1,20 @@ +package com.moabam.global.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaseImageUrl { + + public static final String IMAGE_DOMAIN = "https://image.moabam.com/"; + + public static final String DEFAULT_SKIN_URL = ""; + public static final String DEFAULT_MORNING_AWAKE_SKIN_URL = ""; + public static final String DEFAULT_MORNING_SLEEP_SKIN_URL = ""; + public static final String DEFAULT_NIGHT_AWAKE_SKIN_URL = ""; + public static final String DEFAULT_NIGHT_SLEEP_SKIN_URL = ""; + + public static final String DEFAULT_MORNING_EGG_URL = "moabam/skins/omok/default/egg.png"; + public static final String DEFAULT_NIGHT_EGG_URL = "moabam/skins/owl/default/egg.png"; + public static final String MEMBER_PROFILE_URL = "moabam/default/member-profile.png"; +} diff --git a/src/main/java/com/moabam/global/common/util/ClockHolder.java b/src/main/java/com/moabam/global/common/util/ClockHolder.java new file mode 100644 index 00000000..1ba7a0c5 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/ClockHolder.java @@ -0,0 +1,11 @@ +package com.moabam.global.common.util; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public interface ClockHolder { + + LocalDateTime times(); + + LocalDate date(); +} diff --git a/src/main/java/com/moabam/global/common/util/CookieUtils.java b/src/main/java/com/moabam/global/common/util/CookieUtils.java new file mode 100644 index 00000000..53aaa881 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/CookieUtils.java @@ -0,0 +1,39 @@ +package com.moabam.global.common.util; + +import jakarta.servlet.http.Cookie; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CookieUtils { + + public static Cookie tokenCookie(String name, String value, long expireTime, String domain) { + Cookie cookie = new Cookie(name, value); + cookie.setSecure(true); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setDomain("moabam.com"); + cookie.setMaxAge((int)expireTime); + cookie.setAttribute("SameSite", "None"); + + return cookie; + } + + public static Cookie typeCookie(String value, long expireTime, String domain) { + Cookie cookie = new Cookie("token_type", value); + cookie.setSecure(true); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setDomain("moabam.com"); + cookie.setMaxAge((int)expireTime); + cookie.setAttribute("SameSite", "None"); + + return cookie; + } + + public static Cookie deleteCookie(Cookie cookie) { + cookie.setMaxAge(0); + cookie.setPath("/"); + return cookie; + } +} diff --git a/src/main/java/com/moabam/global/common/util/DateUtils.java b/src/main/java/com/moabam/global/common/util/DateUtils.java new file mode 100644 index 00000000..33e896fa --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/DateUtils.java @@ -0,0 +1,17 @@ +package com.moabam.global.common.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class DateUtils { + + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public static String format(LocalDateTime dateTime) { + return dateTime.format(formatter); + } +} diff --git a/src/main/java/com/moabam/global/common/util/DynamicQuery.java b/src/main/java/com/moabam/global/common/util/DynamicQuery.java new file mode 100644 index 00000000..47468ee9 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/DynamicQuery.java @@ -0,0 +1,34 @@ +package com.moabam.global.common.util; + +import java.util.Objects; +import java.util.function.Function; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.SimpleExpression; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class DynamicQuery { + + public static BooleanExpression generateEq(T value, Function function) { + if (Objects.isNull(value)) { + return null; + } + + return function.apply(value); + } + + public static BooleanExpression generateIsNull(Boolean value, T field) { + if (Objects.isNull(value)) { + return null; + } + + if (Boolean.TRUE.equals(value)) { + return field.isNull(); + } + + return field.isNotNull(); + } +} diff --git a/src/main/java/com/moabam/global/common/util/GlobalConstant.java b/src/main/java/com/moabam/global/common/util/GlobalConstant.java new file mode 100644 index 00000000..7b62d447 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/GlobalConstant.java @@ -0,0 +1,21 @@ +package com.moabam.global.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class GlobalConstant { + + public static final String BLANK = ""; + public static final String DELIMITER = "/"; + public static final String CHARSET_UTF_8 = ";charset=UTF-8"; + public static final String SPACE = " "; + public static final int MIDNIGHT_HOUR = 0; + public static final int ONE_HOUR = 1; + public static final int HOURS_IN_A_DAY = 24; + public static final int NOT_COMPLETED_RANK = 500; + + public static final int ROOM_FIXED_SEARCH_SIZE = 10; + public static final int LEVEL_DIVISOR = 10; + public static final String IMAGE_EXTENSION = ".png"; +} diff --git a/src/main/java/com/moabam/global/common/util/RandomUtils.java b/src/main/java/com/moabam/global/common/util/RandomUtils.java new file mode 100644 index 00000000..462d6f5a --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/RandomUtils.java @@ -0,0 +1,19 @@ +package com.moabam.global.common.util; + +import java.security.SecureRandom; + +import org.apache.commons.lang3.RandomStringUtils; + +public class RandomUtils { + + public static String randomStringValues() { + return RandomStringUtils.random(6, 0, 0, true, true, null, + new SecureRandom()); + } + + public static String randomNumberValues() { + return RandomStringUtils.random(4, 0, 0, false, true, null, + new SecureRandom()); + } + +} diff --git a/src/main/java/com/moabam/global/common/util/StreamUtils.java b/src/main/java/com/moabam/global/common/util/StreamUtils.java new file mode 100644 index 00000000..2820d908 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/StreamUtils.java @@ -0,0 +1,17 @@ +package com.moabam.global.common.util; + +import java.util.List; +import java.util.function.Function; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class StreamUtils { + + public static List map(List list, Function mapper) { + return list.stream() + .map(mapper) + .toList(); + } +} diff --git a/src/main/java/com/moabam/global/common/util/SystemClockHolder.java b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java new file mode 100644 index 00000000..1d24cdc3 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/SystemClockHolder.java @@ -0,0 +1,22 @@ +package com.moabam.global.common.util; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile({"dev", "prod"}) +public class SystemClockHolder implements ClockHolder { + + @Override + public LocalDateTime times() { + return LocalDateTime.now(); + } + + @Override + public LocalDate date() { + return LocalDate.now(); + } +} diff --git a/src/main/java/com/moabam/global/common/util/UrlSubstringParser.java b/src/main/java/com/moabam/global/common/util/UrlSubstringParser.java new file mode 100644 index 00000000..52f2cba2 --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/UrlSubstringParser.java @@ -0,0 +1,25 @@ +package com.moabam.global.common.util; + +import static com.moabam.global.common.util.GlobalConstant.*; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class UrlSubstringParser { + + public static String parseUrl(String url, String distinctToken) { + + int lastSlashTokenIndex = url.lastIndexOf(DELIMITER); + int distinctTokenIndex = url.indexOf(distinctToken); + + if (lastSlashTokenIndex == -1 || distinctTokenIndex == -1 || lastSlashTokenIndex > distinctTokenIndex) { + throw new BadRequestException(ErrorMessage.INVALID_REQUEST_URL); + } + + return url.substring(lastSlashTokenIndex + 1, distinctTokenIndex); + } +} diff --git a/src/main/java/com/moabam/global/common/util/cookie/CookieDevUtils.java b/src/main/java/com/moabam/global/common/util/cookie/CookieDevUtils.java new file mode 100644 index 00000000..ca08b11f --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/cookie/CookieDevUtils.java @@ -0,0 +1,22 @@ +package com.moabam.global.common.util.cookie; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.Cookie; + +@Component +@Profile({"dev", "local", "test"}) +public class CookieDevUtils extends CookieUtils { + + protected Cookie detailCookies(String name, String value, long expireTime) { + Cookie cookie = new Cookie(name, value); + cookie.setSecure(true); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setMaxAge((int)expireTime); + cookie.setAttribute("SameSite", "None"); + + return cookie; + } +} diff --git a/src/main/java/com/moabam/global/common/util/cookie/CookieProdUtils.java b/src/main/java/com/moabam/global/common/util/cookie/CookieProdUtils.java new file mode 100644 index 00000000..72ec0dce --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/cookie/CookieProdUtils.java @@ -0,0 +1,22 @@ +package com.moabam.global.common.util.cookie; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import jakarta.servlet.http.Cookie; + +@Component +@Profile({"prod"}) +public class CookieProdUtils extends CookieUtils { + + protected Cookie detailCookies(String name, String value, long expireTime) { + Cookie cookie = new Cookie(name, value); + cookie.setSecure(true); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setMaxAge((int)expireTime); + + return cookie; + } +} + diff --git a/src/main/java/com/moabam/global/common/util/cookie/CookieUtils.java b/src/main/java/com/moabam/global/common/util/cookie/CookieUtils.java new file mode 100644 index 00000000..e0b7cb9e --- /dev/null +++ b/src/main/java/com/moabam/global/common/util/cookie/CookieUtils.java @@ -0,0 +1,22 @@ +package com.moabam.global.common.util.cookie; + +import jakarta.servlet.http.Cookie; + +public abstract class CookieUtils { + + public Cookie tokenCookie(String name, String value, long expireTime) { + return detailCookies(name, value, expireTime); + } + + public Cookie typeCookie(String value, long expireTime) { + return detailCookies("token_type", value, expireTime); + } + + public Cookie deleteCookie(Cookie cookie) { + cookie.setMaxAge(0); + cookie.setPath("/"); + return cookie; + } + + protected abstract Cookie detailCookies(String name, String value, long expireTime); +} diff --git a/src/main/java/com/moabam/global/config/AllowOriginConfig.java b/src/main/java/com/moabam/global/config/AllowOriginConfig.java new file mode 100644 index 00000000..b580a99f --- /dev/null +++ b/src/main/java/com/moabam/global/config/AllowOriginConfig.java @@ -0,0 +1,13 @@ +package com.moabam.global.config; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "allows") +public record AllowOriginConfig( + String adminDomain, + String domain, + List origin) { + +} diff --git a/src/main/java/com/moabam/global/config/EmbeddedRedisConfig.java b/src/main/java/com/moabam/global/config/EmbeddedRedisConfig.java new file mode 100644 index 00000000..85e7f64a --- /dev/null +++ b/src/main/java/com/moabam/global/config/EmbeddedRedisConfig.java @@ -0,0 +1,227 @@ +package com.moabam.global.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.moabam.global.error.exception.MoabamException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.annotation.PreDestroy; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import redis.embedded.RedisServer; + +@Slf4j +@Configuration +@Profile("test") +public class EmbeddedRedisConfig { + + private final int redisPort; + private final String redisHost; + + private int availablePort; + private RedisServer redisServer; + + public EmbeddedRedisConfig( + @Value("${spring.data.redis.port}") int redisPort, + @Value("${spring.data.redis.host}") String redisHost + ) { + this.redisPort = redisPort; + this.redisHost = redisHost; + + startRedis(); + } + + @Bean + public RedisConnectionFactory redisConnectionFactory(EmbeddedRedisConfig embeddedRedisConfig) { + return new LettuceConnectionFactory(redisHost, embeddedRedisConfig.getAvailablePort()); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory); + + return redisTemplate; + } + + @Order(2) + @Bean + public ObjectMapper objectRedisMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModules(new JavaTimeModule()); + + return objectMapper; + } + + public void startRedis() { + Os os = Os.createOs(); + availablePort = findPort(os); + + if (os.isMac()) { + redisServer = new RedisServer(getRedisFileForArcMac(), availablePort); + } else { + redisServer = RedisServer.builder() + .port(availablePort) + .setting("maxmemory 128M") + .build(); + } + + try { + redisServer.start(); + } catch (Exception e) { + stopRedis(); + throw new MoabamException(e.getMessage()); + } + } + + @PreDestroy + public void stopRedis() { + try { + if (redisServer != null) { + redisServer.stop(); + } + } catch (Exception e) { + throw new MoabamException(e.getMessage()); + } + } + + public int getAvailablePort() { + return availablePort; + } + + private int findPort(Os os) { + if (!isRunning(os.executeCommand(redisPort))) { + return redisPort; + } + + return findAvailablePort(os); + } + + private int findAvailablePort(Os os) { + for (int port = 10000; port <= 65535; port++) { + Process process = os.executeCommand(port); + + if (!isRunning(process)) { + return port; + } + } + + throw new MoabamException(ErrorMessage.NOT_FOUND_AVAILABLE_PORT); + } + + private boolean isRunning(Process process) { + String line; + StringBuilder pidInfo = new StringBuilder(); + + try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + while ((line = input.readLine()) != null) { + pidInfo.append(line); + } + } catch (Exception e) { + throw new MoabamException(ErrorMessage.ERROR_EXECUTING_EMBEDDED_REDIS); + } + + return StringUtils.hasText(pidInfo.toString()); + } + + private File getRedisFileForArcMac() { + try { + return new ClassPathResource("binary/redis/redis-server-arm64").getFile(); + } catch (Exception e) { + throw new MoabamException(e.getMessage()); + } + } + + private static final class Os { + + enum Type { + MAC, + WIN, + LINUX + } + + private final String shellPath; + private final String optionOperator; + private final String command; + private final Type type; + + @Builder + private Os(String shellPath, String optionOperator, String command, Type type) { + this.shellPath = shellPath; + this.optionOperator = optionOperator; + this.command = command; + this.type = type; + } + + public Process executeCommand(int port) { + String osCommand = String.format(this.command, port); + String[] script = {shellPath, optionOperator, osCommand}; + + try { + return Runtime.getRuntime().exec(script); + } catch (IOException e) { + throw new MoabamException(e.getMessage()); + } + } + + public boolean isMac() { + return type == Type.MAC; + } + + public static Os createOs() { + String osArchitecture = System.getProperty("os.arch"); + String osName = System.getProperty("os.name"); + + if (osArchitecture.equals("aarch64") && osName.equals("Mac OS X")) { + return linuxOs(Type.MAC); + } + + if (osArchitecture.equals("amd64") && osName.contains("Windows")) { + return windowOs(); + } + + return linuxOs(Type.LINUX); + } + + // 변경 전 + private static Os linuxOs(Type type) { + return Os.builder() + .shellPath("/bin/sh") + .optionOperator("-c") + .command("netstat -nat | grep LISTEN | grep %d") + .type(type) + .build(); + } + + // 변경 후 + private static Os windowOs() { + return Os.builder() + .shellPath("cmd.exe") + .optionOperator("/c") + .command("netstat -ano | findstr LISTEN | findstr %d") + .type(Type.WIN) + .build(); + } + } +} diff --git a/src/main/java/com/moabam/global/config/FcmConfig.java b/src/main/java/com/moabam/global/config/FcmConfig.java new file mode 100644 index 00000000..6f003820 --- /dev/null +++ b/src/main/java/com/moabam/global/config/FcmConfig.java @@ -0,0 +1,44 @@ +package com.moabam.global.config; + +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import com.moabam.global.error.exception.FcmException; +import com.moabam.global.error.model.ErrorMessage; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Configuration +public class FcmConfig { + + private static final String FIREBASE_PATH = "config/moabam-firebase.json"; + + @Bean + public FirebaseMessaging firebaseMessaging() { + try (InputStream inputStream = new ClassPathResource(FIREBASE_PATH).getInputStream()) { + GoogleCredentials credentials = GoogleCredentials.fromStream(inputStream); + FirebaseOptions firebaseOptions = FirebaseOptions.builder() + .setCredentials(credentials) + .build(); + + if (FirebaseApp.getApps().isEmpty()) { + FirebaseApp.initializeApp(firebaseOptions); + log.info("======= Firebase init start ======="); + } + + return FirebaseMessaging.getInstance(); + } catch (IOException e) { + log.error("======= firebase moabam error =======\n" + e); + throw new FcmException(ErrorMessage.FAILED_FCM_INIT); + } + } +} diff --git a/src/main/java/com/moabam/global/config/JpaConfig.java b/src/main/java/com/moabam/global/config/JpaConfig.java new file mode 100644 index 00000000..a443a76e --- /dev/null +++ b/src/main/java/com/moabam/global/config/JpaConfig.java @@ -0,0 +1,24 @@ +package com.moabam.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +import com.querydsl.jpa.JPQLTemplates; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/moabam/global/config/OAuthConfig.java b/src/main/java/com/moabam/global/config/OAuthConfig.java new file mode 100644 index 00000000..4884aacb --- /dev/null +++ b/src/main/java/com/moabam/global/config/OAuthConfig.java @@ -0,0 +1,34 @@ +package com.moabam.global.config; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "oauth2") +public record OAuthConfig( + Provider provider, + Client client +) { + + public record Client( + String provider, + String clientId, + String clientSecret, + String authorizationGrantType, + List scope, + String adminKey + ) { + + } + + public record Provider( + String authorizationUri, + String redirectUri, + String tokenUri, + String tokenInfo, + String unlink, + String adminRedirectUri + ) { + + } +} diff --git a/src/main/java/com/moabam/global/config/RedisConfig.java b/src/main/java/com/moabam/global/config/RedisConfig.java new file mode 100644 index 00000000..9d017cce --- /dev/null +++ b/src/main/java/com/moabam/global/config/RedisConfig.java @@ -0,0 +1,38 @@ +package com.moabam.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@Profile("!test") +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory); + + return redisTemplate; + } +} diff --git a/src/main/java/com/moabam/global/config/SlackConfig.java b/src/main/java/com/moabam/global/config/SlackConfig.java new file mode 100644 index 00000000..20ec8805 --- /dev/null +++ b/src/main/java/com/moabam/global/config/SlackConfig.java @@ -0,0 +1,19 @@ +package com.moabam.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import net.gpedro.integrations.slack.SlackApi; + +@Configuration +public class SlackConfig { + + @Value("${webhook.slack.url}") + private String webhookUrl; + + @Bean + public SlackApi slackApi() { + return new SlackApi(webhookUrl); + } +} diff --git a/src/main/java/com/moabam/global/config/TokenConfig.java b/src/main/java/com/moabam/global/config/TokenConfig.java new file mode 100644 index 00000000..4bbe5a1e --- /dev/null +++ b/src/main/java/com/moabam/global/config/TokenConfig.java @@ -0,0 +1,32 @@ +package com.moabam.global.config; + +import java.nio.charset.StandardCharsets; +import java.security.Key; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import io.jsonwebtoken.security.Keys; +import lombok.Getter; + +@Getter +@ConfigurationProperties(prefix = "token") +public class TokenConfig { + + private final String iss; + private final long accessExpire; + private final long refreshExpire; + private final String secretKey; + private final String adminSecret; + private final Key key; + private final Key adminKey; + + public TokenConfig(String iss, long accessExpire, long refreshExpire, String secretKey, String adminSecret) { + this.iss = iss; + this.accessExpire = accessExpire; + this.refreshExpire = refreshExpire; + this.secretKey = secretKey; + this.adminSecret = adminSecret; + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + this.adminKey = Keys.hmacShaKeyFor(adminSecret.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/com/moabam/global/config/TossPaymentConfig.java b/src/main/java/com/moabam/global/config/TossPaymentConfig.java new file mode 100644 index 00000000..e3f2bcca --- /dev/null +++ b/src/main/java/com/moabam/global/config/TossPaymentConfig.java @@ -0,0 +1,11 @@ +package com.moabam.global.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "payment.toss") +public record TossPaymentConfig( + String baseUrl, + String secretKey +) { + +} diff --git a/src/main/java/com/moabam/global/config/WebConfig.java b/src/main/java/com/moabam/global/config/WebConfig.java new file mode 100644 index 00000000..e5971d20 --- /dev/null +++ b/src/main/java/com/moabam/global/config/WebConfig.java @@ -0,0 +1,53 @@ +package com.moabam.global.config; + +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.moabam.api.application.auth.mapper.PathMapper; +import com.moabam.global.auth.handler.AuthArgumentResolver; +import com.moabam.global.auth.handler.PathResolver; + +@Configuration +@EnableScheduling +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(handlerMethodArgumentResolver()); + } + + @Bean + public HandlerMethodArgumentResolver handlerMethodArgumentResolver() { + return new AuthArgumentResolver(); + } + + @Bean + public PathResolver pathResolver() { + PathResolver.Paths path = PathResolver.Paths.builder() + .permitAll(List.of( + PathMapper.parsePath("/"), + PathMapper.pathWithMethod("/members", List.of(HttpMethod.POST)), + PathMapper.pathWithMethod("/members/login/oauth", List.of(HttpMethod.GET)), + PathMapper.parsePath("/members/login/*/oauth"), + PathMapper.parsePath("/admins/login/*/oauth"), + PathMapper.parsePath("/css/*"), + PathMapper.parsePath("/js/*"), + PathMapper.parsePath("/images/*"), + PathMapper.parsePath("/webjars/*"), + PathMapper.parsePath("/favicon/*"), + PathMapper.parsePath("/*/icon-*"), + PathMapper.parsePath("/favicon.ico"), + PathMapper.parsePath("/v3/api-docs"), + PathMapper.parsePath("/swagger*/**"), + PathMapper.pathWithMethod("/serverTime", List.of(HttpMethod.GET)))) + .build(); + + return new PathResolver(path); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/BadRequestException.java b/src/main/java/com/moabam/global/error/exception/BadRequestException.java new file mode 100644 index 00000000..e0826af2 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/BadRequestException.java @@ -0,0 +1,10 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class BadRequestException extends MoabamException { + + public BadRequestException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/ConflictException.java b/src/main/java/com/moabam/global/error/exception/ConflictException.java new file mode 100644 index 00000000..fe756197 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/ConflictException.java @@ -0,0 +1,10 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class ConflictException extends MoabamException { + + public ConflictException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/FcmException.java b/src/main/java/com/moabam/global/error/exception/FcmException.java new file mode 100644 index 00000000..25c9fa48 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/FcmException.java @@ -0,0 +1,10 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class FcmException extends MoabamException { + + public FcmException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/ForbiddenException.java b/src/main/java/com/moabam/global/error/exception/ForbiddenException.java new file mode 100644 index 00000000..05ca2c3c --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/ForbiddenException.java @@ -0,0 +1,10 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class ForbiddenException extends MoabamException { + + public ForbiddenException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/MoabamException.java b/src/main/java/com/moabam/global/error/exception/MoabamException.java new file mode 100644 index 00000000..c7988bf3 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/MoabamException.java @@ -0,0 +1,18 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class MoabamException extends RuntimeException { + + private final ErrorMessage errorMessage; + + public MoabamException(ErrorMessage errorMessage) { + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } + + public MoabamException(String message) { + super(message); + this.errorMessage = ErrorMessage.FAILED_MOABAM; + } +} diff --git a/src/main/java/com/moabam/global/error/exception/NotFoundException.java b/src/main/java/com/moabam/global/error/exception/NotFoundException.java new file mode 100644 index 00000000..08273e0e --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/NotFoundException.java @@ -0,0 +1,13 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +import lombok.Getter; + +@Getter +public class NotFoundException extends MoabamException { + + public NotFoundException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/TossPaymentException.java b/src/main/java/com/moabam/global/error/exception/TossPaymentException.java new file mode 100644 index 00000000..2b86b1d4 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/TossPaymentException.java @@ -0,0 +1,8 @@ +package com.moabam.global.error.exception; + +public class TossPaymentException extends MoabamException { + + public TossPaymentException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/exception/UnauthorizedException.java b/src/main/java/com/moabam/global/error/exception/UnauthorizedException.java new file mode 100644 index 00000000..9f99a3e8 --- /dev/null +++ b/src/main/java/com/moabam/global/error/exception/UnauthorizedException.java @@ -0,0 +1,10 @@ +package com.moabam.global.error.exception; + +import com.moabam.global.error.model.ErrorMessage; + +public class UnauthorizedException extends MoabamException { + + public UnauthorizedException(ErrorMessage errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java b/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..766a7e01 --- /dev/null +++ b/src/main/java/com/moabam/global/error/handler/GlobalExceptionHandler.java @@ -0,0 +1,114 @@ +package com.moabam.global.error.handler; + +import static com.moabam.global.error.model.ErrorMessage.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.FcmException; +import com.moabam.global.error.exception.ForbiddenException; +import com.moabam.global.error.exception.MoabamException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.exception.TossPaymentException; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorResponse; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NotFoundException.class) + protected ErrorResponse handleNotFoundException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(UnauthorizedException.class) + protected ErrorResponse handleUnauthorizedException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ForbiddenException.class) + protected ErrorResponse handleForbiddenException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(ConflictException.class) + protected ErrorResponse handleConflictException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(BadRequestException.class) + protected ErrorResponse handleBadRequestException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler({ + FcmException.class, + TossPaymentException.class + }) + protected ErrorResponse handleFcmException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MoabamException.class) + protected ErrorResponse handleMoabamException(MoabamException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(NullPointerException.class) + protected ErrorResponse handleNullPointerException(NullPointerException exception) { + return new ErrorResponse(exception.getMessage(), null); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) { + List fieldErrors = exception.getBindingResult().getFieldErrors(); + Map validation = new HashMap<>(); + + for (FieldError fieldError : fieldErrors) { + validation.put(fieldError.getField(), fieldError.getDefaultMessage()); + } + + return new ErrorResponse(INVALID_REQUEST_FIELD.getMessage(), validation); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + protected ErrorResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException exception) { + String typeName = Optional.ofNullable(exception.getRequiredType()) + .map(Class::getSimpleName) + .orElse(""); + String message = String.format(INVALID_REQUEST_VALUE_TYPE_FORMAT.getMessage(), exception.getValue(), typeName); + + return new ErrorResponse(message, null); + } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + protected ErrorResponse handleMaxSizeException(MaxUploadSizeExceededException exception) { + String message = String.format(S3_INVALID_IMAGE_SIZE.getMessage()); + + return new ErrorResponse(message, null); + } +} diff --git a/src/main/java/com/moabam/global/error/handler/RestTemplateResponseHandler.java b/src/main/java/com/moabam/global/error/handler/RestTemplateResponseHandler.java new file mode 100644 index 00000000..d0708d97 --- /dev/null +++ b/src/main/java/com/moabam/global/error/handler/RestTemplateResponseHandler.java @@ -0,0 +1,62 @@ +package com.moabam.global.error.handler; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.client.ResponseErrorHandler; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +@Component +public class RestTemplateResponseHandler implements ResponseErrorHandler { + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + try { + return response.getStatusCode().isError(); + } catch (IOException ioException) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + } + + @Override + public void handleError(ClientHttpResponse response) { + try { + String errorMessage = parseErrorMessage(response); + HttpStatusCode statusCode = response.getStatusCode(); + + validResponse(statusCode); + } catch (IOException ioException) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + } + + private String parseErrorMessage(ClientHttpResponse response) throws IOException { + BufferedReader errorMessage = new BufferedReader(new InputStreamReader(response.getBody())); + + String line = errorMessage.readLine(); + StringBuilder sb = new StringBuilder(); + + while (line != null) { + sb.append(line).append("\n"); + line = errorMessage.readLine(); + } + + return sb.toString(); + } + + private void validResponse(HttpStatusCode statusCode) { + if (statusCode.is5xxServerError()) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + + if (statusCode.is4xxClientError()) { + throw new BadRequestException(ErrorMessage.INVALID_REQUEST_FIELD); + } + } +} diff --git a/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java b/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java new file mode 100644 index 00000000..b144c62b --- /dev/null +++ b/src/main/java/com/moabam/global/error/handler/SlackExceptionHandler.java @@ -0,0 +1,29 @@ +package com.moabam.global.error.handler; + +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.moabam.api.infrastructure.slack.SlackService; +import com.moabam.global.error.model.ErrorResponse; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +@RestControllerAdvice +@Profile({"dev", "prod"}) +@RequiredArgsConstructor +public class SlackExceptionHandler { + + private final SlackService slackService; + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + protected ErrorResponse handleException(HttpServletRequest request, Exception exception) throws Exception { + slackService.send(request, exception); + + return new ErrorResponse(exception.getMessage(), null); + } +} diff --git a/src/main/java/com/moabam/global/error/model/ErrorMessage.java b/src/main/java/com/moabam/global/error/model/ErrorMessage.java new file mode 100644 index 00000000..62428059 --- /dev/null +++ b/src/main/java/com/moabam/global/error/model/ErrorMessage.java @@ -0,0 +1,105 @@ +package com.moabam.global.error.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorMessage { + + FAILED_MOABAM("모아밤 서버 실행 중 오류가 발생했습니다."), + INVALID_REQUEST_FIELD("올바른 요청 정보가 아닙니다."), + INVALID_REQUEST_VALUE_TYPE_FORMAT("'%s' 값은 유효한 %s 값이 아닙니다."), + NOT_FOUND_AVAILABLE_PORT("사용 가능한 포트를 찾을 수 없습니다. (10000 ~ 65535)"), + ERROR_EXECUTING_EMBEDDED_REDIS("Embedded Redis 실행 중 오류가 발생했습니다."), + INVALID_REQUEST_ROLE("회원은 회원에, 어드민은 어드민에 연결해야 합니다."), + + REPORT_REQUEST_ERROR("신고 요청하고자 하는 방이나 대상이 존재하지 않습니다."), + + ROOM_NOT_FOUND("존재하지 않는 방 입니다."), + ROOM_MAX_USER_COUNT_MODIFY_FAIL("잘못된 최대 인원수 설정입니다."), + ROOM_MODIFY_UNAUTHORIZED_REQUEST("방장이 아닌 사용자는 방을 수정할 수 없습니다."), + ROOM_EXIT_MANAGER_FAIL("인원수가 2명 이상일 때는 방장을 위임해야 합니다."), + PARTICIPANT_NOT_FOUND("방에 대한 참여자의 정보가 없습니다."), + WRONG_ROOM_PASSWORD("방의 비밀번호가 일치하지 않습니다."), + ROOM_MAX_USER_REACHED("방의 인원수가 찼습니다."), + ROOM_DETAILS_ERROR("방 정보를 불러오는데 실패했습니다."), + ROUTINE_LENGTH_ERROR("루틴의 길이가 잘못 되었습니다."), + DUPLICATED_DAILY_MEMBER_CERTIFICATION("이미 오늘의 인증을 완료하였습니다."), + ROUTINE_NOT_FOUND("루틴을 찾을 수 없습니다"), + INVALID_REQUEST_URL("잘못된 URL 요청입니다."), + INVALID_CERTIFY_TIME("현재 인증 시간이 아닙니다."), + CERTIFICATION_NOT_FOUND("인증 정보가 없습니다."), + NEED_TO_EXIT_ALL_ROOMS("모든 방에서 나가야 회원 탈퇴가 가능합니다."), + PARTICIPANT_DEPORT_ERROR("방장은 자신을 추방할 수 없습니다."), + IMAGE_CONVERT_FAIL("이미지 변환을 실패했습니다."), + UNAVAILABLE_TO_CHANGE_CERTIFY_TIME("이미 한명 이상이 인증을 하면 인증 시간을 바꿀 수 없습니다."), + CERTIFIED_ROOM_EXIT_FAILED("오늘 인증한 방은 나갈 수 없습니다."), + ROOM_ENTER_FAILED("해당 방의 인증 시간에는 입장할 수 없습니다."), + + LOGIN_FAILED("로그인에 실패했습니다."), + LOGIN_FAILED_ADMIN_KEY("어드민키가 달라요"), + REQUEST_FAILED("네트워크 접근 실패입니다."), + TOKEN_TYPE_FAILED("토큰 타일이 일치하지 않습니다."), + GRANT_FAILED("인가 코드 실패"), + TOKEN_EXPIRE("토큰이 만료되었습니다."), + AUTHENTICATE_FAIL("인증 실패"), + TOKEN_NOT_FOUND("토큰이 존재하지 않습니다."), + COOKIE_NOT_FOUND("쿠키가 없습니다"), + MEMBER_NOT_FOUND("존재하지 않는 회원입니다."), + MEMBER_NOT_FOUND_BY_MANAGER_OR_NULL("방의 매니저거나 회원이 존재하지 않습니다."), + MEMBER_ROOM_EXCEED("참여할 수 있는 방의 개수가 모두 찼습니다."), + UNLINK_REQUEST_FAIL_ROLLBACK_SUCCESS("카카오 연결 요청 실패로 Rollback하였습니다."), + NICKNAME_CONFLICT("이미 존재하는 닉네임입니다."), + + BASIC_SKIN_NOT_FOUND("기본 스킨 오류 발생, 관리자에게 문의하세요"), + INVALID_DEFAULT_SKIN_SIZE("기본 스킨은 2개여야 합니다. 관리자에게 문의하세요"), + SKIN_TYPE_NOT_FOUND("스킨 타입이 없습니다. 관리자에게 문의하세요"), + + BUG_NOT_ENOUGH("보유한 벌레가 부족합니다."), + + ITEM_NOT_FOUND("존재하지 않는 아이템입니다."), + ITEM_UNLOCK_LEVEL_HIGH("아이템 해금 레벨이 높습니다."), + ITEM_NOT_PURCHASABLE_BY_BUG_TYPE("해당 벌레 타입으로는 구매할 수 없는 아이템입니다."), + INVENTORY_NOT_FOUND("구매하지 않은 아이템은 적용할 수 없습니다."), + DEFAULT_INVENTORY_NOT_FOUND("현재 적용된 아이템이 없습니다."), + INVENTORY_CONFLICT("이미 구매한 아이템입니다."), + + INVALID_BUG_COUNT("벌레 개수는 0 이상이어야 합니다."), + INVALID_PRICE("가격은 0 이상이어야 합니다."), + INVALID_QUANTITY("수량은 1 이상이어야 합니다."), + INVALID_LEVEL("레벨은 1 이상이어야 합니다."), + INVALID_PAYMENT_AMOUNT("결제 금액은 0 이상이어야 합니다."), + + PRODUCT_NOT_FOUND("존재하지 않는 상품입니다."), + + PAYMENT_NOT_FOUND("존재하지 않는 결제 정보입니다."), + INVALID_MEMBER_PAYMENT("해당 회원의 결제 정보가 아닙니다."), + INVALID_PAYMENT_INFO("결제 정보가 일치하지 않습니다."), + + FAILED_FCM_INIT("파이어베이스 설정을 실패했습니다."), + NOT_FOUND_FCM_TOKEN("해당 유저는 접속 중이 아닙니다."), + CONFLICT_KNOCK("이미 콕 알림을 보낸 대상입니다."), + + INVALID_COUPON_POINT("쿠폰의 보너스 포인트는 0 이상이어야 합니다."), + INVALID_COUPON_STOCK("쿠폰의 재고는 0 이상이어야 합니다."), + INVALID_COUPON_STOCK_END("쿠폰 발급 선착순이 마감되었습니다."), + INVALID_COUPON_START_AT_PERIOD("쿠폰 발급 시작 날짜는 현재 날짜보다 이전이거나 같을 수 없습니다."), + INVALID_COUPON_OPEN_AT_PERIOD("쿠폰 정보 오픈 날짜는 시작 날짜보다 이전이여야 합니다."), + INVALID_COUPON_PERIOD("쿠폰 발급 가능 기간이 아닙니다."), + INVALID_DISCOUNT_COUPON("할인 쿠폰은 결제 시, 사용할 수 있습니다."), + INVALID_BUG_COUPON("벌레 쿠폰은 보관함에서 사용할 수 있습니다."), + CONFLICT_COUPON_NAME("쿠폰의 이름이 중복되었습니다."), + CONFLICT_COUPON_START_AT("쿠폰 발급 가능 날짜가 중복되었습니다."), + CONFLICT_COUPON_ISSUE("이미 쿠폰 발급에 성공했습니다!"), + NOT_FOUND_COUPON_TYPE("존재하지 않는 쿠폰 종류입니다."), + NOT_FOUND_COUPON("존재하지 않는 쿠폰입니다."), + NOT_FOUND_COUPON_WALLET("보유하지 않은 쿠폰입니다."), + + S3_UPLOAD_FAIL("S3 업로드를 실패했습니다."), + S3_INVALID_IMAGE("올바른 이미지(파일) 형식이 아닙니다."), + S3_INVALID_IMAGE_SIZE("파일의 용량이 너무 큽니다."), + S3_RESIZE_ERROR("이미지 리사이징에서 에러가 발생했습니다."); + + private final String message; +} diff --git a/src/main/java/com/moabam/global/error/model/ErrorResponse.java b/src/main/java/com/moabam/global/error/model/ErrorResponse.java new file mode 100644 index 00000000..b4349e9c --- /dev/null +++ b/src/main/java/com/moabam/global/error/model/ErrorResponse.java @@ -0,0 +1,11 @@ +package com.moabam.global.error.model; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonInclude; + +public record ErrorResponse( + String message, + @JsonInclude(JsonInclude.Include.NON_EMPTY) Map validation +) { +} diff --git a/src/main/resources/binary/redis/redis-server-arm64 b/src/main/resources/binary/redis/redis-server-arm64 new file mode 100755 index 0000000000000000000000000000000000000000..c3090073bcb4d7020d1363eef431e80993bc7107 GIT binary patch literal 2338192 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 z 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 Y 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 !G4 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 z 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
1-mER z`_A>A^0yGUF~4>l2K*j&9R@%DUS9sqU+-OqL3sL4hileHz*RMs{s}SoeZlo`ymLp% zjsP+)Blfc&^@Q!Wx?yYYUFyNM4Y&xll_!C1%hzq#+BFY=t(}VkY&Uwa&8LmNbRY5H z_go&lcn52b*eqMijp$iENAJm=ZxGuK4^wXc*>gG!_F%~j8%3EH7)3VkEajhgDC@2q zd!N)6Y?=b>FK<5@#URh7-<`{*&me|o*V)jfN_#t0YfCa!l6)2ZFT|;S&=BM$kLMoWA8DceQq;W9TLZPeEszgg+*)eQO{4IkVOF+DeCdnC+`zoKbv|C z$cqR0m!9^W_q*Zv3)7jqWH^2jINkm~fKykTExv?gKf+-b=OP;0+0rYpgRxCo*eBo>&jCjEuBQa%OTLF;V{oDPF{ZZl&&I6W* z{XTmiF}_sn;-=R``Fl+fEkvpXK3WKlp!$J`1OX>k;PpRq*ta zz&dmaux`c2wI0}ofL#dKX-|Sf_yQbU?XHR9+5AOUXz!|1n+L)FA9FhDGH(U$JB$_R zMK*6Z;}gQB*oDnI(DND0MdBQmC(PlHe&+C#!1VXD^>OL^RAKu1Qef)uJBB`113Cs+BWl?ZpR8`^45ie_TEl zzVj?J(ma?r_DRUaXYVR+O*dBEz`$(1O(PzN_G~{q-<{QDtjI7oZQhb<9NjB_CADkmZI-_vKKg?RQCRZZ+bMTu{r&jHse0lNh04Smf{iP zXoxx$g1d*$WeNB^pdWmG;X|z%om`gJulq_boK#-C_$GK21+S=|)EyUo{931GS=$oU z)rL6p+I;q%<%%s#wto~W(lpPJy`O|vH6+00wbHoy`DB)Mt8n1He$D}k@1Q1ebralaLes{-)_9{ z{IV6UZ5pKBfoGfUhhL<^W90XkOg$Odr)OiA52l?-;D~6cjC+gCp+}CKxg}?Nu@SwK zd#@6U1TR~7&huwEInC}EH=>`7`0ra^m}1vIFpp@x?q_DWsgp78Wo`9mb`1pc$P&8- zLTXGgTW?T@;7xLwzsdK0ihVAh#+Dwr%(0p3ulh6?`}ASr34`PBwM84y-Q^8o;Zpxji zHZ`BJ$9_ru8qruIHpy7VqCQlTU$PK;MEMMs-yk<8BUb-h$?k@Ii+AhYnm+832zH75 zFtSVPp_OXvl89@U=z5}Ef-Pj*CH3SZs1Kg+&8r{S!a9e6&!>hyB*wHh^W8U~@3JrT zj-%AxoRTNZ=|CtV9Ie|~4YE0mV@W68SP_f)SR4jK76^lJof`(!HHa%REZm&Kh zen04a|C-+4PmWnN-EZ}^_v}9WWXWp_-gos)--Z3(KZpHN?00S7vx6tzyZ!a~8P>nc zYW`<^p3_I4di#M*_Ttk%AX-ClYUQOs69YKTe!|#PfiE+UbJ0u-HUBHvSJ9h$&+j{) z`=N_EvQ?|4uljK4N(T~cE#bHL8*!!!-(~H?i~9~5`1-V0H212ZbJ@=|$n=VVNps2? zpADb;`i4(FeXYme>#cWQ1Kns0U*!5;<(dTx?Dfi@5qqoamvVh6-;iBUVzxh)Y^xRU zjBGXe_tY2REO839o%n0^-z0XU#~#pXN3!lS|6pBxAhy1baa}WNWF@qdjGOj28hVc- z+uavA+ogkS7dNwAI>>hEAR9W69j-Z;h-_6XNCh;w$Cq8}<>Q&?5Utb$eVTQY5OQ}G zayJOwuy;#UY619@RhNdPM(>OZnLii^=1_f1S^FemAcp+g{5^ShF$kC0k*B z*L1CCAIP6MH2UWFjym-%aXrMNIr&QKjl4oDtUPPx9X_i3WYxl|=UdvtQ`f^BpV8Hh zL$`@*JAJid^Df%4#LL`vw$cvzx)bkJd3N+ay4p-wGxGP9mu%@_Xsg!>A?CO=0Ns?Vgn=i+0=JkKab-Ye&nP+QUm|0W(_=2 zQr%>cH*2Eb{EoTM`l$Lp;7sC#xWAtJ26fwl$Z*<6k8t|q@z%wI;f2uAVvE>D^d{Ak z52&6*%~{dl5cZ5OCVrB>BtB=?h0TG-62GE4DfxL%Z#Mnyk2g>oZz#|9YJ=R#M47H{ zhsJkgFEQI6ucV!)S&uG5E~~u~bA3+ov$vl~{SxK!vZseB{_5`Ov7hywJ-Y713&rE6eZgP@F!1<}@UD_r2IAIwMHA&{{kn1O>F_?y zkLE@CW*%*v1xylfwm>-A44+6pBOJe%|0OPL4)uYbXC~n1SAk6{a93Z~1EUSVD7b23 zd#Q`3BW*l#zWsUHiVXX1d42IV+fN|;_@>}P{QjW{;w{90&`tx_96f^dnyC)_>eqcu zc5x56vCY<<%kaghmW^59 zBU~@G+8st$MI9^8%Pev*9(>jQHBH{jUh8S9@)| z>fR^Ct3$Byt1fOO@UNv;QO~Ve`4YY3CKBQ8&Fy3t-Vyv#!ZBUXlaES$*E)g=Z^1eU ztOKLie;$534_M5Cey=b$~hgTn>4(fAfIOAq6dW`!!{fzqrW6-z<(~tUu`527- z)8BmjW94ypQ%`x5JYH|Tq^~hga>pD@81o)*NMjDVW7c@{8Lx27V$6xMWHz#7Q$ORJ z*UvbA^$EtgtFLkPL0idmr8tF%%U=!rq6u=SK(vL;-IxJiRsBzUp7HZ~^Sqw-G_TJR z>$zl50*!&c5#(uK`2TChoQ(f}MNd)N$#~%J2R^rc0{C15d`^X4e)W~!yx4a&`uT6D z7b+f|L`P3^y|+&2#Nc>%Wx=mAp_yawNyV#*U$?saTG#L6*va=qmOwY(0B>~ud;YiD zx}S1G*+&C@J`CBVd(U!j5BCmr>3*5E?l*}04xjC*`<;w;_h%n`?R2lLp7K0#Tpo|; zuZ`LXAHR*0$dDc8_b-e;k^Ax6>aRcNoI5HmR-usjR-7IZcw)2edz; z}#Q_Eyz1(vueV(br`9k^eZc zzn-o$)$Ok(zpHDPJSsVqpeIJ|!TudEyzu_ZiW>wsKi_j`DBX>5&WwGQYmxD;Zp~PT z<2$hFV2-_a;dpFw`#t))it)8VYqEnfzY}f)fpI%{jXZDyHA9C-L(CGq=CvV<~XUUEoe8}Ii1XjRYaF zzh+KH9l5O0)?w96Ylxp*PX3Yb|MNZik`ZgBFPb;Te;{!`a;^UeANtKTnC)XPNgsRm z`l(HkLXC<6tnUuQrWk}hWf3#(iz@C~a8?|WY6ZNmI1rArkfEog7$cUfx$R!Qeee*u z!kP!g{S4z?oa0xJDPy4DAN-%3C@~60-(`-V>xFp>jOd5$$L)N99AY?fQj9pT->viQ>Smtk>lh;BBvIQiJR$41A}fyLtGMKhXtUv|3_+cY``?UbMX zhJynJe#uwe?|^crkLHTzTw~#S>O^i>R*By*wKfm>rEYR#y$^kan9O&f#rvpj z?VOL9`WEMzUFTz`ss{Xh9p3%ispRWtXLg;RHuZJgKknQQY-Qbtz2mjV<1zMtaoQX; z^-rAN&~=_U^$)uLfpI=Hxbv|w?xi{Iz;@wNT7B&L@;6(!e=GB=yb|FLamn^KuRg_ZlDE3>`^P(XndI>acQ!6EH_cD_p7f8` z@Kaa3@|pIUqdXAOcr&79?V?3voN)`Y&BP0X_Rh*$q3!Zq?o58SzTjJ;M{%RY%7 zUmp!$za**c>?GK>lEY8_UYc}d;t5!Hx(y#?dMx15*Nne+HdaD&(kWQ~8$o@~rmAJZ zc8j?pPjbZj$1K4XSx;wE78Y0l}}2;_NF0msUjJdOYO!NoIXffx1AD1s>?PTdck zugAZXNRzYpmheU2`hd7N&8=us-xBQQw~bR1MDI4xmgHcZ+95h`W^Gcu24kQ?x!n^uRpBV;WCm9W;Y? z)~K!(y!u`LX%kg@ed~9_?dF9Yb;O$--3Ff)?_FTq;phvrE7^e^zOmasoE}@mb@6)5 zZ^!D7&IR`R+43dj5gQv1QUg6t>w?a@>BbUw-8B8z-TpKS`;2_ycpkFGoWdI1m%>d~ zFy|wYhsp9nGQ?Yt)q9n|OmrciuXw!e`|Z;Q&wA|OJ+HJnGWdYifNbNvL&!tz6@SZS zL*qYS6@4a}%U(PwqrxA^&u|svAeQi8Dc^-}G=+efHJ|UoAN2k_a7F%W;mWnlx#XxD zr?Am*;}j}c%jNm5_!j({Cyj3@1i!ub7U}jEx_zj}$Dls&JEeG2&!4}T8jD(oJ6YVR z#aMGsCoe>`Ppa2Z)?qEb8h+I?L%;wUh=*8bT<;rI__S|hVZeWR;aq=vVU>|rV42_E zi7g#hytiyU=59xz=9;2etOIUi%sMx=U#Pk)N46njWgklRYtAfeLA|@aqmFjg*zL^i zSj_RQ_Hm%2ljGa$VzyWO9fvx_Q-?J~{{L^mv8EyVuD0Q(#8NXKVlhVA#N z=~o5#ZjobVQ5zB2*SKUUIuUx&K45z~eNjy!={Y8S6RuU>ZLEs0hOU~>mG^KAzkPK5*t;RToqX5rMu1qTwCeMPqphc*xBI0N zXm6JE*uOk_!sa)^bGvpL>-y5S-M*EN9Zt?Nx_x3ECf~Mw?;qKRz5b8Yhg(n5hw;GU z_^|%_V8OFJyTaz#@XHjcIt zyCMCwLD!C+8=XjeQ5rc^w1>T4n=|pk1)QJObsoBJPE!>y4w!+dT0h{u9ieASno`a5 zskFBuz;|L?E89CQT6#k`UPgZ+^k)Nd>0z!f|NiG`&zNo>hc0RwTv1&aeqJ0t1Z6YG!MP(Y;)Mu$>#8>i_bPzOdfG}&f+P?itXmzIU#ez)HU$qhMM&` zlgyD*bv?wm1*?!bZt4c|Ez548hMHwGZm0j-mz)=UnB4g7jBAZK;i55j=B*4cyUa0BlgMBs&CF1o;sq|@a4XAmm4=Rg!d)ywjXk8v2TBV@y+5l z+aFnM#|~~^vAF$C>St}Cy?W^JIrQ4+=*MqYU2W%-4(9!zP$Q<6njp)GMVrT-8tPw# zIpJSTe#WT$aqN9@BwSPaLD0;aY5|vknK{)Qe)c#cFMnKUZzKm=+i2BHj;1mnlgy!0 z7Y{I|2xh6knjA43S9ETz0Jo`AKhd;^^D>U+fg8f{ME}&Iz@c*Kh1BWFD-`e0SY957 zZ+KC0lQAM?ftMqu_hwNu*rs##AV}F(d`s&ew&WzcKKF#&Oo;kT}~>urinPWzlmJUB}?j@NE-hj`| zy={G}4_p&1tL< zS%&IQ_lV2$WK46Cj8U9&-3hbdKH}bPg4SA*kJb?4P^te=fqo-Cu?-k)VNZ`O?p|h* zTc~-0T&h0>x%49a>`N}O9`Ld9d$yE0-;5j|RQSH+wS4O6!S;HYs|R1Y??hb%bJODA zdY$GRzha|&=Fo=rePCSSdo(WLHvR|V1+t@gt15Tfyp#9z@6B~szC!uirJDl(0Pj!a zebvRN1ZMl8wdHMNqQG_dHC4-I+!+{BTW#jP6uK>CLC73CH3ST2-{`v)ziuKUGG>moOOKpa~;{<@wsnyA2+9;=(=80b|PEfL6g7uest#cxdPy7v6y8C}PZEWWwxc*Ww~UB^ei#{PLu8;P{1{qico)jZ%PU#@f` z57rgz@dB+K(0*|fxPJ_MWh~{|FHXEs$BDGrU%P`7+O?QNwJX|Fn=Pl%rrkI6Y{9_# zVQ~YopAU!8p+z$$*RH>cdXu!DOI=Kp>lV+#8|=DcRjg^XA%ARH^`pG z3((~cSlbfxQ|%=-$lPxCq4>OL0Gk~hY!_{6U!+F+`qT6E{*+cfb!dT`eQnzu?NPbQ zKK{|1cqw*;bV1ox@T?~3xw>cm`a-)dfghYW{l^`3FHl>|e&?>Trb-<R4 zOz}mn+^bIFgO6UyIvqSPG7y@Dme<06Uc(N#fHrn4ba>>w+?S6y@w@gq9K1pJAUQN^ zh;dyWv^dppjS1^i$V|n;?kF2QwpU$a#i(oFPmAxrrhnF}4(XruD*37vkN%(fXH87L>b~@% zMEP+Loc8F`$2d!nxq_=@{IGlr{=CxB?CD@QdP(ajW4h!(1AVUmR+)^aKEWSr;rq4w zeRl7Dr_%4mX2y{T_-x1XO|Sh{@|zk*I&xm?GvcEoUU&2=10C(RZa!oNe#?5+jJkYL z?mRBF=P}cs$Imj4H_$KHjqzFRk;->s_)7KNE3oIKqsH)+>ijk6tPQ`qo+Zxop!Nxw zz&e)VC~R4kFdyUwIpZqsdtAk87eA`nU5ek-j%Vh3HY`^4en+v?BlQlxR=u+i7*J0& zTRA8?_v1JIBj=CPcZ=s9r_Yi%kMj+U;c?!R3{Wg~r;qpP-~GPq{mxogR%|>l+z$-p z&uhW{*-Gs7UVN+D7s3;nudZAt>QHJeD0J7@Ub#+@>&7mqe>-;u%6&#EIHrE2f@A7O zDmbQoq=IAWhx$DUTWyVP*V;b$jdy-wZ1gVlv%9O7&)6OqTDukfEUjhnjKQ=e-9q-1 z_Mb>Qu=wV|v@8E$+K$D$2h;X)bMQR1pZ2ZA?SuIS=ld69?<|Eb1Y7vHV7syG%HFV5 z4uxbp^jR)ndE-GRUc7OqldIKuos+B8`1ZJVJ)`-~9ALzU^Q`bd<2+g3h4Axhe`O6( z^vtp3a*+2EbLH9>JGpYnYvdKsmUI9EI;%j}uw1&bZ6U^1NxiCE)3+=P?uaITzQ3cc z4PW*wa3bZv!Vk{Ub$o-~w?p_Eh>BAKLmn7S|A+{d_Nj|d)1cz-ePz(=R8{Hzhz^Dptd+6;1> zwJY+9`lENhz;gz?RP9?P^(pyArTfkyXr`I>XlJ8w^~~sA{_TAn&s@V%u-i-$P z347Pqp0T$4H{SKc=efR@>wWo_4-YcPyEPgQqbEr&*gkURa+h=k&vru>sR2K=Uug5) zB{N#l)9(PzLEtMKyPe}^=0keB{IL<~ySK-x>#%`-+OL=p- zakFqGqS^z2Vq!uazp?o4H{Co_<^02U4;ZVQbzI3+#j{JNS&mG-4}M$kjUD@p`LXR* z=_ko{t9XTMNxr%9M;p_lhgq`_&&sDQwU@&0Z@}W!-%w9X_`*8S8uY=#=!5SzJ2HAN zbL`oPvUx0IvTZZF{sid;mB3VXQ_&$k>)1~%!i(mqUGrb^Wn!k8=VTfUGrzX17L5YK z9yH3BA`ga(O4+A&By*zmB+V=Hk}=dh*F4$0(LL8(bH9h*+oL~QUrE4$A>e?rf7i_XSAUHBy9jxfPctXUa1;zh>rb+WX&$s)4V~9P_mW9pO&HHG#^d$RVr|Lm z-(K)2xo$~f|F$OdPw>`UXdbG8XJ2`O>SyBGgEt5L(fL~9O*~$`NBVsNuP(-xP%MlG z_i2n%xZD$u$q51W`lj-pN_gg0U=aousxL_VlfAZ-11tn9AG%cOTit8h&7O?g)?3E; zT^YBc+~EsaH~uEa9%egQZ;bC(cg`l~%O~CK;Dvnqw1dW`tplZp zWyI>vcVKWC_?p2z)td(f8;!4IM$t8?A)FcAJGr`i?=Sl2xg|DDIJ5B-OSCmA-L zyiJ{3_~^8?C)T`B0o{lX=0Q7ZHw8E%7ntAPW0dXG({?fJ_WnY9VcL@%mAsZLeavm| z7q52K$qqMYQ}u-M_*YDPV!w;I)|0QK$8Mz0YOe*DsJ(AKL;v`O+EEQ^>kMLc@aM~x zuR>nGkxo5%{1?_E8#dc@^N`z#_L9c-EPWBol(*S7!Mvug+|ABmMw9H9(lo2Ui%GM> z72`y!S@3KY&l{UPd4xV%fxqx##`7NiYT@@1I3V9{g)JYmV=r=^C?DTLK6bkDvGpYK z@!!a$l$=#OMZM(XZH|1DeRLSPD<1q1dA2tqAG`X4eB6T$=lOM$_iCO7E=K5kz}&Ma zblcbkp#ixgt=oJHtN~+3%1^xwdea)OUHS#Q>QRj z3z@6rHpT-FwUa?R74DpicZ+uR|K+3SDv>wXJ^-J^9(!85?ZYO4c9y{3@{vo6(ofV? zL)*kMHA*M11gFH?f66z7FOnM$o+SEqs>|;sHV<8<=>urm)7fm>Cy|fx9qEet&W_VtS|+k~HAlU$QW@7ALq$TwuKJMeBk`oVi{9=Yte5d6u*`2c;BoROSR z-!7+bvO%@(9lXhA-jEY(PxcCCNnRR%{7YA!*}!^ z`q+nykt1Zc_ zZcEM^bz5?t^=%Bk`hg~~C~*s+kgIO}Ae^ zVcmKQ{n>`?sMv34-OdetPWAhg;O5II-MD#!aFsDRF?>!PwZY(r__A=2_oiTde@u1YC%aW}>!#Ii-0=Jr9&YH}mC$?EKo>twbnf!t{Wjx2>b8k~ zv`e&o$bHV>{5{Tl!SPCJOSy1VF1_HWTzbilzGQr5Qhf%SKIy+;YwKyEPsS@+&zylC z?dpeitp?~LES?Q4_EPJLxFg%fboG~m|M{%3=`gjF^T6riTf_G(1@F;I4i;o(fttI=@BzNM)5(e4L*1&|-*kMXUVTb*ksV%r%B-+a zl$8)0+)95`pYloky27IyfSZGB+9x?H_P`~oPgyWyGT-rhiP8_Y|D`)Nu+sfrCUP{N z=k)w|{^eg(TbnL6Hz`M@t7ZlA*`U5)0qxXa3y(dokNOnALEpKT??|V57r1(USmC1Y zx5lP%gOEYMS-y$SxqZlTWcd!yCw#qP+OTu$j}H3SFqjUkZ=b_lkL5ZC1js-T*E-_ISkK zIIm_dmP}?}_N&58kDK`4&?(!wz62g8n%aiEsy@*(`cl=6?~c zrQlT}Trc3f`eVTLYryrElfjiWwSM7R23*IvaCK$mDZ_Ola9tX18q^0|8$>4`1=rPU z%m3Phy**nwDgJBv%SY+KhqKWw(6^+|wEEaf&ahG|i3hXwE$}FksEgsFm;a^`UzufE z1&W`UbCJ18c55^AyPk91V}I7Li)N7MSvd%V-auM)bG@5r$OGGpK1 z9J_6Y=hu7;yX{HWZmaLM+YTiAHD~s?asj&3w0A$uF8DJvr#eur=-bqKOkHRUFKGRsbGr56pu$}3MFNe#|C-rg zw?#YZhjgK=ndk*>`#yXVuOU00nWeS+G1rYs*^;x+9A3aaPjSgD@45Ek6L$UqV=d75 zMgWIQe`eY*=!5K+2mXZr8y@lV&zqY{`3B=^YR&E~lZ%-L@wdKW_}eDTz6PAOV?>{% zold{-+XsI8$+t=&-)aE)R_v?2nw%@v*1dI4TZd27|H0p4)&{+~{Jk}*FdMv`LBG8D zOoX{ISGr za~J})d4AR&I3N0%4qHa%fkS&{5M<8v%i!Uw;J3l2@bQ{PL1={6s@9$B zbApptKS(F;hWcukfJ1rdMm%^9e1tyFqMeoTZ00627NcFQrRZ7tQfCk+R-jl5;-rJ; z;74fBE|8v=41;%5h8I#)qEUTsp-+kTGZ<4IJVN71#K|v$lO7I=jx1mt!DdW+SMWVX zOqkj|lJs44Le`pkKCf%7=&TcIPqHP%|1tQwH+NcxN$i7kjOhveXm;(}O5l*(k6pIT zlo^|nG`C-8ZZ)5p+g#>WvROPPd2X}lo5o>~Bc-uwE#Kh3WA*32%{k!ch2ZK1A2scue(JXes`S(pGYw}LInODVQuRG?TB}4U(&~+-%9CV#Ku%I&k=N% zR`Q;{$GK$wx>q{uA{sk-jp9?#BOa-tf1*DfBmWovfxgNgC7f)c?Rw7MkVn=zvTE|G#1uWpWrxoJ)_f~_Izc6U1HZV2KLA~CsvVv+a8mi2aM!93E-!! zg5Rp1Bxb5H?+PRO%6-Nr$?a-j?Oku$%D&qRuwhDkQN?|P z>HjpwqCc-ebXNE#ao%{V9$;cu(Ls5#IgbzE=wn ze_rD@@b3`yV}B2RRlB$d?iMP>U=w?O<+FCRi|0Z!M_q(I{RlF4A-;&H@OYwe)xR#* zHEi<$xUE{EZ;)@;HVmGt{_{>1?aFtdz6N+c0I%1%bOheHr)`iC&tk3%i7PTLWlZGT z$}ieF+rXYPjvn?$HZw=efpPRTjzu4@BWG`6?gi(YS<72N0RPe#kt0{hH*hR89!w_np4r!!yId_J$g!} znTIrX@Iv-TGM&SNYcsJ2C)(>bT(2B&%h025QX9rwQ#k~mI1FyRHUZrcy!y@-V-pL68#AS!;w#abl=Q)W zd!;+abO||xS`RVYobRoS^#;aVk4+99709-4aMzy?!RwRPck3szPaAVC{Uiu}Z)I-l zssEbEoE9X&>%3m@>f1jPa^WUjgp(l-*i?irAJ?1!HIExIb$IoxaMQ~qqT{U!eJN#x>U<~_^a?Qct^wy{F zX#p>iYxdcEeIxk;#IQsZkG*6NxpF1jTkh~}HwMh+-_L)TfAaSfAAN_pU9tMD$e$Kq zqjid(XQDHG3Vo0m?aEKFm(2Juv1L8$<1sHIwchZ*@%_HY)uzztM{X3}IExr5a#C2Jx2%|_37-ht+r;iaAMiF2XQ6KRinBaGW z{^XkJM;ho`M6njB)#VlA0}sgl0d5DA``WvY#6X{7|B~l$USBYD>o!)qT{ivy5I+9C`gDeqXHd!f!B^tlaOz{u{Jr-> z@nq{ig9iV^W*3PxXwyK*!7ud>zJX5y**>nXzV_a8rF6T^c_RITqp>+IUoL9CrCq zDs2SWKV2~f@TGFCky^Z?<4WF>j5JPnayR_3YZP;k#DTgnwP;;Yrcx=hWN_-|k1HQsDYw({cXV2vR-P_o&m}`G|(W}+l zt!wqB6>LKEb@ZB}8D`C94+kq1@gJX1*b=4eG(?fd7 zy0tTH8Hc@mXx+M*=svctLebqGe6k8TtaYxX+VlLM_??@QzO(YBl7=m;)1q%KB&s@^ z^Eanvm$$bS^5ZRUoi-zE6lL^Wr`_SL(_)J+yl=R4^eorL%!o~ytNpH~&G=%PvFlsl z&NgiFp#Pr-vkMqY)%XiSo_;5I$(C3uy?{OGh_xmDkQ%ubJYy2E+CF2t*3h%)i~Psn zupN&(mw)v$o0yb295ZC{Y}|e4SQi$avi=r5kM{7bl;2NVdpbI&Sw?zVhP%fdaI)d| z3^^CVnM`V!$*+1c_+eXOlWR?zTG^%MrYGrx)>qE3`zO3`@{HbpsXNbTr`vvp*S1^p zhG2!pR_wcG?7Oy;#7?(jyS6~1vRzj(*Dcttt?u3>x}Ion!f$O~dMmb__F<}m4ia+! zx53l3CepFGyEbt9Vroy%_S?ST8hfohE7tTmb5kLEJ&7-`%2O8s>@-;F8h0@ZSS^Epf)Zw6oN%1@k)I9�cl`6adu8Z7d~ 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
jMVoMvjUyV2q?3ML1{)2HI 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
<}u(3y~*;KhcRIAdnay@R_y_ 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

rqI+JDK$6Ln0$XoG?{j z4IHk^J<8cb;w_l%jPy7?hsXQVlN5Gv3q=IW6^(#DJkCk7iuSVT9_TIXVo#Oai+gS# zP4zLK5vYJAY)TI;Xdc842wYx(@7V}rn1f1F*C*g6G!a(%8T*=6#=O`V-vIXf$Wsu63B<{OObrhoI02`?BIS~b)+vce z9obaa`mvYUppuCbLSRd#qoKeaZ)iIq8s|h?3A+W8N9c;00!I^Q0~d!Cz_c>hAsCHV zbHz~)OmT|6$jU&WpC}Gkkm`vtD{Ou@6d8B7VCniMg+xx1tq5(p-rbYi&z$}=6Quv ziDfq`1#F^n!gOv=Acm?pfs77s2IYwy(y-y>725*h7ybg+&*bt9#p?d<05AnKT7#Ny4SZu={4QvCA{pPZG^9D|fS2JtE4?)`^vqQ?np5IVU zuX^2|jiHJUO*RW25QL(3;P06@)$2A&hOG-Vezq^v?>L@S^O4tm3}-9adu1AUs{ zq$!rW=nUv{>{Q+ov}2XN>?V~I!AorD_`(xYXK}$KDC`L#WnxxF^00Li+WE&h8&pte z^@}sU!5H)Pe$BEdBXkTCV(_xEpeYN*=xG@_7Ln%=D1(k|$j*_9`FC^AoPndd? zB_=w!rKK<|cLtQR>T2#t4`4)Z7YZLn5VElg*J=n4=vS9P?qNo1ik?J|onaVj!IZ2$ zPbISvQMX{C$Z5T5rdU^hXZnx>xD{R{WSB6QRdJTx`L}!Avd3Q}zV0%4-r4X^^z##3 zHrDfCLZM;~2xgkqgoS#~{vc8j<&eFQZGiAbD2ybb^%logYso;7Sqoqh9o_(dH#zi& zC;g5bLJ%x@Su`*;C)le67`-qfC*17gjHJdxixVbNz(v6;4^m)Nod;eSP%|HLXZ+0B zXA1`##zHMDh8@irtk!B&U2&E?N(1~Qbbdi@J)xxLlH@b^6!O_&(yC(Iha9)>L{KPf zL4^JB$BO^MM{uDEvP)hTZitww62o1Y4Ag>M|kH!CWvjDoW#+2Sqv?p$Rv#9Ah)n5hREEiHc%`l z>wwZRO1~N6xka+DYW$<+4DwSUvIeF))?P7;GKXjP5rxgg>KdXXVliLsuoof=iS;r_ zsKO%^AR9s<@J}P}05g_h(t;542NEKzw0}w&WVXtMc`K6&u~=+mg)#?S0dOiSB(h2$tiKGEwG6NWg1AGix;TPU~! zSSSPs5A(C|8#-!w_Dfesm{qkWsz3pAPPW(!iOCuxbI5pGXVovdUn}bu_fP@&yFZc!?vh zpplg=xv23HDKthni*=MkknpUv&}5jPF1et_z(+X&L9}-Dun)29rGdzMeP%`Ioh^@ ziw!nE(ZGd{cBLv}HCUQkg0)Hedjh!&_7_F&WEB$KFu83)$Q_we+4>CNjYDHdGn8IU zZ*jAGW-~;Se2Q2wP!kb?nFwS@pDRwl6$uE7)wNcbo$e+ocL`BszfOcyCv4*MrrbAx zul%bWQo2(w&M1ZREI``u4vzS2ia388oit3DS>(RALS#)bA^Z5!6xe_-|!FEG-^Uw%2$ET z1rf8olx=`)GGU1%mM&*P6Di6O8d9t^(VUx@X0c_L3#=kZcq1xdp99r0IIAc68Y1iV z9&gw<0&!By!VP&YHBC2Vh0rDE56ESkoj##i!UPRZOh~Q8elq#H_KW2M3)XbC& zsuwq3_E0CvMaA&prCl=tgplgrz-tBT?{bU2Zw%^wID11omWi3d*w zWr+C@0by%8?8}7{tm2Q*)JAStMb@&(+zTra>&>u{7n(y%EPVXtJkcX00XxrGVs>>d ztgy*X>OgPSxijBBw9DQ+Ua<&Xz|v}-B2R4-9%`0+#S29H>9ie^5!eb6s%@vTVJTz2 zI#LW&9;dIJh`z(p1KvS}#J9V!xN)s3JanXu#pkE(Jf3IN;Uak~2vIZ{u*AX8RpkoL zc0AVMp+yElIKm#MfsD*%sq-I?nGP4hW)=*e$PtxrhL~75k{ixp9O*o|HteMoQ48@j zR5iweGYeD#&=86AK(hEriq;2C)7}sIKj5Wc7X!u=tDhnH5nEu?<_$U98IiN7$qX_L zcvn$$8euSd?mY44NAMuw2)Xsi&Cqa)(PgOQG7Z5wm`pJ9I$or&(NFA&&d4AkM+n2n zV=j1gacIE+5!C)?eTZU6z!_)4r$$7tKIAndam)hjR1$T{e>gmuKQB1x3bRFU`heWo zBev-{_D10o)jNP$Yv{m)?6U!bhOX@&MH2E94M${g4?Dp4`ihv;wj=3|fjS8V>Q|IE zRG~YB{eJK$PO+4^2wBxpvW~sXr8C7^q21RJHXU4>lGB3NB@S-Ib#e$!Mh9~-MReeW z1qGMg;Wc@E;w1K)F>4?zPLRr@+SJ}EkVGDWBA1e~wd%2q{$VDgwc5TA;V|W+Ne!pno5wvC_9RzfT&J=!8N48ZRR*=xegfR~u zLxsoPsOaL5Dj0vjE-h(2+8)Hi@b??+@h${~{(`h-@fl?iEoe`8lu(43;C(RF8E3d>P0YTDV~Aiw2%KFR!AISW z!NzG>>VTY(e1tZ5q7ZRIm6)3ry&+ID_T|yEz;?m+l*@O{p-ZOD>=z^Y@I@8zMxESX z7_zZX>>jTW2NOVbj}k+4Ep?s3i63AU+!hGya_}CvN(8nFg3|z>qP|5OY+l4VIz|$u zhckq*H)6inz5%CJgJ-}M4sdv4pPooi?#0*}-^7fSH}#r`$eFfAsEg_qOGslLoG2J- zNufCx524u&+x;2GNi8T_v``#yJeXLryDB28vytTpOj(dByI@NWQ$I6AM>k?uU_#Yf zP5Y=6WF-Xkg0;?2kLXbJ!{3$R&HDMh=F7@khPiY;tNcVHLmZ?S+a*1r7_X_XflF*Fa=@atlJoP><76>@lj~0)7NvftB$kJ6Xp>-xI2vG|d56iU~s8 zX$=dgP)Wn`D({Gs^`d80zR?$a0aeY++XKyLtKQ<(K{4G0(uNQDf;v=+P?}3Wq~TxXfGzpQ|ZKq8fMNC`(HuX+LQ>!3Qj$XlG5e@c~%w|bwDR{ z6QVn?Avlsbb|!2K(y6P=gyd!52H!R(r8U03Y#RyT7=*cx;TJ&2%VBT1?Kg{dk(@N zlvB}D#&YQ=+<8XMjHpE@G$`=~JE%b>0|JSRD~{wS$cUCnZ)b`r7fCWt@M*z$<YieK0a&GX#ZHS*5JEi%BxP&D-A(M8MY-L+kYICfbPr%1KG73 z{JgN7U|#eKP6Ni$lf5&Sz%Mve2MS9qBZwK?`EpN%YGQP46kCe~AF;JYI{G1NHmX{~ z7Ak_WB%oKZXIZ?8CpZ2KR#!0ol?s5*L?)^LakjN+=!Pu+Fk9A=1o}Ys@dvUOAM#Nb z*kC9Hfoh=lE{Z~i{#f)EWR@h*aH~F&&2k^IMEcq=cVPDnwexU90#kz-9Qmrb%3pAg zKdyZ(8L0oIU#qd3(4a-hzSv}_ido0)J!!OUJkHJzykV~FKVBzI);;K=iF%4rLiah_ zN9(9sY1j79FzVeE(RBh{39%yFVXS_htz@w$MR)g>MBh}c3|2a6tkVvii?NNZ4;YKg z3QClS+kw0%+|LZ=TMnqkOcLivguLDvQX0-JjdG<`RKVGDVXz1X4i}spCm4ooTG7uP=HzG$MYmdRewrm4H|4|S7Tci|6uL*zu0<(Vol~f=GOOy0 zVy&CF`HfNewRQbSnby@x7!&Nr@E?7a+PDH!rQ$tY*jPAgpR@OYIhVy^8ae95%l&df zHg^cM%Cm|`XZz2aw@7YC6lsawh+=9~ivx?-C8xrtl#dGAhAIzq4(>g*6&dbg;;K@r zUlhHJ2PH4gUOON$hN5v z*+B)uY!VdSu3_4_4oU71_PT}-mOs|;p|U?$3Sw86xP|VNTU2~FcwTN`m$fLWTN7v9 zhkBSs zWPo`&G2Fr2%h6xZqk|(2SBD(T%83mbQfWuX9NNKxSP9MV*$z_r*W(;RJZIMgj$#*$ znalyK{mU(kf`}NtCDf6GEQ`o7#&0tFVXdDC9A(ZiD1uUgY<$GTsTi(`FM_CZCjN`E zREp!bLmR?ipUvL&Cg)OQ>l0jnhj%z3CVU|UUJn(AqT>p+=*5W71gPW4^<1bj8(ce< z?^@WK!Sx10IA$KEEyZ3HvEj8+&Pm}mOn6TlPBzs6#cF~$V^XX`CpPCK8Opunkg903 zRf4ehVLp5mK~G~tf$I-T=;f-Y6QB@F9Kt&5+}UCSgvcsBUBcC|VX{1vq^f5|AHV z5R*xA$PZaeID4Y=BR7^Ra}DPcC4X{?I+#1LF(yXNz}Ed@1nV^QDCUNB4?a$yD|C3O zAP}&OXS7kqu4*3NwjQM~+Y(_hP`nK*&+7p-0}FW?8qSVOgUbeo5PJBNbT;ydIs=MS z%Cq_-7R1BzAG!slQtX$B2(Qc=${_kljq(#Cs?JI;6jG?=RC%TB=jgXcG=p)`uX^_r99p>WS|6@m50Zw&%6c&(z$}yv*oKF!m5Rti=*p~ zh0&9$s`gt15tFSblxW?=2_w=A$Zavw5JmZkj~>8g|8lVnMZJ#ARDKuumSj()^@v;J_Qj9Yft?S(I%v;1QKmYNle<%c49TH!Cmep>W_&$&P=!e?615M*J z_$UX602?4G;H@-KXu&!FO0y5flY4gL^W*HTKyv#JRB71#BO;>9gqLIGi#(#J#4aFd zO^)%42p*&0qE;Bu7CMG6s?#tCe3!CvM9N? z0h|JSR2E)fDwM6Bc*z#uFOd#KXl&*zIGI6z^F~{`^8Db22T+CTD8c(rvVDO*n|2<; z)j{QhnN1iP>ccDG`*yHp8U*#><)@!873l`mNoEi|56UK+iExnuA zQSLY+Rh*V6juQ`={09Svmzq?8>yfddaDEh4$Z?0C@@L^|w&Cwd>P7@Lp^RE=n8r^n zfQ5=JPw>v!EEpQXzW!PB!I;5-abRf2SIMfgR`lt|@=Q&km(VP$t_)b4KCKo-0Z!6i zz<#tD6|$2R<9&Y7i&DKx6%(8s=m>OZ_br6Jp$45hdmfy>I8ea_6|Z*BiRMMx0?cFu zkDU_9mFyn=K_ao14)2Woxb-6Xf_?OwAPPj+nxWJR#mjN*ne>pcPd&q0pA6e_`0je} zgF|9drC7rk-E`;x*mf@4qu}Qxj+6fKSlPyi!cn#-r-l~nC!Z}CC&qN@G<^8J%Xvhe zgBJ1?0%f<#pM_e8OeAzE4`j5v*5MT?IGPb*gaJXKFxC=ZzLdwIg|L$~0a`^cU3g7k zHk^(%%&ftZ)k@EPk~f^$6~cXREWwNGlf1+-g%qxIUdU&aDu5Yyw<%ER;8avt^9og< zRKLTC$%Y-egK%OI3^BwJy<$HsB&^&G{1DxBr6cGCL_2}kSsmfyX2D5inUk1K7JLc3 zBqG~K;b6sq(uwm85jBE=7%Ms@4N7*n9Jr`rmJ%i+miz{+UX26Z>g+Y$6Be#Cyr~K) zE0I0H$I-Sr6(_&a7Q#WbEysiF#qSb`&x*+7420KK?TX?lJA?XM7~yTiC(gwex&|>{ zmo3jpm-|F?dnrf8@V$3+`16_bD%6NUxME}v-9mVgM|_{-qy_TJAL8T-(REU}(+vl< z33!0C-7gDo%y5zclqKJ(h&-!ZoIoXdy`h7z@Fr7fL$8^_-q(Q+wF}oF1eQ&=-BLw1 zj9Mz)3_-J_R*DI{mnZiY!Fx>tp^L4)r6Rs-IS)?=l`|)E{q~l#ppSy_@*-%qaROuk z5ifs+ys`-uT|W}F)sb|_$IU`R36qoGI#F35vX>6;A>i9|A&%n5`)1NDEb#9M8N)6(ObLp2#plEB3r8>!D&(o`WS@wkgF&Z)pP2&ZvctK5 zqx}aT>_7aB<0lbtfBJ)JXMzzxkw%Yr5`0%EB-*jF7l=WPUwk)7y+#UtCYY8wB!ABZ zWZ{_=A0#;#LT1l|#Y_1^eC+(-J!eA! z&4xxpe!W|;Qm^KOFc}YG<8x_8S`bkh@Z?B%T@voY`l=kT2?1cskq}=-B$N=60e{=V zUkUt)s|5ZG^~d2k-wpm;@CUcpNQHbiO86-Fy(9cl_-(+S3;ssJ-);uskRJEJ-|~Dy z8X%(ht2i~4G{8;qyME&i@W1*kf5iDu-KAO`(!ZYR* zx^O<>lNUiA78Blg9+vBi6J!Nbfenvu}gv z0_zFwx`Xii24bY$MR?X-gtP%K;Aw8c|L;teMHFQ9>VzN#9041VdOtxr(Y5}?JHuo zeNAXiKQS`DA*AgaVl3ZENF|}P4b&5$l&=N@tz?u|8fk};Ai;~qAHM885 z^t!20whg7LM^L_D1f`otQohzl&B`4qtxbnouoIaU znbMw9sL^*CW!6TV%v_A>?c?!ZmO-bX^l=eSOQ|i`IQoRle;u(`v*(*>c@VgT*@H!=H-vF83 zq@?yONc%0w;|9q8JCwNJh4Ofh8dY6T*zZ%)_dcbqn?TMFp)5awYVtAU=@V+@e?nQ~ zr<8Vo3cCD^8k>6ozo1t37nGKNLHRYeq7o(Z|f@Z5b`uj`6bX7^~Zn@zpyrnwHL(KLhS(Fp{?m z;|04gQtoHg3O{32do$XyFC&#X%xKt;(arlYkNW_4K7rA$3CvY~7&De0&QfxZV63Q^ z(Vi0+S#=__HlN5?=V{EGd87^#^F&(CB=KRDQ;vzf7BHY3w3ATJe+HdQc^ zeh#Bc7cyhoVn*EOGrIf&mePG8BlQY$+dOgT|JtM_`VMbXkBR#duC|=2E?@DI0u3~2Y9gJPHb$~vVtmy<8OeARbo?4) z-Rl`?e3KdN?=sT+9_ai7MmK!GSj{Fz`!_LC_YvdOJs`)&jOKm9jJ8if_r0KtUgpZ_ zW2B*v(Y`MMzh=DoYls6_+|R7(-vG~VK+f-&mG?cQ@jbJe0DV6&D}5_7nzjO;t;~WJ z)$B72TFwoUZy8pRW$+fuAY~qdP0coFU~j{eV1Y|x(143_?b!K;2SXg;{II?8D(d~PA?J-BayIP~ZZ>b`Jnu7ZWq!^{-+wr*?&D^|SKR9O zinG4&IVs%ADY!$n-Y|_SZnCr#lQ*ZBtUJx*Rok0v=`JSSu$xJ`{U)v5-6a0KOtWsZ z$x6nXoaCF^bx$&l=E)|_JkI19Cz@8lRMTuf$>d!pnb!1EOxk{mX)K>*vdTZ0uIWon zv*B8k^j>GO#(Pbp_db*O?l;Z02TZH~0h5+AnXLX%(=2N-X>W_ki~eDfiZv!JdeS7D zpEPOVTGQ%VYm&Bgrjh!rNlV*ItGdl3lb<)O$uF43<`+#uUNd>cYo^ilrb*H^m^A$z zll8u5lFp5gr;VoB@}bE#d<^;e#IzQEYSQMtS1d-K0dlKvmlX#CnFjo+A* zd~aGM-PSTw*bW0l)t=_wYiOSMSVHkOe!(xTlXE!McRMSQzjM(=JG?ekm4 z(oBmL?rRw(<1Aivpha^IvQnDITcmD+MOWurJo8YC6%<-@OQFT49&L>*KgJ>jlPuae z$s&!%0k7jNQdVryj1w$Ueu71PCt6n7iI$mpvPD}@v5cD2ERywm%cwfTvZj|n+#v2Fs|w%VLvPTSnu37GKc_={8v=f7s&b4}-i9TQt4dV%aU0yL*jAT3R9R zFIpu3C5umg$)cNIvRG{=gn!$zroC^<9hkHd$oyhZZaQ*dl>XEY{O&ne|^l zp0-*vjks8g=`u51F1Fs|B55fuwl>v8eIs3F{wNpYeiv=^LzsPBq&n9{Qx9~Jj64^u z8}DNI6J1vOL>DPM+(nlcx(Gko#Y&EIk@8}oPjIokvmqQb1eNEx%+jSUT6BTSsJqlf zt5>+p=~uW&#Z@k|rpCqB*SJXT4KAa7rHi-S1ZmW{jOn+#h_Bvdq~Gl_H{9bgDj#sM z$&a~6>60#2*y*CHI$dVr8!obNy^HzZcDbrQbQ$%Zy4;nYxs2|Axkh&Ox>)&tphw^5 zGIGCmv7$^jZO?R*)qA=*-^*@hhZdx|kZL}A= zY3mfXx$0E6(LLSG^MCI)3Ql*MJ!iOi{~2yFeWsh2&vetOGu@u0XSB;nk;k%+lX`c;D|mG~)~~qO(0#W|@aApXZ^|=X+SkLXT_eVh^pV^pK7v z9>ag3hqW*F&=r63kgUr*X6*{Ncew}rlE>)1-s8%wh4gOp7`~MrQo73HYP`*3wA}6? z{q-K&c!$T;^>+`?yU*h-yWhi7AMzOKk9e5>A0D3b4-YGS#zSkK_3+it0>9@yWa%p& z+VqOY)%>}K_I~c+D|$Vy(yu&L=T{yhzu!YLzVUdozVom$>UCw0^jb9|y)JXUf0q+yw>Djdr8KgUbvE6t^7L_DTCl&@Req?KS03tRxkq~G z(jqT!FY>awQ@uv^>0X|9x|fufz_YWxByYCYS~1&e^!*Xin&UOeA}^U%>7|WJyk^No zUZZxImjo{L8ZDQ5N7Y{KCH2>MS=nD8-i=CRsWQi<~{AT zN}u+!+O=Lvp79d*vtH1f*Yv*tGPQ$@FMF-lm%Y5>WiPAl^wO?QubKP0mo&fb<$bSv zt<`UMN!L4Gn)9C5$ax=rzwhO|+iPWXdyR%oUaM!5m$ZN6HJbkIC2PO%()IuG`ilF! z?$)ooMt#4RE#2y+X)MJ|HB(5nl|sARDMn{X3hzoyF;R zm%n2Q$=)f275h`nEx%5&nle+2ioH^3%U&t2p8OP+eq0L4KR$&8E=)1{mZg|2m!z=T zKc!gw(iBqu=M-9Tc?wCpBE_h=GKJ(_l|p;3N-A0>_)tNcj8*VKe)Z$!WcAB)@g>mw2#Hxp}bkl7x z7QdZXjkgotdOIPFcM+O>H;kR{f${!o!m65xSq$Uj(lx|b(F*aNCa$zkVIE>LjE6sm zG3~#FY}D-Wz!=X8df*wIK(_VY$@E$fKnG zAZohvDa$&9l8QsAkv);R%8rDwXAw2ij-jlpn3CluQnTb#O43fFyl6T#D}E1S#WP?m zG=q|wQW(RXMNJrg(&9hBnCFkwT2)R-=3L4aoA}Uf${AHkTwji>jRYeFQ!K3 z3drxDVLbdejCCKUM$;289$Q0M35@kRo~N$P4=ByqMA_2KFt+`iT3!F9W)qCDI=-YX z|F@K7Z>4k^jG3w#GgouQ`bIFzH1ZZ#u%kvG2Z$WGnanPXwDDJY%vU0;WkL2$1tXO4YPVj!*K5e zef^za46rjyW@H$y;w-};V-3^i`3@#73yb)3N( z&M-(-iNSceVYV(a*z`*cy5b7MsJhaizN=u|a5aoEZZ*uw^#)73(;&qS23vZ+!3!QR zXv>2JTi*%eh}R8T`@Ugr>4Cib#~_Vg8Ege~w=-Rw7JE7AO69z38%`>><*aRYPS@`N zxtNo37;glYbK<^~r*>Y(x2w5= zkLbUeyP9e^+j1M{3vcJH4Gnyo;?;aq-UHmKcz_%E4|3Y^ASctBIIU~qX6r-Tv$UBT zjgLaU9))}X_C3x?cMCW3o`Ce%aI&J6)81BYc0I#M@3Wj0w8PltC2n+gaNgU&jow!{ z&w7;`d7YdWc5+hi8fWeAK)iQ3Tl*f+U7TgT&s|+T+-UiPGarn9az5jv`7>^`{R?#X zIk&QVVO-S9X~w@{JoE+UO{KmubV(#J{DSKQ$Jkniugn-h~(6O*)1lQuBZYBx+$ z%T1nU!g$9rX$^FFS6Qaf=QcU_nAUoaNlH>o+O&;nZQjPDbt3^s!dPZo7|V<@$@J|_ zt91vH_U~vCzLRNn>|`2QzcO8Xchf4|-Q?3@oYnbj7z6ETlK#C+zIil^uf~|XWQ=L8 z7z1OUY?DvUHm$O~OG#oMjqS zvrS$z+cZiR!1!(vsc1eq^1dG19fDO(0(xMilf_7ae1 zxykF6n`YmoCY$zWlWxA;+@|45GqwLJbDO4Xpn%ykIAy`H?7k9P0AmD@`Q0{E#T@0 zOp^JK$txc+N%13~r^igI;xUtKc?{Bi+~gY`H?7PT7{fkc8Z~Q7bK#SwvHU61tb}nX zf7T?G&zijbS<@(f&g6mTOg6Ox_`PBpTV65Esjou1uY#UlH)-+fP)4ttX7PH{3amGc z+_y~H{gz4E-v#}4nKW-Bl=nuHtoQ)*_kn3`=!UY|1Y^<6wC+aNGzk}45+zp+T?4;FV5msLw#yqvl`72L&JU1R~Ie3*+uGaanY_@T%@HA?%m}wo9=OWGVXP;6_2_|^W!eI1;%%)*F&6*F53OR zi)VJbSiwgSzQ^Ti`@&^ped#ibzHu4--@DjU2IDN#&8tl}4Onhh>vnFVDa~!=ZtwQA z@91W&hq_&*$GBPb@orLhf}2&G<2KhWcGI5o-F(9ZFb0G%Tl2+kmUp$=>a2E?@@pW@ zbud=D-p!WQy3K|g-8APWHz``>=F?WWS^sTrS4+K{RW`V{FMrIvgZl|LTh;1zZ+Oye zR6OOT)oa0Rt#i}vb&%FGZd&@B+bDYx#*8nzS|A=3*yY}#QSBjZR9Yn%;Zn{tm)I@iNW7I|PU(_=O+^{}>zl@S=M>23ZYx_`8QLZ}gD0_j|0g2RyvH$>ZsN!b9>~J-oivV@!YA!#kh$n1$;+ zylkC^6u|hb=UETwc^>lA4r8@ff$tj-{~bu@T@SB$*F&?rJfv`=hjwlBkj4)@DJwR4 zthP-aS7(ohb$G>Y@}0ai`&TgL%J9 zmu$EQ##t~XTpjRQWFBF3B#aMWie(hwZt&-VKQTyvWk7k+B_|SExhwUh?nd(9PCEQG z_kXfgT-~ssDX+p271M5}e`VJhHT-(>2J0`bTKA2fmEN0DR;6MP1-=0Emf`XR7@}N} z1X?l$1P>~o%aIDuFw-JwH=+j-hAwdV0x5`!k1wzd(jyR67JUIFk1wz--ro)p)?wlD z1z@ZsaeG9y!s-jaFi74Pa*J>~A?}O_%cOAm0%9Fl;4X-}BJPIhN8BB855!+1W+Lv1 zxEJDY5VH_RBaT5Fi0#94@E zA)bv`hBzDX4~Tz6EJvJ!I2UmqVg=$ki1QH_ATC5)gm^CEV#G?s^AMLHo{zW`@dCsP z5iddvATC3^81WLs<%oYmycF>=#1)8tM!X#H3dAbJD-o|kyc)3@@fyTy5wAn6LA)OE z2E@N0)*{}BxDxRu#8rrYMZ6jD7Q{NlTM=(VydAL~@eahlA>N7DfOr?;-H7)fu135U z@$ZQDAvPl3kN5!MgNRLt4lwjw@>_!Q#Ph-(qo zAwGlnEMgntbBNC)zJS<{_#)y!!(#Px`8BEE(AHsS`v zcM#u2d=Ie;v2@&y@XJTa5a%MEgLp3D1>KFGIWnu?F!kh*#~W(p`!4&4{-l-hsFp z@j=99#D5?@jrbg5J7NdotB9{7zKOU2aTDTZ#4jcKrS#%Ioxi5vs{FPfu0w1`d=*j4 ztHZsC_qz~3MBI$nhqx7yAE@}GAo>t@K+Hhg12GG6AH-b5@rVV8M<5=9I0dorV8yR( zcLvh>$Rwl-_fYhSNOvQBD$;4cR?lZ3?aNg3Sx6U2I>*+d{&DGe#c#!SD*Tm5^HGXk zg>(nfwx0bSu)0NcSV%jP&H~RrqyC*CYKB(qsqq{8glDkbW2GylvI<50K8- zQPCeGy%g!sk=~H5p7$f24fB{npIdc!q^$``{`E+2gLKu-D*X0H7ynApdmz0T>9I)r zU{8ohKNsn|T@`&0(#w%9K)P}_@jS9TeuvLbM?4F0F5*JO^AInV=!dk_rR}2!%l!+) zZxDY#Y~4%cPnTD2mZJF~N*`rNry^Z5T0Kugx?*od@1mdYqi9{;jYyBi=ej7=c&*RNk#0Iv$(@>~ z(rre%0O@}qy%6cAkZwS_4e2dNzlgM$7Z-B<6X{Z<-$1$+>9>(?NBTXa)5fd#A0S5K^~{+CF@z9gBS?~txTdMnbMNE-!8A5-&Hco)*0NT(v5bFg~u zLwY&VX-Kyt{VSw>hpO;<>G(*GMYb{-i-7WNLLrC=hq^=@CZfUh;-qRioO}?DoID?Z{kFy-}K2U z{EV1_)Y|LjWjJPYZHxq^<&@0Iwx2Jt4u z+Ys+UY(#tn@d?Crh;4{3Aijk78sbRI=X#{yL;Mi&GsHf`?-1D$N*)iQ4{=AtT@m+0 z%tqWFaXjKg#3IDwB}VGwWO=^>boJw2zf*ifq5N|Fh#Hu#%J~;PJ9K=8)QivUxcv+H ziw1tt!2cr+oO_gNUoS?iLaar+4e=htCd4NY*CD=$_!{Edh&_nC68*MjPcBk?D`3yQ z=*Q^sWX5%hJ`JDu-XQ46cKYMVDt>083jaCM)kyatP3~9Ezd^bW>F<&5c|bj9$Ex%; zJg8_7(zQ*B_8~p>Aw{Pn?R!|!8Ax{^?MJ%m5%qj8q_;>qQXW0ObK1gmsgD&`?<8^k zJqmAbRk-pGDxdX;_aVOdy1IXTxxzaT-D_36eDqja6{Thawi1e|Di!q&% z3q*LoN*w-wc6$~7eZ)7WsC4&wN8LYoo}x7#jWk`Q@^k2&>Ul9@9pYVxUm$*qIPVda z?&t;;kG!SQIT`6|5pP9&9`PH*jJs64!w}CytV4VQk>0Js?T&aL;vW&0AwGimJfe=z z?@{ry5pxk|BmN2TImCAmw_mN|Wg?!6xBzh_;u^#c5k2>+c>5zRLaar64)JxwZT_yp z?}j)P@hpkK@?G{xhV(|6@BapobgFcIE9qc@m{aYPeJg$$Wz-)aleF+0sy$gJ@FUo%2+4#-6$u_yU`sj;C>&3?J$LU;9>-&sT^W z5qCON`N=&FQ8>{r6sY78GM(z6&ylp^uVvP}1%7;hvXie73x2EUX^592)*wEM__D;v zd^b-N<>4c(h$Zj&C0$*$o5WhgfJDEo=PnH2gIJ02d{?P>>4<*BM}MdC`4VErsfzwJ z;uOR`AU=fnAtE_Vg+CSXe2F5j>N@pw^?VWH6Nu{&yAl6``0F!N_&JD|BEE!}UZS2) zKnx&WhWISvhlpclsPG3Nu0XsWaSh^Uh~FXZF;m6MLtKJ*9bzZq2E>drRrq}o&qKTv zu@&)S#Dhvz_=6D_A)b#|kN6zojipo!~@P!;g%s@i}*LhKE$zSt8j}D zTM%iPx<3-}48#S9Pa|$X96eiwKLYV|#77X`hQT*mtlW+!fM6Gb+gJ(CgPEZmm|J~*p2ADMTH-Ocsk-D#G4Tx zNBk6V-#QiVO2j)6-$vYw=(|;g+Z(YIaUP=XAKr{~8{+GT-y){prqbDclfuK0J{7SP zaXI4Uh;4{tZddURL7b1c9C0<`I>b*A$JeWP$05!@3?N>C_&DN|h$(ldc#{xMM7$I6 zUc~Pu2JPv@FI73dvQow0@PMLq`Oe=%wRa!j^Ua7~B5pxkiT#_`kzRYfO7GAc6fQ)( z3i0~~)cuqP6`q1vf%pXC{<*3>ehKNL)~o#WNjfOUPLHT`_C}nA7(o2=W)<&cq%&?& z^nAqBW)*)T;u;LU5b0YH?~@qhJNj`I&)HwpbgcaWcdk?M9!7iu(f|GkS${hbH-Dh$ zK8cO+YQh~`_0UmxN(i2QAJe_O;% z#QhN`B5w1g3O5Q-k7swqaqSeu8Hg(p??&8+*n_xhipu9W#My{{MSKWx&r}gEsFxS- zQSq*p_kH9>#5)lGj@XL$JmRZ}8xS`m>T&INNV{i#6e`w`bj4C?K)vsAd{hz}q> ziTEXA+Sw}H@rdUm-i!D;;x~w+%2fFM5$8w@^7YP9&-X$+8u4Vr^AT^B7>w65U&Z?Z z@jFC4Kc~mV=6Iq1;5huw)gqo>ZK{V~YM#PR^nz46&hPo)nYh$^hTpa@J&#c!pTfCP za$lRD1dzT?+MnN^9QdQAaZtQM(_BSR^B@o&GcZ9v#|-SD=Nlo2xUw`|AW5>frUQx| zr)i=zFu|r(29DS1qXv$!pQ{Yakk3WpS_3Lw{r`?dN}noqLPjC2$NVQ?dMF;5hO|y? zuA~dJxH?%IAG<`A>kEiGELZeFh`&cX5AjyS?+{1eJo6aD35b72)b-^Nq&Fdcf;i$* z#V-?a@+L(ehV(qdI>fgTHzK+(Q{hKS^xJZkb&21wZw9dhu@Z6a8!DcjcUSW}KC-?| z-QPy*1F^Y8^;2~}^rh2PxYaxFAo;W)wj*vp>_sHMQsL4N*CRG$sQc}^DBO(b-c`|= zh=qtvyJ2|5sV69UH6lMz(F+m#rzpB+s={8x)h8)>?a2yTPf?gLO<^PAz_9%J^ULnXO*IlL@Y(T9B~ceKM}t}+#csw#~@Bc)bp;7ApHfRh5hHf5zj#U zGotQauSHt-&woJL**||2`{|z`?o_Ab*cI^_#K#bKzg0aiL99aj7_s;^_52};Ia*!l zCBP3}Bc2ESH(C_-lSbVPhs*foD z=Oc&VedVuwME7@;fASIKSA9gU%c%a2k0}4@BYJ*W`C}hZ^+f0D7yLy7zi8kW4g8{k zUo`NG27b}NFBKrd z7*5SU_=ujDz}q-4q3`=hw%perHa;m3VOtI|;Qz4u;xnJMwq;K4KoVAm+X~CBI)sCzGT;g5~kH zOmFD%yB+!YEKYit;e5K5cjrX|mruO>t9{W!&%Zk7Aa?xuvV4O2TYXcwKlmz+ye_{A zuM40I_XlbF7$Kj>=AT&m?jwV3hZ9S;LT5W+T(*~xH^&aInI!*=w-o;b*+m5Vi-SM9d6KC7jsHxJ|2+==T91kH&udSP ze~W{^R#2k+8&H0oZyi5Td?88x)k)glTS@Y-MgED(>kRi;F*gFU!GSmrtVhU!A1>_jmBu{ftEUr+2CH*ZCeS9+M>hj3oJ=ktBbA zlKd-^qlec&A4&U0jW?X*k7l$#JK`XJJc|6=2jm~jmyA-_Wji zkpEXh#Ppe-ZN6j1uKvn-ERur4Bh|Dckmx<`?p^57kN4xDtp7JV_!nS4!{zspni%C@m1O&SmxI4nQ#gOWzD=CMCD(dWdHM^cf$2QSpJPkwm&B(DgV+W=ReML@Q-eP;QwOOzv3kOe~TUb z2dn?JsDB-AuvqKhukE-Q<<0y~_#MBUZ0;82hbj>Zy2LY>CztQtRM_ES{m|=P`gvWF z{PSM)Ls9{qoQ+xz`v(4S^1mlZ{=F~wA>{z39)v{sKPdV8$g>jtb^?j&zd1(vC(8d# zhx}SUiSqX+$-mpdUn?M-zmIhPpzO|Rf2}dv|3u~QcktKo!ubcw#mT=`^7oO{vBHlI z-u`!#@p81-Hiq-}ku5RgZ0Q`ktiSmCtN&Z~#_3uajJJkN)BnN*jXgGg(zs!iP|B8)5H$yM~Qyu(u`E(+`bT0S`9_p%hseG(; zE6f^X|3~iw)9KAhh`*0yrYe3;{>5E>NGhO{vr(6$eFJ|u`Jdz9uj3UWzo`=ab~0(( zs(h3prlbA$uiF{I#BYDOos#;oG)ew>Xn*}{75{)EeJAPUU*+Imh!GN%pUCp}k)CIU zCjU0_+=s~epD6#ae2;S%MaCFeijdp@ZDSo=VAx=#L$ z3Gs*h?(n^GA*a*+YS8|QQ2&FKza>Hb`JPAD@=3J(H=zCX43YnT9Q+6Ce_G}KD<8>f8=C%8_ZIDTu>K8}|Jo$^@17+8wj}u< z=-{snEz$B{kK^C6A?p8>B>A_Z|7{o||M?F7dY^Bi@=wP0XNB}X!FpOO{Y9}>r;WOP z=^KAal7DfM{BLyd*Y;nD{w^m?_&vYQE-Bqv`N5`NDfCJI8?c}2_;94`MdGH{xnDV0|MdNE{yx&QtK#S6UmIip=hm$%rB6lm4!NBCZ+FPA_rHeo_uDBr`LD+P zpE`g}KY%wh?R>83zdQKr`w8=R=wHXz`Y+Wt5S{sXUh?;mPbB*7(24SIl=VMg$J0MF ze=!>sdO7*K_YnmI>+cf%cF;umr$_P+0B>OL)@|>_257&p8emGB>%oR{NvUC=aS@~`>r2E4rsA$)b8_iBgx-K(&aj+-_C!c{%3WJ@xPWop8orjl%L1puiG;%m{b03_f3aKL-&8#4*l!& zwfyn;|2j$jIS&3>PObNN_P6_^!b1#Q{H7-&Uiu|CR5sf306#|MKma z(e?kPc$e6YhUGrj#iEjF|dY_0P^2{?Pf; z_jhsd_YIPN-`jqO5x`gy!usE#55#XL|C}WG_b18!PzQgV-$eD_;qd=jK3(pb*6BLw zsR{A-kCHV&1|LFD)=N!h6KhWg|(E?gL8xxiPQ3rpWK%(|nl%)L6 zC&}OK@V`3$iOT;;Li~NCE{6WoLi&HgwzPc#|E(e~zTfB^Db&yP-7g*d_47paUx4ec z!f)%iB5h~CNAn8}BkuA2!u`K~o+$qn9ezk90DlhtD8GCV@}Ev$-x}rMukRmc9dcP$3`#SxQR6r&dy0)pDgP@D{<;hk zmA?S}uSi$LRVAqH4{rN4Z-Gw!-46abY@+;qamv3`Cy40eza=65a8B1A(fdE^vHjKZ z>-2;2Yl~JH(9dcvyN%FrSN&a1N_y?pm zg8waDqTddkX!-ZX;UBP5R6m^jyONb3$A2PSVShS( z+x|#Fs9D<2b+*1k`A6+9{D5EIc8tG*_nr3Nf%0qqIswhUK;J;r`O~+@NWM9mv@ucr zyF==KsGyp^a{<`Nzd-Vb{d+@{zduR&=OrmWi6eirXf{GFr~J#4~V`~#m z{yXI+*8eKnH#&d!Mn9w-fWPp|mcKaszLS4WlKfLQ`hh_J{=zTKKi>X>V`A_RfB)1+ zwEWKZf2+Ivz&H@jd=S*hN%F5rlK)vT_$Rvmt5))d{fD^!(OJGuRbch7Z!eC)KT-XY zjebZg0DnWfu1S(V-{=R%fe62zXm0Ls^2h$VbN|1#Kj-@obur36RyjC^1LVOZ`8OoV zztzbf6_=>|jY;zVr<4C6_1~N%|BsU7-(kE^3O|>e^-+Hi=6xiS^hmq@;^CA{=G@^pP3~8{v`P?ko*f!>ydBHZjt>izx{lA zj_QYXO02f;C;B6Fj!MK)=U*4t(L?*t;%4Fgf}daflvmaPihuua1t!mbu|s|*|IUNL z`ICIbzxI&f;eTx${?!u|Kl}Zc!xde6#PIOH)4?CrY{Rr7!N(t@k4F9ylh^+f4*pL4 z=S))k8YV0B9j`D$V)FdY%NFmgzosv%!YTCa>bxieozFybt5R-0j<>iYR zxX5xUk@s~z25(mbr`si159!i!==^IrwOo4LTGNSrr$_sVl7#c*zs?6?BlT7!@9Xq5 zCayp2moXUk6C`6F4snQVNV zH{F_SI{vh6L9|Fc6v_Jmr`QS8gRA!?!<=s7_NN&SY$w|O2A^)mm)ir20?j4@v*dFh z>Hk`U6X%yItVf*wt)dIQSGW+d8LJRGq@~s%2sPABJK%7h!-GZ3M6y0wqtTPoh zAT}a4ODvAax4v-@))ZCV@cp;L!s}&t`!{&~{dd##k=}EKUP`pMaZ%Y(q#nY*!{#G7 zi+`MS{gs1D_g4I#_MH*~wtT`XhzqBAh|?iD?@ye#BZT(LwPoR();iLYi4EevO_RgZ zdoIcJUX=0cq#PoDgUCwMpcjl2`5V6UUX}SPkZXg{@~6tFAZ9t_<@^fr5p7Z{$tHpazb@K5*`Oxv>;cqZK zk?-M1Z^pslcDs*EKM*e)F`w@*;|J^2fk~z}!I54;lIi{2%j3l(hqs>o+~qw&mgn&K zrQ^|FbiMzbq)TGjtA3#CeNJw;U;4cxy*~^xJ^umW>79e=iQ%27zkwL(#qvu9`-i9Z zXBj^b(+_L;oT&Yj@3-OU-H7?q`nx?w{$iz9H!eKAJ28JcJ!iaFw9a3nBY(r)PTZTI z{TfkJ9lrk6P{&nT-kd{(yvethN>60i>bn|O z`KTx-;pc~{Z)ZIHv3M({sj)MSnKT z=_Oi!obqbgcUZW;_)6x_M>LL*=f2fRafZW=6D^-u`g7h-G(9b^Grp7lUoUS`7+&7$ zJMqDOU4|_0fKx3A(|h1|&BZ$=!d}KEnO-i&*W=q*8PWbuKiB2m>yUT2?OE58MD0-L zFCNaFFue0NI-j~d5d4RuKI?SzWjXoCVTkJc_H|Os;ozgrpB%n^#}b*(f|&E+N}iI) z0il#A@;|KyCl0s04R?B1NIeAWf3>6okpd=o=XytaE0av`7K}gS`d<|8@9viA4WAu7 zg7OOW2s?jL(t}^;Q|0O-FF4YBEy?uWbEMauWO^TCdfLDDN;;6BbwmamZom08<|m%L zICG-s0dkHBx0fHgK5M_p{w~&ad_<4yw?kU@-x%hSkzZ8;?K2RX#AP! z?KG_~#Gh-sP^ZSPKbv}b1M2G*#2>4kiu2%w6H2sQa4M+!h@V$KWCQB!qaRgo|9kD} zcK56HU?;>M%ieUmFjRZ%`kC3=A*ip(i1QJL+JDPAGFtzwn%K>#{bS&+gYNI?Pf|4_7}VSWo4K(Vtw(;b+)|`r72s+feOoeN=l3(OZJ{HvEzf zZ;ELTDF2p$crc>U+i=-iaJ&$#umj8CtdLzlvwB*NdYX@Tx7Hi(tBoaHtnq2iQPKR{ z|DL^VKs{|l)c#G`TX8J45v{j8sV6mW9xbq*M;h#Wh;Dy%nnkKSoMxyW|KvFGK~+!F z5X1F0T=n#S&))VyedRgyHciqMG0Rcgn=?Qj&gW~xaLywo>ffB{{@m!(QD3EqwtpMq z{9D2Q6TMxF`nm~m9ir;L4c^|0qUtT!o(^^Xtyrb2&G^4=exIS9zDE3U{9CBr0tw0N zR8ZZ|OmBNOsdiy3;&{a2?!W2zkm3aUy-vMVAN}*14_S$Nx&!gYvA1A*>f?#oTS7+p zUkvmv>g)3#Rd31H)2^SHy=6S4>S-q8k7IAuWU&2S=X^-Tq@PzkorwA>Laano>(qm9 zPkoc)jI%-yC;l(ivt(J{je2tSLsYxqxAzs@kKt7Rq`)3$Ir*nY)mvyib;gKJJKF`n zO4oTidgPzr?poBx^N8X7-k{#T!RN{j{kGmJP*1vERY%pEpX=xUEAwwZLDBqAvhY5v z+65otK@tn3tgr+Xv2S06FR|-yJf$2a)2oY_UcC6u=aXc5!Toy2OFG~TmM}fVk>1Hk zruRFHKiK_Ox<1xbsP;h%p!YwWF4KkY?;vXWLZq)iyarMEQy+O8=^YshET(NGG%FwdbsWsiybh4~m?*OLpQrdJT*XY{#zJ?Dhi6Xm!3(ym1QE|KX4Bpv!+ zq7D6uko<`Bu9S3Se=lBo*UET4veJ=WT?j4mr|M^MguJ177r%9Sf5Z4%fA>nd#2Gvy zJ?&>I7liAtDI~s-HzFa&4OQMbj=RD9t&!=)e~(`0M<=Aazv&CZ^Y?s$>FIPd>h${; zg{Px^*p3kBR+tr{q|4eX9HT?L1T2kIP~;} z+b_oR6MEd>tcQA#g{e|%qx?@rJc`wh%@*leXMmf@ZF3I#>#Q54z z#FIxq*Yzgnym0;fugmLS;*b~p!E0Er)%hF2c7K%oF8k2SdnXw`nBFc)rl-CWU-y6P zT@QSn)A>L5CT-I{+KbYZ;_8a{vn}gvIx}vos-w6Y+p^6hY5p`JO|JaW)aC3@de|#g&{`fYP zPgVc;d2qpbX8ev+`~u=sqGu}_RNW{0dm-J=sz2@07is)1N|WE^R6f=Av;4IDrvLU| zrTE8;_S?13?z-3Q+b`6-&EBul+sSx}-NoW^fsn*83-_`Q=RKO7I@y~|s`)>o4w+>uqj1UkdYzuU-Ecc;4UPw= z5;f#a?OV3DZvnMlmbjkA4Pre_)xPO^+J@_Lp26*G!fSzs8$LH440&TjQk!RmaXrd2 zxZOf{-KpVb_x}PbKO{!$ZH!Lf1gV(u|2aDKLv(<`|L3i7;cdd}0}VHvpGH{S5~w9^ z*goTX8LX$gjyq7?T0Dl^==*6sgy>I*x;sCOzlX;uZu)v+4A(hzF-+*sZIy5-;dBjA z9v5)F;Ff*c`Q{6o39l_gr$2!5=kEM8E^ki#H@1%~KaoY=vTx@QPX9;rcB1aeTYMj> zQ`{J@A-Z!Ru=+53U#D!9eD0HO>rhSuVk5~d?(d2zwr=oudtd4a4@$e_-gAso6~CZb zFOu!eQ}F`T{=Nr&fMdJVbd9n{M%G^uKbau;m8p2txXvd!KTg+agvv{QNb=LKb6n@g z{?N2*&GkE1@rys#b}COgf4;9y3yzTT8Fl@A3af_$AR*PG*9?unyO zzZgHxH&D7v;f>#S;Cl>|mn;`k{DQ`FM{btm!T=f11cUFuRB)OBjG5YgooVGe$ zxhuO-%3b2R!1f*=*iVZpyaM8S!cFRn8@S>BLj&R_;{P zX^YWKSI`-c2l}|p={z2A!7Jzj=RZ*K4+(xgCNB@fFJS(5^iXxb__&mptNd5HVwTsv zK7Zo*j%V{N!kgb2*fo5JwHl^3eHLv)!Mm%Eweb zH{{&y?>YQX)o?|Y<G6%+!{TGo7KKm zyXQ1cyBBebKNsW;V4e{irw!5aeN1vD)}r5^uAX+KC~w^k{dhh5PE-AQ z4E*%hpcK$Gay;K6$-}>oriy?-%=r;+sA`GpQnsA5*R$#~E+0d0n!O!h=**~fJ#hbOZCfyhRD8}sN#w1rFuW=?M8hIh)KkcpLvy=~XaAg1Z!gy`y3Sm`BdLD;9B>Bl*YB_M)30+J_eBQP`nuqe7t{& z?NGXu?Urt@n6IAhwmw<)-}c_e&Fcs!tMMWrN)>g_&vJYJj9%Yrm+R~G_VfRXey!Ra zW%1qV_K*LW`c>+soaYz~9pK4Im-*dI{A6|9sGr;R#_o9l%2CMDpU!>8Jf5vl?Tybz z9;P^V2l6XO;K$c>`lu=&HU1~2U*|a6WsdW>(4p$r$jJsx)n9#5Yu8g$FN0x#9C6g}NJh^|s6W{;A$)l=?hS&XeVD+Otxx(wAGe zaYuU=P;OFxxU>GL#yB{ns*i)yu9&K+8|~up_X8zY0m08n_jpv!-&zbrQf)UEvI3t(c@Kr#mCnZDbD3m9bdh^9(fA2 zPs+KJWtMYpuTzTlA|Uem@RL3R%74Tu8zUd{bK4Gi-JdN{_{Efc>Ca9YK-ru)9Vc}* zJWl4mVD>*NsDF9A^c)qB>ct)VpHYq9MQQT8LgROBn*3H!d0r1sXecG<;{f~1kxnyy z>8@V~UNrM-q52KEkbtqgLgH#?c*N9F3|z?|abuz3;WY z=R@PD>|gJUqi(Oecx~LPf7NVfhsM3W+896A^_T5=>uYBG(jE8my3G929rt<_zx3z- zL*vfSxD(d~`!lcaC)wHUde0`a9T~Fg$z|5cuIIjK#xGsF-l6d`uj@sP%B8xFH)NN0 z1iKtG`gdnrR~uTlGP-s^C+V(RHN0cCQ$uz>c{fyHF^E^7L!|(Wco7;Zr7u3IY&!6-Pv%CZ}dzJAR+T(%p zYx|#BUefhj3O+LPOZR!8_}I+vtLB%Q{k|SUEckgJ%Y~I+s;BGzB9~{z9OrUF{-V^c z0*1$uGH$U?&GI_rFYa9aV%KLw{X66@TABgl0`CWE``j!q=r0P}{o@8BI$`?~)cB?A zFN!bC^&9dRliMpE5A}Z0<9)yLpD}OsaU&eh_QR9Am;FL|6U0ffhRQId5PrG8Ox^mscX}zuT;r9lj#^r&v&;9?y zZtwSJqh7=mE^gp_oO-`Q!1eEB?VLEdw+`r4`DVMJ+d)=DJss_%`+7DoeaG*cab44) zgEjt4rb_8Ja$WUwxApz0S@>N!c|SMohuh=hK>y#$xWz`B{iSr@Kj`-Rjt#*eRZJSQOaAaTF)j1+NqTmA>9AMNL9jE_^wa>saboXhL!UdPF+{@(VM$8(ik zim7hx&mZCE%iRA~bdlm05xY?aT~4^1Oqcg#xkCA|_hXqK^Rc6PJ@oP%=X!js#^0D? z+aDfw_vP!@uBhwph59kwK(9aF*D782>p5TFa((;dQF?!+)Ba~ZR`eAd2NfQSd9u8z z-tP)x!tK#{Tcyv?%5grf6UUj~TdMx?^}o&79?qUJ{XpaQahm+RUcbKU*(rmSq(I;G zcJ8Aj*&HW*PWTUWKSj#~v;RG!9sT)%@(X91`QdxX0zN61ZrCRVM%N#fd%n*4C^447 z>vG2LZ*vQ$ajG9`+@sIrxVhYv(~-La^`ejmQ$VM_hRou_2l~Na;Ni? z?U&zx<#e}kb6ZZnn(d75OLEemvhTS+Sq^5<3ClZEzRqzj=k;-9u*GSY^NYX~vmU2Azi3hX64y!Xr&@5;E{E~o3VPW_uJ$-iO0?Ha8g znUC&wak@@#(D*Lu3!a_;uNjlr5!TGOobI%}ynevttf<@d z>vGs1PV4uc;s@V(CA$0L@_BDS;P=H6*HyTk@_pb=emSR)fozTH8JNr;bXZXDujqHx z`hcG7qzZ$Q=Y~5Frxir!-Hve_N8Q4$MLj214RDCtpfuJ+rhg>)x}GTG=B~WOadRqf zE;eycSUp2{Jx|pCgg5oH!()1SGU0SO(ccoq_foi}r|H|b;1S-kZy8Ube2pV|EK#rF z7E!njipmW!-G&Iz_nv+7y#xLEnNIy0qw3@_u)nK25PG^(|H|NRbAtY^{W_uzo5afw z+9h%1_Bw4BlTB2UBi0*H1QIhPPF@ zn7Z7bKBx>14%_SbA-Bt}ieLKt0=BCMS9+kKIe+>KKTtglY3+9;jJ#qW1 z72kls|9u8TBh_1$m*d4>Eb6V_hwrD;JBS~D*Ku{4{J7pM7k54=sVn`Bmr05yP15W84-JnOEbvImEJ@-k8oy0x@^gx3lBy(4YJAvTy?vM3 zm#QAH-DyABtOxksjey{Ox^pesn-A4}0^$>*ync-9Qjw%>m5HM)uJoLU_ho&|S+*zS zY!qCt=|;JLahB{PLGMtn6Oxma-4FW)~eywb)7r7wVvXwabZ2-^eoYI?OR?L{Wtd4 zW=AD%!AzE)&`x6Cwws{u;5R<;9%?ugmn(ar{Ij_ zc=P&?j)&Xp^y3KMvtryt1bSxyr=hGo0L;rr0KF~M*%3I^u zWrWvNL^l$h-XZlQb$_!9+h_Rcj2rt+1I4Y!W4Q5r`gny?KrAAPxP=Yr&`)?BB1HPa z@)4#u>l5R~^XY-&Ci1-{Z&vf^m(>1KxAZi=ezUX9r|%*;WBWExJq@E=XnGpQ4djhJ zozlO3)8*T1ah?~xO}Opz4;=poid#Wa-1>|Q{J|NUc_HgP;}ubS8`*u~9`u9M`c?cpzdv6o>tCSo=zHLCj?*~aTxRwovAsc+ zP2zc@;uqy&2IcZbg2lxiGU|8DSWrS`hT?@@>NDL@9T&fZSUP~uha2Ex2GJ>iI~Tamk2-Juj8~I zt@8cHWqvq6jj3s)8}`}hI;KAUa=h$pGk$LO@33dlzZ|P*(13ZWid)aecQJDIf3orI zagZTCKMKSrAFVvI!7UU>$$? zW-Nn~eao)!*0^x^-%-9U9Aw>z4eyfCs8}d~Tq-!+d$0(_X@B6 zHb2d)_LhCS^nL5!yGP2iJ%8PvsR|`IIOUh5XwsxghwV>VI7uSD&nl*_^oJ+=Wl30= zlFp&}N&Nfc=d^!t9vBe6Ci{WUkpkjcio1<3@5jsPM7!qp_Z83|jQ`K)cy*ts3Byz( zf1~|!R{!f@UOL_^ztbAkc*5C)t`AoDpG5fR`Q7f1dUc_^2YMxRNi!j?DQCJR?l_a zmACkMs&HuEbo{**=l5AOk(@nhk~g=;(>QKU<;`n2>Gk3NEqy$Mn+{FC@z2!$@PIhz zWwdhzM4j5V9Ods?@5|%yl=qi8mA3@@hJLl4+;7_k^WtTM(=wuFJx$$z%eg$6|F#qA zDdF`z(cv5W`n~q!=`;-FPx)EB7V2dkf$?_RISaWiQ2|A8tVRq=rw@Hs$G6_xVz^)&h6|I&iS_XrP9 zliyL)US5|yPQ?onnyL@jJg<)a1^p{7fa}l!akAnQ5IioNrQ+$2r{m|z9i4IiEj~Zh zcY1P-b_`lgcr75hhA7H^>V9wijo2^TZ@p(reB$f9PI2S%jA9h|>dAb0o749Rr;mu{ zyo!FWTk>{Z9pc7*uT$J~zqi9UiWQDiZEv?qy?Dm)Ox}}|h zEK%~d^VzqqJ5Vq0AbDf^hI(p0KMl}0!-glQ#b-v`v2QuZmv3X&U4wp|*Dc>6+(y5K zI1YGR&~V#Y;SO$*o1~r&?OPdiff(c)upi8R_t7M8PT%*Vk9$_t)t|@falU_)?yp~W zS|6+X>*;R4{~+!672IL=t5*{a4-n=5dH1Tgz5Q%xk^HRJlb2sG8m`Cxmlk&TcRt-v zHmUmr^<*9HZ5AKw@p{r)iCf?CdYd(EP;c}F$yrtxjyF7yLwQSmKGKbHVLY#G&~S4q zZ-d%s7ntV|Ubz}>xX(0o+ge*Ckbx2|RC*SFKjQCIydWXF&X3pq>hCrCCzlgHexK4GRXl3TFChaZ)T-}O;l6y? zA0ON+`zM-^U9Vq421+4fmP*ZwMbmoX3miHD-KxKGH+-V)Z?>173G#M_7;S`kC*;l;=VG{Wh!g zuRE-V-TnEx)qR{kKT5T|dONpOd-%RQE;zlf)AoH^^-Dk;NtFF+TnA&c6RFkuVfF(k zr+okJrT`R<&qsC^|FDAas!obq>V9=@QrzPIhjxmaKK{%`zIt+Ehi;=3tNE{6&qs85 zGhWcLjoLR@w`n3d`!mrVq61xbZL*NJ`2Ia-Ezt$5#bfd|{a@%$|B$HJzNN0G;=yG0 zEqy)BM!q_n&N^WbaxEmhqAuZ9mK3+0Z(QK_7To0$Zc)OG{Xn#D@#p3P9TyrE-=TGb zK~SA-%JK~*XYqOIyDA<{sDo3(!hSW+KXt!ePv9->GqXpN!l19isXp{#lio2A?#4zbbz{^llnj8*KIcibLqx! zDLIQNZgRX&m+e97y=O5sFK~mO*Y_!%s@_W&->39b6^{-W#{a4Qi1YhI#Rtysb1I(& zHARUh~Crjhu`Hl@<CBrSkIr62G-+^5gwfW0n2{#L^Z%r+>Y?`!>q+mw%zBxxwYP}k%op)-E^IH)(7%WG2PnOO{c>vmZ$R+# zPN(m?)%DQ|cIMCf{`fwe&d*!5y}KV#^-{O(Z+^!0Fe+|y!tLO49Oq(s zy4&r-=Y&zdo!FJm^!suKnJ(MU^~Cx_eQd8epH7*do^BOSb$-u$^!n@f)$6}Z&5PaJ zPu9187$dG*U#i!u@3p???(|Q<_5RUu*4xVRAk$?S*Dm$PJ^Rxa=e~I9=T32+r1U#z ztbbic9{^FDABG*G_XqB$x%lY_TGTv7Y2S<)kZuB~H9VFH3)%RMmbLPe6 zIL>+Xbf^8y3RF@yVAD7=YI7iiO`@@;* z!S%AES5O8nXV)&~H$yAO<@q|tncovf%Xy7^<3eBktn%6O+ zM%7Zq-EF2Z-+6nj{OU!7 zk1qcVo1X4=zdDs;t_Ro4ih8XdKU2>kVwQ4i)eft4{r#%`byAL_3E7?682$ebS!wbc z$avV-k3snwnon?t_Vo|?>r_hcrPP*Lb zGA-p${^yv}osKuU{PO*D>b9JGRocn^!&s%K0dX+VZxZGA)tsT}@nq@%?jP<~?iW4% zc-j{rA1g^cK0(E!oIPcFlEyEbCcjfPem_l<-_JFEM;tEa9RqLgZx#OT*+HlNnbVGO zs?SdO6)XHg!fM|G%4sOUZn4}I`1gTZ1&!NXpzyQihjMPsPh^<+t)X=#mgk2l&NsyR z6(rP;uj}+UwO=njZZ@j=290c~@=NzUpfT0n#OI@|=ZrJQxuSY{Uq6TQxIwq~JP!RP z_w*6{rhrf4=Z2rd7~A2b`-5KhkFF%WV7-f}c!4g4j`fal8m#9MI=+3^i}>E@(81|1 z_t%G~QX_Tyz-`yl-5yWSzQ&YXxS?M-eg>4>xxtTdNL62N)%4+Z6^}Bzl&RO}gM%aQ z_UpkcpLT%WMg1WKL(0W!i~eA{V8?GQ@nb(|y^0qk6w?hL^KTt(_FIzM&mLQT2^qA6 zROQy|{Ww_94Y$=e^HsA8JI1(*c1G9LzN)mN+L?}V(#|B#7wqf;x0f+-d)#L~U1IFU zIL&^#Tl^#+`LkO;V|y-fl3{3^mZ&BESe_EEm{ZbSXKmTtJgz2RXC3@;mD@X5`s38k z<$9P=wvO4!JM$Z?c42=7e3E~Ec7gA2-4gQ^Z?wyPp5wG#={|QwobBslM$tN!+<%QG z6mZ%;x5t~q>HYY2|I5_t$MT++ZMG}C{{Fl+k4SY~9oH?N>&4d>C#ZNLZV5nIBPM)Io9ZGonvp*K_CnW#;`?#;kEq;fp{v7yvdDld<98cBc<+~K; zhM=4mDBp(D`lCEQV?D!tok63B-Fkj>FLV8p+m&rUet*^UU~v5Nn34lG^eVZW6&P~H zCsy{nVIQ-c;diXye}$U8Ox2!$RpmsElWz1Y&g0yBev{mO1X(-;_ItYat-#>NCsuOT zvY(W@L~}Y=>rd>zaD9gS4ep_V(O)Y2V81TylzV<3e%wb+%O=a`b-RuJi~T+-9SfG@ z@MN>yPVT=%6QI%gC1jw4bjOt*t$ulT;lAghG{+UU{am;AnJiW9i_c@O(%Q>-JM9Pa z{--Dx7mMhO=+P*5U@6+?z ze#@NinC*V@aj77|{3RsVi0)vm<9^lUeP|zH^8f#`T-b=ENO!+-_B5$KjS07zOexY| zls{cx^mMn!pLR80h^c$KF;9ADwZz$;AGb^0xSs>>6M35EH~gOC{oclPVf3Q{BCO&O zkt&{7sB-aj*IV0@%Sk^~G+nw{<97qaITP;(;J6+Ai^lI(6_45#v%LN{jo*D~^1GkP z^FDsd8*3N1|AR-F$CXDN^0U&DHjUpiieF6K-3|HH_d_P%pHYyY$6E7^=!>d;art|l z+HYh?@x-bAIc0n8x7^)-q6RVW$nU$@hlVAMaC~i?ROd^2gV8`U&+X@8{94 zb6n?_Nf!!!VAhW>h~Gf>wB)RtEB1=c-_D9y1Y3Z ze|Y@w+jq&%8%aK7|4aG(Lz?n&{#t35f?^o)Yf6)!t`FCx$uIeLC!$bW^UtY1xUFB! z)E}}ovOjKD@$}c>7E$w9F7)>4>2B+(+j7g};60i=%+T7&?V@Dk{iNJ}r}g6UcJwvs zKYq`^n_B(s%InwN?gzKW=XCw0w%KO?rdz}B0}K4P|8%_c_@~5A=9l_3;$3N<($}X{ z<;?5-h~wVi?}xF|$03|hf_HSB*go&B^eu{wg5zX5;_u9 zvwc2B^=DA6zr^inB%yNLe+d^T0qt@?%>02|{|JZ^6<_#%((O2}#qY<3t%{3rbfsR6 zA0f;2prfkCL(Y)+3@R&&NWWD5DIvUSHQaFh zq|vehjGJW!y1=+G4ouaDBOy=E2@;P)-1K##oD;o;+j7FIS;Nil`{M(}%@7!px9mC2 za0{z^L8G``e+~BaX9=&Di2je`cSwD8Pu_T4&S*H{7CI?8ZaiLa`|DLc@8HJY5nG{f z((RjDxFz|H7~#ga>}Yh>W*i6R5KVPl$X4T)^Kn6!w;WYI(Y~>K8hD-}<#Omd+6Epw z=n3z$JCEc|U+2{K`{;Iu<19yBpU1F&-lW-h@Bxt~saPuhop z*ZT@pKE5vgM~$BWh{F?ooig2Zd$;9hIkis?3({}5Y8J~cUuU^){e|-5I9dEv@e7DM zh^A_v+w+hg->*}PueufecDA=%5B5L1RQ^Q!Y~-VwX8&B>hi_xle|w$0NqD_CK-^kY zd#uMb#*O`O0}t{C+Ew{P+!6r7{!U2VUbqdmABDK>e16o@ThWgSzK=9jJq;^42w6V= zXLF;#QB^)s-i&-CP28mAysvF0$-SLF zXIDSmHxOUAFH;m4Q zT||!W52wjb|9?Vzn*5$p<>Sv0pG%XU+ji|0s*m0O*>t~>*P+_#lH2Qn>79G_g#CGz z+mr=WD}eJ2_uq4usy@2i|3l*%b~MYk+xX)50e3#R;daUKQ`%ikT?QO=VmD-8aes9y z`{kb9c+X{ih12D8m$?1^w#E0FxjlBkeW>ae=l2oWZ@rvTexDLQnP2u}R!{Qzm{We^ zRrz@R_D_@F+6T?`aUvhG9b8|h`pkZyj-wvO`UJ$O_nPZ9g)%Z9zCV{|J>lz4>&4~m zsN4VRPxpW6*}s*3LE>*FB{2?Pb(})c2I^R=LHN5rkbh})ecpRWatVyW^=K3|LT!+t9;_}iT%pRWy^-)wfTij}-se~$_KH)PluZZPXh$0BLJf}AW?zh=ho#+4Z74BWqLw>aI3 zraM24EcT{-MLl)fzPi6Y>14c?eaHPdIP&g(yC##su^e;#Sq}Adx7+3Rd!7et9?!Vw zc=V5x`|GDp{C-&{uCLquwWF*jar^u0ufd!jO;+P&KpaZ6g6Q-PISwT5uVFa~&`HlX z_lC2?@rLtd?JNIj84_QRPs;5yIzCsHx8G0P{wy3 z595k5ZXCCwE5BpLjn{7(x3Z}U4~~kaEQuG*W{muJrr-omYuaN4BdhJIT5xUJG~ z^M?oxlAOkA&g8A^d)~tB;18u-1;jUrn)Nh&+@|f0^0t-qRF}7`BM~=4e+gFJFG*3+ z`h9QVwt(>ZKMl84Djw!)Cs5YY=A&eLtnFLFWW+7u{}Lo`t*U&YebaI4INDpdttPx4 zBFg_W=~D6Z6WiN#0s@d7H&e z*|Fs0!rcdb*R~cB*}cG+B_Pso(ZFJF+PBgF zLp==;Wj#fCv-aPd;+B21l&i-0eAA(x>K!)i8gI$lA%s`1h8xaL?e(-lwZ~dd%jU=@ zQH{nP45xak*zYZFQtHAn_-D7UpQRJTZ@lz3q8vJ$?qRGF7H)(z$&zF_iDJ| z^Q!di+sYHAp2p`3I}2{ZzmM-{?9gy?Yg`EY7wsEsrx@V4(CINf{pmv}Z~r0OCVYf( zJ9%@eKQs51>$f4n>qxx*#`>RgowWC1!yYnUU5lGkKJWN%3kauQ6TOnC-MGN*(`o1R zYTPq$X!uadTm1O~qVOV2~w0dYyZu_D~9sLI0Pup_$=4t z%^$*&oIj}Y+vAppe7Tr$`|Wjd9N{#FsCm9%k6W{9kG0>sdG+qlw77lqg|MXzK&u6A z`nqNApS`80=Mr9vHQZLKxIJ#c8)bW}aocKxTdpdfC~rD$%`3cxTN~l^q=p;r`)fqu zNaNVF2IYdEFp+c5S7<3x1V&k#SmeGXj3DB;BZRvX2+fuRF%JpGjB zr<~5XwQD$^el$L>AIUc$ntijk^z;M5=_8`%aUp#@-8=#PUba{IxX^uTay@0aWcY$B zy@lI^ttemn66JNvX4GF653c@zX?)!>MHaV9yu9~Z2LFE<5GN6CEN^ztot?IR>;2|& zL6^7iJ;~)w@301y&pYEn4dL}`4L7Iuty8tf+7HRPuYdoo+X6S%Ywn+dR&VL)ZG>0r z0C5Z7E8Am@TbGUhR-no!`XM@QovXct+g8FW>vObotfy}IZ>MW|>JOm=u^zVH=S)w< z8gJot3gLB{hMQA84P$$Zd3=Eu?r0}CiM^n@0nw!1yJ;_P z9S^B-&ahWq6lWYeM@j_Z`$IsE9N9OX?=p71$d|>GleZhaK7`M+o+rEx^&xIJE~IbY zR`$r9a`ER2{9I-lt%po|7;$Hun~uVHy)JLTM^rw~;I@Hq+DMdfbIX5Qq2b1Mi*XA- zhPZKh=l2mex%y=KJ?<^s_V%NE?N9VlqK_&XF;(43r0WSY4(lJlc^1#>{UPMSg43h& zC)zi@&iBlDQu6iAZ|TEm-_{deJYR77T&7;NCurEW1}&a#ixHUlKj)Z&D6Z*V7ob zkF35B%e3c!J`J}Phq!fn4Yy3hEte?kDefmv9k*#uneE%@|B&+5nB@B-x~Boz`uupD z;H25em&KHmw;R2I@cOHU8}8Ri9k*@`x7KG7H%2LBw3Jhf1k(xoC7jsnG2EsOLwWli z(aVYA|0u&;?F1^Hw#Z-i(^9^y=UHX%1l09}@5K^qy{deoebZ5G>ris+8F~9R;q)F+ z)>FIxpE7O|yZ(M{!{tA?LxSg7hW;@y&n6!i_%idZ|EITb`}%N{x9<==o2Xm3ZTcML z&G6qC2m+>cLvq{UxYhFUMO~cKwTWsGpNjwtAQ+=O9-8+_*g7x)b^8$$WX6Q{OIV z&qfk;TTe3|MtS3T`Y-~Fh{zd{+`h41Gav;jpLgurd4$(R8gBU9*#-OdSpT?%vz+19 zpz?VKw@(PKFNvnBr}ZPvdYWUSr%{#PUQb(*uV?ghpRuT?lZm>mrbNyp;Kn$!eQWm^Zg&z+twcAPaEs6D z$BaX{#OGWE@%KY9~c)DE-&P(BiIxpZsmY<7lGuiQ;-u z>V8NUw$JcG)_jP#>FbsuYFf9-pJ?B7+}d{W7H-!PPJbe5-j9?zZUtji`wZMFUe)xJ z@M7G`RQ^QV*l(fiB8GfDBX8>ouYV977D#P=+BHt%WIaE%)6*VRJ`p!v-r57+lDCTq zr^|`DJ)TZ_0OgJ4C&9k$D)C6fP4|0aDxY`8(@w&xOT*3Xd6v~Yi}i}>n&%KV#=#%L zD?~)&Zpm>=kT;djJGhP674>pAqUrj*WxFeVGVEKfjo%wl`4i<$H)PGoS5L-H*bc{B z!t1+4e@pZlMXl#q`tv5Ms;`d*;8LIcmLbYX0@Z7Qn=Ws;u!*^#s6m{W9p{^!|6K+{Tt!6iYWVUcJD`W+U)va@(FTC@c!^&G$IBGpU{Nl@@B{- zc+|_eL_Z>Gw-4VbZqs&0+<2b@zW|wWi|(cH;L$>~Op{Lla}(AL^7i%? zZYS@7xSdAyO`>+s0U5XMNwPiG^Q_DO;>PPpI&QtHy#2V4yN|bU+kH>OZ6eWBaqCd+ zvBqtR4Q^ekyghE&`+5tvTL`baiL!me_Y%a{-%g)^`0{=vmbX^b9&6lkMxeYI^Ha^f zwX5=p&+FN5A@e4t?dL7rE+IUwAo?ItyZ>h~Zrm;_I_Cws(<5Z;Z{u=7Dv%SDc-YHZ zfy(C{KO`?3<*0z@^+fI7gTd{m=3A069f%u`k4%GHV*lj0v3zp2PLI|2F9e^xt$nEj!3txZOZ_ ztsuI~-iV{!{{dM(nsa1(tjE)p|3N+F=Q6$!iV)-0s><7s3q2~Icl7iJgwqV7wM0*z zB=Kv+MkYBjZb>eglEPaMH}-qET##}#9xQQB#EtctnYF8Y-ofoOg-<~If@m{Qr}CC} zX#euI@fkC2{Ct6ND^ukYabtWLx0uT39o%-`2la9 z!fnb{)KkVyA5X*IaE4oh%I6*2-Xfef6WxEmzT;_vdKw8VH%5ZRNyW=%+}Pd+DS!Pp zo#ED^@_7fhO9-dSiFTWCOMg7g{=kfzE^o11XSj8$eBQzBn?aPX!-=NqzqNl$w%gi& zTV> ztZ@^xj>Pi?-S2HxRQEGf}tnv^@{yjlZ8YP(7U;@|K>yMmTj7 z9W@2z&hELhQ+aEji|@^2?F`N`TdpRBhyA$F;W6BPNjQ}fZE_j6rXs|R^|U|(JThI% zQ6g?;bcJYE`Ml%zwj4(9X(YV-2e><)9(5|}X&g7*;wQIsI&Qrl!|h1I>3E{QA?lW% zt}jM;i`zFdPP1n?leehK=N)-lOY*jw@Op=+TjRpvr=h&XaT}Ptb$AT7{SQRFJdo%Q ziQ4^-QtlFW@;hxQekXaz+@OTE~|slZS^mbZYh zBi|W=-fC~+HZBLph24p=o{qYwf85x6b=La6dg^Sxzk7$vR{Z}mVG(6Wc4)eU@VX=^Zr1y17`N;jr94u^HZm|=>{T4-N!rg z`pOq@T)3BT8?D~^77%B>BGV(5GQqg%8KNx`$M|#CjU{qtNYubh7|&&R$B53s9B;eH zn|eA8?Z0@3aAW(1>mkxyJF2qxuon_zYP246PjN?L(sM)@yE^lS`qP(&7 zZ&Tl2h}$=R2=UVO&>4!}$@Rq#(C?i?xUszPxWMh#YwT3sHmtyLf#oec9Ov^oza1@p zRSVj;h$e4#`|zFOHm6h0H~WkW&di$e{~E@#Y_*bO&xq+N!s~vb{~~I)KZJ41yU(nb z^?Lk$`5-2a8#kQ4JI1)^l=E?V&KhswHt7(wZ&Qe#Lewd4_1GR`e^QecAM>6&xXr%b zTex-IfqHrg;bk5d?ESYUY>%L;nqfYJ)RV| z)W_4n1E{y$?hV>_DrS-91^dU1_0y|#qy5CUb7Sp z8r}Pn(%(iSuAlI@pr`BcZohnENL)|-Aw5U(ILj+vjXmxy`&Lc3E!5-<-z&KND#x^SvX(d=k>6Tq?<0|o_$aB(Kh;ux9r>Dgxe2@vj2wPxob51*}iR( z=T7Bq$^wa7i_chp8=ml2W}Wp85no7nE!J>zYdr0G4CRge;BaQt5iSWr+2t*HdxG$K zktlx`8|5wW{}|k8mbb8KPy9U>e4S~+cd>(bh3BVHRX%awphUaxuhnC?jr}^>x7~@d zo}Qte%cO7LwmgA)%JLJMMV4y^pD$eYGLEO;BHYaDA?f2bsa`%`=rf)=92fe^nitQ{ zU^yeYim21|knVp;yVK8enbBz9;^Ts!|Dx3|9Qra{(;MEhZqtadPZ78H^OYL9 zj^jmv($qGsHz$5SiZ^sYahaN~6(c6u4N0)epMtoteELI(49nZJEr^>gNIGs2mCrl4ts=bcC;Bqcf!epg|IB*F_Kk61%D82# z{Py-OjC^%?Sv#37hrQAHouhTg(DC#+UhbdJMNEgww@D z-PY5?PDI>zJm@=`y1Cr-)lSw#S$sbZYT%tr<7QgY~pa<+sNz z`x9^B7Cao~>j0v^A?j4#T2*_j=k;rjH`_N|-r7}pd)&mQ-ooup!l{+0)9)&>p6Uu5 z!}b~SSFg>pba`u5`4e$td&vB|Jce5*;q@9(^ZYdRd3|uJ+1_Q@%PPpzUc@U<2Fr|Pt;Rg-XbcWcl@^{gwv%&Q^hUd>mN5^gIiFQx5q6{uAW5ME~7g2R*JrVpHXDxY_7dyDXTpD638-Fu~+;uZ-b zZk%7ot=R%MU4FYfhTBo!L%p0qbOBMj^$^AZrN{c%dzg7#V7X&?>lmT*Pq2IyOijMt zo0a7)J>C6C#BE=qZsWFsa5L;1LCm=IA|8VAT$7s|x3ZDm!tJMo*BL|`iMnOqR?@h@ z@~Mvtd83lsH(gH~RX*?7w`T~a=ZOycKFXh4dfH*5r_s^L^_1roYyq0bcuU^?PB^V4 zI_fCI&8_*uCL7$QjZKc5E^iUc$7Yb>+-~%2!s$0eS>D_lPnX)*w}Nqqn{MB9d20)J zOWsxyUJnpuKg90)&TOyjI(*a5`u9V^yGc9}{a#((8dN^-*tao1K)u|J=({e@>wA8U zxbb+(ipJw<{qANxop~gVr+UAH$9qfOZYI3$VBCnhH7-PpH2*Dx63VzW?vY&HSZ`UL z+f_dA$lK(jQQi(AdMi=8|I6s|!0prN#>I%6{#=G}E8Eiql$MP0b`4h(l z&c`?GMZV0IleZh)Erjwmk?3heovtG_O_VrUuUod9h`8~%z<4lj&8mDNZgO!~i!Is1AGx8XAor}0D? zx06*oj@xN;9dQd9xRveKzr4jx>0jRJRry4D)BWCX&|A1IBs?x4dante#=fdzI@jgF zbg@sQdn)3_&t(`7*3+*2QQla8xu~wEjbHN?Zr?i=8{D!EPA+dcZXuP=JM#8#!s{aqH@DV%gEqM39^wqQGL_FexE0Srd8;I9_Iu-g z$ZEQts{c04#t+FmG&ydp*Eov68mi0Yv;dbl^h}&$UcN4|u3szDj#5n6qrYm{pkB4|j z@ObJA(PjDrL(`GT^_2CRnRTdq-qF(+38zI{dwRr9|h});|f+WdA zm$wF$KM^;5{jCl8@@*38v#k>jBAmWK^k$+cZxPGtFm8H=o@328o_AUQxb>?1iMWl{ z80XIPmb~>6PM;G!=A^!QYCWD>;iffLY*>M~>FaNT>|5-3iAN%CJWepbPLM0vh|@|M1y ziUQPA);oWQB#rbmFgLlL>I1-RmCrkRdeF&e&vJ=gMAWGt(uD0X{QYJvzGaEozA=7m z-&!ni)BU&lVu`PJaQo;K#O-sUM}+&13-*3US&3|q^*T~dKH|oHuZ~;2DxWwm=<~Q1 zmCrl4Jx@4oR5VCRB!pXf+CzG3;Km9_dfHQ}t6# zx7|)d+$It=kEizbZT9^Baf|%MJT43iktrZO%~R#=acflhykpzZraYc@tNe+$8RG}R96Q@v^7ab77xc8Bpxph5sNMRT(SB-|C0QW_ zEB^j+mbWMs2vtiw>~X7C`Me`b_32gWFodt9_7g+uVS-@j8-@ zTZ08|x}Js?NPNA6+b(&ipL-JhA<=8@lkeZQUJr39Z^1tzZam*)JXqd(6dsA=sXouj zsrMFc-}*V?HjSv+z6Ay2vxKhmz8X#kaQppr{T^{+91_OUg~|1l@nz;=mCrl!b|c}@ zoD{e8<*m!c59zVMP4AbSbG;>RS-(Je8%NYEZ|URKL%3PVTfM>~QBQSwYw;LvwS-qx z!wuhupFVDZSpRw&{k-=_oi^NpxbgEWmZK&pUd01L1WOQ65imT!{N`yZ;L1@EbkSA1*NT&{81j z!U~$7vfr!ATkiSEapUoq`Io7D-ofo4`fhA4;ljAty-$_pA#agvcgXmEAh(lg?eF^6 z(?(n#0B2c(@O}i%Qf6^-7?;ucLpeLVHgc=s9E0PxXq#Ksp@Gj$s6mPKF?~sFgb2KuCToIsC?eRt(@?vCi*;4{QpAw;`$BpeLWfMIr zpLcM3neh4-QC>&FaUrg!qYDsUw0i|YZx^>ur*`W|p^KB_rpsHs%I6*2_9Q&^CHh?x zKGu3FM?ELMmE~g~Tl_nBp;rSkm0LzUyoB-7h@MWgSkVObLkU9&3?VRtzz_mM z2n-=GguoC2LkN835eNbT4G;pb2>m+HzH_?1&x5kz8hh;WT!dg3K~&C5@h8iDSDwoB zws?8~lCs5Yc>&7L76ow3fe;dTkRFEF1KiUO;SdP3Ae2Ek7eW(+76|JhyaQpBPxx{m z%z;o1p#j2j2zNs0fbb@Stq?}}g)bXI4uokCW75YBhAXG#69fai&RzkQ3!deI&5H>;B0%7=As1Jk#A$%9Y3<$q~a3+LvAzTb$IfUCF zJOJTY2=74n0>apFAgd6LgfIuf84w~67D2cI!k;1B31J; zA)Eo>TnLR2mP2TP@BoDM5MF}t4unr3jNKK=L6{0*HiTb5DBBgZ5PtdjGfPSf>ndu0 zTvu5#r>dy1q6E_N3u|ghs%!JBYb#}}sO+qo+OzVD3#%8DtAvv3>Z)par8H7h0eQsRwYa>J&#LNb>9o4CPYr6TDykNgs9M)n)kPxe zw4}Zq?q4{!s#=nwqLK*Fni?6el1Hd39BT{dI8{cf%GI#~>NXduT3cOM1dTRM^J^B) zpIcQ?lV1uoom*ZhkLQ+DSBZQ8xT2~EXjySN)TO9weid9Sg0!>p=atmfmd^)15Ur7< zs%2LUzx`Y^1W4&rh zN+hG2+Co61v}R$YWK&xLP)Z6CS5#Olxh$wImvmlXxe_g`Fw`^78X{0u9v8sPfdU?i zMhfSZ)RdnqOPAC}N=gV_87aq{k?N{>2stEHSCr3}q9af1YM`t-tpN;-EHY9h5mel4~)>Z-j>NHX-d#AjrNJ3v;Q&?LIh0ZEDr=${ComDPH z?yR%xO6uT_6~zjkit-w021J$mg495Z)H%yaMb$i+GVfctl2pz+b3WXx0zs`nQ0EsG zmF3rh`sUY^RFvimP-PTUr1=QVe9-AKkTppq4@o0XJ)ly%)k%%iXh;B>AA#BflHd9+ zq@1M$em+=|5><45p=3~145C$8SXot5QBnd4*zfYFDh*nyEKQ_50^B22vh|Uw1#nRI zLei?DeCQz*oJeh%lqyir+G+_qw5{kYth(t^d8O<-Qxa4+Np1kM7`mlzZk1G14x%9I zLN`>23kps7pe9lnIm`UI+LC&W7p9@+nDf_ zHM9-WYZodQsnU=vg*9JVPJ?0jJZZ!s0d%#j5c(Rm_3RpDBC89_rDduvlqO9ctH7|J zBEu0SrW)*uOhiSfDUn&>R!Y%;mP@GwzHqAK5U3JXU<;>lC4&en8JyNgC6ttvs-~>2 zwzz5mh&}X0A(%y}HQX)AWWxc>6v8qBStB*BoP&ZO;z^HPa|Is@~mDA=(0ZKOe? zRJws2%2Y~Zt~9E^!l(nBtAtuPlt2VVI;KVfr_{wy1oI10 zTwV!{sH=obN-pZCo6a!?i*w>VCiP0)kIcIw=b-=M%NJdT!D*)GT)%^KVP)D9xQhS`TBjaG&0hd8Ii_2@o9`no8CC^hQTZZXb-0 z+`#S46l>mx$B~)EwN-`UNAqBWs}(;}CgE2K>ta!qDK=j(ZyNzH&&e0%Fqr+gBvP^P zl(}ch@%wkaOwq7q6mV+ph1Vkb3oDB1D!?}6&#jZ9D98EI@_FK~J~izB4RUvXBy%@y zmB;0u$}-R^Xggl^RhHD(ir0O@FU`_(J}|KP;w>oD1E6RXk}G(asD}l6)4 zIsZANLNCjF&&X@bRqn>8`q%dI#g?MHzr|^eoveHN`dJvJBNZ)1@t$Raqk*$q@cxXkpA>E}n#}y)XIU zW<5>A;Mj5%%vb%=>k=Pjl!~ozxnZ%q(0YM9ZvKP3EIkVtHm=O9tEiY)B(BYb=3Ol> zH(ou=R~G>w#41QzcD_tod66tq5-F^P{;4YluTQjR*22K_bfy?Kx3Idp9E4<1ru5>D zKlZef#ATVK;!l}MnpbDmhz-D~<*`g)wB)r+Us+YvS>p0x(2*)>vD0+vf(Mx0xNihl95uWs3-t5|sJqw79qHb09Bc9X zxhz2ALS;{2dJXtoH^NspU;J$(^xs7?q35Cz{z`GzNNLC)fYeQkWa{RRB=QTyhLND} zFF|s2tL%^^*UF0>%jI$7rBD!9eV~edJW>o(I`-F*&}y-6BuvD*Mpi4g7DN}E&v!>k ztNc0e+VE@!ly81q9-E$o=lA|&#r>n|#lxdOz*M}rWcm#8_$W0J{d1J?mz97kCH*P! z_fb{iMaZ}6C0Tm;Tk_cWZ+YCXQ687R3H&mDe4;pibYbPf>XLcl!qL)b$+JsGONINx zXlNcRiNJBaEU~3W)&P2{aBfM3cyM%8x%BEw#3PVz%SSTDrbW;re(-loipA@rOUvuU zTX3yS)qfc*%4CXlFGwCRTUJ!C9V?b~e|MsWlHR&gd8^+XDL8q38zrfXo z3uPTvd<<7J3c=qIw~nc*k<9NOQ(YqI!;q=@pOSqCFcN;y%yXddyc+SZF}0!_t}c6D zUfsM*GU|9yo^O#A$xt`@-7Zxi4*>bnUEo+(4fgDcU7#YRBDzbhXxarV)jP7t@(q%C zRdvZZ;Bh^;3;4G4P85$o&JwW-GCsMB7+oaZ9tDGz^vUMQY&GJuQQ-Yg6<13%zyg(s zdq>X`PmYG!>U*QltOK{<^U;;!vM~|yI5bAQKc+^!3O8(km6}Y^ei`h?@`Il!ZzWpB z;;?YfSm?bvE*5LX!i|(HcR=!%OJuQz3*>QCR329>Qj9*Aa=2BcL_d?~8@`ao zrZ0ib2WF9BBKld*imFUNw^LLZ3>#=#6px^9<@L)p4~obplsI8F|e74u>0 zCf*w>MNeEe4m99nb%%@OotG_<$Jn*<*l?w6&fL0En52p?$H_UVxB!;rHmj?huvr;o zWwAVVY?aO98r>Y=8r>cMu)!^vD{c(bio2og2KdW2v>V`(V@QOUzZihYFQ^o(n26UP zSL_PKRc?p&mx?a~^W1%+Rf5=x*scqdgx>^}h`uf@@{%{?u?LPKLmK_Uy83)^!*1lV=ZnAWR$Emy7krRg zcLV3+eVMs!lRP%6<0f@%c_D+)cO0ufg)Hk~nG<{?u=COJX#2$F z%F=YawIvyHdTE z|3#^WRIPuHCx`J}xY~8EWV7OKdEEGr%p5mJaXdl05;_YlKz%n^(CC=plgAuFf^2rVG6{{_pA^% z?g@73QAmAb&)TZ9!0T(609-$i#XA2j8{hPnJg$D=JqcVIUy+Vr<6X*rzAVo>|0Bg>%Y8DXc{y0rVbX2-azcFMx*%J**q3Ch z!CYLPt>)+m;2*OAwZF@m;8I zD)Yua$n#!kDVRfP9A23Sy8i~0Sn~kfXC$<*s;E>fo>(fboT%K*mWgUU_Q*u#^oS=X zN=t(E{>MZ#u`f)l6>m>eo)-+UA5RqiBJp{)^a~{4*Cmom-j&DR&GNYBQCWxRf1nPT zMWwLRrtI}|dy&Atyq6iB*Y_e(dTTF0Pp%@1XZETQ@9hPi;ugto{XGgHlHgnSme%c# zz2Tnk$li;oUTpzHMFyI?$M+^@^O?QtfSvNmpM(4xRsSpyFYgT^`w1khTO=#mr37Nr z74m$^<6HjQt|_H4XNGE>v4&nB@MgukDSoLf~@Q36VO)ubYJR+k$Y{yd4tpyo*xV)dj- z(Zx0R&m?N_|0bOy;Qui&ivlrjoFpydHT!_og6CFHyK7`cqD>hvyX%G2O*hMU&rLGk zu&>Ba=2LvQ510+{#Xi#Q`~ALv!fUc@#~bq4-X$G$wQhIYzL5&4SF84|se@&S)%(gx zglO8gM6^MntR^9t2(AlYy1t0envxYVS?bcyP;?(3zC1`CxHV#$7TDN^=COb^V!$MV?QAn)6Fp*$|T01A%aUin>+J78&$oq#KX zB-Bm8NR_-?Ev^qzXRHVsvT{oh#Oxi(e#zT@pPE>F90d1Gv;^h)+UIb2&Je^?Co94@TfI1(;2zy(14 zWw@MiW)1jPjgw)v0=Dxkan)pH^u+SX(0xxyE*syM$M&ma!S?GVN9hzkKDiFoE}ns0 ztKb2h7(Q1lo?ImUJh`}JZrwa_|K$0FwPoVr$p9^To?qv-8DeqfX2h-Mh@m7b|x?DPP>N+bQ6b>>h?S zU|1H0k@x-GPt6y99DiEAXdnNReDT5fpXQ5=d*tPdD<}LMmX_5t`K}4S$QS>ZkT3qc zzZ@R&#oJKf-Y<89rgZ#WV$ii-9+y5Zk6WLTc;HkhHl?f(6zZla%HLc$1;Dsh=4pCB z9$VJPJK_b*Kg$&B`Q$PWy zD4$b2F{PreFkf7L0MzSo$-D6dd2D@2-R(tLyHZ%D5Dy%Hj~CV*0P_d&gv|cpC|K(1 zH~>~5>Pqtq)%upWW=b)v>J*pc*H+~##&=FBf#+(FAZ|WDb$06kuo579{jCE)PGD-V zY)S->1csZhmlE3ewp_Z|{Dxe;njxQ4J#gU9VA6Yva>{GOx&wtD*U(xI#Ai`Y90(A- zD+~2rAQ`M*DpmT&(x>?3z@MqdFJD5IEsN#FcE!E-9m)Mze3*4C*fO(mP%C0^Nlkrj%>fNwcQB|mY<++_-tQ9`V#z`{tdWP-UxVBn zt2~=e4psnsaWDd~_z>_aw#YJTZjv3+dW$?RyIJ;G3CyJbc8EmyF38n;8>D3%TP>ei z$g0eufxbrk^^jTO-b0QrsZ~pw;xX7M(fu3XvOEUOA2nS)jC{EwGbc!M;SIsP`fQ5=Jhnxc|h+txP z4fhkMW7~VuOI-hsJT`XAW82#*1mt-aS;UgnV$Q@*_V!RL%!JKa}PJ ziw^@fk15^0XAfYr^1eL)5IM}k!z9pO@xWoK^t!{u$b4~lsaOhoR5oRRiiyUr%@?;G zi0j8+9s)O$>L@NcOc_9UT$f*1nU4mh^{{ir6LM)<{+2A9w$<0-U+VAjb^4cNEb%Sz zwT{YMIjkKbExt{G24Al)mYKELw=ttRqs>>=?cWSpdVJ?C^=G3kFF0w%$mQdf`17{ro>Sy`R_6|hUCe&xZ-d@T^2(&)<@ z6UYlR`)9IX*Zg(&P*0b@_6F zxm~_>{ziY3KeFE63UzPyZ}Im^7XI)?e;{vKR+lfk$shAgTI$>4UzO46>+~)2Wq10< zukcL@toN;iO*W_1=Jxt?H~NZW{ubZT5$84fmS=3t=<;`E#QeD}BN{VTWNsRr-3>Ku zfSOIo%F0^eYxFPoH;mjgvemZ%s@D$nEpCCmIu$*>MqhT!-@3~NU-R&Fz$oU+>+zRu z@vZQ!8C}uf3%B|XTkku4jV~u}i|?o{zWS|DxHoHsFXqc_^arOb^R3z?<}Zr@qFMFJ ze0l39`XZSP{-qg9eanU&);fHpe~a(DCSTrWsAR*K z^{{oRaoBmYBaJAjAnl$^!#So z>JDH1Qs2zwzS+&N@#(bMc3;+3=&kV;1zSLjHu{$OnubMvhXrTmgqLK5BXjb4edn$B z)z?ouE${z{x*zzqrvH!Q`PVkvvgx*NYHG@AIy$SZDC;OI$~uao=qM}7ilVlT`l`tY zim)OmVuFtP3W~5Is40q=q9_ZB_$q=TqvLn7_bZRz_Skox-}mR-oOAEF=iY2tZcV#+ z&DyA~va+Vz46(x+uyxuhMy-wArmHPxkBnK1^Zj;@HGkAr)MXpW2-tE;oo0UqYz+m8 zw4gbX0drJ}ii)btucC7EdqrhYQLE`>O;vfXEjRi97UlQad#wG|vVe8i^tIe;b!T*% z|1ihT)@gNy#=GjIWgY4LW)OY0fpgzfo(?_Mc(%zm088H1)Eklvd-Xx8ZXq84*B9D|NA+nBAxK46Z( zfEnDdwciXVkdZKZuq!=mJ}g$(X^&gmZS7XCJ(A{el=r1K_nXrsv$VR+R@ZII&Te;9 z=hb*@9oGE3(TwJZ-PP2UHgb5EZLHPWZ}vkdEwCtJ_Z&Qw=1ULQ!}gFlqx|MX@Y+he z)9j(?8`9n?{#m6_4)U;U}YdyBIk|NKz){O9Oz2>M6 z?>b-|u_ZDBjy8Li&+Mb3lK$}{nWN)|XZdo+oFQ{Ig%^Y!=A^b4#%#Ugd(%ADYO~91 zm1de_Dsloi>_oj_k0*XQqJLHkdYScMXm6r^l_G=A;?4 zb=wLfXBn8!~HL?Wld!*=jBBOE;&sqo&KOuN`Lg7xhmYF*7A(kJ{Zf zPe#}t&ggP%++IWw+GCx z|Nj}b_S%OtqPCv29$V1fVQc8M1{@woIHPrfC&Qd@6ZPY-5ScbfAtXzNOgPmiYs90T?dTiotTGYf8dJUwuN+w6+!mRftj(UtBpKSsy*+x^bLadG?D_+Gm|-Iwlk4%tGckG^p| z)#uF3jitFc=@j#_)uTGKt|%pJ`b9dEkq9I=hs zhwY)=M(n;B-Lr<$+jq$wu;=EOADyNqZdOZo+R(U|Ep8iixYI(mxLKMa=>zG5={;7z zIi?=FC#@^3!`^My&<=a6quX3by3FfDZT+q`vyOzU!70^2Mg@w8r7&}OcF z-S#e%wqJ1Ol_QQ$+wcx%uRGe!j0uhl zIeN@}NRM-9T$>}D=1z~;`^~iQo5SGjGSf7?d(7UJkvU|m_SmYOxs6_HUW3y|!*^ zZFAf)Y>(K7(z+bJv_zWQ9&wDOwc1?$_Jl3wFo&nx<}>S=+u=?3I=V7K=89R-RnRsy zkk&drVCy}-$8OdQa~IJNGv{f*tUdv2$gJJNW+Hi9ezPQZj}ND{XLmWeY%zPg!#h4< z8<`w2Yd|R7+g9T1!FwJY`M89d9uoh)^+lp%XtVNm0Un7q8G;^0XVixsYr$3{2a=XJ! zLi00lY(u+sG`-*K%hr9o$!^aWa1A;}oDqAgJ!-BvZPu_WQr!#BJ=FIIIpPX4%XIHv2 zr!GIaD0Z0Zg4vasZmYAy5iwJ@)zOz`ZbZ@|(^|*%k00FGoXpM+TXx8-2Y%bIv(#sg zrjOVQ3wkD+?K;h^=ePlLogJCh??~91#>~xFf3`Ut%vOiYEp5!qNp$DG6CtMl{Bx^Ew`4=;?FRk+QXGhhwxbJEIq1kN zF01Z0$INunnq2GJtQBr^7ZWr4Az&>lt0=25jm$90%F4{vCX|}{|EQUwZKg2!o+fiR zt+*uLZFd!Q*s80%)|OUthh@)hGw(y3&gOpWSbC4W!(P#6*1tA;uUU?|>|OSjvg-VV zIZ7cjMIvc~rkl~Uelr2a%<4LpT%FC`c5)WE&D~GH%zLj{_sp9K^WPqu(__o5^;<{M zo&8gL9jZT6=80=K=eKf`0L1j*Zx%&fRs_XFmG>7(|-ZhO$YEit>Z&y1_r z{2sPey3HV6d3nh@>vF#}x23~0^I4td4%oaEvU}~x^&o0)nLX*P*5;Px=FAa$bX?S4 zZdNBV!A;;bhqd3&P%gX23VHuc$x+NVeC?c*ckU74=Th&epwU6r|9HH}yY9DZ~0iP@X0 zdS}Kac00_K(e%?jZqO|6UE|FhiC9a9tld+3?J?U(w%=?tmNhghXzsJeY!P!?9=3I) zn{zrV>gcr(IfCPck}HR~7YJvM+RZ&)TKDu=Mn{HcTKBZTw65{y`dK>QGH;8mC5>a| z8Xh!v+U6ZuUq)zhhuvp7<2CP}+S0nrIpDKp_GS#3yP2Mh(OKp`CM|4hH#aKg-Dcyk zISbp(6*A;7_sUksn4{X0HfU`%@BTW|+e}?|dSVB+*>UDnoYs*Zb$M-l&VkA1j@s(Z zjG3iwD1B%~dyctiIL7R~8SO`oIQks@X+dYgKImvoZy(=dZ|HaQPZ-Lna7D~p%i@rA zIIXBG+ieY+v$(_B*w_-bCvSwz`=*4$T$XK>k+k-#UR!2nW>#iaujy03W^P7uR8HrJ-8q=%p5(K2X2h(G zW=k|cPC3m^aQe@8gKzdPGO-+NjmNYeNA54DgM@2>Q zzbeenyuAFp{E9sD!~ADKenEbI@{da_OLNR$mzzId3YA$b<~PeZ*k2(x`U^pM}lGzqa>pKiqwpd?c>CMovD4 zE7?A9t(^S&?xbB_Ir+7F+jD$7j^Yit_%_vtaVh>7SK+O=V4dnG&D8db@GNY(Q~4sC z*Ckis5U#`Zk1Jn|8}ZG!?X@w*gej= z-5=td3Gy%wxa6O(Ynq(Eoik*|uG$}-y=503-dE1T-9_?DT(Q4gfO`*>=i|b~@*>=H zxO^1$9xb1MYnRApf+c;zj)!2CgZ^7wp<%}wie|AzB#m&dUAlZEa1xLoc3`g`OZ@KA@mGj`lB z?|};*kQdPXB56DO1tcT@F9Kokzcc=2Ralr=pQXF_pZoxT0`9>VUcjDY#<|1d?k+H8*q7+ z@;h)CKZq0fNn9~a^}RTNKfsph%D=$v_-CB6qw>FT3(nYG`*SGy?@rDBoQ?~2l4s!- zT!;tpBHT7Z^-FN&&hqJa1YdwNcTwJgi)YH$VK;8a&ATdp0LSp7IA=HIFW^4>CNA4u z`5+#~F`P9^`JXs5Pj<}G{wc?qxDoG+GiR%Q4i4i5xF}!wA-End!R>RDpNt#l%5|xH zAGrw!@md_m?Kpw`xN@G_cj0iM{5&4sSAHv1KVSX?x8QGZ(|*cFaNhp%UpR!*^R)k5 z$$!sm=2s>z#XI9BJR5i7`FIR3!i5KFd*!$epMZUM8ScmD;;e(z{$gB?uf#3bgCn>d z=PXovKW@NX*pHvZ12~MUO4R2P@^E;~YQ!EJaA&N))~ zjo5#*dxYSsUWhw(PtbBc23 zY#py&JQ=r~s(dD{I!)daXV=K{aU36nUCWf0QZ<4EUCq4rgzM;GU z_v0(E`2*YS$M=R*d%O-;yrq0S9>PIf`L^;`aaF(k8LoRr{sZT{D^J*q`5lpW$KLnl zgK^1b`8YiAfqXt58Iaw$;X~P%s{crS3^#o&zlzH~kq2tD zR`Z=E=JmhBxi)zecVKIQ##d%no{ekMAqIx}VygfXnum&%wq3LM90;qYO~FUI}&N?f{Fc?TXj zTz&!%*2|l)<9vBDZhch#7FTr1f8h92^29=ouk#ss2Cf*8XXBiYOk8ZSAYZTqUfajQH7Tg*T4P38r-1TVz>xGL2? zUG*2>p$xeN*O`9`ncUu8IAQ*UT+)x=>TLOO+-N>woy>c1rTMgR((mHzS@PG|X+CM2 z%>TmG=0luGPnfUqHJFbzCcQK6I7nWIGZ)I`xU)n)ABPvom*DJ!<*RTWZo^*u5N}7Z#DC;7@u*wA40mjh*WiIC<=e3D zDfup3+AVLuqtD5`xN@WXF}C!`Be)R%iQ8XRK5jpaCxCay&Rx^C&#!&(NUmIr>voe% z@fa@04ZABp4oC25xM-I020S!dUWH5M$ZK&Az7==wt-J#V3giF|&Xu3QWBbT2;O0X4 zZJf2Q{3$M2AjfbO{w>x1AmwAYZ=sxFzSwU2-_IyMMqNwq&-z6g7(RbGo5@ZGo*Kb&fRn(CiOwXcyk;#xe2>z6723disd zIOlZbTXEnldE5coUwP-qQ}FOgc_!{$B_Dv@&GHer-XkB6OU(B%B-h7fsrEO>E3p4& zc@^&U%D3Xm+vNLk9expy;rDTLo$9~ES?%%|uEgVuHQo;MO%loe?TVf2wA-xZpkc>Qo+)y}0Oq@_ng%L=NKiALUnZ#n1A`xCMWShvUkB#rc28 z6AsjPM-p-_ZrUmr;C{RaNB>e@iAVpI&%kA4@`b7P+vIC-qxn9I-CH~%Z&kB4wK z?z5zCuYU^{SmjSs?eWjJ!lvADkjCe=%e&y1L!O7T(&QyLk}jW_a)x|4ZX7S)h1({` zFW}5c@|V~>S^gJ~;>iouKTDSKeQ@1$xeSl(C|6-$wtOD$*h#(uH|5B;;_8`l0N3HC zap^4Oy|{L^{2{K~Q~n7j@ZWeiU-^_0ji+QUc{Xm`TV90ycxfu%NBJ_GGf%z*cilD^IgG>j1Dtn&@?qRsEdPbw2g*|yX*|O?9}g~6z6e*8%1d!SJ_9=r zRenXPzD)Mv+{5J%?l@9@11Inx&O1u^Hk^61JoRApw`+;K8!oDl_rdkJB$c16{1lv9 zEnkL*@U6K0ROL_LrW*N8+`LR4#+EbXUvc;>d6z@f-_~>Fy>VB)T#EZv$fx4|2DuIo zUnpOMvo4me!0}7uYjI|id>ih*OuiSFtdbwWdCl^ZxNx<+33py0e}sEm zaINet)%fGr$~n0AI=KuNdE^zi;d;3xmES0@!yep)9c{{A!XtPy_TQxZTO7x~eA+Nzvd?$|ItNc0KaKHR6E_*;8!DBf6 zF!k5BUilul@j>}WTp5sSaqdI%s#N<8@(sA^QTbu)$Isxw|0?gr-go5purnfW!IkgJ z+i=?l^7zH-|1jPeXMd`EFWfOC?~j9D$YpqRi(G+ozLZbF&Tr(4aQ?UQZ>pbzoul%?R6Q=o z!G!XYaQ&EkAuin}ufc=kGPd{U791Wg--ieA6F7={aN`8kzk_oo%U|QVEcqww*-_q# zhp_W-jz8WBkKoz3Y-hD!fZKPIm*U9o@^b9VlP|#|d&q0CV^4WKZkQ`Si}UBnZ{nUp z`CD8&U;YP=E|4c2q49Y3lXGy+0rFlrSS%li%MOx{#Dfdvld!KuUXE*$-+^uB)1|od zFnKNZFP7J(>W`4`$G#)wATB>jeiPRnEf3%b{t34)Q9k)djW@JZo`b{3%B8sV1o>oa zIYn;7o*KCwmo1Z@!=uaP&A4=hyai`nEdPnUm&()1)jxleychPZln=pqtK>=?zEWO+ zEB_;3h5N3OZ^h25<@MOJR(=)_-ypw@<8AVHxc(+N?I`uvd9yqN_dF=i#d+o%c#_9| z5gvO+J{reD@;SKQeAQ2~-jizontUfVA1U6RKZcue56*gB`DPq_LmtMXZ^^&l?Dyp< zM{7Jacvl?Stb87>8;}pjl^@Gz;fg`|Qan5)x8nZKWiO6ykvnmFRPM%+ujSWqhxsC( z~|ylwKqIEE{+`8Olm+t*{CLtcgZGUXd^ z?qvA^Ts}n(rRt~3@8U$39L0ehTls$@*M2J^Ksd7{>k*qx!4kr=i%gYmXq5*7<(R- zkH)3lavkmr$(P{BM!6LSdgPmMPguSecfBG%jUBJa@8fp-KU~~iFoKec{z^Zi}5IK#g>TbZ^15nKW_a{c^9tuSl);m zK9l=!SycW87kwosaPT{M;&GfG-^;nUeMBz8T|di*;%z-x=rOIk*fT zj0>i!z5*BH(^B>Le4H~~_1EC~nX(r*@fln+h0L_P)=E|O2jRmtbCnEiV`EgJjRt}`< zE9K{K@Ob%k++HPb!4+rA)+&v!_*{7su56Nb$Bt{|Lvii(^2vDgPWc?1<&#(A*j@5X z*m<|yf%9+>m*SUj4SpXt<1N^KkGA(e+>5{2e9Q?`RP>q=j2`-ctIY-i5KM`aOEp<0=r(7(@)a) zx?Yoa#5u3ad*Z+*`5;{Iro0pnzAc}Qo8FNx!TImXYg6_3=2U$|`8_EopDASK`@^_% zKzr&Rr*>^NEDE!rYa#-&lY5Vw3OFHF^cC70p4ALQe34?Y!- z{HT06_WmMYjyrynZ^nVG@;$faVX%-WLa#$fY>DLOun1@nyJcsq#Cp{}?%hi;tDxO653;ot4VB;zsN|P5m1_S@{fX zIYZtD*W<%+1fPKYXR7`z+`3%81m~S2Ux(W+lyArF_+i|EyK(PDs_(<`E98&x82$l| z{zrL6jm8&RBkzcZu95e|mbG#*&c{dLN_-B^x?S~doOh?}!@;}dM{o~*3CBB>e~d#9 z%HQBbK>jDy{vmmXWg3s`VR<*)+$kT3{Tt*Xu=`2*q*VP=@;P{@TW-OP&&YS-qUYp? z@$g3ZMVw>4oiVvS_2X*u4UI{Ef*r5P-{VsAEpN&EpH%yPdBW)$U;exDPB<2kXX6p` z&G*Un3vkbe@?z}#SUwed%r|Z(>zi@@=km3<5VzxE9KfZx8#jER_OIghE%Fz5ATIxn zdw!KCouToSn+J&``!f?)e5rZopUIChW!Scs+KQ zXOJYfw=tFD4{!*7gY*7Y{U5l#hCco4sfOKqzE4!g1SO!c?Tu6#NkzvPW{PREFXsJ z4wvh3b-8>~%16mhrRwo#xNwQ`IPR>F|Hhfe%9&^D^>gqXJbIk+#W?>2c^MAlD{$~c z)xm`ThwG-zdkhW23w+mG{V5=c@nBFUfOoX;?lK=e#OcVGnM=@z<5F!K0hxTd?m9 z`97Temi!p*!O!8Qx0Ux{*E@0)$MIHNwORR;dX1-MP~IK4d?Fu(vpc!NV1@d3@*g`-z-m zJ?o`K76mgnG}TjT?9-L3Kw*nOK^ooauNd_Eq%Uv9+%0r}=s{)YSz z?tDvr3R~WmdvFte3kQ?WYBu|42#4R3|G>RC{X&hm?*rw#VAp^=A7_3jAB=19@!0=~ z@-uMLr}CvZ5SQ2BE_@5_#&_eo-&Eg;M@QvXQu!ZpKMwvWe}pr)$}#NxOa2R&;E5M$ z{8e}l?D<>ui*OG<31^Qfug6h*InLUq{3e`-@5dwfNu2wS>U(h^{tS0mCT>5TKc#ZJ z{7j&3ada1X8+IQqPq|qAjpIGA`v~O=apEZXL|nE+UWxng4Jn_c{2p9gC%=q~&X$L8 zUGmxGrvE?UP_yj3ME%RWLe9o5SIK+himT(#W?b@$`~$Z1${Clbe_^~cF5jfQ5WC)x%W-?3d_Eq2TmBEOdr!U_ zy9eYKaKoVd8SeO0{sCuwCjX1QL-N#>>Tmm3@;qEUEHB2*Kg*}!%-`knaA4Y`?Z;~+ z9>dq;yq%Qafd}!!IFhUU1zfs^{1z@dSpEcu@c(eZA~`8nK!`*32d@{e)8M;^viZSpU8 z2wPTbe3`c?pMd@A>T&gV_eVbvdu+rE*P z;C6fp?#AcfvHz*Q2|Ip}uf{HX3(ms#;V|BS`|vY(^e1gEjK^?4&i+~X$G8Rmf&_6m7aKk_H6*!L9;skEPS=Jr4uW$F@x(vA!*Nm56 zP1z-Xj`MbqM{s+l{5LM1DvxW?c%3ul8Q8PCJO>Z#DIbKp_LG<3ZhSiK!I$IggH(SD zE?Ou*jw^5vb{wMob6ij+|B|XdT+X~w{SO``&&6@P1bdb!zZA!gk=yVn~Hj%T?-M(@Oau99|=zh`nz4Y}~k3z6@7gD_@HXuaob@*&g{3Y`H;x0XuO& z&cxr~Qv4UL^s4=|R{Gy5FTi7XDb9XD`T40FyK&PdM z^)E45o{!7t$tU5_LitkcTqJvN!(#ak++8MjVb>AzM%=tqj^M0g$mO{GOnDjZKTEy{JL_aO4&x47d9LzDQm&Vu$FUXi8#wn;`F&j6B7cI* zuav*WSy#z_;{H}S&8_ifUn5V$4Qu5+aLgkYDd?L<&4EV?MbL_dhFtfxBOjf5cfm^558xGuLXoJ$NSezNq>lT=|xKIF8|Caelw@ z)3D=Rc?I?i$SZNvhq4Ekek9+AM?aIhaAZh+4G(=Te}v;-$UoucsBFDf<1710o{Sye z%K5ndfARr1?`Qca+^|hP8M|>E?!YT?C-&lQ9Kc~5#{GCR9>m|{VZ0Sr*e7pakDS+O zeBpFC8+W?o0vyGM;jW3wPr;>= zI#qrXTTYX|#RWC;?>KvzoPLw~S8}>M4Li?}XJa>Bh{y18IHy+ib+{N`frHqK9cQY3 zJ$B*eaouv|n{gBV5_jQ0aU74oS>q|MQ~Oys>uh-uj^JuseXjBqIImuA!KL^nT!RDH zeV*z!;>`2q4^s8`2OM3Y+Bi?0QUo3J*RmhjIB6^82_qD1VQO zo{}xMYdnQd%RAvwydRD|qx=}`eO5jjS3D;-V^>JNAytp>!-dZ)58(m)4z7Jc`Ik73 z6WFs+`J_A4|Kc7w7x&^~T>ql-O5FI8d@gRrYjNw#${)jC{5p=~p;R7L{a<+OHF@Hl z>Yt-mo{l5$$oV)cA}_?b_*gvfzVgd(@B{f)?Eg@H2VE*w$GxAb{$xD(nS3^m;+44Q3+2~g=N9=6?7|P>Ox%s@zEu62xbJIu5POH^ zA92k$@)*vJ$rJ9GD$Sn;i!@c+s+>d+l0N#R!@HQO7 z)9+RP!jtI__D_+I!y|YF?w+dLgMC?Y07vnwxM7;|&vEH=c`L5KlkZc1+HnES-AVNo z*fB${$HP0zH{#J8c|DHpD!+i6c9TCy<@j5ixx4bMxCCe3&;FXFJP*flG49z@c{%Rg zOFjv=?k%5-8w%v+RQ+7}COm-G;~0J#5ALJ-w{ibGIf@$#<==1^JN+6@Kc0od^HqN^ zj^h(?ph)?pxN1N7Hr$UN$9?-N@5jLdK7pwlhR6Xv(O=ZfT$BrZ9O}PC?`C}X^m%qaWN6UZVwhDQN z2Q}XErSfcCSt&2V5qx5*{y60gxEHU*CC4kjA9v&KR6Xv)!79~%ioK`Ezu?Bx`bOHmg4S5sk0u za=8#k@sT*vs{B0MeYM<*%TAf9&okf-{2UJ82=2n4;p|h@{#)FPf5Fbvl#k&cp4yq3 z@5=YaVe(^fAFjg#coiPP>u~gRZSQgHI78lqUHEgHgMY+%c-#i{uK>@)Zag3R@ew$T zm*L2ndi_;+bh+%sndiul;9$M{9Im)f{veg(Z*b8?%74LuMtK{qzgV93sKyh&MBWPz zHOc$qx)!+-xBN$5ft_pQ)i`ghd>0;^+(V2p++I z<1y^&(s-O3RiA^iaXyaW{jje`_2szcMY#r#;Rf9QlJd)O>C5uoW*C4Y;1`sJT+(L3^A*n?e>IfNH-0A{jGJTf3Ap@wIr*I2 zT=biKC$3G%e%y^W;IggCpTk*y$#39F{24CzTlu%R){(V+ef#}#Zmk&4)3CT3+|XH|BNlW%KzX-JpE~nzbaSxTs(vq_+H5O?As{3@>AOZA`PApRZ~&sA>eR(~`1ktbsBJUJgX?<*gI{YCOg zxZnV}1veDSkEiMv$}eKyV);FsQzn0hd$IEw^)KrvkJ^8T-T%sQTxyxVef_dMul^6=skp+bd=K1% z_rt|D<&`*Lm(R!kGEhf zYP`;w%6G+ixEPn=3f#S`>QBS{xDgNHYjJQl)!&V)cbB_T-a~!~7wsv(hduZ^T$-5|5cw2bTq<9H%NEOPan*71Be)B{iyg-+{{=VU9bQs@5+^9% z2e((rN8>oI$6Y5XcjMlZWIqm{EWe0zs^uXZ#Q)%mQ z>TktGXUPv@f1Uh19=kx^gsU!;Kf|4u$UorGOXWXt$7S;5u=*QcDer*`SIH$fw^=?O z7q6Bt!j3ECYq9G;@;$h`ReloJUng(E`R(%Oc=&dC1Uv4Mt*@xRF1!QI!g<(okLruC z6CZ`6_bNXV4+rEXJob=$H4bc$@5F77%3Y~?{5p1ZDIdmNA$cp#c|o4^s>U<)s=O<1 zdPCj^m-fkr<2rl}uE$s527CwZ#v5?xO>J)@9(h}S2e-T<5993j3<9KlcGoX@qr_i!2h5jWroo7A6nJO}sSQtbRf+gpYU@nyIL zufqZS1n$Fc;}QHF&f22w+uu-sit(BY z@`j`x7Rw12$*9s9$a~!{5JOBuW`t) z{AV1;mbbNkIs?k5;Koil4}0*wIDt!W{RY(^i3f3I%3aFqasK1->Qw$;`4(Ipl<&on zC*?;|_4rj>{gm=Av9ntq#q~I&-;8&h#gZRZz9aVHJ#ZK=#C`Zw9L3jQ%PZR6?bwMQ z!?m~vhwx^c`KsD~hqH0|JL+#E-UkQqLhO7^?T^B>_)Hwa7vaoa)nAS4u@5Kkf3fd% z)xVg^@dvnklk%^z2XDnOJmp>X{~M~GgX{4k9Kt8z7``OczEAD1PqoKS;7%M#wa4G$ z(l^z9EB4`W@2P*jJ$Bx{e$2peJRcX%R$h)h_*@*tSK{J5ReuNe;(KsA_G2G@2>Wpe zC-6JCGGE*O7I$KMMB@qKTpYrSa2VI(KD-h~a2p=P58)^d;o>>!Pe1nJFR>3Nupdu) zU;XXGb8!$KheNmlhw&{qdoR8Iqqq?VaWn405&Ry`-&^fRa6SGLcVf$C_Aj1}<9H4( zFVOZ5!+v}MPT)&$*3kLN1nfuFU;~>5cS014JK^(_V zVQ;bWKHP`D!}SL$--?|F$>ToM>o?=wacH6PeQ;-qd~hnq$Kp6%jlGLhe;+P9SnkFV z{0a^qqI@$>;4Ro!syv=*kGG}TAF4d-BlhQE@_{&rPsEnR$}hl9d~2#beg@a#4{#9w z4_nH#y&XQ*>-+KEIF6TM@8PPi!%@5%7apPfc3g~~#Dlmu)gFJEYJa5K|CDNv9fRs$ zIo=T$m#cmrF2##e_4rg=i7&=}d_9ii$FTP(ZU1E)#b4mUqm|n}QU4m3$k{lvLY|AW z@nJX@pPXusFT`HF2K(@xIDV0~{}8q`%0cYM@8Hslm4Ai3coh5axKA~n7%suhm#Y2w zIJ-%{6(=r}AIH9x^2<1e2XOf+<=^5;obj1n-`lKwCtSE%J_tu}CC z;Tux*_bG43F}xn<-mkn1H{$1TGv0)Q_BKoWgy-To zJ`xwM*Xvc`+y~`zQaN6Y>+$WlGobna_B|v&k0bakocply4{;%m;#T|%4uAD+BL z<1Kzf+na|6@rk&(Q~6~$dxLxpjy)#dhI6~*dvO>)jr;IB*z&mQzs8mLcU+ApMAcs} z-UUbT0vy9jaqfS${pGk3ug1-I9S-6R*z$zhzlbZJmEXf2{5c-PKj9d*e5wBDKBx9O z|Oqu7lT_!eCJn(FUM)#Imd zYp?Q6IQMn=eO&p5{51~azi@4z^2uLo{FXQ6U2*g+d2bxY3vvG2%8$g&xC(pl>DY@K za35ZYv-`EZwYU;{u^-=ygZRHVhBxBkceK4txE#Nqs>fS!0{@uG-&OlLbLisZ|v_&4m)lqpHPT-l}sz1Ij zmG6&3!}5tZj+=4ex60S!2;PJnzf=AtF8!bUC$7YkzSHY_@EqLtgX)*y`VskD?EF!_ z9OuX7n{Y9H05{@ZocW9Dhj0-8gX6y{-#MoKd4H1+!jVzA3WtA}&&RcY$k$-YpYnR_ z$4}whzm&g)3-Q;fdOYEK^{4c2)#u|VJ{IS0Q+_cHYjCMc z`OP?r@5eFRjdLfeK7zyeOI$li`H#3BZ^M2(ZbbVdx`XO>#Bn?u=VvNE5clCSTt8X) ziP(?N!rm#$n^NuZnpFF#%5TPv_&(f;AH@m$0xrx_`%O542eEUS@^5i9PGB!i|55v+ zaJuTVu^-RIr8_D=00;4LxH4P$c{q+&;?^0;ufrjHFRtEM`J*_NBR`K5_)T26i}DX~ z1P|jV9>v)+Rd4x8;|<{~+=u7j;$2mL5YEq)m*84ljV-$=KR?wTUyTRxEjW9Y>hHsq zcmww1=W%A9>Nn#c{yx=y59PmO?`%2kXN|WH&%pJ2DxZxz^W`EO!i#YnAA`MfRKE=O z;d)%XxAIlE9$$z3cpWY+Q2m3r5_e+{eg)^xRsCjMi?^iO@1y*uRC~M?H_ub@@(x^#J8|>T$~WR5ehbH!D34-Gh5QGu#1nqgcx&-K*s@ghi*Y4BIaQBW;Cg%& zb{?bl>u@%H1c&fTxDRhh<;T)rTwN(o7*&58@ov~(qr3!rm&wQCC|-`^coi-^UG+EN zO6;~1JimNY>8*wMT4#zH3elIS*NPY%;@LPBge~t4S zRsRp}!;}A1|LQMReju*CL_P&~;)`$suf=^gseT>KzFB?<=i}#aGw#Fnx2XP89Kt{2 zL2ON^zxiI(Pr=1_E*``SQ}ws1{^we&J9q&JYhd6N#LL-C_3kQvmiFL??JldRD z?3fPi2#pZJ*%ms4!$RA#HJXKNXTQZVjcjc~I0zwgeiJg8Mzcl;zw2{Evs zUUlCepX=RqKlk&er>8te^O;X&u4F!sxrX^l=8T)nw=!q{(sCzrKJz`y4a@_~jm$4G z4>A9fxvtZ`-u#d3@@3v(d1vNa=0fIt=A)P!m``SIWWI#C?oRu9E1CP5f1&x^*8i5d znfYPnHs)dGaprfJrkg8#iCw-f=3M5ohs^K8+{Rq2`OHT# z_cMQ;IrER^mojHFU&LI%d@XYs^R3Kn%)e*uW`3GE?N9dg$C$I3KW46B-h9R`UnBFb z%q`3ZF^@8rGj}|0U;iZLN#^e|XFOs3)y%ofKV>dp{ta^(^8j-r^Goti+ShxBx$Ak$ zpE7qdZ~m!Wo{C}X_hFu7KAd^<1?x{_E_l)MSz`px z8@2o@a|QE<%#F;MpV{&DGVjJb{<`^tn5UV)$=vpa^~;z?#w=gO-0-I5UovO^-SQup zdzl|&&UnlEHJZ=-E^|I}+UIt>4a{3Iw=nO{+`)VZbMHUx>z6TCzH9je<|^i;%yrCl z%w5cvF_*n({yOGH=ASSRG5?Zzg!y;OnUm)K#9YNZEYJKVbKSpe{`<@g%o~JPz3}&l z-OPBGcjUD9ZT@b|Ma%~>=l|RKa^^PXZ)^UP_2)C!F<-%)`5)_VWX@*pWUgTT9difs zAoB?G%ghZQ*w=fTd7Sw_n*X8o8?0xSZ=88E=1JxqnDeJ?em?Uc^P$Wm%*Qj2GJl6T z`y=xgGxsvDU>;;{WlsCp=HJHL!Tf9HF6IZBN0}dE&Ydwo%$(2sCUX_@`^??U8SC5m z$o3~pJ%ROeup`21MAZ_u*=uMybW_ny7jv-=Wb+qKjup2!rZE{*wXUF%+1VKGdFBy{ZE*?nODhYS>Mat@&(IJFb^`1 zFxPBt{XdvHn5UUbwy}Pb4ejzZFlRGoXIsA~b1riUb0zaB%+t*0GPiAO{s+um%q`5L z%y%(2ZfEl!V$R*(@)OJzJ6IlNZf5?3Ie$m%H{HlCPt8u2^Ozf$iiv5^8?H+yIQ}RxqmmyuP_fWzr&odyY(M3Pcv^g&yGLii`Hi`moV?d zJjz_eoSkp;kCWfS@)`2X-(xP=)A}~%8s^_JSMFu~!^|zr&od7#w0?p)V{glwWZ3a{ zG4I4YzK`_>Gfy)wW-i#*`g-Oe=IfZNzGVH+nd_LlnY)<(#N4)@&3}@)gZUNa5$3m; zvkPthG;;;>h8x@EnPlFYIsX8gU%;GwpyflEyAHB^g8CxM=QC#sK+4GT+ZU&ip8IZn4dOU7qw~b-ix{9E0zyq9%Vk7xo(m5 zr!Y4%pTpeDd?j-~b1U=Y5$1ovJk5MBb3v)~e`4-9%JS39Bg`)|Hu)vP5 zig_RAwBv35BIc3`%imxgth9U%^9XYj^CF;_5e zvZ)1V^%pajF<-&l#@x!>#r#X=Zsy-Jk24Q3H&ol# zf0eoBJC@&5&x|+wN}j(nZ^}Hm%;xXFoPUPpLgqo{66W3->%Sq-d?9nonbzOPTyU1< z+nM{%wtOG+5cA{A8MW5G%v|`(Pb1idKo%M~(9p_nI$y{>2<)1S*F#n1<`vU9#z+AyRz}&(70`my->&y-H<|moE znLlPuyU_ainRfY$n6sIine&X)7#eY-#7C1HKg=D>4=_(MKgFEgvt#n}l^?=t4J-7I%8XD~m)T);fWT*GV>_&;mqUA$1qPaf1P=n z`4r~fJ?!h9!(6tf@x=w&m@%wewZMT*h3y!d%FF8gmiz3g!~#pE8#* z_c2#6zs_9AJi}bYy!{Tm{+SPBu47)p+`xQ3a|d%1a~Jb<%-zgw%)QKaF!wY6hIx>= zmwAY}pLvA&8Rk*uHO%A8|6!hF-egC+eACSPG0!j`&7AuKyS|QN9&NU~O!*qiS1Mm? z`FiGY=62>u=HD|PTU1$Dv<_zYKnKPL;*vXDRn>m{~mpPX?wdjMqP!?%|MlSa37?vZ)6%dX{uzk0JeTp`^1??^_`pC;MLm2UefVbz60a;Dehr%p zdEdkL9%^4Z{IddySC%ipP8jmOhaW%8x)14<<-^Mq>v-S8PkqJw*<0B9%JQSeXV>>V z{BVi+E9jNwE3p#>HYqnB8Ghm6*1biqj2`E|2|Hn61MhqI?jx-GoL(986Av7Qyzk)) zORd|P=TCVq?|b-O`YUtSv+IvpzG%aB*VlaX@RLW|`r)75OT4mty7AfL_dWa+{nPZy z@~hTgcYWW(ca+=u%W`aeW%)Afgn>=U%}0hGKhC-adS&!^;Um8OeD5C*+Qa#)4)Zmh zjPeU1|9|)2+T(5g`T%kNRo435{DR0he&6Hxb1KZw-^#qQe07F>0eRoUH&&X@=K9L= z&C&Y4htD|4{1LqUDa(gHi%JGEj^Fq2X(yY%h1b8bd~S68`yM`biTNM$`cszY#ZN=V z`o4$Hr!V9B%JSjQ{F5Q?d-$wVZ2gnymF3r9Ck%Pt!%u#j9nYV#d{^Xs58qa0{&rq| zW%+(A9tLvC%}0i>TxuOoGj4y%=yCh62_G29@VrRHe@8O%n zcXxDK?qFV7etG1551+oweE2nEvc9tX%E8-^1(u8S#FN@XB*}-^1(u95;Q$yz*S$_waiE zN4y^-)>od(`yO8J7m4?egjb%+`yO8JFNyb?gjb%+tH=8n?!Lj=_b;5cI^^*8O?v+| z_-cIO*na1AzrP(5<%*F1zwd7?ynUY&z7~#88DBatd@SJ_*rcK!mv7<>yMKHm?87UE z-NYA!Ee0~Y@8NU80~or~!Y6oT{5Py0zI`SG8Q%Bswe;cTq{J&@K73g?hZxB4zK8EQ z+qwh7KGs(byNQn<-+d3it`R}Q<$`o-7@0~>hX z!&lc?w><2_D~H|0H)AIZY~X!g%?|xd(e35WQGT4apJ({<5-8T6IM2TJ^_jN5GJ2f< z&gl7p@8Mg*6D!QUh+bJf{`tuF@PqWz^vd$x;cH_c)}V4e?+e=AOC#rd-$T8?D+4dSC;4L zo4x$LhcBV;r&pHGijLp+@QdkRqF0t5iM;RO7v5~g{}H{ie0==AhcBexY)3o)%5!<& z!|VO3@&46#{8yGQkB;B>@Opo1yx%pv@?75c@OnRNygxR)vV4BDzVG4n{@HjxZFuFm zyzk-le%t?~SDwrJ9$xRyjrZ%u`pR>8-^1(uyz&0t@XGS>`S(4%-v1l#2M(_+AD=(p z!|VOR@&4iP%JT90zK7TQi{t&q;g#j%^?eVo_an#qlfx^^<9h%Gaw_WK_5S5}KXZ6x z%!iG=@8R`+=Xn2fcxCzc_J-pt39q-2u zuPh&5f9mo6%lLUf@4t-s)%e7*=`QR3JWw3vBSOZ{1M%~@Vm=;q@cD$YjwgOR_dPC8 zYpY$?&(bT)ck=jnqEz;x`m|-^1svvafw8*H@MgrzaWmzK8FnUreto-yM11!{gu4#N29nW%-QA z`yRfI{t|lSxxDY;_3sDZ-xI*?Sy?{5KlvVB|K0%p{Q-F8xxDY;_3snl-z$Jup3D0l zUjLo}{(S>@<+;4?;q~ty;NL@lSC)?-4}1@=e=h<5egeF*e0=_V53he;0sr0tyz*S$ z_tluhaaHtq@nDpnFbB zZ2eE^mE}hx?|b;1e)Ac7*!s%yOCs-kfBWBM*LA;tRz&&RA>;dJeEVy^HTZyGW`f6uU<%kuHxLufvF z_?BnQ??SIEAD;dt13Bg9Bf}RzXZ|pHW%RiGZP*C|8+hNt4?l1IYxK&P51);lFtCC5 zJ^aM5`LpPiF(1A$d|=4?9=`Sk^G)>1@-^6T-uKDzzi7Tq$ImR^k?6JS54#?|{w4Fj zrdO69iq`i%eCf;P|3a@UU$n6u0CLLBM}}Yiy7^&xW%M|IQ`iXu8+hNt*NvHfn_e07 z;p6+S@8QSjH{2`CcUitSy8UZDdieIYZ2j%&mF0)h!^AL?SC)^T-})YY<&613 zdS&_e`H}D8C)UF|KcuC-Os_0o7&;8(l$(zXpS!;Kx9FA8n*9=Z7C^v3~}=GX5Jbe{1-_K!*1{d>{RCdS&?* z?1X_0yzk-5^KJbb=#?=a>xUmNk|FPV_?*4W-$t)2pAmWA!{_a7{%(3@`7&F6_V|4d zUrXOZuRQCc+rRJOdkSp*N9dL1_Wc@<(A8CDN`OHMG^$T4OU(CGuK4CqVW%Qy|R2EcEZ3W<>n*9HymVs8ND)kTz}c& z0|Ob}_wX(BKcH8h%ljU_i~eSMW%;b={^xu6zJu-fSJ5lW$Jd|l;j_MM{vmp0d7J|b zZXJ97`Y_PuX;oA?fb9zHPSeV-hE zvH6UB?fz55EFWGrPKLbi;j1`*J9=gL`26`EzKMQ6dS&^k=<(0@@C_w){3Z0t^6}%J z@8Kuu%juQndt$!N5viySR{P`Y!HT`+?%JT8+559*lIl|We9=)=B{Qr~q z9)99T^X>G?@)NtPd;R+!ex%0yZ|Rlg7h)$2Y*KDMGJN`3<{zb3MvvRh@{;f=3}krU z!#DoQ{B!im@~g2E1~%}%hc91c{_ph4n2+^4HwvG^koP_O|%&($X#(enR@PQ%kd-%f7&HtHRS$>uAS?_!JQTi9?mE}7k?|b;hwDja_PtYsN z*I_3NY*KDMGW_Iv=GXs{-F}tPCk$-heGi|uf%)y|l~KVLhYt*S-@_NvFQiwN zZ!tdWeGk8y{t$X)`S|ri-@{LCWXHc)m!DaFB3fVb(ZjEqXa2kN%JR+F2?Lvyn~w}% zy|MY_^vdXQ{#w(+r!bJ=eGfmqsrg&zmF258N%*YyJ$z-h`Mc+{66G^vd$_&)>d>uiex9{QbguF3T5WhJy$L8ON{r$nfo7HorZ+GI|_;{QSfB z@R^60KZIUcKK}ig@8P?O&6m+D%g4Vz^F4h3q2|w`SC)^T|N9<3`!Mt8(<{rz&)<9x zpGW@#dS&_Aa1Jq$Q*J&od<*?bdS&!D|Kr#R0~>hX!#9-J@!vtOjQQ~K>;JxoA34(e zL-fk>E#d#O#X!dTzK5?l$^3Kl%JP*N37_@8hwoo%{w;cC`K;*v<9qnw@0kCLURj>Y z&#v!#_%-x9>>tjT%ko9o2?Lu{)Wc_;Ve9WruZ;OP|5@P!L*DoB`Sgd=E6c|}|M(t0 z=WJX5Bzk4}QS7+&eGflVYyJXyW%>C3_wqe_X`T72>6PW<*MEEuKYgD0pV2GJH$~6? zd=J0;Li4|+SC)^jf8WD5(LYMBEZ=0OZ}$50J$%MRw*Ft~mE~t5?|b<4i_MSIE6Z0z z&!2q{zxWdKn-<#bS6M#%dN3KtDK{S(zGAugz3G+F=fW%;7$@yGY@!}plKf?ipEAX$Cw`o4#6yVv|p z^vd$_>-WBgFX=IV3%#;@PjvhDJ^a-D=I^FgmR}Ni-@{itXnufRS-v~E{Jw`z>ofln zy|R4xu_77BDK{Ux@CEb}^vdXQ)mKNy?|b;xKiT>p(ksjJ>Cf!#$M^7^^cx>w_a9~X z#o-t*ka7IJho7cDWOMV%@|mY5eAfFOK4Yg1lWrHTuPi@?oiMP$`o4#s$ThzYy)x$G z{Kc<-`5wM{XY)n$%JOaDYhWN_ec!{+(3j9F%hx4(t@k~A?Jl-{8NIT6b>w{yKScjE zdgZyi@8Jt~we`P6uPomc-Tr(Jzh-yywe-sJRng<8@8OI0Fn=+6PWnu@eS1 zIDX&5&+KFVxAe-GkK>R3{=)b04F%>OpjVcUUqAOfeAbuDKS{4FpB-KQzK379zxlt? zE6eAH%YcELa`Tbl7t@c^E2GEd&zl!Mg@Fw3d-#Sg+xq|3gn0=9lU6Gs`zd=Rec+@GXnYpG&VSUl%=p^gVpq$>x{S zE6c|}fBPQ3mVO1jvV3uL`F#)HMgLQJW%(tM_dR_2x9s@8zm?tomE~7P-uLh=^mpp~ zG0Vr_e>ERHeA*IQ|F=4S%<@&y`kF6a#rY4?E6c~fzw|xUuQ|omAE8&4FOAmsJ^U*A z>v;Z@<>Twm_weKNuXBB6`S7$d8OSL&9~nO9+jji#&?}?I<4<#X_!I^*yzk*VPc#1! zy|R2`^!&s3@OjJ3Z+wv5ew5|=`2-jl>-!#l>>~49(ksgk;NZiM_dR^q#pZXUSC)?- zzkLti)L{M~dS&^s@Vwfu?|b-~X7gXASC$_(K70JWhcCXy{MYG~+ z-+z4%pVeyq@AS&@@#BZ@;VbB8=#}Nmqwin7hi|{d*59NkoNt%qYvb21I3GQH#_i^J zqF0umO1Be;oO1J#;b-nNUr4Wv9@qca7U5GE$nd_0AMG_?Mz1WN9XB2;>6PWnuoDJ0Sl{=_`a|X~*ZR!z{o5vd z*4MipzVILBTj-VLOQQR~@00Z>%-^c@ndNh_69zUo{(9HLxBk<7H@!0E6PUx6TQ~^9)30bLV9KS`1gmt zhadge)<2Y9c`omJ_^D6KSI{fVS479}d-&4u0x{;EO0O*6jGZvBNrg|ohhP1v`Ezvn znK2*Ne`ol>kk@=<_`1)`UrDblA3uNfJ$%FG=5M4|mak4$U%S5V;n&dLMz1VC5qaOk z52kIDT(%y1W%+o0-@`9l&-@eg%JSvW`o2#tKmA{I`I+V8=U)x3hc92>)_;#)Sw4RK z!1wSi8<+!R zHt@cOAKJ+LvGmHAkM$GC4ZQCYKhOM9d1m>J=<#dF_3(9@n7@=>Sw06lZhhawubglG z7J6m*rf7ZN!)I=0{sDSr`TFSjv+v=XwlM!1y|R3r9scb3_dWa+{pa+`@(ZHl_dR@C zmaV_VA$I#ymamSy@8PSrHop(OvV0qM!oViw<|D(;Y-7HZUKu@ZKkeZI0~y};@NL=V z7t<@tr(-7!Y~Xzl-@dK+GwGEvAM1}qm*4mB^*QD*qgR$6!%i63V13`iSMF&3MtWt; z$NCGx2Zp@w;j8I8>6PWzq_69J4_~sYt^YfEW%=Cb_Un82s@=^G&@0PN+VZoe;-@_IIYhtzbd-^>Rb<>v#*$r`$D;Lp51)O2`Crg0%g5h8e4ng;p!s{XKC}GF zczxHy_cQm?E6Z0$-uLi%2if}1(<{p_!A=<1q}+UD_{JjhWAw`C^TJ2``00E2ri0CY zOs_0o6}|=rGS>G!{22XK;SF+d|53(#tUraFFtCC5J^aWawthanGUmhAr-x5r$on3? zyu|#W^vd#$iC*h{55JoJIC^FI@MBprka7IJhfiN*>z_)mEFb^Lr|BP~SC(IhoiMOTx%tTO zc}Ls&BlODXas9`S|GtOMJjVQ|^vd$>*a-t0tnYjHRmYm&>M*4_{br zerI}R`S|re-^16???bOFzdHK+XWu95f78}4*80ry@%bxsJ$!DZ`IG3C<@>@pz(7v9 z`N;5V=r5#KMvwCszkcI;_|}tc{Tu0(R~N8S}Az{P^d4_=Z!= zKTfYK-yA)E@;!XoQuAZ<%JTi#2?HA(zwhDuPc#2My)vfa`1_;veGlKb%=`}F-(|x6 zS6O~xbpQ7~{HhxBd(kV)=S1uK9)9p_^Ck4k^6~liJ^bog^C#0Q%g2Ae;CuM7bIe!M zE6W!|m*4mBS?8HQmtI+Z4R*r7CgtWM!%v=X{xW)H^tk?Vu@eS1@VA3lEl%J=Za7n}b%y|Vmx_!=0Xc@5_8qF0t*fSoX~ zf%iRp!6oMJp;yLy9DhS}{rMig>oW6?(ksj7M6bX49)6(F{EPI;^6}%J@8KISH~%)h zvV3KFxDa6=r`&vG_>mRnKc-hkkISEpoiMP0_dWdNRpv8F!hDx8A3lEmp!w+G2d*}s zL$53!zkcC+_`>i9CYW1FuPk4LoiMP$@%tXW_8RkNm)P+uV?K_55IbRD1MmA}{p-vx zFEL-pEWa2y40+$fH*@~YCFYgomnV9y_dR_2N?U&wy|R3K|M5Ni1pOcBmF0V)^?eWD zalNho9KEu9MdW=CKS}?0dS&^}$on3?^#)u2KlIA-@$V0OpImB|pYP#ITg~r9uPh(_ch+Per`&vG`0O_GC5!C%mC@t&+lrkquz~kI z{Hj~be|?d8Wz2_H zTmL8Y%JR#x69zU|-}msfx0%0xk*%+c`B;AhJ7Hi0?|b;tUzmS-k$Gjzhwlv^7|8Ix zho7OJSY%!q^WodW2L>{{@8R2SxAiwV+`O{-}msjcbKoDSC(%`w*x>6~! zAY*;sWBm^LJL#3>i=yNAJ^aZ3*!uU;E6expm@GfLzVG2%o-#i`uPh%wfAKwh#*q1E z=#}NmuoDJ0DK{S(zT#Q)uhJ`{$N4W_KYR)U8Q%BsE1x(2DZR3M{P^vA_%ZrTjq%g3+(`5u1YALf5duPk4O9e4b`hcBEke+Rv?eEj(9 zd-w(aG~Y+BEMJQqx4!S;8{Rd)nqFBxzW@6kesa?M|I#bVw_(Sv?|b;>_svhzE6Xo1 zK70G|J$x(u485{^M&x}D-%dZj)b9Vv^7WDTJ$&ZB?fAE&SC*fSyzk*Nrp)g_uPomh zdEY0;Pk)GxpIJWs|9{(E58pj)>zC0h%lB?z2Y{S%^O50eX3U>RuZ%t~e8jh3-@})D zYW^I0W%)MV0Fbf1@8L%_o|oJ9)6VmxAe;LEy?O@*Y`bq-F!R#K6+*ORnhAQzK74*!u(V8%JT8+|GtN>&NBZ7 zy|R4#{vW=FZ{EiIyY$NPi=)f$d-yf6PW< z^?eUNwZHk#=#}U4zK3rk6X})Z2e1S?8I5m0nps{`(8x z!?#>u{zH0Y`S|r$-@|v*oBu+Y-Tsv2r?C?THYqnB8NTfz^Lx@OqsRSkW%$5AhW9;u z-o@s>La!_zUS3Xyyzk-rzh}OJURgeV{_cDDitn30n_gMI0Xtz}gX8x-{NNAF|A1Z@ z^Kto0uoDJ0@VLw55C41XI^Os2W7nGhJ-xDg z{P)AYho4+&{z-ae`375l_V|4dUvs_rx9FASJ0kCU_>3FOZ+LV#UoOk{e8Ii|aw_WK z`+jMDJ9=gL8tjCD4ZQE+^X@ReH@!0E{Ri{Q=#}LwH?}W;oO1J#;qx9g-%PKJ9+!V2d|)8M`yM`X!2D0> zmE~&^z1I64zJdPN^vd$_?_YcmpY@2X-$$=3zdAbqzK0)u%=~lo%JNy!^FQCiPd#CN zj9ytjFM9m*J$&}F=Kn>nEMFR(Ki|VIq2KbDaNW2pzXUsBV3Ue^_}=Gi{hjEQF(23e zD(r-T4ZQE+tB1|+ORtRi$?YdP|GtN>c**>c^vd!xSUe15tnYjH@)7f=(JRZhr6+vW z`yPI5jrj}cmF35c&wAg(ul`^2jr7X$@$Vme4?p^<`4)O*`JQNf-@~_#n(v}lmM@NO zzrKebdc%AVy|R3K|M5M1``^t!La!_z|NP~9_^NU9FVHK?H%7+hy7}uKzwhC*b~QgiuPi?qt?zsI^xe&GP;R$>W%>B$ zXWzr;=bO)>SC${fP8isv+-!$Q za&PlT&?{p;)^Cf>pYP!(_cgzSURgf={gv#QaKnW%=cH__LSa_waR>n*Rm8vV461@jZO!_sn8I$G<>TAG@8K)1w)NLf+5JaZKK}iq z@8LV?GwGG*^1e@wpMEF$X=eGU0hH)p3D0lev6PWHuoDJ0DK{S(zWGdd*kUE6e9c=g;@>Ll2ripI%vh*p{DN-}msHedZhKmF2Ue z6PUtqxF3czv|ECe@?F~Ux}SCut~Z3$nfb8oBtiXGJ0Hp@$WBu58q1v zEWNUP{Q9Tw;R^9dB*%V=#}Lcgv)?|oO1J#;m7Gur&mUg%O88+!%xwlN3T4W_dR^t zkR5*`z4Bb%_wWnoe@L$^UmrdG`X0XV1zZ0%dS&_5*a-uhl$(zXU;dK$RrJc}asG1C z!>2Hi;e8KZ`-=Go>6PV2BJX?n(oyq+^vd$_>kqz%FMQqnoAk=^%cJw>d-(3R%umxR z%g3+Z`X0XiZS$LcEu1fx<#VI&ADWLIzIDQUF1@mRcI15zzv?~nh4jku@$(Ovg1F6URk~iLL(}y|R1*cHH{Dhwq+mew1EWK7Rc6J$xVi z`}E54W%2pveDv_?nYR9>U$^_OvV1NMJ`CiPn~w~iN54J2GJ2f<-0*>c4DWmRQu;mU zl`$WFJUairhhMmr9sgnU%JT8~^F4eKeL21IT;BJ|@zZ}x$ImQ35+A?o;d8dO<3EdD zS-zYPV8|&q9~r)8NAp+GE2GExOOMv~J$!Ah`4)O*`S{;Y^gVpj9_DYOSC+5BP8itW z_oe(as%kuH-SDKF=zWPA(+tMq`4@B3$@8RTj{zK8ES%KXLj%JT8=Pkax*@L2Or^vd#i>ES|zft+&lk>M+jH-8ho zGJ0J8`1fDFPmaIBe5a0|Sw8;%_xfB9-~Dy-_tPuOk6|YaY;gR(hi_eM{tf32hc7zA{CbP+wl#py6_^F?m|0cb%e6I1?^Y453tTyw@ z=#}N;-{1Nke*6~mm(VNA$LsqZKI5n6ucud*&xwxT_waf2?exlXdEdjA(09`-&*gm& zzl8pe^vZL2-@`Z1KTEGHAOHP>@8LUsX6Nr!dS&_e@zeM4t+$!~2fea<{Q9f!;hTPG z{y+4}^6}%p@8P>T&2RJ#yZ~( z-NalukYb=9x=ZSy|R2WcEZ30>-!$Q z^HK9(q*um#tlu2n|9ua?@Nx5BrdO7a|NhGN@FP!}KaO5mz8yPZV1wiLJ$(IB=D$s^ zjQKeJp74Po?|b))W`XO>Tk&VRk@ z6aSp~pUE@J7slT8@Lj{^@1$3jFTqY2*reQiWccwH%s)i0j2`EIRrtU_hW9;u>x<@} zqgR%nO7vRqd-&#;%>RvES-uK8VPJ#xeGflAV*XtnKQrdz_{*c;|7$*a_`+Asr++id zcUgV`cEZ30>uWwTe95T!E$EfeWBp?6gnj`u(Zqqld44$9yThvV8pa3%-Z%qyHMcvV8phlfH+a_@}LZGQF~VXY~EU_wX(M zGJiU~viySR{^NW2_J5ndlwMgrBf9`8OW*dsnhlF2Hi;e8K3kZbFA&@0O~ zM1Mc-d-&po=I^3cmXD9$_we=f57R5lXD3I$_V|4dzmonXdgZyi@8LV?-=SBY%ljU_ zkNz`yW%;b=_6Pd5zK371w_X2-(ksia ziC(|=J^c87=1-zmmamJ>pYP!-_c#AtdS&_e=WpM`_ZOO9L9Z-78m;ep_>Ke2chD=# z4@KVh@O1~8|1-U^eEj^$_wcI@GXDm>vV2MO{mb|86Nj4rh+bKKRpfmS-&SgV>yz#F zt1O=#{r^+Ghwnbt{FmsJHT3(?E6?S94?jeIB)ziy5O%`ACgtWM!*?vT<3E9389grlD0afY2HyAZ zji;MGjb0h^;X9+}FTRJ*TW0=zdS&?d5-z3>6PWlXOG|a@U8SeqF0{F z`yRfX{!V&j`S|s7-@^}_YscS1uPncie}I6Ta`TblCoeNUNUw|@=Rf}Y1>eKZ(7#Bp zEI%Dxf4+yG`o69I4!yGcK=l0A_wc<}n4cH^&;~z0D9gvM-}@eZ&6Vc2p;w;E`yM`R zh50YiE6eAHDHzBpHy^w3`SeBf%II)V_#VFZm*!uhSC%iqP8isv+j1-*k`p zuhJ{a$IpL!pB(?a=1B!7y)t^-e!A1cr!bJ=eGfnOp!qxLmF0_~%kO*moIjd>h+bJfe*bUZ!&mm3 zUrnzpAOHTt_weZtn}3;JS-v>B{Jw`TdCdIV^vd$_?_YhNT>jPOKhx!BmXBZm%yB(@ z{?q2?f7|YV%JTiu`S(5i_>lQ+>6PWvqwk--ho5-f{O=U%}0jMc**<`^vdXQ`)!P_Ki|VQzheGmdS&?z?1X_0*7rSp{{Nb9pjXCxtRFxB z^F4e4{Z;hJb9vvx7ty!UE6?S94_`vxNv|v)zkca^_}ahOp z$on2X>m&2W(<{rz>-!$Qmi}~lW%>C2<9qme`pf8*<#UsxUwiz%hi|2Cp;wlVU%&G` zeEP?B`ERFJmT!sH_kFTH{exPcSsvFR268HVYIQw)FZ1*C%JQ{|Uh91ipZAF!|C{v6 z@|oBP0~@UGd-%c`^B>SFV?K_(G<;yl`yRf5eqL3W@3MUS{897K!;gJt>*vrb%V&pq z7|1w&-^0()?_FiB~m+#>_=b7K1URgdDJ7Hjh)%DMEZ-Eq z1_m8bALjg1=#}M*BJX?n z1-Z8V8T88X%Omf5_-6V`=#}N;@87mF45>&-d{82bf<>uPi@=oiMOTx%tTO(+8SAlU^A;uKzXbhfiT3 z!}}h7_+ax_=<+kmFO0nAqlZr`HvcPnW%>C1|9lVMc9{9+=#}N;-{1HizGad5&*+uq zo3IlGHYqnB89wU>^SRY_`%y-Z^H&jFe&54aA8Gz@dS&^k$on3?vdnx9y|R3A;Ndus zas0kd{L$vGmuHrbe}7Tw`sDbT@73|UJ}<1f8eb?3tzYeW_=00?{a5IfwSN5nfBPQ3 ztK9q~y|R2wbpCt~pLx9bPw189!grSDy9J`o4!BUSfX7Wp@23%dd*o_dR@LmH8riW%=IZ z=+_><@8OGVFS-uZDVPKPT^O51FPBXuZUKu^ke|h-8K!*1{e97tN>*$r`7jB*K zS?_!J+Dpt|L9Z;oGJ5{td-&-q%(v1j%g28|;d}V@73S}zSC&uPaNXnged4b*|DZgx zd>(efz$O(wwYwg^|2p$8(<@^>&VT8?;Zqp$zK2hL(EK>PvV45~`#xE}&-|xapIN>a zJAQrF!#DiVeAXFu`&X79jJ)sROZv_4L9Z;oCi1?A&-sh_MfA$@wRZTk=g;@>&Gd`u zmE~)q`>*feCm*r(Yw4Bc)3FlkWO(1hPtuRlE6d04Kk9q<#n0LLAJHqz$N&F^@8Oq>nBOwI(1^zhW%*p} zgnVfkMXd`yM{`J@ePnE6c|}Klna5{(qVOg^r(j)?+6OY_NX0>){*T zH-8_!GUns_#lOGvJ$%)a`9XSR`K;*t`5wOh1M`2SSC-F=u0P+yH+^FMeR^g2`1!N% z;b&&dZ+NENewF3J+lwUwIpyXf!;f#cAUU_&(JQ0J`HNq_@;!XfJo5$g%JT8!ukYcD zGt3`HuPh(G|CjIK>*t$4lU`XqzW@0iK66v^E9jNw*MxI`ft+&lk>Mxk+vt_iGros!*~5JLS$6wXmd}jdf5i9j9S544 z)}BA#!}l&SUq-Ji&%593`S(41KmAgAW%=A_ecva?Pk)(?pIJU$f7JEx#63oO1J#;nOcQ|31AkdYu0{?1X_0yzk-bFEhXKcf)*_F&}<7`u~?TA3c28 z4d%C_SC+5GP8ir=ec!|P{m}d#^valz_2b7+-@{kmX#OC2W%*p}gnvV48yeGgwx-$$=3 zUmV^4d=KCKw5|UHy|R2JcEZ3W<>n*9_djEP4ZSjY+)-eA zgU{Rg8=Vu@b6LJL90LY2j$iYU;TH^>-+^8keRBNR2?HB=-@|9VV17S(Wz2_<|9;5# z@KyAO(JRZBM3>+9@EtGP`d_D4mLCqsfPswT_dR_5U(KINuPk2>{rZyNSs$(M zd-y*3L+O>}!~d3-4CIuXj|`tZW9y$tuZ$kIpTY2ffei0^_)+>AdS&_e{QDk0>r-3* zQhH_i%w+Yo>-!!)kN$dkW%>B{eGflDe;d8Be0==AhtK`Yj{hEdW%>B{eGgwiKR~Z6 zAOHQj@8R3iHchUZSLl`HTcgJx-zV#DVE$dL&n!O~o&N&YCzn6n{AarS%<}Qy@8!Ba zIezA?>g@j0?t0w*YVn1_z$O(w`5r!FBU^t@dS%RqkDovI9zJ`X`9tZI-!#l$$aw_^vd!b(eoeQ!_RDHzLs8DK7Rh|d-(os%wJ8fEFZuAV+Fvz_fnFIs&VT&#hwtHQbId9KY}3 zn~pU9DZR3M_^~b-^1g?kq~G#9yZtK5w?^Lg$@*Wl^>?FhVwSIuZa=<vRIS4NNfe_v1{3-Ox^25ewz3<^0o6Mh2uPnbZ^1g>(u)_Sc^vd!j8`&2? zPPzHW@a5N;@1j>mkINsw{_lJErt8f=NUtnkgPkz2!TP?3pSZ#N-{_SwAM3~8zkCnB z@+R}2(<{q2V3^$L9B^SC-F>?mxbVZ)-DO zMz1VihMh35!SVYZe)Jae-=tT@d>nuL_g}t;Z~vM33+R>Qi=)?HeGfm+hPV9t% z4c7NP{M0J*f1_8%e5^l>oiMP0_dWc?|Cs-fUK#V@$D;G^d-$Oq^BdNO`7X=%gs*{t zjP*4i8NTU0^IxD>MvwL5pC5e>-}`&>yU;7k=VK=fY_PuX;YaT`e>lA|=41W%`>*fe z>;7Q=EP7@6iRk|4d-x@N=C7nzme0UW7}((WeGgy#kojBbl`$X3-+`Sluz~kIeEOfv z_s}b2K79Q6>wEa(KbwD!URi!1y8OO}Pa81*4!yE`f0%-SjN|t`d_VoX3&ZicjHx*O z`0-cs(ZiQLYU^)LuPi?h=3yXXec!_`c+7kOy|R3~zVG2%2hEq!E6Z0z&wqUnKlY^g zv*?xO8zb*~`08iOf1h4iJ~z7m`yRe&*!)lFmF35y^?eUtJ7WF;dS&_e?}vO3-~Lzg zuh1*YHze2I+Uw8v@L8{ze}`UKemQo+z$WG9Bg40>G5}c+dP1^vd#W(e>|p_{{&9KZ#yh zzBM;les+D|!*_mRv!pwdURgf={f+P8XXr1YSC)_e{c7LC=WT22Uqi1f-(!bAd;GqK zAEEy-y|Vmtbp87tekj-0zmr~BzAk;;^XGf`lHJTdK(8#HzU{i+_wc2MnSX*_Sw1{0 zCj&X<<|D)R(!Wfvj2^eY*!v!S;a6<^f6yz><$Vv|K|e#UEMGn^97Gt%IDX&5PkhzZ zUvRPA|CHt9-@p4FKJ6IuyV5Jm$N&GF@8K)T%^ygwEI%B5|MWe4-tp#-rB{~E!A=<1 zq}+UD_>v0qr_d{-$Mshc-Tr(J-?G^J#q`SZ3!=vl-@~_`Wd3S;W%>MY3>e5be&53n z(6`Ym%a35-5mXF{6%=hq3 zwYL73=#}MrqWiD!;fw0cA49J!AK!m`55M4I^QY1)%U5G33~W+vJ~Dh=gZXpmmC@t; z@$NJ0eGgwxe>uIfd`ooy^F4g}_iX(i(JRXrMBexCg_oP}qF0u04wnG~IpyXf!}rnO zPp^y~mp^|0Ki|Voudww8>6PW<=YPJ3&u=#W61}qg!szn*9)9sk^JDbN^6};OJ^UK_ zf6*(;$N&F|@8L6Vvh~w0vHPF0eEjpL@8LUcHb0+USw8;#yYJyEer$dRdS&_e|3C0O zeDf{lcc)jDPtOWBf-sO%Zay;n%HNp(3cWIV+jOX!v5 zOQN5@d=J0mRr5E~E6Y#W;m@9b-@{jon!lS~S-vmwzK37%n)yC@W%>Td`yPHZ{WJ8+ z@{1$yd-&YfZT&atmF0UP?|b-R`YC#4`S|ZYeGfnJhOM7|sonpTB4Ww8}O5z~QAs@ecjs4>s`!AH=N#1S0IHy25&84w-S+A8H_qWlmDb6f+bnsveRcBlt2l?Bu{!@mQBt_thX@KfdQly_r}$FDp2{2Ax)`QPXM3*_DQCCU3g&fzCbp?^)@ZC{Kl z*?|k3f1Kye|A+J+`~21R>GwZV!yLZrK>ET9`22USgA@WWN~XUV(mm*7fv-~#){IsBq(`dRXBtjGSP*&7b~ zIET+ajQ&n}xBVpY6L;pW{w|!Ca z@f+vx-P7sE|BBBaw|)Bin{f_5=~((*<=yu4ll|iye&~4m8hN+ntcA9ALj6RXVO0*@3wEk zmF&O;&OgrKo6e&Dx4aumasG|j8xH$8hhHN9fxO#3{r)x1;p@)k{{NGA+mFMQuz#Gx zH=aYk*@b-mx$U#R9{(*L=kPrj)9)qkw(rN4?7&4;SdWHpYoR|}-iRd z4qx5I`~Ri9+kT?HfI(M<^=SCUPWla+_54?3j>oSxd&7Z-_j)w^GOgdWncj^#{DgJ0 z|6~UmKF;Bb9^n3mG}F87)8}uT=k}LBS$>(?KK=hsj&u0N$GLxtyxV@k26?#yjq{Il z_|if8TjkyMbMn9K;~c(ki2fh)Zu?2e*MD&iKl(QPki6SIKY9I+bNFTN(tjiGwr}C- zuXz7yJ?8Mu!}MEU#P^@uembsX2QI3z|HL``;CuA@UPSN4`ia?F$$Hs;vI7kt=kWQ< z=nuV!-i`I}-O2rrbNB_{(4Qdhwoku*jdS>xl?rp`)+Fz?pNuQnfeV~}oWu95On=oy zJbyRV#?g0OMDNCW`1L8_v^MwuK;CWNmwf#m=kTNJ(r<7v_jlV@Zzfs3lJ9t}V4NA&aL-I(W|KgsV8#5sJ^A@mQ*yY1)TN_OA^ z`^P!__(SRY<=t41{pVzFIPBvbzDxdNdAEJ~^QSn6FFuU>uYL)ie{TCWeF1{T`Nw%~ z|D)))mhV#AS0?ZOIENqo8U6R<-S+9@7w7P^kE5@YciWfaN_OA^=O5?reaF)uBk#s~ zJpSqHU!21i%%DGA-fiEV-3AUc_K$P;9{Cn|w|(2nIlkP-Iehicx&It_w|x$V{o_36 z>*#N{SKGI*^6mYr!yJD26#Dz+-S*|l$4{KY7oSD{guL6nDEa&m=kVn->7SK%+ZW4^jrH)|xRM>XfRA(dzGnK*<=t2h zpML)v=kP_B(68E(tq*NKEqVTUJ?8NBm(p(_@3v3>{jfNPA8w`JM&7;L=Vu#b2f8Y( zN5jv!lD=HtjrqjvZAS9-W1PeHUPE6g@3v3Beu#7U;%n&-mv`HjWaoebjq{Il_)7T` z<=ytU4>;`O9KK%ue0jJ10Imcd=kQamF3G2?bDw>#yR}pYxIxFyY17TAICZT$iL}dly}>wfBz}Y;hTo& zKa_Xdr=P#$9Dd51^eg?EAAid`d%W@d=~?C5kAIxQ=f6Y0uDsj(r+@ww=kRs^p)Zkl z+b_uf_Wp4WKk*~_J>=c?m1}?7$2olc*YpRVNO|{IKF;Ci$j^{> z+w0|P`TlVZ-zk5Nyn8Gk=kW98+vMG2`8bE~k)JE?9?Qo$e4l)eyn8Gk=kNpa&&j*T z@^KEoSpGeEw|(8J-+unYIsDXB*3G?dzL0m@r~m#&oWu95Mqh9lKmOeI!^!?}4&OPB zzDV9}U!FXF;~aiSev-V~K7IX;bNG4R;r{!{yX|Ku`^Pza#oF{o$-C{-pI^s0e6Ree z@^1UeWdAsaFIodyeun%`@^1T)#K$@Ow2ip`WO=v!G+fCJTvUbiX!y<|`orbj znB%!KGke2>uawqnmL58|2+skMpm~ z-f-B*IehJ=^tZ^n?bE;i5$Evjo73MT@3v3>{3FictGA?oLf&nk{`@D-bLYPm{YyT7 zwSD^c7i+^DzH3|hFXi3#Ew~aMzc`04E1_TKa((=&?MoBy_4uFRXKYWuqrBUGB=K<$ zKWzv41LfWJ>DOOz4qq#Og1p;4_rwYJKhAT068AsPUTvTL{I)jC;oEnlzfRt5pT7U& z9KLfW`dj4P_UZkPbNKo4J@Rh*Wq9Xj2f8Y(N5gmR%>7@HcVnLW_)EV3h;#U&UFrWT z@3vo-_&A3jzXyH(Z}|Lk+n3-X8N9A{vciX2w ze~)wcvc0(fe)4Yn^z}E+;k)FIl6Tt=<4QRHIEQba%>B=nciVTb_H7^M@YVa%x5~Ti z)1QCDIsBv_(%&lYwoiZl8|U!N2hcwt@3t@I=`Vl(#W{S3{Il|I`|M>gcc81ndNh1V zCHH?r-i>O~QasF`*KmH)@KSADY zpML!m=kPP+cb9kDr{BNCIs6>?1LWQI>FZaV!*|LbEAO_Sk-PUR?th%ak2{$6zggaG z-<k054PSH!_y3c;8*@B=()%Ch@Dt@9k$2lq&Taz-8vDmN ze69Qo@^1UWoL=GM9KJz*vAo+pw{h4%&U3zs_y1#iwS5h)L?7nxy@%3&Bk#7Kp7=P2 zpLiJky0fzDq3x>^@Adef;TwNUzm>e(KK=X?=kRlWLcgcH+rAZ7vI7@YVLdu`{)f{a z;qzBxj^|JM_5Z{$hi^K9zER$7pWgpChwnX-{wjI5{k-JQ@8TT3@o4(H2qGRYkm3Q0YITd}J z!%sezezhz4{Bzs)kuTpr&fyDwM!$i)+dlpJAh zbNC6hJpaSw-S!g_ALsDp@-yV!_UZY@IeeG=`SNc2^z&z&!{;Bz^KX-P+ZQC~ALsDn zj~c?bFYHaSq?|3!eX6dAEJ` zvYI>4Rbf3EzVKxF2j$(E z;vBxeo_?jP`21bo+3n-}hqE^vxILVIoWpmYO23i38|&d4a3wo%0Uzh^C8yKxD(}X6 z__^5|4*NKV?~^}7-fcgHe7TQv_`(M6f4sbVEFb6a)8x;QciX4$|2T*5kiSaaZJ(Zh zoWn1c|BJlaKHWdg;Y-ip{eMc{ZJ$2>;~aj0{A==V`+3RZALsCOXLA3K$XoHzc`2Qmfu9)J(iDi_+|3nlXs8h;~c)Ck>`J)yxTtg{v*!e+s~#y zQr>MpnEd%)oWnPsPk*kw+rA=s|HV1{eEBQo-S$O^k8}7w`J3e3%RR1S2QI3@dNlmv znLPjdentO^yc_G`Te3GC_Hhp1D?cLdwojkGaSp%eLhirX z)qMWC?bDCHIESxork^11wolJL&fyo!?=0^g%f~r<-9_C00C~54As@cwk6)a_H_0C* z@3vo@eEk{c@Y7qk|EcnB`}FI_IESAtf2q9NesXgDaSq=ve~rA`z9jK+4nHd2A@869^ZJ$2>;vBwU7WY3$-ff@0{>M3d zvHaokZu|80FV5lT$)6zawokwRjdS?<@-yY#WBE9TUm$;jyn8Gk=kN>V@053s<>MT_ zPyQeBZu_R>{U7J>Q?BIu?|FH*eP80^9KP_k^l!+!?bF|Xk8}80SJN+*ciS((mF&Pp zRalRPue_Fi+_n7pb7PLzk2$!K9k_sxbNI!x={J^lV?F%T>ru4D%;;Nu*=VGj4dP~MI8*gy4g4nN^$`rpdC z?bFxqIEQbSzfs<8KP5W{9B7<>oWl>wcgnl%)6ai#4qx_rp8o^#Zu|7>k2r^)F8`do z+kO;RvI7@5|2T)A_6P3&j=US|@%X2&zj2=1Up{YkwjQ^y9?Qo${0yz%Sl(@){{BOp z!_T>e=f8uz+de)2IESArzrVbDEFb6a^W=|~ciZk054ZpC9{#ki9=6L?6`^Pza*POtEB4!=}>V|lmzQe4RnTwwn=habL&`|l?2#(LcU&g>0`eVoI$-Ai93@3vn+ zzTC$-{M7sCPn37t_ar{f;m38;H_5y0TM{4Vx$~F*jn7|g-;wyXFo!R_pZnh|@3xN_OC)Dy&DtH#|&VE$_x0 zA3y2mpE!qalK;89d%4Gz?7#)~k8}9uN4Wo`@@}lh{^`$e;vBwB{?GDm``YX_aG669^~vw=wS_tSvd8GZly}>wkAIxQ=P#uH&JBG2 zx$XPb`1bk7Ieh=q^c%{%?WZK4|Kc2ed_R4OyxV>pw_pDF#W{Sj{ND0z`}w$%9k{3p z>(TIwp5^|B%DXYg^JfrOvI7_JaSq@0JpI}7ZmfqN!IkX51$>;tcf3GO3eSHRc{kSM z{*T9%?7#(loWl>iN`Iuh8|&fIkKZ_lACf;&-ff@${%4%Sm;IajpCj+KUzD8#4m8d` z&fyDQr@u_zZJ(dhD}0>8kCVS%-aVF&bNE8}+vMH$>HUv$_!)2T{O8NN?PnyfUvUoK z`xgB_<=ytZ$^DOW__oFL1M+VBzQo5l{Pef!Kah9Zr+@z{&f$xO>6gj7?W=JmJ8)4I z)}!I;-lJdZ|M>Ca#vISzbpJSqpSXm6V|llIx__L*H-13BoxIzA0$f&H)D+=O5?r zZSvR2yY18Sk8}8T`P=2)_UX@$;~ai4Z@t{P-6QX|PrrVNbNF#9(LW^b9?Qo${ABq) zdAEJ~_{TYXrTl=r+dh5#;~c(L{sVcpefsZ5#X05bNHF^SIfJ{@^KE|BL5e8w|zx&{&5b!Q2rnCZu=>Tk8}8f@9_RFmUr8a zPkfxiPmupY-fh1m@o}Epe{Jr+)*L>63)J@MpI^l}{Oon;OXS`5>CZ3X9Dc6+9`bJc z^!*>_@blyklXu&vuOD#^-z|TNyn8Gk=kN>VFOzrMr{^E%@O|>P%e%+&aSp#oexba3 zEFb6agYvJ-yY17*FV5kI<-eA9+ozxZ;~c(cT|R#{yot|0xBXD^{Ec(?j`isGkaycp zO?;fgFOfe?-fiET_&A53zdrXrMc!?nzJA9!eEWF%R(ZF5WwL*q!w<>Nm3Q0EN_?Ed z*Kff6|1R&gPrrYTbNGr4>0gs~+o#W;IESAq|AoBUKE3~O4nJFd-JALRaof*IzWX!2WR#-?KIMFZ{h;ztmWd$3Ol0 z+3PWfAJO`W@^1U|-(QY%_yyZ>|8jY^eL?c`_c(`N_+9!+dAI$7>^yOxtHOFTeDeJDryY16IKaX?xg*(zW`TW)P>DM1#k2(Azt-n&7SB!+t1DE6+X`4 z8%pVi@@&0eh-f*Dd;~c(E z{+bRwero$=z}aE%^=SCs-MRmr9rSMdVc_hrk8}90J?Q&7=-u|cIlaQiIeb$&{p<2> z`-O>*bNI=7(l3*DkLBYWK5sAjP5;32ciX4;KhEKY<@b?y+xH~rALsC$dvpI|<=yt_ z$6uVokIMf_-ff?cE7^gIs_Z{;4!>+N_rKu}y#H>j$MY|J{=|81|9$BHC_k#UPe1hZcXRfJ0}UVNx&6P-`~QLbRJDC&POtEB4nJG#zm#{|cO*W};mazx|7y4J{N47& ziH~#mY4YpKyX|KtKF;C$<+qS`+s{dSoWs}ufakxvyxTtg`XkQaYxbj`D(|+>{#Yq@ zpsT`qH2jGCaq@1=@%}H!-f*Dd;~aie{ycfNefsrtoWs}e&+~8d{a4$!}) zJAbXe+2{ZN<43|AzHtiopC|A3{%yFD9k{3p>(TJ}Kcc^1-iXfRA(dNe9utEAPg7_}Z1T|73@KoWswWO8=$2 z+kOH0av$gLm50!;b8EI=X#4c*f3L?JzPg%zTY0yAbFzP&!}rVYBk#6PzkZ4H-1#5I z{SWu~tL@X*kIFEIU#PB^ciX4?$2t7uA9Mdp<=tcXIESAv|9g42ePQzW#d+@h<>&kS z)%HtpB|C6YmHlU7n8SBXyRjb6zoGo>KiOd)=kRSu(Z3?^UhZ)v_&A4e_$mFz z@^1U|^H-e1cgwFam(O3feK)Rz{o_2h|1sQuTlogHeH;1m`yc1tvX2QJ{_ z9KQQR`mg2PSP$Qyz2UHrbNJSi=r_8J&tJEFVepCMp4p*`R7gb?BuELM2r!SXxV~*!farTA-4Ik(5ljIMPcVj(# z`u>Y^_;UFZuw_m=0oWnPqOaFIy zw|)Ba(>RB3k$+j{df z?7#(loWnQ%lD_D6e*C$y9=<+%!(kuix&7r!<%iVvBddPf$2oj|GxtA0-fh2_eEI%y z4nO}Q`d`Sq?b}}Z_Wp4WKjo2`y@3v1rf5$m| zm;5q$w|)Bf$2okr{QB8O`2N#9mXCAz9{C;Q-DCMUhwqg?RNif$e*TDa_(k$F zUwQ)lx$X9+fV0ECE6m}`PUrq_%e(Cxa3%OSho9R(|FOK=ej)kt{o@?I?hN{` z<=ytJxRM>Xs0!=R@FQo^Z}2C6{J1g4^Dq7RQ=G#OHqw7r-fiEydUhk(fyVxE4nJci z{qFK^`=aFe6X)0H{pZQMF~|FF8m?pqF5u%FzUK=11@dmJhp)wz?7#(l zoWmDiN#8H;#(MaMyo#ow_WB;1u^()Td^L|Iaue{s7bG2+ucA&9;oagrc z1N}7bueMMB{HQa`;V0Zef0Df0z7kim0~gpo&f#a=N`Icb8|!iZ%d$5d_Hhp1E`No* z+kX67-}Z41KkqK?f0Ml1z8_bz0~a{|IESCvMSr)v8|!iYL%&eoZC{=E zIENp4fc_PEw|)BcYn;O`kzXS3woku)i*xv8@?Xij?bH3^9KPT|p8vRc{P=O(r~Ah_ ze3ASFdAEIk_CCOYt_thX@Wb*Y@@~xW{_D!#aG>Gi9KL-4&wn?0xBbGLUg6^$e)>c7 zmGW-;j>N|~{4)9J@^1U|{>M3d-^1L$Ufyk=e*DKde8nU5zm#{|FG&%dVZ4F?)N&f&|RrvFghZ9griSNJ%GZpTFW9zIK%U26?xA`uvS^`1$f3@^1UK+@rVR@r!f# zDPM5^JLKK=>H9Cv;m3bTf1kYDKK=89IESy6e?s1EKbV|ngExS# z3hUAEwfW<7&#ial-I(L~yAW5h0~hdd4nJ`<`p@LuSPx%^E7^ey_&A53wL1OkcV)BC zSPx%6F8fb*py9n94L`a*{RDY8=I|Z3k{!5!k8}9;4d}lo@5Xxg^!XF#@Fg44|4`m- zU$93b}+t0+6?7&4;SdWGu-IM+fc{k>G{Hv4Czi|#=~wwncD|vp9_OEa|L^sf!*~6FeiM1Oefs#tIsByk>35NL z+mB10e{l|9eh7V~yxTtg{j)fSZ$Fg&M0vM;`uY*)@Y4^YzeL_`KRtQ>$2olWPv~!! zciVU6XAg9CpsT`qG<;nR{h#ICnB)C7J@Ii4zi>KzpS;_CQu6sT&f!OXM*pU~+dlpI zcbvnI%6}~H9?Qo$d|oa0UnP5?!1LE_Ka%}VIM7weJa_)`h4Le6`-)tPv!ob z%e(ED;!1Yl0{h1~{KV7g%jDfykN015_J+eg&f(|EA0+R#Z%>|oaSlKEOzwY-yxYEP zt#9ui=kN^|(AUem?T3@+Pn^ToTtt7qyxV?G;^Q2CX$yU;yxYDtdHslU_?cJH|DU|u zetzQP9KQ5M`c8Sb{oKUIIsBrz^bg9r?bEM6;~YNkPxQU=Zu|7-hj9)+|IhR<%De3+ z6@UBvALsBxPth-yciT@(zJH2y__F8dSG}7bzi#{V^GBS+cRf$PiM-psnWw+}<0sDH zOBc~^DetyVzy6JL_k054L|c``WksR=6L;@k-gzS!^b)NkopC#|c9FN}|T*(ex zz{ffKl=taxl6PZ0e0%nW!#>X82jx5E-S+9vui_lO^h567Bk#6PfB!bl;VV9(e@5PI zUyLi^{No(Hc7*;VdAEJ~_uu0je#w97-;sCQr@wz2=kP^K>A#Y9+owOjiF5dlPw7|t zD_{TI_C>f7?th%acYj7dUfykANWT32ALsDB^4rS0?H4A`|2T)A^(FV;P2O!kn0)-j zIegvM^ashi?bENn;~aj{|LAMv-S!KT^N(}*;gvVYJvO!SZu_O{@&QCwh4pCo!SVE` z%eyhpef%c-$2ojc5&Z@7Zu|80E6(A^ZAyQ&yxYD4SF!^aIR7|@pE`m57I`<;wcgnl%XD0VQ&f#mm%k%%cyxTtg{jE5M?~;E--fiES>>uawMCA@5{UG)2|=m9DdRs^he0M?JIC4J8*&Xk8}9?a{8I_Zmh@omnG*P z=ehIWoBmFpzuJB@`Te7gFo$p7pZ+0vw|z@?4mi*_|2T)AFopgZdAEJ`YmK?XKF;A6 z%D*e`wy)0rwvTi8@dtAMf_wPwlcXFFuX_kMeH&{N()OJh%Tj^bdM}wf%7N{2dN+`2KV0 zUy^s*r(eIvd2atE`cJ&S+J1WS@jo2q@Ka{euYE6{|L*1fJKz5JiF5dl%jt{d-S*?x z{E`1v=`e^1_RpWgpChwp2r-%s9cpO-v;;ymYXq(8!5ZNCgxvI7@Y*?;=N9KLrB z{m(%&D5bNJ##^qb1N?emh4zc|nNm*`9F)%NM{&lQI`eAyuV0rGD9 z1-OzOxTp&2(eNX$(Vr&o#vJ#*K6}G~hL3aj>Hns`Qr>MpHF^KXIeh03{XBWMeP8nD zuW=6F_zwL-dAI%aepKFVuRs1-{`kc?e2M&8 z-F*JJ?bHANv^a+!{ha%6E$_B(#Fgy8MO9djhOhsEezLq9bG-k{vo{=Q_&A53D?e4< zZJ+(mxx+rr;irGe{eLF!wqKk+f3zNR_=>OT&yaW9mnHkhIeer1CGu|jDT$AB_-^@| zXs0!=R@T05HSIfIG$H!kMu4D%;;Nu*=V^#W-<=t2huP`|4qs3N1VeC7tp^T z@3tS8_&A4Ox+eWw@^1SIT*(ex;QZqpzHA(Q-UEF8yRjbUU!A?-u#a>27Wob3-S+9{ zk2r^)whs5-PTp-lKKcGP&f)7err%ZGZ9fH9!uiKJ{Jc%*50ZD=7m_c(|8Wk#K>k>H z_gFs8;rryzm3NQj;~aiaewMu3etdHNaSlHsKS$nepWgpChtJ!Tk6)L(+dkbt&f&+) zKO*n8UznVKoWl>uKO^t9?@xT3!*@>L`M)Obw$J@ePIepUs<0jn-?th4`|@tg@$plf zz2QK^$2t5G`7h<&SPwrTd&6NL=kNuabN{s-m3P~xU;oBA{D6FeyxV>N zSF!^a*gwwUySC){|61OS^*H~|>kXUKhEKccBJ1T`$7{R>u&qS*=^uJWB)jZUnakYyxTtg`;&1FU%w0Y|B<}gzBl>) zGtS}5%ISY9@3!y3$!7-|=O5?rOXW|NciX35zr{KHGWiRA|J7r8ug4rde@~wOb@Fce zo@^ZsG|oTH;TOvPLEddYkkc!CoWsxAi~HX%@3v2W{uk%)^Y^A-B=5GLlk6Yo@YR#) zN95i1y@`)=_@aI23m($*S8ZQ_E7^gIs<0jnU%W5<&hl=|@$r+s{>3@`==bUOlXu%! zWVe9>js4>szPy6|D0#R2xa8+oaSlIafBIA9-S&ma&tKvke%fL5m&&{CYjS75;{4+r zexCg8^6s&GoWtk;nEU@r-fiEI>>uawjq)GMyX{+WB|C6Y71pESM}NZo*Lj%FA2;TB z|EJI2IESA-jeaM2xBc|&HgKS^f1JaQ$R8x{w$DB+a)*7K!_PaM``5|4?bDCnIEP<& z1bvIV+kRQHf1Jbj97*3H@3v3xf1JZlsG)yI-fcfS*+0(VyW|Ju-S%@5ALsB>kK+E{ z$h+-F6CdaBQ;w$J<`F)B-1h0mPn^Tgkv~A*ZJ&Pq5$EtD@-yV!_UZ4B#yR}r={)~t zdAEJ~_rK#DzU+AVtL5GH>7ReZIefKK^kIKkYR7 z$K~Dj)AIQQKv#wJX!y=E>0gz1V~*F4sko9IxPXsy_@!sjzc25`dieC`PjL=E=WP1V z<=ytva3wo%f&Jqge&ihbb$j^ybz?pDugKnT*vC11>ACdV^w7KQM{;_Fk8}9qCi=

z6o(&;LLACGu|j^y}|9haV@u(xZI;EpMHF>f;=Ky!<-y?rfC1|4VQs zJ8)5z{U^@h^M1$kpZF;EcVm=${$!Up(C~2%Um#!hD7_m?;RllEPn^RK-NgM5ly}?b zC(oZahhIFG{y2HJefsei=kUvJqi>XV+jl1K|2T)Adk6hR@^1U#>@mQBt_thX@U#9z zf5W4E{M?x1@k_scjPu<2%g>WvrnXOie<#l2$IavZ|B!dvcO)NwaSlJPn|`sp+dlpI zQJlk1c!2&3dAEI!J^;{FVLcjt@bC0%|DBJY8*@Bu`|De3vz71Eh0~gpo z&f#Z1P5-pK8|$(E99+o`T)@XUe9<%Xi{;%|55FjT!(kui@Jr-Jeg10u^zrk0%;87+ zx&Qdbvi(Bar$0aSdi!Pa+sM1^TXJW=;{4+r`!_$!{r8r4+fT-o?7&4;SdWHZ@ErY* z<=vR$@tc*s;XuR3Ieg{w^tJMC`}Ff?oWobkpCRwIpSVhH`{nz`Ieg=b-2WnZw|yD; zav$gLo$^=9yX~hWKF;Bn$loFFwoku*jdS?Qmw5gU%Dcz%aSlIM{z-YaeSLENaSmTN z!2Ms5ciT56KF;A=<=yt_^FPkvr^#2zyT|fz4nJM~IC=M2KF;B1$e$(ew(m~f|DLb^&%C^C zSINHr-eRNN$JTc0;bQjw>fN_wAEGYbnq8}|RR2=lHj(qI)RVSlU!yMFj(yjY+5Xvs zTX5qhx&0TYt8ZpMrEXQfrS9(F{3G>lPs{5o}k`p@cG^#XOb`Z;ys-|62~7pYf!nz!Ge-bvl1-dkPp82y3jTJ>@2ZuRNv z!i8Lak-ARZt{zq2rEdQR*FWUB`gwKbxZdHGw9#*gSFW#S$C%Arlb)9-2b+`Hm z^|1O(`(FCX?bUx#cdGmC)$gb){z?CZx?R1-Gd#ccZ8pt)JZz#KQSYKI-Inu8b*uX4 z>LK+7>hkTl{(5zr`giKt>JD|g`Zjfkx?4S}ep+2!!rOmG-KkzB`^5u1KfBb$>TdNu z>K=8ix>tRUx?ep@J)pi#J*4hdmv7JW=~K6<-%z)!N7WtbwV&nrcB*$&cc~9kcdP5w zJ?g8}Mc?KAe^NK7yVOnU1?qnFGwPBZ=$EML)Ss(6)p^h9@l|iA9#LUAhzdVs*Lt8g-j`u6kH~zq(>)`sdW`>bKP+>d)1ArCh(}^SpoM z>h076>hG(2zsL1e>bhOn$EiEim#Hgu<-A*6TEJjx{)otaR_o{o<@2czeaEn>dvm^%x^psnU(eNtsz=o4 zsoVD9`kU3I`?BY&`_+%Cd%n;4bLvs`>+1Fj&PVLkU)%qH^YJh0@!XHSr@BjhxH@lt z&QDbrsIRtH-=nToKd*diBxjYV{fF4)x{g5%r(dZ3pu9 zA5jmf|E(@Pi1U?S=J_=o%q~(FPG#??E>iEOE><6DuRcxProLF+uD(G%ayoDSHg#SD zyGz}nep+2|2Ip_8+tf?d?dmmN;r$s_PgXabMSrrosF8iOdh~4eo$B^;*pH}()r-`X z=W_mzx>~*ZtK7e>iSv!srRTBtQunK?)y2Q${0w!E`Z9Il`JCV6_3Cb~S3jk$oyqmX z>go&FU#UCPg@ZibF7=M;>R)mFe(DbOkzTK^SLa>G^|QQQeT%yPBF>*sH(bmfR2N>t z9#NOy$o@**s$TOoo^QLlL|y(nuK$6$WDfffb(6YA-Ksu8J)mx|zlr`rb-VgEUVk&^ zv(>}uThztB=X{>JL4BXPN&UFGOZ}`muY>*#b)ou0b+P(?>KgU9fAjI_R8LTMt9Mk7 zs1Hz=-opK+sf%xApX9mvJawJ=I(6q5gAFF%R z|5Nv>*L|J$xBPbA-p=X)_2KHKPR`F(7u~_WL_Pdx_BHC_dF-3iJ?g)yd(}^?^X}yO zx75|@Pt`T*wT5`UZR*X`L+V}C!|FrS#dq=cPf$0g&r>(4uT^)c?@;Ich5jLR^}Xz8 z)UE2*)C1}d)x+w%H+cTV_t9^vuI*-TudcqIJ=t@0wYpt>s(Ro7uD?Lt`yl&Tb>0H@ zZR%?E-_*70ht+lJr_~+m0riM_SY7ZCZ~qH*lX|r`d4G!@=DbKuHH*s{s`9} ztgcfZqwY|jqV9i`>(5gUsaw>e>Z{b{k8%CYUa$VEx_KezkE@IS!G2a<{RI0hb&vW> zb!{)_>%PVNpZ8Dp7V4oV**mC5)O)K-p5pufb(4CUx>bFwx=mfL?p2?oF6!g$U8=5D zx2ZeSH>tbScd3Wf530+b=IuSMu2etk_3GEvqv{Vl{}=t&o~zeh%*U(r8O}@8{px+x zJ^h>?q|SSeJzZU{K2zQFJm<~o?nUft)FbK+b=wP^->vRdKdi2Mk@IKOt?IYcr7v;* zwYp(|J>hNMzrvTRqEx_2cS>cR7DUUGX3G=jv+p+V67zR`vGk-uJow zV0GOR_RrM?AFzL^E*W88sV-OlLEWHUs4o1N>j%|c>TlE||K)tsVV+;xC+xk{{Y%-? z)IFcFPgd7{#=bf$dse^Onle#`6CYrV(wsrZWPOVmT^L)FD!bAE=p zM}3_-?|+=nSC6QlQMYA(!(^qryu1(8r7N-5{SWu=S8uD%%jdjIU8vq)U8$~8536g{ z#jDbvrtVQ+pdMAXs@qrN`s>vl>Q425`d)R(>RjKeu2sLNt}fvG19hu9?|t6irZqSp zr*2kHPSNV)Ytf&k?oeN>t{BJpZ`DKU->Zk!^VP-Q z;rf1ckNORD?b@7wpsrJYt?p2-v4oGu&^lbdk$ObEt-7R;^F7tQ>M82FbvZv=-Jw2S z-L@X*jrQt`?bql0x9SG<&FW6|pVXu32h^qG=^t13s|VBt8*u)Px<);!Zd0%J0UwXj z4Y|HZ-J#xAU9l18d#JnAhpDTJIRAxuM179Bc@xerQ+KOxQrB$C`JdFo6WI5wN7esS zmu|-S3+jILV)c-Esk&%$uFw0B_qSU;UfrwSUR}Ng*YBk+DP|w6u2t8l^S0#tWP9~R z>H+mt>Y}Z={$_Qx`VMu6`hIocM6Q2M-KBodejCm|Rkv-+UiBm1-(K}5>bmVX-&WmO z!Y)&HtM^xrsH@a%+jIRf>Rxr7x@HH?&sEo{FH?7@uT@t};`&?F)#~}`R`sLmk{!AJ zId!f2b^Dz-|Il9jg}P~H&I?BP_;jh)S9g?hzO{Nly|cRadz|m%xq7O)a2L*xRClRQ zQ5WyZ`T6P|^>5Vu>KoMsWnBM9b*K7Xb+`Hn^@w_rx@|Z5x79=HPt?u3bH2vMd_4Nq zo2iTU;JjSjpgv69qdrkxS;6&9>UQ-N>VEZ&>V_Y1{a@6b>K^r|x?f$nAJ@ODZdQM( z?pKfdFYizB{#?JQx<MbB5a$o7yVOsrM-JwENS*xyp}DVbK2uk#*ZPF_w^qHAI`0s!-(Ov=KGN&er>N`H z7pe=Y=&w~5sc%#tN-t2@-S>POXiKjZpEUax+~>(!&`2KAbs^89<$o2x79cze66+tr7vJJn}- z{V%xwTJ^B{4t4p-oIkD}P`|1!Ife62)J^J5KI8fIs>{?x^<00ny5>}NgSu0FnR@s% z&TmqepU%EV-Ku_4J)nL|UDCky->7@l>weDjt2=}9J=Ha5vX4@Cs!vyssxMXdUdi>> zs*8ThzD-@CzE9nx?p4=a#r3bKyVW152h=N#@_b9$xPCo#xq3(SfO>DQznbe0@_O~p z)HUie)OG4h)CJej&sG=R%ATt(QQxg@QZH0Dt6x(0t3OaT%;oK^^#$)=;ce_K)y3*E zb&2`_b(8vNb+h_Bb;BQdd$ZI%>OXjW7w3Obx2hjgx2qSaht=<^N7Z>>^85?#=IxDF z7pb>Vm#Ftu_o@%|`oGejpsu^0-Jot#U+(z>oVTlM9%TQ?>(%$GTh%YBOBQhb+g`8! zOx>=||BCmoQ(dgCe29K0b-Q}9*Q*au_o#oYE_|5&ICYWw9CfMsH|k3D&FXgbz3MLY zztnk;@b(w0i_~AL>(uKk?@q@1Y)2PgQsPled4AdPIGyy5LF9FH;w*Z&a76 z?^IW+7pj}pFWEoE+k0Q#`8@k;b(eabuX%r}7jeFmdPKdSdguktk5HGs$Ua%!{2Kcl zb@9L1?dl%&ed^lRIe$i-{e$4SuNPidSF1l)H>(T3;rVu}w^0wjNxz4BRDGnn{VmQ< zQV%R<|61MjHv3j}(L3z>)!phR)CKQyKIrx857i~=y#Miho7C&6+tk~tJJfrtyFcLV zSE_41WdB56t3FQMpl(ois?S$fenfwTx>(dO-a*b>Rs8BkEf9B761Q>V}WG z{=e!b^(xtWIrsZn>Oytif4P1eb-8+Xb;&23PgS?8kM;VcoS&&~P+zPr{FL*n)kW&L z>T31h)E(+x^?>?ib<=0Oz4z3^>QBA?bIw;;iT8I{y|#Kpy_vdXlz3P3`1L{N7 zL+TUNMPJaLtL|1`s_s?KR_A@m^}knlsQ;|)R6nR5Qa_U$+3I5T?dlr! zz3Nf*LUqZ?^v|nn)$gnGR^dE<72dxN^+xK7e9pI07q7Y6p#UFtUV!|LX>IDbant$t74Igazs)a7fl*UJ9l3qHOZ z)DzUT>u|n{x?g>mx~Guy6V%n~vCmbvsxMPFtMr#>`|+H2tJ^kUKdJ6j52))l zSA?;x<-ABdQ^S7x_x8%3)P+KHuaGDc6IY6 zT>p@|cvJS1>gvtdL+V!bSL%|@IseXTygzkYuqUdU)RWa+>Z8=v#aw@?dT2}bC7y4^ z{=Irc{eZe{YtH-BJ=?HfRrjmkR~Kx{`RD3U^{T7${0p|@e7w3sy@h&6U9K)F;rc`D zw`c#{UVVYO^t+t5tLxN%QP=Fi`D5xf_4DffNt`cH7wpJhGy8{P@bS{A-a?abx|4DkE*NHtFFQO zGos#5U9ub3m#T|)XYZr#+=KlS&&%0ostfjHx2W6qVqdH7SKqAe*_-ou>LK-g>S6Wc z>f*^<|E9hA8+GkIoUgkk?_bBh>>bn<-)HZu9#S8uZukM`$Ecgtr>R@jm#BNxv(+Q} z)Biy|s{X6GbPDH>s5=f||4Tifep%gA$@z%7Rb8+a?_ZC42X*a%T>pLb$U*F%sY?!K zpRX>O%5GOztM67<9K!jN>L&H;_EnsJsxDEF8^`mjRqv$EJCy5xpsuK9AFl4LVV|ZR zP`9Xu)Hkb(j^g_H>TdOu>R$C5>b#@5eyO@zz2XE6-rBrBUFz?sM^57U&D147 zXP2pa)fMXQI?fNbSD&qJ`vvD$t4mL2-=gk0g?*2@U;R&YK|SX$t1C}szo)KIf2nR$ zuf7iNU;k-bzoB|ay_I@cy^Ff|bgn;C-J`Bo*EVq8tnN62Jxg72Ci^CJllo8UqO&-^ zUtO(UsP0g|pdL`ascvedA5nLyzf|X)&G}l{KRkfXN9F1wb*s8W-J{-5U41Tnm3l;d zoVuim^9FU%dF+eq)xWh@-(>$wuJ2Sgsqa^JsUKHYpU?G!>Q?oK>XMn9FH_g5S6i3& zzv5S%udA+97pt4qJE;5ByQ_!PKU5c8$lI$?cdJiO7c_I;sIE|7t{zffuP(ia>u*=r zspqR}F6R7Eb-Vf*b-(%@b;BiG|BX8DQg*?5d_3BI%`Q^+swb+uFXOySJ)*9#Z{_?a z_Uhy9FX#L$dv&wA;y0XMt!`4^tggF)^Sjg?>Ic*#>ZjF3^Ec0Z|M(($&FmY=d)ObT z3)Ok+b6%m|K;5j~LEWRCqF$&zM%}BvRNb%sqk2HSP(7r6OZ|p=nR@sEp6?ptd4416 zE!Bk&az0r-UVXH>Nc{_SvHD7NiTd~MS9yC~?$_B1)Xi_P``ue_k$ZeUR$ruEZ3CWf zuX=NJzj{ygv+5(%qZ4_1=d1I!VRxzv)c;VIsu!zk)GKYs{cF|RtLxPJs2kJ=sGHP> zshia`>Q?pH>NfSS)a~kP)&HlSQMaleQ@5#~R`;IH z^LFP`|GpQm?it&v#h8xq3uh>iJCG|8mdOhp9)^KUe2n!1d>;3)EMr3)Q!) zi_|Zui`7H!U-9-nR+p#?Ch+_xskc&>sw>oG>f_br>PyuX>KoOS>ILd*^_%J%^)hv> zdehDH{#Wm-ZctBGH>uB5cc@#`o$4#pUFvpqxB519kNQq^uew{^ukKL~sGm^}sRz}= z>QB`p>NPj#{To&9qF$yxP+fc>-(QEQhg#Vuxi4p5;{FZ$O7*aMj(S9WuXSA@Vy5One+~@OBb)kB1b+!6%b&dLTb*=hxb)6bN!=Hiw z=vXO-sb}Tf`5mMl7+opX8Oz;X-k0nX)ssJG zH>&Fo-`x4ssUQcL$&KS@1(61!2|KaqX0dTJTFT|IA0 z_Fd`%-G25o$en-DR-8Ypo?Og+O+9-)`$P52zp=kjm+s46a~0m+)P2~SsK@K^n53@x zKIfCw`SMfMlePY5>VnB!e}=kIeTjOR{55XP|Dc|ly^dt>ro6wZ@j8vh|L-B*f3vcW zbM*7-67^#B%n6)-q8`bKTu6 z{Q>Iw2iZTif0#W(J@`2LG^=VZT!h<;1mmi-T2EI3+nON@4eynhSc@h&#}=Ts{6CQM}YoDy;SG7&T4#o z%Cp}?#C&u0g#WN7sS8%)>-RqD-qqPt)ouEG@>BKFRXIOd-MTXSJat(fyH!0gd!fVr zzf<>q!}GsOJyYv@)XiUW{+xQ+|JZM-+tr_V{a2j-PrWFg`>nG&@Bet6-&X35?{L1W zx^+$Ve(EKfAEvHZh4Y`Odvt#q)YZB_7pWKN@w`UesJ>NQyasRYPJ7+{0(Jc|-k+z` z%e4L_b-(6sshbV}rU!$IoeW8Q%yGcFq2=+Yt8SDk>)+5<{>e)5y zm(+Rccht>VKdNrgeg$jl@w$wU*9Pe9M4Fp;dt0do{>$D`-Tn!?TwSKFRClUt)NOb1 z_2@)*_Jt(w@9F9}`g-g_^}v0+{okmUzQ*;}t4sdHzFj@{RrcNL8E>+mR4*K4zo1^M z^{=UC|C{qAoLXCe{59ho3T6}yKWPgr?9wIY-b3Az{ahaFr>JYPKc_+e zSlzDsGef;d{!I1YKe>OidQ^|+EcN8qIiI6m(8calx7@?NU)^*s`w9EE*n{fk>>B}` z-+$Dl53onoQ}y_*JdTe?{yn@uh3XES-xlgFJ-(&tLA^irQcro5xBnycke(lhsb}c^ zAFpod<@!eTB=x20uE#mQUOh|xPW2+qA6Cy)Kc#L{zp9?{1aI$s^*r6)S6=@&&e!}7 zACEz;pP;Vm=KQcXel`>PwCXCJCwyofzr-SroCox1;S_Sx#nQocVfQcoD* z{I}|o=h$=9?Rvi5p>EOp?>=>1_M(92!#~v1U!;FYJ>gmQ+v)d=iD_SUxEHJpXq*8zl`5m?tbsN=bn4-`MvLr3U3;G`ilx*G5DK) zLE*ySm;8H$=LUb(8x;NugTKVa$BPVp+K(vxWsd)XmjC+=e)S`De*ZCpw|-RNpELMr zBZc2;@JAZ`PYnLfA6EKb8~m~-EBp@zf6;vkfApiYzBVr@{CEeySmCD`e9yBK{v3lp z@beFm_No~Co8O`IU4y^mRSF*&{L!yb_$v(ld!xV9;Fo=;(qC!t4tFPkg;MuR`_0)^jZ@YdHT{7!@4_mv8Nz~Eo8_#g8z zTE0_@?>>Xy@>R;eY4B6NNa1S+fBbV5J~sG$U!d>{4E~*GDg1JSSFS7k;|9OCuJEtB z@CN^-!B1OL`ac@{x~jsT^0AuVd)5`M8vK#h6y7oTyGa}EBg&sBJ6@V_(omcc&- zLl*1%>kPhnOW{`-{JGzv@S6;N=C>;RHiKXC5{3WF;L~qY`0osU=jSW@h>z3!KYmx? zCmH-E7-E?Iy#{Z+U*W33kA9!RyN=)BFEaQo#((VK@7MnLwETNj2EQSL-<-j}p22^d!SBl8zsul0;DGx)v?es%^QWN<5k@6X_~ z4F0MNesKoBG=sk@gI}4!Kb*m@&)}cV;GfUnU&`ROWbkii@H;a2eHr}04E~5uNbCRM z8T{xBeryImIfI{`!S`nH=VWjtgP)zj%?$p+4DMy{Ne0g|`1u+9f((9P27gNizdVD# zJA+@9!9SA0Kas(2%-~-f0e=S%i#Z(!4G*%+CLtV!5^E! zkIUdEXYd^vd~XJSUIwpc@TClHX7Cqha6f}*8T^$QjL#}1^6C;3s78Q#1I^41Q(?KP!VbGI%G04>I_< z89d0~8yWni8T^6_eqjcGa|XXWgMTQ4e>{VKK7)TbgMTA~-=4vLn!)eP;16c-M?5ZV zzmLk`M`!S3GWb(6_%ky2&J2EL27i7AuV-*QgP)VZtqi`E!7s|-S7-1WGx#?%_&;Uv z-)8W?_{6mSJ}HAQWbkGNU(VoB27h%1zdVC~D1%?0!9ShBZ_40b&fvFZ@b6~uA7=2M zXYji-_-``!0~x&XuhRN^Leg?NP_%MTq89dG4vkd-U zGWdlV{E`g*jtu^R48~_55_yf!HYD)RWiUQNkkEfUgMTlB|0ILonZfVP;16c-!#*iZ z@6j3jxD5XE3|`IP`!e`h8C=icy$rsd!JQ1gKZD_hIjOJbXYf~N@QX9}Wf}a64E~7> z{+SGZQwGCrW0D`ZF-%~%Axz+RWiZ_GC3LvOOW=RV;D;Q3KJufy|Wczztu>+!q+&rjg_ zNj(1+&rjj`X*@rJ=ilM^Sv)_7=jZXf3D5tI=ilS`1w6lq=goM23C}O%`4v3>2cBQW z^J{qCg6FMxejU$m;Q38FZ^QFjczzqt@8J1eJimwM?Rb74&ws%42YCJv&mZCWV?6J` z^Cx)z6wiOe^JjSe6P`cE^MB&`3p{^`=bd=oh3DOP{xhEU;Q7Ds{1u+R#`9i0{{_$A z;Q3oT@5A$cJpVVIzr*tZJRijK|KRz1JpT>PKj8UCJS+F0&)|6wo(JQ32%d-Hc^IC5 zf#)OfJRHwQ;dum}kH+&a@jMdGqwqW$&&S~TSUewx=i~8w0-nd7{|e71 z;dwlsPsZ~EJWs^)Bs`yj=U?ObR6L)C=hN{#8P8|nc?zDV;`vNGpM~dXc%F`D70(@b z?!)qWRh$;x`K)Li742RDTz}FXk8ZSjvvJ!4hpqm}%tM3GAOPiTIvXSI{N9#9&rTTf^~TD?^QnQ;Tz2 zbjQ

qdJxx6m=B)Ch&zYR_7eV%jZ6v%!$OgI3uqx7)|X-gtbn!3t1Rx6QbPiq~N= zil7^l>0mVLJ4|shnVmU+Wp?kQ;${?Z+8!MWNt3rmA4$A_KyVZIM$H~r)kPji(q_+V^mo)^cKY}+}UF}>*aii5AjC!FW z0HgXypHI<5$^i^>G>+gn#9=W*=Gwzi`@{u7g4Hw0`xPhzfv<>QbwFRCJF+I|4E86_vHBMB$)Q53vdifB@?r zwWo*04fG$w6{nL6dzT`hJvy5fT0xf}%O)2nDN>9Z0+-MePBAL92`{luhV4$Fy&m9r z(4S2yR8Y1{5I{YMM(tmO5Y{O7&rfvFTxyL5E;LHtDW)!GllH88G#r$!C<4#MXl@rh z22O}^GA#mtYA&WXzkSnnWI>pKMO^?yBlwQG1!3ODnBpWLR1W!*!eq*pHaMEaQQ7_wp#`Fd zQE7tGOSBGSrtxGp7;AU6h(k{@1UtP2iSx)oKWld=_a&2PZ04~)D2CdmTbO3rJr>31 zi-^8V6%qu40IAJf3}eit!$HU5W6qL70g)C7jXFu9r`$*5&WEnz(CQW&8XKL(XM|;n z>U%y4oHuL!Z0k)j~pi|5VTL#P234?gaTL;;!a=L&hU$UWZ%`eVP1H*G%Pq;T z6A}PbAu=LU_TA`mYgm-!?5M3>@-hcuOvM-!F<@b5G zNx-YDm?WfXKN1ot4n~u?Hr~}%c{ZB0lelK%<6?BdRqNH3z*-;ITG32=9n#Qd{OciW z*R#A_N0Sw!a?HNO*0*7UsDAeAM+~4v44^0a{Wc1N1?PZ_QWIB}J$y zJ`rE+Qjvm0aE?rPRS*c9=UW(9qRFDWMXH{R{?2$jEUfbjq8lqb3j#tAv6K|VaBz4u zOB1JLjD@H0xw%z=Fs=lSD}bTnvJnomm#`;7iYlm#d95cvXrTCg-~o)b_}vkTlkp83 z4-oQ0KR77eO+%z;2%VBZ=#+p$rvxx8ZkQG#vpO|RO%=4P%K;O0GD5DC;XUbOoVw;Z z?jZw`mYa=}MwyM1x=(}{f=+~6r8K~}rf1`{9sOMF1+kXnG}PCbuLcShEPc2JL|0SD zbP55w_m?*HD+oOpVo1lSVnmpP(7mEF*D)dFllJT=5TgzwRKRmCQ(e+lt0(ONWc$g4 zI!>!C%Vz<5SatRa6rTa04~Y&8wiJKVE|KH{TB`-#YmA z)K%1IgoWQ4RIrp&&{weRX^38j-WdXI$YMIcn=n}nVN&3vm}9SC>6Bz5XKNO->R3{% zVxrK6b%+Gib;#ZG8RGcYL_>@cP(;j9rHX6mt%vFnbaPC@M@_Xk$$~L1Rs#q#NMWP1 z_V&AILKi5a9gMgbi3upu{@|n-Q!$9~2NQ_+!rJJz98y~;N7N)2icoo%}~OAF@`#e#i&?j`~4xb z7{E|XkS(rCM<`gIMZk}gr>_b3$5W!5owAR`0n~H;Fd)Vkimttoe_0IGZ~<8aQ_WXd z6pV<91Aidf@iQyXnfZ$zN9z<`Z>sb?Y{O!TuB919K10+15SD*$B60kRc8=dk`_Ptk z1YoSv+9klnDSxNqLyw%=T8h;rF ze<#QuepQdcOUV(fznIKtFddBUx)+#Z>F_6zB(PKiRwZEEL)RL$N8@q`L+6UD>GZE; z+8G-r1DaAMGTAU^4IEH4HKUZjj-%zOFY{NrCSS@4^E8`6+{&*>AzGcCd-N!${YM*+x53t8nR+qUvggO_A&4Im}5ZZf8KVWE9yN>=_Q@ zwoEziX(nTgu0~J`T-Z>%kwR6H1rW6kDkqz=3Utur_Hdn@)WMgEGbo}k#GzwGxb*^E z2rE+VN;))yM(8-vBtIKa-()kZaj?Ht->I#)HV$^G`}M|7>&}(dBJwU@0IhE{VvVzS zqp^Js1v3fs%Kb5$Y+sCJi?N=URA0&i5$En&rQjrNjESvtjiyhe8fd6y1EAzczn0j5j&cSl&skz5 zT*F!~$&9xHu6YBKM0;wvWWl8?9~RiO9CT$sQ0@y?cdVk`gKi8P6)TaEvwp8}#fENV zmQI2-4q=|;i#Gv z;Z3pteWmKVPXPE3XgErAoAKRXb{j>SrNNeL-x4kE;x}Y@`kl3N2$K(15m=bP?P z@{N1eibI;n5>h0pMM21>21;Z-mt7F}%IY**2n#X1FrtVN`xDBC-Twl|a1`*N%|s*0 zp5;f9L;<9Nn4ZLj83D{&kLrfpjdOK|EU&@ObhQ83tzpH0WGAlk{i4(33bm^xl^LvYDPJc8*9 zSP=wfFGhok=?s`}{MFaahzb*t9a+H28zqChoVvGRd4%&eik(L9=m-&ki!6o{qnJcc z-lTZvE6>xQmtrZU#|Cx676YihN~Oq7AdX7V&OB{#?;f+w1j7=ofqi)FytQNyv^v-p zrqqC$c7St?P(_G4DNMWL$emMu=jG685#>xq+$kcvVt#*+;FRYoUJfT*sIu6!qCcF?1s# z&VWyCyTI`!20&j85xYVNHOj~ihG7IEt9yEw)5mwQnAOpjV40p)X7yfN3RYPZ80%W} z`*xw3+kg?brWVF_fq1bhPf*IW50i!G+Eo!CFmm696|?DglL%FZV_o|Y*elBMkP8n2 zSfgK5?W*cmt%h&P`7&iQo{F=|BjiSJf7~=_)f;qYVBwN>0~(tO4ntNk02T>a3M%4V zW=KQ_-)pB(z%jn~zCS&S!M#E^PD#R8Ql1UWe7oZkhOF^uV?5Pvsw91{%pBPzCOt?2 zE0)%>MF`8of!IWfp6g~zv5Jw5R^qE4e*~BT3kgwCOiimMN&83?!u1E2_zZmpRy!H% zWJ8x}j!z1}XhkBLuzu;A2^GPT4a#h}i5}>J5jM_I7sd*;E|6Li7;$pAi#>r@wP(b3 z17a`Z(9Uq!R>KAn&0ZN|0+2@oR33*mSq1jTn^doQops3s2JAMC|CsTb+S)=`hO1yZ zVM=2UHgc>9EHADvX-O?V=wEr!LY`61JkK1`d8VuA$!X-oFk~D%_Tiu%ZH*82+K|W4 z)D48TQmYF(3%7HKs2&Yy{j)-dq@U}K>u`WI?{;x=z-^1lsJA8u;IU+tyx$RZwts)D#0OdQPXH z0^3x>f13?p?_HS^o6tjxo^~2=(`K*Yp_-IHvk!*Jcp@kI(J;JJfVzayYEDHkqihv0 z7@vvkY))vMQOiIBn`M-G<(8f+fYv;pG{w+wwTnbxJ80 z17ZVI1H_8J69AIMeV!|`I^{t-Bo}ci##pILkio#UGjv3B3OVi;6B^UT6l#HNPPB*R z7-Cv&L97Mn337rEatr(PGZYw8g=$LB=#+1VyDPL_FsrmD`3xi=lFt~>1E6mstd>oV z7}QRs(Ro4-D-?@qW7wv_#&k#@y(szyVStYYMQ`m4harx|00@3`o#o7sO`dNX`iK}i z${tM05M)L7XzYX0O6`Gm&#b%54|0!@R}oW^BN!);?7QqH8o=;%IgOVg*T-Fy4TVK) zWoXU%$pjVJ)v7^$NyX5Us_+>y@*<2wfjN>nlCueeA;yi5+M%%rj;@?j@<>>M9LECW zILmO3-YkZ$KVmRr{aEggH(~Kdt@k0)cl`wyv<@ci&YXR13(D@OJD|pMSd8{~7l_%w zXbtUaLt>Gxft?rvU4o*MB4UdCWpPq+M8!NAqvwyOzAUzRi$yPHQk7AxPu&@Sjrev{sz`-PD6z;~FBXIk{R&GKZh{H1I=;y)Tl|grtkGA=>O=7& z5SFOAcJ7sSt4EQDs8{N~Qm@qAV<5V!B`n+^0?BDPc;)2S)macwebk+9adKBg6JAMkOmQnDA0!kANZ()1EmO+r-fVeu> zWf3OwiS4M~8cdLJRonC(dn*GTjgrkHn2xdNXl_>S+Ml*ZB_>Sdm7q?``VM_iGBKEl z>e6iYSzIg?G49ZH{ri%brIW7BG3nQ#EgVwI<3Q9|x7jNX%A%(|bb%`U2SwHCn7Y#$ z65zQ6cFjes&gzQi0BHA~GcTrRqO8b_&UptqFV&ME68uC_=h<>S;TEUhb`wLqn718( z`lc$tk&1bNs$o~6_oLrQZ;G-Y;J47h`2@^m-hspH5il|5FZ>nMU6lZg_8fIp5vhvNG$CjOSUb~~5OBLak&fUZVzks_(TM^R&X^TbbT#2b z`#89A2mIJ!BpceDIPO)dI3nl6IaL%jrvY zTN@ni@I<2vCSA};3WpTMg#9QFUBOu)>LzI)q`4&Ng4~b~NpQGGWd{^fMgCKS>$p_V zCTCJH-FDo2oJ3M9ht~r1C?dtw7#0p5H^v+oRs8f2=O3Ut!Qjbl|Ki{NRF^m$S-4)pj z>69pBnCkt~Knzqt*FEA1KQF5xKXA@RQ*zuR(P|Gb)o#Ng}-0ioQ8v$z2j zR&xmmdIX5Fg)zi+gn_IeGGu>#>sAYTH!9$yIKi=2F561clS&@;1I+tYB8&C+f^Z1x zf^zG-ofHBmZ^ZR{C|v%;Eot7fh>#E^YDHS3(l0ZW!kfiuRaG#sR64NvVO;IXZ3j8E z$>sIBoO{LeKf}_OS#y%WR{ar>^VCZrX!vM$9l+D3`%ch zic$VuPyKlihRSjlObvR-!&9f2kj5v+VnJ3HA$X6tQJN~!*84NEMcX85KE<}SZ%39n z&k=Dxm0pLt03O6X@{H%BJc6f1Jm5~8dW0TvT}S}CAkqP3e7INT&4P|jJ%W3K9-yw^ z0cJRAIL;eq+HBl!dK|<* zj5+8c7u9ry73F+{xTL$T5BmKkCR$oaG-Daj<8jL$J;a!J%!YOwj~L>d9X5uH+)2S( z36Wz^YX9DVl&T ztvZMP6syihrrVKo4U-7g6LQK9M=+qDw1@kU^UGK`RV>A07KriY3}71a!}~{0@5|8J zYU4s{Mg)ix%!pJJG5l$zhH*$PQs9U#T6ceXw%f+mbKCH6X)+qzl9E#w0cq`!V*`Ps ze1Vi+wDZ*kaa7E%0>c z3Vq3C(y-&GO>h%C%BJrTGL5j2;dEu~tkLhIgJ89Z)7YMyKs$;`?GbUtc$yAAisnP? z@376eU9}fl0<)48%EyV#D90hK3vTp4TNI;ZS-5Q&Z*j2!ZP4Q;x9~MlhoUhV58l_X zg2QPiAcT0U51k+LrNP*s)5+Ik6m&`!*ley~k_$9OsjT~Q7uJ%^P~_4czFXUh6GETI zl8=|XX#uq?MM*QDDUGgFL{;Xe8@%_7IPt#^7@;BKJ(^}YM+&L9?e~D$8_>k9)r5C; z;#y;4TaF_eI%qnE@Bt`3;n+!zynwbRhuAo~arGcvtUuXbQnlNZ9W!|D@}MlL0v&q> z{R_n0n(9oW!dc_7jzxA@Y$ppr5#9QHqQzt$T=lvL&y7pY=v-HW?72)%J$%M9?n5aD zV~QYOTo`j;Dhqd2h2ScldGVZ9-^5|jP@Q4bfLc_R5-sir2C%!fWy+qQ`w|@y@}37n zG}muL*{Ypl7YC~l?Zs}vTn5c6TGAtqNYMd=MWU16lN3CRg5f=41O)ZchXaU^9>hBo z#=}rH&{Kq~+?#QmY0#Zgdr*bS94f{g&ia!JTq5!9w4O?G&#~DP^mbZ z8r}cZ6L~&@5?7##pso(1tvTBEDJUT3wfFKMp_~zL6g}}n2+hnE#_;wu@y%dSKpu!? z*b(d4Aq&Sw9JU$G7GVjmdu|~@JD;)`Om{Y1Bp2mSx25Y2&NyO?-g!clxypaa2WRqW*N1L1(+P6;0$y|DF^*m z41imp3sERqhT2ecLCn)s_Ay{Z-IY`3J_D-drnn*aJi{~whG`!k(wN{m5iG2ihR{O; zh3V&&2z@(Vf^?WrTCvKJhf9Mk3#E6kcB$K^1?wawlN6&oBK}G%m(_2K+gz4dr*S*O9QaH#9{3GaJ`G~%EgnNfJoptA=i;o`=B{>x+5(FA5`1bF6hBz*01JWZ-?6?cq0*iT z4*bwJC z_pMA@!I6eAWe}KhvRjCv97=z24d?heXJCit8C$3O@EzVbo(4rNA`GL+_SSrmY9T<# zY1p7(0ORX@KClB6^MSQ2*i{T+$2yij3#O@bO1i}njyjBj=Mqd~NTmSk4pJ$@EeZ<_ z8Y)G_l&_;jsU_L*{D(S!UYCYgs9?5EmIGmgSpfG2*5JkwJAo=^B0Q=KlKB?alnbO9 zyen088x=;Ma~PuSnS>+nCt2RpPLL6kjv#sURd6fHx)e#b%g|yG!IfzlEh<#l(T)R? z!4)dzC~hpv$&&7@dyvQ@i^1@$31X@QP#sbFDog+pkDHh~?YbOe<`4qDLr9Kphe2r*I0 zWVeWM`k@Qjq)O@lC;uF*vs0j_BGhqVlLWgFrg_3F+2o-GM-<)B0ek5ba)g;BS_l*L z6?l}*9tNkY+oQucM6wD$>P4&jqj(v1lr4g-J8mMbc=HKU*(RFD%@4X0v=1YST$nmK zj(3mfgE6d)n5#G@95uu|9MO}}aKyePn*@vfF3^^AyFm1nM%Rbv3-CM7mckiY4@)x# z5d9;j?&)C%?C;x&e%xy>rncj&xM$8V}lv(di z(YdL^;Mlt!T$t9x`6<*6V;%SDE8}9-yXgf=8h|H}dADkLs0GUU8V)UI%#Dp@IPZE((}QVF&jHg$sYgvMi%TdP zWJ?_0RB=CF*Qz}lEPXeCf~TNXo`baJ9F$et9^8h)Dzhs$VR@G_jxfLyV*n+}0A_J& zx7gq|gli1=;b>BStMuUF*h=Z{Lo`O{HP#7{0p6aEGm)Sq9|=aXl3=pj?80jAvQfp> zGkSc*b%B_+dEIec9FP4Dnb!LiUQ_Ok4{#R{q`=Y}8$6~p0cyLh_N_KcWv za25bIo(bfNDi{d9Em%8JC#3zYre;bZh?_o!TPf=SG&C$|4ZG`lxCA$~(@fGSB3>V! z=DdXnVq_9SOz=@?=Hp{ca*}Z&)(7BA5_$k$pLW!ZWZo+mQazU=N$*@PpVH2F2Iqv_ zmd3(LZ!az;b~~mdz1uPRB)VeEgFG?&%oVl5N@~N~}h3EKbLqY@A|1)gU?` zLUeQftSE8Qn?4$0T<_^Q7Oi{7-i#CxY^!kb!|~4hRr(p2uKXfKKUpVP;8s?pq5T@ifl$xX2i=fEb#8clGJ5Cxp|{m#WGK?X!;C0ydH3Lt4wUK zvc+ljLDB}elf~mcby`m&APOP)wsB3fkD5{=`v6L~2?xAUT? zB`L%Vopg<-lrSB$Ju4%`H$BF=?@GCe9EtpuLPc5dkwFJGCZf1kr~RlR9+R={f2OP~ zL4s02k9k41`MEdmbZ?7+wUMFFjdBcTDQt@^;HjJDtjRgTIAPQ#wsY(2$h>7oF>=lT zJw$eSbE2KSmF>kqThxiAE%w~t!fKb@mwqKfb=cieRP`hv?Z8(+Xg%uD4PTSz)OWn6 zD`OHWjM}7s6C{=}gN|_eTenYZqSTa0}NiLYixtK_~ED>>cx=rsFKBzmgh}Br>yg$xIH>uyDPV^X>_{+#cc$m%! z43INPJk9AbIDR{xcaKD0l#`r>K->WbgEo&#N}@hm%Y=td2B#_@Rxn+7~UxhdA!iJ?5MZu`Rdks#Ay*bV^Z+aoo(7^*Ss#QJml|O2LzZ zm>68L;#11!ElHEhSv><_2IdR*-iLz-0NOXyeF*NL+s9xq&@{Waf{ZiY^jpW%Zdg_i zdhSrdm9{w6<_N*1>9RPyv5A$lTx5_MgBp(Y0<4c7-KI`0)f=hkl4A&U`LdH7(?#R1 z;jEes37-8!fw<#@gcRU`Q8PO!ZW?Y8L@dlQ_xLKbs_UydLUE8Rn6X0R;5pQrcycC7 zuNv`H&uFygry@@36#N*ekO>}haRGyg47YC32f!q^QruEB3Y_4hC=z@e5qxTJy%9xW zm?*Gel3>F!52lznm|}det}VXIwai$Gjt(aqueLH{ktmFHnWmf`CJ}XR2kwlCNaVgb z9K$<5D#y@zGCPTms~M*z85bIxEc6TE#J-QgXbEYGJEhVHRm~v-+sf5qJP^yu_8A@Q z23v$*aOO6LKuE0)7Mq%Pm?X~4jd04r9ce=@af64Z#zx=r;xnFI39_hA^3ih2c6rEy zj&cuWJ-K9HLD^aTVCzExv=pIYLqkK}3}$WYkLx3GJCyKPM5gy%9K(g;r0I@=7?F9{ z#qrg}~@7Bv(pz6fupIJxLS- z>4W6llYhH4TEmnRH3D00jH{PC{|ap;%8Sb*x*+59sk76Aoww(Ft-82f{&(dLuwnIJiPNge zLm6j2It&RtW^W9hK*JoDO?E7m{lU}NQMNbaAgjkg@JbJb?Hz;C(#AAfZR5g=MdI-e zfhwZonrq|vG;2=l-+BemV$=t^0zDPChbp}N5kN~U8eozL z8v&=GWjKmy9ua#8D|<^n=?pC1nRI6~EbRZrLv6(^JS{b^+L#V=_XT{~+C~?(6o1^B zt_?jiWD_j_9=0n4RbKg3Z#6g?q}K{O4_pn4+1npP*RO+lItFx4Mvcdyf>_YCGu*kP z3DL5=2NxJTPUjT26614!3?&CW))sS$vLWo`5s){b_HjKP*ofh|KfwgKW4t~q;uR?O zKD0G&H)ZxjyL*Sv2>;GH)}du=}hou3FODJ%RL$y_Ge0%h? z<5=0EyjS7sJnXjE1U|AY5ndUA%z@rJYaSJF+vhCIc9+DGv&oxC^b?;4gZF~^r@_)q ze`HDrw^Lnt6%+R$LZ~5*GV{236O))odLU|WF%*Fu%NVBUpbYDCpvCc^gi|*ncm+QN z7YxYQ$wTD;BMT+GNL@py$PI4qRt1Dz?6yFLx;HFH1Q33EqpKN|X^YW~icKiZSVa%$ z3W(OKuoQpxm@ZgB(b8|VZ^0QuQ2-rbWe}t&*%5FkGdnP@MxTm|*dFxQ02Pi1lqoEN z_i?>90yiL8ARA%wpk={_B@jXq^5F=CP?Y9A6oC+mkPk&5graK3M%d7tZeX2nncnDy z2;g|mWp)EzdwfPwT^7*R8#+4Wa5}6sQW}Yc_9UCZoK1%)C%$5wJAEJ*4|%zlx3A^! zx8B1E_&%&Q&IczpCWN;w4oa{9itYs?4(N9{c|#9h6o=h=Ar+?_&bwoLR9hxi0ik;1 z*r*sw@Tzw-cu&lo@J7dyHJIib;u>7Ob7OMvL?Gl{4B|aH`UgnT^?`wi53B;Tn^-bd z*&!7X2M~^EZAY<&16^>Oi;jLdbx}XXVLYsa9ogx;_y9KyN3c=yOohxd;T8{6389+L zLkBkL=>fNWRaknDT5g(wAdpZH-&hE`KsHP?Opta1%dKc5;$Z@v(<#(0tRC?;G^at) zt~uXYQZo2m$<{b=U~8<0FFMhL*n?)`XzcnLJKESq&$Mf(G_lPTy~Xmgb~Iq1b*FNH zdszlT%om+@xpt<8d6%Rpf=*Leg2chmu)z0epnqu?6poW2j$Z4!BBbaF3a=!-qj06; zYKq2bad}WvZ=Nn~Rf$kN*h-kIQ?w8%0^@jatjD`xP@i?$*5b(!suX(VD-EwJA{P#G zAun&ij6WQkk{mUN3F?>{B4b2ZKXu7cLlfD>fnA{%pTMXrC(t}Yir@*m5zi1>j6${E zZz#r?kB7kt)krMS#3U86%6bzmvwCBPnjI7a5VvZU( z8yBWB+m$p|s(SgH`FJ|#heu#Vg}bScHD46Rdl{dz-$KjK^yF|&2t4x)5+SL@1f@Ul zhUziR`JTJ(#_`gwo#ht)A`tiJnkU#%tTSO{dw4DgUEf-!f+gK~3gOKsbJEja08VEA4;>8Dfwq>sZQ z(dDfPZiWuwN2?5-a?%$&u#duZ(!1}0B4eu=2u7L_Vi@&?AZi=tQJYbhlVh`ub2=y! z)hCOwy1oI`#yC8-gn~JYNg;j23Dd_2YAP-XO|e+#2R3k{PBg!8xl}+E4UTA24XyX0 z9w|J! zFX1)9NDW@{#PL!giZr*Bu&-t*;i|kiHR39O+aa>-5_Pma^KCgq%GlJWR){TlTpaP7 z&=W-U@~jra+W6F?kOZ}DoJBrrbFBcil|JkneJo^l3Y7RVu*E$ly2EekAkFx# zLw@D9#Bgv>n}BEbt>rPDGfi)i5^0eO+$pUTU+HlSib+ifh~x+@9J9shEy$R3LjN9O z?L|F>HSe~GW1MpU!A`2HGj$mRrQ55*@2s)|PS^)L|phVMm>Cxfv~raiN(o z?_N06#>?#najcnpHw>;Y-U1Fq)&aM--9n?%jUwKl!v=tAq~X!~{kwKMFk^MmqZ)$O zU7Vk=fG6T(#}3u^{p=vl%JB?wTxFL;M!Ld0S^r>k^H2y=2}f)gVe zj2zG$_+eY23XGiE%w{Q4VUN;cv8=&u62aj_k@QF+&SZ$0s>ilu)B<7*O}L54VWmNW z>Vl-o%Y$}h7axy8)zs-38w)sfL~eFQp#U!~$Yout-gYf{z1jvk9UqrwH9=f1ghlME z#mgc$TAn~xwK9p4vcauv5xKUE70?wcK|-zICyc-wN-LmpVe{OgmrIl4^CCmIpaq4#2$ zB29{Q06fbfN@M`)k>I#cSeutx*vRdGa=~#2EIy+_cY6TW+vXFfgfP?P5(g>h-ZQ!JLG5% zLW~&$ATwq$=L<}DVuYBxuzbk)*u#RIWC`)$wp8Z>a7jRhyA64v zTPwnPyq(%Xv{Ftv<|TRG!x3}dztN>T=$m8lxgaZffJ)h&sZ(B$z?Cr0wldd8$h2@8 z7Mhy4^^)WqDl3db<2U3~kr)DAYAZ$$;y5>oNPXYCDieWhu3%%Niy z2PRU!$j@WUEwf3M4x$tc6ll3k@A!dNAE`MOgeAN_BRNg7EgdLolTaDjXld@3;Uw$J z2v9G@#jVoRlIzOAv{QPTka~}exq0p}<>Blo*-g<=ul0pdhn}KZ#&Kr7ne4KM;ChD2 z4T$R-9F7M#p_26};dbSaY;d}fLt*qTgtR=&&?2kPNI4PN4O^*xU_e9Y?o%`Y`M?xy zYZ5SyGDHU~8ebDmux@t|W_odA55^Ql>z~i5uJDT<4eqb5Q_+k4vn$j z=i*V4=g_f$5Y%o57bJcL!HI5^$Uq4Ef*+oV3s=o8^88YFoIg7X=kGqpG`*r1V{otu z&UAs1LkL#6e0LR%#>R9hW)5&E6xFUGeA{|OE5}Ur5WAY<|HOm(IBCoM=ozMMH;!;--m4sw^dmFN-!X zjnJ~De=?4EU4Q^qE^kI?s4DPg4jIi;(dz zh$V-oYm`Q1HHk%n8#QP^sIVJy5W;`WP z%oPif-sDLo?40_{xQ1p=*2vKFZdlKiqF7G|48FEp*kFf8*3jzFyzI^-E5S{&5tdi+ z;V)3)L{J+$Osw8;$SA(0NUfYm0deV^OJFo7GLB$t7l&HXePC_X-?*E#+;MG^7VOI! zi>$YusS14V3^jKYImSCUAmdUz7~yjW&%D?pMCv0NbpwUZ+$-nEc)Ou)LTh-wF$&Z1J&P|?E$ zG+`HEE9nS`tid+c&G(^Ep>?-|BC<2KgL#VWal|xKghcMr&dZ(Cq40>`UCB&NRFNg5 zB+`&$6Eg^??%w&07VFS1#&y^Ts8cOxplZ0BvVg_yV)Q7~%mM{g*z<|d)m=as+<0)Q zgZ&>iZn!!r(L%OxPico6Yt|X%G!Rlm`2?NaxRhW^2(I7Xh6JJQG7~50X9f--I}d0( zk=#Fs#+eaqo89pU3-%z<^W=Q6KdP#QFf7mO;d5|2J_9_PU?~Bm-o|BMiO94qpbX*H zC;`Q2z4;3r+c<6f=)ho?ohHhNOumBF%tI{?5F!`AM=P83nC~Z|w-)@kC!uPGoBFz) z93k1e6^U~CpwKfVDUU#0jZx7&;t66fsy?8?$*VN1r3{541`Ld|n*R(LO{J_)k-&@; z{3*$_d$&TD$dVA8ArdO>!SZoKwNc}?D|9~YrD;!bWOViONM`h2LPnKfo~Y?@64VJI zi%@H4X-0wsx9wQUO|nQSA^{(f5u@i=FWONQ6?R{kiJeGA^4L8;|05ZoODE*#TB`6; zro&e!eADfmot6e$-*W{(H6e2Mxl5g2Nzp`i3`F2`2{26^=m}}W-|?}u@RL65gozbjzHZL1wD&F z!^)0G@np1!MsVSay1&ILX#bTZi`ubA3TDinD19*@wI)0DgdE&BbRrsO*2LUOywn?- zwJBHDJSKpkg%U2JwZ<0lu>$U+c5$WJ`vN&CHA1L8$UaY^-Cj}Q3njq9+Qd6ku&Lp5 zSGKFhVgAM&hIud~qe92*rrVf>-RA|tt04Sg@cX9nvYSwI`iJlbk zXvtw#(&I6jqzw#)cWFeZbh*ObtWL3vg;n4}v8M3F=%aBjPQlfz)`bd6B&;CvX2%;8 zmLqLi!l>&j4a$@_vv-r1IA+p*?kCj7zp38e4Pnb(Mjq8UId;80@{Q|x^L z{9NBFmq{*Ofq)8nqN9&G79^BympK}BKaI49%ME6k!OU%y;(1> z`6Lyok?8w^g!J7!sGP{6+mLaAhLR73$QUbGw(d+KvM%7VD?y@z0q)}vsx7zeEGhE| zktIV5v6W7NO>5huv*yvvY0J#rRRKjTZ4$0~q_@Z>PJ2zk|o zrtMSsFaf1~it(z37I~M$Yi=qAM*F-lZjEnVY4eMmK*ND0WWUh6nDNj6ImGMsGplrq+tZ1o}v?xCGZ^->I@ zrfWdWQuU$jac#0>UEF4y>e{ao+TN0^-2tm2z>r!@WmwnV46xSTEMMT?8I|5g)*qgj(^**$$f~XI)S>4(jXPQm;hCaueW2d$>R~ z2QaKMD3tIq#7kwT5NCeCM6sZ=HKxWIzVUE6<*Ces2YMz z7{d^XF(~msG4)rRqF_tV2pwZfcz|rrEUac#5|%`BexFam{vOx^Tm z0mA~i3-D>43P$_p1u zx~9~Mw40RLlzZ6LR5UGdhgc11L1~baJV94>IBWg2!Hl0wwu7Kp*-4djan22=ykZ_` zvIa^js32xfQ;VY2nv&a(#=ej+`WDWdYEoEviUmE(RVavK(7I@Mhgx5bpHoKz!wX{? z8ON`$Gke#jw=GZEC32mZ&eP<=np{;v=wP4(fR?lTb9#}uwJya(ik`NJte)t=P$McD z09Pg}hd9t!%12WrfY7Y&EPmfOax=sg%uP2|!7|q3;&Fh5)dlj$4iidnh@&jsp%mNO z>{)qjy~V4ioq<4C*NNMDidMJz+++YJBWO_d0Fu*whsnPUhf}O{SFOBQA{GsI^xih- zAT;rnT<-=XOptJ90{h1rJz=;Y=#a_|HCm{S00jVo*FnByGw-AklenB!mIiWY-5;>K zrJ&d+%;jh%>bH-Yb5ZW$b5N)IKm?nHi^YfHu`S$yvir@BEN($j5xC1I9#tHEZ4MKN z_w^x_jT*rQ9kd@79PWm8aD7NTHxaF#6wv+jJU~E+&BAYW1bpu1#>auSg(M1$b$p?& z%TEUd6r~q$G7)0~3!7UUO#!3vqg)Y1-^2Z_5TjVy<0Ptx8qzjK$>yJ^6$>OyEM~U5 zmmvi~;3!hSa9bvZF;0YONo9dV?j;n|I8|FGDSR#R(}4?lAaWs9!{MSZX3_Gfec>7R z;`7Atyv7Bj$MpFr0a<)L zbm#*9C=5F>`>b>@>JxgX1?{b<=!kBDi?7#v!cwHG85g5Jb&RTaI9ksm(9ZsPGHO4f z%5h^))00gebcSk=h>l5l9E>EFHY^RuuodOgaV(3+E8%6l@-~7jeo0 zP8^3!r?}LQuZ^noz;z-qUO{N1V`?~^ctoLLApJ5twyinlLjurd>->IJh580ZqXb7i z37>_6-2of2y2RzBUBu@sSdjSc-jd(}dnp~UY3^vd7bGO~E-NB{RX}M#^5j?- zE*32N>GxzIq!duP;sS}57Jh|O>^DvUwfd9zCyN=uRHl>aNsqIo?MnKnu`e+waXD{x zgay2Q0BQk+4KBzC~q?5s#pA*ggan z>nG<#pfw4z1k19RQ3!!^X*Mv=2`R($9GVQ;b7(q*&mp=2eGWMq-sf=SgZ&(~Xy~8A zp@INip{7?irIu-u;>`+-HR&eo`pL+`-E4JDqMl-81jw!CJyyTX>sWPQ-M)J7g$s8= z;ES6eE*7z_f=UBV6AmehjtHh!?`UTE)pHVTW6UNvIF4C`K)H^qZ`jOsIG4u^y*21e z+tV|T?b~OhaZICwy$bnAkQwtK!ZM{B}V5HVz<6yeAc-8pqxp#Q26(n`i{FKq63yw;~xk`~OeMK>QIQ|2?l#FR@S z@hk0+j0F*552UT8Oh+hnQlWXk5|qs%N2Vh0^Ci7XkG5@C)}=Srr2RFb7=bE?K+IND zK)EHTvxEk7G3i!e12(oJQJ6RaB;IogERzczrNb=iD4-6edt{nvOt-4;VHmC1?T3Jo znJz{%sCm<D;hhWC>AFBT7CG7_!T|f;>D4Ca*2BJ!PSeQ59 zEJB@HmIm7y442|LRb>P>Td?O*`t!tT=~KE56}i6+_3qnJp~c@OtaEl?;~~q{y%ttZ zc(WDL%nDi;I_D6jQ=J8ynr=e#vYc+SRa! zt^Uc($6&|-ZWnpn{pEPn8X+w>=st!*;>PR=T|sbb4|poWt@d=<4t26X=2o8%EFCoy z23UzAFEFAYvIe}K9bQ<)pB73mfwML>X~T)+$=QgB3fmgQyqu9bY-9CySgejGvl8EQ zSZQ6VU2APN8rV6T!1y{X;hPN_6aFB6Fbz%yw?si}4T~E*bJ>;S(0X)V%r9`YDfTGv z!+YCZ_u>R6U80R!_m4X)F>KezG-vCOGoNm&@R^1$qjt`yXz5n6OexUA?C>KLf^6-* zdeT0;6EbiPx9a@@>+${=N6@hxZ}F@N>jSo~q9+a@TfGZtMwmNRdxJwHMN;Qbrbpz3r?#j@;8w$66j2>?>sctQv_Uo%XXxhX;^+VqPAYkyb+;$Y0L8_ z0stPfuvbb0hwaG=yu@^Qw6)b6qaiq~@PlB$OW_;>hD{T0W)K(82sC%X!f6MOvH6Q* zHXCpUh?cYtC&mOA(QVf7_$^&DE|7I;P`3Is#9~Ooj!Now)5WE#PhKewR`godHs4-eJe0dEN*mzLGG*xxyB``oeg4HiRB;w28A% zwe&L)hVJSr6vEtg`GGg7CgVnPjli-?kDk*iXc=pDilg=o$bXAQdlkRjJBr#@vCX*k zD8L3?t^;uH3f&bg!+polFmRm<>CJP06n6M)eZVp*HxUANj1@QB)bAI_u+iy9g|$b+ za@&-ZyXuF5{d5(J`ctaDr>mIYxUVBFGy?}5147lHn>j4g-3Y-~Bq5UB8ZRPXD?>c% znV|sXnoZBpX0jEK6VW4ExGT-0JnZon(lu}o?&Ml+I0M7vwm|HgKWs=&^I_&$CX|Ng zuJV~TkCo|c0gFC`k$x1|-JVn^DijW}J2!=7)#>sIttW(a$Qh3I!bmYUC;|T zDCu7h)x_?xq%inYYKyz5%E3*C#q?yGi~ZEsg%?6Nrizb4EE7cL@oXQ}*F;^#jf?cq z9=%Se1GqevlV4Fot5V^e9ohEnwn{H0UaW6n8&Z7GpdKPEgtt)=P8?7TiD7&|e2Uym z$BD)eL_FMjIRUU)9zq}MV2V0U;?KEmLZvNQQ01riX6wxlIe-Q8iD6#)Cx_Kf z$o`Xq$F;P)bhqPJq-Axy*H~7~z}~KYLH5x!cGCy|henE%2f+v0Z8Vc^L^x?}BD4!( z0@H4!jlIyL&gG2L4oo>#vvjexkIoro!4OJq=2Sx{70PCFzqZ$^tgkn>8v9KvWscsf z>|Cr_nS3UeGo|sa9c=H`b{o~r*8X6E;o-vQVBDzGw+{Adtv#^pA@L7|Cyig?!BVbS z-(mi|$6gIx6!R+OmMDG;Jx)2&*chhT*x9IGY-xvEAWO}Z-f!JvqalhVwI{{Ti`3kz zTwW-?A+v;KSS{4F_RF-fQBJpl#rd6s{msxo0=bCmHB4mHN_Dfob5Yg)sD=_^5+dm@ z!gi^?wH4B+ZUtmtZb8ASbF-yeRkjvbXpq|l!RI(8Cq-P(x8u&sAepm#ETtud+p0H{ z{wyg8($#Um=(1*cQ6xwb^G>5xyIhCPqNjs;3+3?)d9eM)0R-P|f+<51Y`1dItSz*O zVxea()~1q@I5@DbxwTbZbxKV5(eD@ghC{aDF>#^Dj+jTYQ`xS0IUyAePC+J|`+GRD z&d$pv*t}eb-qVv%2Rmz9jp`-m1bTVL3iPT6NQD0Gl8r66P+m`)uFUn3rZ%2d0G zu@I>!25YVi2muY@g6`HaAA4z4yDD(ZEeI@%dRX3LY2$0bSmSsq(TG$sFnL@+of{iy zaeVuA^sYR-AeD==pVf}MDt5bmaj$|&#uv&Vv_c#d;&qtg;tcCsJ%|~~>8Lc2aADb1 zjh^6z2jud#c{v5vk+Ra zNyz&87G#7U+ZNhm7h>>~B$mC}hU$G<^A?N8F2v#~Ni3+tFqn61dreCI5Y}Nd%&v7y z-}nv@2hdKwq!&8oB(Atu8$;5q1Md77eaANDtY^nqOM8DmnyGgQnUvvcR2YI91lsL- zV;cj(IQMEIhy$-7j9aKXSHmz-GB9}vpj?>dej^qa(8~l7hfs_LXjUsbApwE36!L03 zSP4W)HVF=~8xt+X1&5mfMKm!9E{Y*p8VV7ZW$^&LEGoZB)HzzSsqdk5@%)`MP-vgD zc|z;R9!3-sw=^{S2Rqc)tY73~v+9!5N#S?u_hDo}8M#*tr z21)Z7Yt|}zSR=%_GE|Z!!}W1I7<*FVli_#-J8{zMVivDvpcxF`Ics$XQ(P8aEYt$W z^5C8E>FvUjygc9%>|)(mCeEu?BWLxNO+UW0`7D$X9=IAc0^wWCy%Ha=LUXp;M0A^~ z^|36kUr#D~wIrzoL-A97ak7_6VKN z9F($>Zt2u_s(WkkLJHOp!6fvJtwy4p8Iz)oGg>xm%6pV%w2s)^s_n!%CulKjXN3~j z(XaxJgKZN|C(%v889&uFHI-XM1VogtfLt&^ZU&k~87W$u@*YJLX?#EN7JQ6OjV4duh-TN zE+(NBLwzi3o_>wTh zx-fkAEwr|k#}KjZrK|!*nHSh%<@?s|0aUOMb--nTlu<)MPhGFYt!@ZUZPpT99%R8} zWpro@miCmbFwF86PX_VWrwmYt$amVfTTXNnitUX#k)bEI88OHVg;id zNDH_~&>GQFOn8%uCbL_HZDGJCJ=YKx*IrG&C1abBTLg`gj_|E8oVWJ(>PaAhHI(!m z-%$h#g%B|CSZh}I>T7OMwR^Az%dU$I z5Exq3&C1RWwumeV!|ohxlV}ksr|bbNXBX1aFo~VwU?{B#1{$gmmj26jN*?xbf`WFH5TjxsZI#*#%Qdlp%;(I$is z9PKg$99HIemZ80@TLpVQXJpBeacDb3=4YLSa9Ng{`xU?Q>Uj}5*w{FbcK1=KoIle& zh=imO8_JYE&18y8__U_IJTcC=Q4-X$CP^5Q$fM>kCaKUQQ&ANgPg;2FwbdacTGdO> zIcV%1IN{m!c-?ahCCCVN#EqMozD@GL#&a&+3gu$fmPmC-A7!l6cx1L6?eWxGw=3=h zz+MXqb+lXK*<^Q!8;(w#21?U~UvFI5VR)!$I8GpEVLX>hBEvRz>M$4P%$T5p{o6<3 zx0bW0!N>|UH?cW{eS+&T3YX1~B)-#*yl)`2^xkad379?hID%VV-H9Ab~h7*73S zN7t#-gdoGP6S!v*A;xIzg4w&aci>hbL|m-x>{gnt*oxVgXng;Sks+q7#>Jrhk%fpQ zM`0W(glu9XJjx`haWOrI)Pcu1943kFGR^_6rFh`aC?h{Y2T7(3ZrWc8O$0aD;$%fUykMe|^Mv{mi$LErkGY%ZNQ4T%M z#UltA92D@@Kz2T2uXbgx?&n8Cvj6815|^Y(L^^(=&`Hus3D(z|_3N&ecv*xawLGWg z>%$W?BAElI4)|PojONLM%O(X+j3QfK%i;neiVD!VF@dHLrClT1HkViB*@8rkh6P2) zPW4&~{gvWQQ*=n|P3}7NBgMJ6H1H-2>kTl&gS;H1a=Zb*gn!TN{UqGLro5zL4=s&Y z1?w_g8aQd_t;W`s11H5`?`|Niq}YbrM!PnuxOfnB52NfJa22k}8fppGL9>#qc#Kog znm-pf7AxD0^@FWiD;lB>6ZEVIgS9R8L3lVkbkdY;Nolm$npub@sh!>CwVkTfz0GIX zm}z5H#MPM+2Z(vn+z*G0V>HI;{pk2dMqUz3Gw1ZeN>ff;_`#IngU+21G?wP(!M@D! zKD@`2JSV-lPwwd!0S`>wrnE9gI(lB#gy>7^ur7hd6;4`-V8kgakJLUDW72>$G^9N7 z5OCp=25nxc?0U%%?TU$JxWWB|*pG#lMgaQ$#A}nbE7TPYtD&N}m%t~g6%zkHB7X~%9U5P9;Yu61E za^q2Pn0jucKgvaY5`7ob_?>657F+ln#zLbr1rF`_43Oz5G9 zMMG9c)%6Gyr5a%uHe=!-mMqwzyZDh$jzpI+L9(ZN+& zKl24ziW>Bm==8#zPLqOiSv}Zqi0kYyuCC(Zg5g}YJtEG$M_N=GjRjF4VpoK*@?b(l zBWM)8-Co_RCC7iSCg><+f?r<#gk?!8u!b}bux%V?C=TQ4Nmyjr6BhCl6SGj*L~q0= zAgpA#mm`FP8+rhXe$)>d&^ruL^vgunoi| z(l!u-xD}jcxw6YWlYvV3gpMVVywopg*}5%7lafq zN4B)Nv(sGBVJm6V^xYMUQyY z^NAtx{aeAZz33bXUKZi(Mk9bf2Bek|Bw-SGf8KH^-U}Kd^M(xovXdS+x)E~ww{rBL!uDEtu zLRP|D9LLBh&fw{F!cQs#~Q!G`-XfyM4VmkiDO>* z)Zi*xT^wEw;H~`@{qkbdDQ8$f%E4HA3E5j;6EC1pse5Z@^xWgW^$gcA4!DR<9nfFM z4yt=qDy@%XBX0E`g)Re^JL(HyfSg1zG?9JyXQqpf{W09X!nesPZr>6{#i@5)eA^sO ziRj9ASN-X%7UTX&o1ehL{JcP9C!(V<^qys8w7m=K%VGW6!|-edGuaSsK^RG|v%%&q z(0KI9Dc?_S?G%UTb*QKoK0s`5J8ig9vrnO~cUGmd&L6P7b^`zv7efYTq?#u8SW({#7wJN(1498r-|B-!6XNxz7iXN&G?&-wa zO$(k|;0y)jIfrXnr~?h{kg_AF-!*EChG(n*9i7Ug9*8hf;WaGt76i{KXV?}&)tF}> z{ffc1AnTJ=5di~?3nQ}!?4l}G4Q|n24t%^x;~Q=Z9=F@08C?(3Er{gAi3uN(G$Kl^ zzD7J+lcy9aR@*1=oz9vNuX#51l58qppPwWVS7637q);1nXX2hne)(+}!WZoKZuOsL zqnEDK(2eO5kCT$p0HOLxqQ@_Y2l_gSLBtt@h?*dSsl4RCyoo3vHDTq1^D6lNgHJC+ z?=jTVQS@mS9_%5s*{fKS7*0UDH|s_I>l@A^nam!6jZ)P~E0e<%uF|c83@#_ADL7?< z3#J>ZvlBFIb9bL5;}1&2AC!bYs3ZQ|1DAQ?p3G(&6P)@@cZ?dPa&(Buz~HfAAcISs zlyBooxInx~%{R^h=6((Xa1{^y>(EUWe&EU(9u86K5VhcE7KiOyXLNl=PkNY5KzEp4 zK;hU6zV)(Sz}K-%i&eOzgCNe)2%Mv~2cvVCt8JvN>ki1yyVHr&cB5YPW zExOPlX3v&hYJ}=P10k~MPv@w zGTIlvG2`(INeFKmf>J4dQ9+n~rTDGp*LGLmpmW`s(~5A|jrnaLpMR1BUD2bheASFd8I4Si7^y9L)}^1&#$=7Tg9%`o~t ziVQ7rLymA5W%T@p!A19T__)q079F!LBwI!q!nZRh;#V^bLyD;O>`GJ2=__|g!{2d) z8TzcipZn*d;trAPPrq;V!s^|3^omJ&#}U5w2~mH?3aV$^UG09D!B7Vl!)p5}8pZgj zA7&gYqXJHVKg>W$!i)gPnu}%3#&EGo)sB^l+Wau1UU4P*XXVL7^5IuU2}S0gl{6#x z@RJo!rT@HyHHr^EX^OXhUdj@~hn{e6+B(9Nh*__Fa>vS141VbOh(q1M{@9!PNHH4H zSU=>vsb}mvR?I_oZ#Hg!$XT+m$cw%q7jZNnN*>B-7qXzoB{GwR{&~l6HeMaCeDEC~ z{icp} zUil>B_z0C!CdZzB^Jky<(4BAmo648J?pZs3_vD9v#jm{QOMc`p9{Ppv_~bAC`Ja05 z{;PlZD_;BKSI58fuKt@I^7gmA?f5~Bs}KFh=fC0kx4!xhe)ff%U-z_s^FZ`>Kk82_ zul>}&{?!-U_nY7OfqOpTvwnB==IRq4|JltaJnKb2^DE`szJ30UFWY;}_x|Whp84kX z!+Zbr=(B(Dt)KkszjgPE-|z+Re&_dn-xK$KW94H$_x<1XO-C>Np3PUjsP&p3|MKmh ze9T8a8z1|+v$vew^T@N; zzVRC#|7E}T(D(nZ55BAShNIuU^v;8az4*z$^xUWX{*^!ciyxT$%&)!We}34fJ?On( z_~)PaX;1l-dms6_;<1mr_EYbB&)e_Vdgon#`^eqj{)}(?>0i9#PagO5NA17#58zqWqkS4W@pV_*5& zumAe?@BZtxw+`O?Q7@Z3;hk?QzU3|NdG~+$%_n}%pM2>PKkkzr_Tr!X+WpG4U#s6W zeEq{;{=Khx#j7{|@Tvd#vp)Tfr{3uP@>{?E%l_&KSLW}3^C!Ih@vr~#FM9cVzkT%Q zkKO;(t#^OS@UFi;4b6Kk)E- zzU$ie{KnUO%~Rj_ra%6&uluN@=7azG#vg94K6w4j5B{z9-+aW=U-Nk{z5J&?^MOD9 z%b)%I&;Ez6e%<@NzI*-fD_->SkALuk{^8o{^Q&)v&$aF=Uiy@8d&ff${$BtRK<&RP z0WX7E5Xp)wdJG9Tw84I+pj-Fdi2fX%snX_=A=*OB#e-W<fxjege)Lgx8!tGo8RHspZ+((Hl8{>! z4(pY8)FmfQk+5#Ivb?-~>S(z>j`<<{GhuF_U253 z+FIon>aoo9*!q0wFdX4NjlR z21zJ%-XS#VHzj8-_R4tVlt>f9=1{%I>?(+rul2;}Fd%yb=T`=$eUzJ+{xKTgY39L0 zz=joMOlu*4HVhMM42YNVYNmWslWCKluX;cIPrtZ93)Ei)<!~*WPOw#j$V12Y%Zk{v~5F6M8FN;t>6^Y*`=bXw**prBd6j#dq zB!zCt*C+G@_JMxs^9mpdJJgKmD&#d?zmwq%qV~*`+i~9{iVow=TSJ{QtZF+0YVJ%7 zG-^*Yr{<0m&x9e#4FGA&-X_VVU{dTO;QG9=;Q*k?^HDMqmTDkz=CMkH(PBcb5`0*O z6Nrovv%ji-vRf*Tdn<4M6&RgA(5OFHG$fM=T&}bbtV# z+aTqO;adz*)FH5=)VPDfxc1@yGm&4+rO^d6p|mP_hpy;`Cp-Edjp|OZQ?4lymO69( zaR+Q_{4z`p7m|P*dSPeq$T@)NNf{qZs z+F>N=)&p%p>Dhj7bvM%YOc!ji+XI7y0`XXqW+RZl4HdUKR!&)iiWr``Yk%mzj)J6d zd6uWWm{8j7JFCqR0Jntf;OvE5FV%3Xi?N4Tpe6(wLI+|d!KKA$dS;`=DXggBlBeeHnf@Wl7V?#Dz#t6*?K+X`K;66EG}W?lzLNj% z_7{DJ6?vGu{>$w02JL7zCr^K0P=&^`&$kYpW+Q66S!PDjnkH;&rh^0`Y7j*h7G>ELovtN+(aJ7AKn$OWQt}8$4c>z6bCVv zizzg<1^|}5*#vKc`RPKHRW=$061i{^Md>VmxkdU`1a zk0!=j7d1zClu)Y5-?h3Z9#4M;uGJl2{0Pq>ne4lTedS5`Nhsjul$oV*_SOlBsa)R) z{oGGc>0)s<6vAXB%cine$Tzn~*n33boGQ-ZQ6M)qN&|fWe-6O`JW=kXlDWF!C_UrU zj%!DOZXz6vw;}*ktDSWB+=*xeL*LD&X2;HUL;1Xj&mmsvkT{wG&asnK0a#Qjj}*j5 zehi_YA6!Ki0p6j!&Kkr;Mg@q8?vQ}=*reDD5gd|h!IRN8?J3Gc=?1bXJmZw(Y9*ek zYpMXI)T*|G`57G&BC&<4&SbRl2}?tI97WBweCCN*M~ymJ{hSPp)u}q%mmqxA;IgJh)UXY z0NpfBr9XG3hZT)E{?9wQ_-Sl+(4jE^B=o3^Oyc|JqCyuu^E?Uv|8RC^74R`(gK^Yw+t_LLNf^0Mq&BT$5YV&qIEW*vjOLH3-S95Q55z zWb}s4U_*M=!BO_%?;4WOq3F@-LVRffjWsjslGT{ID?D3sXFOBj6D73i;DFf_s z4P2U6xR?Tq$YjK{Xj5DpTM1P8n}7H7dn&K3g_8(!q{A@~2u%UoqymcZS*D8TIP&>DV+Ff4!Qn*JdLhP^{!G|0qoZwDGauHz$Kc9h{ zzenWWc@)=BS=!ft2PcdQJE~~BKxyG#!;cED%4w(hM(*8l2i(QXfRAsEcI_z|dMfva z^FS2FF*0J61ME?~KfkZ2-n;dqcV>6iD})OhZt|;AJf3iFT2slTOHootWTqXe)Kx;A zDhp$Hc>UrQEyHqQY9CmeCR|n%YE};t;p&f;s$*v>T0;XSO{y8OQn(|ruyI9eUu**z zVnOeIX+gY?Mu=9u$VEsiS!}_WlEydu>^qgvePHAcC9uFezoF^$mWE;+p@Ye+AUDRd;$< zXvi10MOvf#)wOF+ljx{)YL0Dd>BhK>*Hsa+O$0X4Df~&V5(Rh$QFF2yeNFGDpQLZG zL*wT_0M6F;Ken7>soS_x(8$r*)_ z9aPJlM~=g^Wo7JP)1IdS^Rf~=2uFF6ml3v63k$vWoL#@NP~+e$y53yF9$kT5PpyBP zUshcr!b{Zv6E(5)At0J_W!lh-s5r6ALHNSK-;$u)6SBtP9?`WYJFUQ1-xshFF^ zfVmPUzo?cmZK=~ePTx@3^9DJY-gG|cT~!ReOB${X3mb+cQ6Rmj*9zP73@AqWi$bo& zbA7Ly@EyHz!hK|_XbD7DH}~AmIpO4Tk1z;1_fuFZj|H{u9>W13hYA7ewWJNCDU%PR z_&npqwK`hS>8CrWh;$!y^IV{gy)c{`PX2PcQ;Y&x32F*7GRicIbn{sKN-*fYG8EzkMw1!ED3xO`(9$O(_x>!2 zx7D9#Go96!L7w(NlM%3D~p+9aRR&KCEZ$Q|e{EtZsnLYl#2 zM1=+UH`A=LO94pX0?x`ScYb_awF*$63F@s7*Ket?1-e|w)nPR)o$EFC1);3Oy{(z_ zq->)3uOn;`?tX@JY1RYwU-eqMrq>u@>LV;*e{gnovpfND#x+Gj3P zeVPUBclQXA)ykwY9|gC5LK}WF(x;TBiItyS_EU0MTz#E122VBQpoecn?d%p; zpv-Q~Tc!avgnaH4ekCweIp@oFm>_6axB@};!G$h>|oLo(5_{R}1>C)d=B)Hj6>s`jsjGH14s?CY* zqon!jkff=`dy1pOR7pn(xG%M?4d_uA&ZV3~$PAe2wqR=4;16Z4_5WxC9Dh=`o@H8f z`J;q=8`$KA>92xyp$v~(JN_{&Dd5o8oxbHOekH5x&frp|4rh zvRV14Qr)p(Q2RJ>Gx}r7E1Mg1U-BxVc~<|fiEOdOZsCb8*;&LJQHHRe z2&BC7LMc#x+xCvv3f4-u%A`@xl@7p&<8|J>U=kZ*^os#%@^GBru$Pdeo}b8sd~P3@ zp#aI+E~R93e`J{&TXVu;0lzshP;~%4>-HiR7x#P|ZIcGi# z({rxUO-Q}oPCHy9pQUWo10md_w$PKkmpHf&UgxXEm;oz7vE(%`x$Mq@(hw{At?JN!i?R;SBuC_%v@mct zET>yHy8Vt`(A1Ieh${5r$68E*^{@;wL6U#Wf`pMpygfO;#&3-aF!iU<0v8T$JM^T` ztL&78Ki|t{FE3glo|FBxGE8x8iBeu)9xS>|WU-S&eoFJ&xKM>Olx#*Emy*J``B#2Q zR%vn#%gND|FSw$(LAp5L0+L&9Syj7{hC30nPxf>{5{2Emou`(G5cU7e3`BJq8FYg} z!h~v<QIg)iEt%# zsw0Du<;p{S9_sUISk-v2=TsN4AUj2V@v2so3Ej0`t016{ook8)TivM8K>}hM%~{Kd zlDkk$e(IZ5l>>IX zrDww61-PH}w{D3wZ!2~hY3di+xMDEg2ROq3ic8f?9vCx-j5}t^NChyiqIq*)$HTpR zcD7g$5b}z^9!s z(qur_@1Pg~%IlVI`5pAM#5v*_i&-bDQ~>`(Ho`wCvo~K3!lUvlMOR}`YXnB4a2p_| zh@jd_dxgE*yQ&cP0S5i-j??us<=-wp!&z1y^O*@^C`B_lL`c5-jAh9lccSY%`Y|%+ z436uEA5+q?vZOq}ka?HTHbNQX9-y;#1h#FKI_S>{|2`DC!A8OGFqn$; zKG-0pO;0SOG%-YaNBY(4k#@wXGBIw8s2DpoXrQ1qgO`$FR{YJSSyJQ$@W0WhYea={ z4NviL`4^=Qi@58VE6uyC^aTmU_ksJ?0&%!-OVw{$GNQj)g4X6%tX(ZREhUZVSu~1^ zHstQ9)1aEP)2epm1wh)7$OAO)p>+NyfmZBa@{ktFFsBukH&q0Fi?c(0KyTH~F{xz_ zOGPvH(K7F9{m@Uo9)GJgQ+XV`f(ZG;rL9C=fixObPovu$fE3_f!{^8n=>4aS{ag=E z8Ja7+3X!#xO~fsk8W>Q3L9~ggAoBmSD^E~s3E+}b+)9sW1-4jSkW!}!W2RH&1*Z4j zqi3}_5ZT^46FrMnE8U~{*3t4yF9LK%$}jZ833aX-k*LJrxuE??cCyKn6nSf?^OOj4 z3@vWr2=CMI<(;2wSZCS_*PD7eO#fDcsKRky9Ny$kXnrq-i8kJ&^+Ok?0TC1VhJyA= zJO#WP+jf+Uf)IE86%~Oy#l^-~a^%+CtEo)uYFb~o{jfC*i1wah_FM+dp3HSM%xe0Q z-y?gGr~R31U+jCH`KVMvlSY-*pNV~rL=xu>9!fHuplxMF<}?JE(g-v*K#CfXGN`J` z3EHLe4EYc=wk8G%a{A+9U(?Z0-al~YqAH3Fo6Us=`XGlZ_i20sOVz5--rM7b;2bBh z`?iW3@RKK5*5@oG(vgLgf~K zx^dxk)gvGzcB3Ol)t&j?YMI5P8Vfo?5cI~s#`hh5RPG>$`>Il|4HJ8$CUjyIT9A+T zm7hlK397$YLui{-hGywe+n>1!H&M@%iA@Tn4%L&drByb=NaN>6;!$=-|6Sm8Uz#A)nba1 zZFwk5AQ(~Fk^N+5)EGe#P@}16#6~#31P0T25;r!LC_hvcay#wqMa$30Cwu{e@p-Rj zPi8;xlZ#JQP+iP@V3y@(Kumv}QfhA{Z=x*J4#fR2Jmmz%Pm2p;gCw7{aG@^|BJ%;K zC>m5t;?2C^ikida?A8a{HmQ#EP^ZsDXpCmdPseCh*+vI)tbqwQ9Bs=irP<^C3?M|Z z`2m|~UMncy1<`0fJ_iLqXs?ctyj#HZ)AqfwB-L2RrOlr?HM!k%r)-9pti8{t$@@ zsE~oew=2k2`h{C35cHj%tHW+NM+d~HC*vsM{H(&G`NBfwfZvupj1>BWre-T43`8)F z>O1#xPN5x+auW|}(uUAMh^%ld`CFCyoe0U4w29r>a5YM$^w&S~)o(AUJgI!gP5vl^ zjBiUvZBX9XJNb{XC=Fu;&NT*EPIwXYA$vd@y~`Etg`v{1j8YPbY67>8oEa1Azv;R9 z7vV8V(68E+2_4QWJxdH&KEi#dNR5Gs()giCEVvt$jY16KyfF+hVbh{l;10LB?~g|R zguvs-Y9Z{h>_mBaLaJuC$%*>TD{rv5kwJc2h@KiyB!+x7A-o59elty3eH#G347-1= z5p!{AlEStMm->UMLXhYoe$0CJ4>trq2&dQn+Mr8*e?EPgNJdFG#MJq`#HO*A@14NV z^&ils$yP0RztOC`z`m8+dvEv%BhkN|fonSYYfoR8&lI3DxaRm!5Zr^18SM%_k0_@b zVsn(wI?m|U)fuaK)GZF-bu+R}tC#FI+rpt^1;LF% z{T++P)DpA0qJ4r{%g-P!KjI4Y zpb0KHbZCu%IK&u|=ToN;s>L4g-Z}T5Fi0uBuo>4&R1mo-(v+{-LLEj#99%)>8=dZ8 zqFr2v^4x2iD_A~a61|;{=WZaf4)Ou+gt2c4^lY?lcEOM4WP<+pJndkooWFEN_Ycy7 zCb;poOuOBq6_1Ra&aA0k?*(SVDdFB9aM-R4)PIU?#wxp+&Q&k7c*`oW9123TP%>ha zb9#%8-n*k$Oe)XkFuJ>SK-S&G(`a?3!qTTG612NHh~O?G6lLJVh-8W!l$|4D@-6}- zdc#_uU!p|^_+ur{$oP`Or8V~!eEG$a+xH4-eBQyvdypqdv&m}g#mFMDPw1#8YabZ^X=OlauU-vJ z1eymq9B&IFjLj1h45NZAC^wn5>!*+P!6cR7f3S?)+LfPVbKTvGJdmgYwXa2*2Vxr9 zu&9RPo$#Bu5cISTf`pX;mx6c~G|OKhC$T=${3Ug6I#aOFx4n>^Rqzio_>+sQhtRd) zipQM5V)5(-k!!NU;5}kHsXv}xjWD5rR>%Cs<>s6TPYc@11=|BK$lv>jH@sLJ-P>Uv z5-Yj%qlJ+9jGz!qZn({>KatHtHrC}7YBWAsmJ-M&lAFA;FFwix&~oz-GGeyG3NEo) zrHmmx4%6sb6YQ4cHke12^P5w3Lerm#nLKXZ#?B#mP<&9@w+f9%d9G;SkV__^Abz0b zbZ5Xl>{0xT!^d}1;gg38T4_;IKOBDfa%1{Q7v&C+^zJ{nY`yzi!|*qoK0|;SiHRS( zYc@lcaCu$wb8GNS`5@PLBi_Ic3O_CITqBaNCGXpsIQKc)@pkPyw>S%So4|EPs@ynR z2B0`hRovR&V80->op?6znXv;vJd42}mcd}n|Ce(h$4xPtl=F;qVVDH=Z{UDEUK{Fs zQs%pewrl6|2QWs}0YmWCt2cog6=*{lVl)ZIdC;(KHe0p~zh8|~pDVUiOf4@m+RCt> zvp&!lM&W$b;R)@wSLx|B=FqB+7VP6T9SKMDViKCt7qZ5KH44^E<>x24M-2iPm$sK+ zgwB|waEYuUPlzSRCWyV|NPF6uU@QXU^p?g)0VyF`Uu&cWN>Yo*&M+^qi=mz$c%zoH z3Q>zO6!$Qsnc|SmD-pdeLuIVkH8)iW!Oo76(zq^weJdXb4C+c;te-*)t*P6nv-wln z${S!K9N05Tw1X>8^>jYlvBofJ%o%Q}&>SqwMo3khI-!!AtMe4o2vk{La`e59SGOw4 zg1xd_Ur0bZO7q@*La|$dq0Y4ottsC_hkV~C!jpgEM=(0lWE;0E+lTnUHf&$jFSyk- zqd0Aj!-nh9qU!;5E72aegL;r$lhd0f!y-nsXLz2I^ST>%n-#`-hpG5}GNgYR{}2>k z^!%Au8V`)iL^%UM;jmK5*=CNxY{%to7iY@ixmq0kC`*1a9h{F1f>>ulkz4&630dc1 zkaO!Y=JK7d5d6#dfZJ}#IpvWGq+<&>#?Za0+2q$hHOj?v_HZlrTK9P#<34H9?=7p{ zdhx%GJt#!5rrC(g(^R3R5~1Yjov;2C4aJ+c-OcquMlRhir~lL4+vtjj)*MMNvOz9! zrYOR3R0M|xKZyIn=dpFo4Ju@5&P|6yD5#_~6XlNE7rnB`9#~c5Wv}73{F2n9`&BV1 zBaOMP1YCCfqadmom1TkAKUAZ4TYTXq&2js=1>4+_MK|no4;h6J5uUhyYRo9ve``Cq zwE4AWhc20qgw)~QBqCMBc5oh5u2q)ED&1DC#c@W7j$@pcW4u6Jn)1d)NaK1kvs70G zWB716bt#!#Ke3x|gI(=}cZf@B7W3GHEz{J$ZIwWBKq!syZjB+r&-g&%AB-5WN-6rZ zn`)^_xYvzsE2ysMpl$QJTx2%UjB3mw%Mh?kO60cXKCyYsywZoJIVVYvr2onoY zZx2(!^mL>kPB%*6scm-_OVUw{RFhg>9MJkj^Mqil+F1UJP(VnUETp3HSk@5~nERD# zsN72Ntc{|e&`Xt~P~93KuebgxATX-}xom=fgSc~5KQ#tU#v?2y3sS!fzvM%)WJ-^` zUt<~fK5~Br{N!;dio#P$jK<`Th06JSadbQUl8+)}(F*D+<2z9Kzi*N82^~#0`;hdv z0w!uJGg<{p-OVD(q%cq+A`Mp84Bg{W|5@mGv57vL+15|; zF$^^^`;?##j@jFI{9;q757O>aS0pLLSH?yo=vBOU;%9Y84z71; z`xu_R&U^V65>rIyMydkWeeqIxzp`Y9wMlNL@Y03Hi4p+1<{hnhaCCA9B5wyuhpjlvLy>coE_r?30X zidpN*%83mcssjSJb4y=y(?+kGFfBQmz+;q&X;-wD(S!x9C=HLgYsbvwPS=S)%99~f zX<0X)kSxLn;o${gULxKdH_e2;(n!h*7q+ew9zqPdNNmzcw$2g1R;lW+w0K-sZAYmQ zEFZN|OWRfh#=!!?NJ!j+eHMa6SAI5MMN> z#ypTw+5mebe{ER_(ZEtHkIzK;<^lyRA3^a=ZVk#=X|{CS*|KS;7mr(*D2o6WtfC5J zxFs^8!Ejl|gK-xE2+i0IND$*eZ4E3cT2CM@-x8t-Dxp-#ZFupFlxT?95R!MMrOig; z^9*2>HrDM86#F=ABsClucZ{wKg!t7ju506?B)9T41X-)>vAO>jJ_K_fE&`&7GsFAJ3=mo zC3)w1;KKpTiX?Ii0yIt7-msE9Jqb?rK=DRW=s2X?+lMoyn|K1|l(9re|7NZjgb~(o z!^ea50bAwi2~rQs1SPT|r-A&tui1TN$KFcEEn3ikYtHkZi5oM2Ptenx4*X3_&KY!d zNr3K|oKls&RNPu(37G{ zYw2R&hk1Ylnz4!T_bA|P&Xzhyb6mf#N$n9@vx{ocB_HG1TXCbNMNL(9D?*2Y#A36f z!Prqls9p^p+2Jf}4q0&cO5Biw`H5SFSs)sUsNe*tU+mLP8%&9APCLw#S*RHBlgKFu z_@3*zO#FL5=oOrkxARq(3<^dd64A$bS|>$!>2;0)XK36C<>Oko?*atSdJ;sjcfCh* zx%Y*j(2};Tp5MW&Y*1X`2~voQQ(1o^@5{k>tH73 z=;7Nz{T_zyd;TRX0q`Fb>%N$e|}bX_^`%-0|MAMoiWzOij4dd#Sa3qhUf_f_0&=BQKr5BU1| zD$qz{CiP@|$_C-Y{#*D^V%o$9<YPrrb}u z(_^*vd!cQAIWV%IeBd2ho7|+`f*~WBOYjnYG zR$YhAyIf&_?3G+k?AwoN*rwouULo3J-&e}E-~N*s*%`QxfSEMjhXM5#GpQBWjXBCN zR4Na!h#}*m80xt{#g?UtI_+*ZCvLep?HhO^c*R|*$x4vR%ZbW50|pt>l)aJH1Kdk- zD;BNgJDN;G>(MTCMGz%>73385e!nrd4Xc?vCDkO#KT_@PRwl|!S3Xlw)pC)9(S4KM z;EleDvS=;42Q{g5?7I#X$SnDh0pQ`1QbD4F)LP0VETmU$(;dy?cPBnDLuz4t? zDcMpZ-u{wC9tPw`n3l9PT=WiWq2gGC2~B5n>F$pj8N6IwuSx)LivJILAS6nTZK=bR zr|i;fMN=bRI$thARcyupl}U|ERGy0RD2)n_%Kjaannh1D8c}6m5)?~0^i8hC3!M%! zf6cweOM6}P9*ZOr2Zm*CpSN$qpG+T)pTh?Ey{8v)lGFdB$qBzJ3S7oSv$`KoK4wQf z^9WR*)gwTF|G!|HK)z!J=JJfJv%ONCUM^$H6X!Bhd?v?lI>b>(+Sto}Zg`ilx4Cj$ z)RTmj;`rBc8ys&{m%&tce#bE+2&K=?ZCYC919jaxjDh!?CD zzD%V-=xt*1Vxy`-7uD}~B9+)mD)#d!is#b?^Xvxim_rmNEj!umIu2ywMUhdoPUvfkcR{O|Qa7`Yh;46t;U85mf_{~2^ zsRi22#rUVqAUe6=RU$KmyoZ2XI9=!HRkHnD@&myiQBO#oi<<}bH6NQ? z8%}10$*L`F3Ww~^3A1-*w;ovzLw-^ii^#`GbpHtpM0ZS*PN3IJ8>NE_=@%OA#K3k> z^#|kc3-$a#Uaugz5$v%%wEXt!m|rY#l!J=0OKCFn0L4t!I9ysyCJehQ*iOGlGgdb@ zFX4Jji$tq)duwy770J#zRz;xL|HGh(T)W9uMZVjn)TWj}pfF`0h$ir&=hkc;Ma#7( z(_z4^^_mJ1z4J6SwnVWy_o$KGgPfY4zR|lw|Z_r=ef28^Co7_e|T;wUVJhX92j|5L7*U ztWo(KlVBobTZkCik7CJ#7r8UY0h#MNaMsYG-7|m&!@0q?WuDdO$I=>EWN@{Qx_rgt z;Ek5c$iXr!L{LMC2ff_O-G8t9d!c9g@G1gG@Sm1TQ?$EZ64x`5xbt+B=8?mqFM7 zXflKr_1#DE&!O=I3T;L~IC4Bdp8;ol8jt7kTM0p68|6-0E zifqDYsd_#@crwKZPsR`qc6ij7qWr6zYc*0rjXnHo97jYbyIePD?y0!AVJ4Ub8kzE( z8RaQ1p~3T9h~-;LS|u*vS3P+?c6mTFQ&Q)2y4Ob-%Jqz27A|nc!wqw@TYjWopfR3D z5o?J>>tjL=6oE8@(u*Io9dT>THL!?*{<81U6b|*mZb+#*(3V4SPH1oofD^DePqdp* zkq+a!FCJCd$b0;A{ejiM2PHGCox6QEXoZqksHV-LiBA&r(i|RXh;GZQo*f3`cM&Dn;*2%3*p70nzcD z)6IW@Hh$ycvOVO5(zp5t&J34wO50 zuPR+#@A($pWXuGZo&a8}sC=U2$p&t*nL2i2dUGEV$!N_TWvwymCXBsMq($Cg^8Cj( zr=_@PB>$Vq0U;ZnY4Jn7>D0hHANJl-fiCnS4(M&I+^Pff&L+zt&I9+f;~C?R`EOWB zd=k6)?)KMy9}i?!^!il>&B^^5XX{Vlf(cxL`d|YooyE^HX;|=Un+iz(KnoQbZA(7W z;3Yl;*lB-Ycc6g-1!yLg98eQ1tA{E+T_6BgZ1T9AxpBYZN-qqeS}a#U@4)8{A@pI=;Gl%rC7R%Vn*o42kx+1Vlbfy)eo+cY;^}~0hB344cO~`twRU{ zNN8UcN^mHhBetXg>winTWk)u$jvPryn>dUpel(pl8!DFsHj&BQRS_X zU-VmD*x`Z{x0YJAD>wCBE@4(N#?cJkZ{X|;kGgOq8L=m)q8(Hie=gjWQ>RNc{!X17 ze!Yh}tfl1SKj!u+Yzn(O6j0Ip4xX8bl=mR|o5Y_}&}3ZM7j|d>y%DvkT(JS|$z_cC z>bX1ta-XIusX;eASY-2*K>5Y1NWkswGT5B{PMP3M51z>>@M9#!PdLTh{Xwo)jOv|Y z;hAP(PKH3-ezLlZ-_xYcO0)!TUX!>};gyG>7EcWSkeZ|nn^-o=}q#7B*L0Atq z>ufD;wE#%-p(dQ081x=6gG957Ib0bvx3T*sE!x{wZB?S$;40(98Zn`+020xZX^kT{S~9l4$Cfu>%KH z9Uurpmo9u!F%n{feRBxo=kV7T7O))AUMpb-!k7n`gVmEWKlI>}fdVSn3pEU)Z-c@a z6W(EFuE-9-N&m>g0tuQ@p@>BK z6~|fcCFGvKxKbzwPkRPchM`|qKD_k>cBlI#6NSqSfdb(^euvFYY`E|cRvSk5q-&8vXmyHiwnRxbZ{Kvo`**F& z80&Dh0*LMVJU`k3Vl}S&29h0BAdgvudwF&iy81QY5W@k%KjW*{O>4x7aw(pE>;f711lzL_nQt<8+a)9jT9DU-xHH+X`0 zFDMtEI?t~jq6!fsvi@Mz0tXa}Cgd#9R?wgR-gTefLC1xnA9l7W$FOcK5&+mFo0Fw9 zGN8s=Xh06Spr0lK$+1h|(JEp{*OR`nY+ysR@Nmt5`p#H7qc(OyNe>R0fv6MpR5&YJ z33etPC&z{}wx{T3#*`|+=g;#uEC*bJX@-!@EN{`A6@m#Dv1!EW->1*$znsmsMyMCy8MT14( zmMsb$4mT}4UMz!BqGAa>T^n3NE8$nt;PvbWNaHG|x4W=rrNYig7e)b!?)&`Dq~ORT z4R=|8(bGr5pF8U-r-zQ0f@7{gQQ(n zAaR{b8#1mGbnlVV!g(Dx1dIEKA6UQHa@-1FI)Gs?Jy%K0V01fBvJmkPUl|OH2H2c^ z^E7~;w?@s1-{H`5prP=mGiud99>IT8S|^w@swru}F1|-AM2tW!Ham`Qm!2751fRPv zgJO>>%ui+X%P4VMW}B={x}PX=wQ5PK~O~;;%|p)i*U&NaD^b;B!8_l^aAgv zIsSOyW>CuatDhj9j9cO}uH8}A3F+%!JA|nhbEPFE*`J0sW|Xamz%iN{Z?wg{@Xk4TiF;RsZ~K`esd7lH zqx*v8L^7!WifV)*b3UwOBE9S`$+I7aD3kS+S2An*+#*cLWzJf7QrLZS6^4;Rh07ny z1-;s^v$MUrWpS?e*^d&Y4tUtgS!(!*eOfpq$zOphwTF~Uu7^O#upMnS*}Rd<>5(+o zj0Nm^Ko|~Q?_FU(;aEaqb)HnCeBVhMxt&3KmOzuQj)23Bjhw>TF8r>}(uL$1*@uzR z!{DM9cxVaNcSGlS)A>E_i4S1rs;SC?X_Z ziQXDM3Re~I7fDOe4|95rDO$5(?#=x?fNR+8o=2AKT(HRK3itn zYK}5dSEnL%O^g0in)<(*A1;$5wg#SA8jhloe9?j^Z>BQtT+-PUEGU?rzQKvmjtlP; zWIo6Y^OQbU+DF^^H~GhV;s5(bimC+qTd0|X^_gz)ZkhlNs{kEXn)_Q<(W|==O1RVq zQ^is&+arZQKXw8$Nl$q{Y0(cbUJH=#AWk4PQonogS1{@7U0tFr`EEq6=|UwmM{uU+ zKKDKCCv)}D-~K+lJ^HcBTA*`yAeSXs)WnS#r>3Lg@+=RUN@^0)D}xz>e-<|UCz!mS z{Jcw%KJs;!sQ(oCJe0P*vylk?Ya5grAUN^=&HIh-WX$7OrUVdK5HMr_pp0bc(Zhs$ zt*{F#@qn|}6qY#z6@^R)s(zm< zhZKRsxZvNLR37uY7)NRtz@rXNtt2gA6fhPQdQQd=;RXyy3%bojNd|6X8%Qz7R?+uJ zmmsb;B;}r|cln6(esnA%uuMK-KRv z+DCLb1Fb}xn=o6ahKdxeour*^ZH=J-YX>mTqc)c^uYho*W$(UI<&o$kNx9QSu|jWMmp% z)Le(}$CJFj-3s1>;I`>DJMkk@JHWLJub73)ES-O3AYZ8|BLL8hbpeM|Am`NVXh1YAz5v{{e>bKb+o~m0za>O@_Z9+5Qe4yDkjf3$5`*JF(Wg$SBTL$vFUh5c9NsfR^_uS6_^q`7#AprYZ-Obxs zS~xjEsF@FeJZWF{pp>}DR|BhedACXoTB7Vo(aVDH`?9BnjA4rIt8dXU{4Mt zd_j2hlVV&o4OgIZpyPvo5@JSuft*A{h%*b08)q}b zxDBiFd=_@^UwMvI8sD;Zmg|(l2hV`vqM@jgdp`LGh#*Lu+zHS9tuV?MdE;K@il3a7 zA5YWI9{zTJZR*$jxZ)D0FYKq``ewR>KWweR>wUx-?H1l3!Nqn?w-AQ;TGd(hw+uk zPUa?YsLt2wx>AA*lEJ9^HgosZO-=4 zrgWQS8gR+2NK0{O5#KN;-s}=IFc^kYCY)y3*fw+8Zzz4z=?a z8z_AJ&XLyXJPC!>;y%{~oV;AE1Q8>#a10)V1OJ0d-hzKP{?&ndWsG1IIfQoqRQFoA z&@DOK&C&Kt>azOP9|V!Lixzye^A$re8dcLUpKsJp*wHVnV+E);t#VhfCB2D7^YJzuYnoO;SCg6E(o zDmlT~Wi;yOXSy#}2`V3C!m}DvwUlrhwiXH2!rr1F6mS + + + + + ${SLACK_WEBHOOK_URL} + + + 에러 로그를 수집했습니다 🚓 + ✅ Timestamp\n%d{yyyy-MM-dd HH:mm:ss}\n 📍 Error Message\n%msg + + true + + + + + + + ERROR + + + + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/src/main/resources/static/docs/coupon.html b/src/main/resources/static/docs/coupon.html new file mode 100644 index 00000000..8041f735 --- /dev/null +++ b/src/main/resources/static/docs/coupon.html @@ -0,0 +1,723 @@ + + + + + + + +쿠폰(Coupon) + + + + +

+
+
+

쿠폰(Coupon)

+
+
+
+
쿠폰에 대해 생성/삭제/조회/발급/사용 기능을 제공합니다.
+
+
+
+
+

쿠폰 생성

+
+
+
관리자가 쿠폰을 생성합니다.
+
+
+

요청

+
+
+
POST /admins/coupons HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Content-Length: 186
+Host: localhost:8080
+
+{
+  "name" : "couponName",
+  "description" : "coupon description",
+  "type" : "황금",
+  "point" : 10,
+  "maxCount" : 10,
+  "startAt" : "2023-02-01",
+  "openAt" : "2023-01-01"
+}
+
+
+

응답

+
+
+
HTTP/1.1 201 Created
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+

쿠폰 삭제

+
+
+
관리자가 쿠폰 ID와 일치하는 쿠폰을 삭제합니다.
+
+
+

요청

+
+
+
DELETE /admins/coupons/36 HTTP/1.1
+Host: localhost:8080
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+

특정 쿠폰 조회

+
+
+
관리자 혹은 사용자가 특정 ID와 일치하는 쿠폰을 조회합니다.
+
+
+
+

요청

+
+
+
GET /coupons/24 HTTP/1.1
+Host: localhost:8080
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+Content-Type: application/json
+Content-Length: 202
+
+{
+  "id" : 24,
+  "adminId" : 1,
+  "name" : "couponName",
+  "description" : "",
+  "point" : 10,
+  "maxCount" : 100,
+  "type" : "MORNING",
+  "startAt" : "2023-02-01",
+  "openAt" : "2023-01-01"
+}
+
+
+
+
+
+
+

상태에 따른 쿠폰들을 조회

+
+
+
관리자 혹은 사용자가 날짜 상태에 따라 쿠폰들을 조회합니다.
+
+
+
+

요청

+
+
+
POST /coupons/search HTTP/1.1
+Content-Type: application/json;charset=UTF-8
+Content-Length: 44
+Host: localhost:8080
+
+{
+  "opened" : false,
+  "ended" : false
+}
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+Content-Type: application/json
+Content-Length: 203
+
+[ {
+  "id" : 25,
+  "adminId" : 1,
+  "name" : "coupon1",
+  "description" : "",
+  "point" : 10,
+  "maxCount" : 100,
+  "type" : "MORNING",
+  "startAt" : "2023-03-01",
+  "openAt" : "2023-01-01"
+} ]
+
+
+
+
+
+
+

특정 쿠폰에 대해 발급

+
+
+
사용자가 발급 가능한 쿠폰을 선착순으로 발급 받습니다.
+
+
+
+

요청

+
+
+
POST /coupons HTTP/1.1
+Content-Type: application/x-www-form-urlencoded
+Host: localhost:8080
+Content-Length: 21
+
+couponName=couponName
+
+
+

응답

+
+
+
HTTP/1.1 409 Conflict
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+Content-Type: application/json
+Content-Length: 65
+
+{
+  "message" : "이미 쿠폰 발급에 성공했습니다!"
+}
+
+
+
+
+
+
+

특정 사용자의 쿠폰 보관함을 조회

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 조회합니다.
+
+
+
+

요청

+
+
+
GET /my-coupons HTTP/1.1
+Host: localhost:8080
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+Content-Type: application/json
+Content-Length: 3
+
+[ ]
+
+
+
+
+
+
+

쿠폰을 사용

+
+
+
사용자가 자신의 보관함에 있는 쿠폰들을 사용합니다.
+
+
+
+

요청

+
+
+
POST /my-coupons/8 HTTP/1.1
+Host: localhost:8080
+Content-Type: application/x-www-form-urlencoded
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/static/docs/index.html b/src/main/resources/static/docs/index.html new file mode 100644 index 00000000..215db37a --- /dev/null +++ b/src/main/resources/static/docs/index.html @@ -0,0 +1,630 @@ + + + + + + + +MOABAM API 문서 + + + + + + +
+
+
+

1. 개요

+
+
+

이 API 문서는 'MOABAM' 프로젝트의 산출물입니다.

+
+
+

1.1. API 서버 경로

+ +++++ + + + + + + + + + + + + + + + + + +

환경

DNS

비고

개발(dev)

dev.moabam.com

운영(prod)

www.moabam.com

+
+ + + + + +
+ + +
+

해당 프로젝트 API 문서는 [특이사항]입니다.

+
+
+
+
+ + + + + +
+ + +
+

해당 프로젝트 API 문서는 [주의사항]입니다.

+
+
+
+
+
+

1.2. 응답형식

+
+

프로젝트는 다음과 같은 응답형식을 제공합니다.

+
+
+

1.2.1. 정상(2XX)

+ ++++ + + + + + + + + + + + + +
응답데이터가 없는 경우응답데이터가 있는 경우
+
+
{
+
+}
+
+
+
+
{
+  "name": "Hong-Dosan"
+}
+
+
+
+
+

1.2.2. 상태코드(HttpStatus)

+
+

응답시 다음과 같은 응답상태 헤더, 응답코드 및 응답메시지를 제공합니다.

+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HttpStatus설명

OK(200)

정상 응답

CREATED(201)

새로운 리소스 생성

BAD_REQUEST(400)

요청값 누락, 잘못된 기입

UNAUTHORIZED(401)

비인증 요청

NOT_FOUND(404)

요청값 누락, 잘못된 기입, 비인가 접속 등

CONFLICT(409)

요청값 중복

INTERNAL_SERVER_ERROR(500)

알 수 없는 서버 에러가 발생했습니다. 관리자에게 문의하세요.

+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/main/resources/static/docs/notification.html b/src/main/resources/static/docs/notification.html new file mode 100644 index 00000000..4cd1aa89 --- /dev/null +++ b/src/main/resources/static/docs/notification.html @@ -0,0 +1,520 @@ + + + + + + + +알림(Notification) + + + + + +
+
+

알림(Notification)

+
+
+
+
콕 찌르기 알림, FCM Token 저장 기능을 제공합니다.
+
+
+
+

콕 찌르기 알림

+
+
+
1) 특정 방의 사용자가 다른 사용자를 콕 찌릅니다.
+2) 서버에서 콕 찌를 대상의 FCM Token 여부를 검증합니다.
+3) Firebase 서버에 FCM Push Messaing 알림을 비동기로 요청합니다.
+4) Firebase 서버에서 FCM Token으로 식별된 기기에 알림을 보냅니다.
+
+
+

요청

+
+
+
GET /notifications/rooms/1/members/2 HTTP/1.1
+Host: localhost:8080
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+

FCM TOKEN 저장

+
+
+
1) 특정 사용자의 FCM-TOKEN을 받아서 REDIS DB에 저장합니다.
+
+
+

요청

+
+
+
POST /notifications HTTP/1.1
+Content-Type: application/x-www-form-urlencoded
+Host: localhost:8080
+Content-Length: 18
+
+fcmToken=FCM-TOKEN
+
+
+

응답

+
+
+
HTTP/1.1 200 OK
+Access-Control-Allow-Origin: http://localhost:8080
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
+Access-Control-Allow-Headers: Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers, X-Requested-With,Content-Type, Referer
+Access-Control-Allow-Credentials: true
+Access-Control-Max-Age: 3600
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/test/java/com/moabam/MoabamServerApplicationTests.java b/src/test/java/com/moabam/MoabamServerApplicationTests.java index ec2bac91..3eabd1b5 100644 --- a/src/test/java/com/moabam/MoabamServerApplicationTests.java +++ b/src/test/java/com/moabam/MoabamServerApplicationTests.java @@ -1,13 +1,8 @@ package com.moabam; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MoabamServerApplicationTests { - @Test - void contextLoads() { - } - } diff --git a/src/test/java/com/moabam/api/application/auth/AuthorizationServiceTest.java b/src/test/java/com/moabam/api/application/auth/AuthorizationServiceTest.java new file mode 100644 index 00000000..75f4c5a9 --- /dev/null +++ b/src/test/java/com/moabam/api/application/auth/AuthorizationServiceTest.java @@ -0,0 +1,352 @@ +package com.moabam.api.application.auth; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; + +import com.moabam.admin.application.admin.AdminService; +import com.moabam.api.application.auth.mapper.AuthorizationMapper; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.auth.repository.TokenRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.auth.AuthorizationCodeRequest; +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenRequest; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.common.util.CookieUtils; +import com.moabam.global.config.AllowOriginConfig; +import com.moabam.global.config.OAuthConfig; +import com.moabam.global.config.TokenConfig; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.AuthorizationResponseFixture; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.TokenSaveValueFixture; + +import jakarta.servlet.http.Cookie; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class AuthorizationServiceTest { + + @InjectMocks + AuthorizationService authorizationService; + + @Mock + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + + @Mock + MemberService memberService; + + @Mock + AdminService adminService; + + @Mock + JwtProviderService jwtProviderService; + + @Mock + FcmService fcmService; + + @Mock + TokenRepository tokenRepository; + + AllowOriginConfig allowOriginsConfig; + OAuthConfig oauthConfig; + TokenConfig tokenConfig; + AuthorizationService noPropertyService; + OAuthConfig noOAuthConfig; + String domain = "Test"; + + @BeforeEach + public void initParams() { + String secretKey = "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"; + String adminKey = "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"; + + allowOriginsConfig = new AllowOriginConfig(domain, domain, List.of("test", "test")); + ReflectionTestUtils.setField(authorizationService, "allowOriginsConfig", allowOriginsConfig); + tokenConfig = new TokenConfig(null, 100000, 150000, secretKey, adminKey); + ReflectionTestUtils.setField(authorizationService, "tokenConfig", tokenConfig); + + oauthConfig = new OAuthConfig( + new OAuthConfig.Provider("https://authorization/url", "http://redirect/url", "http://token/url", + "http://tokenInfo/url", "https://deleteRequest/url", "https://adminRequest/url"), + new OAuthConfig.Client("provider", "testtestetsttest", "testtesttest", "authorization_code", + List.of("profile_nickname", "profile_image"), "adminKey")); + ReflectionTestUtils.setField(authorizationService, "oAuthConfig", oauthConfig); + + noOAuthConfig = new OAuthConfig( + new OAuthConfig.Provider(null, null, null, null, null, null), + new OAuthConfig.Client(null, null, null, null, null, null)); + noPropertyService = new AuthorizationService(fcmService, noOAuthConfig, tokenConfig, + oAuth2AuthorizationServerRequestService, memberService, adminService, + jwtProviderService, tokenRepository, allowOriginsConfig); + } + + @DisplayName("인가코드 URI 생성 매퍼 실패") + @Test + void authorization_code_request_mapping_fail() { + // When + Then + Assertions.assertThatThrownBy(() -> AuthorizationMapper.toAuthorizationCodeRequest(noOAuthConfig)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("인가코드 URI 생성 매퍼 성공") + @Test + void authorization_code_request_mapping_success() { + // Given + AuthorizationCodeRequest authorizationCodeRequest = AuthorizationMapper.toAuthorizationCodeRequest(oauthConfig); + + // When + Then + assertThat(authorizationCodeRequest).isNotNull(); + assertAll(() -> assertThat(authorizationCodeRequest.clientId()).isEqualTo(oauthConfig.client().clientId()), + () -> assertThat(authorizationCodeRequest.redirectUri()).isEqualTo(oauthConfig.provider().redirectUri())); + } + + @DisplayName("redirect 로그인페이지 성공") + @Test + void redirect_loginPage_success() { + // given + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + // when + authorizationService.redirectToLoginPage(mockHttpServletResponse); + + // then + verify(oAuth2AuthorizationServerRequestService).loginRequest(eq(mockHttpServletResponse), anyString()); + } + + @DisplayName("인가코드 반환 실패") + @Test + void authorization_grant_fail() { + // Given + AuthorizationCodeResponse authorizationCodeResponse = new AuthorizationCodeResponse(null, "error", + "errorDescription", null); + + // When + Then + assertThatThrownBy(() -> authorizationService.requestToken(authorizationCodeResponse)).isInstanceOf( + BadRequestException.class).hasMessage(ErrorMessage.GRANT_FAILED.getMessage()); + } + + @DisplayName("인가코드 반환 성공") + @Test + void authorization_grant_success() { + // Given + AuthorizationCodeResponse authorizationCodeResponse = + new AuthorizationCodeResponse("test", null, null, null); + AuthorizationTokenResponse authorizationTokenResponse = + AuthorizationResponseFixture.authorizationTokenResponse(); + + // When + when(oAuth2AuthorizationServerRequestService.requestAuthorizationServer(anyString(), any())).thenReturn( + new ResponseEntity<>(authorizationTokenResponse, HttpStatus.OK)); + + // When + Then + assertThatNoException().isThrownBy(() -> authorizationService.requestToken(authorizationCodeResponse)); + } + + @DisplayName("토큰 요청 매퍼 실패 - code null") + @Test + void token_request_mapping_failBy_code() { + // When + Then + Assertions.assertThatThrownBy(() -> AuthorizationMapper.toAuthorizationTokenRequest(oauthConfig, + null, oauthConfig.provider().redirectUri())) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("토큰 요청 매퍼 실패 - config 에러") + @Test + void token_request_mapping_failBy_config() { + // When + Then + Assertions.assertThatThrownBy(() -> AuthorizationMapper.toAuthorizationTokenRequest(noOAuthConfig, "Test", + oauthConfig.provider().redirectUri())) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("토큰 요청 매퍼 성공") + @Test + void token_request_mapping_success() { + // Given + String code = "Test"; + AuthorizationTokenRequest authorizationTokenRequest = AuthorizationMapper.toAuthorizationTokenRequest( + oauthConfig, code, oauthConfig.provider().redirectUri()); + + // When + Then + assertThat(authorizationTokenRequest).isNotNull(); + assertAll(() -> assertThat(authorizationTokenRequest.clientId()).isEqualTo(oauthConfig.client().clientId()), + () -> assertThat(authorizationTokenRequest.redirectUri()).isEqualTo(oauthConfig.provider().redirectUri()), + () -> assertThat(authorizationTokenRequest.code()).isEqualTo(code)); + } + + @DisplayName("토큰 변경 성공") + @Test + void generate_token() { + // Given + AuthorizationTokenResponse tokenResponse = AuthorizationResponseFixture.authorizationTokenResponse(); + AuthorizationTokenInfoResponse tokenInfoResponse = + AuthorizationResponseFixture.authorizationTokenInfoResponse(); + + // When + when(oAuth2AuthorizationServerRequestService.tokenInfoRequest(any(String.class), + eq("Bearer " + tokenResponse.accessToken()))).thenReturn( + new ResponseEntity<>(tokenInfoResponse, HttpStatus.OK)); + + // Then + assertThatNoException().isThrownBy(() -> authorizationService.requestTokenInfo(tokenResponse)); + } + + @DisplayName("회원 가입 및 로그인 성공 테스트") + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void signUp_success(boolean isSignUp) { + // given + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = + AuthorizationResponseFixture.authorizationTokenInfoResponse(); + LoginResponse loginResponse = LoginResponse.builder() + .publicClaim(PublicClaim.builder().id(1L).nickname("nickname").role(Role.USER).build()) + .isSignUp(isSignUp) + .build(); + + willReturn(loginResponse).given(memberService).login(authorizationTokenInfoResponse); + + // when + LoginResponse result = authorizationService.signUpOrLogin(httpServletResponse, authorizationTokenInfoResponse); + + // then + assertThat(loginResponse).isEqualTo(result); + + Cookie tokenType = httpServletResponse.getCookie("token_type"); + assertThat(tokenType).isNotNull(); + assertThat(tokenType.getValue()).isEqualTo("Bearer"); + + Cookie accessCookie = httpServletResponse.getCookie("access_token"); + assertThat(accessCookie).isNotNull(); + assertAll(() -> assertThat(accessCookie.getSecure()).isTrue(), + () -> assertThat(accessCookie.isHttpOnly()).isTrue(), + () -> assertThat(accessCookie.getPath()).isEqualTo("/")); + + Cookie refreshCookie = httpServletResponse.getCookie("refresh_token"); + assertThat(refreshCookie).isNotNull(); + assertAll(() -> assertThat(refreshCookie.getSecure()).isTrue(), + () -> assertThat(refreshCookie.isHttpOnly()).isTrue(), + () -> assertThat(refreshCookie.getPath()).isEqualTo("/")); + } + + @DisplayName("토큰 redis 검증") + @Test + void valid_token_in_redis() { + // Given + willReturn(TokenSaveValueFixture.tokenSaveValue("token")) + .given(tokenRepository).getTokenSaveValue(1L, Role.USER); + + // When + Then + assertThatNoException().isThrownBy(() -> + authorizationService.validTokenPair(1L, "token", Role.USER)); + } + + @DisplayName("이전 토큰과 동일한지 검증") + @Test + void valid_token_failby_notEquals_token() { + // Given + willReturn(TokenSaveValueFixture.tokenSaveValue("token")) + .given(tokenRepository).getTokenSaveValue(1L, Role.USER); + + // When + Then + assertThatThrownBy(() -> authorizationService.validTokenPair(1L, "oldToken", Role.USER)).isInstanceOf( + UnauthorizedException.class).hasMessage(ErrorMessage.AUTHENTICATE_FAIL.getMessage()); + verify(tokenRepository).delete(1L, Role.USER); + } + + @DisplayName("토큰 삭제 성공") + @Test + void error_with_expire_token(@WithMember AuthMember authMember) { + // given + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + httpServletRequest.setCookies( + CookieUtils.tokenCookie("access_token", "value", 100000, domain), + CookieUtils.tokenCookie("refresh_token", "value", 100000, domain), + CookieUtils.typeCookie("Bearer", 100000, domain)); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + // When + authorizationService.logout(authMember, httpServletRequest, httpServletResponse); + Cookie cookie = httpServletResponse.getCookie("access_token"); + + // Then + assertThat(cookie).isNotNull(); + assertThat(cookie.getMaxAge()).isZero(); + assertThat(cookie.getValue()).isEqualTo("value"); + + verify(tokenRepository).delete(authMember.id(), Role.USER); + } + + @DisplayName("토큰 없어서 삭제 실패") + @Test + void token_null_delete_fail(@WithMember AuthMember authMember) { + // given + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + // When + authorizationService.logout(authMember, httpServletRequest, httpServletResponse); + + // Then + assertThat(httpServletResponse.getCookies()).isEmpty(); + } + + @DisplayName("회원 탈퇴 요청 성공") + @Test + void unlink_success(@WithMember AuthMember authMember) { + // given + Member member = MemberFixture.member(); + + given(memberService.findMember(any())).willReturn(MemberFixture.member()); + doNothing().when(oAuth2AuthorizationServerRequestService) + .unlinkMemberRequest(eq(oauthConfig.provider().unlink()), eq(oauthConfig.client().adminKey()), any()); + + // When + Then + assertThatNoException().isThrownBy(() -> authorizationService.unLinkMember(authMember)); + } + + @DisplayName("회원이 없어서 찾기 실패") + @Test + void unlink_failBy_find_Member(@WithMember AuthMember authMember) { + // Given + When + willThrow(new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)) + .given(memberService) + .validateMemberToDelete(authMember.id()); + + assertThatThrownBy(() -> authorizationService.unLinkMember(authMember)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } +} diff --git a/src/test/java/com/moabam/api/application/auth/JwtAuthenticationServiceTest.java b/src/test/java/com/moabam/api/application/auth/JwtAuthenticationServiceTest.java new file mode 100644 index 00000000..37149fb2 --- /dev/null +++ b/src/test/java/com/moabam/api/application/auth/JwtAuthenticationServiceTest.java @@ -0,0 +1,145 @@ +package com.moabam.api.application.auth; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +import org.assertj.core.api.Assertions; +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.config.TokenConfig; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.support.fixture.PublicClaimFixture; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +@ExtendWith(MockitoExtension.class) +class JwtAuthenticationServiceTest { + + String originIss = "PARK"; + String originSecretKey = "testestestestestestestestestesttestestestestestestestestestest"; + String adminKey = "testestestestestestestestestesttestestestestestestestestestest"; + long originId = 1L; + long originAccessExpire = 100000; + long originRefreshExpire = 150000; + + TokenConfig tokenConfig; + JwtAuthenticationService jwtAuthenticationService; + JwtProviderService jwtProviderService; + + @BeforeEach + void initConfig() { + tokenConfig = new TokenConfig(originIss, originAccessExpire, originRefreshExpire, originSecretKey, adminKey); + jwtProviderService = new JwtProviderService(tokenConfig); + jwtAuthenticationService = new JwtAuthenticationService(tokenConfig); + } + + @DisplayName("토큰 인증 성공 테스트") + @Test + void token_authentication_success() { + // given + String token = jwtProviderService.provideAccessToken(PublicClaimFixture.publicClaim()); + + // when, then + assertThatNoException().isThrownBy(() -> + jwtAuthenticationService.isTokenExpire(token, Role.USER)); + } + + @DisplayName("토큰 인증 시간 만료 테스트") + @Test + void token_authentication_time_expire() { + // Given + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + TokenConfig tokenConfig = new TokenConfig(originIss, 0, 0, originSecretKey, adminKey); + JwtAuthenticationService jwtAuthenticationService = new JwtAuthenticationService(tokenConfig); + JwtProviderService jwtProviderService = new JwtProviderService(tokenConfig); + String token = jwtProviderService.provideAccessToken(publicClaim); + + // When + assertThatNoException().isThrownBy(() -> { + boolean result = jwtAuthenticationService.isTokenExpire(token, Role.USER); + + // Then + assertThat(result).isTrue(); + }); + } + + @DisplayName("토큰의 payload 변조되어 인증 실패") + @Test + void token_authenticate_failBy_payload() { + // Given + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + String token = jwtProviderService.provideAccessToken(publicClaim); + String[] parts = token.split("\\."); + String claims = new String(Base64.getDecoder().decode(parts[1])); + + JSONObject tokenJson = new JSONObject(claims); + + // When + tokenJson.put("id", "2"); + + claims = tokenJson.toString(); + String newToken = String.join(".", parts[0], + Base64.getEncoder().encodeToString(claims.getBytes()), + parts[2]); + + // Then + Assertions.assertThatThrownBy(() -> jwtAuthenticationService.isTokenExpire(newToken, Role.USER)) + .isInstanceOf(UnauthorizedException.class); + } + + @DisplayName("토큰 위조 값 검증 테스트") + @Test + void token_authenticate_failBy_key() { + // Givne + String fakeKey = "fakefakefakefakefakefakefakefakefakefakefakefake"; + Key key = Keys.hmacShaKeyFor(fakeKey.getBytes(StandardCharsets.UTF_8)); + + Date now = new Date(); + String token = Jwts.builder() + .setHeaderParam("alg", "HS256") + .setHeaderParam("typ", "JWT") + .setIssuer(originIss) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + originAccessExpire)) + .claim("id", 5L) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + // When + Then + assertThatThrownBy(() -> jwtAuthenticationService.isTokenExpire(token, Role.USER)) + .isExactlyInstanceOf(UnauthorizedException.class); + } + + @DisplayName("토큰을 PublicClaim으로 변환 성공") + @Test + void token_parse_to_public_claim() { + // given + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + String token = jwtProviderService.provideAccessToken(publicClaim); + + // when + PublicClaim parsedClaim = jwtAuthenticationService.parseClaim(token); + + // then + assertAll( + () -> assertThat(publicClaim.id()).isEqualTo(parsedClaim.id()), + () -> assertThat(publicClaim.nickname()).isEqualTo(parsedClaim.nickname()), + () -> assertThat(publicClaim.role()).isEqualTo(parsedClaim.role()) + ); + } +} diff --git a/src/test/java/com/moabam/api/application/auth/JwtProviderServiceTest.java b/src/test/java/com/moabam/api/application/auth/JwtProviderServiceTest.java new file mode 100644 index 00000000..4a85f46a --- /dev/null +++ b/src/test/java/com/moabam/api/application/auth/JwtProviderServiceTest.java @@ -0,0 +1,168 @@ +package com.moabam.api.application.auth; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Base64; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.config.TokenConfig; +import com.moabam.support.fixture.PublicClaimFixture; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; + +class JwtProviderServiceTest { + + String iss = "PARK"; + String secretKey = "testestestestestestestestestesttestestestestestestestestestest"; + String adminKey = "testestestestestestestestestesttestestestestestestestestestest"; + long id = 1L; + + @DisplayName("access 토큰 생성 성공") + @Test + void create_access_token_success() throws JSONException { + // given + long accessExpire = 10000L; + + TokenConfig tokenConfig = new TokenConfig("PARK", accessExpire, 0L, secretKey, adminKey); + JwtProviderService jwtProviderService = new JwtProviderService(tokenConfig); + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + // when + String accessToken = jwtProviderService.provideAccessToken(publicClaim); + + String[] parts = accessToken.split("\\."); + String headers = new String(Base64.getDecoder().decode(parts[0])); + String claims = new String(Base64.getDecoder().decode(parts[1])); + + JSONObject headersJson = new JSONObject(headers); + JSONObject claimsJson = new JSONObject(claims); + + // then + assertAll( + () -> assertThat(headersJson.get("alg")).isEqualTo("HS256"), + () -> assertThat(headersJson.get("typ")).isEqualTo("JWT"), + () -> assertThat(claimsJson.get("iss")).isEqualTo(iss) + ); + + Long iat = Long.valueOf(claimsJson.get("iat").toString()); + Long exp = Long.valueOf(claimsJson.get("exp").toString()); + assertThat(iat).isLessThan(exp); + } + + @DisplayName("토큰 디코딩 실패") + @Test + void decoding_token_failBy_url() { + // given + String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + + ".eyJpc3MiOiJtb2Ftb2Ftb2FiYW0iLCJpYXQiOjE3MDEyMzQyNjksImV4c" + + "CI6MTcwMTIzNDU2OSwiaWQiOjIsIm5pY2tuYW1lIjoiXHVEODNEXHVEQzNC6rOw64-M7J20Iiwicm9sZSI6IlVTRVIifQ" + + ".yVcvshWQ6fsQ0OQ-A5kolDo-8QsLVFCD6dIENKWZH-A"; + String[] parts = token.split("\\."); + + // when + then + assertThatThrownBy(() -> Base64.getDecoder().decode(parts[1])).isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("토큰 디코딩 성공") + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + + ".eyJpc3MiOiJtb2Ftb2Ftb2FiYW0iLCJpYXQiOjE3MDEyMzQyNjksImV4cCI6MTcwMjQ0Mzg2OX0" + + ".IrcH_LvBKK1HezgY3PVY-0HQlhP6neEuydH6Mhz4Jgo", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + + ".eyJpc3MiOiJtb2Ftb2Ftb2FiYW0iLCJpYXQiOjE3MDEyMzQyNjksImV4cCI6MTcwMTIzNDU2OSwiaWQiOjIsIm" + + "5pY2tuYW1lIjoiXHVEODNEXHVEQzNC6rOw64-M7J20Iiwicm9sZSI6IlVTRVIifQ" + + ".yVcvshWQ6fsQ0OQ-A5kolDo-8QsLVFCD6dIENKWZH-A" + }) + void decoding_token_success(String token) { + // given + String[] parts = token.split("\\."); + + // When + Then + assertThatNoException().isThrownBy(() -> Decoders.BASE64URL.decode(parts[1])); + } + + @DisplayName("refresh 토큰 생성 성공") + @Test + void create_refresh_token_success() throws JSONException { + // given + long refreshExpire = 15000L; + + TokenConfig tokenConfig = new TokenConfig("PARK", 0L, refreshExpire, secretKey, adminKey); + JwtProviderService jwtProviderService = new JwtProviderService(tokenConfig); + + // when + String refreshToken = jwtProviderService.provideRefreshToken(Role.USER); + + String[] parts = refreshToken.split("\\."); + String headers = new String(Base64.getDecoder().decode(parts[0])); + String claims = new String(Base64.getDecoder().decode(parts[1])); + + JSONObject headersJson = new JSONObject(headers); + JSONObject claimsJson = new JSONObject(claims); + + // then + assertAll( + () -> assertThat(headersJson.get("alg")).isEqualTo("HS256"), + () -> assertThat(headersJson.get("typ")).isEqualTo("JWT"), + () -> assertThat(claimsJson.get("iss")).isEqualTo(iss) + ); + + Long iat = Long.valueOf(claimsJson.get("iat").toString()); + Long exp = Long.valueOf(claimsJson.get("exp").toString()); + assertThat(iat).isLessThan(exp); + } + + @DisplayName("access 토큰 만료시간에 따른 생성 실패") + @Test + void create_access_token_fail() { + // given + long accessExpire = -1L; + + TokenConfig tokenConfig = new TokenConfig("PARK", accessExpire, 0L, secretKey, adminKey); + JwtProviderService jwtProviderService = new JwtProviderService(tokenConfig); + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + // when + String accessToken = jwtProviderService.provideAccessToken(publicClaim); + + // then + assertThatThrownBy(() -> Jwts.parserBuilder() + .setSigningKey(tokenConfig.getKey()) + .build() + .parseClaimsJwt(accessToken) + ).isInstanceOf(ExpiredJwtException.class); + } + + @DisplayName("refresh 토큰 만료시간에 따른 생성 실패") + @Test + void create_token_fail() { + // given + long refreshExpire = -1L; + + TokenConfig tokenConfig = new TokenConfig("PARK", 0L, refreshExpire, secretKey, adminKey); + JwtProviderService jwtProviderService = new JwtProviderService(tokenConfig); + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + // when + String accessToken = jwtProviderService.provideAccessToken(publicClaim); + + // then + assertThatThrownBy(() -> Jwts.parserBuilder() + .setSigningKey(tokenConfig.getKey()) + .build() + .parseClaimsJwt(accessToken) + ).isExactlyInstanceOf(ExpiredJwtException.class); + } +} diff --git a/src/test/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestServiceTest.java b/src/test/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestServiceTest.java new file mode 100644 index 00000000..1d3e1ae2 --- /dev/null +++ b/src/test/java/com/moabam/api/application/auth/OAuth2AuthorizationServerRequestServiceTest.java @@ -0,0 +1,266 @@ +package com.moabam.api.application.auth; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletResponse; + +@ExtendWith(MockitoExtension.class) +public class OAuth2AuthorizationServerRequestServiceTest { + + @InjectMocks + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + + @Mock + RestTemplate restTemplate; + + @BeforeEach + void initField() { + ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); + } + + @DisplayName("로그인 페이지 접근 요청") + @Nested + class LoginPage { + + String uri = "https://authorization/url?" + + "response_type=code&" + + "client_id=testtestetsttest&" + + "redirect_uri=http://redirect/url&scope=profile_nickname,profile_image"; + + @DisplayName("로그인 페이지 접근 요청 성공") + @Test + void authorization_code_uri_generate_success() { + // Given + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + // When + oAuth2AuthorizationServerRequestService.loginRequest(mockHttpServletResponse, uri); + + // Then + assertThat(mockHttpServletResponse.getContentType()) + .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + assertThat(mockHttpServletResponse.getRedirectedUrl()).isEqualTo(uri); + } + + @DisplayName("redirect 실패 테스트") + @Test + void redirect_fail() { + // Given + HttpServletResponse mockHttpServletResponse = mock(HttpServletResponse.class); + + try { + doThrow(IOException.class).when(mockHttpServletResponse).sendRedirect(any(String.class)); + + assertThatThrownBy(() -> { + // When + Then + oAuth2AuthorizationServerRequestService.loginRequest(mockHttpServletResponse, uri); + }).isExactlyInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.REQUEST_FAILED.getMessage()); + } catch (Exception ignored) { + + } + } + } + + @DisplayName("Authorization Server 토큰 발급 요청") + @Nested + class TokenRequest { + + @DisplayName("토큰 발급 요청 성공") + @Test + void token_issue_request_success() { + // Given + String tokenUri = "test"; + MultiValueMap uriParams = new LinkedMultiValueMap<>(); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + HttpEntity> httpEntity = new HttpEntity<>(uriParams, headers); + + // When + doReturn(new ResponseEntity(HttpStatus.OK)) + .when(restTemplate).exchange( + eq(tokenUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(AuthorizationTokenResponse.class)); + oAuth2AuthorizationServerRequestService.requestAuthorizationServer(tokenUri, uriParams); + + // Then + verify(restTemplate, times(1)) + .exchange(tokenUri, HttpMethod.POST, httpEntity, AuthorizationTokenResponse.class); + } + + @DisplayName("토큰 발급 요청 실패") + @ParameterizedTest + @ValueSource(ints = {400, 401, 403, 429, 500, 502, 503}) + void token_issue_request_fail(int code) { + // Given + String tokenUri = "test"; + MultiValueMap uriParams = new LinkedMultiValueMap<>(); + + // When + doThrow(new HttpClientErrorException(HttpStatusCode.valueOf(code))) + .when(restTemplate).exchange( + eq(tokenUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(AuthorizationTokenResponse.class)); + + // Then + assertThatThrownBy(() -> + oAuth2AuthorizationServerRequestService.requestAuthorizationServer(tokenUri, uriParams)) + .isInstanceOf(HttpClientErrorException.class); + } + } + + @DisplayName("토큰 정보 조회 발급 요청") + @Nested + class TokenInfoRequest { + + @DisplayName("토큰 정보 조회 요청 성공") + @Test + void token_info_request_success() { + // Given + String tokenInfoUri = "http://tokenInfo/uri"; + String tokenValue = "Bearer access-token"; + + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", tokenValue); + HttpEntity httpEntity = new HttpEntity<>(headers); + + // When + doReturn(new ResponseEntity(HttpStatus.OK)) + .when(restTemplate).exchange( + eq(tokenInfoUri), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(AuthorizationTokenInfoResponse.class)); + oAuth2AuthorizationServerRequestService.tokenInfoRequest(tokenInfoUri, tokenValue); + + // Then + verify(restTemplate, times(1)) + .exchange(tokenInfoUri, HttpMethod.GET, httpEntity, AuthorizationTokenInfoResponse.class); + } + + @DisplayName("토큰 발급 요청 실패") + @ParameterizedTest + @ValueSource(ints = {400, 401}) + void token_issue_request_fail(int code) { + // Given + String tokenInfoUri = "http://tokenInfo/uri"; + String tokenValue = "Bearer access-token"; + + // When + doThrow(new HttpClientErrorException(HttpStatusCode.valueOf(code))) + .when(restTemplate).exchange( + eq(tokenInfoUri), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(AuthorizationTokenInfoResponse.class)); + + // Then + assertThatThrownBy(() -> + oAuth2AuthorizationServerRequestService.tokenInfoRequest(tokenInfoUri, tokenValue)) + .isInstanceOf(HttpClientErrorException.class); + } + } + + @DisplayName("회원 연결 끊기 요청") + @Nested + class Delete { + + @DisplayName("성공") + @Test + void token_info_request_success() { + // Given + String deleteUri = "https://deleteUrl/uri"; + String adminKey = "admin-token"; + String socialId = "1"; + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8); + headers.add("Authorization", "KakaoAK " + adminKey); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("target_id_type", "user_id"); + params.add("target_id", socialId); + + HttpEntity> httpEntity = new HttpEntity<>(params, headers); + + // When + doReturn(new ResponseEntity(HttpStatus.OK)) + .when(restTemplate).exchange( + eq(deleteUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(Void.class)); + oAuth2AuthorizationServerRequestService.unlinkMemberRequest(deleteUri, adminKey, params); + + // Then + verify(restTemplate, times(1)) + .exchange(deleteUri, HttpMethod.POST, httpEntity, Void.class); + } + + @DisplayName("실패") + @ParameterizedTest + @ValueSource(ints = {400, 401}) + void token_issue_request_fail(int code) { + // Given + String deleteUri = "https://deleteUrl/uri"; + String adminKey = "admin-token"; + String socialId = "1"; + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("target_id_type", "user_id"); + params.add("target_id", socialId); + + // When + doThrow(new HttpClientErrorException(HttpStatusCode.valueOf(code))) + .when(restTemplate).exchange( + eq(deleteUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(Void.class)); + + // Then + assertThatThrownBy(() -> + oAuth2AuthorizationServerRequestService.unlinkMemberRequest(deleteUri, adminKey, params)) + .isInstanceOf(HttpClientErrorException.class); + } + } +} diff --git a/src/test/java/com/moabam/api/application/bug/BugServiceTest.java b/src/test/java/com/moabam/api/application/bug/BugServiceTest.java new file mode 100644 index 00000000..7c147a98 --- /dev/null +++ b/src/test/java/com/moabam/api/application/bug/BugServiceTest.java @@ -0,0 +1,183 @@ +package com.moabam.api.application.bug; + +import static com.moabam.api.domain.product.ProductType.*; +import static com.moabam.support.fixture.BugFixture.*; +import static com.moabam.support.fixture.CouponFixture.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.payment.PaymentMapper; +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.bug.repository.BugHistoryRepository; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.repository.ProductRepository; +import com.moabam.api.dto.bug.BugResponse; +import com.moabam.api.dto.product.ProductResponse; +import com.moabam.api.dto.product.ProductsResponse; +import com.moabam.api.dto.product.PurchaseProductRequest; +import com.moabam.api.dto.product.PurchaseProductResponse; +import com.moabam.global.common.util.StreamUtils; +import com.moabam.global.error.exception.NotFoundException; + +@ExtendWith(MockitoExtension.class) +class BugServiceTest { + + @InjectMocks + BugService bugService; + + @Mock + MemberService memberService; + + @Mock + BugHistoryRepository bugHistoryRepository; + + @Mock + ProductRepository productRepository; + + @Mock + PaymentRepository paymentRepository; + + @Mock + CouponWalletSearchRepository couponWalletSearchRepository; + + @DisplayName("벌레를 조회한다.") + @Test + void get_bug_success() { + // given + Long memberId = 1L; + Member member = member(); + given(memberService.findMember(memberId)).willReturn(member); + + // when + BugResponse response = bugService.getBug(memberId); + + // then + Bug bug = member.getBug(); + assertThat(response.morningBug()).isEqualTo(bug.getMorningBug()); + assertThat(response.nightBug()).isEqualTo(bug.getNightBug()); + assertThat(response.goldenBug()).isEqualTo(bug.getGoldenBug()); + } + + @DisplayName("벌레 상품 목록을 조회한다.") + @Test + void get_bug_products_success() { + // given + Product product1 = bugProduct(); + Product product2 = bugProduct(); + given(productRepository.findAllByType(BUG)).willReturn(List.of(product1, product2)); + + // when + ProductsResponse response = bugService.getBugProducts(); + + // then + List productNames = StreamUtils.map(response.products(), ProductResponse::name); + assertThat(response.products()).hasSize(2); + assertThat(productNames).containsExactly(BUG_PRODUCT_NAME, BUG_PRODUCT_NAME); + } + + @DisplayName("벌레 상품을 구매한다.") + @Nested + class PurchaseBugProduct { + + @DisplayName("쿠폰 적용에 성공한다.") + @Test + void apply_coupon_success() { + // given + Long memberId = 1L; + Long productId = 1L; + Long couponWalletId = 1L; + CouponWallet couponWallet = CouponWallet.create(memberId, discount1000Coupon()); + Payment payment = PaymentMapper.toPayment(memberId, bugProduct()); + PurchaseProductRequest request = new PurchaseProductRequest(couponWalletId); + given(productRepository.findById(productId)).willReturn(Optional.of(bugProduct())); + given(paymentRepository.save(any(Payment.class))).willReturn(payment); + given(couponWalletSearchRepository.findByIdAndMemberId(couponWalletId, memberId)).willReturn( + Optional.of(couponWallet)); + + // when + PurchaseProductResponse response = bugService.purchaseBugProduct(memberId, productId, request); + + // then + assertThat(response.price()).isEqualTo(BUG_PRODUCT_PRICE - 1000); + assertThat(response.orderName()).isEqualTo(BUG_PRODUCT_NAME); + } + + @DisplayName("해당 상품이 존재하지 않으면 예외가 발생한다.") + @Test + void product_not_found_exception() { + // given + Long memberId = 1L; + Long productId = 1L; + PurchaseProductRequest request = new PurchaseProductRequest(null); + given(productRepository.findById(productId)).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> bugService.purchaseBugProduct(memberId, productId, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("존재하지 않는 상품입니다."); + } + } + + @DisplayName("벌레를 사용한다.") + @Test + void use_success() { + // given + Member member = spy(member()); + given(member.getId()).willReturn(1L); + + // when + bugService.use(member, BugType.MORNING, 5); + + // then + assertThat(member.getBug().getMorningBug()).isEqualTo(MORNING_BUG - 5); + } + + @DisplayName("벌레 보상을 준다.") + @Test + void reward_success() { + // given + Member member = spy(member()); + given(member.getId()).willReturn(1L); + + // when + bugService.reward(member, BugType.NIGHT, 5); + + // then + assertThat(member.getBug().getNightBug()).isEqualTo(NIGHT_BUG + 5); + } + + @DisplayName("벌레를 충전한다.") + @Test + void charge_success() { + // given + Long memberId = 1L; + Member member = member(); + given(memberService.findMember(memberId)).willReturn(member); + + // when + bugService.charge(memberId, bugProduct()); + + // then + assertThat(member.getBug().getGoldenBug()).isEqualTo(GOLDEN_BUG + BUG_PRODUCT_QUANTITY); + } +} diff --git a/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java new file mode 100644 index 00000000..d209cdfe --- /dev/null +++ b/src/test/java/com/moabam/api/application/coupon/CouponManageServiceTest.java @@ -0,0 +1,172 @@ +package com.moabam.api.application.coupon; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.notification.NotificationService; +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponManageRepository; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.CouponFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class CouponManageServiceTest { + + @InjectMocks + CouponManageService couponManageService; + + @Mock + NotificationService notificationService; + + @Mock + CouponRepository couponRepository; + + @Mock + CouponManageRepository couponManageRepository; + + @Mock + CouponWalletRepository couponWalletRepository; + + @Mock + ClockHolder clockHolder; + + @DisplayName("10명의 사용자가 쿠폰 발행을 성공적으로 한다.") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideValues_Long") + @ParameterizedTest + void issue_all_success(Set values) { + // Given + Coupon coupon = CouponFixture.coupon(1000, 100); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponManageRepository.rangeQueue(any(String.class), any(long.class), any(long.class))) + .willReturn(values); + given(couponManageRepository.getCount(any(String.class))).willReturn(coupon.getMaxCount() - 1); + + // When + couponManageService.issue(); + + // Then + verify(couponWalletRepository, times(10)).save(any(CouponWallet.class)); + verify(notificationService, times(10)) + .sendCouponIssueResult(any(Long.class), any(String.class), any(String.class)); + } + + @DisplayName("발행 가능한 쿠폰이 없다.") + @Test + void issue_notStartAt() { + // Given + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.empty()); + + // When + couponManageService.issue(); + + // Then + verify(couponWalletRepository, times(0)).save(any(CouponWallet.class)); + verify(couponManageRepository, times(0)) + .rangeQueue(any(String.class), any(long.class), any(long.class)); + verify(notificationService, times(0)) + .sendCouponIssueResult(any(Long.class), any(String.class), any(String.class)); + } + + @DisplayName("해당 쿠폰은 재고가 마감된 쿠폰이다.") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideValues_Long") + @ParameterizedTest + void issue_stockEnd() { + // Given + Coupon coupon = CouponFixture.coupon(1000, 100); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByStartAt(any(LocalDate.class))).willReturn(Optional.of(coupon)); + given(couponManageRepository.getCount(any(String.class))).willReturn(coupon.getMaxCount()); + + // When + couponManageService.issue(); + + // Then + verify(couponManageRepository, times(0)).increase(any(String.class), any(int.class)); + verify(couponWalletRepository, times(0)).save(any(CouponWallet.class)); + verify(couponManageRepository, times(0)).rangeQueue(any(String.class), any(int.class), any(int.class)); + verify(notificationService, times(0)) + .sendCouponIssueResult(any(Long.class), any(String.class), any(String.class)); + } + + @DisplayName("쿠폰 발급 요청을 성공적으로 큐에 등록한다. - Void") + @Test + void registerQueue_success() { + // Given + Coupon coupon = CouponFixture.coupon(); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponManageRepository.sizeQueue(any(String.class))).willReturn(coupon.getMaxCount() - 1); + given(couponRepository.findByNameAndStartAt(any(String.class), any(LocalDate.class))) + .willReturn(Optional.of(coupon)); + + // When + couponManageService.registerQueue(coupon.getName(), 1L); + + // Then + verify(couponManageRepository).addIfAbsentQueue(any(String.class), any(Long.class), any(double.class)); + } + + @DisplayName("금일 발급이 가능한 쿠폰이 없다. - BadRequestException") + @Test + void registerQueue_No_BadRequestException() { + // Given + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponRepository.findByNameAndStartAt(any(String.class), any(LocalDate.class))) + .willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> couponManageService.registerQueue("couponName", 1L)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_PERIOD.getMessage()); + } + + @DisplayName("쿠폰 대기열과 발행된 재고가 정상적으로 삭제된다.") + @Test + void deleteQueue_success() { + // Given + String couponName = "couponName"; + + // When + couponManageService.delete(couponName); + + // Then + verify(couponManageRepository).deleteQueue(couponName); + } + + @DisplayName("쿠폰 대기열이 정상적으로 삭제되지 않는다.") + @Test + void deleteQueue_NullPointerException() { + // Given + willThrow(NullPointerException.class) + .given(couponManageRepository) + .deleteQueue(any(String.class)); + + // When & Then + assertThatThrownBy(() -> couponManageService.delete("null")) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java b/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java new file mode 100644 index 00000000..b44d6f8c --- /dev/null +++ b/src/test/java/com/moabam/api/application/coupon/CouponServiceTest.java @@ -0,0 +1,367 @@ +package com.moabam.api.application.coupon; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponSearchRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.coupon.CouponResponse; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.dto.coupon.MyCouponResponse; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.CouponFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class CouponServiceTest { + + @InjectMocks + CouponService couponService; + + @Mock + BugService bugService; + + @Mock + CouponManageService couponManageService; + + @Mock + CouponRepository couponRepository; + + @Mock + CouponWalletRepository couponWalletRepository; + + @Mock + CouponSearchRepository couponSearchRepository; + + @Mock + CouponWalletSearchRepository couponWalletSearchRepository; + + @Mock + ClockHolder clockHolder; + + @DisplayName("관리자가 쿠폰을 성공적으로 발행한다. - Void") + @Test + void create_success() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When + couponService.create(request, 1L, Role.ADMIN); + + // Then + verify(couponRepository).save(any(Coupon.class)); + } + + @DisplayName("권한 없는 사용자가 쿠폰을 발행한다. - NotFoundException") + @Test + void create_Admin_NotFoundException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.USER)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("존재하지 않는 쿠폰 종류를 발행한다. - NotFoundException") + @Test + void create_Type_NotFoundException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest("UNKNOWN", 2, 1); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.ADMIN)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON_TYPE.getMessage()); + } + + @DisplayName("중복된 쿠폰명을 발행한다. - ConflictException") + @Test + void create_Name_ConflictException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(couponRepository.existsByName(any(String.class))).willReturn(true); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.ADMIN)) + .isInstanceOf(ConflictException.class) + .hasMessage(ErrorMessage.CONFLICT_COUPON_NAME.getMessage()); + } + + @DisplayName("중복된 쿠폰 발행 가능 날짜를 발행한다. - ConflictException") + @Test + void create_StartAt_ConflictException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(true); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.ADMIN)) + .isInstanceOf(ConflictException.class) + .hasMessage(ErrorMessage.CONFLICT_COUPON_START_AT.getMessage()); + } + + @DisplayName("현재 날짜가 쿠폰 발급 가능 날짜와 같거나 이후이다. - BadRequestException") + @Test + void create_StartAt_BadRequestException() { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(clockHolder.date()).willReturn(LocalDate.of(2025, 1, 1)); + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(false); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.ADMIN)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_START_AT_PERIOD.getMessage()); + } + + @DisplayName("쿠폰 정보 오픈 날짜가 쿠폰 발급 시작 날짜와 같거나 이후인 쿠폰을 발행한다. - BadRequestException") + @Test + void create_OpenAt_BadRequestException() { + // Given + String couponType = CouponType.GOLDEN.getName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 1); + + given(couponRepository.existsByName(any(String.class))).willReturn(false); + given(couponRepository.existsByStartAt(any(LocalDate.class))).willReturn(false); + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + assertThatThrownBy(() -> couponService.create(request, 1L, Role.ADMIN)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_OPEN_AT_PERIOD.getMessage()); + } + + @DisplayName("쿠폰 아이디와 일치하는 쿠폰을 성공적으로 삭제한다. - Void") + @Test + void delete_success() { + // Given + Coupon coupon = CouponFixture.coupon(10, 100); + + given(couponRepository.findById(any(Long.class))).willReturn(Optional.of(coupon)); + + // When + couponService.delete(1L, Role.ADMIN); + + // Then + verify(couponRepository).delete(coupon); + verify(couponManageService).delete(any(String.class)); + } + + @DisplayName("권한 없는 사용자가 쿠폰을 삭제한다. - NotFoundException") + @Test + void delete_Admin_NotFoundException() { + // When & Then + assertThatThrownBy(() -> couponService.delete(1L, Role.USER)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("존재하지 않는 쿠폰 아이디를 삭제하려고 시도한다. - NotFoundException") + @Test + void delete_NotFoundException() { + // Given + given(couponRepository.findById(any(Long.class))).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> couponService.delete(1L, Role.ADMIN)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON.getMessage()); + } + + @DisplayName("특정 쿠폰을 성공적으로 조회한다. - CouponResponse") + @Test + void getById_success() { + // Given + Coupon coupon = CouponFixture.coupon(10, 100); + + given(couponRepository.findById(any(Long.class))).willReturn(Optional.of(coupon)); + + // When + CouponResponse actual = couponService.getById(1L); + + // Then + assertThat(actual.point()).isEqualTo(coupon.getPoint()); + assertThat(actual.maxCount()).isEqualTo(coupon.getMaxCount()); + } + + @DisplayName("존재하지 않는 쿠폰을 조회한다. - NotFoundException") + @Test + void getById_NotFoundException() { + // Given + given(couponRepository.findById(any(Long.class))).willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> couponService.getById(1L)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON.getMessage()); + } + + @DisplayName("모든 쿠폰을 성공적으로 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void getAllByStatus_success(List coupons) { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); + + given(clockHolder.date()).willReturn(LocalDate.now()); + given(couponSearchRepository.findAllByStatus(any(LocalDate.class), any(CouponStatusRequest.class))) + .willReturn(coupons); + + // When + List actual = couponService.getAllByStatus(request); + + // Then + assertThat(actual).hasSize(coupons.size()); + } + + @DisplayName("나의 모든 쿠폰을 성공적으로 조회한다.") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletByCouponId1_total5") + @ParameterizedTest + void getAllByWalletIdAndMemberId_all_success(List couponWallets) { + // Given + given(couponWalletSearchRepository.findAllByIdAndMemberId(isNull(), any(Long.class))) + .willReturn(couponWallets); + + // When + List actual = couponService.getAllByWalletIdAndMemberId(null, 1L); + + // Then + assertThat(actual).hasSize(couponWallets.size()); + } + + @DisplayName("나의 특정 쿠폰을 성공적으로 조회한다.") + @Test + void getAllByWalletIdAndMemberId_success() { + // Given + Coupon coupon = CouponFixture.coupon(); + List couponWallets = List.of(CouponWallet.create(1L, coupon)); + + given(couponWalletSearchRepository.findAllByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(couponWallets); + + // When + List actual = couponService.getAllByWalletIdAndMemberId(1L, 1L); + + // Then + assertThat(actual).hasSize(1); + } + + @DisplayName("특정 회원이 쿠폰 지갑에 가지고 있는 특정 쿠폰을 성공적으로 사용한다. - Void") + @Test + void use_success() { + // Given + Coupon coupon = CouponFixture.coupon(CouponType.GOLDEN, 1000); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.of(couponWallet)); + + // When + couponService.use(1L, 1L); + + // Then + verify(couponWalletRepository).delete(any(CouponWallet.class)); + verify(bugService).applyCoupon(any(Long.class), any(BugType.class), any(int.class)); + } + + @DisplayName("특정 회원이 쿠폰 지갑에 가지고 있는 할인 쿠폰을 사용한다. - BadRequestException") + @Test + void use_BadRequestException() { + // Given + Coupon coupon = CouponFixture.coupon(CouponType.DISCOUNT, 1000); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.of(couponWallet)); + + // When & Then + assertThatThrownBy(() -> couponService.use(1L, 1L)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_DISCOUNT_COUPON.getMessage()); + } + + @DisplayName("특정 회원이 쿠폰 지갑에 가지고 있지 않은 쿠폰을 사용한다. - NotFoundException") + @Test + void use_NotFoundException() { + // Given + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> couponService.use(1L, 1L)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON_WALLET.getMessage()); + } + + @DisplayName("결제할 때, 할인 쿠폰을 사용한다. - Void") + @Test + void discount_success() { + // Given + Coupon coupon = CouponFixture.coupon(CouponType.DISCOUNT, 1000); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.of(couponWallet)); + + // When + couponService.discount(1L, 1L); + + // Then + verify(couponWalletRepository).delete(couponWallet); + } + + @DisplayName("결제할 때, 벌레 쿠폰을 사용한다. - BadRequestException") + @Test + void discount_BadRequestException() { + // Given + Coupon coupon = CouponFixture.coupon(CouponType.GOLDEN, 1000); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + given(couponWalletSearchRepository.findByIdAndMemberId(any(Long.class), any(Long.class))) + .willReturn(Optional.of(couponWallet)); + + // When & Then + assertThatThrownBy(() -> couponService.discount(1L, 1L)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_BUG_COUPON.getMessage()); + } +} diff --git a/src/test/java/com/moabam/api/application/image/ImageServiceTest.java b/src/test/java/com/moabam/api/application/image/ImageServiceTest.java new file mode 100644 index 00000000..ea2e67ed --- /dev/null +++ b/src/test/java/com/moabam/api/application/image/ImageServiceTest.java @@ -0,0 +1,62 @@ +package com.moabam.api.application.image; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.moabam.api.domain.image.ImageType; +import com.moabam.api.domain.image.NewImage; +import com.moabam.api.infrastructure.s3.S3Manager; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith(MockitoExtension.class) +class ImageServiceTest { + + @InjectMocks + private ImageService imageService; + + @Mock + private S3Manager s3Manager; + + @DisplayName("이미지 리사이징 이후 업로드 성공") + @Test + void image_resize_upload_success() { + // given + List multipartFiles = new ArrayList<>(); + ImageType imageType = ImageType.CERTIFICATION; + MockMultipartFile image1 = RoomFixture.makeMultipartFile1(); + List images = List.of(image1); + + given(s3Manager.uploadImage(anyString(), any(NewImage.class))).willReturn(image1.getName()); + + // when + List result = imageService.uploadImages(images, imageType); + + // then + assertThat(image1.getName()).isEqualTo(result.get(0)); + } + + @DisplayName("이미지 삭제 성공") + @Test + void delete_image_success() { + // given + String imageUrl = "test"; + + // when + imageService.deleteImage(imageUrl); + + // then + verify(s3Manager).deleteImage(imageUrl); + } +} diff --git a/src/test/java/com/moabam/api/application/item/ItemServiceTest.java b/src/test/java/com/moabam/api/application/item/ItemServiceTest.java new file mode 100644 index 00000000..280c70fe --- /dev/null +++ b/src/test/java/com/moabam/api/application/item/ItemServiceTest.java @@ -0,0 +1,189 @@ +package com.moabam.api.application.item; + +import static com.moabam.support.fixture.InventoryFixture.*; +import static com.moabam.support.fixture.ItemFixture.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.bug.repository.BugHistoryRepository; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.InventorySearchRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.item.repository.ItemSearchRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.item.ItemResponse; +import com.moabam.api.dto.item.ItemsResponse; +import com.moabam.api.dto.item.PurchaseItemRequest; +import com.moabam.global.common.util.StreamUtils; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; + +@ExtendWith(MockitoExtension.class) +class ItemServiceTest { + + @InjectMocks + ItemService itemService; + + @Mock + MemberService memberService; + + @Mock + BugService bugService; + + @Mock + ItemRepository itemRepository; + + @Mock + ItemSearchRepository itemSearchRepository; + + @Mock + InventoryRepository inventoryRepository; + + @Mock + InventorySearchRepository inventorySearchRepository; + + @Mock + BugHistoryRepository bugHistoryRepository; + + @DisplayName("아이템 목록을 조회한다.") + @Test + void get_products_success() { + // given + Long memberId = 1L; + ItemType type = ItemType.MORNING; + Item item1 = morningSantaSkin().build(); + Item item2 = morningKillerSkin().build(); + Inventory inventory = inventory(memberId, item1); + given(inventorySearchRepository.findDefault(memberId, type)).willReturn(Optional.of(inventory)); + given(inventorySearchRepository.findItems(memberId, type)).willReturn(List.of(item1, item2)); + given(itemSearchRepository.findNotPurchasedItems(memberId, type)).willReturn(emptyList()); + + // when + ItemsResponse response = itemService.getItems(memberId, type); + + // then + List purchasedItemNames = StreamUtils.map(response.purchasedItems(), ItemResponse::name); + assertThat(response.purchasedItems()).hasSize(2); + assertThat(purchasedItemNames).containsExactly(MORNING_SANTA_SKIN_NAME, MORNING_KILLER_SKIN_NAME); + assertThat(response.notPurchasedItems()).isEmpty(); + } + + @DisplayName("아이템을 구매한다.") + @Nested + class PurchaseItem { + + @DisplayName("성공한다.") + @Test + void success() { + // given + Long memberId = 1L; + Long itemId = 1L; + PurchaseItemRequest request = new PurchaseItemRequest(BugType.GOLDEN); + Member member = member(); + Item item = nightMageSkin(); + given(memberService.findMember(memberId)).willReturn(member); + given(itemRepository.findById(itemId)).willReturn(Optional.of(item)); + given(inventorySearchRepository.findOne(memberId, itemId)).willReturn(Optional.empty()); + + // When + itemService.purchaseItem(memberId, itemId, request); + + // Then + verify(bugService).use(any(Member.class), any(BugType.class), anyInt()); + verify(inventoryRepository).save(any(Inventory.class)); + } + + @DisplayName("해당 아이템이 존재하지 않으면 예외가 발생한다.") + @Test + void item_not_found_exception() { + // given + Long memberId = 1L; + Long itemId = 1L; + PurchaseItemRequest request = new PurchaseItemRequest(BugType.GOLDEN); + given(itemRepository.findById(itemId)).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> itemService.purchaseItem(memberId, itemId, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("존재하지 않는 아이템입니다."); + } + + @DisplayName("이미 구매한 아이템이면 예외가 발생한다.") + @Test + void inventory_conflict_exception() { + // given + Long memberId = 1L; + Long itemId = 1L; + PurchaseItemRequest request = new PurchaseItemRequest(BugType.GOLDEN); + Item item = nightMageSkin(); + Inventory inventory = inventory(memberId, item); + given(itemRepository.findById(itemId)).willReturn(Optional.of(item)); + given(inventorySearchRepository.findOne(memberId, itemId)).willReturn(Optional.of(inventory)); + + // when, then + assertThatThrownBy(() -> itemService.purchaseItem(memberId, itemId, request)) + .isInstanceOf(ConflictException.class) + .hasMessage("이미 구매한 아이템입니다."); + } + } + + @DisplayName("아이템을 적용한다.") + @Nested + class SelectItem { + + @DisplayName("성공한다.") + @Test + void success() { + // given + Long memberId = 1L; + Long itemId = 1L; + Inventory inventory = inventory(memberId, nightMageSkin()); + Inventory defaultInventory = inventory(memberId, nightMageSkin()); + ItemType itemType = inventory.getItemType(); + given(memberService.findMember(memberId)).willReturn(member()); + given(inventorySearchRepository.findOne(memberId, itemId)).willReturn(Optional.of(inventory)); + given(inventorySearchRepository.findDefault(memberId, itemType)).willReturn(Optional.of(defaultInventory)); + + // when + itemService.selectItem(memberId, itemId); + + // then + assertFalse(defaultInventory.isDefault()); + assertTrue(inventory.isDefault()); + } + + @DisplayName("인벤토리 아이템이 아니면 예외가 발생한다.") + @Test + void exception() { + // given + Long memberId = 1L; + Long itemId = 1L; + given(inventorySearchRepository.findOne(memberId, itemId)).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> itemService.selectItem(memberId, itemId)) + .isInstanceOf(NotFoundException.class) + .hasMessage("구매하지 않은 아이템은 적용할 수 없습니다."); + } + } +} diff --git a/src/test/java/com/moabam/api/application/member/MemberServiceTest.java b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java new file mode 100644 index 00000000..5bb68977 --- /dev/null +++ b/src/test/java/com/moabam/api/application/member/MemberServiceTest.java @@ -0,0 +1,249 @@ +package com.moabam.api.application.member; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.ranking.RankingService; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.InventorySearchRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.LoginResponse; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoResponse; +import com.moabam.api.dto.member.ModifyMemberRequest; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.AuthorizationResponseFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.MemberInfoSearchFixture; +import com.moabam.support.fixture.ModifyImageFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class MemberServiceTest { + + @InjectMocks + MemberService memberService; + + @Mock + MemberRepository memberRepository; + + @Mock + MemberSearchRepository memberSearchRepository; + + @Mock + ParticipantRepository participantRepository; + + @Mock + ParticipantSearchRepository participantSearchRepository; + + @Mock + InventorySearchRepository inventorySearchRepository; + + @Mock + InventoryRepository inventoryRepository; + + @Mock + RankingService rankingService; + + @Mock + FcmService fcmService; + + @Mock + ItemRepository itemRepository; + + @Mock + ClockHolder clockHolder; + + @DisplayName("회원 존재하고 로그인 성공") + @Test + void member_exist_and_login_success() { + // given + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = + AuthorizationResponseFixture.authorizationTokenInfoResponse(); + Member member = MemberFixture.member(); + willReturn(Optional.of(member)) + .given(memberRepository).findBySocialId(String.valueOf(authorizationTokenInfoResponse.id())); + + // when + LoginResponse result = memberService.login(authorizationTokenInfoResponse); + + // then + assertThat(result.publicClaim().id()).isEqualTo(member.getId()); + assertThat(result.isSignUp()).isFalse(); + } + + @DisplayName("회원가입 성공") + @Test + void signUp_success() { + // given + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = + AuthorizationResponseFixture.authorizationTokenInfoResponse(); + willReturn(Optional.empty()) + .given(memberRepository).findBySocialId(String.valueOf(authorizationTokenInfoResponse.id())); + + Member member = spy(MemberFixture.member()); + given(member.getId()).willReturn(1L); + willReturn(member) + .given(memberRepository).save(any(Member.class)); + willReturn(List.of(ItemFixture.morningSantaSkin().build(), ItemFixture.nightMageSkin())) + .given(itemRepository).findAllById(any()); + + // when + LoginResponse result = memberService.login(authorizationTokenInfoResponse); + + // then + assertThat(authorizationTokenInfoResponse.id()).isEqualTo(result.publicClaim().id()); + assertThat(result.isSignUp()).isTrue(); + } + + @DisplayName("회원 삭제 성공") + @Test + void undo_delete_member(@WithMember AuthMember authMember) { + // given + Member member = MemberFixture.member(); + given(clockHolder.times()).willReturn(LocalDateTime.now()); + + // When + memberService.delete(member); + + // then + assertThat(member).isNotNull(); + assertThat(member.getSocialId()).contains("delete"); + } + + @DisplayName("내 회원 정보가 없어서 예외 발생") + @Test + void search_my_info_failBy_member_null(@WithMember AuthMember authMember) { + // given + given(memberSearchRepository.findMemberAndBadges(authMember.id(), true)) + .willReturn(List.of()); + + // When + Then + assertThatThrownBy(() -> memberService.searchInfo(authMember, null)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("친구 회원 정보가 없어서 예외 발생") + @Test + void search_friend_info_failBy_member_null(@WithMember AuthMember authMember) { + // given + given(memberSearchRepository.findMemberAndBadges(123L, false)) + .willReturn(List.of()); + + // When + Then + assertThatThrownBy(() -> memberService.searchInfo(authMember, 123L)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.MEMBER_NOT_FOUND.getMessage()); + } + + @DisplayName("내 기본 스킨 2개가 없을 때 예외 발생") + @Test + void search_my_info_success(@WithMember AuthMember authMember) { + // Given + long total = 36; + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + + given(memberSearchRepository.findMemberAndBadges(authMember.id(), true)) + .willReturn(MemberInfoSearchFixture.friendMemberInfo(total)); + + // When + Then + MemberInfoResponse memberInfoResponse = memberService.searchInfo(authMember, null); + + assertAll( + () -> assertThat(memberInfoResponse.exp()).isEqualTo(total % 10), + () -> assertThat(memberInfoResponse.level()).isEqualTo(total / 10) + ); + } + + @DisplayName("기본 스킨을 가져온다.") + @Nested + class GetDefaultSkin { + + @DisplayName("성공") + @Test + void success(@WithMember AuthMember authMember) { + // given + long searchId = 1L; + Item morning = ItemFixture.morningSantaSkin().build(); + Item night = ItemFixture.nightMageSkin(); + Inventory morningSkin = InventoryFixture.inventory(searchId, morning); + Inventory nightSkin = InventoryFixture.inventory(searchId, night); + List memberInfos = MemberInfoSearchFixture + .myInfo(morningSkin.getItem().getAwakeImage(), nightSkin.getItem().getAwakeImage()); + + given(memberSearchRepository.findMemberAndBadges(anyLong(), anyBoolean())) + .willReturn(memberInfos); + + // when + MemberInfoResponse memberInfoResponse = memberService.searchInfo(authMember, null); + + // then + assertThat(memberInfoResponse.birds()).containsEntry("MORNING", morningSkin.getItem().getAwakeImage()); + assertThat(memberInfoResponse.birds()).containsEntry("NIGHT", nightSkin.getItem().getAwakeImage()); + } + } + + @DisplayName("사용자 정보 수정 성공") + @Test + void modify_success_test(@WithMember AuthMember authMember) { + // given + Member member = MemberFixture.member(); + ModifyMemberRequest modifyMemberRequest = ModifyImageFixture.modifyMemberRequest(); + given(memberSearchRepository.findMember(authMember.id())).willReturn(Optional.ofNullable(member)); + given(participantSearchRepository.findAllRoomMangerByMemberId(any())) + .willReturn(List.of()); + + // when + memberService.modifyInfo(authMember, modifyMemberRequest, "/main"); + + // Then + assertAll( + () -> assertThat(member.getNickname()).isEqualTo(modifyMemberRequest.nickname()), + () -> assertThat(member.getIntro()).isEqualTo(modifyMemberRequest.intro()), + () -> assertThat(member.getProfileImage()).isEqualTo("/main") + ); + } + + @DisplayName("모든 랭킹 업데이트") + @Test + void update_all_ranking() { + // given + Member member1 = MemberFixture.member("1"); + Member member2 = MemberFixture.member("2"); + given(memberSearchRepository.findAllMembers()) + .willReturn(List.of(member1, member2)); + + // when + Then + assertThatNoException().isThrownBy(() -> memberService.updateAllRanking()); + } +} diff --git a/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java new file mode 100644 index 00000000..a08d2b06 --- /dev/null +++ b/src/test/java/com/moabam/api/application/notification/NotificationServiceTest.java @@ -0,0 +1,234 @@ +package com.moabam.api.application.notification; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.RoomService; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.notification.repository.NotificationRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.AuthorizationThreadLocal; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.exception.ConflictException; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class NotificationServiceTest { + + @InjectMocks + NotificationService notificationService; + + @Mock + MemberService memberService; + + @Mock + RoomService roomService; + + @Mock + FcmService fcmService; + + @Mock + NotificationRepository notificationRepository; + + @Mock + ParticipantSearchRepository participantSearchRepository; + + @Mock + ClockHolder clockHolder; + + String successIssueResult = "%s 쿠폰 발행을 성공했습니다. 축하드립니다!"; + + @DisplayName("상대에게 콕 알림을 성공적으로 보낸다. - Void") + @Test + void sendKnock_success() { + // Given + Room room = RoomFixture.room(); + Member member = MemberFixture.member(); + + given(roomService.findRoom(any(Long.class))).willReturn(room); + given(memberService.findMember(any(Long.class))).willReturn(member); + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); + + // When + notificationService.sendKnock(1L, 1L, 2L); + + // Then + verify(fcmService).sendAsync(any(String.class), any(String.class), any(String.class)); + verify(notificationRepository).saveKnock(any(Long.class), any(Long.class), any(Long.class)); + } + + @DisplayName("콕 찌를 때, 방이 존재하지 않는다. - NotFoundException") + @Test + void sendKnock_Room_NotFoundException() { + // Given + given(roomService.findRoom(any(Long.class))).willThrow(NotFoundException.class); + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); + + // When & Then + assertThatThrownBy(() -> notificationService.sendKnock(1L, 1L, 2L)) + .isInstanceOf(NotFoundException.class); + } + + @DisplayName("콕 찌를 상대가 존재하지 않는다. - NotFoundException") + @Test + void sendKnock_Member_NotFoundException() { + // Given + Room room = RoomFixture.room(); + + given(roomService.findRoom(any(Long.class))).willReturn(room); + given(memberService.findMember(any(Long.class))).willThrow(NotFoundException.class); + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); + + // When & Then + assertThatThrownBy(() -> notificationService.sendKnock(1L, 1L, 2L)) + .isInstanceOf(NotFoundException.class); + } + + @DisplayName("콕 찌를 상대의 FCM 토큰이 존재하지 않는다. - NotFoundException") + @Test + void sendKnock_FcmToken_NotFoundException() { + // Given + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.empty()); + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); + + // When & Then + assertThatThrownBy(() -> notificationService.sendKnock(1L, 1L, 2L)) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_FCM_TOKEN.getMessage()); + } + + @DisplayName("콕 찌를 상대가 이미 찌른 상대이다. - ConflictException") + @Test + void sendKnock_ConflictException() { + // Given + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(true); + + // When & Then + assertThatThrownBy(() -> notificationService.sendKnock(1L, 1L, 2L)) + .isInstanceOf(ConflictException.class) + .hasMessage(ErrorMessage.CONFLICT_KNOCK.getMessage()); + } + + @DisplayName("특정 사용자에게 쿠폰 이슈 결과를 성공적으로 전송한다. - Void") + @Test + void sendCouponIssueResult_success() { + // Given + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); + + // When + notificationService.sendCouponIssueResult(1L, "couponName", successIssueResult); + + // Then + verify(fcmService).sendAsync(any(String.class), any(String.class), any(String.class)); + } + + @DisplayName("로그아웃된 사용자에게 쿠폰 이슈 결과를 성공적으로 전송한다. - Void") + @Test + void sendCouponIssueResult_fcmToken_null() { + // Given + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.empty()); + + // When + notificationService.sendCouponIssueResult(1L, "couponName", successIssueResult); + + // Then + verify(fcmService).sendAsync(isNull(), any(String.class), any(String.class)); + } + + @DisplayName("특정 인증 시간에 해당하는 방 사용자들에게 알림을 성공적으로 보낸다. - Void") + @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideParticipants") + @ParameterizedTest + void sendCertificationTime_success(List participants) { + // Given + given(participantSearchRepository.findAllByRoomCertifyTime(any(Integer.class))).willReturn(participants); + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.of("FCM-TOKEN")); + given(clockHolder.times()).willReturn(LocalDateTime.now()); + + // When + notificationService.sendCertificationTime(); + + // Then + verify(fcmService, times(3)) + .sendAsync(any(String.class), any(String.class), any(String.class)); + } + + @DisplayName("특정 인증 시간에 해당하는 방 사용자들의 토큰값이 없다. - Void") + @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideParticipants") + @ParameterizedTest + void sendCertificationTime_NoFirebaseMessaging(List participants) { + // Given + given(participantSearchRepository.findAllByRoomCertifyTime(any(Integer.class))).willReturn(participants); + given(fcmService.findTokenByMemberId(any(Long.class))).willReturn(Optional.empty()); + given(clockHolder.times()).willReturn(LocalDateTime.now()); + + // When + notificationService.sendCertificationTime(); + + // Then + verify(fcmService, times(0)) + .sendAsync(any(String.class), any(String.class), any(String.class)); + } + + @WithMember + @DisplayName("특정 방에서 나 이외의 모든 사용자에게 콕 알림을 보낸다. - List") + @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideParticipants") + @ParameterizedTest + void getMyKnockStatusInRoom_knocked(List participants) { + // Given + AuthMember member = AuthorizationThreadLocal.getAuthMember(); + + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(true); + + // When + List actual = notificationService.getMyKnockStatusInRoom(member.id(), 1L, participants); + + // Then + assertThat(actual).hasSize(2); + } + + @WithMember + @DisplayName("특정 방에서 나 이외의 모든 사용자에게 콕 알림을 보낸 적이 없다. - List") + @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideParticipants") + @ParameterizedTest + void getMyKnockStatusInRoom_notKnocked(List participants) { + // Given + AuthMember member = AuthorizationThreadLocal.getAuthMember(); + + given(notificationRepository.existsKnockByKey(any(Long.class), any(Long.class), any(Long.class))) + .willReturn(false); + + // When + List actual = notificationService.getMyKnockStatusInRoom(member.id(), 1L, participants); + + // Then + assertThat(actual).isEmpty(); + } +} diff --git a/src/test/java/com/moabam/api/application/payment/PaymentServiceTest.java b/src/test/java/com/moabam/api/application/payment/PaymentServiceTest.java new file mode 100644 index 00000000..4158682c --- /dev/null +++ b/src/test/java/com/moabam/api/application/payment/PaymentServiceTest.java @@ -0,0 +1,118 @@ +package com.moabam.api.application.payment; + +import static com.moabam.support.fixture.CouponFixture.*; +import static com.moabam.support.fixture.PaymentFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.coupon.CouponService; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.PaymentStatus; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.payment.repository.PaymentSearchRepository; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.PaymentRequest; +import com.moabam.api.infrastructure.payment.TossPaymentService; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.exception.TossPaymentException; + +@ExtendWith(MockitoExtension.class) +class PaymentServiceTest { + + @InjectMocks + PaymentService paymentService; + + @Mock + BugService bugService; + + @Mock + CouponService couponService; + + @Mock + TossPaymentService tossPaymentService; + + @Mock + PaymentRepository paymentRepository; + + @Mock + PaymentSearchRepository paymentSearchRepository; + + @DisplayName("결제 요청 시 해당 결제 정보가 존재하지 않으면 예외가 발생한다.") + @Test + void request_not_found_exception() { + // given + Long memberId = 1L; + Long paymentId = 1L; + PaymentRequest request = new PaymentRequest(ORDER_ID); + given(paymentRepository.findById(paymentId)).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> paymentService.request(memberId, paymentId, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("존재하지 않는 결제 정보입니다."); + } + + @DisplayName("결제 승인을 요청한다.") + @Nested + class RequestConfirm { + + @DisplayName("해당 결제 정보가 존재하지 않으면 예외가 발생한다.") + @Test + void validate_info_not_found_exception() { + // given + Long memberId = 1L; + ConfirmPaymentRequest request = confirmPaymentRequest(); + given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> paymentService.requestConfirm(memberId, request)) + .isInstanceOf(NotFoundException.class) + .hasMessage("존재하지 않는 결제 정보입니다."); + } + + @DisplayName("토스 결제 승인 요청이 실패하면 결제 실패 처리한다.") + @Test + void toss_fail() { + // given + Long memberId = 1L; + Payment payment = payment(bugProduct()); + ConfirmPaymentRequest request = confirmPaymentRequest(); + given(paymentSearchRepository.findByOrderId(request.orderId())).willReturn(Optional.of(payment)); + given(tossPaymentService.confirm(request)).willThrow(TossPaymentException.class); + + // when, then + assertThatThrownBy(() -> paymentService.requestConfirm(memberId, request)) + .isInstanceOf(TossPaymentException.class); + assertThat(payment.getPaymentKey()).isEqualTo(PAYMENT_KEY); + assertThat(payment.getStatus()).isEqualTo(PaymentStatus.ABORTED); + } + } + + @DisplayName("결제 승인에 성공한다.") + @Test + void confirm_success() { + // given + Long memberId = 1L; + Long couponWalletId = 1L; + Payment payment = paymentWithCoupon(bugProduct(), discount1000Coupon(), couponWalletId); + + // when + paymentService.confirm(memberId, payment, PAYMENT_KEY); + + // then + verify(couponService, times(1)).discount(couponWalletId, memberId); + verify(bugService, times(1)).charge(memberId, payment.getProduct()); + } +} diff --git a/src/test/java/com/moabam/api/application/ranking/RankingServiceTest.java b/src/test/java/com/moabam/api/application/ranking/RankingServiceTest.java new file mode 100644 index 00000000..1dbb1843 --- /dev/null +++ b/src/test/java/com/moabam/api/application/ranking/RankingServiceTest.java @@ -0,0 +1,234 @@ +package com.moabam.api.application.ranking; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; + +import com.moabam.api.application.member.MemberMapper; +import com.moabam.api.domain.member.Member; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.TopRankingInfo; +import com.moabam.api.dto.ranking.TopRankingResponse; +import com.moabam.api.dto.ranking.UpdateRanking; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; +import com.moabam.global.config.EmbeddedRedisConfig; +import com.moabam.support.fixture.BugFixture; +import com.moabam.support.fixture.MemberFixture; + +@SpringBootTest(classes = {EmbeddedRedisConfig.class, RankingService.class, ZSetRedisRepository.class}) +public class RankingServiceTest { + + @Autowired + ZSetRedisRepository zSetRedisRepository; + + @Autowired + RedisTemplate redisTemplate; + + @Autowired + RankingService rankingService; + + @BeforeEach + void init() { + redisTemplate.delete("Ranking"); + } + + @DisplayName("redis에 추가") + @Nested + class Add { + + @DisplayName("성공") + @Test + void add_success() { + // given + Long totalCertifyCount = 0L; + RankingInfo rankingInfo = RankingInfo.builder() + .image("https://image.moabam.com/test") + .memberId(1L) + .nickname("nickname") + .build(); + + // when + rankingService.addRanking(rankingInfo, totalCertifyCount); + + // then + Double resultDouble = redisTemplate.opsForZSet().score("Ranking", rankingInfo); + + assertAll(() -> assertThat(resultDouble).isNotNull(), + () -> assertThat(resultDouble).isEqualTo(Double.valueOf(totalCertifyCount))); + } + } + + @DisplayName("스코어 업데이트") + @Nested + class Update { + + @DisplayName("성공") + @Test + void update_success() { + // given + Member member = MemberFixture.member("1"); + member.increaseTotalCertifyCount(); + member.increaseTotalCertifyCount(); + + Member member1 = MemberFixture.member("2"); + member1.increaseTotalCertifyCount(); + member1.increaseTotalCertifyCount(); + + List members = List.of(member, member1); + List updateRankings = members.stream().map(MemberMapper::toUpdateRanking).toList(); + + // when + rankingService.updateScores(updateRankings); + + Double resultDouble = redisTemplate.opsForZSet().score("Ranking", updateRankings.get(0).rankingInfo()); + + // then + assertAll(() -> assertThat(resultDouble).isNotNull(), + () -> assertThat(resultDouble).isEqualTo(Double.valueOf(member.getTotalCertifyCount()))); + } + } + + @DisplayName("사용자 정보 변경") + @Nested + class Change { + + @DisplayName("성공") + @Test + void update_success() { + // given + Member member = Member.builder().socialId("1").bug(BugFixture.bug()).build(); + member.increaseTotalCertifyCount(); + member.increaseTotalCertifyCount(); + + long expect = member.getTotalCertifyCount(); + RankingInfo before = MemberMapper.toRankingInfo(member); + rankingService.addRanking(before, member.getTotalCertifyCount()); + + // when + member.changeIntro("밥세공기"); + RankingInfo changeInfo = MemberMapper.toRankingInfo(member); + rankingService.changeInfos(before, changeInfo); + + Double resultDouble = redisTemplate.opsForZSet().score("Ranking", changeInfo); + + // then + assertAll(() -> assertThat(resultDouble).isNotNull(), + () -> assertThat(resultDouble).isEqualTo(Double.valueOf(expect))); + } + } + + @DisplayName("랭킹 삭제") + @Nested + class Delete { + + @DisplayName("성공") + @Test + void update_success() { + // given + Long totalCertify = 5L; + Member member = Member.builder().socialId("1").bug(BugFixture.bug()).build(); + member.increaseTotalCertifyCount(); + member.increaseTotalCertifyCount(); + RankingInfo rankingInfo = MemberMapper.toRankingInfo(member); + + rankingService.addRanking(rankingInfo, totalCertify); + + // when + rankingService.removeRanking(rankingInfo); + + Double resultDouble = redisTemplate.opsForZSet().score("Ranking", rankingInfo); + + // then + assertThat(resultDouble).isNull(); + } + } + + @DisplayName("조회") + @Nested + class Select { + + @DisplayName("성공") + @Test + void test() { + // given + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(1L, "Hello1", "123"), 1); + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(2L, "Hello2", "123"), 2); + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(3L, "Hello3", "123"), 3); + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(4L, "Hello4", "123"), 4); + + // when + setSerialize(Object.class); + Set> rankings = redisTemplate.opsForZSet() + .reverseRangeWithScores("Ranking", 0, 2); + setSerialize(String.class); + + // then + assertThat(rankings).hasSize(3); + } + + @DisplayName("일부만 조회 성공") + @Test + void search_part() { + // given + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(1L, "Hello1", "123"), 1); + redisTemplate.opsForZSet().add("Ranking", new RankingInfo(2L, "Hello2", "123"), 2); + + // when + setSerialize(Object.class); + Set> rankings = redisTemplate.opsForZSet() + .reverseRangeWithScores("Ranking", 0, 10); + setSerialize(String.class); + + // then + assertThat(rankings).hasSize(2); + } + + private void setSerialize(Class classes) { + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(classes)); + } + + @DisplayName("랭킹 조회 성공") + @Test + void getTopRankings() { + // given + for (int i = 0; i < 20; i++) { + RankingInfo rankingInfo = new RankingInfo((long)(i + 1), "Hello" + (i + 1), "123"); + redisTemplate.opsForZSet().add("Ranking", rankingInfo, i + 1); + } + RankingInfo rankingInfo = new RankingInfo(21L, "Hello22", "123"); + redisTemplate.opsForZSet().add("Ranking", rankingInfo, 20); + RankingInfo rankingInfo2 = new RankingInfo(22L, "Hello23", "123"); + redisTemplate.opsForZSet().add("Ranking", rankingInfo2, 19); + + UpdateRanking myRanking = UpdateRanking.builder() + .score(1L) + .rankingInfo(RankingInfo.builder().nickname("Hello1").memberId(1L).image("123").build()) + .build(); + // When + TopRankingResponse topRankingResponse = rankingService.getMemberRanking(myRanking); + + // Then + List topRankings = topRankingResponse.topRankings(); + TopRankingInfo myRank = topRankingResponse.myRanking(); + assertAll(() -> assertThat(topRankings).hasSize(10), () -> assertThat(myRank.score()).isEqualTo(1), + () -> assertThat(topRankings.get(0).rank()).isEqualTo(1), + () -> assertThat(topRankings.get(1).rank()).isEqualTo(1), + () -> assertThat(topRankings.get(2).rank()).isEqualTo(2), + () -> assertThat(topRankings.get(3).rank()).isEqualTo(2), + () -> assertThat(topRankings.get(4).rank()).isEqualTo(3)); + + } + } +} diff --git a/src/test/java/com/moabam/api/application/report/ReportServiceTest.java b/src/test/java/com/moabam/api/application/report/ReportServiceTest.java new file mode 100644 index 00000000..4adc8c01 --- /dev/null +++ b/src/test/java/com/moabam/api/application/report/ReportServiceTest.java @@ -0,0 +1,93 @@ +package com.moabam.api.application.report; + +import static com.moabam.global.error.model.ErrorMessage.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.CertificationService; +import com.moabam.api.application.room.RoomService; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.report.repository.ReportRepository; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.dto.report.ReportRequest; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.FilterProcessExtension; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.ReportFixture; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith({MockitoExtension.class, FilterProcessExtension.class}) +class ReportServiceTest { + + @InjectMocks + ReportService reportService; + + @Mock + CertificationService certificationService; + + @Mock + RoomService roomService; + + @Mock + MemberService memberService; + + @Mock + ReportRepository reportRepository; + + @DisplayName("신고 대상이 없어서 실패") + @Test + void no_report_subject_fail(@WithMember AuthMember authMember) { + // given + ReportRequest reportRequest = new ReportRequest(null, null, null, "st"); + + // When + Then + assertThatThrownBy(() -> reportService.report(authMember, reportRequest)) + .isInstanceOf(BadRequestException.class) + .hasMessage(REPORT_REQUEST_ERROR.getMessage()); + } + + @DisplayName("신고 성공") + @ParameterizedTest + @CsvSource({"true, false", "false, true"}) + void report_success(boolean roomFilter, boolean certificationFilter, @WithMember AuthMember authMember) { + // given + Room room = RoomFixture.room(); + Routine routine = RoomFixture.routine(room, "ets"); + Certification certification = RoomFixture.certification(routine); + Member member = spy(MemberFixture.member()); + + Long roomId = null; + Long certificationId = null; + + if (roomFilter) { + given(roomService.findRoom(any())).willReturn(RoomFixture.room()); + roomId = 1L; + } + if (certificationFilter) { + given(certificationService.findCertification(any())).willReturn(certification); + certificationId = 1L; + } + + ReportRequest reportRequest = ReportFixture.reportRequest(2L, roomId, certificationId); + given(member.getId()).willReturn(2L); + given(memberService.findMember(reportRequest.reportedId())).willReturn(member); + + // When + Then + assertThatNoException() + .isThrownBy(() -> reportService.report(authMember, reportRequest)); + } +} diff --git a/src/test/java/com/moabam/api/application/room/CertificationServiceConcurrencyTest.java b/src/test/java/com/moabam/api/application/room/CertificationServiceConcurrencyTest.java new file mode 100644 index 00000000..3edc5543 --- /dev/null +++ b/src/test/java/com/moabam/api/application/room/CertificationServiceConcurrencyTest.java @@ -0,0 +1,128 @@ +package com.moabam.api.application.room; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.moabam.api.application.room.mapper.CertificationsMapper; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.DailyRoomCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.dto.room.CertifiedMemberInfo; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@SpringBootTest +class CertificationServiceConcurrencyTest { + + @Autowired + private RoomRepository roomRepository; + + @Autowired + private ParticipantRepository participantRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CertificationService certificationService; + + @Autowired + private DailyMemberCertificationRepository dailyMemberCertificationRepository; + + @Autowired + private DailyRoomCertificationRepository dailyRoomCertificationRepository; + + @DisplayName("방의 모든 참여자의 요청으로 방에 대한 인증") + @Test + void certify_room_success() throws InterruptedException { + // given + Room room = RoomFixture.room("테스트 하는 방이요", RoomType.MORNING, 9); + for (int i = 0; i < 4; i++) { + room.increaseCurrentUserCount(); + } + Room savedRoom = roomRepository.save(room); + + Member member1 = MemberFixture.member("0000"); + Member member2 = MemberFixture.member("1234"); + Member member3 = MemberFixture.member("5678"); + Member member4 = MemberFixture.member("3333"); + Member member5 = MemberFixture.member("5555"); + + List members = memberRepository.saveAll(List.of(member1, member2, member3, member4, member5)); + + Participant participant1 = RoomFixture.participant(savedRoom, member1.getId()); + Participant participant2 = RoomFixture.participant(savedRoom, member2.getId()); + Participant participant3 = RoomFixture.participant(savedRoom, member3.getId()); + Participant participant4 = RoomFixture.participant(savedRoom, member4.getId()); + Participant participant5 = RoomFixture.participant(savedRoom, member5.getId()); + + participantRepository.saveAll(List.of(participant1, participant2, participant3, participant4, participant5)); + + DailyMemberCertification dailyMemberCertification1 = RoomFixture.dailyMemberCertification(member1.getId(), + savedRoom.getId(), participant1); + DailyMemberCertification dailyMemberCertification2 = RoomFixture.dailyMemberCertification(member2.getId(), + savedRoom.getId(), participant2); + DailyMemberCertification dailyMemberCertification3 = RoomFixture.dailyMemberCertification(member3.getId(), + savedRoom.getId(), participant3); + DailyMemberCertification dailyMemberCertification4 = RoomFixture.dailyMemberCertification(member4.getId(), + savedRoom.getId(), participant4); + DailyMemberCertification dailyMemberCertification5 = RoomFixture.dailyMemberCertification(member5.getId(), + savedRoom.getId(), participant5); + + dailyMemberCertificationRepository.saveAll( + List.of(dailyMemberCertification1, dailyMemberCertification2, dailyMemberCertification3, + dailyMemberCertification4, dailyMemberCertification5)); + + int threadCount = 5; + ExecutorService executorService = Executors.newFixedThreadPool(10); + CountDownLatch countDownLatch = new CountDownLatch(threadCount); + + // when + for (int i = 0; i < threadCount; i++) { + final int currentIndex = i; + + executorService.submit(() -> { + try { + CertifiedMemberInfo certifiedMemberInfo = CertificationsMapper.toCertifiedMemberInfo( + LocalDate.now(), BugType.MORNING, savedRoom, members.get(currentIndex)); + + certificationService.certifyRoom(certifiedMemberInfo); + } finally { + countDownLatch.countDown(); + } + }); + } + + countDownLatch.await(); + + Member savedMember1 = memberRepository.findById(member1.getId()).orElseThrow(); + List dailyRoomCertification = dailyRoomCertificationRepository.findAll(); + assertThat(savedMember1.getBug().getMorningBug()).isEqualTo(11); + assertThat(dailyRoomCertification).hasSize(1); + + participantRepository.deleteAll(); + memberRepository.deleteAllById( + List.of(member1.getId(), member2.getId(), member3.getId(), member4.getId(), member5.getId())); + dailyRoomCertificationRepository.deleteAll(); + dailyMemberCertificationRepository.deleteAll(); + } +} diff --git a/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java new file mode 100644 index 00000000..e02eac44 --- /dev/null +++ b/src/test/java/com/moabam/api/application/room/CertificationServiceTest.java @@ -0,0 +1,199 @@ +package com.moabam.api.application.room; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.image.ImageService; +import com.moabam.api.application.member.BadgeService; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.mapper.CertificationsMapper; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationRepository; +import com.moabam.api.domain.room.repository.CertificationsSearchRepository; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.DailyRoomCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CertifiedMemberInfo; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith(MockitoExtension.class) +class CertificationServiceTest { + + @InjectMocks + private CertificationService certificationService; + + @Mock + private MemberService memberService; + + @Mock + private BugService bugService; + + @Mock + private RoomRepository roomRepository; + + @Mock + private RoutineRepository routineRepository; + + @Mock + private ParticipantRepository participantRepository; + + @Mock + private CertificationRepository certificationRepository; + + @Mock + private CertificationsSearchRepository certificationsSearchRepository; + + @Mock + private ParticipantSearchRepository participantSearchRepository; + + @Mock + private DailyRoomCertificationRepository dailyRoomCertificationRepository; + + @Mock + private DailyMemberCertificationRepository dailyMemberCertificationRepository; + + @Mock + private ImageService imageService; + + @Mock + private BadgeService badgeService; + + @Mock + private ClockHolder clockHolder; + + @Spy + private Room room; + + @Spy + private Participant participant; + + private Member member1; + private Member member2; + private Member member3; + private LocalDate today; + private Long memberId; + private Long roomId; + + @BeforeEach + void init() { + room = spy(RoomFixture.room()); + participant = spy(RoomFixture.participant(room, 1L)); + member1 = MemberFixture.member("1"); + member2 = MemberFixture.member("2"); + member3 = MemberFixture.member("3"); + + lenient().when(room.getId()).thenReturn(1L); + lenient().when(participant.getRoom()).thenReturn(room); + + today = LocalDate.now(); + memberId = 1L; + roomId = room.getId(); + room.levelUp(); + room.levelUp(); + } + + @DisplayName("방 인증 전 개인 인증 후 정보 불러오기 성공") + @Test + void get_certified_member_info_success() { + // given + List routines = RoomFixture.routines(room); + DailyMemberCertification dailyMemberCertification = + RoomFixture.dailyMemberCertification(memberId, roomId, participant); + List imageUrls = new ArrayList<>(); + imageUrls.add("https://image.moabam.com/certifications/20231108/1_asdfsdfxcv-4815vcx-asfd"); + imageUrls.add("https://image.moabam.com/certifications/20231108/2_asdfsdfxcv-4815vcx-asfd"); + + given(clockHolder.times()).willReturn(LocalDateTime.now().withHour(10).withMinute(6)); + given(clockHolder.date()).willReturn(today); + given(participantSearchRepository.findOne(memberId, roomId)).willReturn(Optional.of(participant)); + given(memberService.findMember(memberId)).willReturn(member1); + given(routineRepository.findById(1L)).willReturn(Optional.of(routines.get(0))); + given(routineRepository.findById(2L)).willReturn(Optional.of(routines.get(1))); + given(dailyMemberCertificationRepository.save(any(DailyMemberCertification.class))).willReturn( + dailyMemberCertification); + given(certificationRepository.saveAll(anyList())).willReturn(List.of()); + + // when + CertifiedMemberInfo certifiedMemberInfo = certificationService.getCertifiedMemberInfo(memberId, roomId, + imageUrls); + + // then + assertThat(certifiedMemberInfo.member().getNickname()).isEqualTo(member1.getNickname()); + assertThat(certifiedMemberInfo.bugType()).isEqualTo(BugType.MORNING); + assertThat(certifiedMemberInfo.date()).isEqualTo(today); + } + + @DisplayName("이미 인증되어 있는 방에서 루틴 인증 성공") + @Test + void already_certified_room_routine_success() { + // given + DailyRoomCertification dailyRoomCertification = RoomFixture.dailyRoomCertification(roomId, today); + + given(clockHolder.date()).willReturn(today); + given(certificationsSearchRepository.findDailyRoomCertification(roomId, today)).willReturn( + Optional.of(dailyRoomCertification)); + + CertifiedMemberInfo certifyInfo = CertificationsMapper.toCertifiedMemberInfo(clockHolder.date(), + BugType.MORNING, room, + member1); + + // when + certificationService.certifyRoom(certifyInfo); + + // then + verify(bugService).reward(any(Member.class), any(BugType.class), anyInt()); + } + + @DisplayName("인증되지 않은 방에서 루틴 인증 후 방의 인증 성공") + @Test + void not_certified_room_routine_success() { + // given + List dailyMemberCertifications = + RoomFixture.dailyMemberCertifications(roomId, participant); + + given(clockHolder.date()).willReturn(today); + given(certificationsSearchRepository.findSortedDailyMemberCertifications(roomId, today)) + .willReturn(dailyMemberCertifications); + given(memberService.getRoomMembers(anyList())).willReturn(List.of(member1, member2, member3)); + + CertifiedMemberInfo certifyInfo = CertificationsMapper.toCertifiedMemberInfo(clockHolder.date(), + BugType.MORNING, room, + member1); + + // when + certificationService.certifyRoom(certifyInfo); + + // then + verify(bugService, times(3)).reward(any(Member.class), any(BugType.class), anyInt()); + assertThat(room.getExp()).isEqualTo(1); + assertThat(room.getLevel()).isEqualTo(2); + } +} diff --git a/src/test/java/com/moabam/api/application/room/RoomServiceConcurrencyTest.java b/src/test/java/com/moabam/api/application/room/RoomServiceConcurrencyTest.java new file mode 100644 index 00000000..91e011da --- /dev/null +++ b/src/test/java/com/moabam/api/application/room/RoomServiceConcurrencyTest.java @@ -0,0 +1,111 @@ +package com.moabam.api.application.room; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.dto.room.EnterRoomRequest; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@SpringBootTest +class RoomServiceConcurrencyTest { + + @Autowired + private RoomService roomService; + + @Autowired + private RoomRepository roomRepository; + + @Autowired + private ParticipantRepository participantRepository; + + @Autowired + private ParticipantSearchRepository participantSearchRepository; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("입장 가능이 1명이 남은 상태에서 3명 동시 입장 요청") + @Test + void enter_room_concurrency_test() throws InterruptedException { + // given + Room room = Room.builder() + .title("테스트방") + .roomType(RoomType.MORNING) + .certifyTime(10) + .maxUserCount(4) + .build(); + + for (int i = 0; i < 2; i++) { + room.increaseCurrentUserCount(); + } + + Room savedRoom = roomRepository.save(room); + + Member member1 = MemberFixture.member("qwe"); + Member member2 = MemberFixture.member("qwfe"); + Member member3 = MemberFixture.member("qff"); + memberRepository.saveAll(List.of(member1, member2, member3)); + + Participant participant1 = RoomFixture.participant(savedRoom, member1.getId()); + Participant participant2 = RoomFixture.participant(savedRoom, member2.getId()); + Participant participant3 = RoomFixture.participant(savedRoom, member3.getId()); + participantRepository.saveAll(List.of(participant1, participant2, participant3)); + + int threadCount = 3; + ExecutorService executorService = Executors.newFixedThreadPool(10); + CountDownLatch countDownLatch = new CountDownLatch(threadCount); + + EnterRoomRequest enterRoomRequest = new EnterRoomRequest(null); + List newMembers = new ArrayList<>(); + + // when + for (int i = 0; i < threadCount; i++) { + Member member = MemberFixture.member(String.valueOf(i + 100)); + newMembers.add(member); + memberRepository.save(member); + final Long memberId = member.getId(); + + executorService.submit(() -> { + try { + roomService.enterRoom(memberId, room.getId(), enterRoomRequest); + } finally { + countDownLatch.countDown(); + } + }); + } + + countDownLatch.await(); + + List actual = participantSearchRepository.findAllByRoomId(room.getId()); + Member newMember1 = memberRepository.findById(newMembers.get(0).getId()).orElseThrow(); + Member newMember2 = memberRepository.findById(newMembers.get(1).getId()).orElseThrow(); + Member newMember3 = memberRepository.findById(newMembers.get(2).getId()).orElseThrow(); + + // then + assertThat(actual).hasSize(4); + assertThat(newMember1.getCurrentMorningCount() + newMember2.getCurrentMorningCount() + + newMember3.getCurrentMorningCount()).isEqualTo(1); + + memberRepository.deleteAllById(List.of(member1.getId(), member2.getId(), member3.getId())); + memberRepository.deleteAll(newMembers); + } +} diff --git a/src/test/java/com/moabam/api/application/room/RoomServiceTest.java b/src/test/java/com/moabam/api/application/room/RoomServiceTest.java new file mode 100644 index 00000000..2a37acfc --- /dev/null +++ b/src/test/java/com/moabam/api/application/room/RoomServiceTest.java @@ -0,0 +1,163 @@ +package com.moabam.api.application.room; + +import static com.moabam.api.domain.room.RoomType.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.room.mapper.RoomMapper; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.global.error.exception.ForbiddenException; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith(MockitoExtension.class) +class RoomServiceTest { + + @InjectMocks + private RoomService roomService; + + @Mock + private MemberService memberService; + + @Mock + private RoomRepository roomRepository; + + @Mock + private RoutineRepository routineRepository; + + @Mock + private ParticipantRepository participantRepository; + + @Mock + private ParticipantSearchRepository participantSearchRepository; + + @DisplayName("비밀번호 없는 방 생성 성공") + @Test + void create_room_no_password_success() { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + Member member = spy(MemberFixture.member()); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "재윤과 앵맹이의 방임", null, routines, MORNING, 10, 4); + + Room expectedRoom = RoomMapper.toRoomEntity(createRoomRequest); + given(roomRepository.save(any(Room.class))).willReturn(expectedRoom); + given(memberService.findMember(1L)).willReturn(member); + + // when + Long result = roomService.createRoom(1L, createRoomRequest); + + // then + verify(roomRepository).save(any(Room.class)); + verify(routineRepository).saveAll(ArgumentMatchers.anyList()); + verify(participantRepository).save(any(Participant.class)); + assertThat(result).isEqualTo(expectedRoom.getId()); + assertThat(expectedRoom.getPassword()).isNull(); + } + + @DisplayName("비밀번호 있는 방 생성 성공") + @Test + void create_room_with_password_success() { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + Member member = spy(MemberFixture.member()); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "재윤과 앵맹이의 방임", "1234", routines, MORNING, 10, 4); + + Room expectedRoom = RoomMapper.toRoomEntity(createRoomRequest); + given(roomRepository.save(any(Room.class))).willReturn(expectedRoom); + given(memberService.findMember(1L)).willReturn(member); + + // when + Long result = roomService.createRoom(1L, createRoomRequest); + + // then + verify(roomRepository).save(any(Room.class)); + verify(routineRepository).saveAll(ArgumentMatchers.anyList()); + verify(participantRepository).save(any(Participant.class)); + assertThat(result).isEqualTo(expectedRoom.getId()); + assertThat(expectedRoom.getPassword()).isEqualTo("1234"); + } + + @DisplayName("방장 위임 성공") + @Test + void room_manager_mandate_success() { + // given + Long managerId = 1L; + Long memberId = 2L; + + Member member = MemberFixture.member("1234"); + + Room room = spy(RoomFixture.room()); + given(room.getId()).willReturn(1L); + + Participant memberParticipant = RoomFixture.participant(room, memberId); + Participant managerParticipant = RoomFixture.participant(room, managerId); + managerParticipant.enableManager(); + + given(participantSearchRepository.findOne(memberId, room.getId())).willReturn( + Optional.of(memberParticipant)); + given(participantSearchRepository.findOne(managerId, room.getId())).willReturn( + Optional.of(managerParticipant)); + given(memberService.findMember(2L)).willReturn(member); + + // when + roomService.mandateManager(managerId, room.getId(), memberId); + + // then + assertThat(managerParticipant.isManager()).isFalse(); + assertThat(memberParticipant.isManager()).isTrue(); + } + + @DisplayName("방장 위임 실패 - 방장이 아닌 유저가 요청할 때") + @Test + void room_manager_mandate_fail() { + // given + Long managerId = 1L; + Long memberId = 2L; + + Room room = spy(RoomFixture.room()); + given(room.getId()).willReturn(1L); + + Participant memberParticipant = RoomFixture.participant(room, memberId); + Participant managerParticipant = RoomFixture.participant(room, managerId); + + given(participantSearchRepository.findOne(memberId, room.getId())).willReturn( + Optional.of(memberParticipant)); + given(participantSearchRepository.findOne(managerId, room.getId())).willReturn( + Optional.of(managerParticipant)); + + // when, then + assertThatThrownBy(() -> roomService.mandateManager(managerId, 1L, memberId)) + .isInstanceOf(ForbiddenException.class); + } +} diff --git a/src/test/java/com/moabam/api/application/room/SearchServiceTest.java b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java new file mode 100644 index 00000000..6562caa2 --- /dev/null +++ b/src/test/java/com/moabam/api/application/room/SearchServiceTest.java @@ -0,0 +1,417 @@ +package com.moabam.api.application.room; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.anyList; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.spy; +import static org.mockito.BDDMockito.when; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationsSearchRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoomSearchRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.GetAllRoomsResponse; +import com.moabam.api.dto.room.MyRoomsResponse; +import com.moabam.api.dto.room.RoomsHistoryResponse; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.support.fixture.RoomFixture; + +@ExtendWith(MockitoExtension.class) +class SearchServiceTest { + + @InjectMocks + private SearchService searchService; + + @Mock + private CertificationsSearchRepository certificationsSearchRepository; + + @Mock + private ParticipantSearchRepository participantSearchRepository; + + @Mock + private RoutineRepository routineRepository; + + @Mock + private RoomSearchRepository roomSearchRepository; + + @Mock + private MemberService memberService; + + @Mock + private CertificationService certificationService; + + @Mock + private RoomRepository roomRepository; + + @Mock + private ClockHolder clockHolder; + + @DisplayName("유저가 참여중인 방 목록 조회 성공") + @Test + void get_my_rooms_success() { + // given + LocalDate today = LocalDate.now(); + Long memberId = 1L; + Room room1 = spy(RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10)); + Room room2 = spy(RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9)); + Room room3 = spy(RoomFixture.room("밤 - 첫 번째 방", RoomType.NIGHT, 22)); + + when(room1.getId()).thenReturn(1L); + when(room2.getId()).thenReturn(2L); + when(room3.getId()).thenReturn(3L); + + Participant participant1 = RoomFixture.participant(room1, memberId); + Participant participant2 = RoomFixture.participant(room2, memberId); + Participant participant3 = RoomFixture.participant(room3, memberId); + List participants = List.of(participant1, participant2, participant3); + + given(participantSearchRepository.findNotDeletedAllByMemberId(memberId)).willReturn(participants); + given(certificationService.existsMemberCertification(memberId, room1.getId(), today)).willReturn(true); + given(certificationService.existsMemberCertification(memberId, room2.getId(), today)).willReturn(false); + given(certificationService.existsMemberCertification(memberId, room3.getId(), today)).willReturn(true); + + given(certificationService.existsRoomCertification(room1.getId(), today)).willReturn(true); + given(certificationService.existsRoomCertification(room2.getId(), today)).willReturn(false); + given(certificationService.existsRoomCertification(room3.getId(), today)).willReturn(false); + + given(clockHolder.date()).willReturn(LocalDate.now()); + + // when + MyRoomsResponse myRooms = searchService.getMyRooms(memberId); + + // then + assertThat(myRooms.participatingRooms()).hasSize(3); + + assertThat(myRooms.participatingRooms().get(0).isMemberCertifiedToday()).isTrue(); + assertThat(myRooms.participatingRooms().get(0).isRoomCertifiedToday()).isTrue(); + + assertThat(myRooms.participatingRooms().get(1).isMemberCertifiedToday()).isFalse(); + assertThat(myRooms.participatingRooms().get(1).isRoomCertifiedToday()).isFalse(); + + assertThat(myRooms.participatingRooms().get(2).isMemberCertifiedToday()).isTrue(); + assertThat(myRooms.participatingRooms().get(2).isRoomCertifiedToday()).isFalse(); + } + + @DisplayName("방 참여 기록 조회 성공") + @Test + void get_my_join_history_success() { + // given + LocalDateTime today = LocalDateTime.now(); + Long memberId = 1L; + Room room1 = spy(RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10)); + Room room2 = spy(RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9)); + Room room3 = RoomFixture.room("밤 - 첫 번째 방", RoomType.NIGHT, 22); + + when(room1.getId()).thenReturn(1L); + when(room2.getId()).thenReturn(2L); + + Participant participant1 = RoomFixture.participant(room1, memberId); + Participant participant2 = RoomFixture.participant(room2, memberId); + Participant participant3 = spy(RoomFixture.participant(room3, memberId)); + participant3.removeRoom(); + List participants = List.of(participant1, participant2, participant3); + + when(participant3.getDeletedAt()).thenReturn(today); + when(participant3.getDeletedRoomTitle()).thenReturn("밤 - 첫 번째 방"); + given(participantSearchRepository.findAllByMemberId(memberId)).willReturn(participants); + + // when + RoomsHistoryResponse response = searchService.getJoinHistory(memberId); + + // then + assertThat(response.roomHistory()).hasSize(3); + + assertThat(response.roomHistory().get(0).deletedAt()).isNull(); + assertThat(response.roomHistory().get(0).title()).isEqualTo(room1.getTitle()); + + assertThat(response.roomHistory().get(1).deletedAt()).isNull(); + assertThat(response.roomHistory().get(1).title()).isEqualTo(room2.getTitle()); + + assertThat(response.roomHistory().get(2).deletedAt()).isNotNull(); + assertThat(response.roomHistory().get(2).title()).isEqualTo(participant3.getDeletedRoomTitle()); + } + + @DisplayName("아침, 저녁 전체 방 조회 성공, 첫 번째 조회, 다음 페이지 있음") + @Test + void search_all_morning_night_rooms_success() { + // given + Room room1 = spy(RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234")); + Room room2 = spy(RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9)); + Room room3 = spy(RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22)); + Room room4 = spy(RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7)); + Room room5 = spy(RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869")); + Room room6 = spy(RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8)); + Room room7 = spy(RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20)); + Room room8 = spy(RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236")); + Room room9 = spy(RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4)); + Room room10 = spy(RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979")); + Room room11 = spy(RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22)); + Room room12 = spy(RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10)); + Room room13 = spy(RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2)); + Room room14 = spy(RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21)); + + given(room1.getId()).willReturn(1L); + given(room2.getId()).willReturn(2L); + given(room3.getId()).willReturn(3L); + given(room4.getId()).willReturn(4L); + given(room5.getId()).willReturn(5L); + given(room6.getId()).willReturn(6L); + given(room7.getId()).willReturn(7L); + given(room8.getId()).willReturn(8L); + given(room9.getId()).willReturn(9L); + given(room10.getId()).willReturn(10L); + given(room11.getId()).willReturn(11L); + given(room12.getId()).willReturn(12L); + given(room13.getId()).willReturn(13L); + given(room14.getId()).willReturn(14L); + + List rooms = List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11); + + Routine routine1 = spy(RoomFixture.routine(room1, "방1의 루틴1")); + Routine routine2 = spy(RoomFixture.routine(room1, "방1의 루틴2")); + + Routine routine3 = spy(RoomFixture.routine(room2, "방2의 루틴1")); + Routine routine4 = spy(RoomFixture.routine(room2, "방2의 루틴2")); + + Routine routine5 = spy(RoomFixture.routine(room3, "방3의 루틴1")); + Routine routine6 = spy(RoomFixture.routine(room3, "방3의 루틴2")); + + Routine routine7 = spy(RoomFixture.routine(room4, "방4의 루틴1")); + Routine routine8 = spy(RoomFixture.routine(room4, "방4의 루틴2")); + + Routine routine9 = spy(RoomFixture.routine(room5, "방5의 루틴1")); + Routine routine10 = spy(RoomFixture.routine(room5, "방5의 루틴2")); + + Routine routine11 = spy(RoomFixture.routine(room6, "방6의 루틴1")); + Routine routine12 = spy(RoomFixture.routine(room6, "방6의 루틴2")); + + Routine routine13 = spy(RoomFixture.routine(room7, "방7의 루틴1")); + Routine routine14 = spy(RoomFixture.routine(room7, "방7의 루틴2")); + + Routine routine15 = spy(RoomFixture.routine(room8, "방8의 루틴1")); + Routine routine16 = spy(RoomFixture.routine(room8, "방8의 루틴2")); + + Routine routine17 = spy(RoomFixture.routine(room9, "방9의 루틴1")); + Routine routine18 = spy(RoomFixture.routine(room9, "방9의 루틴2")); + + Routine routine19 = spy(RoomFixture.routine(room10, "방10의 루틴1")); + Routine routine20 = spy(RoomFixture.routine(room10, "방10의 루틴2")); + + Routine routine21 = spy(RoomFixture.routine(room11, "방11의 루틴1")); + Routine routine22 = spy(RoomFixture.routine(room11, "방11의 루틴2")); + + Routine routine23 = spy(RoomFixture.routine(room12, "방12의 루틴1")); + Routine routine24 = spy(RoomFixture.routine(room12, "방12의 루틴2")); + + Routine routine25 = spy(RoomFixture.routine(room13, "방13의 루틴1")); + Routine routine26 = spy(RoomFixture.routine(room13, "방13의 루틴2")); + + Routine routine27 = spy(RoomFixture.routine(room14, "방14의 루틴1")); + Routine routine28 = spy(RoomFixture.routine(room14, "방14의 루틴2")); + + given(routine1.getId()).willReturn(1L); + given(routine2.getId()).willReturn(2L); + given(routine3.getId()).willReturn(3L); + given(routine4.getId()).willReturn(4L); + given(routine5.getId()).willReturn(5L); + given(routine6.getId()).willReturn(6L); + given(routine7.getId()).willReturn(7L); + given(routine8.getId()).willReturn(8L); + given(routine9.getId()).willReturn(9L); + given(routine10.getId()).willReturn(10L); + given(routine11.getId()).willReturn(11L); + given(routine12.getId()).willReturn(12L); + given(routine13.getId()).willReturn(13L); + given(routine14.getId()).willReturn(14L); + given(routine15.getId()).willReturn(15L); + given(routine16.getId()).willReturn(16L); + given(routine17.getId()).willReturn(17L); + given(routine18.getId()).willReturn(18L); + given(routine19.getId()).willReturn(19L); + given(routine20.getId()).willReturn(20L); + + List routines = List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, + routine9, routine10, routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, + routine19, routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, + routine28); + + given(roomSearchRepository.findAllWithNoOffset(null, null)).willReturn(rooms); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); + + // when + GetAllRoomsResponse getAllRoomsResponse = searchService.getAllRooms(null, null); + + // then + assertThat(getAllRoomsResponse.hasNext()).isTrue(); + assertThat(getAllRoomsResponse.rooms()).hasSize(10); + assertThat(getAllRoomsResponse.rooms().get(0).id()).isEqualTo(1L); + assertThat(getAllRoomsResponse.rooms().get(9).id()).isEqualTo(10L); + } + + @DisplayName("아침, 저녁 전체 방 조회 성공, 마지막 페이 조회, 다음 페이지 없음") + @Test + void search_last_page_all_morning_night_rooms_success() { + // given + Room room11 = spy(RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22)); + Room room12 = spy(RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10)); + Room room13 = spy(RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2)); + Room room14 = spy(RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21)); + + given(room11.getId()).willReturn(11L); + given(room12.getId()).willReturn(12L); + given(room13.getId()).willReturn(13L); + given(room14.getId()).willReturn(14L); + + List rooms = List.of(room11, room12, room13, room14); + + Routine routine21 = spy(RoomFixture.routine(room11, "방11의 루틴1")); + Routine routine22 = spy(RoomFixture.routine(room11, "방11의 루틴2")); + + Routine routine23 = spy(RoomFixture.routine(room12, "방12의 루틴1")); + Routine routine24 = spy(RoomFixture.routine(room12, "방12의 루틴2")); + + Routine routine25 = spy(RoomFixture.routine(room13, "방13의 루틴1")); + Routine routine26 = spy(RoomFixture.routine(room13, "방13의 루틴2")); + + Routine routine27 = spy(RoomFixture.routine(room14, "방14의 루틴1")); + Routine routine28 = spy(RoomFixture.routine(room14, "방14의 루틴2")); + + given(routine21.getId()).willReturn(21L); + given(routine22.getId()).willReturn(22L); + given(routine23.getId()).willReturn(23L); + given(routine24.getId()).willReturn(24L); + given(routine25.getId()).willReturn(25L); + given(routine26.getId()).willReturn(26L); + given(routine27.getId()).willReturn(27L); + given(routine28.getId()).willReturn(28L); + + List routines = List.of(routine21, routine22, routine23, routine24, routine25, routine26, routine27, + routine28); + + given(roomSearchRepository.findAllWithNoOffset(null, 10L)).willReturn(rooms); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); + + // when + GetAllRoomsResponse getAllRoomsResponse = searchService.getAllRooms(null, 10L); + + // then + assertThat(getAllRoomsResponse.hasNext()).isFalse(); + assertThat(getAllRoomsResponse.rooms()).hasSize(4); + assertThat(getAllRoomsResponse.rooms().get(0).id()).isEqualTo(11L); + assertThat(getAllRoomsResponse.rooms().get(3).id()).isEqualTo(14L); + } + + @DisplayName("전체 방 제목, 방장 이름, 루틴 내용으로 검색 성공 - 최초 조회") + @Test + void search_room_by_title_manager_nickname_routine_success() { + // given + Room room1 = spy(RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234")); + Room room2 = spy(RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9)); + Room room3 = spy(RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22)); + Room room4 = spy(RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7)); + Room room5 = spy(RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869")); + Room room6 = spy(RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8)); + Room room7 = spy(RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20)); + Room room8 = spy(RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236")); + Room room9 = spy(RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4)); + Room room10 = spy(RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979")); + Room room11 = spy(RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22)); + Room room12 = spy(RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10)); + Room room13 = spy(RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2)); + Room room14 = spy(RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21)); + + given(room4.getId()).willReturn(4L); + given(room5.getId()).willReturn(5L); + given(room6.getId()).willReturn(6L); + given(room7.getId()).willReturn(7L); + given(room8.getId()).willReturn(8L); + given(room9.getId()).willReturn(9L); + given(room10.getId()).willReturn(10L); + given(room11.getId()).willReturn(11L); + given(room12.getId()).willReturn(12L); + given(room13.getId()).willReturn(13L); + given(room14.getId()).willReturn(14L); + + List rooms = List.of(room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, room14); + + Routine routine9 = spy(RoomFixture.routine(room5, "방5의 루틴1")); + Routine routine10 = spy(RoomFixture.routine(room5, "방5의 루틴2")); + + Routine routine11 = spy(RoomFixture.routine(room6, "방6의 루틴1")); + Routine routine12 = spy(RoomFixture.routine(room6, "방6의 루틴2")); + + Routine routine13 = spy(RoomFixture.routine(room7, "방7의 루틴1")); + Routine routine14 = spy(RoomFixture.routine(room7, "방7의 루틴2")); + + Routine routine15 = spy(RoomFixture.routine(room8, "방8의 루틴1")); + Routine routine16 = spy(RoomFixture.routine(room8, "방8의 루틴2")); + + Routine routine17 = spy(RoomFixture.routine(room9, "방9의 루틴1")); + Routine routine18 = spy(RoomFixture.routine(room9, "방9의 루틴2")); + + Routine routine19 = spy(RoomFixture.routine(room10, "방10의 루틴1")); + Routine routine20 = spy(RoomFixture.routine(room10, "방10의 루틴2")); + + Routine routine21 = spy(RoomFixture.routine(room11, "방11의 루틴1")); + Routine routine22 = spy(RoomFixture.routine(room11, "방11의 루틴2")); + + Routine routine23 = spy(RoomFixture.routine(room12, "방12의 루틴1")); + Routine routine24 = spy(RoomFixture.routine(room12, "방12의 루틴2")); + + Routine routine25 = spy(RoomFixture.routine(room13, "방13의 루틴1")); + Routine routine26 = spy(RoomFixture.routine(room13, "방13의 루틴2")); + + Routine routine27 = spy(RoomFixture.routine(room14, "방14의 루틴1")); + Routine routine28 = spy(RoomFixture.routine(room14, "방14의 루틴2")); + + given(routine9.getId()).willReturn(9L); + given(routine10.getId()).willReturn(10L); + given(routine11.getId()).willReturn(11L); + given(routine12.getId()).willReturn(12L); + given(routine13.getId()).willReturn(13L); + given(routine14.getId()).willReturn(14L); + given(routine15.getId()).willReturn(15L); + given(routine16.getId()).willReturn(16L); + given(routine17.getId()).willReturn(17L); + given(routine18.getId()).willReturn(18L); + given(routine19.getId()).willReturn(19L); + given(routine20.getId()).willReturn(20L); + given(routine21.getId()).willReturn(21L); + given(routine22.getId()).willReturn(22L); + given(routine23.getId()).willReturn(23L); + given(routine24.getId()).willReturn(24L); + given(routine25.getId()).willReturn(25L); + given(routine26.getId()).willReturn(26L); + + List routines = List.of(routine9, routine10, routine11, routine12, routine13, routine14, routine15, + routine16, routine17, routine18, routine19, routine20, routine21, routine22, routine23, routine24, + routine25, routine26, routine27, routine28); + + given(roomRepository.searchByKeyword("번째")).willReturn(rooms); + given(routineRepository.findAllByRoomIdIn(anyList())).willReturn(routines); + + // when + GetAllRoomsResponse getAllRoomsResponse = searchService.searchRooms("번째", null, null); + + // then + assertThat(getAllRoomsResponse.hasNext()).isTrue(); + assertThat(getAllRoomsResponse.rooms()).hasSize(10); + } +} diff --git a/src/test/java/com/moabam/api/domain/bug/BugTest.java b/src/test/java/com/moabam/api/domain/bug/BugTest.java new file mode 100644 index 00000000..4fe62d0d --- /dev/null +++ b/src/test/java/com/moabam/api/domain/bug/BugTest.java @@ -0,0 +1,79 @@ +package com.moabam.api.domain.bug; + +import static com.moabam.support.fixture.BugFixture.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.moabam.global.error.exception.BadRequestException; + +class BugTest { + + @DisplayName("벌레 개수가 음수이면 예외가 발생한다.") + @ParameterizedTest + @CsvSource({ + "-10, 10, 10", + "10, -10, 10", + "10, 10, -10", + }) + void validate_bug_count_exception(int morningBug, int nightBug, int goldenBug) { + Bug.BugBuilder bugBuilder = Bug.builder() + .morningBug(morningBug) + .nightBug(nightBug) + .goldenBug(goldenBug); + + assertThatThrownBy(bugBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("벌레 개수는 0 이상이어야 합니다."); + } + + @DisplayName("벌레를 사용한다.") + @Nested + class Use { + + @DisplayName("성공한다.") + @Test + void success() { + // given + Bug bug = bug(); + + // when + bug.use(BugType.MORNING, 5); + + // then + assertThat(bug.getMorningBug()).isEqualTo(MORNING_BUG - 5); + } + + @DisplayName("벌레 개수가 부족하면 사용할 수 없다.") + @Test + void not_enough_exception() { + // given + Bug bug = bug(); + + // when, then + assertThatThrownBy(() -> bug.use(BugType.MORNING, 50)) + .isInstanceOf(BadRequestException.class) + .hasMessage("보유한 벌레가 부족합니다."); + } + } + + @DisplayName("해당 벌레 타입의 개수를 증가한다.") + @Test + void increase_bug_success() { + // given + Bug bug = bug(); + + // when + bug.increase(BugType.MORNING, 5); + bug.increase(BugType.NIGHT, 5); + bug.increase(BugType.GOLDEN, 5); + + // then + assertThat(bug.getMorningBug()).isEqualTo(MORNING_BUG + 5); + assertThat(bug.getNightBug()).isEqualTo(NIGHT_BUG + 5); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponTest.java new file mode 100644 index 00000000..caca0818 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/CouponTest.java @@ -0,0 +1,62 @@ +package com.moabam.api.domain.coupon; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.fixture.CouponFixture; + +class CouponTest { + + @DisplayName("쿠폰이 성공적으로 생성된다. - Coupon") + @Test + void coupon_success() { + // Given + LocalDate startAt = LocalDate.of(2023, 2, 1); + LocalDate openAt = LocalDate.of(2023, 1, 1); + + // When + Coupon actual = Coupon.builder() + .name("couponName") + .point(10) + .type(CouponType.MORNING) + .maxCount(100) + .startAt(startAt) + .openAt(openAt) + .adminId(1L) + .build(); + + // Then + assertThat(actual.getName()).isEqualTo("couponName"); + assertThat(actual.getDescription()).isBlank(); + assertThat(actual.getPoint()).isEqualTo(10); + assertThat(actual.getMaxCount()).isEqualTo(100); + assertThat(actual.getType()).isEqualTo(CouponType.MORNING); + assertThat(actual.getStartAt()).isEqualTo(startAt); + assertThat(actual.getOpenAt()).isEqualTo(openAt); + assertThat(actual.getAdminId()).isEqualTo(1L); + } + + @DisplayName("쿠폰 보너스 포인트가 1보다 작다. - BadRequestException") + @Test + void validatePoint_BadRequestException() { + // When& Then + assertThatThrownBy(() -> CouponFixture.coupon(0, 1)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_POINT.getMessage()); + } + + @DisplayName("쿠폰 재고가 1보다 작다. - BadRequestException") + @Test + void validateStock_BadRequestException() { + // When& Then + assertThatThrownBy(() -> CouponFixture.coupon(1, 0)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_COUPON_STOCK.getMessage()); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java new file mode 100644 index 00000000..cf774ca9 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/CouponTypeTest.java @@ -0,0 +1,52 @@ +package com.moabam.api.domain.coupon; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; + +class CouponTypeTest { + + @DisplayName("존재하는 쿠폰을 성공적으로 가져온다. - CouponType") + @Test + void from_success() { + // When + CouponType actual = CouponType.from(CouponType.GOLDEN.getName()); + + // Then + assertThat(actual).isEqualTo(CouponType.GOLDEN); + } + + @DisplayName("존재하지 않는 쿠폰을 가져온다. - NotFoundException") + @Test + void from_NotFoundException() { + // When & Then + assertThatThrownBy(() -> CouponType.from("Not-Coupon")) + .isInstanceOf(NotFoundException.class) + .hasMessage(ErrorMessage.NOT_FOUND_COUPON_TYPE.getMessage()); + } + + @DisplayName("할인 쿠폰인 확인한다. - Boolean") + @Test + void isDiscount_true() { + // When + boolean actual = CouponType.DISCOUNT.isDiscount(); + + // Then + assertThat(actual).isTrue(); + } + + @DisplayName("벌레 타입을 반환한다. - CouponType") + @Test + void getBugType_success() { + // When + BugType actual = CouponType.GOLDEN.getBugType(); + + // Then + assertThat(actual).isEqualTo(BugType.GOLDEN); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java b/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java new file mode 100644 index 00000000..13bcd9e5 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/CouponWalletTest.java @@ -0,0 +1,25 @@ +package com.moabam.api.domain.coupon; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.moabam.support.fixture.CouponFixture; + +class CouponWalletTest { + + @DisplayName("쿠폰 지갑 엔티티를 성공적으로 생성한다. - Void") + @Test + void couponWallet_success() { + // Given + Coupon coupon = CouponFixture.coupon("CouponName", 1, 2); + + // When + CouponWallet actual = CouponWallet.create(1L, coupon); + + // Then + assertThat(actual.getMemberId()).isEqualTo(1L); + assertThat(actual.getCoupon().getName()).isEqualTo(coupon.getName()); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java new file mode 100644 index 00000000..f63cc680 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponManageRepositoryTest.java @@ -0,0 +1,119 @@ +package com.moabam.api.domain.coupon.repository; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; + +@ExtendWith(MockitoExtension.class) +class CouponManageRepositoryTest { + + @InjectMocks + CouponManageRepository couponManageRepository; + + @Mock + ZSetRedisRepository zSetRedisRepository; + + @Mock + ValueRedisRepository valueRedisRepository; + + @DisplayName("쿠폰 대기열에 사용자가 성공적으로 등록된다. - Void") + @Test + void addIfAbsentQueue_success() { + // When + couponManageRepository.addIfAbsentQueue("couponName", 1L, 1); + + // Then + verify(zSetRedisRepository).addIfAbsent(any(String.class), any(Long.class), any(double.class), any(int.class)); + } + + @DisplayName("쿠폰명이 Null인 대기열에 사용자를 등록한다.- NullPointerException") + @Test + void addIfAbsentQueue_couponName_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.addIfAbsentQueue(null, 1L, 1)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열에 사용자 ID가 Null인 사용자를 등록한다. - NullPointerException") + @Test + void addIfAbsentQueue_memberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.addIfAbsentQueue("couponName", null, 1)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열에서 성공적으로 10명을 조회한다. - Set") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideValues_Object") + @ParameterizedTest + void range_success(Set values) { + // Given + given(zSetRedisRepository.range(any(String.class), any(long.class), any(long.class))).willReturn(values); + + // When + Set actual = couponManageRepository.rangeQueue("couponName", 0, 10); + + // Then + assertThat(actual).hasSize(10); + } + + @DisplayName("쿠폰명이 Null인 대기열에서 사용자를 조회한다. - NullPointerException") + @Test + void range_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.rangeQueue(null, 0, 10)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("특정 사용자의 쿠폰 순위를 정상적으로 조회한다. - int") + @Test + void rankQueue_success() { + // Given + given(zSetRedisRepository.rank(any(String.class), any(Long.class))).willReturn(1L); + + // When + int actual = couponManageRepository.rankQueue("couponName", 1L); + + // Then + assertThat(actual).isEqualTo(1); + } + + @DisplayName("쿠폰명이 Null인 특정 사용자 쿠폰 순위를 조회한다. - int") + @Test + void rankQueue_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.rankQueue(null, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("쿠폰 대기열을 성공적으로 삭제한다. - Void") + @Test + void deleteQueue_success() { + // When + couponManageRepository.deleteQueue("couponName"); + + // Then + verify(valueRedisRepository).delete(any(String.class)); + } + + @DisplayName("쿠폰명이 Null인 대기열을 삭제한다. - NullPointerException") + @Test + void deleteQueue_NullPointerException() { + // When & Then + assertThatThrownBy(() -> couponManageRepository.deleteQueue(null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java new file mode 100644 index 00000000..41d5cf78 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponSearchRepositoryTest.java @@ -0,0 +1,104 @@ +package com.moabam.api.domain.coupon.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.global.config.JpaConfig; +import com.moabam.support.fixture.CouponFixture; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@Import({JpaConfig.class, CouponSearchRepository.class}) +class CouponSearchRepositoryTest { + + @Autowired + CouponRepository couponRepository; + + @Autowired + CouponSearchRepository couponSearchRepository; + + @DisplayName("발급 가능한 쿠폰을 성공적으로 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void findAllByStatus_success(List coupons) { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); + LocalDate now = LocalDate.of(2023, 7, 1); + + couponRepository.saveAll(coupons); + + // When + List actual = couponSearchRepository.findAllByStatus(now, request); + + // Then + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getStartAt()).isEqualTo(LocalDate.of(2023, 7, 1)); + } + + @DisplayName("모든 쿠폰을 발급 가능 날짜 순으로 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void findAllByStatus_order_by_startAt(List coupons) { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(true, true); + LocalDate now = LocalDate.now(); + + couponRepository.saveAll(coupons); + + // When + List actual = couponSearchRepository.findAllByStatus(now, request); + + // Then + assertThat(actual).hasSize(coupons.size()); + assertThat(actual.get(0).getStartAt()).isEqualTo(LocalDate.of(2023, 3, 1)); + } + + @DisplayName("발급 가능한 쿠폰 포함하여 쿠폰 정보 오픈 중인 쿠폰들을 발급 가능 날짜 순으로 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void findAllByStatus_opened_order_by_startAt(List coupons) { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(true, false); + LocalDate now = LocalDate.of(2023, 7, 1); + + couponRepository.saveAll(coupons); + + // When + List actual = couponSearchRepository.findAllByStatus(now, request); + + // Then + assertThat(actual).hasSize(3); + assertThat(actual.get(0).getStartAt()).isEqualTo(LocalDate.of(2023, 7, 1)); + } + + @DisplayName("종료된 쿠폰들을 발급 가능 날짜 순으로 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void findAllByStatus_ended_order_by_startAt(List coupons) { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(false, true); + LocalDate now = LocalDate.of(2023, 8, 1); + + couponRepository.saveAll(coupons); + + // When + List actual = couponSearchRepository.findAllByStatus(now, request); + + // Then + assertThat(actual).hasSize(5); + assertThat(actual.get(0).getStartAt()).isEqualTo(LocalDate.of(2023, 3, 1)); + } +} diff --git a/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java new file mode 100644 index 00000000..2770be82 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/coupon/repository/CouponWalletSearchRepositoryTest.java @@ -0,0 +1,126 @@ +package com.moabam.api.domain.coupon.repository; + +import static com.moabam.support.fixture.CouponFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.global.error.exception.NotFoundException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.CouponFixture; + +@QuerydslRepositoryTest +class CouponWalletSearchRepositoryTest { + + @Autowired + private CouponRepository couponRepository; + + @Autowired + private CouponWalletRepository couponWalletRepository; + + @Autowired + private CouponWalletSearchRepository couponWalletSearchRepository; + + @DisplayName("나의 쿠폰함의 특정 쿠폰을 조회한다. - List") + @Test + void findAllByIdAndMemberId_success() { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // When + List actual = couponWalletSearchRepository + .findAllByIdAndMemberId(couponWallet.getId(), 1L); + + // Then + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getCoupon().getName()).isEqualTo(coupon.getName()); + assertThat(actual.get(0).getMemberId()).isEqualTo(couponWallet.getMemberId()); + } + + @DisplayName("ID가 1인 회원은 쿠폰 1개를 가지고 있다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByIdAndMemberId_Id1_success(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByIdAndMemberId(null, 1L); + + // Then + assertThat(actual).hasSize(1); + } + + @DisplayName("ID가 2인 회원은 쿠폰 ID가 777인 쿠폰을 가지고 있지 않다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByIdAndMemberId_Id2_notCouponId777(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByIdAndMemberId(777L, 2L); + + // Then + assertThat(actual).isEmpty(); + } + + @DisplayName("ID가 3인 회원은 쿠폰 3개를 가지고 있다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletAll") + @ParameterizedTest + void findAllByIdAndMemberId_Id3_success(List couponWallets) { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(couponWallet.getMemberId(), coupon)); + }); + + // When + List actual = couponWalletSearchRepository.findAllByIdAndMemberId(null, 3L); + + // Then + assertThat(actual).hasSize(3); + } + + @DisplayName("회원의 특정 쿠폰 지갑을 성공적으로 조회한다. - CouponWallet") + @Test + void findByIdAndMemberId_success() { + // given + Coupon coupon = couponRepository.save(discount1000Coupon()); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // when + CouponWallet actual = couponWalletSearchRepository.findByIdAndMemberId(couponWallet.getId(), 1L) + .orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON_WALLET)); + + // then + assertThat(actual.getCoupon()).isEqualTo(coupon); + } + + @DisplayName("특정 회원의 특정 쿠폰 지갑이 조회되지 않는다. - CouponWallet") + @Test + void findByIdAndMemberId_notFound() { + // When + Optional actual = couponWalletSearchRepository.findByIdAndMemberId(1L, 1L); + + // Then + assertThat(actual).isEmpty(); + } +} diff --git a/src/test/java/com/moabam/api/domain/entity/MemberTest.java b/src/test/java/com/moabam/api/domain/entity/MemberTest.java new file mode 100644 index 00000000..1abd753b --- /dev/null +++ b/src/test/java/com/moabam/api/domain/entity/MemberTest.java @@ -0,0 +1,111 @@ +package com.moabam.api.domain.entity; + +import static com.moabam.global.common.util.BaseImageUrl.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.Role; +import com.moabam.api.domain.room.RoomType; +import com.moabam.support.fixture.MemberFixture; + +class MemberTest { + + String socialId = "1"; + String nickname = "밥세공기"; + String profileImage = "kakao/profile/url"; + + @DisplayName("회원 생성 성공") + @Test + void create_member_success() { + // When + Then + assertThatNoException().isThrownBy(() -> Member.builder() + .socialId(socialId) + .bug(Bug.builder().build()) + .build()); + } + + @DisplayName("프로필 이미지 없이 회원 생성 성공") + @Test + void create_member_noImage_success() { + // When + Then + assertThatNoException().isThrownBy(() -> { + Member member = Member.builder() + .socialId(socialId) + .bug(Bug.builder().build()) + .build(); + + assertAll( + () -> assertThat(member.getProfileImage()).isEqualTo(IMAGE_DOMAIN + MEMBER_PROFILE_URL), + () -> assertThat(member.getRole()).isEqualTo(Role.USER), + () -> assertThat(member.getBug().getNightBug()).isZero(), + () -> assertThat(member.getBug().getGoldenBug()).isZero(), + () -> assertThat(member.getBug().getMorningBug()).isZero(), + () -> assertThat(member.getTotalCertifyCount()).isZero(), + () -> assertThat(member.getReportCount()).isZero(), + () -> assertThat(member.getCurrentMorningCount()).isZero(), + () -> assertThat(member.getCurrentNightCount()).isZero() + ); + }); + } + + @DisplayName("소셜ID에 따른 회원 생성 실패") + @Test + void creat_member_failBy_socialId() { + // When + Then + assertThatThrownBy(Member.builder()::build) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("닉네임에 따른 회원 생성 실패") + @Test + void create_member_failBy_nickname() { + // When + Then + assertThatThrownBy(Member.builder() + .socialId(socialId)::build) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("멤버 방 출입 기능 테스트") + @Nested + class MemberRoomInOut { + + @DisplayName("회원 방 입장 성공") + @Test + void member_room_enter_success() { + // given + Member member = MemberFixture.member(); + + // when + int beforeMorningCount = member.getCurrentMorningCount(); + member.enterRoom(RoomType.MORNING); + + int beforeNightCount = member.getCurrentNightCount(); + member.enterRoom(RoomType.NIGHT); + + // then + assertThat(member.getCurrentMorningCount()).isEqualTo(beforeMorningCount + 1); + assertThat(member.getCurrentMorningCount()).isEqualTo(beforeNightCount + 1); + } + + @DisplayName("회원 방 탈출 성공") + @Test + void member_room_exit_success() { + // given + Member member = MemberFixture.member(); + + // when + member.exitRoom(RoomType.MORNING); + member.exitRoom(RoomType.NIGHT); + + // then + assertThat(member.getCurrentMorningCount()).isZero(); + assertThat(member.getCurrentNightCount()).isZero(); + } + } +} diff --git a/src/test/java/com/moabam/api/domain/item/ItemTest.java b/src/test/java/com/moabam/api/domain/item/ItemTest.java new file mode 100644 index 00000000..326dd553 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/item/ItemTest.java @@ -0,0 +1,117 @@ +package com.moabam.api.domain.item; + +import static com.moabam.support.fixture.ItemFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.moabam.api.domain.bug.BugType; +import com.moabam.global.error.exception.BadRequestException; + +class ItemTest { + + @DisplayName("아이템을 생성한다.") + @Nested + class Create { + + @DisplayName("해금 레벨은 기본 1로 설정한다.") + @Test + void default_unlock_level() { + // given, when + Item item = nightMageSkin(); + + // then + assertThat(item.getUnlockLevel()).isEqualTo(1); + } + + @DisplayName("가격이 음수이면 예외가 발생한다.") + @ParameterizedTest + @CsvSource({ + "-10, 10", + "10, -10", + }) + void price_exception(int bugPrice, int goldenBugPrice) { + Item.ItemBuilder itemBuilder = morningSantaSkin() + .bugPrice(bugPrice) + .goldenBugPrice(goldenBugPrice); + + assertThatThrownBy(itemBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("가격은 0 이상이어야 합니다."); + } + + @DisplayName("레벨이 1보다 작으면 예외가 발생한다.") + @Test + void level_exception() { + Item.ItemBuilder itemBuilder = morningSantaSkin() + .unlockLevel(-1); + + assertThatThrownBy(itemBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("레벨은 1 이상이어야 합니다."); + } + } + + @DisplayName("해당 벌레 타입의 가격을 조회한다.") + @ParameterizedTest + @CsvSource({ + "MORNING, 10", + "GOLDEN, 5", + }) + void get_price_success(BugType bugType, int expected) { + // given + Item item = morningSantaSkin() + .bugPrice(10) + .goldenBugPrice(5) + .build(); + + // when, then + assertThat(item.getPrice(bugType)).isEqualTo(expected); + } + + @DisplayName("아이템 구매 가능 여부를 검증한다.") + @Nested + class ValidatePurchasable { + + @DisplayName("성공한다.") + @Test + void success() { + // given + Item item = nightMageSkin(); + + // when, then + assertDoesNotThrow(() -> item.validatePurchasable(BugType.NIGHT, 5)); + } + + @DisplayName("해금 레벨이 높으면 구매할 수 없다.") + @Test + void unlocked_exception() { + // given + Item item = morningSantaSkin() + .unlockLevel(10) + .build(); + + // when, then + assertThatThrownBy(() -> item.validatePurchasable(BugType.MORNING, 5)) + .isInstanceOf(BadRequestException.class) + .hasMessage("아이템 해금 레벨이 높습니다."); + } + + @DisplayName("벌레 타입이 맞지 않으면 구매할 수 없다.") + @Test + void bug_type_exception() { + // given + Item item = nightMageSkin(); + + // when, then + assertThatThrownBy(() -> item.validatePurchasable(BugType.MORNING, 5)) + .isInstanceOf(BadRequestException.class) + .hasMessage("해당 벌레 타입으로는 구매할 수 없는 아이템입니다."); + } + } +} diff --git a/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java new file mode 100644 index 00000000..b609e309 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/item/repository/InventorySearchRepositoryTest.java @@ -0,0 +1,169 @@ +package com.moabam.api.domain.item.repository; + +import static com.moabam.support.fixture.InventoryFixture.*; +import static com.moabam.support.fixture.ItemFixture.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.RoomType; +import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; +import com.moabam.support.fixture.MemberFixture; + +@QuerydslRepositoryTest +class InventorySearchRepositoryTest { + + @Autowired + InventorySearchRepository inventorySearchRepository; + + @Autowired + MemberRepository memberRepository; + + @Autowired + ItemRepository itemRepository; + + @Autowired + InventoryRepository inventoryRepository; + + @DisplayName("인벤토리 아이템 목록을 조회한다.") + @Nested + class FindItems { + + @DisplayName("해당 타입의 아이템 목록을 구매일 순으로 정렬한다.") + @Test + void sorted_by_created_at_success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().build()); + inventoryRepository.save(inventory(memberId, morningSantaSkin)); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().build()); + inventoryRepository.save(inventory(memberId, morningKillerSkin)); + Item nightMageSkin = itemRepository.save(nightMageSkin()); + inventoryRepository.save(inventory(memberId, nightMageSkin)); + + // when + List actual = inventorySearchRepository.findItems(memberId, ItemType.MORNING); + + // then + assertThat(actual).hasSize(2) + .containsExactly(morningKillerSkin, morningSantaSkin); + } + + @DisplayName("해당 타입의 아이템이 없으면 빈 목록을 조회한다.") + @Test + void empty_success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().build()); + inventoryRepository.save(inventory(memberId, morningSantaSkin)); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().build()); + inventoryRepository.save(inventory(memberId, morningKillerSkin)); + + // when + List actual = inventorySearchRepository.findItems(memberId, ItemType.NIGHT); + + // then + assertThat(actual).isEmpty(); + } + } + + @DisplayName("인벤토리를 조회한다.") + @Test + void find_one_success() { + // given + Member member = memberRepository.save(member("999")); + Item item = itemRepository.save(nightMageSkin()); + Inventory inventory = inventoryRepository.save(inventory(member.getId(), item)); + + // when + Optional actual = inventorySearchRepository.findOne(member.getId(), item.getId()); + + // then + assertThat(actual).isPresent().contains(inventory); + } + + @DisplayName("현재 적용된 인벤토리를 조회한다.") + @Test + void find_default_success() { + // given + Member member = memberRepository.save(member("11314")); + Item item = itemRepository.save(nightMageSkin()); + Inventory inventory = inventoryRepository.save(inventory(member.getId(), item)); + inventory.select(member); + + // when + Optional actual = inventorySearchRepository.findDefault(member.getId(), inventory.getItemType()); + + // then + assertThat(actual).isPresent().contains(inventory); + } + + @DisplayName("여러 회원의 밤 타입에 적용된 인벤토리를 조회한다.") + @Test + void find_all_default_type_night_success() { + // given + Member member1 = memberRepository.save(member("625")); + Member member2 = memberRepository.save(member("255")); + Item item = itemRepository.save(nightMageSkin()); + Inventory inventory1 = inventoryRepository.save(inventory(member1.getId(), item)); + Inventory inventory2 = inventoryRepository.save(inventory(member2.getId(), item)); + inventory1.select(member1); + inventory2.select(member2); + + // when + List actual = inventorySearchRepository.findDefaultInventories(List.of(member1.getId(), + member2.getId()), RoomType.NIGHT.name()); + + // then + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getItem().getName()).isEqualTo(nightMageSkin().getName()); + } + + @DisplayName("기본 새 찾는 쿼리") + @Nested + class FindDefaultBird { + + @DisplayName("default 가져오기 성공") + @Test + void bird_find_success() { + // given + Member member = MemberFixture.member("fffdd"); + member.exitRoom(RoomType.MORNING); + memberRepository.save(member); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(member); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(member); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // when + List inventories = inventorySearchRepository.findDefaultSkin(member.getId()); + + // then + assertThat(inventories).hasSize(2); + } + } +} diff --git a/src/test/java/com/moabam/api/domain/item/repository/ItemSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/item/repository/ItemSearchRepositoryTest.java new file mode 100644 index 00000000..1d1988a7 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/item/repository/ItemSearchRepositoryTest.java @@ -0,0 +1,100 @@ +package com.moabam.api.domain.item.repository; + +import static com.moabam.support.fixture.InventoryFixture.*; +import static com.moabam.support.fixture.ItemFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.support.annotation.QuerydslRepositoryTest; + +@QuerydslRepositoryTest +class ItemSearchRepositoryTest { + + @Autowired + ItemSearchRepository itemSearchRepository; + + @Autowired + ItemRepository itemRepository; + + @Autowired + InventoryRepository inventoryRepository; + + @DisplayName("구매하지 않은 아이템 목록을 조회한다.") + @Nested + class FindNotPurchasedItems { + + @DisplayName("해당 타입의 구매하지 않은 아이템 목록을 조회한다.") + @Test + void success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().build()); + inventoryRepository.save(inventory(memberId, morningSantaSkin)); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().build()); + itemRepository.save(nightMageSkin()); + + // when + List actual = itemSearchRepository.findNotPurchasedItems(memberId, ItemType.MORNING); + + // then + assertThat(actual).hasSize(1) + .containsExactly(morningKillerSkin); + } + + @DisplayName("구매하지 않은 아이템 목록은 레벨 순으로 정렬된다.") + @Test + void sorted_by_level_success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().unlockLevel(5).build()); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().unlockLevel(1).build()); + + // when + List actual = itemSearchRepository.findNotPurchasedItems(memberId, ItemType.MORNING); + + // then + assertThat(actual).hasSize(2) + .containsExactly(morningKillerSkin, morningSantaSkin); + } + + @DisplayName("레벨이 같으면 가격 순으로 정렬된다.") + @Test + void sorted_by_price_success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().bugPrice(10).build()); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().bugPrice(20).build()); + + // when + List actual = itemSearchRepository.findNotPurchasedItems(memberId, ItemType.MORNING); + + // then + assertThat(actual).hasSize(2) + .containsExactly(morningSantaSkin, morningKillerSkin); + } + + @DisplayName("레벨과 가격이 같으면 이름 순으로 정렬된다.") + @Test + void sorted_by_name_success() { + // given + Long memberId = 1L; + Item morningSantaSkin = itemRepository.save(morningSantaSkin().build()); + Item morningKillerSkin = itemRepository.save(morningKillerSkin().build()); + + // when + List actual = itemSearchRepository.findNotPurchasedItems(memberId, ItemType.MORNING); + + // then + assertThat(actual).hasSize(2) + .containsExactly(morningSantaSkin, morningKillerSkin); + } + } +} diff --git a/src/test/java/com/moabam/api/domain/member/BadgeRepositoryTest.java b/src/test/java/com/moabam/api/domain/member/BadgeRepositoryTest.java new file mode 100644 index 00000000..a250f84b --- /dev/null +++ b/src/test/java/com/moabam/api/domain/member/BadgeRepositoryTest.java @@ -0,0 +1,84 @@ +package com.moabam.api.domain.member; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; + +import com.moabam.api.application.member.BadgeService; +import com.moabam.api.domain.member.repository.BadgeRepository; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.MemberFixture; + +@QuerydslRepositoryTest +class BadgeRepositoryTest { + + @Autowired + BadgeRepository badgeRepository; + + @Autowired + MemberRepository memberRepository; + + @DisplayName("인증 횟수에 따른 값 뱃지 확인") + @Test + void get_badge_by_certifyCount() { + assertThat(BadgeType.getBadgeFrom(10).get()).isEqualTo(BadgeType.BIRTH); + assertThat(BadgeType.getBadgeFrom(100).get()).isEqualTo(BadgeType.LEVEL10); + assertThat(BadgeType.getBadgeFrom(500).get()).isEqualTo(BadgeType.LEVEL50); + assertThat(BadgeType.getBadgeFrom(9)).isEmpty(); + } + + @DisplayName("뱃지 생성 성공") + @ParameterizedTest + @ValueSource(ints = {10, 100, 500}) + void member_get_badge_success(int certifyCount) { + // given + BadgeService badgeService = new BadgeService(badgeRepository); + + Member member = MemberFixture.member(); + for (int i = 0; i < certifyCount; i++) { + member.increaseTotalCertifyCount(); + } + + memberRepository.save(member); + + // when + badgeService.createBadge(member.getId(), member.getTotalCertifyCount()); + BadgeType expectedType = BadgeType.getBadgeFrom(certifyCount).get(); + + // then + assertThat(badgeRepository.existsByMemberIdAndType(member.getId(), expectedType)) + .isTrue(); + } + + @DisplayName("뱃지가 있으면 저장하지 않는다.") + @ParameterizedTest + @ValueSource(ints = {10, 100, 500}) + void already_exist_bage_then_no_save(int certifyCount) { + // given + BadgeService badgeService = new BadgeService(badgeRepository); + + Member member = MemberFixture.member(); + for (int i = 0; i < certifyCount; i++) { + member.increaseTotalCertifyCount(); + } + + memberRepository.save(member); + + // when + BadgeType expectedType = BadgeType.getBadgeFrom(certifyCount).get(); + + Badge badge = Badge.builder().memberId(member.getId()).type(expectedType).build(); + badgeRepository.save(badge); + + // then + assertThatNoException() + .isThrownBy(() -> badgeService.createBadge(member.getId(), member.getTotalCertifyCount())); + assertThat(badgeRepository.existsByMemberIdAndType(member.getId(), expectedType)) + .isTrue(); + } +} diff --git a/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java b/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java new file mode 100644 index 00000000..dd7f0ce4 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/member/MemberRepositoryTest.java @@ -0,0 +1,152 @@ +package com.moabam.api.domain.member; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.application.member.MemberMapper; +import com.moabam.api.domain.member.repository.BadgeRepository; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.dto.member.MemberInfo; +import com.moabam.api.dto.member.MemberInfoSearchResponse; +import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.BadgeFixture; +import com.moabam.support.fixture.MemberFixture; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@QuerydslRepositoryTest +class MemberRepositoryTest { + + @Autowired + MemberRepository memberRepository; + + @Autowired + MemberSearchRepository memberSearchRepository; + + @Autowired + RoomRepository roomRepository; + + @Autowired + BadgeRepository badgeRepository; + + @Autowired + ParticipantRepository participantRepository; + + @PersistenceContext + EntityManager entityManager; + + @DisplayName("회원 생성 테스트") + @Test + void test() { + // given + Member member = MemberFixture.member("313"); + memberRepository.save(member); + + // when + Member savedMember = memberRepository.findBySocialId(member.getSocialId()).orElse(null); + + // then + assertThat(savedMember).isNotNull(); + } + + @DisplayName("회원 정보 찾는 Query") + @Nested + class FindMemberInfo { + + @DisplayName("회원 없어서 실패") + @Test + void member_not_found() { + // Given + List memberInfos = memberSearchRepository.findMemberAndBadges(999L, false); + + // When + Then + assertThat(memberInfos).isEmpty(); + } + + @DisplayName("성공") + @Test + void search_info_success() { + // given + Member member = MemberFixture.member("hhhh"); + member.enterRoom(RoomType.MORNING); + memberRepository.save(member); + + Badge birth = BadgeFixture.badge(member.getId(), BadgeType.BIRTH); + Badge level50 = BadgeFixture.badge(member.getId(), BadgeType.LEVEL50); + Badge level10 = BadgeFixture.badge(member.getId(), BadgeType.LEVEL10); + List badges = List.of(birth, level10, level50); + badgeRepository.saveAll(badges); + + // when + List memberInfos = memberSearchRepository.findMemberAndBadges(member.getId(), true); + + // then + assertThat(memberInfos).isNotEmpty(); + + MemberInfoSearchResponse memberInfoSearchResponse = MemberMapper.toMemberInfoSearchResponse(memberInfos); + assertThat(memberInfoSearchResponse.badges()).hasSize(badges.size()); + } + + @DisplayName("성공") + @Test + void no_badges_search_success() { + // given + Member member = MemberFixture.member("ttttt"); + member.enterRoom(RoomType.MORNING); + memberRepository.save(member); + + // when + List memberInfos = memberSearchRepository.findMemberAndBadges(member.getId(), true); + + // then + assertThat(memberInfos).isNotEmpty(); + + MemberInfoSearchResponse memberInfoSearchResponse = MemberMapper.toMemberInfoSearchResponse(memberInfos); + assertThat(memberInfoSearchResponse.badges()).isEmpty(); + } + } + + @DisplayName("삭제된 회원 찾기 테스트") + @Transactional + @Test + void findMemberTest() { + // Given + Member member = MemberFixture.member(); + + // When + memberRepository.save(member); + + member.delete(LocalDateTime.now()); + memberRepository.flush(); + memberRepository.delete(member); + + memberRepository.flush(); + + // then + Optional deletedMember = memberSearchRepository.findMember(member.getId(), false); + + Assertions.assertAll( + () -> assertThat(deletedMember).isPresent(), + () -> { + Member delete = deletedMember.get(); + assertThat(delete.getSocialId()).contains("delete"); + assertThat(delete.getDeletedAt()).isNotNull(); + } + ); + } +} diff --git a/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java b/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java new file mode 100644 index 00000000..681a9aa5 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/notification/repository/NotificationRepositoryTest.java @@ -0,0 +1,93 @@ +package com.moabam.api.domain.notification.repository; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.Duration; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; + +@ExtendWith(MockitoExtension.class) +class NotificationRepositoryTest { + + @InjectMocks + NotificationRepository notificationRepository; + + @Mock + ValueRedisRepository valueRedisRepository; + + @DisplayName("콕 알림이 성공적으로 저장된다. - Void") + @Test + void saveKnock_success() { + // When + notificationRepository.saveKnock(1L, 1L, 1L); + + // Then + verify(valueRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); + } + + @DisplayName("콕 찌르는 사용자의 ID가 Null인 콕 알림을 저장한다. - NullPointerException") + @Test + void saveKnock_MemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.saveKnock(null, 1L, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("콕 찌를 대상의 ID가 Null인 콕 알림을 저장한다. - NullPointerException") + @Test + void saveKnock_TargetId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.saveKnock(1L, null, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("방 ID가 Null인 콕 알림을 저장한다. - NullPointerException") + @Test + void saveKnock_RoomId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.saveKnock(1L, 2L, null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("콕 알림 여부 체크를 성공적으로 확인한다. - Boolean") + @Test + void existsKnockByKey_success() { + // When + notificationRepository.existsKnockByKey(1L, 1L, 1L); + + // Then + verify(valueRedisRepository).hasKey(any(String.class)); + } + + @DisplayName("콕 찌르는 사용자의 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") + @Test + void existsKnockByKey_MemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(null, 1L, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("콕 찌를 상대 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") + @Test + void existsKnockByKey_TargetId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(1L, null, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("방 ID가 Null인 콕 알림 여부를 체크한다. - NullPointerException") + @Test + void existsKnockByKey_RoomId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> notificationRepository.existsKnockByKey(1L, 2L, null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/domain/payment/OrderTest.java b/src/test/java/com/moabam/api/domain/payment/OrderTest.java new file mode 100644 index 00000000..c84b2dfc --- /dev/null +++ b/src/test/java/com/moabam/api/domain/payment/OrderTest.java @@ -0,0 +1,24 @@ +package com.moabam.api.domain.payment; + +import static com.moabam.support.fixture.PaymentFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OrderTest { + + @DisplayName("주문 id를 갱신한다.") + @Test + void update_id_success() { + // given + Order order = order(bugProduct()); + + // when + order.updateId(ORDER_ID); + + // then + assertThat(order.getId()).isEqualTo(ORDER_ID); + } +} diff --git a/src/test/java/com/moabam/api/domain/payment/PaymentTest.java b/src/test/java/com/moabam/api/domain/payment/PaymentTest.java new file mode 100644 index 00000000..ede0ed7a --- /dev/null +++ b/src/test/java/com/moabam/api/domain/payment/PaymentTest.java @@ -0,0 +1,93 @@ +package com.moabam.api.domain.payment; + +import static com.moabam.support.fixture.CouponFixture.*; +import static com.moabam.support.fixture.PaymentFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.global.error.exception.BadRequestException; + +class PaymentTest { + + @DisplayName("금액이 음수이면 예외가 발생한다.") + @Test + void validate_amount_exception() { + Payment.PaymentBuilder paymentBuilder = Payment.builder() + .memberId(1L) + .product(bugProduct()) + .order(order(bugProduct())) + .totalAmount(-1000); + + assertThatThrownBy(paymentBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("결제 금액은 0 이상이어야 합니다."); + } + + @DisplayName("쿠폰을 적용한다.") + @Nested + class ApplyCoupon { + + @DisplayName("성공한다.") + @Test + void success() { + // given + Payment payment = payment(bugProduct()); + Coupon coupon = discount1000Coupon(); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + // when + payment.applyCoupon(couponWallet); + + // then + assertThat(payment.getTotalAmount()).isEqualTo(BUG_PRODUCT_PRICE - 1000); + assertThat(payment.getDiscountAmount()).isEqualTo(coupon.getPoint()); + } + + @DisplayName("할인 금액이 더 크면 0으로 처리한다.") + @Test + void discount_amount_greater() { + // given + Payment payment = payment(bugProduct()); + Coupon coupon = discount10000Coupon(); + CouponWallet couponWallet = CouponWallet.create(1L, coupon); + + // when + payment.applyCoupon(couponWallet); + + // then + assertThat(payment.getTotalAmount()).isZero(); + } + } + + @DisplayName("해당 회원의 결제 정보가 아니면 예외가 발생한다.") + @Test + void validate_by_member_exception() { + // given + Long memberId = 2L; + Payment payment = payment(bugProduct()); + + // when, then + assertThatThrownBy(() -> payment.validateByMember(memberId)) + .isInstanceOf(BadRequestException.class) + .hasMessage("해당 회원의 결제 정보가 아닙니다."); + } + + @DisplayName("결제를 요청한다.") + @Test + void request_success() { + // given + Payment payment = payment(bugProduct()); + + // when + payment.request(ORDER_ID); + + // then + assertThat(payment.getOrder().getId()).isEqualTo(ORDER_ID); + } +} diff --git a/src/test/java/com/moabam/api/domain/product/ProductTest.java b/src/test/java/com/moabam/api/domain/product/ProductTest.java new file mode 100644 index 00000000..4aa61a90 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/product/ProductTest.java @@ -0,0 +1,36 @@ +package com.moabam.api.domain.product; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.moabam.global.error.exception.BadRequestException; + +class ProductTest { + + @DisplayName("상품 가격이 0 보다 작으면 예외가 발생한다.") + @Test + void validate_price_exception() { + Product.ProductBuilder productBuilder = Product.builder() + .name("황금벌레 10") + .price(-10); + + assertThatThrownBy(productBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("가격은 0 이상이어야 합니다."); + } + + @DisplayName("상품량이 1 보다 작으면 예외가 발생한다.") + @Test + void validate_quantity_exception() { + Product.ProductBuilder productBuilder = Product.builder() + .name("황금벌레 10") + .price(1000) + .quantity(-1); + + assertThatThrownBy(productBuilder::build) + .isInstanceOf(BadRequestException.class) + .hasMessage("수량은 1 이상이어야 합니다."); + } +} diff --git a/src/test/java/com/moabam/api/domain/room/CertificationTest.java b/src/test/java/com/moabam/api/domain/room/CertificationTest.java new file mode 100644 index 00000000..338bbc23 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/room/CertificationTest.java @@ -0,0 +1,39 @@ +package com.moabam.api.domain.room; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CertificationTest { + + String content = "물 마시기"; + String image = "https://s3.testtest"; + + @DisplayName("Certification 생성 성공") + @Test + void create_certification_success() { + Room room = Room.builder() + .title("앵윤이의 방") + .roomType(RoomType.MORNING) + .certifyTime(10) + .maxUserCount(9) + .build(); + + Routine routine = Routine.builder() + .room(room) + .content(content) + .build(); + + assertThatNoException().isThrownBy(() -> { + Certification certification = Certification.builder() + .routine(routine) + .memberId(1L) + .image(image).build(); + + assertThat(certification.getImage()).isEqualTo(image); + assertThat(certification.getMemberId()).isEqualTo(1L); + assertThat(certification.getRoutine()).isEqualTo(routine); + }); + } +} diff --git a/src/test/java/com/moabam/api/domain/room/RoomTest.java b/src/test/java/com/moabam/api/domain/room/RoomTest.java new file mode 100644 index 00000000..72e350df --- /dev/null +++ b/src/test/java/com/moabam/api/domain/room/RoomTest.java @@ -0,0 +1,111 @@ +package com.moabam.api.domain.room; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.fixture.RoomFixture; + +class RoomTest { + + @DisplayName("비밀번호 없이 방 생성 성공") + @Test + void create_room_without_password_success() { + // given, when + Room room = Room.builder() + .title("앵윤이의 방") + .roomType(RoomType.MORNING) + .certifyTime(10) + .maxUserCount(9) + .build(); + + // then + assertThat(room.getPassword()).isNull(); + assertThat(room.getRoomImage()).isEqualTo("https://image.moabam.com/moabam/default/room-level-00.png"); + assertThat(room.getRoomType()).isEqualTo(RoomType.MORNING); + assertThat(room.getCertifyTime()).isEqualTo(10); + assertThat(room.getMaxUserCount()).isEqualTo(9); + assertThat(room.getLevel()).isZero(); + assertThat(room.getCurrentUserCount()).isEqualTo(1); + assertThat(room.getAnnouncement()).isNull(); + } + + @DisplayName("비밀번호 설정 후 방 생성 성공") + @Test + void create_room_with_password_success() { + // given, when + Room room = Room.builder() + .title("앵윤이의 방") + .password("12345") + .roomType(RoomType.MORNING) + .certifyTime(10) + .maxUserCount(9) + .build(); + + // then + assertThat(room.getPassword()).isEqualTo("12345"); + } + + @DisplayName("아침 방 설정 시, 저녁 시간이 들어오는 예외 발생") + @ParameterizedTest + @CsvSource({ + "13", "19", "3", "11", "0" + }) + void morning_time_validate_exception(int certifyTime) { + Room room = Room.builder() + .title("모아밤 짱") + .password("1234") + .roomType(RoomType.MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + // given, when, then + assertThatThrownBy(() -> room.changeCertifyTime(certifyTime)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_REQUEST_FIELD.getMessage()); + } + + @DisplayName("저녁 방 설정 시, 아침 시간이 들어오는 경우 예외 발생") + @ParameterizedTest + @CsvSource({ + "3", "5", "-1", "15", "8", "19" + }) + void night_time_validate_exception(int certifyTime) { + Room room = Room.builder() + .title("모아밤 짱") + .roomType(RoomType.NIGHT) + .certifyTime(21) + .maxUserCount(5) + .build(); + + // given, when, then + assertThatThrownBy(() -> room.changeCertifyTime(certifyTime)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_REQUEST_FIELD.getMessage()); + } + + @DisplayName("레벨에 따른 이미지 업데이트") + @ParameterizedTest + @CsvSource({ + "1, https://image.moabam.com/moabam/default/room-level-01.png", + "2, https://image.moabam.com/moabam/default/room-level-02.png", + "3, https://image.moabam.com/moabam/default/room-level-03.png", + "4, https://image.moabam.com/moabam/default/room-level-04.png", + }) + void update_room_image_success(int level, String image) { + // given + Room room = RoomFixture.room(); + + // when + room.upgradeRoomImage(level); + + // then + assertThat(room.getRoomImage()).isEqualTo(image); + } +} diff --git a/src/test/java/com/moabam/api/domain/room/repository/CertificationsSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/room/repository/CertificationsSearchRepositoryTest.java new file mode 100644 index 00000000..27ca807a --- /dev/null +++ b/src/test/java/com/moabam/api/domain/room/repository/CertificationsSearchRepositoryTest.java @@ -0,0 +1,109 @@ +package com.moabam.api.domain.room.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.support.annotation.QuerydslRepositoryTest; +import com.moabam.support.fixture.RoomFixture; + +@QuerydslRepositoryTest +class CertificationsSearchRepositoryTest { + + @Autowired + private CertificationsSearchRepository certificationsSearchRepository; + + @Autowired + private CertificationRepository certificationRepository; + + @Autowired + private DailyMemberCertificationRepository dailyMemberCertificationRepository; + + @Autowired + private DailyRoomCertificationRepository dailyRoomCertificationRepository; + + @Autowired + private RoomRepository roomRepository; + + @Autowired + private RoutineRepository routineRepository; + + @Autowired + private ParticipantRepository participantRepository; + + @DisplayName("방에서 당일 유저들의 인증 조회") + @Test + void find_certifications_test() { + // given + Room room = RoomFixture.room(); + List routines = RoomFixture.routines(room); + Certification certification1 = RoomFixture.certification(routines.get(0)); + Certification certification2 = RoomFixture.certification(routines.get(1)); + + Room savedRoom = roomRepository.save(room); + routineRepository.save(routines.get(0)); + routineRepository.save(routines.get(1)); + certificationRepository.save(certification1); + certificationRepository.save(certification2); + + // when + List actual = certificationsSearchRepository.findCertifications(savedRoom.getId(), + LocalDate.now()); + + //then + assertThat(actual).hasSize(2) + .containsExactly(certification1, certification2); + } + + @DisplayName("당일 유저가 특정 방에서 인증 여부 조회") + @Test + void find_daily_member_certification() { + // given + Room room = roomRepository.save(RoomFixture.room()); + Participant participant = participantRepository.save(RoomFixture.participant(room, 1L)); + + DailyMemberCertification dailyMemberCertification = RoomFixture.dailyMemberCertification(1L, + room.getId(), participant); + dailyMemberCertificationRepository.save(dailyMemberCertification); + + // when + Optional actual = certificationsSearchRepository.findDailyMemberCertification(1L, + room.getId(), LocalDate.now()); + + // then + assertThat(actual) + .isPresent() + .contains(dailyMemberCertification); + } + + @DisplayName("당일 방의 인증 여부 조회") + @Test + void find_daily_room_certification() { + // given + Room room = roomRepository.save(RoomFixture.room()); + DailyRoomCertification dailyRoomCertification = RoomFixture.dailyRoomCertification(room.getId(), + LocalDate.now()); + dailyRoomCertificationRepository.save(dailyRoomCertification); + + // when + Optional actual = certificationsSearchRepository.findDailyRoomCertification( + room.getId(), LocalDate.now()); + + // then + assertThat(actual) + .isPresent() + .contains(dailyRoomCertification); + } +} diff --git a/src/test/java/com/moabam/api/domain/room/repository/ParticipantSearchRepositoryTest.java b/src/test/java/com/moabam/api/domain/room/repository/ParticipantSearchRepositoryTest.java new file mode 100644 index 00000000..82937bf5 --- /dev/null +++ b/src/test/java/com/moabam/api/domain/room/repository/ParticipantSearchRepositoryTest.java @@ -0,0 +1,48 @@ +package com.moabam.api.domain.room.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.global.config.JpaConfig; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@Import({JpaConfig.class, ParticipantSearchRepository.class}) +class ParticipantSearchRepositoryTest { + + @Autowired + private ParticipantSearchRepository participantSearchRepository; + + @Autowired + private ParticipantRepository participantRepository; + + @Autowired + private RoomRepository roomRepository; + + @DisplayName("인증 시간에 따른 참여자 조회를 성공적으로 했을 때, - List") + @MethodSource("com.moabam.support.fixture.ParticipantFixture#provideRoomAndParticipants") + @ParameterizedTest + void participantSearchRepository_findAllByRoomCertifyTime(Room room, List participants) { + // Given + roomRepository.save(room); + participantRepository.saveAll(participants); + + // When + List actual = participantSearchRepository.findAllByRoomCertifyTime(10); + + // Then + assertThat(actual).hasSize(5); + } +} diff --git a/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java b/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java new file mode 100644 index 00000000..3454611b --- /dev/null +++ b/src/test/java/com/moabam/api/dto/coupon/CreateCouponRequestTest.java @@ -0,0 +1,31 @@ +package com.moabam.api.dto.coupon; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +class CreateCouponRequestTest { + + @DisplayName("쿠폰 발급 가능 시작 날짜가 올바른 형식으로 입력된다. - yyyy-MM-dd") + @Test + void startAt_success() throws JsonProcessingException { + // Given + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + + String json = "{\"startAt\":\"2023-11-09\"}"; + + // When + CreateCouponRequest actual = objectMapper.readValue(json, CreateCouponRequest.class); + + // Then + assertThat(actual.startAt()).isEqualTo(LocalDate.of(2023, 11, 9)); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java new file mode 100644 index 00000000..6eed5267 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/fcm/FcmRepositoryTest.java @@ -0,0 +1,88 @@ +package com.moabam.api.infrastructure.fcm; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Duration; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.infrastructure.redis.ValueRedisRepository; + +@ExtendWith(MockitoExtension.class) +class FcmRepositoryTest { + + @InjectMocks + FcmRepository fcmRepository; + + @Mock + ValueRedisRepository valueRedisRepository; + + @DisplayName("FCM 토큰이 성공적으로 저장된다. - Void") + @Test + void saveToken_success() { + // When + fcmRepository.saveToken("FCM-TOKEN", 1L); + + // Then + verify(valueRedisRepository).save(any(String.class), any(String.class), any(Duration.class)); + } + + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 저장한다. - NullPointerException") + @Test + void saveToken_MemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> fcmRepository.saveToken("FCM-TOKEN", null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("토큰이 Null인 FCM 토큰을 저장한다. - NullPointerException") + @Test + void saveToken_FcmToken_NullPointerException() { + // When & Then + assertThatThrownBy(() -> fcmRepository.saveToken(null, 1L)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("FCM 토큰이 성공적으로 삭제된다. - Void") + @Test + void deleteTokenByMemberId_success() { + // When + fcmRepository.deleteTokenByMemberId(1L); + + // Then + verify(valueRedisRepository).delete(any(String.class)); + } + + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 삭제한다. - NullPointerException") + @Test + void deleteTokenByMemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> fcmRepository.deleteTokenByMemberId(null)) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("FCM 토큰을 성공적으로 조회된다. - (String) FCM TOKEN") + @Test + void findTokenByMemberId_success() { + // When + fcmRepository.findTokenByMemberId(1L); + + // Then + verify(valueRedisRepository).get(any(String.class)); + } + + @DisplayName("ID가 Null인 사용자가 FCM 토큰을 조회한다. - NullPointerException") + @Test + void findTokenByMemberId_NullPointerException() { + // When & Then + assertThatThrownBy(() -> fcmRepository.findTokenByMemberId(null)) + .isInstanceOf(NullPointerException.class); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java b/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java new file mode 100644 index 00000000..9720b843 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/fcm/FcmServiceTest.java @@ -0,0 +1,97 @@ +package com.moabam.api.infrastructure.fcm; + +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import com.moabam.global.config.FcmConfig; +import com.moabam.support.common.WithoutFilterSupporter; + +@SpringBootTest(classes = {FcmConfig.class, FcmService.class}) +class FcmServiceTest extends WithoutFilterSupporter { + + @Autowired + FcmService fcmService; + + @MockBean + FirebaseMessaging firebaseMessaging; + + @MockBean + FcmRepository fcmRepository; + + @DisplayName("FCM 토큰이 성공적으로 저장된다. - Void") + @Test + void saveToken_success() { + // When + fcmService.createToken("FCM-TOKEN", 1L); + + // Then + verify(fcmRepository).saveToken(any(String.class), any(Long.class)); + } + + @DisplayName("FCM 토큰으로 빈값이 넘어와 아무일도 일어나지 않는다. - Void") + @Test + void saveToken_Blank() { + // When + fcmService.createToken("", 1L); + + // Then + verify(fcmRepository, times(0)).saveToken(any(String.class), any(Long.class)); + } + + @DisplayName("FCM 토큰으로 null이 넘어와 아무일도 일어나지 않는다. - Void") + @Test + void saveToken_Null() { + // When + fcmService.createToken(null, 1L); + + // Then + verify(fcmRepository, times(0)).saveToken(any(String.class), any(Long.class)); + } + + @DisplayName("FCM 토큰이 성공적으로 삭제된다. - Void") + @Test + void deleteTokenByMemberId_success() { + // When + fcmRepository.deleteTokenByMemberId(1L); + + // Then + verify(fcmRepository).deleteTokenByMemberId(any(Long.class)); + } + + @DisplayName("FCM 토큰을 성공적으로 조회된다. - (String) FCM TOKEN") + @Test + void findTokenByMemberId_success() { + // When + fcmRepository.findTokenByMemberId(1L); + + // Then + verify(fcmRepository).findTokenByMemberId(any(Long.class)); + } + + @DisplayName("비동기 FCM 알림을 성공적으로 보낸다. - Void") + @Test + void sendAsync_success() { + // When + fcmService.sendAsync("FCM-TOKEN", "title", "body"); + + // Then + verify(firebaseMessaging).sendAsync(any(Message.class)); + } + + @DisplayName("FCM 토큰이 null이여서 비동기 FCM 알림을 보내지 않는다. - Void") + @Test + void sendAsync_null() { + // When + fcmService.sendAsync(null, "titile", "body"); + + // Then + verify(firebaseMessaging, times(0)).sendAsync(any(Message.class)); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/payment/TossPaymentServiceTest.java b/src/test/java/com/moabam/api/infrastructure/payment/TossPaymentServiceTest.java new file mode 100644 index 00000000..10617019 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/payment/TossPaymentServiceTest.java @@ -0,0 +1,92 @@ +package com.moabam.api.infrastructure.payment; + +import static com.moabam.support.fixture.PaymentFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.ConfirmTossPaymentResponse; +import com.moabam.global.config.TossPaymentConfig; +import com.moabam.global.error.exception.MoabamException; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; + +@SpringBootTest +@ActiveProfiles("test") +class TossPaymentServiceTest { + + @Autowired + TossPaymentConfig config; + + @Autowired + ObjectMapper objectMapper; + + TossPaymentService tossPaymentService; + MockWebServer mockWebServer; + + @BeforeEach + public void setup() { + mockWebServer = new MockWebServer(); + tossPaymentService = new TossPaymentService( + new TossPaymentConfig(mockWebServer.url("/").toString(), config.secretKey()) + ); + tossPaymentService.init(); + } + + @AfterEach + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + + @DisplayName("결제 승인을 요청한다.") + @Nested + class Confirm { + + @DisplayName("성공한다.") + @Test + void success() throws Exception { + // given + ConfirmPaymentRequest request = confirmPaymentRequest(); + ConfirmTossPaymentResponse expected = confirmTossPaymentResponse(); + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setBody(objectMapper.writeValueAsString(expected)) + .addHeader("Content-Type", "application/json")); + + // when + ConfirmTossPaymentResponse actual = tossPaymentService.confirm(request); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("예외가 발생한다.") + @Test + void exception() { + // given + ConfirmPaymentRequest request = confirmPaymentRequest(); + String jsonString = "{\"code\":\"NOT_FOUND_PAYMENT\",\"message\":\"존재하지 않는 결제 입니다.\"}"; + mockWebServer.enqueue(new MockResponse() + .setResponseCode(404) + .setBody(jsonString) + .addHeader("Content-Type", "application/json")); + + // when, then + assertThatThrownBy(() -> tossPaymentService.confirm(request)) + .isInstanceOf(MoabamException.class) + .hasMessage("존재하지 않는 결제 입니다."); + } + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/redis/HashRedisRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/redis/HashRedisRepositoryTest.java new file mode 100644 index 00000000..ab0cd833 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/redis/HashRedisRepositoryTest.java @@ -0,0 +1,77 @@ +package com.moabam.api.infrastructure.redis; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Duration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.global.config.EmbeddedRedisConfig; +import com.moabam.global.config.RedisConfig; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.fixture.TokenSaveValueFixture; + +@SpringBootTest(classes = {RedisConfig.class, EmbeddedRedisConfig.class, HashRedisRepository.class}) +class HashRedisRepositoryTest { + + @Autowired + private HashRedisRepository hashRedisRepository; + + String key = "auth_123"; + String token = "token"; + String ip = "ip"; + TokenSaveValue tokenSaveValue = TokenSaveValueFixture.tokenSaveValue(token, ip); + Duration duration = Duration.ofMillis(5000); + + @BeforeEach + void setUp() { + hashRedisRepository.save(key, (Object)tokenSaveValue, duration); + } + + @AfterEach + void delete() { + hashRedisRepository.delete(key); + } + + @DisplayName("레디스에 hash 저장 성공") + @Test + void hashTemplate_repository_save_success() { + // Given + When + TokenSaveValue object = (TokenSaveValue)hashRedisRepository.get(key); + + // Then + assertAll( + () -> assertThat(object.refreshToken()).isEqualTo(token), + () -> assertThat(object.loginIp()).isEqualTo(ip) + ); + } + + @DisplayName("삭제 성공 테스트") + @Test + void delete_and_get_null() { + // Given + hashRedisRepository.delete(key); + + // When + Then + assertThatThrownBy(() -> hashRedisRepository.get(key)) + .isInstanceOf(UnauthorizedException.class) + .hasMessage(ErrorMessage.AUTHENTICATE_FAIL.getMessage()); + } + + @DisplayName("토큰이 null 이어서 예외 발생") + @Test + void valid_token_failby_token_is_null() { + // Given + When + Then + assertThatThrownBy(() -> hashRedisRepository.get("0")) + .isInstanceOf(UnauthorizedException.class) + .hasMessage(ErrorMessage.AUTHENTICATE_FAIL.getMessage()); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/redis/TokenRepostiroyTest.java b/src/test/java/com/moabam/api/infrastructure/redis/TokenRepostiroyTest.java new file mode 100644 index 00000000..006d392e --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/redis/TokenRepostiroyTest.java @@ -0,0 +1,66 @@ +package com.moabam.api.infrastructure.redis; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.Duration; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.moabam.api.domain.auth.repository.TokenRepository; +import com.moabam.api.domain.member.Role; +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.support.fixture.TokenSaveValueFixture; + +@ExtendWith(MockitoExtension.class) +class TokenRepostiroyTest { + + @InjectMocks + TokenRepository tokenRepository; + + @Mock + HashRedisRepository hashRedisRepository; + + @DisplayName("토큰 저장 성공") + @Test + void save_token_suceess() { + // Given + willDoNothing().given(hashRedisRepository).save(any(), any(TokenSaveValue.class), any(Duration.class)); + + // When + Then + Assertions.assertThatNoException() + .isThrownBy(() -> tokenRepository.saveToken(1L, TokenSaveValueFixture.tokenSaveValue(), Role.USER)); + } + + @DisplayName("토큰 조회 성공") + @Test + void token_get_success() { + // given + willReturn(TokenSaveValueFixture.tokenSaveValue("token")) + .given(hashRedisRepository).get(anyString()); + + // when + TokenSaveValue tokenSaveValue = tokenRepository.getTokenSaveValue(123L, Role.USER); + + // then + assertAll( + () -> assertThat(tokenSaveValue).isNotNull(), + () -> assertThat(tokenSaveValue.refreshToken()).isEqualTo("token") + ); + } + + @DisplayName("토큰 저장 삭제") + @Test + void delete_token_suceess() { + // When + Then + Assertions.assertThatNoException() + .isThrownBy(() -> tokenRepository.delete(1L, Role.USER)); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java new file mode 100644 index 00000000..7be74a87 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/redis/ValueRedisRepositoryTest.java @@ -0,0 +1,86 @@ +package com.moabam.api.infrastructure.redis; + +import static org.assertj.core.api.Assertions.*; + +import java.time.Duration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import com.moabam.global.config.EmbeddedRedisConfig; + +@SpringBootTest(classes = {EmbeddedRedisConfig.class, ValueRedisRepository.class}) +class ValueRedisRepositoryTest { + + @Autowired + ValueRedisRepository valueRedisRepository; + + String key = "key"; + String value = "value"; + String stockKey = "key_INCR"; + Duration duration = Duration.ofMillis(5000); + + @BeforeEach + void setUp() { + valueRedisRepository.save(key, value, duration); + } + + @AfterEach + void setDown() { + if (valueRedisRepository.hasKey(key)) { + valueRedisRepository.delete(key); + } + + if (valueRedisRepository.hasKey(stockKey)) { + valueRedisRepository.delete(stockKey); + } + } + + @DisplayName("레디스에 문자열 데이터가 성공적으로 저장된다. - Void") + @Test + void save_success() { + // Then + assertThat(valueRedisRepository.get(key)).isEqualTo(value); + } + + @DisplayName("레디스의 특정 데이터가 성공적으로 조회된다. - String(Value)") + @Test + void get_success() { + // When + String actual = valueRedisRepository.get(key); + + // Then + assertThat(actual).isEqualTo(valueRedisRepository.get(key)); + } + + @DisplayName("레디스의 특정 데이터 존재 여부를 성공적으로 체크한다. - Boolean") + @Test + void hasKey_success() { + // When & Then + assertThat(valueRedisRepository.hasKey("not found key")).isFalse(); + } + + @DisplayName("레디스의 특정 데이터가 성공적으로 삭제된다. - Void") + @Test + void delete_success() { + // When + valueRedisRepository.delete(key); + + // Then + assertThat(valueRedisRepository.hasKey(key)).isFalse(); + } + + @DisplayName("레디스의 특정 데이터의 값이 1 증가한다.") + @Test + void increment_success() { + // When + Long actual = valueRedisRepository.increment(stockKey, 1); + + // Then + assertThat(actual).isEqualTo(1L); + } +} diff --git a/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java b/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java new file mode 100644 index 00000000..072dcf25 --- /dev/null +++ b/src/test/java/com/moabam/api/infrastructure/redis/ZSetRedisRepositoryTest.java @@ -0,0 +1,111 @@ +package com.moabam.api.infrastructure.redis; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Set; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; + +import com.moabam.global.config.EmbeddedRedisConfig; + +@SpringBootTest(classes = {EmbeddedRedisConfig.class, ZSetRedisRepository.class, ValueRedisRepository.class}) +class ZSetRedisRepositoryTest { + + @Autowired + ZSetRedisRepository zSetRedisRepository; + + @Autowired + ValueRedisRepository valueRedisRepository; + + @Autowired + RedisTemplate redisTemplate; + + String key = "key"; + Long value = 1L; + int expireDays = 2; + + @AfterEach + void afterEach() { + if (valueRedisRepository.hasKey(key)) { + valueRedisRepository.delete(key); + } + } + + @Disabled + @DisplayName("레디스의 SortedSet 데이터가 성공적으로 저장된다. - Void") + @Test + void addIfAbsent_success() { + // When + zSetRedisRepository.addIfAbsent(key, value, 1, expireDays); + + // Then + assertThat(valueRedisRepository.hasKey(key)).isTrue(); + } + + @Disabled + @DisplayName("이미 존재하는 값을 한 번 더 저장을 시도한다. - Void") + @Test + void setRedisRepository_addIfAbsent_not_update() { + // When + zSetRedisRepository.addIfAbsent(key, value, 1, expireDays); + zSetRedisRepository.addIfAbsent(key, value, 5, expireDays); + + // Then + assertThat(redisTemplate.opsForZSet().score(key, value)).isEqualTo(1); + } + + @Disabled + @DisplayName("저장된 데이터와 동일한 갯수만큼 조회한다. - Set") + @Test + void range_same_success() { + // Given + zSetRedisRepository.addIfAbsent(key, value + 1, 1, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 2, 2, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 3, 3, expireDays); + + // When + Set actual = zSetRedisRepository.range(key, 0, 3); + + // Then + assertThat(actual).hasSize(3); + } + + @Disabled + @DisplayName("저장된 데이터보다 많은 갯수만큼 조회한다. - Set") + @Test + void range_more_success() { + // Given + zSetRedisRepository.addIfAbsent(key, value + 1, 1, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 2, 2, expireDays); + + // When + Set actual = zSetRedisRepository.range(key, 0, 3); + + // Then + assertThat(actual).hasSize(2); + } + + @Disabled + @DisplayName("저장된 데이터보다 더 적은 갯수만큼 조회한다. - Set") + @Test + void range_less_success() { + // Given + zSetRedisRepository.addIfAbsent(key, value + 1, 1, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 2, 2, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 3, 3, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 4, 4, expireDays); + zSetRedisRepository.addIfAbsent(key, value + 5, 5, expireDays); + + // When + Set actual = zSetRedisRepository.range(key, 0, 3); + + // Then + assertThat(actual).hasSize(3); + } +} diff --git a/src/test/java/com/moabam/api/presentation/BugControllerTest.java b/src/test/java/com/moabam/api/presentation/BugControllerTest.java new file mode 100644 index 00000000..c7887beb --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/BugControllerTest.java @@ -0,0 +1,190 @@ +package com.moabam.api.presentation; + +import static com.moabam.global.auth.model.AuthorizationThreadLocal.*; +import static com.moabam.support.fixture.BugFixture.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static com.moabam.support.fixture.PaymentFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static java.nio.charset.StandardCharsets.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.http.MediaType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.bug.BugMapper; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.application.product.ProductMapper; +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.bug.repository.BugHistoryRepository; +import com.moabam.api.domain.bug.repository.BugHistorySearchRepository; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.repository.ProductRepository; +import com.moabam.api.dto.bug.BugHistoryResponse; +import com.moabam.api.dto.bug.BugResponse; +import com.moabam.api.dto.product.ProductsResponse; +import com.moabam.api.dto.product.PurchaseProductRequest; +import com.moabam.api.dto.product.PurchaseProductResponse; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +class BugControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + MemberService memberService; + + @Autowired + BugHistoryRepository bugHistoryRepository; + + @Autowired + BugHistorySearchRepository bugHistorySearchRepository; + + @Autowired + ProductRepository productRepository; + + @Autowired + PaymentRepository paymentRepository; + + @DisplayName("벌레를 조회한다.") + @WithMember + @Test + void get_bug_success() throws Exception { + // given + Long memberId = getAuthMember().id(); + BugResponse expected = BugMapper.toBugResponse(bug()); + given(memberService.findMember(memberId)).willReturn(member()); + + // expected + String content = mockMvc.perform(get("/bugs") + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + BugResponse actual = objectMapper.readValue(content, BugResponse.class); + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("벌레 내역을 조회한다.") + @Nested + class GetBugHistory { + + @DisplayName("성공한다.") + @WithMember + @Test + void success() throws Exception { + // given + Long memberId = getAuthMember().id(); + bugHistoryRepository.save(rewardMorningBugHistory(memberId)); + + // expected + String content = mockMvc.perform(get("/bugs/history") + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + BugHistoryResponse actual = objectMapper.readValue(content, BugHistoryResponse.class); + assertThat(actual.history().get(0).bugType()).isEqualTo(BugType.MORNING); + assertThat(actual.history().get(0).actionType()).isEqualTo(BugActionType.REWARD); + assertThat(actual.history().get(0).quantity()).isEqualTo(REWARD_MORNING_BUG); + assertThat(actual.history().get(0).payment()).isNull(); + } + + @DisplayName("벌레 충전 내역인 경우 결제 정보를 포함한다.") + @WithMember + @Test + void charge_success() throws Exception { + // given + Long memberId = getAuthMember().id(); + Product product = productRepository.save(bugProduct()); + Payment payment = paymentRepository.save(payment(product)); + bugHistoryRepository.save(chargeGoldenBugHistory(memberId, payment)); + + // expected + String content = mockMvc.perform(get("/bugs/history") + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + BugHistoryResponse actual = objectMapper.readValue(content, BugHistoryResponse.class); + assertThat(actual.history().get(0).bugType()).isEqualTo(BugType.GOLDEN); + assertThat(actual.history().get(0).actionType()).isEqualTo(BugActionType.CHARGE); + assertThat(actual.history().get(0).quantity()).isEqualTo(BUG_PRODUCT_QUANTITY); + assertThat(actual.history().get(0).payment().orderName()).isEqualTo(BUG_PRODUCT_NAME); + assertThat(actual.history().get(0).payment().totalAmount()).isEqualTo(BUG_PRODUCT_PRICE); + assertThat(actual.history().get(0).payment().discountAmount()).isZero(); + } + } + + @DisplayName("벌레 상품 목록을 조회한다.") + @Test + void get_bug_products_success() throws Exception { + // given + List products = productRepository.saveAll(List.of(bugProduct(), bugProduct())); + ProductsResponse expected = ProductMapper.toProductsResponse(products); + + // expected + String content = mockMvc.perform(get("/bugs/products") + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + ProductsResponse actual = objectMapper.readValue(content, ProductsResponse.class); + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("벌레 상품을 구매한다.") + @WithMember + @Test + void purchase_bug_product_success() throws Exception { + // given + Product product = productRepository.save(bugProduct()); + PurchaseProductRequest request = new PurchaseProductRequest(null); + + // expected + String content = mockMvc.perform(post("/bugs/products/{productId}/purchase", product.getId()) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + PurchaseProductResponse actual = objectMapper.readValue(content, PurchaseProductResponse.class); + assertThat(actual.orderName()).isEqualTo(BUG_PRODUCT_NAME); + assertThat(actual.price()).isEqualTo(BUG_PRODUCT_PRICE); + } +} diff --git a/src/test/java/com/moabam/api/presentation/CouponControllerTest.java b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java new file mode 100644 index 00000000..65f25723 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/CouponControllerTest.java @@ -0,0 +1,518 @@ +package com.moabam.api.presentation; + +import static org.hamcrest.Matchers.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.coupon.CouponMapper; +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.domain.coupon.CouponWallet; +import com.moabam.api.domain.coupon.repository.CouponRepository; +import com.moabam.api.domain.coupon.repository.CouponWalletRepository; +import com.moabam.api.domain.member.Role; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.api.dto.coupon.CreateCouponRequest; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.api.infrastructure.redis.ZSetRedisRepository; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.CouponFixture; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.snippet.CouponSnippet; +import com.moabam.support.snippet.CouponWalletSnippet; +import com.moabam.support.snippet.ErrorSnippet; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureRestDocs +class CouponControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + MemberRepository memberRepository; + + @Autowired + CouponRepository couponRepository; + + @Autowired + CouponWalletRepository couponWalletRepository; + + @MockBean + ClockHolder clockHolder; + + @MockBean + ZSetRedisRepository zSetRedisRepository; + + @MockBean + ValueRedisRepository valueRedisRepository; + + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 쿠폰을 성공적으로 발행한다. - Void") + @Test + void create_Coupon_success() throws Exception { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST)) + .andExpect(status().isCreated()); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 현재 날짜가 쿠폰 발급 가능 날짜와 같거나 이후이다. - BadRequestException") + @Test + void create_Coupon_StartAt_BadRequestException() throws Exception { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + + given(clockHolder.date()).willReturn(LocalDate.of(2025, 1, 1)); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_START_AT_PERIOD.getMessage())); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 쿠폰 정보 오픈 날짜가 쿠폰 발급 시작 날짜와 같거나 이후인 쿠폰을 발행한다. - BadRequestException") + @Test + void create_Coupon_OpenAt_BadRequestException() throws Exception { + // Given + String couponType = CouponType.GOLDEN.getName(); + CreateCouponRequest request = CouponFixture.createCouponRequest(couponType, 1, 1); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_OPEN_AT_PERIOD.getMessage())); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 쿠폰명이 중복된 쿠폰을 발행한다. - ConflictException") + @Test + void create_Coupon_Name_ConflictException() throws Exception { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + couponRepository.save(CouponMapper.toEntity(1L, request)); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_COUPON_NAME.getMessage())); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("POST - 쿠폰 발행 가능 날짜가 중복된 쿠폰을 발행한다. - ConflictException") + @Test + void create_Coupon_StartAt_ConflictException() throws Exception { + // Given + CreateCouponRequest request = CouponFixture.createCouponRequest(); + Coupon conflictStartAtCoupon = CouponFixture.coupon("NotConflictName", 2, 1); + couponRepository.save(conflictStartAtCoupon); + + // When & Then + mockMvc.perform(post("/admins/coupons") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("admins/coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.CREATE_COUPON_REQUEST, + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_COUPON_START_AT.getMessage())); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("DELETE - 쿠폰을 성공적으로 삭제한다. - Void") + @Test + void delete_Coupon_success() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon(10, 100)); + + // When & Then + mockMvc.perform(delete("/admins/coupons/" + coupon.getId())) + .andDo(print()) + .andDo(document("admins/coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember(role = Role.ADMIN) + @DisplayName("DELETE - 존재하지 않는 쿠폰을 삭제한다. - NotFoundException") + @Test + void delete_Coupon_NotFoundException() throws Exception { + // When & Then + mockMvc.perform(delete("/admins/coupons/77777777777")) + .andDo(print()) + .andDo(document("admins/coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON.getMessage())); + } + + @DisplayName("GET - 특정 쿠폰을 성공적으로 조회한다. - CouponResponse") + @Test + void getById_Coupon_success() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon(10, 100)); + + // When & Then + mockMvc.perform(get("/coupons/" + coupon.getId())) + .andDo(print()) + .andDo(document("coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.COUPON_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(coupon.getId())); + } + + @DisplayName("GET - 존재하지 않는 쿠폰을 조회한다. - NotFoundException") + @Test + void getById_Coupon_NotFoundException() throws Exception { + // When & Then + mockMvc.perform(get("/coupons/77777777777")) + .andDo(print()) + .andDo(document("coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON.getMessage())); + } + + @DisplayName("POST - 모든 쿠폰을 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void getAllByStatus_Coupons_success(List coupons) throws Exception { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(true, true); + List coupon = couponRepository.saveAll(coupons); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + mockMvc.perform(post("/coupons/search") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("coupons/search", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.COUPON_STATUS_REQUEST, + CouponSnippet.COUPON_STATUS_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(coupon.size()))); + } + + @DisplayName("POST - 발급 가능한 쿠폰만 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponFixture#provideCoupons") + @ParameterizedTest + void getAllByStatus_Coupon_success(List coupons) throws Exception { + // Given + CouponStatusRequest request = CouponFixture.couponStatusRequest(false, false); + couponRepository.saveAll(coupons); + + given(clockHolder.date()).willReturn(LocalDate.of(2023, 3, 1)); + + // When & Then + mockMvc.perform(post("/coupons/search") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("coupons/search", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponSnippet.COUPON_STATUS_REQUEST)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(1))); + } + + @WithMember + @DisplayName("GET - 나의 쿠폰함에서 특정 쿠폰을 조회한다. - List") + @Test + void getAllByWalletIdAndMemberId_success() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // When & Then + mockMvc.perform(get("/my-coupons/" + couponWallet.getId())) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponWalletSnippet.COUPON_WALLET_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].id").value(coupon.getId())) + .andExpect(jsonPath("$[0].name").value(coupon.getName())); + } + + @WithMember + @DisplayName("GET - 나의 쿠폰 보관함에 있는 모든 쿠폰을 조회한다. - List") + @MethodSource("com.moabam.support.fixture.CouponWalletFixture#provideCouponWalletByCouponId1_total5") + @ParameterizedTest + void getAllByWalletIdAndMemberId_all_success(List couponWallets) throws Exception { + // Given + couponWallets.forEach(couponWallet -> { + Coupon coupon = couponRepository.save(couponWallet.getCoupon()); + couponWalletRepository.save(CouponWallet.create(1L, coupon)); + }); + + // When & Then + mockMvc.perform(get("/my-coupons")) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + CouponWalletSnippet.COUPON_WALLET_RESPONSE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(couponWallets.size()))); + } + + @WithMember + @DisplayName("GET - 쿠폰이 없는 사용자의 쿠폰함을 조회한다. - List") + @Test + void getAllByWalletIdAndMemberId_no_coupon() throws Exception { + // When & Then + mockMvc.perform(get("/my-coupons")) + .andDo(print()) + .andDo(document("my-coupons/couponId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(0))); + } + + @WithMember + @DisplayName("POST - 특정 회원이 보유한 쿠폰을 성공적으로 사용한다. - Void") + @Test + void use_success() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + memberRepository.save(MemberFixture.member(1L)); + + // When & Then + mockMvc.perform(post("/my-coupons/" + couponWallet.getId())) + .andDo(print()) + .andDo(document("my-coupons/couponWalletId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember + @DisplayName("POST - 특정 회원이 보유하지 않은 쿠폰을 사용한다. - NotFoundException") + @Test + void use_NotFoundException() throws Exception { + // When & Then + mockMvc.perform(post("/my-coupons/" + 777L)) + .andDo(print()) + .andDo(document("my-coupons/couponWalletId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_COUPON_WALLET.getMessage())); + ; + } + + @WithMember + @DisplayName("POST - 특정 회원이 보유한 할인 쿠폰을 사용한다. - BadRequestException") + @Test + void use_BadRequestException() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon(CouponType.DISCOUNT, 1000)); + CouponWallet couponWallet = couponWalletRepository.save(CouponWallet.create(1L, coupon)); + + // When & Then + mockMvc.perform(post("/my-coupons/" + couponWallet.getId())) + .andDo(print()) + .andDo(document("my-coupons/couponWalletId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_DISCOUNT_COUPON.getMessage())); + } + + @WithMember + @DisplayName("POST - 쿠폰 발급을 성공적으로 한다. - Void") + @Test + void registerQueue_success() throws Exception { + // Given + Coupon couponFixture = CouponFixture.coupon("CouponName", 2, 1); + Coupon coupon = couponRepository.save(couponFixture); + + given(clockHolder.date()).willReturn(LocalDate.of(2023, 2, 1)); + given(zSetRedisRepository.score(anyString(), anyLong())).willReturn(null); + given(zSetRedisRepository.size(anyString())).willReturn((long)(coupon.getMaxCount() - 1)); + + // When & Then + mockMvc.perform(post("/coupons") + .param("couponName", coupon.getName())) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember + @DisplayName("POST - 발급 가능 날짜가 아닌 쿠폰에 발급 요청을 한다. - NotFoundException") + @Test + void registerQueue_NotFoundException() throws Exception { + // Given + Coupon couponFixture = CouponFixture.coupon(); + Coupon coupon = couponRepository.save(couponFixture); + + given(clockHolder.date()).willReturn(LocalDate.of(2022, 1, 1)); + + // When & Then + mockMvc.perform(post("/coupons") + .param("couponName", coupon.getName())) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_PERIOD.getMessage())); + } + + @WithMember + @DisplayName("POST - 동일한 쿠폰 이벤트에 중복으로 요청한다. - ConflictException") + @Test + void registerQueue_ConflictException() throws Exception { + // Given + Coupon coupon = couponRepository.save(CouponFixture.coupon()); + + given(clockHolder.date()).willReturn(LocalDate.of(2023, 2, 1)); + given(zSetRedisRepository.score(anyString(), anyLong())).willReturn(7.0); + + // When & Then + mockMvc.perform(post("/coupons") + .param("couponName", coupon.getName())) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_COUPON_ISSUE.getMessage())); + } + + @WithMember + @DisplayName("POST - 선착순 이벤트가 마감된 쿠폰에 발급 요청을 한다. - BadRequestException") + @Test + void registerQueue_BadRequestException() throws Exception { + // Given + Coupon couponFixture = CouponFixture.coupon(); + Coupon coupon = couponRepository.save(couponFixture); + + given(clockHolder.date()).willReturn(LocalDate.of(2023, 2, 1)); + given(zSetRedisRepository.score(anyString(), anyLong())).willReturn(null); + given(zSetRedisRepository.size(anyString())).willReturn((long)(coupon.getMaxCount())); + + // When & Then + mockMvc.perform(post("/coupons") + .param("couponName", coupon.getName())) + .andDo(print()) + .andDo(document("coupons", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.INVALID_COUPON_STOCK_END.getMessage())); + } +} diff --git a/src/test/java/com/moabam/api/presentation/ItemControllerTest.java b/src/test/java/com/moabam/api/presentation/ItemControllerTest.java new file mode 100644 index 00000000..4745e997 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/ItemControllerTest.java @@ -0,0 +1,165 @@ +package com.moabam.api.presentation; + +import static com.moabam.global.auth.model.AuthorizationThreadLocal.*; +import static com.moabam.support.fixture.InventoryFixture.*; +import static com.moabam.support.fixture.ItemFixture.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static java.nio.charset.StandardCharsets.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.http.MediaType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.bug.BugService; +import com.moabam.api.application.item.ItemMapper; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemType; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.dto.item.ItemsResponse; +import com.moabam.api.dto.item.PurchaseItemRequest; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +class ItemControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + MemberService memberService; + + @MockBean + BugService bugService; + + @Autowired + ItemRepository itemRepository; + + @Autowired + InventoryRepository inventoryRepository; + + @DisplayName("아이템 목록을 조회한다.") + @Nested + class GetItems { + + @DisplayName("성공한다.") + @WithMember + @Test + void success() throws Exception { + // given + Long memberId = getAuthMember().id(); + Item item1 = itemRepository.save(morningSantaSkin().build()); + Inventory inventory = inventoryRepository.save(inventory(memberId, item1)); + inventory.select(member()); + Item item2 = itemRepository.save(morningKillerSkin().build()); + ItemsResponse expected = ItemMapper.toItemsResponse(item1.getId(), List.of(item1), List.of(item2)); + + // expected + String content = mockMvc.perform(get("/items") + .param("type", ItemType.MORNING.name()) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(UTF_8); + ItemsResponse actual = objectMapper.readValue(content, ItemsResponse.class); + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("아이템 타입이 유효하지 않으면 예외가 발생한다.") + @WithMember + @ParameterizedTest + @ValueSource(strings = {"HI", ""}) + void item_type_bad_request_exception(String itemType) throws Exception { + mockMvc.perform(get("/items") + .param("type", itemType) + .contentType(APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + } + + @Nested + @DisplayName("아이템을 구매한다.") + class PurchaseItem { + + @DisplayName("성공한다.") + @WithMember + @Test + void success() throws Exception { + // given + Long memberId = getAuthMember().id(); + Item item = itemRepository.save(nightMageSkin()); + PurchaseItemRequest request = new PurchaseItemRequest(BugType.NIGHT); + given(memberService.findMember(memberId)).willReturn(member()); + + // expected + mockMvc.perform(post("/items/{itemId}/purchase", item.getId()) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("아이템 구매 요청 바디가 유효하지 않으면 예외가 발생한다.") + @WithMember + @Test + void bad_request_body_exception() throws Exception { + // given + Long itemId = 1L; + PurchaseItemRequest request = new PurchaseItemRequest(null); + + // expected + mockMvc.perform(post("/items/{itemId}/purchase", itemId) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("올바른 요청 정보가 아닙니다.")) + .andDo(print()); + } + } + + @DisplayName("아이템을 적용한다.") + @WithMember + @Test + void select_item_success() throws Exception { + // given + Long memberId = getAuthMember().id(); + Item item = itemRepository.save(nightMageSkin()); + given(memberService.findMember(memberId)).willReturn(member()); + inventoryRepository.save(inventory(memberId, item)); + + // when, then + mockMvc.perform(post("/items/{itemId}/select", item.getId()) + .contentType(APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()); + } +} diff --git a/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java new file mode 100644 index 00000000..be66cc08 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/MemberAuthorizeControllerTest.java @@ -0,0 +1,219 @@ +package com.moabam.api.presentation; + +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.auth.AuthorizationService; +import com.moabam.api.application.auth.OAuth2AuthorizationServerRequestService; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; +import com.moabam.global.auth.filter.CorsFilter; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.config.OAuthConfig; +import com.moabam.global.error.handler.RestTemplateResponseHandler; +import com.moabam.support.fixture.AuthorizationResponseFixture; +import com.moabam.support.fixture.ItemFixture; + +@SpringBootTest +@AutoConfigureMockMvc +class MemberAuthorizeControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + + @Autowired + ItemRepository itemRepository; + + @SpyBean + AuthorizationService authorizationService; + + @SpyBean + CorsFilter corsFilter; + + @Autowired + OAuthConfig oAuthConfig; + + static RestTemplateBuilder restTemplateBuilder; + + MockRestServiceServer mockRestServiceServer; + + @BeforeAll + static void allSetUp() { + restTemplateBuilder = new RestTemplateBuilder() + .errorHandler(new RestTemplateResponseHandler()); + } + + @BeforeEach + void setUp() { + // TODO 추후 RestTemplate -> REstTemplateBuilder & Bean등록하여 테스트 코드도 일부 변경됨 + RestTemplate restTemplate = restTemplateBuilder.build(); + ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); + mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); + willReturn("http://localhost").given(corsFilter).getReferer(any()); + } + + @DisplayName("인가 코드 받기 위한 로그인 페이지 요청") + @Test + void authorization_code_request_success() throws Exception { + // given + String uri = UriComponentsBuilder + .fromUriString(oAuthConfig.provider().authorizationUri()) + .queryParam("response_type", "code") + .queryParam("client_id", oAuthConfig.client().clientId()) + .queryParam("redirect_uri", oAuthConfig.provider().redirectUri()) + .queryParam("scope", String.join(",", oAuthConfig.client().scope())) + .toUriString(); + + // expected + ResultActions result = mockMvc.perform(get("/members/login/oauth")); + + result.andExpect(status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.header().string("Content-type", + MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8)) + .andExpect(MockMvcResultMatchers.redirectedUrl(uri)); + } + + @DisplayName("소셜 로그인 및 회원가입 요청 성공") + @Test + void social_login_signUp_request_success() throws Exception { + // given + MultiValueMap contentParams = new LinkedMultiValueMap<>(); + contentParams.add("grant_type", oAuthConfig.client().authorizationGrantType()); + contentParams.add("client_id", oAuthConfig.client().clientId()); + contentParams.add("redirect_uri", oAuthConfig.provider().redirectUri()); + contentParams.add("code", "test"); + contentParams.add("client_secret", oAuthConfig.client().clientSecret()); + + Item morningEgg = ItemFixture.morningSantaSkin().build(); + Item nightEgg = ItemFixture.nightMageSkin(); + itemRepository.saveAll(List.of(morningEgg, nightEgg)); + + AuthorizationCodeResponse authorizationCodeResponse = AuthorizationResponseFixture.successCodeResponse(); + String requestBody = objectMapper.writeValueAsString(authorizationCodeResponse); + AuthorizationTokenResponse authorizationTokenResponse = + AuthorizationResponseFixture.authorizationTokenResponse(); + String response = objectMapper.writeValueAsString(authorizationTokenResponse); + + AuthorizationTokenInfoResponse authorizationTokenInfoResponse = + AuthorizationResponseFixture.authorizationTokenInfoResponse(); + String tokenInfoResponse = objectMapper.writeValueAsString(authorizationTokenInfoResponse); + + // expected + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().tokenUri())) + .andExpect(MockRestRequestMatchers.content().formData(contentParams)) + .andExpect(MockRestRequestMatchers.content().contentType("application/x-www-form-urlencoded;charset=UTF-8")) + .andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess(response, MediaType.APPLICATION_JSON)); + + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().tokenInfo())) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andExpect(MockRestRequestMatchers.header("Authorization", "Bearer accessToken")) + .andRespond(withSuccess(tokenInfoResponse, MediaType.APPLICATION_JSON)); + + mockMvc.perform(post("/members/login/kakao/oauth") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpectAll( + status().isOk(), + MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON), + cookie().value("token_type", "Bearer"), + cookie().exists("access_token"), + cookie().httpOnly("access_token", true), + cookie().secure("access_token", true), + cookie().exists("refresh_token"), + cookie().httpOnly("refresh_token", true), + cookie().secure("refresh_token", true) + ) + .andExpect(MockMvcResultMatchers.jsonPath("$.isSignUp").value(true)); + } + + @DisplayName("Authorization Token 발급 실패") + @ParameterizedTest + @ValueSource(ints = {400, 401, 403, 429, 500, 502, 503}) + void authorization_token_request_fail(int code) throws Exception { + // given + MultiValueMap contentParams = new LinkedMultiValueMap<>(); + contentParams.add("grant_type", oAuthConfig.client().authorizationGrantType()); + contentParams.add("client_id", oAuthConfig.client().clientId()); + contentParams.add("redirect_uri", oAuthConfig.provider().redirectUri()); + contentParams.add("code", "test"); + contentParams.add("client_secret", oAuthConfig.client().clientSecret()); + + AuthorizationCodeResponse authorizationCodeResponse = AuthorizationResponseFixture.successCodeResponse(); + String requestBody = objectMapper.writeValueAsString(authorizationCodeResponse); + + // expected + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().tokenUri())) + .andExpect(MockRestRequestMatchers.content().formData(contentParams)) + .andExpect(MockRestRequestMatchers.content().contentType("application/x-www-form-urlencoded;charset=UTF-8")) + .andExpect(method(HttpMethod.POST)) + .andRespond(withStatus(HttpStatusCode.valueOf(code))); + + mockMvc.perform(post("/members/login/kakao/oauth") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andExpect(status().isBadRequest()); + } + + @DisplayName("토큰 정보 요청 실패") + @ParameterizedTest + @ValueSource(ints = {400, 401}) + void token_info_response_fail(int code) throws Exception { + // given + AuthorizationCodeResponse authorizationCodeResponse = AuthorizationResponseFixture.successCodeResponse(); + + // when + doReturn(AuthorizationResponseFixture.authorizationTokenResponse()) + .when(authorizationService).requestToken(authorizationCodeResponse); + + // expected + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().tokenInfo())) + .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) + .andExpect(MockRestRequestMatchers.header("Authorization", "Bearer accessToken")) + .andRespond(withStatus(HttpStatusCode.valueOf(code))); + + mockMvc.perform(post("/members/login/kakao/oauth") + .flashAttr("authorizationCodeResponse", authorizationCodeResponse)) + .andExpect(status().isBadRequest()); + } +} diff --git a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java new file mode 100644 index 00000000..80efe377 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java @@ -0,0 +1,528 @@ +package com.moabam.api.presentation; + +import static com.moabam.global.common.util.GlobalConstant.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureMockRestServiceServer; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Import; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.match.MockRestRequestMatchers; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.auth.OAuth2AuthorizationServerRequestService; +import com.moabam.api.application.image.ImageService; +import com.moabam.api.application.member.MemberMapper; +import com.moabam.api.domain.auth.repository.TokenRepository; +import com.moabam.api.domain.image.ImageType; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.Role; +import com.moabam.api.domain.member.repository.BadgeRepository; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.dto.auth.TokenSaveValue; +import com.moabam.api.dto.member.ModifyMemberRequest; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.global.config.EmbeddedRedisConfig; +import com.moabam.global.config.OAuthConfig; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.global.error.handler.RestTemplateResponseHandler; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.BadgeFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.ParticipantFixture; +import com.moabam.support.fixture.RoomFixture; +import com.moabam.support.fixture.TokenSaveValueFixture; + +import jakarta.persistence.EntityManager; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureMockRestServiceServer +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Import(EmbeddedRedisConfig.class) +class MemberControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + MemberRepository memberRepository; + + @Autowired + MemberSearchRepository memberSearchRepository; + + @Autowired + TokenRepository tokenRepository; + + @Autowired + RoomRepository roomRepository; + + @Autowired + ItemRepository itemRepository; + + @Autowired + BadgeRepository badgeRepository; + + @Autowired + InventoryRepository inventoryRepository; + + @Autowired + ParticipantRepository participantRepository; + + @Autowired + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + + @Autowired + OAuthConfig oAuthConfig; + + @SpyBean + ImageService imageService; + + RestTemplateBuilder restTemplateBuilder; + + MockRestServiceServer mockRestServiceServer; + + Member member; + + @Autowired + EntityManager entityManager; + + @Autowired + RedisTemplate redisTemplate; + + @BeforeAll + void allSetUp() { + restTemplateBuilder = new RestTemplateBuilder() + .errorHandler(new RestTemplateResponseHandler()); + + member = MemberFixture.member("1234567890987654"); + member.increaseTotalCertifyCount(); + memberRepository.save(member); + } + + @BeforeEach + void setUp() { + RestTemplate restTemplate = restTemplateBuilder.build(); + ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); + mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); + member = entityManager.merge(member); + } + + @DisplayName("로그아웃 성공 테스트") + @WithMember + @Test + void logout_success() throws Exception { + // given + TokenSaveValue tokenSaveValue = TokenSaveValueFixture.tokenSaveValue(); + tokenRepository.saveToken(member.getId(), tokenSaveValue, Role.USER); + + // expected + ResultActions result = mockMvc.perform(get("/members/logout")); + + result.andExpect(status().is2xxSuccessful()); + + Assertions.assertThatThrownBy(() -> tokenRepository.getTokenSaveValue(member.getId(), Role.USER)) + .isInstanceOf(UnauthorizedException.class); + } + + @DisplayName("회원 삭제 성공 테스트") + @WithMember + @Test + void delete_member_success() throws Exception { + // Given + String nickname = member.getNickname(); + + // expected + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().unlink())) + .andExpect(MockRestRequestMatchers.content() + .contentType("application/x-www-form-urlencoded;charset=UTF-8")) + .andExpect(MockRestRequestMatchers.header( + "Authorization", "KakaoAK " + oAuthConfig.client().adminKey())) + .andExpect(method(HttpMethod.POST)) + .andRespond(withStatus(HttpStatus.OK)); + + mockMvc.perform(delete("/members")); + memberRepository.flush(); + + Optional deletedMemberOptional = memberRepository.findById(member.getId()); + assertThat(deletedMemberOptional).isNotEmpty(); + + Member deletedMEmber = deletedMemberOptional.get(); + assertThat(deletedMEmber.getDeletedAt()).isNotNull(); + assertThat(deletedMEmber.getNickname()).isNull(); + } + + @DisplayName("회원이 없어서 회원 삭제 실패") + @WithMember(id = 123L) + @Test + void delete_member_failBy_not_found_member() throws Exception { + // expected + mockMvc.perform(delete("/members")) + .andExpect(status().isNotFound()); + } + + @DisplayName("연결 오류로 인한 카카오 연결 끊기 실패로 롤백") + @WithMember + @ParameterizedTest + @ValueSource(ints = {401, 400}) + void unlink_social_member_failby_connection_error_and_rollback(int code) throws Exception { + // expected + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().unlink())) + .andExpect(MockRestRequestMatchers.header( + "Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")) + .andExpect(MockRestRequestMatchers.header( + "Authorization", "KakaoAK " + oAuthConfig.client().adminKey())) + .andExpect(method(HttpMethod.POST)) + .andRespond(withStatus(HttpStatusCode.valueOf(code))); + + ResultActions result = mockMvc.perform(delete("/members")); + result.andExpect(status().isBadRequest()); + + Optional rollbackMemberOptional = memberSearchRepository.findMember(member.getId()); + assertThat(rollbackMemberOptional).isPresent(); + + Member rollMember = rollbackMemberOptional.get(); + assertAll( + () -> assertThat(rollMember.getSocialId()).isEqualTo(member.getSocialId()), + () -> assertThat(rollMember.getDeletedAt()).isNull() + ); + } + + @DisplayName("방장으로 인해 회원 삭제 조회 실패") + @WithMember + @Test + void unlink_social_member_failby_meber_is_manger() throws Exception { + // given + Room room = RoomFixture.room(); + room.changeManagerNickname(member.getNickname()); + + Participant participant = ParticipantFixture.participant(room, member.getId()); + participant.enableManager(); + roomRepository.save(room); + participantRepository.save(participant); + + // then + mockMvc.perform(delete("/members")) + .andExpect(status().isNotFound()); + } + + @DisplayName("내 정보 조회 성공") + @WithMember + @Test + void search_my_info_success() throws Exception { + // given + Badge birth = BadgeFixture.badge(member.getId(), BadgeType.BIRTH); + Badge level50 = BadgeFixture.badge(member.getId(), BadgeType.LEVEL50); + Badge level10 = BadgeFixture.badge(member.getId(), BadgeType.LEVEL10); + List badges = List.of(birth, level10, level50); + badgeRepository.saveAll(badges); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(member); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(member); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + member.changeDefaultSkintUrl(night); + member.changeDefaultSkintUrl(morning); + memberRepository.flush(); + + // expected + mockMvc.perform(get("/members")) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(member.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(member.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(member.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(member.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(member.getTotalCertifyCount() % LEVEL_DIVISOR), + + // MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getImage()), + // MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("탄생 축하 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("10레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("50레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.goldenBug").value(member.getBug().getGoldenBug()), + MockMvcResultMatchers.jsonPath("$.morningBug").value(member.getBug().getMorningBug()), + MockMvcResultMatchers.jsonPath("$.nightBug").value(member.getBug().getNightBug()) + ).andDo(print()); + } + + @DisplayName("뱃지없는 내 정보 조회 성공") + @WithMember + @Test + void search_my_info_with_no_badge_success() throws Exception { + // given + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(member); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(member); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + member.changeDefaultSkintUrl(night); + member.changeDefaultSkintUrl(morning); + + memberRepository.flush(); + + // expected + mockMvc.perform(get("/members")) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(member.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(member.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(member.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(member.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(member.getTotalCertifyCount() % LEVEL_DIVISOR), + + // MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getImage()), + // MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("탄생 축하 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("10레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("50레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(false), + MockMvcResultMatchers.jsonPath("$.goldenBug").value(member.getBug().getGoldenBug()), + MockMvcResultMatchers.jsonPath("$.morningBug").value(member.getBug().getMorningBug()), + MockMvcResultMatchers.jsonPath("$.nightBug").value(member.getBug().getNightBug()) + ).andDo(print()); + } + + @DisplayName("친구 정보 조회 성공") + @WithMember + @Test + void search_friend_info_success() throws Exception { + // given + Member friend = MemberFixture.member("123456789"); + memberRepository.save(friend); + + Badge birth = BadgeFixture.badge(friend.getId(), BadgeType.BIRTH); + Badge level10 = BadgeFixture.badge(friend.getId(), BadgeType.LEVEL10); + List badges = List.of(birth, level10); + badgeRepository.saveAll(badges); + + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(friend.getId(), night); + nightInven.select(friend); + + Inventory morningInven = InventoryFixture.inventory(friend.getId(), morning); + morningInven.select(friend); + + Inventory killerInven = InventoryFixture.inventory(friend.getId(), killer); + friend.changeDefaultSkintUrl(morning); + friend.changeDefaultSkintUrl(night); + memberRepository.flush(); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + friend.changeDefaultSkintUrl(morning); + friend.changeDefaultSkintUrl(night); + memberRepository.flush(); + + // expected + mockMvc.perform(get("/members/{memberId}", friend.getId())) + .andExpect(status().isOk()) + .andExpectAll( + MockMvcResultMatchers.jsonPath("$.nickname").value(friend.getNickname()), + MockMvcResultMatchers.jsonPath("$.profileImage").value(friend.getProfileImage()), + MockMvcResultMatchers.jsonPath("$.intro").value(friend.getIntro()), + MockMvcResultMatchers.jsonPath("$.level").value(friend.getTotalCertifyCount() / LEVEL_DIVISOR), + MockMvcResultMatchers.jsonPath("$.exp").value(friend.getTotalCertifyCount() % LEVEL_DIVISOR), + + MockMvcResultMatchers.jsonPath("$.birds.MORNING").value(morningInven.getItem().getAwakeImage()), + MockMvcResultMatchers.jsonPath("$.birds.NIGHT").value(nightInven.getItem().getAwakeImage()), + + MockMvcResultMatchers.jsonPath("$.badges[0].badge").value("탄생 축하 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[0].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[1].badge").value("10레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[1].unlock").value(true), + MockMvcResultMatchers.jsonPath("$.badges[2].badge").value("50레벨 뱃지"), + MockMvcResultMatchers.jsonPath("$.badges[2].unlock").value(false) + ).andDo(print()); + } + + @DisplayName("회원 정보 찾기 실패로 예외 발생") + @WithMember(id = 123L) + @Test + void search_member_failBy_not_found_member() throws Exception { + // expected + mockMvc.perform(get("/members/{memberId}", 123L)) + .andExpect(status().is4xxClientError()); + } + + @DisplayName("기본 스킨의 갯수가 다를때 예외 발생") + @Test + void search_member_failBy_default_skin_size() throws Exception { + // given + Item night = ItemFixture.nightMageSkin(); + Item morning = ItemFixture.morningSantaSkin().build(); + Item killer = ItemFixture.morningKillerSkin().build(); + itemRepository.saveAll(List.of(night, morning, killer)); + + Inventory nightInven = InventoryFixture.inventory(member.getId(), night); + nightInven.select(member); + + Inventory morningInven = InventoryFixture.inventory(member.getId(), morning); + morningInven.select(member); + + Inventory killerInven = InventoryFixture.inventory(member.getId(), killer); + killerInven.select(member); + inventoryRepository.saveAll(List.of(nightInven, morningInven, killerInven)); + + // expected + mockMvc.perform(get("/members/{memberId}", 123L)) + .andExpect(status().is4xxClientError()); + } + + @DisplayName("회원 정보 요청 성공") + @WithMember + @ParameterizedTest + @CsvSource({"intro,", ", nickname", ",", "intro, nickname"}) + void member_modify_request_success(String intro, String nickname) throws Exception { + // given + ModifyMemberRequest request = new ModifyMemberRequest(intro, nickname); + MockMultipartFile newProfileImage = + new MockMultipartFile( + "profileImage", + "tooth.png", + "multipart/form-data", + "uploadFile".getBytes(StandardCharsets.UTF_8)); + MockMultipartFile modifyMemberRequest = + new MockMultipartFile( + "modifyMemberRequest", + null, + "application/json", + objectMapper.writeValueAsString(request).getBytes(StandardCharsets.UTF_8)); + + willReturn(List.of("/main")) + .given(imageService).uploadImages(List.of(newProfileImage), ImageType.PROFILE_IMAGE); + + // expected + mockMvc.perform(multipart(HttpMethod.POST, "/members/modify") + .file(modifyMemberRequest) + .file(newProfileImage) + .contentType("multipart/form-data") + + .characterEncoding("UTF-8")) + .andExpect(status().is2xxSuccessful()) + .andDo(print()); + + } + + @DisplayName("회원 프로필없이 성공 ") + @WithMember + @ParameterizedTest + @CsvSource({"intro,", ", nickname", ",", "intro, nickname"}) + void member_modify_no_image_request_success(String intro, String nickname) throws Exception { + // given + ModifyMemberRequest request = new ModifyMemberRequest(intro, nickname); + MockMultipartFile modifyMemberRequest = + new MockMultipartFile( + "modifyMemberRequest", + null, + "application/json", + objectMapper.writeValueAsString(request).getBytes(StandardCharsets.UTF_8)); + + willThrow(NullPointerException.class) + .given(imageService).uploadImages(any(), any()); + RankingInfo rankingInfo = MemberMapper.toRankingInfo(member); + redisTemplate.opsForZSet().add("Ranking", rankingInfo, member.getTotalCertifyCount()); + + // expected + mockMvc.perform(multipart(HttpMethod.POST, "/members/modify") + .file(modifyMemberRequest) + .contentType("multipart/form-data") + + .characterEncoding("UTF-8")) + .andExpect(status().is2xxSuccessful()) + .andDo(print()); + + String updateNick = member.getNickname(); + + if (Objects.nonNull(nickname)) { + updateNick = nickname; + } + + Double result = redisTemplate.opsForZSet() + .score("Ranking", new RankingInfo(member.getId(), updateNick, member.getProfileImage())); + assertThat(result).isEqualTo(member.getTotalCertifyCount()); + } +} diff --git a/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java new file mode 100644 index 00000000..b623bcb9 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/NotificationControllerTest.java @@ -0,0 +1,174 @@ +package com.moabam.api.presentation; + +import static org.mockito.BDDMockito.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.Message; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.notification.repository.NotificationRepository; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.infrastructure.fcm.FcmRepository; +import com.moabam.api.infrastructure.fcm.FcmService; +import com.moabam.api.infrastructure.redis.ValueRedisRepository; +import com.moabam.global.error.model.ErrorMessage; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; +import com.moabam.support.snippet.ErrorSnippet; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureRestDocs +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class NotificationControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + MemberRepository memberRepository; + + @Autowired + RoomRepository roomRepository; + + @Autowired + NotificationRepository notificationRepository; + + @Autowired + ValueRedisRepository valueRedisRepository; + + @Autowired + FcmService fcmService; + + @Autowired + FcmRepository fcmRepository; + + @MockBean + FirebaseMessaging firebaseMessaging; + + Member member; + Member target; + Room room; + String knockKey; + + @BeforeAll + void setUp() { + member = memberRepository.save(MemberFixture.member(1L)); + target = memberRepository.save(MemberFixture.member("socialId")); + room = roomRepository.save(RoomFixture.room()); + knockKey = String.format("roomId=%s_targetId=%s_memberId=%s", room.getId(), target.getId(), member.getId()); + + willReturn(null) + .given(firebaseMessaging) + .sendAsync(any(Message.class)); + } + + @AfterEach + void setDown() { + fcmService.deleteTokenByMemberId(target.getId()); + valueRedisRepository.delete(knockKey); + } + + @WithMember + @DisplayName("POST - 성공적으로 FCM Token을 저장한다. - Void") + @Test + void createFcmToken_success() throws Exception { + // When & Then + mockMvc.perform(post("/notifications") + .param("fcmToken", "FCM-TOKEN")) + .andDo(print()) + .andDo(document("notifications", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember + @DisplayName("POST - FCM Token이 BLANK라 아무일도 일어나지 않는다. - Void") + @Test + void createFcmToken_blank() throws Exception { + // When & Then + mockMvc.perform(post("/notifications") + .param("fcmToken", "")) + .andDo(print()) + .andDo(document("notifications", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember + @DisplayName("GET - 상대에게 콕 알림을 성공적으로 보낸다. - Void") + @Test + void sendKnock_success() throws Exception { + // Given + fcmRepository.saveToken("FCM_TOKEN", target.getId()); + + // When & Then + mockMvc.perform(get("/notifications/rooms/" + room.getId() + "/members/" + target.getId())) + .andDo(print()) + .andDo(document("notifications/rooms/roomId/members/memberId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()))) + .andExpect(status().isOk()); + } + + @WithMember + @DisplayName("GET - 콕 알림을 보낸 상대가 접속 중이 아니다. - NotFoundException") + @Test + void sendKnock_NotFoundException() throws Exception { + // When & Then + mockMvc.perform(get("/notifications/rooms/" + room.getId() + "/members/" + target.getId())) + .andDo(print()) + .andDo(document("notifications/rooms/roomId/members/memberId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isNotFound()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.NOT_FOUND_FCM_TOKEN.getMessage())); + } + + @WithMember + @DisplayName("GET - 이미 콕 알림을 보낸 대상이다. - ConflictException") + @Test + void sendKnock_ConflictException() throws Exception { + // Given + fcmRepository.saveToken("FCM_TOKEN", target.getId()); + notificationRepository.saveKnock(1L, target.getId(), room.getId()); + + // When & Then + mockMvc.perform(get("/notifications/rooms/" + room.getId() + "/members/" + target.getId())) + .andDo(print()) + .andDo(document("notifications/rooms/roomId/members/memberId", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + ErrorSnippet.ERROR_MESSAGE_RESPONSE)) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.message").value(ErrorMessage.CONFLICT_KNOCK.getMessage())); + } +} diff --git a/src/test/java/com/moabam/api/presentation/PaymentControllerTest.java b/src/test/java/com/moabam/api/presentation/PaymentControllerTest.java new file mode 100644 index 00000000..7bbd2b6c --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/PaymentControllerTest.java @@ -0,0 +1,179 @@ +package com.moabam.api.presentation; + +import static com.moabam.global.auth.model.AuthorizationThreadLocal.*; +import static com.moabam.support.fixture.MemberFixture.*; +import static com.moabam.support.fixture.PaymentFixture.*; +import static com.moabam.support.fixture.ProductFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.http.MediaType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.application.member.MemberService; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.payment.PaymentStatus; +import com.moabam.api.domain.payment.repository.PaymentRepository; +import com.moabam.api.domain.payment.repository.PaymentSearchRepository; +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.repository.ProductRepository; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.PaymentRequest; +import com.moabam.api.infrastructure.payment.TossPaymentService; +import com.moabam.global.error.exception.TossPaymentException; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +class PaymentControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + MemberService memberService; + + @MockBean + TossPaymentService tossPaymentService; + + @Autowired + PaymentRepository paymentRepository; + + @Autowired + PaymentSearchRepository paymentSearchRepository; + + @Autowired + ProductRepository productRepository; + + @Nested + @DisplayName("결제를 요청한다.") + class Request { + + @DisplayName("성공한다.") + @WithMember + @Test + void success() throws Exception { + // given + Product product = productRepository.save(bugProduct()); + Payment payment = paymentRepository.save(payment(product)); + PaymentRequest request = new PaymentRequest(ORDER_ID); + + // expected + mockMvc.perform(post("/payments/{paymentId}", payment.getId()) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(print()); + Payment actual = paymentRepository.findById(payment.getId()).orElseThrow(); + assertThat(actual.getOrder().getId()).isEqualTo(ORDER_ID); + } + + @DisplayName("결제 요청 바디가 유효하지 않으면 예외가 발생한다.") + @WithMember + @ParameterizedTest + @NullAndEmptySource + void bad_request_body_exception(String orderId) throws Exception { + // given + Long paymentId = 1L; + PaymentRequest request = new PaymentRequest(orderId); + + // expected + mockMvc.perform(post("/payments/{paymentId}", paymentId) + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("올바른 요청 정보가 아닙니다.")) + .andDo(print()); + } + } + + @Nested + @DisplayName("결제를 승인한다.") + class Confirm { + + @DisplayName("성공한다.") + @WithMember + @Test + void success() throws Exception { + // given + Long memberId = getAuthMember().id(); + Product product = productRepository.save(bugProduct()); + Payment payment = paymentRepository.save(payment(product)); + payment.request(ORDER_ID); + ConfirmPaymentRequest request = confirmPaymentRequest(); + given(tossPaymentService.confirm(request)).willReturn(confirmTossPaymentResponse()); + given(memberService.findMember(memberId)).willReturn(member()); + + // expected + mockMvc.perform(post("/payments/confirm") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andDo(print()); + Payment actual = paymentRepository.findById(payment.getId()).orElseThrow(); + assertThat(actual.getStatus()).isEqualTo(PaymentStatus.DONE); + } + + @DisplayName("결제 승인 요청 바디가 유효하지 않으면 예외가 발생한다.") + @WithMember + @ParameterizedTest + @CsvSource(value = { + ", random_order_id_123, 2000", + "payment_key_123, , 2000", + "payment_key_123, random_order_id_123, -1000", + }) + void bad_request_body_exception(String paymentKey, String orderId, int amount) throws Exception { + // given + ConfirmPaymentRequest request = new ConfirmPaymentRequest(paymentKey, orderId, amount); + + // expected + mockMvc.perform(post("/payments/confirm") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("올바른 요청 정보가 아닙니다.")) + .andDo(print()); + } + + @DisplayName("토스 결제 승인 요청이 실패하면 예외가 발생한다.") + @WithMember + @Test + void confirm_toss_exception() throws Exception { + // given + Long memberId = getAuthMember().id(); + Product product = productRepository.save(bugProduct()); + Payment payment = paymentRepository.save(payment(product)); + payment.request(ORDER_ID); + ConfirmPaymentRequest request = confirmPaymentRequest(); + given(memberService.findMember(memberId)).willReturn(member()); + given(tossPaymentService.confirm(request)).willThrow(TossPaymentException.class); + + // expected + mockMvc.perform(post("/payments/confirm") + .contentType(APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isInternalServerError()) + .andDo(print()); + } + } +} diff --git a/src/test/java/com/moabam/api/presentation/RankingControllerTest.java b/src/test/java/com/moabam/api/presentation/RankingControllerTest.java new file mode 100644 index 00000000..6399e91c --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/RankingControllerTest.java @@ -0,0 +1,88 @@ +package com.moabam.api.presentation; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.dto.ranking.RankingInfo; +import com.moabam.api.dto.ranking.UpdateRanking; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.MemberFixture; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureRestDocs +class RankingControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + MemberRepository memberRepository; + + @Autowired + RedisTemplate redisTemplate; + + @BeforeEach + void init() { + redisTemplate.delete("Ranking"); + } + + @DisplayName("") + @WithMember + @Test + void top_ranking() throws Exception { + // given + List members = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + Member member = MemberFixture.member(String.valueOf(i + 1)); + members.add(member); + + RankingInfo rankingInfo = new RankingInfo((long)(i + 1), member.getNickname(), member.getProfileImage()); + redisTemplate.opsForZSet().add("Ranking", rankingInfo, i + 1); + } + memberRepository.saveAll(members); + + RankingInfo rankingInfo = new RankingInfo(21L, "Hello22", "123"); + redisTemplate.opsForZSet().add("Ranking", rankingInfo, 20); + RankingInfo rankingInfo2 = new RankingInfo(22L, "Hello23", "123"); + redisTemplate.opsForZSet().add("Ranking", rankingInfo2, 19); + + UpdateRanking myRanking = UpdateRanking.builder() + .score(1L) + .rankingInfo(RankingInfo.builder() + .nickname(members.get(0).getNickname()) + .memberId(members.get(0).getId()) + .image(members.get(0).getProfileImage()).build()) + .build(); + + // when + mockMvc.perform(MockMvcRequestBuilders.get("/rankings")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.topRankings", hasSize(10))) + .andExpect(jsonPath("$.myRanking.nickname", is(members.get(0).getNickname()))) + .andExpect(jsonPath("$.myRanking.rank", is(22))); + + // then + + } + +} diff --git a/src/test/java/com/moabam/api/presentation/ReportControllerTest.java b/src/test/java/com/moabam/api/presentation/ReportControllerTest.java new file mode 100644 index 00000000..4f5c688d --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/ReportControllerTest.java @@ -0,0 +1,165 @@ +package com.moabam.api.presentation; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.report.ReportRequest; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.ReportFixture; +import com.moabam.support.fixture.RoomFixture; + +import jakarta.persistence.EntityManagerFactory; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ReportControllerTest extends WithoutFilterSupporter { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + MemberRepository memberRepository; + + @Autowired + RoomRepository roomRepository; + + @Autowired + CertificationRepository certificationRepository; + + @Autowired + RoutineRepository routineRepository; + + @Autowired + EntityManagerFactory entityManagerFactory; + + Member reportedMember; + + @BeforeAll + void setUp() { + reportedMember = MemberFixture.member(); + memberRepository.save(reportedMember); + } + + @DisplayName("방이나 인증 하나 신고") + @WithMember + @ParameterizedTest + @CsvSource({"true, false", "false, true", "true, true"}) + void reports_success(boolean roomFilter, boolean certificationFilter) throws Exception { + // given + String content = "내용"; + Room room = RoomFixture.room(); + Routine routine = RoomFixture.routine(room, content); + Certification certification = RoomFixture.certification(routine); + roomRepository.save(room); + routineRepository.save(routine); + certificationRepository.save(certification); + + Long roomId = null; + Long certificationId = null; + + if (roomFilter) { + roomId = room.getId(); + } + if (certificationFilter) { + certificationId = certification.getId(); + } + + ReportRequest reportRequest = ReportFixture.reportRequest(reportedMember.getId(), roomId, certificationId); + String request = objectMapper.writeValueAsString(reportRequest); + + // expected + mockMvc.perform(post("/reports") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().is2xxSuccessful()); + } + + @DisplayName("사용자 신고 성공") + @WithMember + @Test + void reports_failBy_subject_null() throws Exception { + // given + Member member = MemberFixture.member("2"); + memberRepository.save(member); + + ReportRequest reportRequest = ReportFixture.reportRequest(member.getId(), null, null); + String request = objectMapper.writeValueAsString(reportRequest); + + // expected + mockMvc.perform(post("/reports") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().is2xxSuccessful()); + } + + @DisplayName("회원 조회 실패로 신고 실패") + @WithMember + @Test + void reports_failBy_member() throws Exception { + // given + Member newMember = MemberFixture.member("9999"); + memberRepository.save(newMember); + + newMember.delete(LocalDateTime.now()); + memberRepository.flush(); + memberRepository.delete(newMember); + memberRepository.flush(); + + ReportRequest reportRequest = ReportFixture.reportRequest(newMember.getId(), 1L, 1L); + String request = objectMapper.writeValueAsString(reportRequest); + + // expected + mockMvc.perform(post("/reports") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().is4xxClientError()); + } + + @DisplayName("방이나 인증 하나 신고 실패") + @WithMember + @ParameterizedTest + @CsvSource({"12394,", ",123415", "12394, 123415"}) + void reports_failBy_room_certification(Long roomId, Long certificationId) throws Exception { + // given + ReportRequest reportRequest = ReportFixture.reportRequest(reportedMember.getId(), roomId, + certificationId); + String request = objectMapper.writeValueAsString(reportRequest); + + // expected + mockMvc.perform(post("/reports") + .contentType(MediaType.APPLICATION_JSON) + .content(request)) + .andExpect(status().is4xxClientError()); + } +} diff --git a/src/test/java/com/moabam/api/presentation/RoomControllerTest.java b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java new file mode 100644 index 00000000..f3de23a6 --- /dev/null +++ b/src/test/java/com/moabam/api/presentation/RoomControllerTest.java @@ -0,0 +1,1592 @@ +package com.moabam.api.presentation; + +import static com.moabam.api.domain.room.RoomType.*; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.http.MediaType.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.repository.InventoryRepository; +import com.moabam.api.domain.item.repository.ItemRepository; +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.repository.MemberRepository; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; +import com.moabam.api.domain.room.repository.CertificationRepository; +import com.moabam.api.domain.room.repository.DailyMemberCertificationRepository; +import com.moabam.api.domain.room.repository.DailyRoomCertificationRepository; +import com.moabam.api.domain.room.repository.ParticipantRepository; +import com.moabam.api.domain.room.repository.ParticipantSearchRepository; +import com.moabam.api.domain.room.repository.RoomRepository; +import com.moabam.api.domain.room.repository.RoutineRepository; +import com.moabam.api.dto.room.CreateRoomRequest; +import com.moabam.api.dto.room.EnterRoomRequest; +import com.moabam.api.dto.room.ModifyRoomRequest; +import com.moabam.global.common.util.ClockHolder; +import com.moabam.support.annotation.WithMember; +import com.moabam.support.common.WithoutFilterSupporter; +import com.moabam.support.fixture.BugFixture; +import com.moabam.support.fixture.InventoryFixture; +import com.moabam.support.fixture.ItemFixture; +import com.moabam.support.fixture.MemberFixture; +import com.moabam.support.fixture.RoomFixture; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RoomControllerTest extends WithoutFilterSupporter { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RoomRepository roomRepository; + + @Autowired + private RoutineRepository routineRepository; + + @Autowired + private ParticipantRepository participantRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CertificationRepository certificationRepository; + + @Autowired + private DailyMemberCertificationRepository dailyMemberCertificationRepository; + + @Autowired + private DailyRoomCertificationRepository dailyRoomCertificationRepository; + + @Autowired + private ParticipantSearchRepository participantSearchRepository; + + @Autowired + private ItemRepository itemRepository; + + @Autowired + private InventoryRepository inventoryRepository; + + @Autowired + private ClockHolder clockHolder; + + Member member; + + @BeforeAll + void setUp() { + member = MemberFixture.member(); + memberRepository.save(member); + } + + @AfterEach + void cleanUp() { + while (member.getCurrentMorningCount() > 0) { + member.exitRoom(MORNING); + } + + while (member.getCurrentNightCount() > 0) { + member.exitRoom(NIGHT); + } + } + + @DisplayName("비밀번호 없는 방 생성 성공") + @WithMember + @Test + void create_room_no_password_success() throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "재윤과 앵맹이의 방임", null, routines, MORNING, 10, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isCreated()) + .andDo(print()); + + assertThat(roomRepository.findAll()).hasSize(1); + assertThat(roomRepository.findAll().get(0).getTitle()).isEqualTo("재윤과 앵맹이의 방임"); + assertThat(roomRepository.findAll().get(0).getPassword()).isNull(); + } + + @DisplayName("비밀번호 있는 방 생성 성공") + @WithMember + @ParameterizedTest + @CsvSource({ + "1234", "12345678", "98765" + }) + void create_room_with_password_success(String password) throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 있는 재맹의 방임", password, routines, MORNING, 10, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isCreated()) + .andDo(print()); + + assertThat(roomRepository.findAll()).hasSize(1); + assertThat(roomRepository.findAll().get(0).getTitle()).isEqualTo("비번 있는 재맹의 방임"); + assertThat(roomRepository.findAll().get(0).getPassword()).isEqualTo(password); + } + + @DisplayName("올바르지 않은 비밀번호 방 생성시 예외 발생") + @ParameterizedTest + @CsvSource({ + "1", "12", "123", "123456789", "abc" + }) + void create_room_with_wrong_password_fail(String password) throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 있는 재윤과 앵맹이의 방임", password, routines, MORNING, 10, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("Routine 갯수를 초과한 방 생성시 예외 발생") + @Test + void create_room_with_too_many_routine_fail() throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + routines.add("밥 먹기"); + routines.add("코드 리뷰 달기"); + routines.add("책 읽기"); + routines.add("산책 하기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 없는 재윤과 앵맹이의 방임", null, routines, MORNING, 10, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("Routine 없는 방 생성시 예외 발생") + @Test + void create_room_with_no_routine_fail() throws Exception { + // given + List routines = new ArrayList<>(); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 없는 재윤과 앵맹이의 방임", null, routines, MORNING, 10, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("올바르지 못한 시간으로 아침 방 생성시 예외 발생") + @ParameterizedTest + @CsvSource({ + "1", "3", "11", "12", "20" + }) + void create_morning_room_wrong_certify_time_fail(int certifyTime) throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 없는 재윤과 앵맹이의 방임", null, routines, MORNING, certifyTime, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("올바르지 못한 시간으로 저녁 방 생성시 에외 발생") + @ParameterizedTest + @CsvSource({ + "19", "3", "6", "9" + }) + void create_night_room_wrong_certify_time_fail(int certifyTime) throws Exception { + // given + List routines = new ArrayList<>(); + routines.add("물 마시기"); + routines.add("코테 풀기"); + + CreateRoomRequest createRoomRequest = new CreateRoomRequest( + "비번 없는 재윤과 앵맹이의 방임", null, routines, NIGHT, certifyTime, 4); + String json = objectMapper.writeValueAsString(createRoomRequest); + + // expected + mockMvc.perform(post("/rooms") + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("방 수정 성공 - 방장일 경우") + @WithMember(id = 1L) + @Test + void modify_room_success() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + List routines = RoomFixture.routines(room); + + Participant participant = RoomFixture.participant(room, 1L); + participant.enableManager(); + + roomRepository.save(room); + routineRepository.saveAll(routines); + participantRepository.save(participant); + + ModifyRoomRequest modifyRoomRequest = new ModifyRoomRequest("수정할 방임!", "공지공지", "4567", 10, 7); + String json = objectMapper.writeValueAsString(modifyRoomRequest); + + // expected + mockMvc.perform(put("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andDo(print()); + + Room modifiedRoom = roomRepository.findById(room.getId()).orElseThrow(); + List modifiedRoutines = routineRepository.findAllByRoomId(room.getId()); + + assertThat(modifiedRoom.getTitle()).isEqualTo("수정할 방임!"); + assertThat(modifiedRoom.getCertifyTime()).isEqualTo(10); + assertThat(modifiedRoom.getPassword()).isEqualTo("4567"); + assertThat(modifiedRoom.getAnnouncement()).isEqualTo("공지공지"); + assertThat(modifiedRoom.getMaxUserCount()).isEqualTo(7); + assertThat(modifiedRoutines).hasSize(2); + } + + @DisplayName("방 수정 실패 - 방장 아닐 경우") + @WithMember(id = 1L) + @Test + void unauthorized_modify_room_fail() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + Participant participant = RoomFixture.participant(room, 1L); + + roomRepository.save(room); + participantRepository.save(participant); + ModifyRoomRequest modifyRoomRequest = new ModifyRoomRequest("수정할 방임!", "방 공지", "1234", 9, 7); + String json = objectMapper.writeValueAsString(modifyRoomRequest); + String message = "{\"message\":\"방장이 아닌 사용자는 방을 수정할 수 없습니다.\"}"; + + // expected + mockMvc.perform(put("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isNotFound()) + .andExpect(content().json(message)) + .andDo(print()); + } + + @DisplayName("방 수정 실패 - 이미 한 참여자가 인증하고 방의 인증 시간을 바꾸려고 할때 예외 처리") + @WithMember(id = 1L) + @Test + void room_certify_time_modification_fail() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + room = roomRepository.save(room); + + Member member2 = MemberFixture.member("12313123"); + member2 = memberRepository.save(member2); + + Participant participant1 = RoomFixture.participant(room, 1L); + participant1.enableManager(); + + Participant participant2 = RoomFixture.participant(room, member2.getId()); + + participantRepository.saveAll(List.of(participant1, participant2)); + + DailyMemberCertification dailyMemberCertification = RoomFixture.dailyMemberCertification(member2.getId(), + room.getId(), participant2); + + dailyMemberCertificationRepository.save(dailyMemberCertification); + + ModifyRoomRequest modifyRoomRequest = new ModifyRoomRequest("수정할 방임!", "방 공지", "1234", 10, 7); + String json = objectMapper.writeValueAsString(modifyRoomRequest); + + // expected + mockMvc.perform(put("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("비밀번호 있는 방 참여 성공") + @WithMember(id = 1L) + @Test + void enter_room_with_password_success() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("7777") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("7777"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // expected + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("비밀번호 없는 방 참여 성공") + @WithMember(id = 1L) + @Test + void enter_room_with_no_password_success() throws Exception { + // given + Room room = RoomFixture.room(); + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest(null); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // expected + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 참여 후 인원수 증가 테스트") + @WithMember(id = 1L) + @Test + void enter_and_increase_room_user_count() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // when + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()); + + Room findRoom = roomRepository.findById(room.getId()).orElseThrow(); + + // then + assertThat(findRoom.getCurrentUserCount()).isEqualTo(2); + } + + @DisplayName("아침 방 참여 후 사용자의 방 입장 횟수 증가 테스트") + @WithMember(id = 1L) + @Test + void enter_and_increase_morning_room_count() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // when + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()); + + Member getMember = memberRepository.findById(1L).orElseThrow(); + + // then + assertThat(getMember.getCurrentMorningCount()).isEqualTo(1); + assertThat(getMember.getCurrentNightCount()).isZero(); + } + + @DisplayName("저녁 방 참여 후 사용자의 방 입장 횟수 증가 테스트") + @WithMember(id = 1L) + @Test + void enter_and_increase_night_room_count() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(NIGHT) + .certifyTime(21) + .maxUserCount(5) + .build(); + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // when + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()); + + Member getMember = memberRepository.findById(1L).orElseThrow(); + + // then + assertThat(getMember.getCurrentNightCount()).isEqualTo(1); + assertThat(getMember.getCurrentMorningCount()).isZero(); + } + + @DisplayName("사용자의 아침 방 입장 횟수 3일시 예외 처리") + @Test + void enter_and_morning_room_over_three_fail() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + for (int i = 0; i < 3; i++) { + member.enterRoom(MORNING); + } + + memberRepository.save(member); + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // when + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()); + } + + @DisplayName("사용자의 저녁 방 입장 횟수 3일시 예외 처리") + @Test + void enter_and_night_room_over_three_fail() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(NIGHT) + .certifyTime(22) + .maxUserCount(5) + .build(); + + for (int i = 0; i < 3; i++) { + member.enterRoom(NIGHT); + } + + memberRepository.save(member); + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + + // when + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()); + } + + @DisplayName("비밀번호 불일치 방 참여시 예외 발생") + @WithMember(id = 1L) + @Test + void enter_room_wrong_password_fail() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("7777") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + Member member = Member.builder() + .id(1L) + .socialId("1") + .bug(BugFixture.bug()) + .build(); + + memberRepository.save(member); + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("1234"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + String message = "{\"message\":\"방의 비밀번호가 일치하지 않습니다.\"}"; + + // expected + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andExpect(content().json(message)) + .andDo(print()); + } + + @DisplayName("인원수가 모두 찬 방 참여시 예외 발생") + @WithMember(id = 1L) + @Test + void enter_max_user_room_fail() throws Exception { + // given + Room room = Room.builder() + .title("처음 제목") + .password("7777") + .roomType(MORNING) + .certifyTime(9) + .maxUserCount(5) + .build(); + + for (int i = 0; i < 4; i++) { + room.increaseCurrentUserCount(); + } + + roomRepository.save(room); + EnterRoomRequest enterRoomRequest = new EnterRoomRequest("7777"); + String json = objectMapper.writeValueAsString(enterRoomRequest); + String message = "{\"message\":\"방의 인원수가 찼습니다.\"}"; + + // expected + mockMvc.perform(post("/rooms/" + room.getId()) + .contentType(APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andExpect(content().json(message)) + .andDo(print()); + } + + @DisplayName("일반 사용자의 방 나가기 성공") + @WithMember(id = 1L) + @Test + void no_manager_exit_room_success() throws Exception { + // given + Room room = Room.builder() + .title("5명이 있는 방~") + .roomType(NIGHT) + .certifyTime(21) + .maxUserCount(8) + .build(); + + Participant participant = RoomFixture.participant(room, 1L); + + for (int i = 0; i < 4; i++) { + room.increaseCurrentUserCount(); + } + + roomRepository.save(room); + participantRepository.save(participant); + + // expected + mockMvc.perform(delete("/rooms/" + room.getId())) + .andExpect(status().isOk()) + .andDo(print()); + + Room findRoom = roomRepository.findById(room.getId()).orElseThrow(); + List deletedParticipant = participantRepository.findAll(); + + assertThat(findRoom.getCurrentUserCount()).isEqualTo(4); + assertThat(deletedParticipant).hasSize(1); + assertThat(deletedParticipant.get(0).getDeletedAt()).isNotNull(); + assertThat(deletedParticipant.get(0).getDeletedRoomTitle()).isNotNull(); + } + + @DisplayName("방장의 방 나가기 - 방 삭제 성공") + @WithMember(id = 1L) + @Test + void manager_delete_room_success() throws Exception { + // given + Room room = Room.builder() + .title("1명이 있는 방~") + .roomType(NIGHT) + .certifyTime(21) + .maxUserCount(8) + .build(); + + List routines = RoomFixture.routines(room); + + Participant participant = RoomFixture.participant(room, 1L); + participant.enableManager(); + + roomRepository.save(room); + routineRepository.saveAll(routines); + participantRepository.save(participant); + + // expected + mockMvc.perform(delete("/rooms/" + room.getId())) + .andExpect(status().isOk()) + .andDo(print()); + + List deletedParticipant = participantRepository.findAll(); + + assertThat(deletedParticipant).hasSize(1); + assertThat(deletedParticipant.get(0).getDeletedAt()).isNotNull(); + assertThat(deletedParticipant.get(0).getDeletedRoomTitle()).isNotNull(); + } + + @DisplayName("방장이 위임하지 않고 방 나가기 실패") + @WithMember(id = 1L) + @Test + void manager_exit_room_fail() throws Exception { + // given + Room room = Room.builder() + .title("7명이 있는 방~") + .roomType(NIGHT) + .certifyTime(21) + .maxUserCount(10) + .build(); + + Participant participant = RoomFixture.participant(room, 1L); + participant.enableManager(); + + for (int i = 0; i < 6; i++) { + room.increaseCurrentUserCount(); + } + + roomRepository.save(room); + participantRepository.save(participant); + String message = "{\"message\":\"인원수가 2명 이상일 때는 방장을 위임해야 합니다.\"}"; + + // expected + mockMvc.perform(delete("/rooms/" + room.getId())) + .andExpect(status().isBadRequest()) + .andExpect(content().json(message)) + .andDo(print()); + } + + @DisplayName("아침 방 나가기 이후 사용자의 방 입장 횟수 감소 테스트") + @WithMember(id = 1L) + @Test + void exit_and_decrease_morning_room_count() throws Exception { + // given + Room room = RoomFixture.room(); + + Participant participant = RoomFixture.participant(room, 1L); + + for (int i = 0; i < 3; i++) { + member.enterRoom(RoomType.MORNING); + } + + memberRepository.save(member); + roomRepository.save(room); + participantRepository.save(participant); + + // when + mockMvc.perform(delete("/rooms/" + room.getId())) + .andExpect(status().isOk()); + + Member getMember = memberRepository.findById(1L).orElseThrow(); + + // then + assertThat(getMember.getCurrentMorningCount()).isEqualTo(2); + } + + @DisplayName("저녁 방 나가기 이후 사용자의 방 입장 횟수 감소 테스트") + @WithMember(id = 1L) + @Test + void exit_and_decrease_night_room_count() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(NIGHT) + .certifyTime(23) + .maxUserCount(5) + .build(); + + Participant participant = RoomFixture.participant(room, 1L); + + for (int i = 0; i < 3; i++) { + member.enterRoom(NIGHT); + } + + memberRepository.save(member); + roomRepository.save(room); + participantRepository.save(participant); + + // when + mockMvc.perform(delete("/rooms/" + room.getId())) + .andExpect(status().isOk()); + + Member getMember = memberRepository.findById(1L).orElseThrow(); + + // then + assertThat(getMember.getCurrentNightCount()).isEqualTo(2); + } + + @DisplayName("방 상세 정보 조회 성공 테스트") + @WithMember(id = 1L) + @Test + void get_room_details_test() throws Exception { + // given + Room room = Room.builder() + .title("방 제목") + .password("1234") + .roomType(NIGHT) + .certifyTime(23) + .maxUserCount(5) + .build(); + + room.increaseCurrentUserCount(); + room.increaseCurrentUserCount(); + + List routines = RoomFixture.routines(room); + + Participant participant1 = RoomFixture.participant(room, 1L); + participant1.enableManager(); + + Member member2 = MemberFixture.member("2"); + Member member3 = MemberFixture.member("3"); + + roomRepository.save(room); + routineRepository.saveAll(routines); + member2 = memberRepository.save(member2); + member3 = memberRepository.save(member3); + + Item item = ItemFixture.nightMageSkin(); + + Inventory inventory1 = InventoryFixture.inventory(1L, item); + Inventory inventory2 = InventoryFixture.inventory(member2.getId(), item); + Inventory inventory3 = InventoryFixture.inventory(member3.getId(), item); + inventory1.select(member); + inventory2.select(member2); + inventory3.select(member3); + + itemRepository.save(item); + inventoryRepository.saveAll(List.of(inventory1, inventory2, inventory3)); + + Participant participant2 = RoomFixture.participant(room, member2.getId()); + Participant participant3 = RoomFixture.participant(room, member3.getId()); + + participantRepository.save(participant1); + participantRepository.save(participant2); + participantRepository.save(participant3); + + Certification certification1 = Certification.builder() + .routine(routines.get(0)) + .memberId(member.getId()) + .image("member1Image") + .build(); + + Certification certification2 = Certification.builder() + .routine(routines.get(1)) + .memberId(member.getId()) + .image("member2Image") + .build(); + + certificationRepository.save(certification1); + certificationRepository.save(certification2); + + DailyMemberCertification dailyMemberCertification = RoomFixture.dailyMemberCertification(member.getId(), + room.getId(), participant1); + dailyMemberCertificationRepository.save(dailyMemberCertification); + + DailyRoomCertification dailyRoomCertification = RoomFixture.dailyRoomCertification(room.getId(), + LocalDate.now()); + dailyRoomCertificationRepository.save(dailyRoomCertification); + + DailyRoomCertification dailyRoomCertification1 = RoomFixture.dailyRoomCertification(room.getId(), + LocalDate.now().minusDays(3)); + dailyRoomCertificationRepository.save(dailyRoomCertification1); + + // expected + mockMvc.perform(get("/rooms/" + room.getId() + "/" + LocalDate.now())) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 추방 성공") + @WithMember(id = 1L) + @Test + void deport_member_success() throws Exception { + // given + Room room = RoomFixture.room(); + Member member = MemberFixture.member("1234"); + memberRepository.save(member); + + Participant memberParticipant = RoomFixture.participant(room, member.getId()); + Participant managerParticipant = RoomFixture.participant(room, 1L); + managerParticipant.enableManager(); + + room.increaseCurrentUserCount(); + + roomRepository.save(room); + participantRepository.save(memberParticipant); + participantRepository.save(managerParticipant); + + // expected + mockMvc.perform(delete("/rooms/" + room.getId() + "/members/" + member.getId())) + .andExpect(status().isOk()) + .andDo(print()); + roomRepository.flush(); + + Room getRoom = roomRepository.findById(room.getId()).orElseThrow(); + Participant getMemberParticipant = participantRepository.findById(memberParticipant.getId()).orElseThrow(); + + assertThat(getRoom.getCurrentUserCount()).isEqualTo(1); + assertThat(getMemberParticipant.getDeletedAt()).isNotNull(); + assertThat(participantSearchRepository.findOne(member.getId(), room.getId())).isEmpty(); + } + + @DisplayName("방장 본인 추방 시도 - 예외 처리") + @WithMember(id = 1L) + @Test + void deport_self_fail() throws Exception { + // given + Room room = RoomFixture.room(); + + Participant managerParticipant = RoomFixture.participant(room, member.getId()); + managerParticipant.enableManager(); + + roomRepository.save(room); + participantRepository.save(managerParticipant); + + // expected + mockMvc.perform(delete("/rooms/" + room.getId() + "/members/" + member.getId())) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + + @DisplayName("방장 위임 성공") + @WithMember(id = 1L) + @Test + void mandate_manager_success() throws Exception { + // given + Member member2 = MemberFixture.member("1234"); + memberRepository.save(member2); + + Room room = RoomFixture.room(); + Participant participant1 = RoomFixture.participant(room, member.getId()); + participant1.enableManager(); + Participant participant2 = RoomFixture.participant(room, member2.getId()); + + roomRepository.save(room); + participantRepository.save(participant1); + participantRepository.save(participant2); + + // expected + mockMvc.perform(put("/rooms/" + room.getId() + "/members/" + member2.getId() + "/mandate")) + .andExpect(status().isOk()) + .andDo(print()); + + Room savedRoom = roomRepository.findById(room.getId()).orElseThrow(); + Participant savedParticipant1 = participantRepository.findById(participant1.getId()).orElseThrow(); + Participant savedParticipant2 = participantRepository.findById(participant2.getId()).orElseThrow(); + + assertThat(savedRoom.getManagerNickname()).isEqualTo(member2.getNickname()); + assertThat(savedParticipant1.isManager()).isFalse(); + assertThat(savedParticipant2.isManager()).isTrue(); + } + + @DisplayName("현재 참여중인 모든 방 조회 성공 - 첫번째 방은 개인과 방 모두 인증 성공") + @WithMember(id = 1L) + @Test + void get_all_my_rooms_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", MORNING, 10); + Room room2 = RoomFixture.room("아침 - 두 번째 방", MORNING, 8); + Room room3 = RoomFixture.room("밤 - 세 번째 방", NIGHT, 22); + + Participant participant1 = RoomFixture.participant(room1, 1L); + Participant participant2 = RoomFixture.participant(room2, 1L); + Participant participant3 = RoomFixture.participant(room3, 1L); + + DailyMemberCertification dailyMemberCertification = RoomFixture.dailyMemberCertification(1L, 1L, participant1); + DailyRoomCertification dailyRoomCertification = RoomFixture.dailyRoomCertification(1L, LocalDate.now()); + + roomRepository.saveAll(List.of(room1, room2, room3)); + participantRepository.saveAll(List.of(participant1, participant2, participant3)); + dailyMemberCertificationRepository.save(dailyMemberCertification); + dailyRoomCertificationRepository.save(dailyRoomCertification); + + // expected + mockMvc.perform(get("/rooms/my-join")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 참여 기록 조회 성공") + @WithMember(id = 1L) + @Test + void get_join_history_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", MORNING, 10); + Room room2 = RoomFixture.room("아침 - 두 번째 방", MORNING, 8); + Room room3 = RoomFixture.room("밤 - 세 번째 방", NIGHT, 22); + + Participant participant1 = RoomFixture.participant(room1, 1L); + Participant participant2 = RoomFixture.participant(room2, 1L); + Participant participant3 = RoomFixture.participant(room3, 1L); + + roomRepository.saveAll(List.of(room1, room2, room3)); + participantRepository.saveAll(List.of(participant1, participant2, participant3)); + + participant3.removeRoom(); + participantRepository.flush(); + participantRepository.delete(participant3); + + // expected + mockMvc.perform(get("/rooms/join-history")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("참여중이지 않은 방에 대한 확인 성공") + @WithMember + @Test + void check_if_participant_false_success() throws Exception { + // given + Room room = RoomFixture.room(); + Room savedRoom = roomRepository.save(room); + + // expected + mockMvc.perform(get("/rooms/" + savedRoom.getId() + "/check")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("참여중이지 않은 방의 정보 불러오기 성공") + @Test + void get_un_joined_room_details() throws Exception { + // given + Room room = RoomFixture.room("테스트 방", NIGHT, 21); + Room savedRoom = roomRepository.save(room); + + Member member1 = MemberFixture.member("901010"); + member1 = memberRepository.save(member1); + + Item item = ItemFixture.nightMageSkin(); + + Inventory inventory = InventoryFixture.inventory(member1.getId(), item); + inventory.select(member1); + + itemRepository.save(item); + inventoryRepository.save(inventory); + + Participant participant = RoomFixture.participant(savedRoom, member1.getId()); + participantRepository.save(participant); + + Routine routine1 = RoomFixture.routine(savedRoom, "물 마시기"); + Routine routine2 = RoomFixture.routine(savedRoom, "커피 마시기"); + routineRepository.saveAll(List.of(routine1, routine2)); + + DailyMemberCertification dailyMemberCertification = RoomFixture.dailyMemberCertification(member1.getId(), + savedRoom.getId(), participant); + dailyMemberCertificationRepository.save(dailyMemberCertification); + + // expected + mockMvc.perform(get("/rooms/" + savedRoom.getId() + "/un-joined")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("참여중인 방에 대한 확인 성공") + @WithMember + @Test + void check_if_participant_true_success() throws Exception { + // given + Room room = RoomFixture.room(); + Room savedRoom = roomRepository.save(room); + + Participant participant = RoomFixture.participant(room, 1L); + participantRepository.save(participant); + + // expected + mockMvc.perform(get("/rooms/" + savedRoom.getId() + "/check")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("아침, 저녁 방 전체 조회 성공 - 첫 번째 조회, 다음 페이지 있음") + @WithMember(id = 1L) + @Test + void search_all_morning_night_rooms_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234"); + Room room2 = RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("아침, 저녁 방 전체 조회 성공 - 마지막 조회, 다음 페이지 없음") + @WithMember(id = 1L) + @Test + void search_last_page_all_morning_night_rooms_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234"); + Room room2 = RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms?roomId=5")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("아침 방 전체 조회 성공 - 첫 번째 조회, 다음 페이지 없음") + @WithMember(id = 1L) + @Test + void search_last_page_all_morning_rooms_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234"); + Room room2 = RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms?roomType=MORNING")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 검색 조회 성공 - 키워드만 존재") + @WithMember(id = 1L) + @Test + void search_first_page_all_rooms_by_keyword_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234"); + Room room2 = RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms/search?keyword=아침")) + .andExpect(status().isOk()) + .andDo(print()); + + mockMvc.perform(get("/rooms/search?keyword=방12")) + .andExpect(status().isOk()) + .andDo(print()); + + mockMvc.perform(get("/rooms/search?keyword=방")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 검색 조회 성공 - 키워드 + 방 타입 존재") + @WithMember(id = 1L) + @Test + void search_first_page_all_rooms_by_keyword_roomType_success() throws Exception { + // given + Room room1 = RoomFixture.room("아침 - 첫 번째 방", RoomType.MORNING, 10, "1234"); + Room room2 = RoomFixture.room("아침 - 두 번째 방", RoomType.MORNING, 9); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("아침 - 아홉 번째 방", RoomType.MORNING, 4); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("아침 - 열둘 번째 방", RoomType.MORNING, 10); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms/search?keyword=번째&roomType=MORNING")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 검색 조회 성공 - 키워드 + 방 타입 + 추가 페이지 존재X") + @WithMember(id = 1L) + @Test + void search_first_page_all_rooms_by_keyword_roomType_roomId_success() throws Exception { + // given + Room room1 = RoomFixture.room("밤 - 첫 번째 방", RoomType.NIGHT, 1, "1234"); + Room room2 = RoomFixture.room("밤 - 두 번째 방", RoomType.NIGHT, 1); + Room room3 = RoomFixture.room("밤 - 세 번째 방", RoomType.NIGHT, 22); + Room room4 = RoomFixture.room("아침 - 네 번째 방", RoomType.MORNING, 7); + Room room5 = RoomFixture.room("밤 - 다섯 번째 방", RoomType.NIGHT, 23, "5869"); + Room room6 = RoomFixture.room("아침 - 여섯 번째 방", RoomType.MORNING, 8); + Room room7 = RoomFixture.room("밤 - 일곱 번째 방", RoomType.NIGHT, 20); + Room room8 = RoomFixture.room("밤 - 여덟 번째 방", RoomType.NIGHT, 1, "5236"); + Room room9 = RoomFixture.room("밤 - 아홉 번째 방", RoomType.NIGHT, 1, "5236"); + Room room10 = RoomFixture.room("밤 - 열 번째 방", RoomType.NIGHT, 1, "97979"); + Room room11 = RoomFixture.room("밤 - 열하나 번째 방", RoomType.NIGHT, 22); + Room room12 = RoomFixture.room("밤 - 열둘 번째 방", RoomType.NIGHT, 1); + Room room13 = RoomFixture.room("밤 - 열셋 번째 방", RoomType.NIGHT, 2); + Room room14 = RoomFixture.room("밤 - 열넷 번째 방", RoomType.NIGHT, 21); + + Routine routine1 = RoomFixture.routine(room1, "방1의 루틴1"); + Routine routine2 = RoomFixture.routine(room1, "방1의 루틴2"); + + Routine routine3 = RoomFixture.routine(room2, "방2의 루틴1"); + Routine routine4 = RoomFixture.routine(room2, "방2의 루틴2"); + + Routine routine5 = RoomFixture.routine(room3, "방3의 루틴1"); + Routine routine6 = RoomFixture.routine(room3, "방3의 루틴2"); + + Routine routine7 = RoomFixture.routine(room4, "방4의 루틴1"); + Routine routine8 = RoomFixture.routine(room4, "방4의 루틴2"); + + Routine routine9 = RoomFixture.routine(room5, "방5의 루틴1"); + Routine routine10 = RoomFixture.routine(room5, "방5의 루틴2"); + + Routine routine11 = RoomFixture.routine(room6, "방6의 루틴1"); + Routine routine12 = RoomFixture.routine(room6, "방6의 루틴2"); + + Routine routine13 = RoomFixture.routine(room7, "방7의 루틴1"); + Routine routine14 = RoomFixture.routine(room7, "방7의 루틴2"); + + Routine routine15 = RoomFixture.routine(room8, "방8의 루틴1"); + Routine routine16 = RoomFixture.routine(room8, "방8의 루틴2"); + + Routine routine17 = RoomFixture.routine(room9, "방9의 루틴1"); + Routine routine18 = RoomFixture.routine(room9, "방9의 루틴2"); + + Routine routine19 = RoomFixture.routine(room10, "방10의 루틴1"); + Routine routine20 = RoomFixture.routine(room10, "방10의 루틴2"); + + Routine routine21 = RoomFixture.routine(room11, "방11의 루틴1"); + Routine routine22 = RoomFixture.routine(room11, "방11의 루틴2"); + + Routine routine23 = RoomFixture.routine(room12, "방12의 루틴1"); + Routine routine24 = RoomFixture.routine(room12, "방12의 루틴2"); + + Routine routine25 = RoomFixture.routine(room13, "방13의 루틴1"); + Routine routine26 = RoomFixture.routine(room13, "방13의 루틴2"); + + Routine routine27 = RoomFixture.routine(room14, "방14의 루틴1"); + Routine routine28 = RoomFixture.routine(room14, "방14의 루틴2"); + + roomRepository.saveAll( + List.of(room1, room2, room3, room4, room5, room6, room7, room8, room9, room10, room11, room12, room13, + room14)); + + routineRepository.saveAll( + List.of(routine1, routine2, routine3, routine4, routine5, routine6, routine7, routine8, routine9, routine10, + routine11, routine12, routine13, routine14, routine15, routine16, routine17, routine18, routine19, + routine20, routine21, routine22, routine23, routine24, routine25, routine26, routine27, routine28)); + + // expected + mockMvc.perform(get("/rooms/search?keyword=루틴&roomType=NIGHT&roomId=3")) + .andExpect(status().isOk()) + .andDo(print()); + } + + @DisplayName("방 수정전 정보 불러오기 성공") + @WithMember(id = 1L) + @Test + void get_room_details_before_modification_success() throws Exception { + // given + Member member2 = MemberFixture.member("123"); + Member member3 = MemberFixture.member("456"); + member2 = memberRepository.save(member2); + member3 = memberRepository.save(member3); + + Room room = RoomFixture.room("수정 전 방 제목", MORNING, 10, "1234"); + Participant participant1 = RoomFixture.participant(room, 1L); + participant1.enableManager(); + Participant participant2 = RoomFixture.participant(room, member2.getId()); + Participant participant3 = RoomFixture.participant(room, member3.getId()); + List routines = RoomFixture.routines(room); + + roomRepository.save(room); + participantRepository.saveAll(List.of(participant1, participant2, participant3)); + routineRepository.saveAll(routines); + + // expected + mockMvc.perform(get("/rooms/" + room.getId())) + .andExpect(status().isOk()) + .andDo(print()); + } +} diff --git a/src/test/java/com/moabam/global/common/handler/AuthArgumentResolverTest.java b/src/test/java/com/moabam/global/common/handler/AuthArgumentResolverTest.java new file mode 100644 index 00000000..a42203d9 --- /dev/null +++ b/src/test/java/com/moabam/global/common/handler/AuthArgumentResolverTest.java @@ -0,0 +1,116 @@ +package com.moabam.global.common.handler; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +import com.moabam.api.domain.member.Member; +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.annotation.Auth; +import com.moabam.global.auth.handler.AuthArgumentResolver; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.AuthorizationThreadLocal; + +@ExtendWith(MockitoExtension.class) +class AuthArgumentResolverTest { + + @InjectMocks + AuthArgumentResolver authArgumentResolver; + + @Nested + @DisplayName("제공 파라미터 검증") + class SupportParameter { + + @DisplayName("파라미터 제공 성공") + @Test + void support_parameter_success() { + // given + MethodParameter parameter = mock(MethodParameter.class); + + willReturn(mock(Auth.class)) + .given(parameter).getParameterAnnotation(any()); + willReturn(AuthMember.class) + .given(parameter).getParameterType(); + + // when + boolean support = authArgumentResolver.supportsParameter(parameter); + + // then + assertThat(support).isTrue(); + } + + @DisplayName("어노테이션이 없어서 지원 실패") + @Test + void support_paramter_failby_no_annotation() { + // given + MethodParameter parameter = mock(MethodParameter.class); + + willReturn(null) + .given(parameter).getParameterAnnotation(any()); + + // when + boolean support = authArgumentResolver.supportsParameter(parameter); + + // then + assertThat(support).isFalse(); + } + + @DisplayName("AuthMember 클래스로 받지 않았을 때 실패") + @Test + void support_paramter_failby_not_authmember() { + // given + MethodParameter parameter = mock(MethodParameter.class); + + willReturn(mock(Auth.class)) + .given(parameter).getParameterAnnotation(any()); + willReturn(Member.class) + .given(parameter).getParameterType(); + + // when + boolean support = authArgumentResolver.supportsParameter(parameter); + + // then + assertThat(support).isFalse(); + } + } + + @DisplayName("값 변환한다") + @Nested + class Resolve { + + @DisplayName("값 변환 성공") + @Test + void resolve_argument_success() { + MethodParameter parameter = mock(MethodParameter.class); + ModelAndViewContainer mavContainer = mock(ModelAndViewContainer.class); + NativeWebRequest webRequest = mock(NativeWebRequest.class); + WebDataBinderFactory binderFactory = mock(WebDataBinderFactory.class); + + AuthorizationThreadLocal.setAuthMember(new AuthMember(1L, "park", Role.USER)); + + Object object = + authArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); + + assertAll( + () -> assertThat(object).isNotNull(), + () -> { + AuthMember authMember = (AuthMember)object; + + assertThat(authMember.id()).isEqualTo(1L); + } + ); + } + } + +} diff --git a/src/test/java/com/moabam/global/common/handler/PathResolverTest.java b/src/test/java/com/moabam/global/common/handler/PathResolverTest.java new file mode 100644 index 00000000..69ec965f --- /dev/null +++ b/src/test/java/com/moabam/global/common/handler/PathResolverTest.java @@ -0,0 +1,65 @@ +package com.moabam.global.common.handler; + +import static com.moabam.api.domain.member.Role.*; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.http.HttpMethod.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.moabam.global.auth.handler.PathResolver; + +class PathResolverTest { + + @DisplayName("path 기본 생성 성공") + @Test + void create_basic_path_success() { + // given + PathResolver.Path path = PathResolver.Path.builder() + .uri("/") + .build(); + + assertAll( + () -> assertThat(path.uri()).isEqualTo("/"), + () -> assertThat(path.roles()).contains(USER), + () -> assertThat(path.httpMethods()).contains(GET, PUT, DELETE, POST, PATCH) + ); + } + + @DisplayName("method직접 설정 생성 성공") + @Test + void create_custom_mehtod_path_success() { + // given + PathResolver.Path path = PathResolver.Path.builder() + .uri("/") + .httpMethod(GET) + .httpMethods(List.of(POST, DELETE)) + .build(); + + assertAll( + () -> assertThat(path.uri()).isEqualTo("/"), + () -> assertThat(path.roles()).contains(USER), + () -> assertThat(path.httpMethods()).contains(GET, DELETE, POST) + ); + } + + @DisplayName("role직접 설정 생성 성공") + @Test + void create_role_mehtod_path_success() { + // given + PathResolver.Path path = PathResolver.Path.builder() + .uri("/") + .role(USER) + .roles(List.of(BLACK)) + .build(); + + assertAll( + () -> assertThat(path.uri()).isEqualTo("/"), + () -> assertThat(path.roles()).contains(USER, BLACK), + () -> assertThat(path.httpMethods()).contains(GET, PUT, DELETE, POST, PATCH) + ); + } +} diff --git a/src/test/java/com/moabam/global/common/util/CookieMakeTest.java b/src/test/java/com/moabam/global/common/util/CookieMakeTest.java new file mode 100644 index 00000000..2f7e2d09 --- /dev/null +++ b/src/test/java/com/moabam/global/common/util/CookieMakeTest.java @@ -0,0 +1,59 @@ +package com.moabam.global.common.util; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import jakarta.servlet.http.Cookie; + +@ExtendWith(MockitoExtension.class) +class CookieMakeTest { + + String domain = "test"; + + @DisplayName("prod환경에서 cookie 생성 테스트") + @Test + void create_test() { + // Given + Cookie cookie = CookieUtils.tokenCookie("access_token", "value", 10000, domain); + + // When + Then + assertAll( + () -> assertThat(cookie.getSecure()).isTrue(), + () -> assertThat(cookie.getSecure()).isTrue(), + () -> assertThat(cookie.getPath()).isEqualTo("/"), + () -> assertThat(cookie.getMaxAge()).isEqualTo(10000), + () -> assertThat(cookie.getAttribute("SameSite")).isEqualTo("None") + ); + } + + @DisplayName("") + @Test + void delete_test() { + // given + Cookie cookie = CookieUtils.tokenCookie("access_token", "value", 10000, domain); + + // when + Cookie deletedCookie = CookieUtils.deleteCookie(cookie); + + // then + assertAll( + () -> assertThat(deletedCookie.getMaxAge()).isZero(), + () -> assertThat(deletedCookie.getPath()).isEqualTo("/") + ); + } + + @DisplayName("") + @Test + void typeCookie_create_test() { + // Given + When + Cookie cookie = CookieUtils.typeCookie("Bearer", 10000, domain); + + // then + assertThat(cookie.getName()).isEqualTo("token_type"); + } +} diff --git a/src/test/java/com/moabam/global/common/util/UrlSubstringParserTest.java b/src/test/java/com/moabam/global/common/util/UrlSubstringParserTest.java new file mode 100644 index 00000000..cfaac3cd --- /dev/null +++ b/src/test/java/com/moabam/global/common/util/UrlSubstringParserTest.java @@ -0,0 +1,41 @@ +package com.moabam.global.common.util; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +class UrlSubstringParserTest { + + @DisplayName("UrlSubstringParser 성공적으로 parse 하는지") + @ParameterizedTest + @CsvSource({ + "https://image.moabam.com/certifications/20231108/1_asdfsdfxcv-4815vcx-asfd, 1", + "https://image.moabam.com/certifications/20231108/5_fwjo39ug-fi2og90-fkw0d, 5" + }) + void url_substring_parser_success(String url, Long result) { + // given, when + String parseUrl = UrlSubstringParser.parseUrl(url, "_"); + + // then + Assertions.assertThat(Long.parseLong(parseUrl)).isEqualTo(result); + } + + @DisplayName("UrlSubstringParser 실패하면 예외 던지는지") + @ParameterizedTest + @CsvSource({ + "https:image.moabam.com.certifications.20231108.1_asdfsdfxcv-4815vcx-asfd", + "https://image.moabam.com/certifications/20231108/5-fwjo39ug-fi2og90-fkw0d", + "https://image.moabam.com/certifications/20231108/5_fwjo39ug-fi2og90-fkw0d/", + "https://image.moabam.com/certifications/20231108/5-fwjo39ug-fi2og90-fkw0d_/" + }) + void url_substring_parser_success(String url) { + // given, when, then + Assertions.assertThatThrownBy(() -> UrlSubstringParser.parseUrl(url, "_")) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.INVALID_REQUEST_URL.getMessage()); + } +} diff --git a/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java b/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java new file mode 100644 index 00000000..24e0c5c4 --- /dev/null +++ b/src/test/java/com/moabam/global/filter/AuthorizationFilterTest.java @@ -0,0 +1,175 @@ +package com.moabam.global.filter; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import com.moabam.api.application.auth.AuthorizationService; +import com.moabam.api.application.auth.JwtAuthenticationService; +import com.moabam.api.application.auth.JwtProviderService; +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.filter.AuthorizationFilter; +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.AuthorizationThreadLocal; +import com.moabam.global.auth.model.PublicClaim; +import com.moabam.global.error.exception.UnauthorizedException; +import com.moabam.support.fixture.JwtProviderFixture; +import com.moabam.support.fixture.PublicClaimFixture; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; + +@ExtendWith(MockitoExtension.class) +class AuthorizationFilterTest { + + @InjectMocks + AuthorizationFilter authorizationFilter; + + @Mock + HandlerExceptionResolver handlerExceptionResolver; + + @Mock + JwtAuthenticationService jwtAuthenticationService; + + @Mock + AuthorizationService authorizationService; + + @DisplayName("토큰 타입이 Bearer가 아니면 예외 발생") + @ParameterizedTest + @ValueSource(strings = { + "Access", "ID", "Self-signed", "Refresh", "Federated" + }) + void filter_token_type_mismatch(String tokenType) throws ServletException, IOException { + // Given + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + httpServletRequest.addHeader("token_type", tokenType); + MockFilterChain mockFilterChain = new MockFilterChain(); + + // When + Then + authorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain); + + verify(handlerExceptionResolver, times(1)) + .resolveException( + eq(httpServletRequest), eq(httpServletResponse), + eq(null), any(UnauthorizedException.class)); + } + + @DisplayName("필터가 쿠키가 없다면 예외 발생") + @Test + void filter_have_any_cookie_error() throws ServletException, IOException { + // Given + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockFilterChain mockFilterChain = new MockFilterChain(); + httpServletRequest.addHeader("token_type", "Bearer"); + + // when + authorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain); + + // then + verify(handlerExceptionResolver, times(1)) + .resolveException( + eq(httpServletRequest), eq(httpServletResponse), + eq(null), any(UnauthorizedException.class)); + } + + @DisplayName("엑세스 토큰이 없어서 예외 발생") + @Test + void filter_have_any_access_token_error() throws ServletException, IOException { + // given + JwtProviderService jwtProviderService = JwtProviderFixture.jwtProviderService(); + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockFilterChain mockFilterChain = new MockFilterChain(); + httpServletRequest.addHeader("token_type", "Bearer"); + + // when + String token = jwtProviderService.provideRefreshToken(Role.USER); + httpServletRequest.setCookies(new Cookie("refresh_token", token)); + + authorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain); + + // then + verify(handlerExceptionResolver, times(1)) + .resolveException( + eq(httpServletRequest), eq(httpServletResponse), + eq(null), any(UnauthorizedException.class)); + } + + @DisplayName("refresh 토큰이 없어서 예외 발생") + @Test + void filter_have_any_refresh_token_error() throws ServletException, IOException { + // given + JwtProviderService jwtProviderService = JwtProviderFixture.jwtProviderService(); + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockFilterChain mockFilterChain = new MockFilterChain(); + + // when + String token = jwtProviderService.provideAccessToken(publicClaim); + httpServletRequest.setCookies( + new Cookie("token_type", "Bearer"), + new Cookie("access_token", token)); + + when(jwtAuthenticationService.parseClaim(token)).thenReturn(publicClaim); + when(jwtAuthenticationService.isTokenExpire(token, Role.USER)).thenReturn(true); + + authorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain); + + // then + verify(handlerExceptionResolver, times(1)) + .resolveException( + eq(httpServletRequest), eq(httpServletResponse), + eq(null), any(UnauthorizedException.class)); + } + + @DisplayName("새로운 토큰 발급 성공") + @Test + void issue_new_token_success() throws ServletException, IOException { + // given + JwtProviderService jwtProviderService = JwtProviderFixture.jwtProviderService(); + PublicClaim publicClaim = PublicClaimFixture.publicClaim(); + + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockFilterChain mockFilterChain = new MockFilterChain(); + + // when + String accessToken = jwtProviderService.provideAccessToken(publicClaim); + String refreshToken = jwtProviderService.provideRefreshToken(Role.USER); + httpServletRequest.setCookies( + new Cookie("token_type", "Bearer"), + new Cookie("access_token", accessToken), + new Cookie("refresh_token", refreshToken)); + + when(jwtAuthenticationService.parseClaim(accessToken)).thenReturn(publicClaim); + when(jwtAuthenticationService.isTokenExpire(accessToken, Role.USER)).thenReturn(true); + when(jwtAuthenticationService.isTokenExpire(refreshToken, Role.USER)).thenReturn(false); + + authorizationFilter.doFilter(httpServletRequest, httpServletResponse, mockFilterChain); + + // then + verify(authorizationService, times(1)) + .issueServiceToken(httpServletResponse, publicClaim); + + AuthMember authMember = AuthorizationThreadLocal.getAuthMember(); + assertThat(authMember.id()).isEqualTo(1L); + } +} diff --git a/src/test/java/com/moabam/global/filter/PathFilterTest.java b/src/test/java/com/moabam/global/filter/PathFilterTest.java new file mode 100644 index 00000000..9c172127 --- /dev/null +++ b/src/test/java/com/moabam/global/filter/PathFilterTest.java @@ -0,0 +1,77 @@ +package com.moabam.global.filter; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.io.IOException; +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import com.moabam.global.auth.filter.PathFilter; +import com.moabam.global.auth.handler.PathResolver; + +import jakarta.servlet.ServletException; + +@ExtendWith(MockitoExtension.class) +class PathFilterTest { + + @InjectMocks + PathFilter pathFilter; + + @Mock + PathResolver pathResolver; + + @DisplayName("Authentication을 넘기기 위한 필터 설정") + @ParameterizedTest + @ValueSource(strings = { + "GET", "POST", "PATCH", "DELETE", "OPTIONS" + }) + void filter_pass_for_authentication(String method) throws ServletException, IOException { + // given + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + httpServletRequest.setMethod(method); + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + willReturn(Optional.of(PathResolver.Path.builder() + .uri("/") + .build())) + .given(pathResolver).permitPathMatch(any()); + + // when + pathFilter.doFilterInternal(httpServletRequest, httpServletResponse, new MockFilterChain()); + + // then + assertThat(httpServletRequest.getAttribute("isPermit")) + .isEqualTo(true); + } + + @DisplayName("경로 허가 없다.") + @Test + void filter_with_no_permit() throws ServletException, IOException { + // given + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + MockHttpServletResponse httpServletResponse = new MockHttpServletResponse(); + + willReturn(Optional.empty()) + .given(pathResolver) + .permitPathMatch(any()); + + // when + pathFilter.doFilterInternal(httpServletRequest, httpServletResponse, new MockFilterChain()); + + // then + assertThat(httpServletRequest.getAttribute("isPermit")).isNull(); + } +} diff --git a/src/test/java/com/moabam/support/annotation/QuerydslRepositoryTest.java b/src/test/java/com/moabam/support/annotation/QuerydslRepositoryTest.java new file mode 100644 index 00000000..0f31b471 --- /dev/null +++ b/src/test/java/com/moabam/support/annotation/QuerydslRepositoryTest.java @@ -0,0 +1,22 @@ +package com.moabam.support.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import com.moabam.support.config.TestQuerydslConfig; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Import(TestQuerydslConfig.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +public @interface QuerydslRepositoryTest { + +} diff --git a/src/test/java/com/moabam/support/annotation/WithMember.java b/src/test/java/com/moabam/support/annotation/WithMember.java new file mode 100644 index 00000000..20c4f334 --- /dev/null +++ b/src/test/java/com/moabam/support/annotation/WithMember.java @@ -0,0 +1,19 @@ +package com.moabam.support.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.moabam.api.domain.member.Role; + +@Target({ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface WithMember { + + long id() default 1L; + + String nickname() default "닉네임"; + + Role role() default Role.USER; +} diff --git a/src/test/java/com/moabam/support/common/ClearDataExtension.java b/src/test/java/com/moabam/support/common/ClearDataExtension.java new file mode 100644 index 00000000..200a2502 --- /dev/null +++ b/src/test/java/com/moabam/support/common/ClearDataExtension.java @@ -0,0 +1,15 @@ +package com.moabam.support.common; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +public class ClearDataExtension implements AfterAllCallback { + + @Override + public void afterAll(ExtensionContext context) { + DataCleanResolver dataCleanResolver = + SpringExtension.getApplicationContext(context).getBean(DataCleanResolver.class); + dataCleanResolver.clean(); + } +} diff --git a/src/test/java/com/moabam/support/common/DataCleanResolver.java b/src/test/java/com/moabam/support/common/DataCleanResolver.java new file mode 100644 index 00000000..dfd259f0 --- /dev/null +++ b/src/test/java/com/moabam/support/common/DataCleanResolver.java @@ -0,0 +1,54 @@ +package com.moabam.support.common; + +import java.util.List; + +import javax.annotation.Nullable; + +import org.springframework.boot.test.context.TestComponent; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.Query; + +@TestComponent +public class DataCleanResolver { + + private EntityManager entityManager; + + public DataCleanResolver(@Nullable EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Transactional + public void clean() { + if (entityManager == null) { + return; + } + + List tableInfos = getTableInfos(); + doClean(tableInfos); + entityManager.clear(); + } + + private List getTableInfos() { + List tableInfos = entityManager.createNativeQuery("show tables").getResultList(); + + return tableInfos.stream() + .map(tableInfo -> (String)tableInfo) + .toList(); + } + + private void doClean(List tableInfos) { + setForeignKeyCheck(0); + tableInfos.stream() + .map(tableInfo -> entityManager.createNativeQuery( + String.format("TRUNCATE TABLE %s", tableInfo))) + .forEach(Query::executeUpdate); + setForeignKeyCheck(1); + } + + private void setForeignKeyCheck(int data) { + entityManager.createNativeQuery(String.format("SET foreign_key_checks = %d", data)) + .executeUpdate(); + } +} diff --git a/src/test/java/com/moabam/support/common/FilterProcessExtension.java b/src/test/java/com/moabam/support/common/FilterProcessExtension.java new file mode 100644 index 00000000..28654921 --- /dev/null +++ b/src/test/java/com/moabam/support/common/FilterProcessExtension.java @@ -0,0 +1,61 @@ +package com.moabam.support.common; + +import static java.util.Objects.*; + +import java.lang.reflect.AnnotatedElement; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import com.moabam.global.auth.model.AuthMember; +import com.moabam.global.auth.model.AuthorizationThreadLocal; +import com.moabam.support.annotation.WithMember; + +public class FilterProcessExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + @Override + public void beforeEach(ExtensionContext context) { + AnnotatedElement annotatedElement = + context.getElement().orElse(null); + + if (isNull(annotatedElement)) { + return; + } + + WithMember withMember = annotatedElement.getAnnotation(WithMember.class); + + if (isNull(withMember)) { + return; + } + + AuthorizationThreadLocal.setAuthMember( + new AuthMember(withMember.id(), withMember.nickname(), withMember.role())); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + AuthorizationThreadLocal.remove(); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws + ParameterResolutionException { + return parameterContext.getParameter().isAnnotationPresent(WithMember.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws + ParameterResolutionException { + WithMember withMember = parameterContext.getParameter().getAnnotation(WithMember.class); + + if (isNull(withMember)) { + return null; + } + + return new AuthMember(withMember.id(), withMember.nickname(), withMember.role()); + } +} diff --git a/src/test/java/com/moabam/support/common/RestDocsFactory.java b/src/test/java/com/moabam/support/common/RestDocsFactory.java new file mode 100644 index 00000000..736f15f9 --- /dev/null +++ b/src/test/java/com/moabam/support/common/RestDocsFactory.java @@ -0,0 +1,31 @@ +package com.moabam.support.common; + +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; + +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.MockMvcSnippetConfigurer; +import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; +import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; + +public class RestDocsFactory { + + public static MockMvcSnippetConfigurer restdocs(RestDocumentationContextProvider restDocumentationContextProvider) { + return MockMvcRestDocumentation.documentationConfiguration(restDocumentationContextProvider) + .uris() + .withScheme("http") + .withHost("dev-api.moabam.com") + .withPort(80) + .and() + .snippets() + .withEncoding("UTF-8"); + } + + public static OperationRequestPreprocessor getDocumentRequest() { + return preprocessRequest(prettyPrint()); + } + + public static OperationResponsePreprocessor getDocumentResponse() { + return preprocessResponse(prettyPrint()); + } +} diff --git a/src/test/java/com/moabam/support/common/TestClockHolder.java b/src/test/java/com/moabam/support/common/TestClockHolder.java new file mode 100644 index 00000000..aa75977d --- /dev/null +++ b/src/test/java/com/moabam/support/common/TestClockHolder.java @@ -0,0 +1,24 @@ +package com.moabam.support.common; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.moabam.global.common.util.ClockHolder; + +@Component +@Profile("test") +public class TestClockHolder implements ClockHolder { + + @Override + public LocalDateTime times() { + return LocalDateTime.of(2023, 12, 3, 14, 30, 0); + } + + @Override + public LocalDate date() { + return LocalDateTime.now().toLocalDate(); + } +} diff --git a/src/test/java/com/moabam/support/common/WithFilterSupporter.java b/src/test/java/com/moabam/support/common/WithFilterSupporter.java new file mode 100644 index 00000000..cd92c8cf --- /dev/null +++ b/src/test/java/com/moabam/support/common/WithFilterSupporter.java @@ -0,0 +1,55 @@ +package com.moabam.support.common; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import com.moabam.api.application.auth.JwtProviderService; +import com.moabam.api.domain.member.Role; +import com.moabam.global.common.util.cookie.CookieUtils; +import com.moabam.global.config.TokenConfig; +import com.moabam.support.fixture.PublicClaimFixture; + +@SpringBootTest +public class WithFilterSupporter { + + @RegisterExtension + RestDocumentationExtension restDocumentationExtension = new RestDocumentationExtension(); + + @Autowired + WebApplicationContext webApplicationContext; + + @Autowired + JwtProviderService jwtProviderService; + + @Autowired + TokenConfig tokenConfig; + + @Autowired + CookieUtils cookieUtils; + + protected MockMvc mockMvc; + + @BeforeEach + void setUpMockMvc(RestDocumentationContextProvider contextProvider) { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .apply(RestDocsFactory.restdocs(contextProvider)) + .defaultRequest(get("/") + .cookie(cookieUtils.typeCookie("Bearer", tokenConfig.getRefreshExpire())) + .cookie(cookieUtils.tokenCookie("access_token", + jwtProviderService.provideAccessToken(PublicClaimFixture.publicClaim()), + tokenConfig.getRefreshExpire())) + .cookie(cookieUtils.tokenCookie("refresh_token", + jwtProviderService.provideRefreshToken(Role.USER), + tokenConfig.getRefreshExpire()))) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java b/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java new file mode 100644 index 00000000..ea3eecad --- /dev/null +++ b/src/test/java/com/moabam/support/common/WithoutFilterSupporter.java @@ -0,0 +1,47 @@ +package com.moabam.support.common; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.filter.CorsFilter; +import com.moabam.global.auth.handler.PathResolver; +import com.moabam.global.config.AllowOriginConfig; + +@Import(DataCleanResolver.class) +@ExtendWith({FilterProcessExtension.class, ClearDataExtension.class}) +public class WithoutFilterSupporter { + + @MockBean + private PathResolver pathResolver; + + @MockBean + private DefaultHandlerExceptionResolver handlerExceptionResolver; + + @SpyBean + private CorsFilter corsFilter; + + @MockBean + private AllowOriginConfig allowOriginConfig; + + @BeforeEach + void setUpMock() { + willReturn("http://localhost:8080") + .given(corsFilter).getReferer(any()); + + willReturn(Optional.of(PathResolver.Path.builder() + .uri("/") + .role(Role.USER) + .build())) + .given(pathResolver).permitPathMatch(any()); + } +} diff --git a/src/test/java/com/moabam/support/config/TestQuerydslConfig.java b/src/test/java/com/moabam/support/config/TestQuerydslConfig.java new file mode 100644 index 00000000..c494ab42 --- /dev/null +++ b/src/test/java/com/moabam/support/config/TestQuerydslConfig.java @@ -0,0 +1,53 @@ +package com.moabam.support.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository; +import com.moabam.api.domain.item.repository.InventorySearchRepository; +import com.moabam.api.domain.item.repository.ItemSearchRepository; +import com.moabam.api.domain.member.repository.MemberSearchRepository; +import com.moabam.api.domain.room.repository.CertificationsSearchRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@EnableJpaAuditing +@TestConfiguration +public class TestQuerydslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } + + @Bean + public ItemSearchRepository itemSearchRepository() { + return new ItemSearchRepository(jpaQueryFactory()); + } + + @Bean + public InventorySearchRepository inventorySearchRepository() { + return new InventorySearchRepository(jpaQueryFactory()); + } + + @Bean + public CertificationsSearchRepository certificationsSearchRepository() { + return new CertificationsSearchRepository(jpaQueryFactory()); + } + + @Bean + public MemberSearchRepository memberSearchRepository() { + return new MemberSearchRepository(jpaQueryFactory()); + } + + @Bean + public CouponWalletSearchRepository couponWalletSearchRepository() { + return new CouponWalletSearchRepository(jpaQueryFactory()); + } +} diff --git a/src/test/java/com/moabam/support/fixture/AuthMemberFixture.java b/src/test/java/com/moabam/support/fixture/AuthMemberFixture.java new file mode 100644 index 00000000..e4a899b2 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/AuthMemberFixture.java @@ -0,0 +1,11 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.member.Member; +import com.moabam.global.auth.model.AuthMember; + +public final class AuthMemberFixture { + + public static AuthMember authMember(Member member) { + return new AuthMember(member.getId(), member.getNickname(), member.getRole()); + } +} diff --git a/src/test/java/com/moabam/support/fixture/AuthorizationResponseFixture.java b/src/test/java/com/moabam/support/fixture/AuthorizationResponseFixture.java new file mode 100644 index 00000000..c8e00e7f --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/AuthorizationResponseFixture.java @@ -0,0 +1,29 @@ +package com.moabam.support.fixture; + +import com.moabam.api.dto.auth.AuthorizationCodeResponse; +import com.moabam.api.dto.auth.AuthorizationTokenInfoResponse; +import com.moabam.api.dto.auth.AuthorizationTokenResponse; + +public final class AuthorizationResponseFixture { + + static final String tokenType = "tokenType"; + static final String accessToken = "accessToken"; + static final String idToken = "id"; + static final String expiresin = "exp"; + static final String refreshToken = "ref"; + static final String refreshTokenExpiresIn = "refs"; + static final String scope = "scope"; + + public static AuthorizationCodeResponse successCodeResponse() { + return new AuthorizationCodeResponse("test", null, null, null); + } + + public static AuthorizationTokenInfoResponse authorizationTokenInfoResponse() { + return new AuthorizationTokenInfoResponse(1L, "expiresIn", "appId"); + } + + public static AuthorizationTokenResponse authorizationTokenResponse() { + return new AuthorizationTokenResponse(tokenType, accessToken, idToken, + expiresin, refreshToken, refreshTokenExpiresIn, scope); + } +} diff --git a/src/test/java/com/moabam/support/fixture/BadgeFixture.java b/src/test/java/com/moabam/support/fixture/BadgeFixture.java new file mode 100644 index 00000000..de7d40b1 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/BadgeFixture.java @@ -0,0 +1,14 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.member.Badge; +import com.moabam.api.domain.member.BadgeType; + +public class BadgeFixture { + + public static Badge badge(Long memberId, BadgeType badgeType) { + return Badge.builder() + .memberId(memberId) + .type(badgeType) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/BugFixture.java b/src/test/java/com/moabam/support/fixture/BugFixture.java new file mode 100644 index 00000000..ec3b82c9 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/BugFixture.java @@ -0,0 +1,52 @@ +package com.moabam.support.fixture; + +import static com.moabam.support.fixture.ProductFixture.*; + +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugHistory; +import com.moabam.api.domain.bug.BugType; +import com.moabam.api.domain.payment.Payment; + +public final class BugFixture { + + public static final int MORNING_BUG = 10; + public static final int NIGHT_BUG = 20; + public static final int GOLDEN_BUG = 30; + public static final int REWARD_MORNING_BUG = 3; + + public static Bug bug() { + return Bug.builder() + .morningBug(MORNING_BUG) + .nightBug(NIGHT_BUG) + .goldenBug(GOLDEN_BUG) + .build(); + } + + public static Bug zeroBug() { + return Bug.builder() + .morningBug(0) + .nightBug(0) + .goldenBug(0) + .build(); + } + + public static BugHistory rewardMorningBugHistory(Long memberId) { + return BugHistory.builder() + .memberId(memberId) + .bugType(BugType.MORNING) + .actionType(BugActionType.REWARD) + .quantity(REWARD_MORNING_BUG) + .build(); + } + + public static BugHistory chargeGoldenBugHistory(Long memberId, Payment payment) { + return BugHistory.builder() + .memberId(memberId) + .payment(payment) + .bugType(BugType.GOLDEN) + .actionType(BugActionType.CHARGE) + .quantity(BUG_PRODUCT_QUANTITY) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/BugHistoryFixture.java b/src/test/java/com/moabam/support/fixture/BugHistoryFixture.java new file mode 100644 index 00000000..544fcce4 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/BugHistoryFixture.java @@ -0,0 +1,26 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.bug.BugActionType; +import com.moabam.api.domain.bug.BugHistory; +import com.moabam.api.domain.bug.BugType; + +public final class BugHistoryFixture { + + public static BugHistory rewardMorningBug(Long memberId, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(BugType.MORNING) + .actionType(BugActionType.REWARD) + .quantity(quantity) + .build(); + } + + public static BugHistory rewardNightBug(Long memberId, int quantity) { + return BugHistory.builder() + .memberId(memberId) + .bugType(BugType.NIGHT) + .actionType(BugActionType.REWARD) + .quantity(quantity) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/CouponFixture.java b/src/test/java/com/moabam/support/fixture/CouponFixture.java new file mode 100644 index 00000000..6cff038a --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/CouponFixture.java @@ -0,0 +1,184 @@ +package com.moabam.support.fixture; + +import java.time.LocalDate; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.coupon.CouponType; +import com.moabam.api.dto.coupon.CouponStatusRequest; +import com.moabam.api.dto.coupon.CreateCouponRequest; + +public final class CouponFixture { + + public static final String DISCOUNT_1000_COUPON_NAME = "황금벌레 1000원 할인"; + public static final String DISCOUNT_10000_COUPON_NAME = "황금벌레 10000원 할인"; + + public static Coupon coupon() { + return Coupon.builder() + .name("couponName") + .point(1000) + .type(CouponType.MORNING) + .maxCount(100) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + + public static Coupon coupon(String name, int startAt) { + return Coupon.builder() + .name(name) + .point(10) + .type(CouponType.MORNING) + .maxCount(100) + .startAt(LocalDate.of(2023, startAt, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + + public static Coupon coupon(int point, int maxCount) { + return Coupon.builder() + .name("couponName") + .point(point) + .type(CouponType.MORNING) + .maxCount(maxCount) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + + public static Coupon coupon(CouponType couponType, int point) { + return Coupon.builder() + .name("couponName") + .point(point) + .type(couponType) + .maxCount(100) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + + public static Coupon coupon(String name, int startMonth, int openMonth) { + return Coupon.builder() + .name(name) + .point(10) + .type(CouponType.MORNING) + .maxCount(100) + .startAt(LocalDate.of(2023, startMonth, 1)) + .openAt(LocalDate.of(2023, openMonth, 1)) + .adminId(1L) + .build(); + } + + public static Coupon discount1000Coupon() { + return Coupon.builder() + .name(DISCOUNT_1000_COUPON_NAME) + .point(1000) + .type(CouponType.DISCOUNT) + .maxCount(100) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .adminId(1L) + .build(); + } + + public static Coupon discount10000Coupon() { + return Coupon.builder() + .name(DISCOUNT_10000_COUPON_NAME) + .point(10000) + .type(CouponType.DISCOUNT) + .maxCount(100) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 2, 1)) + .adminId(1L) + .build(); + } + + public static CreateCouponRequest createCouponRequest() { + return CreateCouponRequest.builder() + .name("couponName") + .description("coupon description") + .point(10) + .type(CouponType.GOLDEN.getName()) + .maxCount(10) + .startAt(LocalDate.of(2023, 2, 1)) + .openAt(LocalDate.of(2023, 1, 1)) + .build(); + } + + public static CreateCouponRequest createCouponRequest(String couponType, int startMonth, int openMonth) { + return CreateCouponRequest.builder() + .name("couponName") + .description("coupon description") + .point(10) + .type(couponType) + .maxCount(10) + .startAt(LocalDate.of(2023, startMonth, 1)) + .openAt(LocalDate.of(2023, openMonth, 1)) + .build(); + } + + public static CouponStatusRequest couponStatusRequest(boolean ongoing, boolean ended) { + return CouponStatusRequest.builder() + .opened(ongoing) + .ended(ended) + .build(); + } + + public static Stream provideCoupons() { + return Stream.of(Arguments.of( + List.of( + coupon("coupon1", 3, 1), + coupon("coupon2", 4, 2), + coupon("coupon3", 5, 3), + coupon("coupon4", 6, 4), + coupon("coupon5", 7, 5), + coupon("coupon6", 8, 6), + coupon("coupon7", 9, 7), + coupon("coupon8", 10, 8), + coupon("coupon9", 11, 9), + coupon("coupon10", 12, 10) + )) + ); + } + + public static Stream provideValues_Object() { + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + values.add(4L); + values.add(1L); + values.add(5L); + values.add(6L); + values.add(7L); + values.add(8L); + values.add(9L); + values.add(10L); + + return Stream.of(Arguments.of(values)); + } + + public static Stream provideValues_Long() { + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + values.add(4L); + values.add(1L); + values.add(5L); + values.add(6L); + values.add(7L); + values.add(8L); + values.add(9L); + values.add(10L); + + return Stream.of(Arguments.of(values)); + } +} diff --git a/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java b/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java new file mode 100644 index 00000000..7fa78e1f --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/CouponWalletFixture.java @@ -0,0 +1,38 @@ +package com.moabam.support.fixture; + +import static com.moabam.support.fixture.CouponFixture.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; + +import com.moabam.api.domain.coupon.CouponWallet; + +public final class CouponWalletFixture { + + public static Stream provideCouponWalletByCouponId1_total5() { + return Stream.of(Arguments.of( + List.of( + CouponWallet.create(1L, coupon("c1", 1)), + CouponWallet.create(1L, coupon("c2", 2)), + CouponWallet.create(1L, coupon("c3", 3)), + CouponWallet.create(1L, coupon("c4", 4)), + CouponWallet.create(1L, coupon("c5", 5)) + )) + ); + } + + public static Stream provideCouponWalletAll() { + return Stream.of(Arguments.of( + List.of( + CouponWallet.create(1L, coupon("c2", 2)), + CouponWallet.create(2L, coupon("c3", 3)), + CouponWallet.create(2L, coupon("c4", 4)), + CouponWallet.create(3L, coupon("c5", 5)), + CouponWallet.create(3L, coupon("c6", 6)), + CouponWallet.create(3L, coupon("c7", 7)) + )) + ); + } +} diff --git a/src/test/java/com/moabam/support/fixture/DeleteMemberFixture.java b/src/test/java/com/moabam/support/fixture/DeleteMemberFixture.java new file mode 100644 index 00000000..5da3cd6d --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/DeleteMemberFixture.java @@ -0,0 +1,13 @@ +package com.moabam.support.fixture; + +import com.moabam.api.dto.member.DeleteMemberResponse; + +public final class DeleteMemberFixture { + + public static DeleteMemberResponse deleteMemberResponse() { + return DeleteMemberResponse.builder() + .id(1L) + .socialId("1") + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/InventoryFixture.java b/src/test/java/com/moabam/support/fixture/InventoryFixture.java new file mode 100644 index 00000000..c1be1228 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/InventoryFixture.java @@ -0,0 +1,14 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.item.Inventory; +import com.moabam.api.domain.item.Item; + +public class InventoryFixture { + + public static Inventory inventory(Long memberId, Item item) { + return Inventory.builder() + .memberId(memberId) + .item(item) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/ItemFixture.java b/src/test/java/com/moabam/support/fixture/ItemFixture.java new file mode 100644 index 00000000..3e65477a --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/ItemFixture.java @@ -0,0 +1,58 @@ +package com.moabam.support.fixture; + +import static com.moabam.global.common.util.BaseImageUrl.*; + +import com.moabam.api.domain.item.Item; +import com.moabam.api.domain.item.ItemCategory; +import com.moabam.api.domain.item.ItemImage; +import com.moabam.api.domain.item.ItemType; + +public class ItemFixture { + + public static final String MORNING_SANTA_SKIN_NAME = "산타 오목눈이"; + public static final String MORNING_SANTA_SKIN_IMAGE = IMAGE_DOMAIN + "item/morning_santa.png"; + public static final String MORNING_KILLER_SKIN_NAME = "킬러 오목눈이"; + public static final String MORNING_KILLER_SKIN_IMAGE = IMAGE_DOMAIN + "item/morning_killer.png"; + public static final String NIGHT_MAGE_SKIN_NAME = "메이지 부엉이"; + public static final String NIGHT_MAGE_SKIN_IMAGE = IMAGE_DOMAIN + "item/night_mage.png"; + + public static Item.ItemBuilder morningSantaSkin() { + ItemImage image = ItemImage.builder() + .awakeImage(MORNING_SANTA_SKIN_IMAGE) + .sleepImage(MORNING_SANTA_SKIN_IMAGE) + .build(); + + return Item.builder() + .type(ItemType.MORNING) + .category(ItemCategory.SKIN) + .name(MORNING_SANTA_SKIN_NAME) + .image(image); + } + + public static Item.ItemBuilder morningKillerSkin() { + ItemImage image = ItemImage.builder() + .awakeImage(MORNING_KILLER_SKIN_IMAGE) + .sleepImage(MORNING_KILLER_SKIN_IMAGE) + .build(); + + return Item.builder() + .type(ItemType.MORNING) + .category(ItemCategory.SKIN) + .name(MORNING_KILLER_SKIN_NAME) + .image(image); + } + + public static Item nightMageSkin() { + ItemImage image = ItemImage.builder() + .awakeImage(NIGHT_MAGE_SKIN_IMAGE) + .sleepImage(NIGHT_MAGE_SKIN_IMAGE) + .build(); + + return Item.builder() + .type(ItemType.NIGHT) + .category(ItemCategory.SKIN) + .name(NIGHT_MAGE_SKIN_NAME) + .image(image) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/JwtProviderFixture.java b/src/test/java/com/moabam/support/fixture/JwtProviderFixture.java new file mode 100644 index 00000000..50fa714c --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/JwtProviderFixture.java @@ -0,0 +1,21 @@ +package com.moabam.support.fixture; + +import com.moabam.api.application.auth.JwtProviderService; +import com.moabam.global.config.TokenConfig; + +public class JwtProviderFixture { + + public static final String originIss = "PARK"; + public static final String originSecretKey = "testestestestestestestestestesttestestestestestestestestestest"; + public static final String adminKey = "testestestestestestestestestesttestestestestestestestestestest"; + public static final long originId = 1L; + public static final long originAccessExpire = 100000; + public static final long originRefreshExpire = 150000; + + public static JwtProviderService jwtProviderService() { + TokenConfig tokenConfig = + new TokenConfig(originIss, originAccessExpire, originRefreshExpire, originSecretKey, adminKey); + + return new JwtProviderService(tokenConfig); + } +} diff --git a/src/test/java/com/moabam/support/fixture/MemberFixture.java b/src/test/java/com/moabam/support/fixture/MemberFixture.java new file mode 100644 index 00000000..c0ed7551 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/MemberFixture.java @@ -0,0 +1,39 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.bug.Bug; +import com.moabam.api.domain.member.Member; + +public final class MemberFixture { + + public static final String SOCIAL_ID = "1"; + public static final String NICKNAME = "모아밤"; + + public static Member member() { + return Member.builder() + .socialId(SOCIAL_ID) + .bug(BugFixture.bug()) + .build(); + } + + public static Member member(Long id) { + return Member.builder() + .id(id) + .socialId(SOCIAL_ID) + .bug(BugFixture.bug()) + .build(); + } + + public static Member member(Bug bug) { + return Member.builder() + .socialId(SOCIAL_ID) + .bug(bug) + .build(); + } + + public static Member member(String socialId) { + return Member.builder() + .socialId(socialId) + .bug(BugFixture.bug()) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java b/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java new file mode 100644 index 00000000..6f43c132 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/MemberInfoSearchFixture.java @@ -0,0 +1,40 @@ +package com.moabam.support.fixture; + +import static com.moabam.global.common.util.BaseImageUrl.*; + +import java.util.List; + +import com.moabam.api.domain.member.BadgeType; +import com.moabam.api.dto.member.MemberInfo; + +public class MemberInfoSearchFixture { + + private static final String NICKNAME = "nickname"; + private static final String PROFILE_IMAGE = IMAGE_DOMAIN + MEMBER_PROFILE_URL; + private static final String INTRO = "intro"; + private static final long TOTAL_CERTIFY_COUNT = 15; + private static final String MORNING_EGG = IMAGE_DOMAIN + DEFAULT_MORNING_EGG_URL; + private static final String NIGHT_EGG = IMAGE_DOMAIN + DEFAULT_NIGHT_EGG_URL; + + public static List friendMemberInfo() { + return friendMemberInfo(TOTAL_CERTIFY_COUNT); + } + + public static List friendMemberInfo(long total) { + return List.of( + new MemberInfo(NICKNAME, PROFILE_IMAGE, MORNING_EGG, NIGHT_EGG, INTRO, total, BadgeType.BIRTH, + 0, 0, 0), + new MemberInfo(NICKNAME, PROFILE_IMAGE, MORNING_EGG, NIGHT_EGG, INTRO, total, BadgeType.LEVEL10, + 0, 0, 0) + ); + } + + public static List myInfo(String morningImage, String nightImage) { + return List.of( + new MemberInfo(NICKNAME, PROFILE_IMAGE, morningImage, nightImage, INTRO, TOTAL_CERTIFY_COUNT, + BadgeType.BIRTH, 0, 0, 0), + new MemberInfo(NICKNAME, PROFILE_IMAGE, morningImage, nightImage, INTRO, TOTAL_CERTIFY_COUNT, + BadgeType.LEVEL10, 0, 0, 0) + ); + } +} diff --git a/src/test/java/com/moabam/support/fixture/ModifyImageFixture.java b/src/test/java/com/moabam/support/fixture/ModifyImageFixture.java new file mode 100644 index 00000000..afee62fd --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/ModifyImageFixture.java @@ -0,0 +1,28 @@ +package com.moabam.support.fixture; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.springframework.mock.web.MockMultipartFile; + +import com.moabam.api.dto.member.ModifyMemberRequest; + +public class ModifyImageFixture { + + public static MockMultipartFile makeMultipartFile() { + try { + File file = new File("src/test/resources/image.png"); + FileInputStream fileInputStream = new FileInputStream(file); + + return new MockMultipartFile("1", "image.png", "image/png", fileInputStream); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public static ModifyMemberRequest modifyMemberRequest() { + return new ModifyMemberRequest("intro", "sldsldsld"); + } + +} diff --git a/src/test/java/com/moabam/support/fixture/ParticipantFixture.java b/src/test/java/com/moabam/support/fixture/ParticipantFixture.java new file mode 100644 index 00000000..e8d60215 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/ParticipantFixture.java @@ -0,0 +1,44 @@ +package com.moabam.support.fixture; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; + +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; + +public final class ParticipantFixture { + + public static Participant participant(Room room, Long memberId) { + return Participant.builder() + .room(room) + .memberId(memberId) + .build(); + } + + public static Stream provideParticipants() { + Room room = RoomFixture.room(10); + + return Stream.of(Arguments.of(List.of( + ParticipantFixture.participant(room, 1L), + ParticipantFixture.participant(room, 3L), + ParticipantFixture.participant(room, 7L) + ))); + } + + public static Stream provideRoomAndParticipants() { + Room room = RoomFixture.room(10); + + return Stream.of(Arguments.of( + room, + List.of( + ParticipantFixture.participant(room, 1L), + ParticipantFixture.participant(room, 2L), + ParticipantFixture.participant(room, 3L), + ParticipantFixture.participant(room, 5L), + ParticipantFixture.participant(room, 7L) + )) + ); + } +} diff --git a/src/test/java/com/moabam/support/fixture/PaymentFixture.java b/src/test/java/com/moabam/support/fixture/PaymentFixture.java new file mode 100644 index 00000000..464652fb --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/PaymentFixture.java @@ -0,0 +1,60 @@ +package com.moabam.support.fixture; + +import static com.moabam.support.fixture.ProductFixture.*; + +import com.moabam.api.domain.coupon.Coupon; +import com.moabam.api.domain.payment.Order; +import com.moabam.api.domain.payment.Payment; +import com.moabam.api.domain.product.Product; +import com.moabam.api.dto.payment.ConfirmPaymentRequest; +import com.moabam.api.dto.payment.ConfirmTossPaymentResponse; + +public final class PaymentFixture { + + public static final String PAYMENT_KEY = "payment_key_123"; + public static final String ORDER_ID = "random_order_id_123"; + public static final int AMOUNT = 3000; + + public static Payment payment(Product product) { + return Payment.builder() + .memberId(1L) + .product(product) + .order(order(product)) + .totalAmount(product.getPrice()) + .build(); + } + + public static Payment paymentWithCoupon(Product product, Coupon coupon, Long couponWalletId) { + return Payment.builder() + .memberId(1L) + .product(product) + .couponWalletId(couponWalletId) + .order(order(product)) + .totalAmount(product.getPrice()) + .discountAmount(coupon.getPoint()) + .build(); + } + + public static Order order(Product product) { + return Order.builder() + .name(product.getName()) + .build(); + } + + public static ConfirmPaymentRequest confirmPaymentRequest() { + return ConfirmPaymentRequest.builder() + .paymentKey(PAYMENT_KEY) + .orderId(ORDER_ID) + .amount(AMOUNT) + .build(); + } + + public static ConfirmTossPaymentResponse confirmTossPaymentResponse() { + return ConfirmTossPaymentResponse.builder() + .paymentKey(PAYMENT_KEY) + .orderId(ORDER_ID) + .orderName(BUG_PRODUCT_NAME) + .totalAmount(AMOUNT) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/ProductFixture.java b/src/test/java/com/moabam/support/fixture/ProductFixture.java new file mode 100644 index 00000000..73cb81cd --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/ProductFixture.java @@ -0,0 +1,20 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.product.Product; +import com.moabam.api.domain.product.ProductType; + +public class ProductFixture { + + public static final String BUG_PRODUCT_NAME = "황금벌레 10"; + public static final int BUG_PRODUCT_PRICE = 3000; + public static final int BUG_PRODUCT_QUANTITY = 10; + + public static Product bugProduct() { + return Product.builder() + .type(ProductType.BUG) + .name(BUG_PRODUCT_NAME) + .price(BUG_PRODUCT_PRICE) + .quantity(BUG_PRODUCT_QUANTITY) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/PublicClaimFixture.java b/src/test/java/com/moabam/support/fixture/PublicClaimFixture.java new file mode 100644 index 00000000..06bbb415 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/PublicClaimFixture.java @@ -0,0 +1,15 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.member.Role; +import com.moabam.global.auth.model.PublicClaim; + +public class PublicClaimFixture { + + public static final PublicClaim publicClaim() { + return PublicClaim.builder() + .id(1L) + .nickname("nickname") + .role(Role.USER) + .build(); + } +} diff --git a/src/test/java/com/moabam/support/fixture/RankingInfoFixture.java b/src/test/java/com/moabam/support/fixture/RankingInfoFixture.java new file mode 100644 index 00000000..9058ebcc --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/RankingInfoFixture.java @@ -0,0 +1,5 @@ +package com.moabam.support.fixture; + +public class RankingInfoFixture { + +} diff --git a/src/test/java/com/moabam/support/fixture/ReportFixture.java b/src/test/java/com/moabam/support/fixture/ReportFixture.java new file mode 100644 index 00000000..a3d48301 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/ReportFixture.java @@ -0,0 +1,30 @@ +package com.moabam.support.fixture; + +import com.moabam.api.domain.report.Report; +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.Room; +import com.moabam.api.dto.report.ReportRequest; + +public class ReportFixture { + + private static Long reportedId = 99L; + private static Long roomId = 1L; + private static Long certificationId = 1L; + + public static Report report(Room room, Certification certification) { + return Report.builder() + .reporterId(1L) + .reportedMemberId(2L) + .room(room) + .certification(certification) + .build(); + } + + public static ReportRequest reportRequest() { + return new ReportRequest(reportedId, roomId, certificationId, "description"); + } + + public static ReportRequest reportRequest(Long reportedId, Long roomId, Long certificationId) { + return new ReportRequest(reportedId, roomId, certificationId, "description"); + } +} diff --git a/src/test/java/com/moabam/support/fixture/RoomFixture.java b/src/test/java/com/moabam/support/fixture/RoomFixture.java new file mode 100644 index 00000000..b286c880 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/RoomFixture.java @@ -0,0 +1,170 @@ +package com.moabam.support.fixture; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.mock.web.MockMultipartFile; + +import com.moabam.api.domain.room.Certification; +import com.moabam.api.domain.room.DailyMemberCertification; +import com.moabam.api.domain.room.DailyRoomCertification; +import com.moabam.api.domain.room.Participant; +import com.moabam.api.domain.room.Room; +import com.moabam.api.domain.room.RoomType; +import com.moabam.api.domain.room.Routine; + +public class RoomFixture { + + public static Room room() { + return Room.builder() + .title("testTitle") + .roomType(RoomType.MORNING) + .certifyTime(10) + .maxUserCount(8) + .build(); + } + + public static Room room(int certifyTime) { + return Room.builder() + .id(1L) + .title("testTitle") + .roomType(RoomType.MORNING) + .certifyTime(certifyTime) + .maxUserCount(8) + .build(); + } + + public static Room room(String title, RoomType roomType, int certifyTime) { + return Room.builder() + .title(title) + .roomType(roomType) + .certifyTime(certifyTime) + .maxUserCount(8) + .build(); + } + + public static Room room(String title, RoomType roomType, int certifyTime, String password) { + return Room.builder() + .title(title) + .password(password) + .roomType(roomType) + .certifyTime(certifyTime) + .maxUserCount(8) + .build(); + } + + public static Participant participant(Room room, Long memberId) { + return Participant.builder() + .room(room) + .memberId(memberId) + .build(); + } + + public static Routine routine(Room room, String content) { + return Routine.builder() + .room(room) + .content(content) + .build(); + } + + public static List routines(Room room) { + List routines = new ArrayList<>(); + + Routine routine1 = Routine.builder() + .room(room) + .content("첫 루틴") + .build(); + Routine routine2 = Routine.builder() + .room(room) + .content("두번째 루틴") + .build(); + + routines.add(routine1); + routines.add(routine2); + + return routines; + } + + public static Certification certification(Routine routine) { + return Certification.builder() + .routine(routine) + .memberId(1L) + .image("test1") + .build(); + } + + public static DailyMemberCertification dailyMemberCertification(Long memberId, Long roomId, + Participant participant) { + return DailyMemberCertification.builder() + .memberId(memberId) + .roomId(roomId) + .participant(participant) + .build(); + } + + public static List dailyMemberCertifications(Long roomId, Participant participant) { + + List dailyMemberCertifications = new ArrayList<>(); + dailyMemberCertifications.add(DailyMemberCertification.builder() + .roomId(roomId) + .memberId(1L) + .participant(participant) + .build()); + dailyMemberCertifications.add(DailyMemberCertification.builder() + .roomId(roomId) + .memberId(2L) + .participant(participant) + .build()); + dailyMemberCertifications.add(DailyMemberCertification.builder() + .roomId(roomId) + .memberId(3L) + .participant(participant) + .build()); + + return dailyMemberCertifications; + } + + public static DailyRoomCertification dailyRoomCertification(Long roomId, LocalDate today) { + return DailyRoomCertification.builder() + .roomId(roomId) + .certifiedAt(today) + .build(); + } + + public static MockMultipartFile makeMultipartFile1() { + try { + File file = new File("src/test/resources/image.png"); + FileInputStream fileInputStream = new FileInputStream(file); + + return new MockMultipartFile("1", "image.png", "image/png", fileInputStream); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public static MockMultipartFile makeMultipartFile2() { + try { + File file = new File("src/test/resources/image.png"); + FileInputStream fileInputStream = new FileInputStream(file); + + return new MockMultipartFile("2", "image.png", "image/png", fileInputStream); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public static MockMultipartFile makeMultipartFile3() { + try { + File file = new File("src/test/resources/image.png"); + FileInputStream fileInputStream = new FileInputStream(file); + + return new MockMultipartFile("3", "image.png", "image/png", fileInputStream); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/com/moabam/support/fixture/TokenSaveValueFixture.java b/src/test/java/com/moabam/support/fixture/TokenSaveValueFixture.java new file mode 100644 index 00000000..efc64305 --- /dev/null +++ b/src/test/java/com/moabam/support/fixture/TokenSaveValueFixture.java @@ -0,0 +1,24 @@ +package com.moabam.support.fixture; + +import com.moabam.api.dto.auth.TokenSaveValue; + +public class TokenSaveValueFixture { + + public static TokenSaveValue tokenSaveValue(String token, String ip) { + return TokenSaveValue.builder() + .refreshToken(token) + .loginIp(ip) + .build(); + } + + public static TokenSaveValue tokenSaveValue(String token) { + return TokenSaveValue.builder() + .refreshToken(token) + .loginIp("127.0.0.1") + .build(); + } + + public static TokenSaveValue tokenSaveValue() { + return tokenSaveValue("token"); + } +} diff --git a/src/test/java/com/moabam/support/snippet/CouponSnippet.java b/src/test/java/com/moabam/support/snippet/CouponSnippet.java new file mode 100644 index 00000000..852c6f4c --- /dev/null +++ b/src/test/java/com/moabam/support/snippet/CouponSnippet.java @@ -0,0 +1,50 @@ +package com.moabam.support.snippet; + +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; + +import org.springframework.restdocs.payload.RequestFieldsSnippet; +import org.springframework.restdocs.payload.ResponseFieldsSnippet; +import org.springframework.restdocs.snippet.Snippet; + +public final class CouponSnippet { + + public static final RequestFieldsSnippet CREATE_COUPON_REQUEST = requestFields( + fieldWithPath("name").type(STRING).description("쿠폰명"), + fieldWithPath("description").type(STRING).description("쿠폰 간단 소개 (NULL 가능)"), + fieldWithPath("type").type(STRING).description("쿠폰 종류 (아침, 저녁, 황금, 할인)"), + fieldWithPath("point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("maxCount").type(NUMBER).description("쿠폰을 발급 최대 갯수"), + fieldWithPath("startAt").type(STRING).description("쿠폰 발급 시작 날짜 (Ex: yyyy-MM-dd)"), + fieldWithPath("openAt").type(STRING).description("쿠폰 정보 오픈 날짜 (Ex: yyyy-MM-dd)") + ); + + public static final ResponseFieldsSnippet COUPON_RESPONSE = responseFields( + fieldWithPath("id").type(NUMBER).description("쿠폰 ID"), + fieldWithPath("adminId").type(NUMBER).description("쿠폰 관리자 ID"), + fieldWithPath("name").type(STRING).description("쿠폰명"), + fieldWithPath("description").type(STRING).description("쿠폰에 대한 간단 소개 (NULL 가능)"), + fieldWithPath("point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("maxCount").type(NUMBER).description("쿠폰을 발급 최대 갯수"), + fieldWithPath("type").type(STRING).description("쿠폰 종류 (MORNING, NIGHT, GOLDEN, DISCOUNT)"), + fieldWithPath("startAt").type(STRING).description("쿠폰 발급 시작 날짜 (Ex: yyyy-MM-dd)"), + fieldWithPath("openAt").type(STRING).description("쿠폰 정보 오픈 날짜 (Ex: yyyy-MM-dd)") + ); + + public static final Snippet COUPON_STATUS_REQUEST = requestFields( + fieldWithPath("opened").type(BOOLEAN).description("쿠폰 정보가 오픈된 쿠폰 (true, false)"), + fieldWithPath("ended").type(BOOLEAN).description("종료된 쿠폰 (true, false)") + ); + + public static final ResponseFieldsSnippet COUPON_STATUS_RESPONSE = responseFields( + fieldWithPath("[].id").type(NUMBER).description("쿠폰 ID"), + fieldWithPath("[].adminId").type(NUMBER).description("쿠폰 관리자 ID"), + fieldWithPath("[].name").type(STRING).description("쿠폰명"), + fieldWithPath("[].description").type(STRING).description("쿠폰에 대한 간단 소개 (NULL 가능)"), + fieldWithPath("[].point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("[].maxCount").type(NUMBER).description("쿠폰을 발급 최대 갯수"), + fieldWithPath("[].type").type(STRING).description("쿠폰 종류 (MORNING, NIGHT, GOLDEN, DISCOUNT)"), + fieldWithPath("[].startAt").type(STRING).description("쿠폰 발급 시작 날짜 (Ex: yyyy-MM-dd)"), + fieldWithPath("[].openAt").type(STRING).description("쿠폰 정보 오픈 날짜 (Ex: yyyy-MM-dd)") + ); +} diff --git a/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java b/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java new file mode 100644 index 00000000..d4a887cb --- /dev/null +++ b/src/test/java/com/moabam/support/snippet/CouponWalletSnippet.java @@ -0,0 +1,18 @@ +package com.moabam.support.snippet; + +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; + +import org.springframework.restdocs.payload.ResponseFieldsSnippet; + +public final class CouponWalletSnippet { + + public static final ResponseFieldsSnippet COUPON_WALLET_RESPONSE = responseFields( + fieldWithPath("[].walletId").type(NUMBER).description("쿠폰지갑 ID"), + fieldWithPath("[].id").type(NUMBER).description("쿠폰 ID"), + fieldWithPath("[].name").type(STRING).description("쿠폰명"), + fieldWithPath("[].description").type(STRING).description("쿠폰에 대한 간단 소개 (NULL 가능)"), + fieldWithPath("[].point").type(NUMBER).description("쿠폰 사용 시, 제공하는 포인트량"), + fieldWithPath("[].type").type(STRING).description("쿠폰 종류 (MORNING, NIGHT, GOLDEN, DISCOUNT)") + ); +} diff --git a/src/test/java/com/moabam/support/snippet/ErrorSnippet.java b/src/test/java/com/moabam/support/snippet/ErrorSnippet.java new file mode 100644 index 00000000..69e2c5d3 --- /dev/null +++ b/src/test/java/com/moabam/support/snippet/ErrorSnippet.java @@ -0,0 +1,13 @@ +package com.moabam.support.snippet; + +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; + +import org.springframework.restdocs.snippet.Snippet; + +public class ErrorSnippet { + + public static final Snippet ERROR_MESSAGE_RESPONSE = responseFields( + fieldWithPath("message").type(STRING).description("에러 메시지") + ); +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 00000000..7083d3d7 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,93 @@ +logging: + level: + org.hibernate.SQL: debug + org.springframework: DEBUG + +spring: + + # Profile + profiles: + active: test + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3305/moabam?serverTimezone=UTC&characterEncoding=UTF-8 + username: root + password: 1234 + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + format_sql: true + highlight_sql: true + database: mysql + + # Redis + data: + redis: + host: 127.0.0.1 + port: 6379 + + # AWS + cloud: + aws: + region: + static: ap-test-test + s3: + bucket: test + url: test + cloud-front: + url: test + credentials: + access-key: test + secret-key: test + max-request-size: 10MB # 요청 당 최대 사이즈 + +oauth2: + client: + provider: test + client-id: testtestetsttest + client-secret: testtestetsttest + authorization-grant-type: authorization_code + admin-key: testtesttesttesttesttesttest + scope: + - profile_nickname + - profile_image + + provider: + authorization_uri: https://authorization.com/test/test + redirect_uri: http://redirect:8080/test + token-uri: https://kauth.kakao.com/oauth/token + token-info: https://kapi.kakao.com/v1/user/access_token_info + unlink: https://kapi.kakao.com/v1/user/unlink + admin-redirect-uri: https://dev-admin.moabam.com/login/kakao/oauth + + +token: + iss: "PARK" + access-expire: 100000 + refresh-expire: 150000 + secret-key: testestestestestestestestestesttestestestestestestestestestest + admin-secret: testestestestestestestestestesttestestestestestestestestestest + +allows: + admin-domain: "localhost" + domain: "localhost" + origin: + - "https://test.com" + - "https://test.com" + +admin: moamoamoabam + +# Payment +payment: + toss: + base-url: "https://api.tosspayments.com" + secret-key: "test_sk_4yKeq5bgrpWk4XYdDoBxVGX0lzW6:" + +# Webhook +webhook: + slack: + url: test diff --git a/src/test/resources/image.png b/src/test/resources/image.png new file mode 100755 index 0000000000000000000000000000000000000000..0950368c3e8259058e328e9c713cc71766f4da93 GIT binary patch literal 52270 zcmd?S2~?BU);AvKp#_U5s7w_K6cG@SF%YUEhzzMxnTJZ05JHH|Qv%-FGLu|InISDx zL?$7DFa@d%DKbO|1W6=F5F$eeAq;^K_{aA4eeZqWcYW*oukYG+t@m*)o;>@UXYb$H zXP;@G{Tz2*@4N+kcFo$>8n9~@0I*B)53s`oTn6mlyKn!#z5Dm?+kfD|{)2}{IL@!<_ zfddB)A3Q93_^|Akr{qq3`TzUc`57Q{aNm!I0eg0-0(Q&n+9R`TrxT#~0p`9BaR1}l zwMPQ?!0v;RREN)Y0e0`&y=UK^L;LqgO0j1jK=QO#X5VT3{YNie50*W4>wxNcgTFm^ z_*3l-r*A@@4<1*$bnt?2#^ll$-xcxYP0NbvkBOMlBjTpll&b9;L|;l zw`BIn04xC;A}8F1fb*SqTCKc&RdqlC-u%4vG0S7QHS789RLiFsPiM}Ex{(vNw{FD# zb}QLk)9NQ)1b1mlZfoGKGhh8jhJpAG`95Ued`Qt+&))&G^$1I?_93IGqJ?%0__y0P zU)}siQpxXGuI}^skcLb7H484~n|sc`CiMz`%hLN#T(0v>cL3-N{(z zw)s`gKM1=HZ?8VVZMXSQw9_q5uww#GK7$D{1UbdQ2Kz8-z~~q8T{1v06~f8hLE5sP-Ww*^=xNu+{A<=bi>vKYDiDng>5jo&HG9||joGX=K=#7i(te0on*#YE8bOf&%p$`Vk5jrU****GVJNRk@k-?}G7!3G@*z-23zWVUi zzewBF_`7^?dq8_Kbq8?X`RBl<^>G)zY5R=r2N@D<9{Xo{aT3w8648aCr`wvwTRk6W z*bfk8@+1oWjjKn(2ik8CKjc&UkneAGoDUHFBsRBLA(U7%#(8VNZ|3E;_yeP{Er(IE=^6;$NySp&GvvDK*|Ty z@y!!CeMUb}{*%zeW3;mgJAgjiBI)((|40&YM86eb>K~kbl^(lRBg~%=s3Sk1_?10J zqSAk`m%LK-GeZ{cP-WEG$ACuX6wKUtxA)$Ebi03@AG@n7#2h^Vb*`kmdU$?*K0R zW&~;N{gzMvw;4fMv3N>P0G=x=f3mrS`(LU6&0iIOn_rHXn8K~k|ALM6BSI?wt=u)A zt^fYx=)I39?ytvsmy_}6yKR{Go00S4@Et&I{LORge?62R6VX2V39tAW+=v6uc+_X3 z^sVOKKc=+59`D%x)zNKObYf+@L=9QlhrasjLHwAA*0bhowuS^p+~S&d0R4LB{iVz8+hS((4X$4gbf|YVYxPmo-sO9LJ&Yd_(Jr1IT~hm; z=kuC$AG$Yccl1{uQQBXS^?Js<-n<^@h#RPYe24S1=A*wL#*YZ;Nc6Aees22Vro7Ai z$B!uMFUUHkXN5ce<&wkp|IX64(@0OvW#jS4Z=wzrehs+y7p$R=2I)YB7#LIh+<4P@ z)#hH#G~nPzlk`_)9Me98`{o*Qg)?$gbi`#dbS~Lp`!V^tD&i&J zvwtu5zc{zmcs%IIBj&8fpI-5YzWX|g`|ekDeesq0gI7x+_3Br1hPe*Pfz5%6%eSi{ zQ+5Co*bMF0LA`!ly`zf*!WGwu22|ViX%#*=k@=3-EM+9EQrc1WYAVe{-+H(-z9yfV z)*EDQ5Dywqxj7&9${tdQMDmYhX=s?GQ>HK9n|e$BDQ^}wJ~&swt?dk}T&af)#n?RR zaDZoL7nIUzoa$nw5L)uRy~ck`EozKIhaXN{L)%`G*x|K5M&|z@WtZkpZ`Bm9alBIvXeVN2GFQolO1wBUrw7(ld*ltlLlF9&mx zJ#X&DQLS5SpEeZhDv{ab@d_COQcSzl8YV92r?KjN#S80)%Tlj+ae-ofu`4 zSjjU+xqwfxj#m%$S#_i2@s_5^jJTe$fJV?bCo7FQ2V~xKXNwB%tMJT!VwVO5gtvweS&0vdB_U zNe2DTHZQ}8sHDgv58L`FhbCn@!!IyBDr@>Ds?EJ1T#^siEz~E2wQt*kVSXZ{b0AB8 zV{A>FREP^)7WrHRo8~aoUaA@;Vb#+uj0lqSo^G-p5iRA{8rS12so=gqdUBD zod&CFI85DK?xwYOYmbg1Q)~MA1_T4BVUsxQ0qIw;758ggJdM`m=!NowP8k>&%vHbl zrHmcP3J>}T5fRkqmP~Xu#zFlyR44U+S{Scy(r!S6uVWL;ma%P-xH<)D zBBjg5wvoZ%vYKU!CdjQGBPAL}t*@ory5yy(_C<5gCnJt4BY3>4W9=y7AyYj%HsPh1~?pPOBxBQn(4k1IoSa+c8g9bp$HTY+{z zb&x{A7=50yffAHkz~Lfkqp`>(L0l=_QOVr5a)TuF{81if{s!>Ljq}dKg;~ip6Ftj| z)#QF>_YHT50Bvi_Se+>2<-}f02-^zvN{E^2j}g=A%F4_;$@r409Y9EXhw|=QZma6B=>RKeefic4%NH%$ zDI413)G8bjF$G&(sYElW+kSn9i>)T&NS9Lxbv%ouw%IVzvsXHG=BUbN2k-6x9&cWW zy_M+;$~+rN+IHRUKi!6^vmSq1x$zeN{#AY-uGz*cIca)<>c~7&u}!V4RVBr%V`jljjV@6AN#Ony9ZAB#mBn%=;5Z>XHs9 zfjri)Vrfw+&;oqLIc=g{K_s5{*T)Okw zpJ@vFA$6X{TRy}XspF)SJH0Z|%Qe{PqnkDXqX9#;?#@P4kP@qqB++R>;%$@4yN-kY z^<&xw*yGzM8}EhXf;OOiTPiK!9Q;D#s+l>lAK$CA5Q#S-#@k{tWCt|lh%{wy?UC>v zqZ01g4q$a*A1U2d>6Qw7MB9jXC5E@ROf7n>GtNz?J%DWvzppaezUq&_PVY%}N?EJ% zdD0cMcA`C0af4nvo-Fb`HJ=mBqUlw=*B{U^)H3h9sDv5^zY8VQ!hJ9S6_(TXQ#32e z=Gw-nNHrP-eLLf>C@<+w-cn?ZdZH?0`r2|Q8Hy-_#Mu=SP>sV?itO)QN#JwGc_e56 z{A5*7MiZY2MJ_ciy}RdwlFYI{SdIm%Xa^vMb+w)3s^q=*kslDZx5qbly2o!{nOb~| z0Drbxn1Nlbu}o;)oj3l+bRWux9@$vEJZ&@$Q2!Vv`G3Pxit@}%TBcj`W{0qIl4cXh z@EvjVr^I;RBVp721?$X?pz6o3+xq8Yf#v=7o_5lN@BPx*58C+1Ppm?*iCUbs%irIc zV2t|n9L46_($lY{E+5YbQ?X(2*F3zb)zjNAcy zDhYMk?3S7db2E(%*qFkd6x8@=h?c<&#pZnZx1N^}rwY#6zs<7&y#t$p@vcJTlI!!J zF$S9DG92SGJhl0H1FtU(`&`~z;*HtGQvuR7OL}L#2LmhwnE6!|GBsAbmV;L4T8*20 zVc6W1jfH%-j&)6D3dRH)b`CWL7JfGk4Q5Vr$(pGF?|QZ_|9rT{_y5KO{@?Rxe4S%c z_h&8Q4zxybW3Ry6>hUF32dACgoqn2DY&-QGIya(Qq2 z0))8z_f6v~9T!fWx)K(nV{`)hnxY^UL%EMRF%cZtyYW-7^{gYB7255aQ9FRvMkq8^ zi_tGfQm(o9Qyb1Aa&f{Ct~8N$r5W5cEYiMDG$}R|*;IDn+FciK_-;3z zRB_f%Yk^FA8QY0S1)tILaM*=m+A({P#XgvAPGJkh1FW|&PnZX)asBgUVB;*>>Q-|M zy}mLE)mt;^zO52nMzYJ>SKOhl(f$;ZcoTQIf>J)jdQD&x-Lo(r_MjhoF*&u4=EK$Y zq4caucTKux#y3+Ie|zf*CI@_5pbXmqcpsWxqsN!^8`0cNvSJ0pp}*6Ptf`zq0)$X1LeGKTJMYa3d90p#kJ0Fh&{<@ldZ^I zuPNz=I|8E_1Y`@Buis5)ER75cZU{9t?RY8^JAgMv)&AGyy+4blmW96cdOhS3%2V}Lcl%g3ob zJ~qX}^P!{C8T;+W5HGc__4SD{k2~V7)nr}4_tcEWDqZ1_GmgVpDRh+a#B7{n`p=EJ z_x}b%Cw@xnHpymeXhgPw-L?x9%DQ#{uoxT@Nm_s#z z;>VcS&4oFO`~}kCwexEr!LxdM!4T20 zA2#c^B*Siu8VX3u6S(SrX6h1%?y_$Q7QPv$Uy{SKmhv`ZFn0hm z$?4nr`sQztKB>A=ekk`e?_NVaeN;RFxDv`^N1+u;JEu$Bn@`J(@rY z`+pxD{;xP3e4k?#_WjF0#R%1Ta=<%1CuU`hNM9!LCyFWGH;?vc-hs%O1uve)0Cxb} z1DTkNp%8RNt4+7@Qu=bKAB}hhUjJzDRz68GsNc3Im z#g2Vo5y?`E#_F``wAsl|I(!9fa%8p+wrVn^q4=P_+(5q-opE$}{OQK|9=SIBz1#W3 zd9%O<%vSu_<T%t!l#UnTS} z$B*@JxC3Ullw=EcMlvEWT1Ytp{b4B_7ms7D;b! zN*2G+rP(UibZ@V^IX?(42J$$5nMbPZ1(ENo1R~AbkfY6MbAo9f`+f zS^!LH?SjF<*SZCDPQ4|9q%9kUe}x@l9p!_QdS{aXef@$4I6{5M-EQIAu(K&y=^6X)V%zV#u~Nj=PE&w!P0A5H)lcs*>Avuo|eSWfw&zDs$HLCSPz%XmriRkUilDd5mAV!-QL~ zz7gEe0)Dke<6q+f?QAx#FSG_@l+5j}TwNEdYqrab>V7HqBcm-;HKQE-;QEFIWoGBd zzN-wE3ue9V1=j8$mu!Zg5%ab&_OWh1_`sC%dI@Fx6h`eamqt zxG{Wge%OvWb_aj--e+(A=eqc}Qva+D<*4GQyTyKj$3v}R+H`+xNXk$K?18MlD~qKZ z6_?8($4P-qcs@Gp2P!ACXpMJi4)^77@*|U_!J|X9#5jz{Ys(CAL`E{K5huSmJGF00 z{oHaMV{pKjS%O^KQgVjo&J<=S^tEc1*S{998f!hGFkkQPeR=2-dKaWCWT@HW#-JRz z*-D6Vy~2}gopAHdVQI^_x?LjUqr=aQHvApXRnL>+$hosrHIaw>h>R`M8EwnyiJ%PH zLZ-A@bW#!v`rKt)5arum)>i?;oT{6u+6cU?(y<&+p-k0PU=Yl!f;`Zb*a@9M#G9>b zk>+hcPvt*X92ol$0Eq>^m`Y!gf*wh#eAny6k1T%w4inCqobuW#6-3~r9gJcp`)$jv zxx)jPk2)z{Q_oj5-_OKrqchW9S5b(D7;$vKtTM>WvP7IQ1-v;7nOID$S{Zdpb1pEl z<(tRrT7Tb%82%>qj~JW&96Q>jlUGZ|(j)S8co@ij?6J<<)VK!*lQL75`w2>M(mR0V zTPf$(G%penT%C-w+$jSDX_fJ8ETv$L;aN?nZb)@l)fa$HPkkS;=ibsB9r+!jua?*^ zpz7=ZGELEZf^ctXm9tD>L2_NmhLJ}pSwba2*XPb74Yq3vJ~8Yj=-Q8QO*JCZ*{Z11K~?6>E9n)V?P@-${l46Igk7`ckmBbT z(<}dcMIhGz@1*Edw9oaBAlSpF@_2~i*Sl(6?*6m2004Vi?=+ab?KexRdS(rbbtNKY z9q>`v{B%;R0~BsW`MwB6pae0k+5)(sEZd@~+)8uf>t5C=dToji3lgVC*8}xQVA$eT+siu)q@gu9%nlT`ytp8#!5n*todN#zx=oE|4YXAAJ)_+Cu z5uSdX|JoOsI5d6r$Auv8z8L-DF09`3vb0iA@%S_EK@Y!_ND)qngt?>v)5>+b&P{Cjt>GJu!Hyzu*5Bm{%|ED68JVbThkW?BzIEn=j$gn` z?~1kq37a>~5B%hly9<%Sw|ip25wuq4_L1tfcRI>Yp3P(T);9!_kgeM!Dln z75ZQ9%6;}BTfPI()41_aI(H_PFjefc@^k!<2qg;C?CKqAxY8{kGU%$bCgOzVp-K$*v`M(JJ z_dnWL_4b7&mRsHZBR4K&6Vsw$u}!QkD5NNp_WKt=*BX)Q4o#;5N9%$iZr z6|AHQv6r8P>dCEws)hJn8~F-}F4rGsyj&@<*=|81d-9#4SWqa?`0HF&@Fc|ux3)d6 z@(^*r|H9TK1BdhMNn@-4Vti)e5m#T99%nf|GWEP>4M?o#f-i>YLk*-aX1&_gx7V}h_$s)Hy(0!#B^zf_AWmr&F$PfW*m3kP_pCqm1Ewqcc~(?$SF!tpiMxd zhP2L$Wp=&x$siSC9aZ~S%EpzIMH+~9aNH^3(vqiFa1h?A^bb_3tPpiJ`x3c|Cw zkC$yfawMQ13j6BNzj~(+`Kz4uRGTnSR#wC0%?vV|qOs$QXtcy2mXR-G$htZa8IsCV zn55cvA-@Ofh#@tvJJe(6&$ZLSol1sX^f=U|jk{iaw49?-=c>E8{#&Ah8niEFQGreOXp5ZqWe+@Ucg(=gdP19nbbcVg zLHcB?%~0#WNKK_=SzD;|Vs*p+0GHN4Sey}<8wi#_$RGX=W%8MHo z;QGNgpQc@3TV6rS{>1djK-KDlm)|Ku@6VfnV%YQ{En^lehT7jS9=!vI#Kq9ELquMAL`4OMZX$?R!K#&IRm}q337k(O^6o9^D&afs8(A3g+`00- zGAvU5z}(1aq!SFQ+va13*0BlKQF^f4u@+yCI*{-lVwcdgbR&ZpIcs1nl$Oj zpCb`?i&=gHF#b+L+`t-mA0lu}{{{hMmYjtBKGapD+d;EXMWoQL}(HC*_oNC|( z!^=K;wK*!qVOB;e!xCYlarT?m0CFjk*5pQ-ReXeGR!iBl2130dNc^#!r;l4MkLA!} ziJa#qnlla-`Rj4qW|XsgTwjFGb{tvTWvJY_8!(h=I0{LazU1Z`vAC=CbWP$`~$d+S<;fdPLaV^7{Y zk&Aoev-xM<+s>7p;JJauXJCVKHF;hm!F>)e4;!|ZtX;P-sOAd@ZT{}<)^5K0yX1l_ zfj<$^iqQR}Gs?03hucgSd=C~j{jio*Gv*)jyjxg^gLTvlTSdp(64A6)BggM_I7*M^ z=X!k$`<=Ct!-Levlx3=V@^n`C_G895WF12e=ViorUtwMT=vS*zRuFK2~WVO!$uf@hdK z4mcn1&kbL8o_$XdKAo2k+pgSEQ|hNxL6LOBe5u^An7hGz4{f8XO#{m*Pa#_y!rBR2#}r|14)$H%U-96ym> z7w}5#4j^%My~}oSLWk3Gjrxd+dtv(Qar%rJ<@w5i9!cZZUy?q6LCURj9UvMA&ihV5 zN+-&gGu2`q#w*Bgs~Lt|yx(RSw$vS=`OHP)1Cyb28lKvGH|*Sig{Bb38NR-eIh?6? ztaP!>rAH~&uQs|#9$9Yj<+-RI_g{l~F3k>(UYBPZ_M!~aMcEq;_+Io>7T)k#s!dqn zSn3y3M0YiGUP2zpbR`NeoqDmX>pRfX+SW+O6v?Ltj%(P0>xf4BQ;)^<>+?1j=~w;J zb-xr4N`soJvbm{Tl>~?z!&|?yYe6s{;$X}IzrUR>jEI*H6RD-jS&b ztD)cUg;fqaLrS{sLbm8VkTJIz1??xj-a7hXw?K@da;js9`ihWVLwZ$Ji zD(?0*p8h4wu^Uw|&33JYNA+_{hrEHW`INIcYE;}+oT*5yGl>Iyi;-=_FLZhj;zhf z3*{F`jsZbl8D+Ig7RFm`@R8;Ohagyvxb|mtse!NlkJ{YLOnJ{qS|a7Lc zA$~b#2Ve(R&1xuQZQr7PhLbouzW+Z;{x>ALyr&f1nQ6bIT>1yP;rDh1Nm?5(D)?Q0 zZuYaGvGc4!_ZzjX44P!QIdErX{TJ#lN*4$vQ|QLh5KelX?_{WqAwJC!$-oKCY%p7s z)$1wN%L9+{&h7xlrrrgepQ1(J>vIvaz}v6<>qZ}~WcNnl+Eim!mnUZ3hHeqJ7Q%3# zYqh}et-_X9#m4pc@b*&M^fF`DJ8xT!jkZUx;7I~V@^+dZzBXSLBrrR3BmQj;epUyg zA@w#b3!{$LNCiJMBCEz1Um8W_&mvTW7S4gr6QyiDm7#beLT8Ii*Ge8!&MW|?Y=Vc0 z@S8yku^Z-<5c)K}ZPBrUW~RtzlLXVKvhs@wPeIL22^PZ1$spDsYHabn>Qj;M0 z?yV=5>G1((@i3S<^4%Jt%~2+=$pW3}9WK(WjDriCe?YLem7Tn1&4on>eBpb^gvyN5 z*fjdy&=Y7rNuO*}ER^w|24xI6vnp1J?#79adsBo}%qYg!yWM~O-A;Dtx$-A;iW=wP z?lD&&(GQ`wJ00-;(I>fjyQ-cIZRU?w%^2s>YkPTokNHL;dpFoA*9LcYBTj>;!zw__ zfKVKl^`sOyQ+c(qzi9Z|j+3E~{P4VW)79~yak=D4Yhev0f*AO>FYR1^7YG1+qHFr% z8NW@Ye3Fsg(o`Wp=(mUPR}gF9X@gIuZrdXjj3#>njnf_#l&R>!SDM1y`xt?%xzkgIiC4Y=x2Wws>mPdd1^9L@r?dWFi>nYC0IR!;(UDK<2zxe!;9_EfV_6Pc42CG|R z9gMykBt1)>MHO0X%uC|@!B`9Sxq5bf9hXR9&bVv#;BIk$j>=5M@cpY+g*uWGyby}D zK4PQxZnG{31+i%lMDF8e)=1Z#5B9Py*8LLm4dt4ZqhPy=ZK;*O&!~NKbaincQ85bX zmW>;!Si?Qbzz}vLavSsFcRU{F4QYXgitIcZ;=wF8vie#(DecN|zRo(RisQdc?B!6Iw9g zY=LH_RaaY?!1A%%OYM&5E6s}+pMXH`#0^5ca>=BQl+PUCK?ubY9tGZ(v?+|kyff3eS{hC4u_?%rY9h&8kCm+jTlYI%qK8jv@@rhnHwudAP=qhXE5$u_dQ(<_%>%fPF?TW@n}CytAWE*hdfx~Vf{AifT}s|?lyE19m=M!3}@UDL&yH6=#Zu2i_D zf@4?)G7UQb-@;;x3gBh(P;-R&qs=U=)-p4GH1q`|7n1(akj=Sxx;@Vx{{qfV-Ag4z1?n;cc)aRp6AZ+h30d69wP&m2J`lt*RFS7 z7>~&BFTSZA8q;&1s$|foWn|j8BqN+dr!Uf??@)_D!Z7%_pm)IMC&pN0iMZ0xM7eI51yS;gr@;~0I?h;7W&|~5 z7;8bolACS5{o;1|U;nR{Bnn%5Q_7k^Fd=-4kIVHWplX%iZqw~$ zV)U&}6}vU8a*Z)mm)T3~jdeR2^2<;?Z zBNJvR?EOXR$kl(a|46q#qc1+b663ka^^?-TN(tsdS^DfQObcTzhoP!g+1K-2rbXgA zAy98$Fo&nkHByfb4^)_-2PKBG{ds9g6*G10xn$l%!c}j$~LTN_D-y@=vm~d zgP)F!a@tc9Z*zTNM7|5=`)mU_6$=Z5E*u`2@PG( zZT)G>3iHjwYdmb0ap`7-@kmMlhl`7Z4$s!kJ@b)9h4o-0UBO^0_;qQAiC;#-t$cUI z6HAaBmhS^eF#j2pLdtAZ1E#q^T{K+iB>NH$a#B9}OVBhp3|+S_+%9ye8--1bzt1XM zxyVZPWww5ilZ5eSoe(2sO4T%Uj6<(il*VqqIB(?+i3{BjmZ{$PVcGdX>&xXazDv;R zyh5K95_IMPaNyOXInoKn&f%p>qZJ*&u!Rs-WU7tD#!9%O6eC@_y4lmM3T=eHfa-Ir zFw8?iUihLWp}M)jbe-iXe>0Yaw+EXTv9<$ffn>8JF&>NAM6lJ<@|3bn8sR~vPpI+B%ot7VbR-jt!K-kEENwKvfg?v zmTlz{1=tgX55J2JpPn0f-)FT4WXt zFHOS+v(B={Lc09OOh_uK*QQ(9&`b}3FFP|AI|o_oB;G-p=1yt1oEqs1O3>pR zWR&W#&RiI);Y&$-$vG}Mlx3_Sl{6H-!;lnj4??SIyImLi*3FROQiycyjfl0-aIt}0 zfJ1R-$2IZSt3V{sit+-AF7$FOU}+38fxmz9@8IX_3wcDbUA~KZ3_9Q~!|<4<(OODI zC?txEVnyrL9HnI214y{Oabr@yQ>2-t13_(S$Hol2nIXL7Z-U2kPR@^eZ7cC4AC@{i zP3aSou)@0+4OD^;>y^CxNFDe;*zR?Ce|fldRPyH!+cw<|sYLmFwF9V=KR9VtJrwMy z{|7=EbYu66J)#<$d-iIY1VyiICO zjCKYXHt;L^P;cTld?Qx#>_als))+0ulhuNjAK|(j)|uyBMjVL!paa})KLtYOSj z|0$y5en3?)3RXhKa~ z-J1NR7t>TR;q7zj75x0l0s&98^ka5Xf55_HuT+l&qpi(P3T9V?RSWFLqzFG>Xd$0N zdP5wHs7Zr`46!y)#LCY8^H$&(a}qN`^%+a_)BtP zI&~!6@=3!rR`kZwd)(IZve;W)i}dFnTW@mwnCr%vS`Ia+DztX<4I^&&?cy9z5*}`! zMY4n`ZU}^(_A&K5vY}*ytK(aVoZAv`4%j5R7NPt~nV3p*$(G#Ydux_}e*2g{UckUn3^# z$Y+DoG-#!d^0j_H&6m#l-V9WsXF&w+fa{R`4c|D$UD^YBKlXH(V2W*WBO8DrB(8G| znE8ouTtlk$(uQuJb4N5KN^o#j#tj#DsR##oVt#Nd!zd|dx=kFh)i(0<1f$lU;h}%0 zLHkNM(9TlnCk%In;h&078n0?aPBpVkSRQ`QO;w?CrmU43trmGqW^YufsvrHsqL#44 zl66oo+bGKkSzcV8_St-TyX@@sj;50SD!RL>4$-DLjc*a;%7d*K7kZZY`f1|aM2F`Z ztG#ae<=RrPK~Y@AD$amgPL81P>P-2nw&Klkv4$Jx?6Xn2W3jTAk$+F=$n6s6wxP%p zE|5|}wJB7q9Jw_#N!D0tv(5EOwHCQc;vAN-a!xMA&nvKkS(apTSn>^(g}2&e+rilC zz^q=3W(?y^S)qqP^)+i6!O=mtD7HdwyTbc;$2rKFdk(pd63GVZSK7o37ql>~2Q6=_MnxQ= z;PDX%PZc?oE8kd~KHyxqp$9T^O8U&Rdutec_9oTJe>9Tu0pmQtua{ zzKifCXKolwnjkUSmGm2i&4n%N%U1SQyeaoc?RmE_ZFkj(9L^l)4kkb zWMXJKzus(lY%(`+Q?WMnB7XQzPU;ms85tNu4Lzd?W3C{F1AGZ%{!7zgap|Z=NuQ75 zk1N_}I~;(UfRCoU?J7^PR>THw?}8_ zyyd>dtY>C8{x!((?c|{@-?bhYS-FA@8e&}(6Ubn>_*!;VCNb4(-<4)#top1&+4)v{c5G zvf7tsQ8o_S=Vw+w4K_@TCF4pAL+u+n%TN|*Z zp*sMFpaIE_WHp9xf7PHYXPJetH0%N*o>HH1mFt?5Ns-4zk1DBy)q_JI-4TU8$^X;v zGVve}#t%O06-*7`%P6u@k*iEz;GlPX4`&0b>%(X1K78GBprrpPS6TtKDwFU!zE85H z&LV55@sdPd4&G;&ihUw;rXiM^||V5PLnLb-kd0$K{MqubU1O74}-p%CagQS8*nV!Ipt)Hc|P( zpNehp4HkE5{F&jO%dw*N-q#*`-x^TIR5r!sjORZf_N$_NHKz!^U0F(`((|M0t>Vs)cQh2h``)WQ*;?3#O zAE3E%{Fza!5Wau7F%I^WKE0O5J5G72WhphHd6`w&?ciNa&z|QRC5evt)ng-zs{;uN zYueMpkghj8CXCE}pddLWVhpTIlJgR$xeGiZ5w6S_AyR)zW&8(fo=eC1ZYito;(d6s zYnwoh)5ZrKZJ(5l!o zHhBnr2cgq29EQiWnH!90d1fP0zqnHFpPkzm65t8Gyl4uy?UB#3-m1{ATg;Dll25MJ zcR<09GoW18`TC~thEjq(>X*e)f5z$1{b#eeoWwol^I^@Ev7_rcUwK8p{e4t8V7?JB z6zh2C#}O;!La2u#^~Ip9%C^CJBeQ+0Y<9}&H8~JLJw`i=&LGX6e=E!8aMG(cZOWF# z4#%uSj=$_GpZJ~AN4Z~#ON&uIzN!EIk zzV!oF=B3W~@w^-6Z7Gxsi_Tia8gr(nQxNjip*~mvUx0 z_~TwC`wv3~`Pxn{Gc%>&Za}g6D{p?O>A@t4IZmg)pJ4s={1QZV1{$DdCswf z-Y5oY1fC*UeJA9PUsmEz9LK}0f+%+#EsKXEC_1wf$-{3;we<%y24xLC1U#wZ*D5Ro zYxlk2@9K-1G#;~JtiqCn`V54%m#+Kt^XM|yWX9m+OUPz9vfaD7TgUu{cyMWhPO6x= ziQhElpjox7*F29K^r{LV-B)sC(Dy=Pa7NqdP6Iw`nv1)M9izr=7`Q`@g=3t6jsjxz zYJj<=oP{+>`s@ZoPBQF{<}m&Fh+Jvf-R(Vr?s4*oy7tFgnII1YtA}ocwPn7g+*BIj zCb2wXju%V2_Gf4!e4{qPKOL)`<$LJDujtc_>#KlPHu*&ABVD!9{*Gc>)7aX~!l4|U z1SCcrkwgqD%xKSgnivp!D^;n^AO4+zlSZMh#LG;&pPYSd9P-p5@pM9K2HiH zk;r`>=rWqJj6v_znr`g%=Sz~!_fzi)A~Yyqi|J4VCAZNOD3Wh$G|ygXQC4pLG^=AX ziHX<&JROMAJC=4m>y4p-Ek6ah8R{5xsPir?Bp?(P;%Efv)wKPiYH>9!c2pEGK3pQ;9}AX_`ba)dQN zy-v-9V6tvJe1RX?(u67_aMlE(6U978V0iAD$jEo(9VOR**V?(bxyPZ3HmBwBZNL2k zeLCSNziZ`0BTl-%gxVge9>FpP>Ds)mmNB{p8yfmXt1j^+HZIR{t;ow?8l^jQ_Bq_bO&pH8Qtzg#}vwY>Hv$ zA5~cF5aV8(B5?2A?iLv0T2>HmE*l>oS{s!^MNN_Q6XpC%wwRqfgV7X_>TE}QYh%1VniP0pY zPGa4+Rt$&)iE+hsrb!ev$|M>W6lg>-iK10O+>j=U)VQFcs1PBFOHdK_UGqNeIrE-* z&vmZ%oNLanbIn|xKfD0txu5%fyqE9t`Fy^Ks9kIP=0kT@Zejs>6E7N@+v_OI=!^@o zEmB#Q{%ByGRDnt2qSV3mjk)#wWmWyWqcTq-S{-D0FIq>|LCO1|oNtG@2=-<&hT`!I z>?gwPT>o8s4OJtwpqR7*C?v@_mO(DbT_cm@@~H2M4aVc864lg*DMyF&(@7AmVZk0 z7Cm$Tqkgjd3Foj!$K-w=h!f~$q*Xf?}_u#s1^a4uSjThOQ$!AB$0 zKiT9z2yHs&bHbOs<3bbgV$lOqOp)qe%ypy#rZD^Dz`33cdv=%wfgxgHS4e5{_1ay{MWy}^|KXFSY1^6 z-6ogV0Lw3?S2+Ly=Z>GL-v0#u^PT5^AT3&dD2$Sf-AvW$u*wOhdR`e?%RO^)lQg?o zy;@Z5BnrjBoZ80}O106OS>aV>!}D^GFTFOxdzf=(!(HlH=>Dh4H!6$S;|eI1q2rS~ z-`Y@kIqNJ64dV2-AMADRaN}w#X2#n+S64o<1$83C8MC>ILNm2BD#ccu8nx@{mAXIZ zcXfUoshPf2zO#Qm*QlnilQ}vPfiN`M%oGE?>CY3EU`;mO_T#s1c9+LTU#ok$ zgpoNGi1`*6D6_b6P#b_bkRa1@Zk^`e*Q5j|lNZz`1XCnx!U_;dlLWp|DVu$o`Y<$N zg?&tap~1h{x|ob`+?{RdvvM=8vY=Roo(p9R4eDCo)f@p(f(bnCE%?YQ0Y7mqPqXZy zNZ@||GDVlu;O*CeV;c;~Vs$NJMA@OWpMpsRufCQwuT5@cf75-1OP$5G#jFu-t5rTL z@hkwLR>a1a<^ImAKP}gol-!0-ovgc9hdy^PYSeg;ZB?wim5(SUD*RT*1y#A{xEpHh z<2I74gfxx5@=^gY3Zsr&&y@4AXX?HcEJ?I<{8J(^?ne=os%6`&!3CwG<-PvKgF3Tt zOq+I^&Z1smOD6yMEqKM5HG0-wrxQPjX6NGW7ENy2q*p|;&7d{dlK1SLR<`}-lPo(L z;{02d^eJp|zEXn0R*OqJd3Sa!CH$LDWI9)fSNh}u-U#d!r3Q(Wb12=LebTk9*Lo)y z6afkzCM)?Bh^W5G=O$gr+Ld9ghSdWL0Ix*U%~SU+`F1I~sAv}qQM^b!YSnX64xVV= zxATqh8JU0LLAA^}RQ6n-EY)mEux>^pz}L`fH=_F=&S-Pv0W4H6cJQZG7wWT)?f%Nk z6-)&{)Js0BdtrjT2Vrz}ChN4$c)aVADUZHrz+Q}2j||)-Q;-{812dE=#$GGAa=>%_ zYJ>}s5e4p670hP7*H6t@I6qP{H#En)(g~9clNYf9ij!NJ?G2il#2Bp(v0x-4y~+kg z-KcGo6s#6)r5^#uPz8DPsGP_TC;jPAx4>_Q-O1x7&6rw$1uN08>aRD0K3QO{^T3UE zA`=TCp5fCKYVTJgTxm;Whq;&Nbc$UU!Om9{5il{XdLNGM4?J)_+5NzvHee{Rz)kPL zR*xh8uJj|tEK9GQHKi61K5IpU*9b!HCGe>Llz%Ex>&D45K+;plS6{3`iP@mZ<+<6Z zXkjuQ;D;!Xc^6!9gpWjyUq}4l1VDV^-l$kqW#5y7H%s1~=32I69L#)+b|#h%SoAiN zR?wt?a;JgxRM1u1(6aBT&97tds-0JMIy@FL-rAZ_&Bljs6mrRs;XYLEeqYjrVQO9v zM%Ewnffs>O(H)Q={I;)K?Aqxwt~d`ayh>x&^Ogwu<7?9ZL+wXcrx0OMed_|pPhy>1 zVw8jhEpO!JJ=mZd;us3Gc5!2k_VX3g54=X2eesqAf#arrI(lAfMZU&e4OMo+U@-qs zH2)c6a_VG*GsN56VFyoELZ|!pF6M7|wsk_`{{)%5R(S=iP)-v}n3ffqqsgHSV&`ioHe!!xIX%wu+!8rpWRg`1%tW@5AL|1Yg^oE7~gLt8J?U$9ENLG|oI;0`EF{f8tXD z=mgW$kE!sjE$wx}E%IJlwvetP)&<^83!KDc%#7Lr0%y!9&Jop;^I11myImX(byJU7 zvXNg5nX4xNF!AcdA4ECsx=<{d&W?^Sh_BHBsrloaM0SfjAaz8wTDg4hc(DXc$nV|h z-6+^a(c>MheYhE&t0GfkA(M(L7fy^R_a$3^1V@t6+W>1-?af9Y^DZ*!fSjBa#g;U; z=KMy*8Z!&;dDglfQs{X2J>|g90`ipibfhyBPn6{<(>99)ldk~V8$6LoYEisuzQQ&8 z^cAw_#*Xn?@1u_rNzO-&p@+3sGv2db4a+*LucVE>W_lClu`RI1+&b7u@qu5w%L_w~ z0(grVB<V~?&0s`Ngg73Bm4-ZAnbzjbn zZR9`s+4VA=SffP7q)v+ZO>SeVv=kA$Fr|L}a~jR2uztND#1>#ReD>p?pKgBtUw<6` zj~3sBiL4pDB3y5;5^P|$4j=rPw)EAnE@k;H8(uBhsK&PO&B>`p_ohSY{d0wz?WW(| zPxk-Fx$3mPZ2;QoVsdk@lKVfcE6`Zg7#wI6o0d z+L9*79kJZTmG+)@#5C=MdXs$2^WU$gOTN+)AQ%% z&u?+toG@$*7=Grfru}0TAS0^wM6vX0Bh9ZW9%ZcPyC6O+{|JE zsx8seTX(~o?nKCZU}ULonQ{Bju!M6mrwz`>Cq`?LY)0yAcbp2ZNVsSp{x+;37o{aV%L}k6+p2-YiZQd$QH`8^+wBh9F@-7ytHV7NjI}OnoZ>YK5Q%O0 zHXuj3me**zuYJ0+7oryYc~y1Hy;1z$Ok~rPy5^K!b8a3HKps8yR=N;2J7mP#z0t$# zjsQdA&dP@aAz$=c><1f|pfMoE60e+Xu)?V8AdX>ARGMe)ryaY6)ccsbHPhBLk=8ih zY$N=#yU`wp%_`w4e8rJb4Z#EQIiD(Cx(TXH(Jo$mdGi$zrTJa)D>EPT8$I3F}J>Dl(x6xbJ#UCRaXsYj3 zJ_Dl>!vSm%qgSB=fM3L}9n6-;_XN0V>_jn>X3okT7&pRmX>#E zX7P#c7-30N2FHm)5q3PkyY|~r$ZF^0m^LS+?(2z@^<0ys^gcw^j_d%kI4D?=Xg4}Q zDf$D?(hVms!EKa)B#}h?{>gj!6w*sPUfd5~3ZT`PgMu1wZs@8TpN`IJ#ici4Cf_}q zQlrpR?Z5fDCvnuO_RQ&%#;Jw-o`8sFYKkU3p&D!9d=%>De2JH9|AG^4^G4;^tCOaw zpA%DPmx_5AI`Oq>_gwBIUw@GlAa=m&0Fm~7H_h+rbYC9l*iy6@U1I+0+UHFlXervO zJOMUx*0nrHfG5!eRRmU02n;jYZ);cB?ef0B#`w8C zd(VQp@Cvjr)TCeytaEtOXXcD+#%1x*pqOoWZm}sW&)+9pg64;(H}gQgjJ=kV%6Z!GFl`r5G zY0ib?l3t$P&m6wKG9W6Fi8`Zs54?U$fY~H$H@i!3@fwmpdl`SVmD;HjZQwgHqQ z6)INxQa*M{`+8`!yi20+XItd?1o(X6yrNX9?t~LXp@R%wP(NFT-{8RPdxp-w9ks93 zx_z$8v=1j5LkZ9>e&UW-Qpg4F&6`m5l90zhN$2~_DDQ@iMhaC8>Nz@e0!?hpIn*=r z9BTNc=j4?2(p0M7Td3!;V@;JG4WXAYi)*rMO#8vO9JC3$(Td5GSZ6?tAwPS?jOQw@ zDk>h?_x2g#Y(n_Why@*{jhPi?yNaE5DdT!{TEQY@x&K57X0Q32Pns`)){VoC53-yx*D`kyDI9EH(3O%7YwiT27pu)T-SdhuY(*^z;;_~q z)i}7@1;@l!yS1+Ibt@0RvU=HH;(9zg7{eVzbBgAdc-|F7kPMYW2ha3SrD%xT)2UM) z%LmTnrdyt;M^6jIfO#>aHa7`|-m^x4zUk>=jBUhKLb^_S8VLC996^XG>D_w^#(I@8p%sy51YB^7jYqX<1+MY1hNh{ zi$bGTVS7ZUsQrHX{*H@d_kH2A$KlDzF`Jt7s4lA8rJ6DmTunHsERk=KEcSRu)}jg~ zyixgp*x0ScuH&1zRHu2tiZ^LvvW@|{9Ws^L>XT9n9qeTElJ57|m9|0A*-+ShJ3?Y< zG@;qyb?(wi`yMZ;@HV)k^(bv1D#wbY8sKi9eDH8bfjyFW_+Y$=@afRamr!W{u_#K( zN?c=E)?_p6!2wk?ifa|G#C*XDU#@&%r<|^}trywfoEOkupPj}xPDrGu+nl?d{EdF! zzpQdh%k)IGa8MoaZWCR{4B)Y+Z5Gn+!(y|9wocww_#`E+KtA{=xqQVVh6FXYs`bk7 z=^qTVUK+*hikD_K8{s@#jQAsBpVxU`q7NP);&zn2(fdBzu?8x0D_7gOA39sDFC5e}mbqWx9{C|#VW=KT|gg`-X>M`D`HkbMzKeveNVp`zAg}RZX2zLCn4t&Q_Ta5=05neBj6Rs zuOg|zZw=j8$m+G8T2L!g6pOrUHZkrLKZlnMCu=V+DfX=J5-s*=k7hu&+YuQwx9AsT zZVA5m609)T=wMQfrB~V=3tx9Tb*)T@TYFmN0FA@h)k$dZ`M}T=J33eZ>Fju?@XnkN zy7YsjzS|h8nsT)1kY$Q$M*Ec z->4+=2zpdJl2bP{#ZanYSS7)B3bjcY+&_hMds%#)Pf zhe0}FB4obu_0S%MyS&mqFe{X!AG9QS=a+}&&_c}`*FMo))8G0bvrS~s&}secz_%U5 z@dZw7GbYW%;d{ZdD!5>GK|&1NJ|NZlN>C7`rg(G%f}vzgDlBa%$k?JLm6{hMWR$A=Kl9_87`vJ`w$-*_a*;S>QZHbD0FXK z+GSvuY;;E$l@r&R{(3vL%Ap>`NKwN5fGSOg+S~i~EO<-j=jvc+0BDkC$2HS4%<$UL zp8^*G53P_ica$yD!Fl5X)kh=I{J#RRySHfeZH}sY;Wwnz5|c!##wL#XHJm3>^lKB%lyuJtcNOZl_&r3?I!7d z#eUR0dPgV~X?OVSTzRI|s?lk^Kxls8c-&F(425sdm3 z-u+@R9pBaXCm|i*QfeS}%=~$_B#Qe@`WN9g-q|kt2cMkhL2#YX`&$&|+FU3j^^J;% ztW{YgY2*iQt#&FM3zSq^lCZdV4&2gT5-zzeOYtc{$|ngk`gv!@YqYCI7oHSeT%^V9 znBuap%$QUH3}yY>&a-Ab+!S_uxJBdj~gCQ}U^55{hJTG$e$I@|@&oX%vsGzHlh z41YJZ&;jXyQ*11x&#^g)XqmQ)$5GNddJaDbOQ{7f zOV{d&)ofZ1EZN1V(kjCoEb9rPrp%_^=)AQY6S;xb*|8zwiM^iKv%6`Rn9<%MJZT}) zI=s~Nhn-u?(r>f<{D_F|0@8lcD(i|;XjpR?ZINPQ{{55&t@XXurq#}{%Dat45`!R_ zCRMfa1moF*W{?sjsaC49Oy~49i>vq@ts#Xzk7`a2Z-tQDoSXqxrTQBc#zv%I9-}RM zY0C~Nd(}2-aYS?|E#Upwl&)(195h!Eib@czv!IJS-+~H}u6IVmMCIW0pj?q*UUisO zSM3y{!*{DJ*z0uP=U6Jc(+OU*$(HO{{5{)R39mn%)C^P1K%9HusOZ14a|h*;*MRt5 zoD$xX8^<=8G`FC zhK%1B9NyjJ1^+bVJ6bV0GMD4+Aq`Qys>bvO|HbQPpcalZ7-cc;>E%8!PYp`w#xeS= z817?R#nqU}29}4-Hwl2LeqJY{;l_4Z^QP#W;2~#WvsxbQY^Jo(50*Du?xyy7&e}69 zc$*m^hh`n;wZLhU=yvFmxQ~hwM*pK;nE?rHC1}oaOP2`vChS;U8Mp0c)S3IQ| z{e168kcGWgrC**N=eQ`P#=Nxw3K|h?2Ot;;@#GRh&C!hg$p2^ zxsfOPk0TBUDm5R9lXndA^go<)teky`5%hH*dTvP8y6R9>AWMM&N7$ynQMp85pSlu1 zlOuNc1NnT5wqgp|;kNPCYkh;yGM)-C;nhyPe2c*8;&G66|BKzDMjOR4T`T+wqf_xo z>1LAg8x`eAnuAFmsAD~U@EhVa1O}JwkId&6p1Ih+6zS&G+Ah&!T^_7Uu_BmQ-I_DMWX>uJvFE3WPn0A5pP-}eCByDGNRH3u|$ z(;?}hJ=Bq~0Yp)|wyb0bUcPg%+~aLvrjgTHr>3U&C+=*Ym2CG$fLltbzZE&J4MM5ouFYol|%?Ug!!>o`BNSz|KIVO}MVnNtOx=P_%T zDxS7-hL4I}7p;ZouJ$} zJG#tHJJvQDMCDg9VmI@P#EKLRA#TGr?)E)YlgWpDPe(~0kL-1+rY(4W4 zE3zzaC!=?*`v}VnhWorX8NTgo4?Q3^p>UhA%w5?2^#H5O2M;57y*uyFm~mRhx`$~Cm^>>=aua=wjyVMpiU4uPZJhHb;%kURZker8Pp_^2SseO< z&ln4q4X~mo5YJ<#(n?75#>{Cxx+&N~w`2L6SNo9tm%DgYnw~8~MiCKAXMjEY^iEX2 zZ%$i>3v6J)>`}=UCLrpQjPBmBH!73&)E+eWX0qapt#(>_I|29UAy6@E5(D#ddB)RB=X>Apk>aY}TVAB$-=vG@dM+;Ev4n$++D{%S6) zSb|ZUL6*@Cm>p2erP<(>AyeP1PJnCYa_Q=n)`8fi#QxgoW^743S{Rpmeq*`RUN4*jgjjgt$cjNpe=4#shwufFSsF)v( z<5Y0USjgbY2ajqO_Tb|aA86%j&}DX~b^aJ7K0n!AV0OMDPJ#iy0CJ$6{EW-)(6P8^ ztM5cFlaua>T`m*F=Ob3O41lt|ElJl2G06?$PW8SYAyJ2e$&MGb&QIJQ5QjXa1zd|g z^?2!(sG$*1SJ}#3CI_c|;4no|d?0r7#=u^~Gs1tEDcbz{p5D+{s3A%uB+xxXOEdPf+am z5&t-V_P9?=T%RaKEsof=Btch%^B_5+IC_TRqi5GgLq7lfSrfU+n1x43EVU8DXh~9IvFpg0Vzka$_i8>OK-o(M`Mv_AZzEvRD)j z*VE*MNjSEBe0^QIdDAOt!)qIdTJNW(Y37h9sN6coD7CiriB7DzeL!TYR!elVM_=g_ z?^JR!m~grWs?j`-k&o#gYXQJ%X7MkJ*Z(xS5G`j$V^q=C1^#uY%$@a2 za^1|?ooL6Cqx0t*Du*NL!#ZpoSaFgQqWB`p(JfJ^?I|dzb`Sw*PgV0>CmH_trNvpj zF*A_HA+(?Ly8VHpRbbM^&B76)>gScCvs^_~mxH^*Y@T`>3>LF}jzyj|?S2qiROQEz z2$-RO&;|cXLiRLWKVrKpo50r^LSqSde*xOxz_?Lb?PgLSn0Zy(xrgf7zUMU0Y(5v( zcxARHiJv6)Z(xTGI$Ae}eB^|YL4IhvSgDwVyHVTi6o3X}CFBot7b4ZvF6vUU7q=kk;< z>PfC)F0nPZ6;PisU8Ku(XbrND>DU)R8#>w+#5OyI@H)?iUdU;;$pqpCt+C=1YRV({ zhDOmXxIjddSmTSN2U*FF=c&4^N&f)x^NBqd+Oi;F>y&V1B4&PL2eP$$z5?f?n_Q*$ z=?{G;;pS8i>&7p|t+oaR6ZUbWmPXZHd8>6&C`WHJ0-Y3s{)VQfFtAsowkA5yN2A4^ zJwn+KYCUP1=T^DW6R=A7%pg9F9@kp$PjW8&4#_KHoBP&i57oUahhc;2U2KXE66;zb zyW)IQv8cR&63?u0c8;l*ftjs6T9Z)0y0?yBLE2P`LcJD7(2YAF5!C+H^xo(B3Wu1l zWxKmKA6JK?3(y&@kgma=1VGTU?WUeul`Pp+FXKY4`Gc1V)b9D(96D4@f4mk8u?t!m zmqHCOV{cTN#Nlz~A9VBHeU^2f-d^RChb4t`8mf0)pdo0=rl6s?)vyWpocnIF zKwGKb0N(pk{}|m@@-<_^)8lEkO&!0Wc?6=?gVVX^heTY;e?Ct!4-0rohyD zb{W(;Ua{LfTOIE{zfQN3OGkD{M`QN1#u4G80c%SXS@$M&s9K z#e~y>&I`rEEnb#s^ZskvF6${r9T0XYk6|ux%-SzS23DEHxq;2FwW5vLj6do-v|%7$ zz3p4c#dcOnCtE~^!cXqMQK5PDc%N|vbX5Esg4(w31f;)IZ}jKS>YIn<)CUNIH7wO8 zhQkQk6>dE#>GnUW{F*Yz7om4kKFdn^Xx9jk1^nsP7D0YHG;a3Jj~6&;^&V;i%l%-U zvZim5UZ{Es&ZI_QB#i#eWiq8?J{iDYnSU{A9u;vm*v?&;nZZk2RDM4kg~4hJq0BTn z4YbRWxSTSCW35Tl@cahp{#2^s(Fk`rzIE-pl_6xLXHQR;$+M?p25W5OQgrl2fc%z! z^OJVRhpG};AAzA|Xz|e$?>IfmEiw51PzszH7~^i1ckD*f(P%k6@YKO+r42yAOEaaI zE>m_HY@hc>qq{jrUNC;{}lhi_DX$lcC;z8pAMqLH>`(3fe$;3stoy;lA+sVB4Q8cp5_=P^udL>9teOk6;;{AO z410(deJ~t6PeWIBpt+b7RxtnW&01;=xQXoAyFm% zVkam5ddTH9pavv1MPdsViC`uiV%Vq+6M}|(o)oPN} zTDnQZMPnSS)7!wBVw)of)FcO-c}h=M-L1{tXK(7?ji`Lz98sC+4}Hk6NvSY=qjJ92 z=^)P>%cLkYendT6^=a_Oq*61(%DW&4?n!yN_!g5zPI7-qt;Y82o8JO-sqz!FSY z=JNx3Q{fi}jzELc&nMMvK# zHuralyKf~i)4QtoDeax%7wxzGo(v#sQZ$o`&hcE~je^m%y}Lgcgc@5FjCVkic;|ik zZnWTq{Xvy)RMd5smIRYCoUB#pmqfPD(c$oIit51pLP3KX+8d5qTUswBK(7sU1#QY= z{RZT-6BGA2n~99h-adG%I%F$;=9urT5Y;Qvqe|CN66SJI5yeJaYI@(nm);Wpw#;ja z^kC`~bO0bYFXm;7;T=g^x3)~d9KJYmAH#-I=FGh(N}fIC6Y>d^{9aawo#jH9JG9&*(Y6 znaFl9&&4{}O04$c$Hy3_PQ6~=jhGd@u3JFV1xSI~yvks^tvI|CdwXtCbl{zAb8~ih zt~>i(w(*%AdOq(88nV42XucBs zP*m4A$Z&GGB>yN{?Cywhwr{q{4ZNQ_vCWGFm}$Iwr=6c#O+I7MqT}6#IcyTYXy)Xh z=XSn-L&=4*HYfKLU*c9}2QMEx-E%W7pKZRr1(?%{Wgtr#jw)qJ~qGos8$(sXaPfr#938ipaBU73#E=vIn<*P;__@IO85OTREExi$U+b7XMYyjg9{qvT{H)XRD*C3v z*>-4ts*YWj2Lgp}bF2K;Ctfxc?p?Xr7Q@*_st9L+SfI58>~kE3Yc92gBsQ?m zPV>%pFSShts^gb?uC>rDWg<`ZT51eNHxyxu)#1!b58W9pW3MLqFs629${Sv79%(4j z4ljS(7NBg3d@h8~jgy?;sJu~$4SAz7)7R>?Di$3Mgj=>j!Se0YY)^oZ5K}HN2Pt2W z1!)!yYU@ES3mR z;vQTviEMkTI2UvEJBJB$gt0w*{34AK0hmG_?o^Pl&^#lC01n5B%Wi zRYarBX4rddOOT_x?M{_2bdZfOe*a1J^9!99KKc?o_V)-?jFCcJ*85oO(S>^HplyE-q-h3-mQ2=1uKskopFfug?;l>b;6s zgGGh(qMwR0XCS}#fnIr|QdP(+ZaBhSF`L8$`6#@Nu2~+ZXyLbie2zN%)WFX5Y^Zrd>20aY{MWyIYZe?Rs>*Nj?_*X+tCR zi*-^v*OXaV-Q8frg)WE@6=@@F`^T|Qi+9!3<{N)EUJA!2wY!GUX~Cb}Y4~5>>%aDw z|6HH<@A_Ws{5&dz%4>dy0EK#Zw0)gRei?L3n<*`2p>lB zKW!|b3&x<8ByT9DQt43RA!bQ#5Gr=U z-n)nFR%0ctdbEWd)V?@+f4w3R19i0-mKdV4_`I;ak^QPIZA1ABCH#6NJLJXBKQx}8 z{{Q#mLkZ9FZ(rs_)veFOoo35SN>g}k*VhOwNArMtoHntzOqyNWqKc%Oimeh8`8j<( z4~N~ukz}c~?w%q9CVNLsA3oIwCX1~(<-M?BFf6e~9^~=cf;kzxnfAk%_ zBQ916`}41Pzkjv1-~DCeR|)eA@+0tI^~kWd9{j6y@mKAS+AkyjZ;K!&;kUQ^alDO~ z^^D5W5HF9J3>vTjO#j9vfL>IJmt%n*8F+E{T>hJ!0^E(S|N9iUl=vIn1D?o_I6FSA zEI0ZsP5|dKRlvyS^hZ30^ooEp@Hb}y{WAWO?KR}zunDxj_VY=23UsVTHGVJ6E9BA@&_Phj=cm!nF};62_tiht&cEqJZ~rp#FMn}_>S~X$&Z%_&n~vlk z>-PW01#B~Z`O`aJ{9_IMn_gx3i}QOg%_roN>#60E=->V3mOn7^|1AHX;~@W8{{LD2 z|5^TD{hMy&|4_I8bNc^X0Qf(r|8MT}|8IVeht1Cs@bxZW0{qAN|A+RGf7grtXY2pB m`S0ffVc_4g^><=K0ObGvv;F@@xBpB3Z}8){X8pby|9=2zlM(g+ literal 0 HcmV?d00001