Skip to content

Commit f167aa6

Browse files
authored
[이성 검색] 사용자는 이성을 검색할 수 있다.(#54) (#88)
* feat: 화살 보내기 요청 DTO 추가(#24) 보낼 화살 수를 담은 요청 DTO 정의 * feat: 확살 송수신 내역 repository, 내역 확인 로직 추가(#24) - 화살 송수신 내역 repository 정의 - 보낸 사용자, 받은 사용자를 통해 내역 존재 여부를 확인하는 로직 구현 * feat: 화살 송수신 내역 존재 여부 확인 로직 추가(#24) 보낸 사용자, 받은 사용자를 통해 송수신 내역 존재 여부를 확인하는 로직 구현 * feat: 화살 송수신 내역 저장 기능 구현(#24) * feat: 화살 관련 예외 enum 정의(#24) 다음 상황들에 대한 예외 enum을 정의하였다. - 자기 자신에게 화살을 보내는 경우 - 이미 화살을 보낸 사용자인 경우 - 가진 화살 수가 부족해 화살을 보낼 수 없는 경우 * feat: 사용자 화살 감소 로직 추가(#24) 감소하려는 화살 수가 사용자가 보유한 화살 수보다 클 경우 예외 발생 * refactor: 생성 시간 필드, 불필요한 업데이트 불가 DDL 속성 삭제(#24) * feat: 화살 보내기 비즈니스 로직 추가(#24) 화살 보내기 비즈니스 로직은 다음 과정을 거친다. 1. 자기 자신에게 화살을 보내는 상황 검증 2. 화살을 보낸 사용자에게 또 보내는 상황 검증 3. 화살 내역 저장 4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생(과정 3 롤백) 5. 받는 사용자 화살 증가 * feat: 화살 보내기 API 구현(#24) * refactor: 화살 command repository 삭제(#24) - query, command를 service, repository를 중개하는 별도의 계층을 통해 구분 - 이에 따라 기존 command repository 삭제 - 기존 command repository 사용 위치, 화살 command로 대체 * refactor: 코드 정렬 수정(#24) * refactor: 사용자 화살 증감 메서드 이름 수정(#24) 서로 대치되는 작업을 수행하는 상황을 잘 표현할 수 있도록 메서드 이름 수정 * feat: 화살 관련 예외 enum 추가(#24) - 이미 화살을 보낸 사용자 예외 - 화살이 부족하여 화살을 보낼 수 없는 예외 * refactor: 화살 내역 존재 여부 확인 메서드 이름 수정(#24) 가독성 향상을 위해 간단한 이름을 수정 * refactor: 코드 배치 수정(#24) dev 브랜치 작업 내역 병합에 따른 코드 배치 수정 * refactor: 화살 보내기 API 파라미터 이름 수정(#24) 좀 더 명확하게 대치되는 의미를 나타낼 수 있도록 화살을 받는 사용자 ID 파라미터 이름을 receiverId로 수정 * refactor: 컨벤션에 맞게 코드 배치 수정(#24) * feat: 이름 칼럼, 생성자 추가(#27) - 이름 칼럼 추가 - 테스트시 활용 가능한 생성자 추가 * feat: 상대 프로필 응답 DTO 수정(#27) - 래퍼 타입 필드들 기본 타입으로 변경 - 프로필 사진 URL 리스트 필드 추가 - 사용자가 가진 총 화살 수 필드 추가 - 음주 성향, 흡연 성향 필드 추가 - 코드 정렬 * feat: 사용자 엔티티 수정(#27) - 레퍼런스 타입 not null 필드들, Column 어노테이션 사용 명시 - salt 필드 추가 - 생성 일시, 최종 수정 일시 필드 추가 - JpaAuditing 활용, 생성/최종 수정 일시 필드들 엔티티 저장시 자동 설정 - 새로운 생성자 구성 * feat: 상대 프로필 조회 비즈니스 로직 추가(#27) 프로필을 조회하는 사용자 ID의 경우 상대방이 화살을 보넀던 사용자인지 확인하기 위해 파라미터로 받는다. * feat: 상대 프로필 API 구현 및 코드 정렬(#27) * feat: 사용자 엔티티 수정(#27) - 요구사항 변경에 따라 음주 성향, 흡연 성향 필드 삭제 - 성별 정보 제공 메서드 추가 * feat: 사용자 프로필 응답 DTO 수정(#27) - 음주 성향, 흡연 성향 필드 삭제 - 성별 필드 추가 * feat: 사용자 프로필 상세 응답 DTO 수정(#27) - 음주 성향, 흡연 성향 필드 삭제 - 생성자 성별 필드 설정 로직 수정 - 코드 정렬 * feat: 사용자 프로필 조회 비즈니스 로직 수정(#27) - 음주 성향, 흡연 성향 삭제 - 성별 정보 응답에 포함토록 수정 - 코드 정렬 * chore: spring mail 종속성 추가(#35) * chore: Gmail SMTP 사용을 위한 설정 추가(#35) * feat: 이메일 기반 사용자 조회 로직 추가(#35) * feat: 이메일 기반 사용자 조회 로직 추가(#35) * feat: 서비스 사용 정규식 enum 정의(#35) - 서비스에서 사용하는 정규식들을 모아놓는 enum 클래스 정의 - 이메일, 비밀번호 정규식 정의 * feat: 임시 비밀번호 발급 기능 구현(#35) - 비밀번호 정규식을 충족하는 임의의 임시 비밀번호를 발급 - Random보다 생성하는 난수를 예측하기 어려워 보안적으로 뛰어난 SecureRandom 사용 * feat: 메일 관련 설정 정의(#35) - spring mail에서 이메일 전송을 위해 제공하는 JavaMailSender 빈 등록 - 필요한 설정 값들은 application.yml에 정의된 값들을 끌어와 사용 - 빈 등록 로직에서 구글 SMTP 서버를 통해 메일을 전송하기 위한 설정 코드 추가 * feat: 이메일 전송 로직 추가(#35) - 단순 텍스트 이메일 전송 기능 구현 - 발신자는 application.yml에 정의해논 구글 SMTP 사용자 이름 값으로 설정 * feat: 임시 비밀번호 발급 비즈니스 로직 추가(#35) 임시 비밀번호는 다음 과정을 거친다. 1. 요청 이메일 기반 사용자 조회 2. 임시 비밀번호 생성 3. 사용자 비밀번호, 임시 비밀번호로 변경 4. 임시 비밀번호 발급 메일 전송 임시 비밀번호를 암호화하는 로직은 추후 구현 예정 * feat: 임시 비밃번호 발급 비즈니스 로직 트랜잭션 정의 추가(#35) * feat: 임시 비밀번호 발급 요청 DTO 수정(#35) - 불필요한 기존 생성자 삭제, 기본 생성자 추가 - 비밀번호 형식 제약 조건 추가 - 코드 정렬 * feat: 임시 비밀번호 발급 API 구현(#35) - 임시 비밀번호 발급 API 구현 - 명세 속성 수정 - 코드 정렬 * refactor: 병합에 따른 코드 재배치(#35) dev 브랜치 작업 내역 병합에 따른 코드 재배치 * refactor: 병합에 따른 코드 재배치(#35) dev 브랜치 작업 내역 병합에 따른 코드 재배치 * chore: sms 서비스 sdk 의존성 추가(#28) coolsms 서비스에서 제공하는 java sdk 의존성 추가 * chore: sms 서비스 관련 설정 추가(#28) sms 서비스를 이용하기 위해 필요한 설정들 추가 * feat: sms 서비스 이용을 위한 설정 정의(#28) - coolsms에서는 메시지 전송을 수행할 수 있는 DefaultMessageService 제공 - config에서 application.yml을 통해 값을 주입 받아 DefaultMessageService 빈 등록 * feat: 인증 코드 생성 로직 추가(#28) - apache.common.lang3 라이브러리의 RandomStringUtils 이용 - 영어 대소문자, 숫자로 구성된 6자리 랜덤 인증 코드를 생성 - 영어 대소문자 52개(26+26), 숫자 10개로 총 62개의 선택 가능 문자 존재 - 생성 가능한 인증 코드 수, 약 56억 8천만 개(62^6)로 보안성을 확보하였음 * feat: 문자 전송 로직 추가(#28) * feat: 전화번호 기반 사용자 존재 여부 확인 로직 정의(#28) * feat: 전화번호 기반 사용자 존재 여부 확인 로직 추가(#28) * feat: 전화번호 형식 제약 조건 추가(#28) 010으로 시작하며 0~9까지의 숫자로 이뤄진 11자리 문자열만 허용토록 제약 조건 추가 * chore: application.yml 수정(#28) sms.provider 속성도 application.properties 통해 관리하도록 수정 * feat: 인증 코드 sms 전송 비즈니스 로직 추가(#28) - 요청된 전화번호를 통해 사용자 존재 확인 - 사용자가 존재할 경우만 sms로 인증 코드 전송 * feat: 전화번호 인증 코드 전송 API 구현(#28) * chore: spring data redis 의존성 추가(#28) * chore: redis 관련 설정 추가(#28) * feat: redis 관련 설정 정의(#28) - redis 커넥션 팩토리 빈 등록 - CRUD 연산 지원을 위한 RedisTemplate 빈 등록 * feat: redis 키-값 저장,조회,삭제 로직 추가(#28) - redis를 이용한 저장, 삭제 기능 지원 유틸 클래스 정의 - 만료 시간을 분 단위로 설정하는 저장 로직 구현 - 기본 조회 로직 구현, Optional 리턴 타입을 통해 null 처리 지원 - 기본 삭제 로직 구현 * feat: 인증 코드 sms 전송 비즈니스 로직 수정(#28) - 인증 코드 sms 전송 이전 redis에 번호를 키로 코드를 저장하는 로직 추가 - 새 인증 코드 발급 이전 기존 발급 내역 삭제 * feat: 인증 코드 정규식 정의(#28) 영어 대소문자, 숫자로 이뤄진 6자리 문자열만 허용하는 정규식 enum 정의 * feat: 아이디 찾기 예외 enum 정의(#28) - 인증 코드 내역이 존재하지 않는 경우(인증 코드 만료도 포함) - 인증 코드가 일치하지 않는 경우 * feat: 전화번호 기반 사용자 조회 기능 정의(#28) * feat: 전화번호 기반 사용자 조회 로직 추가(#28) * refactor: 코드 정렬(#28) * feat: 아이디 찾기 요청 DTO 수정(#28) - 인증 번호를 발급한 전화번호 필드 추가 - 인증 코드 필드 명세 속성 수정 - 인증 코드 정규식 제약 조건 추가 - 불필요한 기존 생성자, 기본 생성자로 대체 * feat: 아이디 찾기 비즈니스 로직 추가(#28) 아이디 찾기 요청은 인증 코드 발급 전화번호, 인증 코드를 포함한다. 아이디 찾기는 다음 과정을 거쳐 이뤄진다. 1. 발급 전화번호를 기반으로 인증 코드 redis에서 조회 2. 인증 코드 일치 확인 3. 전화번호 기반 사용자 조회 4. 확인한 인증 코드 삭제 5. 찾은 이메일을 응답으로 반환 * feat: 아이디 찾기 API 구현(#28) * feat: 전화번호 정규식 정의(#28) * feat: 회원가입 요청 DTO 수정(#8) - 요구사항 변경에 따라 필드들 삭제 및 추가 - 필드들 API 명세 - 문자열 필드들 정규식 제약 조건 추가 - 불필요한 생성자 삭제, 기본 생성자로 대체 * refactor: PasswordEncryptor 최상위 util 패키지로 이동(#8) - 비밀번호 암호화 기능을 전역적으로 사용하기에 패키지 경로 변경 - 사용 위치 수정 * refactor: 잘못된 들여쓰기 수정을 위해 코드 정렬(#8) * feat: 이름 기반 동물상 조회 로직 정의(#8) * feat: 이름 기반 닮은 동물상 조회 로직 구현(#8) * feat: 이름 기반 지역 조회 로직 정의(#8) * feat: 이름 기반 지역 조회 로직 구현(#8) * feat: 분류 기반 음역대 조회 로직 정의(#8) * feat: 분류 기반 음역대 조회 로직 구현(#8) * feat: 사용자 선호 조건 repository 정의(#8) * feat: 사용자 선호 조건 엔티티 저장 로직 구현(#8) * feat: 사용자 선호조건 엔티티 구성(#8) 이성 추천, 다른 사용자 검색시 사용되는 사용자 선호조건 엔티티 정의 다음 필드들을 포함한다. - 선호하는 나이 하한, 상한 - 선호하는 키 하한, 상한 - 선호하는 MBTI - 선호하는 체형 - 선호하는 음역대 - 선호하는 지역 - 선호하는 동물상 - 선호 조건 소유 사용자 * feat: 사용자 엔티티 저장 로직 구성(#8) * feat: 프로필 사진 repository 정의(#8) * feat: 프로필 사진 엔티티 저장 로직 구성(#8) * feat: 회원가입 관련 예외 enum 정의(#8) 다음 예외 enum 들을 정의 - 비밀번호와 비밀번호 확인 값 불일치 상황 - 존재하지 않는 음역대 - 존재하지 않는 동물상 - 존재하지 않는 지역 * refactor: 분류 기반 음역대 조회 로직 수정(#8) - 잘못된 인덴트 수정, 코드 정렬 - 회원가입 관련 예외 enum을 사용토록 로직 수정 * refactor: 이름 기반 닮은 동물상 조회 기능 수정(#8) - 잘못된 들여쓰기 수정, 코드 정렬 - 회원가입 관련 예외 enum을 이용하도록 수정 * refactor: 이름 기반 지역 조회 로직 수정(#8) - 잘못된 들여쓰기, 코드 정렬 수정 - 회원가입 관련 예외 enum 사용토록 수정 * refactor: 잘못된 들여쓰기, 코드 정렬로 수정(#8) * refactor: 회원가입 요청 DTO 수정(#8) - 기존 생성자 삭제, @ModelAttribute 사용을 위한 생성자 추가 - 잘못된 들여쓰기 수정을 위한 코드 정렬 수정 * feat: 회원가입 응답 DTO 구성(#8) 회원가입시 바로 서비스를 이용할 수 있도록 access token과 refresh token을 바로 지급하는 형태로 구성 * feat: 성별 enum 구성(#8) - 성별 enum 정의 - 문자열 성별 값을 enum으로 치환할 수 있는 편의 메서드 구현 * feat: 비밀번호 암호화시 사용되는 salt 생성 로직 구현(#8) * refactor: 잘못된 들여쓰기 수정, 코드 정렬(#8) * feat: 회원가입 관련 로직 추가(#8) - 사용자 엔티티 생성 및 저장 로직 - 프로필 사진 엔티티 생성 및 저장 로직 - 사용자 선호조건 엔티티 생성 및 저장 로직 * feat: 회원가입 비즈니스 로직 초안 구성(#8) 회원가입 비즈니스 로직은 다음 과정을 거쳐 이뤄진다. 1. 사용자 엔티티 생성 및 저장 2. 프로필 사진 엔티티 생성 및 저장 3. 사용자 선호조건 엔티티 생성 및 저장 4. access token, refresh token 발급 * feat: 회원가입 API 초안 구현 및 코드 정렬(#8) - 회원가입 API 구현 - 잘못된 들여쓰기 수정을 위한 코드 정렬 * refactor: 분류 기반 음역대 조회 로직 이름 수정, 코드 정렬(#8) * feat: 프로필 사진 엔티티 저장 로직 수정(#8) 무조건 일괄적 List 형태로 저장되는 특성을 고려해 List 파라미터로 받아 일괄 저장하도록 로직 수정 * feat: S3 이용 프로필 사진 업로드 로직 추가(#8) 프로필 사진 업로드는 다음 과정을 거쳐 이뤄진다. 1. 사진 파일들 확장자 검증 2. 파일 식별을 위한 키 생성, profilephoto/{userId}-{photo idx} 형식 3. 사진 파일 업로드 요청 생성 및 업로드 수행 * feat: 프로필 사진 엔티티 저장 로직에 S3 업로드 로직 추가(#8) * feat: 프로필 사진 컬렉션 필드 초기화 & 연관관계 설정 로직 추가(#8) - NPE 방지를 위한 컬렉션 필드 초기화 설정 - 순수한 객체 관계를 고려한 프로필 사진 엔티티 연관관계 설정 로직 구성 * feat: 이미 사용 중인 닉네임, 이메일 확인 로직 정의(#8) - 이미 사용중인 닉네임 확인 로직 정의 - 이미 사용중인 이메일 확인 로직 정의 * feat: 이미 사용 중인 닉네임, 이메일 확인 로직 추가(#8) * feat: 랜덤 salt 생성 로직 수정(#8) - salt의 길이 고정 32비트(4바이트)로 변경 - 32비트 길이 salt 만으로 대부분의 보안적 위협 충분히 커버 가능 - base64 인코딩에 따라 로직에서 생성되는 salt 문자열의 길이는 8 * feat: 이미 사용 중인 이메일/닉네임 예외 추가(#8) - 이미 사용 중인 이메일 예외 enum 정의 - 이미 사용 중인 닉네임 예외 enum 정의 * feat: s3 업로드된 프로필 사진 삭제 로직 추가(#8) * feat: 회원가입 관련 로직 수정(#8) - 사용자 엔티티 저장 로직에 이미 사용 중인 이메일/닉네임 검증 로직 추가 - 프로필 사진 저장 로직에 사용자 엔티티와의 연관관계 설정 로직 추가 * feat: 프로필 사진 삭제 이벤트 정의(#8) - 회원가입 과정중 트랜잭션 롤백 발생시 업로드된 프로필 사진 삭제를 위해 정의 - 업로드된 프로필 사진 엔티티를 필드를 통해 전달 * feat: 프로필 사진 삭제 이벤트 리스너 정의(#8) - 회원가입 과정중 트랜잭션 롤백 발생시 프로필 사진 삭제 이벤트 발생 - 이벤트를 통해 삭제 대상인 프로필 사진 엔티티들을 전달 받음 - S3 서비스를 통해 해당 엔티티들의 삭제 작업 진행 * feat: 회원가입 로직 롤백시 업로드된 프로필 사진 삭제 이벤트 발행 로직 추가(#8) 회원가입 도중 예외 상황 등으로 인해 트랜잭션 롤백시 이미 s3에 업로드된 프로필 사진들을 삭제하기 위한 이벤트 발행 로직 추가 * chore: jjwt 의존성 추가(#8) jwt 활용을 위해 사용되는 jjwt 라이브러리 의존성 추가 * feat: access/refresh token 발급 및 검증 로직 추가(#8) - jwt 시크릿 키 값의 경우 무작위 64바이트 바이너리를 16진수로 인코딩한 문자열 사용 - jwt 포맷의 access/refresh token 발급 로직 추가 - 만료 시간을 계산하는 공통 로직, 별도의 메서드로 추출 - 사용자 엔티티를 기반으로 jwt를 생성하는 공통 로직, 별도의 메서드로 추출 - access token의 보편적인 존속기간은 수 시간가량 따라서 8시간으로 설정 - refresh token의 보편적인 존속기간은 수 일에서 수 개월 내외 따라서 10일로 설정 - jwt의 일반적인 관행에 따라 사용자 이메일을 subject로 설정 - 사용자 엔티티의 PK를 클레임으로 설정하여 인가시 사용할 수 있도록 구성 - 검증 로직에선 시그니처의 유효성과 JWT 만료 여부를 검증 * feat: refresh token 필드 및 업데이트 로직 추가(#8) - 사용자의 refresh token을 저장할 필드 추가 - 사용자 refresh token 업데이트 로직 추가 * feat: 회원가입 로직, access/refresh token 발급 로직 추가(#8) - 회원가입 정상 완료의 결과로 access token과 refresh token 제공 - 회원가입 비즈니스 로직에 access token, refresh token 발급 로직 추가 - 새 refresh token 발급시 사용자 refresh token 업데이트 수행 * refactor: 잘못된 주석 들여쓰기 수정(#8) * refactor: 문자열 값 Geneder enum 변환 메서드 이름 수정(#8) * refactor: 회원가입 비즈니스 로직 코드 정렬(#8) * refactor: Gender 문자열 값 변환 메서드 사용 위치 수정 및 코드 정렬(#8) * feat: salt 길이 32바이트로 변경(#8) 보안적으로 안전한 32자로 salt 길이 변경 * refactor: 병합으로 코드 수정(#8) * feat: 임시 비밀번호 발급 로직에 비밀번호 암호화 로직 추가(#8) * refactor: 잘못된 들여쓰기 간격 수정(#8) * chore: querydsl 의존성 추가(#43) * feat: querydsl 관련 설정 정의(#43) * feat: 즐겨찾기한 사용자 목록 조회 로직 정의(#43) * feat: 즐겨찾기한 사용자 조회 로직 추가(#43) - input으로 들어오는 페이지 번호(page)가 0부터 시작한다 가정 - 페이지 형태로 조회 결과 제공 - 조회 쿼리와 카운트 쿼리 분리, 공통 쿼리를 별도 메서드로 추출 - 조회 성능 향상을 위해 Projections 활용하여 DTO로 조회 - 즐겨찾기한 사용자 조회시 삭제되거나 휴면 상태인 사용자 배제 * feat: querydsl 커스텀 repository 상속 추가(#43) * refactor: 사용자 목록 조회 요청 DTO 수정(#43) - includePreferredAnimal 필드, nullable하기에 wrapper 타입으로 변경 - 검증 조건 예외 메시지 추가 - 코드 재정렬 * feat: 사용자 목록 조회 응답 DTO 필드 추가 및 수정(#43) - age 필드 추가 - not null 필드들 원시 타입으로 변경 - is... 네이밍의 boolean 타입 필드, 이름 그대로 직렬화 되지 않는 문제 해결을 위해 wrapper 타입으로 설정 * refactor: 사용자 목록 조회 페이지 응답 DTO 수정(#43) - not null 필드들 원시 타입으로 변경 - is... 네이밍 필드 이름 그대로 직렬화되기 위해 wrapper 타입으로 설정 - 코드 정렬 * feat: 즐겨찾기한 사용자 조회 로직 추가(#43) * feat: 즐겨찾기한 사용자 조회 비즈니스 로직 추가(#43) * feat: 이성 목록 조회 API, 즐겨찾기한 사용자 조회 로직 추가(#43) * refactor: 부적절한 쿼리 생성 메서드명 수정(#43) * refactor: 화살 보낸 사용자, 화살 받은 사용자 필드명 수정(#43) - 화살 보낸 사용자 sender로 이름 수정 - 화살 받은 사용자 receiver로 이름 수정 - 관련 로직에서 변수명, 메서드명 수정 * feat: 화살 보낸 사용자 조회 로직 정의(#43) * refactor: type 필드 API 명세 내용 수정(#43) 화살 보낸 사용자 조회시 type = ARROW_RECEIVERS로 요청하도록 수정 * feat: 편의성 팩토리 메서드 추가(#43) Page를 파라미터로 받아 UserQueryPageResponse를 생성하는 정적 팩토리 메서드 추가 * refactor: 지인 엔티티 수정(#43) - 사용자 엔티티와의 연관관계 형성 - not null 필드들 칼럼 속성 추가 - 생성자 수정 * feat: 화살 보낸 사용자 조회 로직 추가 및 일부 로직 수정(#43) - 화살을 보낸 사용자 조회 로직 추가 화실을 보낸 사용자를 조회할 때 기본적으로 다음 사용자를 배제한다. - 휴면 상태인 사용자 - 삭제된 사용자 - 지인 - 공통적으로 사용되는 where 파라미터절에 오는 조건, 서브 쿼리 별도 메서드 추출 - 카운트 쿼리에서 불필요한 join 절 제거 * feat: 화살을 보낸 사용자 조회 로직 추가(#43) * feat: 화살을 보낸 사용자 조회 비즈니스 로직 추가(#43) * feat: 사용자 목록 조회 API, 화살 보낸 사용자 조회 로직 추가(#43) * chore: 잘못된 TODO 수정(#43) * feat: 나에게 화살을 보낸 사용자 조회 로직 정의(#43) * feat: 나에게 화살을 보낸 사용자 조회 로직 추가 및 일부 로직 수정(#43) - 나에게 화살을 보낸 사용자 조회 로직 추가 휴면 상태인 사용자, 삭제된 사용자를 배제하고 조회 - 지인은 애초에 즐겨찾기 등록, 화살 보내기가 불가능하므로 연관 조회 로직에서 지인을 제외하는 조건 삭제 - 일부 잘못된 로직 및 주석 내용 수정 * feat: 나에게 화살을 보낸 사용자 조회 로직 추가(#43) * feat: 나에게 화살을 보낸 사용자 조회 비즈니스 로직 추가, 일부 로직 수정(#43) - 공통적으로 사용되는 기본 페이지 사이즈(6) 상수 정의 - 나에게 화살을 보낸 사용자 조회 비즈니스 로직 추가 * feat: 사용자 목록 조회 API,내게 화살을 보낸 사용자 조회 로직 추가(#43) * feat: 사용자 검색 내역 엔티티 추가(#43) * feat: 사용자 추천 내역 repository 정의(#43) * feat: 사용자 추천 내역 저장 로직 구현(#43) * feat: 사용자(이성) 추천 로직 정의(#43) * feat: 사용자 목록 조회 요청 DTO 수정(#43) - 불필요한 필드 제거 - type 필드 정규식 검증 조건 추가 * feat: 사용자 추천 내역 엔티티 수정(#43) - 엔티티 리스너 활용, 엔티티 저장시 createdAt 필드 자동 설정 - 기존 생성자 제거, 적절한 새 생성자 구성 * feat: 사용자 추천 내역 엔티티 저장 로직 구성(#43) 사용자 추천 내역은 한 번에 2개씩 저장되기 때문에 기존 메서드를 제거하고 컬렉션 형태로 엔티티를 저장하는 로직 구성 * feat: 추천 이성 조회 로직 추가 및 리팩터링(#43) - 추천 이성 조회 로직 추가 이성 추천시 배제되는 사용자 - 자기 자신(조회하는 사용자) - 추천(선호) 조건을 만족하지 않는 사용자 - 화살을 주고 받은 적이 있는 사용자 - 즐겨찾기한 사용자 - 삭제, 휴면 상태인 사용자 - 지인(전화번호로 구분) - 추천 일자에 이미 추천된 사용자 - 추천 일자에 검색된 적 있는 사용자 - UserQueryResponse에 필요한 필드를 select 하는 쿼리, 별도 메서드 추출 - exists를 통해 특정 데이터 존재 여부를 확인하던 로직들, selectOne 형식으로 변경. 이를 통해 존재하는 경우와 존재하지 않는 경우에 모두 활용 가능 * feat: 추천 이성 조회 로직 추가(#43) - 추천 이성 조회 로직 추가 - 여러 Id를 기반으로 다수의 사용자를 조회하는 로직 추가 * feat: 추천 이성 조회 비즈니스 로직 추가 및 리팩터링(#43) - 추천 이성 조회 비즈니스 로직 추가 - 페이지 사이즈가 같은 조회 로직들, 통합된 한 로직내에서 type으로 분기하여 처리토록 리팩터링 * feat: 사용자 목록 조회 API, 추천 이성 조회 로직 추가(#43) - 추천 이성 조회 로직 추가 - 이성 추천의 경우를 제외한 나머지 한 비즈니스 로직으로 통합하여 처리 * refactor: 병합에 따른 코드 수정(#43) dev 브랜치 작업내역 병합에 따른 코드 수정 * refactor: 화살 내역 사용자 필드명 수정(#43) sender, receiver로 주고 받는 사람을 잘 표현할 수 있도록 필드명 수정. 사용 위치 수정 * feat: 이성 검색 요청 dto 추가(#54) page 필드를 제외한 모든 필드들은 nullable, 따라서 wrapper 타입으로 정의 * feat: 사용자 검색 내역 repository 정의(#54) * feat: 사용자 검색 내역 저장 로직 추가(#54) 사용자 검색 내역은 한 번에 4개씩 생성, 저장됨. 따라서 다수의 검색 내역을 한 번에 저장하는 형태로 로직 구현 * feat: 이성 검색 로직 정의(#54) * feat: 이성 검색 로직 추가 및 일부 리팩터링(#54) - 차단한 사용자 제외 조건 로직 추가 - 이성 추천 로직에 차단한 사용자 제외 조건 추가 - 이성 검색 로직 추가 이성 검색시 제외되는 사용자는 다음과 같다. - 자기 자신(조회하는 사용자) - 지인 - 차단한 사용자 - 휴면 ,삭제 상태 사용자 - 화살을 주고 받은 적 있는 사용자 - 검색일에 이미 검색된 적 있는 사용자 한편, 요청에 포함된 조건만을 검색 쿼리에 반영해야하기 떄문에 요청 dto를 넘겨받아 조건 필드별 null 여부를 판단하여 경우에 따라 알맞게 조건 조합을 생성하도록 로직 구성 - 검색일에 이미 검색된 사용자 제외 조건 메서드 이름, 같은 날짜에 이미 검색된 사용자를 제외한다는 것을 나타낼 수 있게 수정 * feat: 이성 검색 로직 추가(#54) * feat: 이성 검색 비즈니스 로직 및 사용자 조회 로직 추가(#54) 이성 검색 비즈니스 로직은 다음 과정을 거친다. 1. 응답할 데이터 조회 2. 검색을 한 사용자 조회 3. 검색될 사용자들(응답에 포함된 사용자들) 조회 4. 검색 내역 저장 5. 응답 데이터 반환 한편, 이성 추천 로직과 이성 검색 로직에서 응답에 포함된 사용자를 id값들을 기반으로 조회하는 공통 로직을 별도의 메서드로 추출하였다. * feat: 이성 검색 API 추가(#54) * feat: 잘못된 기본 타입 생성자 파라미터, 래퍼 타입으로 변경(#54) * feat: 이성 검색 로직 변경(#54) - 사용자 ID, 성별을 함께 전달받기 위해 엔티티를 파라미터로 받도록 수정 - 검색일 파라미터 변수명 수정 * feat: 이성 검색 요청 DTO 수정(#54) - 페이지 번호 필드 nullable 하게 변경, 검증 제약 수정 - 나이/키 상한 및 하한 범위 검증 제약 추가 * feat: 사용 되지 않는 클래스 삭제(#54) * feat: 이성 목록 조회 로직 수정(#54) - 이성 검색 메서드명 변경 - 이성 검색 메서드 파라미터명, 타입 변경 - 이성 검색 조건에 같은 성별 사용자 제외 조건 추가 - 화살 내역 존재 여부 확인 쿼리에 사용자간 송수신 타입 조건 추가 - 하한/상한 범위 조건 생성 로직 별도 분리 * feat: 한계값 검증 로직 추가(#54) - 여러 비즈니스 로직에서 사용되는 한계값 검증 로직 별도 분리 - 래퍼 타입의 하한/상한이 둘 다 존재할 경우 하한 <= 상한 여부 검증 * feat: 별도 분리된 하한/상한 검증 로직 사용토록 변경(#54) * feat: 이성 검색 로직 수정(#54) - 페이지 번호 nullable하게 변경, 기본 첫 페이지(0) - 검색하는 사용자 조회 로직 추가 - 검색 나이/키 하한,상한 검증 로직 추가 * feat: 추천 이성 조회 로직 수정(#54) 사용자 엔티티 파라미터를 통해 사용자 ID, 성별을 제공받도록 수정 * feat: 추천 이성 조회, 이성 검색 로직 수정(#54) - 응답에 포함된 사용자들 조회 로직, 별도 분리 - 추천 이성 조회 로직 변경에 따라 사용 위치 수정 - 이성 검색 메서드명 변경 * feat: 검색 요청 DTO, null 여부 검증 추가(#54) query parameters로 요청 DTO를 매핑하는 구조에서는 필드 존재 유무와 상관 없이 DTO가 생성되지만 request body를 통해 매핑하는 경우 request body 자체가 존재하지 않을 수 있기 때문에 요청 DTO null 여부 검증 추가 * feat: 이성 검색 요청 DTO, request body에서 매핑하도록 변경(#54) * feat: 추천 이성 조회, 이성 검색 API HTTP Method POST로 변경(#54) - 추천 이성 조회, 이성 검색 API의 경우 요청시 내역 저장 작업 수행 - 서버의 상태를 변경하는 작업이 수반되므로 GET으로 표현하기 부적절, POST로 변경 * feat: 잘못 병합된 내역 수정(#54) * feat: 사용자 조회, 이성 검색 페이지 사이즈 30으로 조정(#54) * feat: 잘못된 코드 배치 수정(#54) * feat: 사용하지 않는 예외 삭제(#54) * feat: where 조건절, 이성 검색 로직 수정(#54) - 이성 검색 조건, BooleanExpression을 통해 구성되도록 수정 - 나이/키 범위 조건, BooleanExpression 이용하도록 수정 - 같은 성별 사용자 제외 조건, 별도 메서드로 분리 - 선호 동물상 포함 조건 형성 로직 ,별도 메서드로 분리 * feat: 이성 검색 request body 필수 아니도록 설정(#54) * feat: 이성 검색 요청 DTO, 검증 과정 수정(#54) 페이지 번호 존재 여부, 하한/상한 값 검증을 DTO가 존재할 경우 함께 수행하도록 잘못된 로직 변경
1 parent 44f8bae commit f167aa6

File tree

18 files changed

+398
-91
lines changed

18 files changed

+398
-91
lines changed
Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package yeonba.be.exception;
22

3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
35
import org.springframework.http.HttpStatus;
46

7+
@Getter
8+
@AllArgsConstructor
59
public enum JoinException implements BaseException {
610

7-
PASSWORD_CONFIRMATION_NOT_MATCH(
8-
HttpStatus.BAD_REQUEST,
9-
"비밀번호 확인 값이 비밀번호와 일치하지 않습니다."),
10-
1111
ALREADY_USED_NICKNAME(
1212
HttpStatus.BAD_REQUEST,
1313
"이미 사용 중인 닉네임입니다."),
@@ -18,22 +18,4 @@ public enum JoinException implements BaseException {
1818

1919
private final HttpStatus httpStatus;
2020
private final String reason;
21-
22-
JoinException(HttpStatus httpStatus, String reason) {
23-
24-
this.httpStatus = httpStatus;
25-
this.reason = reason;
26-
}
27-
28-
@Override
29-
public HttpStatus getHttpStatus() {
30-
31-
return httpStatus;
32-
}
33-
34-
@Override
35-
public String getReason() {
36-
37-
return reason;
38-
}
3921
}

be/src/main/java/yeonba/be/mypage/entity/Acquaintance.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package yeonba.be.mypage.entity;
22

3+
import jakarta.persistence.Column;
34
import jakarta.persistence.Entity;
45
import jakarta.persistence.GeneratedValue;
56
import jakarta.persistence.GenerationType;
67
import jakarta.persistence.Id;
78
import jakarta.persistence.Table;
9+
import lombok.AccessLevel;
810
import lombok.AllArgsConstructor;
911
import lombok.EqualsAndHashCode;
1012
import lombok.Getter;
@@ -14,20 +16,24 @@
1416
@Getter
1517
@Entity
1618
@EqualsAndHashCode(of = {"userId", "phoneNumber"})
17-
@NoArgsConstructor
19+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
1820
@AllArgsConstructor
1921
public class Acquaintance {
2022

2123
@Id
2224
@GeneratedValue(strategy = GenerationType.IDENTITY)
2325
private Long id;
2426

27+
@Column(nullable = false)
2528
private long userId;
29+
30+
@Column(nullable = false)
2631
private String name;
32+
33+
@Column(nullable = false)
2734
private String phoneNumber;
2835

2936
public Acquaintance(long userId, String name, String phoneNumber) {
30-
3137
this.userId = userId;
3238
this.name = name;
3339
this.phoneNumber = phoneNumber;

be/src/main/java/yeonba/be/mypage/service/MyPageService.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package yeonba.be.mypage.service;
22

3+
import static yeonba.be.util.BoundsValidator.validateBounds;
4+
35
import java.time.LocalDate;
46
import java.time.Period;
57
import java.util.List;
6-
import java.util.Objects;
78
import java.util.Optional;
89
import lombok.RequiredArgsConstructor;
910
import org.springframework.beans.factory.annotation.Value;
@@ -35,10 +36,10 @@
3536
import yeonba.be.user.entity.User;
3637
import yeonba.be.user.entity.UserPreference;
3738
import yeonba.be.user.entity.VocalRange;
38-
import yeonba.be.user.repository.block.BlockCommand;
39-
import yeonba.be.user.repository.block.BlockQuery;
4039
import yeonba.be.user.repository.animal.AnimalQuery;
4140
import yeonba.be.user.repository.area.AreaQuery;
41+
import yeonba.be.user.repository.block.BlockCommand;
42+
import yeonba.be.user.repository.block.BlockQuery;
4243
import yeonba.be.user.repository.user.UserQuery;
4344
import yeonba.be.user.repository.userpreference.UserPreferenceQuery;
4445
import yeonba.be.user.repository.vocalrange.VocalRangeQuery;
@@ -172,18 +173,6 @@ private Area findAreaByName(List<Area> areas, String name) {
172173
.orElseThrow(() -> new GeneralException(UserException.AREA_NOT_FOUND));
173174
}
174175

175-
private void validateBounds(Integer lowerBound, Integer upperBound) {
176-
177-
if (Objects.isNull(lowerBound) || Objects.isNull(upperBound)) {
178-
179-
return;
180-
}
181-
182-
if (lowerBound > upperBound) {
183-
throw new GeneralException(UserException.LOWER_BOUND_LESS_THAN_OR_EQUAL_UPPER_BOUND);
184-
}
185-
}
186-
187176
public void updateProfilePhotos(List<MultipartFile> profilePhotos, MultipartFile realTimePhoto,
188177
long userId) {
189178

be/src/main/java/yeonba/be/user/controller/UserController.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import yeonba.be.mypage.service.ReportService;
2121
import yeonba.be.user.dto.request.UserQueryRequest;
2222
import yeonba.be.user.dto.request.UserReportRequest;
23+
import yeonba.be.user.dto.request.UserSearchRequest;
2324
import yeonba.be.user.dto.request.UserUpdateDeviceTokenRequest;
2425
import yeonba.be.user.dto.response.UserProfileResponse;
2526
import yeonba.be.user.dto.response.UserQueryPageResponse;
@@ -54,7 +55,7 @@ public ResponseEntity<CustomResponse<UserQueryPageResponse>> getUsers(
5455

5556
@Operation(summary = "추천 이성 조회", description = "추천 이성을 조회할 수 있다.")
5657
@ApiResponse(responseCode = "200", description = "추천 이성 정상 조회")
57-
@GetMapping("/users/recommend")
58+
@PostMapping("/users/recommend")
5859
public ResponseEntity<CustomResponse<UserQueryPageResponse>> getRecommendUsers(
5960
@RequestAttribute("userId") long userId) {
6061

@@ -145,6 +146,20 @@ public ResponseEntity<CustomResponse<Void>> block(
145146
.body(new CustomResponse<>());
146147
}
147148

149+
@Operation(summary = "이성 검색", description = "이성을 검색할 수 있습니다.")
150+
@ApiResponse(responseCode = "200", description = "이성 검색 성공")
151+
@PostMapping("/users/search")
152+
public ResponseEntity<CustomResponse<UserQueryPageResponse>> search(
153+
@RequestAttribute("userId") long userId,
154+
@Valid @RequestBody(required = false) UserSearchRequest request) {
155+
156+
UserQueryPageResponse response = userService.findUsersBySearchCondition(userId, request);
157+
158+
return ResponseEntity
159+
.ok()
160+
.body(new CustomResponse<>(response));
161+
}
162+
148163
@Operation(summary = "device token 업데이트", description = "device token을 업데이트할 수 있습니다.")
149164
@ApiResponse(responseCode = "200", description = "device token 업데이트 성공")
150165
@PatchMapping("/users/device-token")
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package yeonba.be.user.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.PositiveOrZero;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import org.hibernate.validator.constraints.Range;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
public class UserSearchRequest {
12+
13+
@Schema(
14+
type = "number",
15+
description = "페이지 번호, 기본 첫 페이지(0)",
16+
example = "0")
17+
@PositiveOrZero(message = "페이지 번호는 0이상이어야 합니다.")
18+
private Integer page;
19+
20+
@Schema(
21+
type = "string",
22+
description = "검색 지역",
23+
example = "서울")
24+
private String area;
25+
26+
@Schema(
27+
type = "string",
28+
description = "검색 음역대",
29+
example = "고음")
30+
private String vocalRange;
31+
32+
@Schema(
33+
type = "number",
34+
description = "검색 나이 하한",
35+
example = "20")
36+
@Range(min = 20, max = 40, message = "검색 나이는 20~40내 값만 가능합니다.")
37+
private Integer ageLowerBound;
38+
39+
@Schema(
40+
type = "number",
41+
description = "검색 나이 상한",
42+
example = "30")
43+
@Range(min = 20, max = 40, message = "검색 나이는 20~40내 값만 가능합니다.")
44+
private Integer ageUpperBound;
45+
46+
@Schema(
47+
type = "number",
48+
description = "검색 키 하한",
49+
example = "160")
50+
@Range(min = 130, max = 220, message = "검색 키는 130~220cm 내 값만 가능합니다.")
51+
private Integer heightLowerBound;
52+
53+
@Schema(
54+
type = "number",
55+
description = "검색 키 상한",
56+
example = "170")
57+
@Range(min = 130, max = 220, message = "검색 키는 130~220cm 내 값만 가능합니다.")
58+
private Integer heightUpperBound;
59+
60+
@Schema(
61+
type = "boolean",
62+
description = "검색 기준에 선호하는 동물상 포함 여부",
63+
example = "true")
64+
private Boolean includePreferredAnimal;
65+
}

be/src/main/java/yeonba/be/user/dto/response/UserQueryPageResponse.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import io.swagger.v3.oas.annotations.media.Schema;
55
import java.util.List;
6+
import lombok.AccessLevel;
67
import lombok.AllArgsConstructor;
78
import lombok.Getter;
89
import org.springframework.data.domain.Page;
910

1011
@Getter
11-
@AllArgsConstructor
12+
@AllArgsConstructor(access = AccessLevel.PRIVATE)
1213
public class UserQueryPageResponse {
1314

1415
@Schema(

be/src/main/java/yeonba/be/user/entity/User.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ public void changeInactiveStatus(boolean inactiveStatus) {
215215
}
216216

217217
public void updateProfilePhotos(List<ProfilePhoto> profilePhotos) {
218-
219218
this.profilePhotos = profilePhotos;
220219
}
221220

be/src/main/java/yeonba/be/user/entity/UserPreference.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ public class UserPreference {
5454
private Animal animal;
5555

5656
public UserPreference(
57-
int ageLowerBound,
58-
int ageUpperBound,
59-
int heightLowerBound,
60-
int heightUpperBound,
57+
Integer ageLowerBound,
58+
Integer ageUpperBound,
59+
Integer heightLowerBound,
60+
Integer heightUpperBound,
6161
String mbti,
6262
String bodyType,
6363
User user,

be/src/main/java/yeonba/be/user/enums/Gender.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum Gender {
1313

1414
public final String genderString;
1515
public final boolean genderBoolean;
16+
1617
public static Gender from(String genderString) {
1718

1819
if (StringUtils.equals(genderString, MALE.genderString)) {

be/src/main/java/yeonba/be/user/repository/user/UserQuery.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.stereotype.Component;
1010
import yeonba.be.exception.GeneralException;
1111
import yeonba.be.exception.UserException;
12+
import yeonba.be.user.dto.request.UserSearchRequest;
1213
import yeonba.be.user.dto.response.UserQueryPageResponse;
1314
import yeonba.be.user.dto.response.UserQueryResponse;
1415
import yeonba.be.user.entity.User;
@@ -73,10 +74,22 @@ public List<User> findByIds(List<Long> userIds) {
7374
}
7475

7576
public UserQueryPageResponse findRecommendUsers(
76-
long userId, boolean userGender, PageRequest pageRequest, LocalDate recommendDay) {
77+
User user, PageRequest pageRequest, LocalDate recommendDay) {
7778

7879
Page<UserQueryResponse> page = userRepository
79-
.findRecommendUsers(userId, userGender, pageRequest, recommendDay);
80+
.findRecommendUsers(user, pageRequest, recommendDay);
81+
82+
return UserQueryPageResponse.from(page);
83+
}
84+
85+
public UserQueryPageResponse findUsersBySearchCondition(
86+
User user,
87+
PageRequest pageRequest,
88+
LocalDate searchDay,
89+
UserSearchRequest request) {
90+
91+
Page<UserQueryResponse> page = userRepository
92+
.findUsersBySearchCondition(user, pageRequest, searchDay, request);
8093

8194
return UserQueryPageResponse.from(page);
8295
}

be/src/main/java/yeonba/be/user/repository/user/UserRepositoryCustom.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import java.time.LocalDate;
44
import org.springframework.data.domain.Page;
55
import org.springframework.data.domain.PageRequest;
6+
import yeonba.be.user.dto.request.UserSearchRequest;
67
import yeonba.be.user.dto.response.UserQueryResponse;
8+
import yeonba.be.user.entity.User;
79

810
public interface UserRepositoryCustom {
911

@@ -14,8 +16,13 @@ public interface UserRepositoryCustom {
1416
Page<UserQueryResponse> findArrowSendersBy(long receiverId, PageRequest pageRequest);
1517

1618
Page<UserQueryResponse> findRecommendUsers(
17-
long userId,
18-
boolean userGender,
19+
User queryingUser,
1920
PageRequest pageRequest,
2021
LocalDate recommendDay);
21-
}
22+
23+
Page<UserQueryResponse> findUsersBySearchCondition(
24+
User searchingUser,
25+
PageRequest pageRequest,
26+
LocalDate searchDay,
27+
UserSearchRequest request);
28+
}

0 commit comments

Comments
 (0)