Skip to content

Commit

Permalink
[회원 가입] 회원가입 연관 비즈니스 로직 & API 구현(#8) (#48)
Browse files Browse the repository at this point in the history
* 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)

* refactor: 병합으로 인한 코드 수정(#8)

dev 브랜치 내역 병합으로 인한 코드 수정

* refactor: 사용자 상세 프로필 응답 DTO 수정(#8)

- 사진 싱크로율 필드 타입 int로 수정
- 채형 필드 명세 오타 수정

* fix: 불필요한 업로드된 사진 삭제 로직 제거(#8)

- 동일한 URL로 사진을 업로드하기에 이미 업로드된 사진 URL을 업데이트하는
  방식으로 데이터 무결성 보장 가능
- 이에 따라 불필요한 로직들 제거

* refactor: jwt 생성 로직 수정(#8)

- issuedAt, 기존 서비스 명명 방식에 부합하고 더 친숙한 generatedAt으로
  수정
- 불필요한 사용자 이메일 sub 설정 제거

* refactor: JWT 검증 예외, 일괄 처리하도록 로직 수정(#8)

* refactor: 불필요한 트랜잭션 선언 제거(#8)

* feat: 유틸 기능 연관 예외 정의(#8)

- 유효하지 않은 JWT
- 허용되지 않은 이미지 파일 확장자

* refactor: 커스텀 예외 사용하도록 검증 로직 수정(#8)

* refactor: 커스텀 예외 사용토록 로직 수정(#8)

* feat: 인가 과정 제외 URL들 추가(#8)

인가 과정이 필요 없는 리소스 경로들 설정

* refactor: 이미 사용하는 전화번호 검증 메서드명 수정(#8)

이미 사용하는 전화번호인지 확인하는 작업을 수행한다는 것을 잘 표현하기 위해 수정

* feat: 이미 사용 중인 전화번호 검증 로직, 회원가입 로직에 추가(#8)

* refactor: 병합과 메서드명 변경에 따른 수정(#8)

- 이미 사용 중인 전화번호 검증 메서드명 변경에 따라 코드 수정
- dev 브랜치 작업 내역 병합에 따른 수정

* refactor: 병합에 따른 코드 수정(#8)

dev 브랜치 작업내역 병합에 따른 코드 수정

* refactor: jwt 검증 로직 삭제 및 로직 리팩토링(#8)

- 일단 사용하지 않는 jwt 검증 로직 삭제
- issuedAt 변수명, generatedAt으로 일괄 수정

* refactor: EOL 추가, 코드 정렬(#8)

* refactor: 사용 중인 데이터 검증 로직 이름 수정(#8)

사용 중인 데이터(이메일, 폰번호, 별명) 검증 로직 이름 일괄 수정

* refactor: 메서드명 수정에 따른 사용 위치 수정(#8)

* refactor: 메서드명 수정에 따른 사용 위치 수정(#8)
  • Loading branch information
Minjae-An authored Apr 4, 2024
1 parent 87f0b9d commit bdd2d4e
Show file tree
Hide file tree
Showing 33 changed files with 1,029 additions and 114 deletions.
6 changes: 6 additions & 0 deletions be/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

// jwt
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'com.sun.xml.bind:jaxb-impl:2.3.3'
implementation 'com.sun.xml.bind:jaxb-core:2.3.0.1'
}

tasks.named('bootBuildImage') {
Expand Down
9 changes: 7 additions & 2 deletions be/src/main/java/yeonba/be/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public void addInterceptors(InterceptorRegistry registry) {
"/swagger-resources/**",
"/v2/api-docs",
"/webjars/**",
"/error");
"/error")
.excludePathPatterns(
"/users/join/**",
"/users/email-inquiry/**",
"/users/pw-inquiry",
"/users/login",
"/users/refresh");
}

}
24 changes: 24 additions & 0 deletions be/src/main/java/yeonba/be/exception/JoinException.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@

public enum JoinException implements BaseException {

PASSWORD_CONFIRMATION_NOT_MATCH(
HttpStatus.BAD_REQUEST,
"비밀번호 확인 값이 비밀번호와 일치하지 않습니다."),

VOCAL_RANGE_NOT_FOUND(
HttpStatus.BAD_REQUEST,
"존재하지 않는 음역대입니다."),

ANIMAL_NOT_FOUND(
HttpStatus.BAD_REQUEST,
"존재하지 않는 동물상입니다."),

AREA_NOT_FOUND(
HttpStatus.BAD_REQUEST,
"존재하지 않는 지역입니다."),

ALREADY_USED_EMAIL(
HttpStatus.BAD_REQUEST,
"이미 사용 중인 이메일입니다."),

ALREADY_USED_NICKNAME(
HttpStatus.BAD_REQUEST,
"이미 사용 중인 닉네임입니다."),

ALREADY_USED_PHONE_NUMBER(
HttpStatus.BAD_REQUEST,
"이미 사용 중인 핸드폰 번호입니다.");
Expand Down
35 changes: 35 additions & 0 deletions be/src/main/java/yeonba/be/exception/UtilException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package yeonba.be.exception;

import org.springframework.http.HttpStatus;

public enum UtilException implements BaseException {

INVALID_JWT(
HttpStatus.BAD_REQUEST,
"유효하지 않은 JWT입니다. 다시 로그인 해주세요."),

NOT_ALLOWED_IMAGE_FILE_EXTENSION(
HttpStatus.BAD_REQUEST,
"jpg, jpeg, png 확장자 형식의 파일만 허용됩니다.");

private final HttpStatus httpStatus;
private final String reason;

UtilException(HttpStatus httpStatus, String reason) {

this.httpStatus = httpStatus;
this.reason = reason;
}

@Override
public HttpStatus getHttpStatus() {

return httpStatus;
}

@Override
public String getReason() {

return reason;
}
}
12 changes: 8 additions & 4 deletions be/src/main/java/yeonba/be/login/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -21,6 +22,7 @@
import yeonba.be.login.dto.response.UserLoginResponse;
import yeonba.be.login.dto.response.UserRefreshTokenResponse;
import yeonba.be.login.service.LoginService;
import yeonba.be.user.service.JoinService;
import yeonba.be.util.CustomResponse;

@Tag(name = "Login", description = "로그인 관련 API")
Expand All @@ -29,17 +31,18 @@
public class LoginController {

private final LoginService loginService;
private final JoinService joinService;

@Operation(summary = "회원가입", description = "회원가입을 할 수 있습니다.")
@PostMapping("/users/join")
@PostMapping(path = "/users/join", consumes = "multipart/form-data")
public ResponseEntity<CustomResponse<UserJoinResponse>> join(
@RequestBody UserJoinRequest request) {
@Valid @ModelAttribute UserJoinRequest request) {

String createdJwt = "created";
UserJoinResponse response = joinService.join(request);

return ResponseEntity
.ok()
.body(new CustomResponse<>(new UserJoinResponse(createdJwt)));
.body(new CustomResponse<>(response));
}

@Operation(summary = "이메일 찾기 인증 코드 sms 전송", description = "이메일 찾기를 위한 인증번호 sms 전송을 요청합니다.")
Expand Down Expand Up @@ -82,6 +85,7 @@ public ResponseEntity<CustomResponse<Void>> passwordInquiry(
}

@Operation(summary = "로그인", description = "로그인을 할 수 있습니다.")
@ApiResponse(responseCode = "200", description = "로그인 성공")
@PostMapping("/users/login")
public ResponseEntity<CustomResponse<UserLoginResponse>> login(
@RequestBody UserLoginRequest request) {
Expand Down
220 changes: 197 additions & 23 deletions be/src/main/java/yeonba/be/login/dto/request/UserJoinRequest.java
Original file line number Diff line number Diff line change
@@ -1,51 +1,225 @@
package yeonba.be.login.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserJoinRequest {

@Schema(description = "휴대폰 번호", example = "01012345678")
private String phoneNumber;
@Schema(
type = "string",
description = "성별",
example = "남")
@Pattern(
regexp = "^(남|여)$",
message = "성별은 남 또는 여만 가능합니다.")
@NotBlank(message = "성별은 반드시 입력되어야 합니다.")
private String gender;

@Schema(description = "이름", example = "안민재")
private String name;
@Schema(
type = "string",
description = "전화번호",
example = "01011112222")
@Pattern(
regexp = "^010\\d{8}$",
message = "전화번호는 11자리 010으로 시작하며 하이픈(-) 없이 0~9의 숫자로 이뤄져야 합니다.")
@NotBlank(message = "전화번호는 반드시 입력되어야 합니다.")
private String phoneNumber;

@Schema(description = "생일", example = "980315")
private String birth;
@Schema(
type = "string",
description = "비밀번호",
example = "Aa1234!@")
@Pattern(
regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$",
message = """
비밀번호는 영어대소문자, 숫자, 특수문자(~#@!)를
최소 1자씩 포함하며 8~20자 사이여야 합니다.""")
@NotBlank(message = "비밀번호는 반드시 입력되어야 합니다.")
private String password;

@Schema(description = "성별", example = "남자")
private String gender;
@Schema(
type = "string",
description = "비밀번호 확인값",
example = "Aa1234!@")
@NotBlank(message = "비밀번호 확인값은 반드시 입력되어야 합니다.")
private String passwordConfirmation;

@Schema(description = "이메일", example = "[email protected]")
@Schema(
type = "string",
description = "이메일",
example = "[email protected]")
@Pattern(
regexp = "[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$",
message = "유효하지 않은 이메일 형식입니다.")
@NotBlank(message = "이메일은 반드시 입력되어야 합니다.")
private String email;

@Schema(description = "닉네임", example = "calm_min")
@Schema(
type = "string",
description = "생년월일",
example = "1998-04-08")
@NotNull(message = "생년월일은 반드시 입력되어야 합니다.")
private LocalDate birth;

@Schema(
type = "string",
description = "이름",
example = "안민재")
@NotBlank(message = " 이름은 반드시 입력되어야 합니다.")
private String name;

@Schema(
type = "string",
description = "닉네임",
example = "존잘남")
@Pattern(
regexp = "^[a-zA-Z0-9가-힣]{1,8}$",
message = "닉네임은 공백 없이 영어 대소문자,한글,숫자로 구성되어야 하며 최대 8자까지 가능합니다.")
@NotBlank(message = "닉네임은 반드시 입력되어야 합니다.")
private String nickname;

@Schema(description = "키", example = "160")
@Schema(
type = "number",
description = "키",
example = "180")
@Positive(message = "키는 양의 정수여야 합니다.")
private int height;

@Schema(description = "활동 지역", example = "서울시 성북구")
private String activityArea;
@Schema(
type = "string",
description = "체형",
example = "마른체형")
@NotBlank(message = "체형은 반드시 입력되어야 합니다.")
private String bodyType;

@Schema(description = "선호하는 동물상 ID", example = "1")
private int preferAnimalId;
@Schema(
type = "string",
description = "직업",
example = "학생")
@NotBlank(message = "직업은 반드시 입력되어야 합니다.")
private String job;

@Schema(description = "닮은 동물상 ID", example = "1")
private int lookAlikeAnimalId;
@Schema(
type = "string",
description = "활동 지역",
example = "서울")
@NotBlank(message = "활동 지역은 반드시 입력되어야 합니다.")
private String activityArea;

@Schema(description = "프로필 이미지", example = "https://avatars.githubusercontent.com/u/156646513?s=200&v=4")
private String[] images;
@Schema(
type = "string",
description = "MBTI",
example = "ESTJ")
@Pattern(
regexp = "^[EI][SN][TF][JP]$",
message = "유효하지 않은 MBTI 형식입니다.")
@NotBlank(message = "MBTI는 반드시 입력되어야 합니다.")
private String mbti;

@Schema(description = "목소리", example = "중음")
@Schema(
type = "string",
description = "음역대",
example = "저음")
@NotBlank(message = "음역대는 반드시 입력되어야 합니다.")
private String vocalRange;

@Schema(description = "사진 싱그로율", example = "60")
@Schema(
type = "array",
description = "프로필 사진 파일들")
@Size(min = 2, max = 2)
private List<MultipartFile> profilePhotos;

@Schema(
type = "number",
description = "사진 싱크로율",
example = "80")
@Min(
value = 80,
message = "사진 싱크로율이 80퍼 이상이어야 가입할 수 있습니다.")
private int photoSyncRate;

@Schema(
type = "string",
description = "닮은 동물상",
example = "강아지상")
@NotBlank(message = "닮은 동물상은 반드시 입력되어야 합니다.")
private String lookAlikeAnimal;

@Schema(
type = "string",
description = "선호하는 동물상",
example = "강아지상")
@NotBlank(message = "선호하는 동물상은 반드시 입력되어야 합니다.")
private String preferredAnimal;

@Schema(
type = "string",
description = "선호하는 지역",
example = "서울")
@NotBlank(message = "선호하는 지역은 반드시 입력되어야 합니다.")
private String preferredArea;

@Schema(
type = "string",
description = "선호하는 음역대",
example = "저음")
@NotBlank(message = "선호하는 음역대는 반드시 입력되어야 합니다.")
private String preferredVocalRange;

@Schema(
type = "number",
description = "선호하는 나이 하한",
example = "22")
@Positive(message = "선호하는 나이 하한은 양수여야 합니다.")
private int preferredAgeLowerBound;

@Schema(
type = "number",
description = "선호하는 나이 상한",
example = "30")
@Positive(message = "선호하는 나이 상한은 양수여야 합니다.")
private int preferredAgeUpperBound;

@Schema(
type = "number",
description = "선호하는 키 하한",
example = "177")
@Positive(message = "선호하는 키 하한은 양수여야 합니다.")
private int preferredHeightLowerBound;

@Schema(
type = "number",
description = "선호하는 키 상한",
example = "185")
@Positive(message = "선호하는 키 상한은 양수여야 합니다.")
private int preferredHeightUpperBound;

@Schema(
type = "string",
description = "선호하는 체형",
example = "마른체형")
@NotBlank(message = "선호하는 체형은 반드시 입력되어야 합니다.")
private String preferredBodyType;

@Schema(
type = "string",
description = "선호하는 MBTI",
example = "ISTJ")
@Pattern(
regexp = "^[EI][SN][TF][JP]$",
message = "유효하지 않은 MBTI 형식입니다.")
@NotBlank(message = "선호하는 MBTI는 반드시 입력되어야 합니다.")
private String preferredMbti;
}
Loading

0 comments on commit bdd2d4e

Please sign in to comment.