Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ repositories {
}

dependencies {
// implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/dmu/dasom/api/ApiApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class ApiApplication {

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/dmu/dasom/api/domain/common/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dmu.dasom.api.domain.common;

import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@Getter
@MappedSuperclass
public class BaseEntity {

// 생성일
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;

// 수정일
@LastModifiedDate
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

// 상태
@Column(name = "status", length = 16, nullable = false)
@Enumerated(EnumType.STRING)
private Status status = Status.ACTIVE;

// 상태 변경
public void updateStatus(final Status status) {
this.status = status;
}

}
7 changes: 7 additions & 0 deletions src/main/java/dmu/dasom/api/domain/common/Status.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dmu.dasom.api.domain.common;

public enum Status {
ACTIVE,
INACTIVE,
DELETED
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package dmu.dasom.api.domain.common.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class CustomControllerAdvice {

@ExceptionHandler(CustomException.class)
public ErrorResponse customException(final CustomException e) {
return new ErrorResponse(e.getErrorCode().getCode(), e.getErrorCode().getMessage());
public ResponseEntity<ErrorResponse> customException(final CustomException e) {
return ResponseEntity.status(e.getErrorCode().getStatus()).body(new ErrorResponse(e.getErrorCode()));
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> methodArgumentNotValidException(final MethodArgumentNotValidException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse(ErrorCode.ARGUMENT_NOT_VALID));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ public enum ErrorCode {

UNAUTHORIZED(401, "C001", "인증되지 않은 사용자입니다."),
FORBIDDEN(403, "C002", "권한이 없습니다."),
MEMBER_NOT_FOUND(400, "C003", "해당 회원을 찾을 수 없습니다."),
TOKEN_EXPIRED(400, "C004", "토큰이 만료되었습니다."),
LOGIN_FAILED(400, "C005", "로그인에 실패하였습니다."),
SIGNUP_FAILED(400, "C006", "회원가입에 실패하였습니다."),
ARGUMENT_NOT_VALID(400, "C007", "요청한 값이 올바르지 않습니다."),
TOKEN_NOT_VALID(400, "C008", "토큰이 올바르지 않습니다."),
;

private final int status;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package dmu.dasom.api.domain.common.exception;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {

private String code;
private String message;

public ErrorResponse(final ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dmu.dasom.api.domain.member.controller;

import dmu.dasom.api.domain.common.exception.ErrorResponse;
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
import dmu.dasom.api.domain.member.service.MemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RestController;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;

// 회원가입
@Operation(summary = "회원가입")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "회원가입 성공"),
@ApiResponse(responseCode = "400", description = "실패 케이스",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class),
examples = {
@ExampleObject(
name = "이메일 중복",
value = "{ \"code\": \"C006\", \"message\": \"회원가입에 실패하였습니다.\" }"
),
@ExampleObject(
name = "이메일 또는 비밀번호 형식 올바르지 않음",
value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"
)
}
)
)
})
@PostMapping("/auth/signup")
public ResponseEntity<Void> signUp(@Valid @RequestBody final SignupRequestDto request) {
memberService.signUp(request);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dmu.dasom.api.domain.member.dto;

import dmu.dasom.api.domain.member.entity.Member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import org.hibernate.validator.constraints.Length;

@Getter
@Schema(name = "SignupRequestDto", description = "회원가입 요청 DTO")
public class SignupRequestDto {

@Email
@Length(max = 64)
@NotNull
@Schema(description = "이메일", example = "[email protected]", maxLength = 64)
private String email;

@Length(min = 8, max = 128)
@NotNull
@Schema(description = "비밀번호", example = "password", minLength = 8, maxLength = 128)
private String password;

public Member toEntity(final String password) {
return Member.builder()
.email(this.email)
.password(password)
.build();
}
}
36 changes: 36 additions & 0 deletions src/main/java/dmu/dasom/api/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dmu.dasom.api.domain.member.entity;

import dmu.dasom.api.domain.common.BaseEntity;
import dmu.dasom.api.domain.member.enums.Role;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Builder
@Entity
@Getter
@NoArgsConstructor
public class Member extends BaseEntity {

@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;

@Column(name = "email", unique = true, length = 64, nullable = false)
@Email
private String email;

@Column(name = "password", length = 128, nullable = false)
private String password;

@Builder.Default
@Column(name = "role", length = 16, nullable = false)
@Enumerated(EnumType.STRING)
private Role role = Role.ROLE_MEMBER;

}
16 changes: 16 additions & 0 deletions src/main/java/dmu/dasom/api/domain/member/enums/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dmu.dasom.api.domain.member.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum Role {

ROLE_MEMBER("MEMBER"),
ROLE_ADMIN("ADMIN"),
;

private final String name;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dmu.dasom.api.domain.member.repository;

import dmu.dasom.api.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByEmail(final String email);

boolean existsByEmail(final String email);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dmu.dasom.api.domain.member.service;

import dmu.dasom.api.domain.member.dto.SignupRequestDto;
import dmu.dasom.api.domain.member.entity.Member;

public interface MemberService {

Member getMemberByEmail(final String email);

boolean checkByEmail(final String email);

void signUp(final SignupRequestDto request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dmu.dasom.api.domain.member.service;

import dmu.dasom.api.domain.common.exception.CustomException;
import dmu.dasom.api.domain.common.exception.ErrorCode;
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
import dmu.dasom.api.domain.member.entity.Member;
import dmu.dasom.api.domain.member.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class MemberServiceImpl implements MemberService {

private final BCryptPasswordEncoder encoder;
private final MemberRepository memberRepository;

// 이메일로 사용자 조회
@Override
public Member getMemberByEmail(final String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
}

// 이메일 확인
@Override
public boolean checkByEmail(final String email) {
return memberRepository.existsByEmail(email);
}

// 회원가입
@Override
public void signUp(final SignupRequestDto request) {
// 이미 가입된 이메일인지 확인
if (checkByEmail(request.getEmail()))
throw new CustomException(ErrorCode.SIGNUP_FAILED);

// 비밀번호 암호화 후 저장
memberRepository.save(request.toEntity(encoder.encode(request.getPassword())));
}

}
15 changes: 15 additions & 0 deletions src/main/java/dmu/dasom/api/global/auth/config/EncoderConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dmu.dasom.api.global.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class EncoderConfig {

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

}
Loading
Loading