본문 바로가기

카테고리 없음

아예 새로운 프로젝트를 만들어보기. admin부터

그냥 내가 배운걸 바탕으로 새로운 프로젝트를 만들어 볼까 한다.

 

관리자와 사용자를 따로 두고 관리자는 jwt로 유저는 오어스로 로그인을 하고 관리자는 게시판 관리 기능,유저에 관리 기능을 가지고 유저는게시판 작성, 댓글 쓰기, 신고 등의 기능을 가진다, 그리고 검색으로 제목, 인기검색어, 추천 높은순 검색 등을 넣는다.

 

어드민 가입기능은

@Table(name = "admin")
@Entity
class Admin(
    @Column(name = "email", unique = true, nullable = false)
    var email: String,

    @Column(name = "password", nullable = false)
    var password: String,

    @Column(name = "nickname", nullable = false)
    var nickname: String,

    @Column(name = "address", nullable = false)
    var address: String,

    @Column(name = "phone_number", nullable = false)
    var phoneNumber: String,

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null
)

이렇게 이메일, 비번, 닉네임, 주소, 전화번호로 한다.

data class AdminSignUpRequest(
    val email: String,
    val password: String,
    val passwordCheck: String,
    var nickname: String,
    var address: String,
    var phoneNumber: String
)

이것이 회원가입에 필요한 dto고 닉네임 밑으론 수정기능으로 변경을 할수있게 var을 섰다.

 

그리고 dto는

AdminSignUpRequest
AdminUpdateRequest
AdminSignInRequest
AdminResponse
AdminSignInResponse 이런게 필요함.

 

리퀘스트는 스웨거 화면에서 작성하는거고 리스폰스는 사용자에게 보여지는 부분.

 

AdminSignInResponse는 어드민에게만 특별한 권한을 부여하려고 하면 필요함.

class AdminSignInResponse (
    val accessToken: String,
)

 

이러면 어드민의 토큰만 특별취급 가능

 

data class AdminResponse(
    val id: Long,
    val email: String,
    val nickname: String,
    var phoneNumber: String
) {
    companion object {
        fun toResponse(admin: Admin): AdminResponse {
            return AdminResponse(
                admin.id!!,
                admin.email,
                admin.nickname,
                admin.phoneNumber
            )
        }
    }
}

 

 

이것은 서비스임플과 연계되어  어드민을 생성시 연결됨

interface AdminRepository : JpaRepository<Admin, Long> {
    fun findByEmail(email: String): Admin? // 이메일이 있는지 찾는거
    fun existsByEmail(email: String): Boolean // 이메일이 중복되는지 찾는거
}

 

이것도 서비스임플과 연결되서 이메일을 찾는다

@Service
class AdminServiceImpl(
    private val adminRepository: AdminRepository,
    private val passwordEncoder: PasswordEncoder,
    private val jwtPlugin: JwtPlugin,
) : AdminService {

    @Transactional
    override fun registerAdmin(adminSignUpRequest: AdminSignUpRequest): AdminResponse {
        if (adminRepository.existsByEmail(adminSignUpRequest.email)) {
            throw IllegalStateException("Email is already in use")
        }
        if (adminSignUpRequest.passwordCheck != adminSignUpRequest.password) throw InvalidCredentialException("Passwords do not match")
        val admin = adminRepository.save(adminSignUpRequest.toEntity(passwordEncoder))
        return AdminResponse.fromAdmin(admin)
    }

    @Transactional
    override fun updateAdmin(id: Long, adminUpdateRequest: AdminUpdateRequest): AdminResponse {
        val admin = adminRepository.findById(id).orElseThrow {
            ModelNotFoundException("Admin not found", id)
        }

        // 관리자 정보 업데이트 로직
        return AdminResponse.fromAdmin(admin)
    }

    @Transactional
    override fun deleteAdmin(id: Long) {
        val admin = adminRepository.findById(id).orElseThrow {
            ModelNotFoundException("Admin not found", id)
        }

        admin.deletedAt = LocalDateTime.now()
        adminRepository.save(admin)
    }

    @Transactional
    override fun signInAdmin(adminSignInRequest: AdminSignInRequest): AdminSignInResponse {
        val admin =
            adminRepository.findByEmail(adminSignInRequest.email) ?: throw ModelNotFoundException("User not found", null)
        if (!passwordEncoder.matches(adminSignInRequest.password, admin.password)) throw InvalidCredentialException("Invalid email or password")

        return AdminSignInResponse(
            accessToken =
            jwtPlugin.generateAccessToken(
                subject = admin.id.toString(),
                // 다음 필드는 Admin 엔티티가 해당 정보를 가지고 있다고 가정합니다.
                nickname = admin.nickname,
                email = admin.email,
            ),
        )
    }
}

