Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Category 기본 CRUD 구현 #2

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
86cc1d7
feat: 스프링 시큐리티 jwt 적용 WIP
hjj4060 Jul 27, 2024
bc06f3c
feat: develop rebase
hjj4060 Aug 4, 2024
dd54226
feat: 스프링 시큐리티 jwt 적용 WIP
hjj4060 Jul 31, 2024
35f441f
feat: memberService 코틀린 적용 WIP
hjj4060 Aug 1, 2024
96d158c
feat: memberService 코틀린 적용 WIP
hjj4060 Aug 2, 2024
d0bbe7a
feat: 이메일 적용
hjj4060 Aug 3, 2024
f7d1e6f
feat: address, seller 코틀린 전환
hjj4060 Aug 3, 2024
a06a523
feat: 오류 수정
hjj4060 Aug 3, 2024
7499767
Merge pull request #8 from ClothingStoreService/feature/spring-securi…
hjj4060 Aug 4, 2024
1cee9fe
feat: Category 기본 CRUD 구현
Ogu1208 Jul 27, 2024
edc96c9
refactor: Category DTO, 엔티티 생성 refactor
Ogu1208 Aug 3, 2024
3829b24
refactor: toEntity 로직 삭제
Ogu1208 Aug 3, 2024
ccaaa13
test: CategoryService Test
Ogu1208 Aug 4, 2024
31b086b
refactor: 중복 setter 수정
Ogu1208 Aug 4, 2024
e0e4604
feat: Product 도메인 엔티티 구현
Ogu1208 Aug 3, 2024
d1d8b6c
refactor: id null
Ogu1208 Aug 4, 2024
9bfc559
fix: import 에러 해결 및 rebase
Ogu1208 Aug 4, 2024
5a84d66
fix: product 도메인 매핑 오류 수정, 개행 수정
Ogu1208 Aug 4, 2024
853e6e6
fix: product 도메인 엔티티 DB 예약어 필드 네이밍 수정
Ogu1208 Aug 4, 2024
712e848
feat: product 도메인 레포지토리 생성
Ogu1208 Aug 4, 2024
a2eb3bf
refactor: 생성자 컨벤션 수정, 엔티티 연관관계 수정
Ogu1208 Aug 4, 2024
0b6b598
refactor: product 도메인 연관관계 , 컨벤션 수정
Ogu1208 Aug 5, 2024
c1a069c
Merge branch 'feature/develop-before-merge' into feature/product-opti…
Ogu1208 Aug 5, 2024
8b1aa7e
Merge pull request #5 from ClothingStoreService/feature/product-optio…
Ogu1208 Aug 5, 2024
1d5fdc8
feat: 오류 수정
hjj4060 Aug 5, 2024
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
22 changes: 13 additions & 9 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,24 @@ dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springframework.boot:spring-boot-starter-validation")
compileOnly("org.projectlombok:lombok")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("io.github.oshai:kotlin-logging-jvm:5.1.4")

//web
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")

// logging
implementation("io.github.oshai:kotlin-logging-jvm:5.1.4")

//security
// implementation("org.springframework.boot:spring-boot-starter-security")
// implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
// implementation("io.jsonwebtoken:jjwt-api:0.12.3")
// runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
// runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
// testImplementation("org.springframework.boot:spring-boot-starter-test")
// testImplementation("org.springframework.security:spring-security-test")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
implementation("io.jsonwebtoken:jjwt-api:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")

