This commit is contained in:
ParkWonYeop
2025-12-27 17:52:40 +09:00
parent 884853586d
commit 46a8a43163
29 changed files with 646 additions and 149 deletions

View File

@@ -17,22 +17,28 @@ class PostController(
private val postService: PostService
) {
// 목록 조회 (기본값: 최신순, 10개씩)
@GetMapping
fun getPosts(
@RequestParam(required = false) keyword: String?,
@RequestParam(required = false) category: String?,
@RequestParam(required = false) tag: String?, // 👈 파라미터 추가
@PageableDefault(size = 10, sort = ["id"], direction = Sort.Direction.DESC) pageable: Pageable
@RequestParam(required = false) category: String?, // 👈 프론트는 'category'로 보냄
@RequestParam(required = false) tag: String?,
@PageableDefault(size = 10, sort = ["createdAt"], direction = Sort.Direction.DESC) pageable: Pageable
): ResponseEntity<ApiResponse<Page<PostSummaryResponse>>> {
val result = postService.searchPosts(keyword, category, tag, pageable)
return ResponseEntity.ok(ApiResponse.success(result))
// 검색 조건이 하나라도 있으면 searchPosts 호출 (검색 + 카테고리 필터링)
return if (keyword != null || category != null || tag != null) {
val posts = postService.searchPosts(keyword, category, tag, pageable)
ResponseEntity.ok(ApiResponse.success(posts))
} else {
// 조건이 없으면 전체 목록 조회
val posts = postService.getPosts(pageable)
ResponseEntity.ok(ApiResponse.success(posts))
}
}
// 상세 조회 (Slug)
@GetMapping("/{slug}")
fun getPost(@PathVariable slug: String): ResponseEntity<ApiResponse<PostResponse>> {
return ResponseEntity.ok(ApiResponse.success(postService.getPostBySlug(slug)))
val post = postService.getPostBySlug(slug)
return ResponseEntity.ok(ApiResponse.success(post))
}
}

View File

@@ -0,0 +1,21 @@
package me.wypark.blogbackend.api.controller
import me.wypark.blogbackend.api.common.ApiResponse
import me.wypark.blogbackend.api.dto.ProfileResponse
import me.wypark.blogbackend.domain.profile.BlogProfileService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/profile")
class ProfileController(
private val blogProfileService: BlogProfileService
) {
@GetMapping
fun getProfile(): ResponseEntity<ApiResponse<ProfileResponse>> {
return ResponseEntity.ok(ApiResponse.success(blogProfileService.getProfile()))
}
}

View File

@@ -2,6 +2,7 @@ package me.wypark.blogbackend.api.controller.admin
import me.wypark.blogbackend.api.common.ApiResponse
import me.wypark.blogbackend.api.dto.CategoryCreateRequest
import me.wypark.blogbackend.api.dto.CategoryUpdateRequest
import me.wypark.blogbackend.domain.category.CategoryService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@@ -18,6 +19,16 @@ class AdminCategoryController(
return ResponseEntity.ok(ApiResponse.success(id, "카테고리가 생성되었습니다."))
}
// 👈 [추가] 카테고리 수정 (이름, 위치 이동)
@PutMapping("/{id}")
fun updateCategory(
@PathVariable id: Long,
@RequestBody request: CategoryUpdateRequest
): ResponseEntity<ApiResponse<Nothing>> {
categoryService.updateCategory(id, request)
return ResponseEntity.ok(ApiResponse.success(message = "카테고리가 수정되었습니다."))
}
@DeleteMapping("/{id}")
fun deleteCategory(@PathVariable id: Long): ResponseEntity<ApiResponse<Nothing>> {
categoryService.deleteCategory(id)

View File

@@ -7,10 +7,7 @@ import me.wypark.blogbackend.domain.post.PostService
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.userdetails.User
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
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/admin/posts")
@@ -23,8 +20,23 @@ class AdminPostController(
@RequestBody @Valid request: PostSaveRequest,
@AuthenticationPrincipal user: User
): ResponseEntity<ApiResponse<Long>> {
// user.username은 email입니다.
val postId = postService.createPost(request, user.username)
return ResponseEntity.ok(ApiResponse.success(postId, "게시글이 작성되었습니다."))
}
// 👈 [추가] 게시글 수정 엔드포인트
@PutMapping("/{id}")
fun updatePost(
@PathVariable id: Long,
@RequestBody @Valid request: PostSaveRequest
): ResponseEntity<ApiResponse<Long>> {
val postId = postService.updatePost(id, request)
return ResponseEntity.ok(ApiResponse.success(postId, "게시글이 수정되었습니다."))
}
@DeleteMapping("/{id}")
fun deletePost(@PathVariable id: Long): ResponseEntity<ApiResponse<Nothing>> {
postService.deletePost(id)
return ResponseEntity.ok(ApiResponse.success(message = "게시글과 포함된 이미지가 삭제되었습니다."))
}
}