여기서 어드민 생성을 살펴보면,

이메일 중복 검사: 먼저 adminRepository.existsByEmail(adminSignUpRequest.email)를 호출하여 입력받은 이메일이 이미 사용 중인지 검사.

이 메소드는 AdminRepository 인터페이스에 정의되어 있으며, 해당 이메일을 가진 관리자가 존재하는지 확인하고 만약 이미 존재한다면, IllegalStateException을 발생시켜 "Email is already in use"라는 메시지와 함께 예외 처리

비밀번호 일치 검사: 입력받은 비밀번호(adminSignUpRequest.password)와 비밀번호 확인(adminSignUpRequest.passwordCheck) 필드가 같은지 검사. 만약 두 필드가 일치하지 않는다면 InvalidCredentialException을 발생시켜 "Passwords do not match"라는 메시지와 함께 예외 처리.

관리자 정보 저장: 위의 두 검사를 모두 통과하면, adminSignUpRequest.toEntity(passwordEncoder)를 호출하여 관리자 등록 요청 정보를 Admin 엔티티로 변환. 여기서 passwordEncoder는 비밀번호를 안전하게 저장함. 그 다음, 변환된 Admin 엔티티를 adminRepository.save(admin)를 통해 데이터베이스에 저장

응답 생성: 마지막으로, 저장된 관리자 정보(admin)를 AdminResponse.toResponse(admin)을 호출하여 AdminResponse 객체로 변환합니다. 이 객체는 클라이언트에 반환될 응답 데이터를 담고 있습니다. toResponse 메소드는 Admin 객체를 받아서 AdminResponse 객체를 생성하고 반환

 

그리고 삭제시엔 소프트 딜리트를 넣기로 결정함.

 

그러니 admin도 소프트딜리트 되게 바꿔야함.

 

@Table(name = "admin")
@Entity
class Admin(
    @Column(name = "email", unique = true, nullable = false)
    var email: String,

    @Column(name = "password", nullable = false)
    var password: String,

    @Column(name = "nickname", nullable = false)
    var nickname: String,

    @Column(name = "address", nullable = false)
    var address: String,

    @Column(name = "phone_number", nullable = false)
    var phoneNumber: String,

    @Column(name = "deleted_at")
    var deletedAt: LocalDateTime? = null, //소프트딜리트 추가

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null
)

 

interface AdminRepository : JpaRepository<Admin, Long> {
    fun findByEmail(email: String): Admin? // 이메일이 있는지 찾는거
    fun existsByEmail(email: String): Boolean // 이메일이 중복되는지 찾는거
    fun findByIdAndDeletedAtIsNull(id: Long): Optional<Admin>// 논리삭제 안된거 조회

}

 

이제 소프트딜리트가 추가됬다.

 

@RestController
@RequestMapping("/sellers")
class SellerController(
    private val sellerService: SellerService,
) {
    @PostMapping("/signup")
    fun signUpSeller(
        @RequestBody sellerSignUpRequest: SellerSignUpRequest,
    ): ResponseEntity<SellerResponse> {
        return ResponseEntity
            .status(HttpStatus.OK)
            .body(sellerService.signUpSeller(sellerSignUpRequest))
    }

    @PostMapping("/signin")
    fun signInSeller(
        @RequestBody sellerSignInRequest: SellerSignInRequest,
    ): ResponseEntity<SellerSignInResponse> {
        return ResponseEntity
            .status(HttpStatus.OK)
            .body(sellerService.signInSeller(sellerSignInRequest))
    }

    @DeleteMapping
    fun deleteSeller(
        @AuthenticationPrincipal sellerPrincipal: UserPrincipal,
    ): ResponseEntity<SellerResponse> {
        return ResponseEntity.status(HttpStatus.NO_CONTENT)
            .body(sellerService.deleteSeller(sellerPrincipal.id))
    }
}

 

이제 컨트롤러도 만들었고 다음엔 jwt, 패스워드 인코더를 만들어야 한다. 익셉션은 이전걸 사용했다.