//DB
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
Expand Down Expand Up @@ -69,6 +72,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-mail") //mail 전송
implementation("org.springframework.boot:spring-boot-starter-data-redis") //redis
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/org/store/clothstar/ClothstarApplication.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.store.clothstar

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@ConfigurationPropertiesScan
@SpringBootApplication
@EnableJpaAuditing //Jpa Auditing 기능 활성화
class ClothstarApplication {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.store.clothstar.category.controller

import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import org.store.clothstar.category.dto.request.CreateCategoryRequest
import org.store.clothstar.category.dto.request.UpdateCategoryRequest
import org.store.clothstar.category.dto.response.CategoryResponse
import org.store.clothstar.category.service.CategoryService
import org.store.clothstar.common.dto.MessageDTO
import org.store.clothstar.common.util.URIBuilder

@RestController
@RequestMapping("/v1/categories")
class CategoryController(
private val categoryService: CategoryService,
// private val productLineService: ProductLineService
) {

@Operation(summary = "전체 카테고리 조회", description = "모든 카테고리를 조회한다.")
@GetMapping
fun getAllCategories(): ResponseEntity<List<CategoryResponse>> {
val categoryResponses = categoryService.getAllCategories()
return ResponseEntity.ok(categoryResponses)
}

@Operation(summary = "카테고리 상세 조회", description = "id로 카테고리 한개를 상세 조회한다.")
@GetMapping("/{categoryId}")
fun getCategory(@PathVariable categoryId: Long): ResponseEntity<CategoryResponse> {
val categoryDetailResponse = categoryService.getCategory(categoryId)
return ResponseEntity.ok(categoryDetailResponse)
}

@Operation(summary = "카테고리 등록", description = "카테고리 타입(이름)을 입력하여 신규 카테고리를 등록한다.")
@PostMapping
fun createCategory(@Validated @RequestBody createCategoryRequest: CreateCategoryRequest): ResponseEntity<Void> {
val categoryId = categoryService.createCategory(createCategoryRequest)
val location = URIBuilder.buildURI(categoryId)
return ResponseEntity.created(location).build()
}

@Operation(summary = "카테고리 수정", description = "카테고리 이름을 수정한다.")
@PutMapping("/{categoryId}")
fun updateCategories(
@PathVariable categoryId: Long,
@Validated @RequestBody updateCategoryRequest: UpdateCategoryRequest
): ResponseEntity<MessageDTO> {
categoryService.updateCategory(categoryId, updateCategoryRequest)
return ResponseEntity.ok(MessageDTO(HttpStatus.OK.value(), "Category updated successfully"))
}

@Operation(summary = "카테고리 삭제", description = "id로 카테고리를 삭제한다.")
@DeleteMapping("/{categoryId}")
fun deleteCategory(@PathVariable categoryId: Long): ResponseEntity<MessageDTO> {
categoryService.deleteCategory(categoryId)
return ResponseEntity.ok(MessageDTO(HttpStatus.OK.value(), "Category deleted successfully"))
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/org/store/clothstar/category/domain/Category.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.store.clothstar.category.domain

import jakarta.persistence.*
import org.store.clothstar.category.dto.request.UpdateCategoryRequest
import org.store.clothstar.common.domain.BaseTimeEntity

@Entity
data class Category(

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

@Column(nullable = false, unique = true)
var categoryType: String

) : BaseTimeEntity() {

fun updateCategory(updateCategoryRequest: UpdateCategoryRequest) {
this.categoryType = updateCategoryRequest.categoryType
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.store.clothstar.category.dto.request

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import org.store.clothstar.category.domain.Category

class CreateCategoryRequest(
@Schema(description = "카테고리 타입(이름)", nullable = false)
@field:NotBlank(message = "카테고리 타입을 입력해주세요.")
val categoryType: String
)
// 공백, 긴 리스트, 빈 문자열 -> 예외처리가 되어있어야 함
// value, null 허용 X
// dto -> 데이터가 변경되는지 고민
// data -> equals(), hashcode(), toString(), comporeTo() 자동 생성 -> 사용성이 있는지 고민
// toCategory -> 모든 DTO에 Entity로 변환하는 로직이 있어야함 (toEntity)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.store.clothstar.category.dto.request

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank

data class UpdateCategoryRequest(
@Schema(description = "카테고리 타입(이름)", nullable = false)
@field:NotBlank(message = "카테고리 타입을 입력해주세요.")
val categoryType: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.store.clothstar.category.dto.response

import org.store.clothstar.category.domain.Category

data class CategoryResponse(
val categoryId: Long?,
val categoryType: String?
) {
companion object {
fun from(category: Category): CategoryResponse {
return CategoryResponse(
categoryId = category.categoryId,
categoryType = category.categoryType
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.store.clothstar.category.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import org.store.clothstar.category.domain.Category

@Repository
interface CategoryJpaRepository : JpaRepository<Category, Long>

// TODO: QuryDSL 클래스
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.store.clothstar.category.service

import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import org.store.clothstar.category.domain.Category
import org.store.clothstar.category.dto.request.CreateCategoryRequest
import org.store.clothstar.category.dto.request.UpdateCategoryRequest
import org.store.clothstar.category.dto.response.CategoryResponse
import org.store.clothstar.category.repository.CategoryJpaRepository
import org.store.clothstar.common.util.Logger.Companion.log

@Service
class CategoryService(
private val categoryRepository: CategoryJpaRepository
) {
@Transactional(readOnly = true)
fun getAllCategories(): List<CategoryResponse> {
return categoryRepository.findAll().map { CategoryResponse.from(it) }
}

@Transactional(readOnly = true)
fun getCategory(categoryId: Long): CategoryResponse {
val category = categoryRepository.findById(categoryId)
.orElseThrow { ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 카테고리를 찾을 수 없습니다.") }
return CategoryResponse.from(category)
}

@Transactional
fun createCategory(createCategoryRequest: CreateCategoryRequest): Long? {
val category = Category(categoryType = createCategoryRequest.categoryType) // dto의 메서드로 entity로 변환 -> ?? 코틀린에서는
val savedCategory = categoryRepository.save(category)
return savedCategory.categoryId
.also {
log.info { "Category created, id : ${category.categoryId}" }
}
}

@Transactional
fun updateCategory(categoryId: Long, updateCategoryRequest: UpdateCategoryRequest) {
val category = categoryRepository.findById(categoryId)
.orElseThrow { ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 카테고리를 찾을 수 없습니다.") }
category.updateCategory(updateCategoryRequest)
}

@Transactional
fun deleteCategory(categoryId: Long) {
val category = categoryRepository.findById(categoryId)
.orElseThrow { ResponseStatusException(HttpStatus.BAD_REQUEST, "해당 카테고리를 찾을 수 없습니다.") }
categoryRepository.delete(category)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.store.clothstar.common.config

import com.fasterxml.jackson.databind.ObjectMapper
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.stereotype.Component
import org.store.clothstar.common.dto.MessageDTO
import java.io.IOException

@Component
class CustomAuthenticationEntryPoint : AuthenticationEntryPoint {
private val log = KotlinLogging.logger {}

@Throws(IOException::class, ServletException::class)
override fun commence(
request: HttpServletRequest,
response: HttpServletResponse,
authException: AuthenticationException,
) {
log.error { "인증 실패 로직 실행" }
response.status = HttpServletResponse.SC_UNAUTHORIZED
response.characterEncoding = "UTF-8"
response.contentType = "application/json"

val messageDTO = MessageDTO(
HttpServletResponse.SC_UNAUTHORIZED,
"권한이 없습니다."
)

response.writer.write(ObjectMapper().writeValueAsString(messageDTO))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.store.clothstar.common.config

import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
import org.store.clothstar.common.error.ErrorCode
import org.store.clothstar.member.domain.Account
import org.store.clothstar.member.domain.CustomUserDetails
import org.store.clothstar.member.repository.AccountRepository

@Service
class CustomUserDetailsService(
private val accountRepository: AccountRepository,
) : UserDetailsService {
private val log = KotlinLogging.logger {}

@Throws(UsernameNotFoundException::class)
override fun loadUserByUsername(email: String): UserDetails {
log.info { "loadUserByUsername() 실행" }
val account: Account = accountRepository.findByEmail(email)
?: throw UsernameNotFoundException(ErrorCode.NOT_FOUND_ACCOUNT.message)

return CustomUserDetails(account)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.store.clothstar.common
package org.store.clothstar.common.config

import org.jasypt.encryption.StringEncryptor
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor
Expand Down
Loading