View File

@@ -0,0 +1,23 @@
package me.wypark.blogbackend.api.controller.admin
import me.wypark.blogbackend.api.common.ApiResponse
import me.wypark.blogbackend.api.dto.ProfileUpdateRequest
import me.wypark.blogbackend.domain.profile.BlogProfileService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/admin/profile")
class AdminProfileController(
private val blogProfileService: BlogProfileService
) {
@PutMapping
fun updateProfile(@RequestBody request: ProfileUpdateRequest): ResponseEntity<ApiResponse<Nothing>> {
blogProfileService.updateProfile(request)
return ResponseEntity.ok(ApiResponse.success(message = "프로필이 수정되었습니다."))
}
}

View File

@@ -8,6 +8,12 @@ data class CategoryCreateRequest(
val parentId: Long? = null // null이면 최상위(Root) 카테고리
)
// [요청] 카테고리 수정 (이름 + 부모 이동)
data class CategoryUpdateRequest(
val name: String,
val parentId: Long? // null이면 최상위(Root)로 이동
)
// [응답] 카테고리 트리 구조 (재귀)
data class CategoryResponse(
val id: Long,

View File

@@ -8,7 +8,8 @@ data class CommentResponse(
val id: Long,
val content: String,
val author: String,
val isPostAuthor: Boolean, // 👈 [추가] 게시글 작성자 여부
val isPostAuthor: Boolean,
val memberId: Long?,
val createdAt: LocalDateTime,
val children: List<CommentResponse>
) {
@@ -22,7 +23,8 @@ data class CommentResponse(
id = comment.id!!,
content = comment.content,
author = comment.getAuthorName(),
isPostAuthor = isAuthor, // 👈 계산된 값 주입
isPostAuthor = isAuthor,
memberId = comment.member?.id,
createdAt = comment.createdAt,
children = comment.children.map { from(it) }
)

View File

@@ -36,7 +36,8 @@ data class PostSummaryResponse(
val slug: String,
val categoryName: String?,
val viewCount: Long,
val createdAt: LocalDateTime
val createdAt: LocalDateTime,
val content: String?
) {
companion object {
fun from(post: Post): PostSummaryResponse {
@@ -46,7 +47,8 @@ data class PostSummaryResponse(
slug = post.slug,
categoryName = post.category?.name,
viewCount = post.viewCount,
createdAt = post.createdAt
createdAt = post.createdAt,
content = post.content
)
}
}

View File

@@ -0,0 +1,31 @@
package me.wypark.blogbackend.api.dto
import me.wypark.blogbackend.domain.profile.BlogProfile
data class ProfileResponse(
val name: String,
val bio: String,
val imageUrl: String?,
val githubUrl: String?,
val email: String?
) {
companion object {
fun from(profile: BlogProfile): ProfileResponse {
return ProfileResponse(
name = profile.name,
bio = profile.bio,
imageUrl = profile.imageUrl,
githubUrl = profile.githubUrl,
email = profile.email
)
}
}
}
data class ProfileUpdateRequest(
val name: String,
val bio: String,
val imageUrl: String?,
val githubUrl: String?,
val email: String?
)