Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@

- AUTH-TOKEN 쿠키를 사용합니다.

## 사용자 권한

- 사용자의 권한은 users 테이블의 role INT 필드에 저장되며, 기본값은 0입니다
- 관리자 권한은 1000으로 설정됩니다

## Admin

- db 초기화 시 id: admin, pw: admin, role: 1000 인 사용자가 생성됩니다
- 테스트는 이 상태를 기준으로 작성됩니다
- 배포 환경에서는 배포 즉시 비밀번호를 변경해야 합니다

## Google OAuth2

- `/oauth2/authorization/google?redirect_uri=` 로 이동하면 구글 로그인 과정이 진행됩니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.wafflestudio.team2server.article.dto.response.ArticlePagingResponse
import com.wafflestudio.team2server.article.dto.response.CreateArticleResponse
import com.wafflestudio.team2server.article.service.ArticleService
import com.wafflestudio.team2server.hotstandard.dto.core.HotStandardDto
import com.wafflestudio.team2server.user.AdminUser
import com.wafflestudio.team2server.user.model.User
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
Expand Down Expand Up @@ -71,14 +73,15 @@ class ArticleController(
)
@PatchMapping("/hots")
fun hotsUpdate(
@Parameter(hidden = true) @AdminUser user: User,
@Parameter(
description = "총 점수 기준",
example = "10",
)@RequestParam(value = "hotScore", required = false) hotScore: Long?,
@Parameter(
description = "조회수 가중치",
example = "1.0",
)@RequestParam(value = "viewsWeight", required = false) viewsWeight: kotlin.Double?,
)@RequestParam(value = "viewsWeight", required = false) viewsWeight: Double?,
): ResponseEntity<HotStandardDto> {
val hotStandardDto =
articleService.hotsUpdate(
Expand All @@ -88,7 +91,7 @@ class ArticleController(
return ResponseEntity.ok(hotStandardDto)
}

@Operation(summary = "게시글 생성", description = "게시판에 글이 작성되는지 확인.")
@Operation(summary = "게시글 생성", description = "전용 게시판에 게시글 작성")
@ApiResponses(
value = [
ApiResponse(responseCode = "201", description = "게시글 생성"),
Expand All @@ -98,6 +101,7 @@ class ArticleController(
)
@PostMapping
fun create(
@Parameter(hidden = true) @AdminUser user: User,
@RequestBody createArticleRequest: CreateArticleRequest,
): ResponseEntity<CreateArticleResponse> {
val articleDto =
Expand Down Expand Up @@ -194,6 +198,7 @@ class ArticleController(
)
@PatchMapping("/{articleId}")
fun update(
@Parameter(hidden = true) @AdminUser user: User,
@Parameter(
description = "게시글 ID",
example = "1",
Expand Down Expand Up @@ -222,6 +227,7 @@ class ArticleController(
)
@DeleteMapping("/{articleId}")
fun delete(
@Parameter(hidden = true) @AdminUser user: User,
@Parameter(
description = "게시글 ID",
example = "1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.team2server.user

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class AdminUser
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,23 @@ class UserArgumentResolver(
private val userRepository: UserRepository,
) : HandlerMethodArgumentResolver {
override fun supportsParameter(parameter: MethodParameter): Boolean =
parameter.hasParameterAnnotation(LoggedInUser::class.java) && parameter.parameterType == User::class.java
(parameter.hasParameterAnnotation(LoggedInUser::class.java) && parameter.parameterType == User::class.java) ||
(parameter.hasParameterAnnotation(AdminUser::class.java) && parameter.parameterType == User::class.java)

override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?,
): User =
runCatching {
val userId = webRequest.getAttribute("userId", 0) as Long
userRepository.findById(userId)
}.getOrNull()?.orElseThrow { AuthenticateException() } ?: throw AuthenticateException()
): User {
val user =
runCatching {
val userId = webRequest.getAttribute("userId", 0) as Long
userRepository.findById(userId)
}.getOrNull()?.orElseThrow { AuthenticateException() } ?: throw AuthenticateException()
if (parameter.hasParameterAnnotation(AdminUser::class.java) && user.role < 1000) {
throw AdminRequiredException()
}
return user
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ class AuthenticateException :
msg = "Authenticate failed",
)

class AdminRequiredException :
UserException(
errorCode = 0,
httpStatusCode = HttpStatus.FORBIDDEN,
msg = "Admin role required",
)

class ChangePasswordIllegalStateException :
UserException(
errorCode = 0,
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/db/migration/V12__insert_admin_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INSERT INTO users (local_id, password, role) VALUES ('admin', 'admin', 1000);
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.wafflestudio.team2server.article.repository.ArticleRepository
import com.wafflestudio.team2server.article.service.ArticleService
import com.wafflestudio.team2server.helper.DataGenerator
import com.wafflestudio.team2server.helper.QueryCounter
import jakarta.servlet.http.Cookie
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.hasSize
import org.junit.jupiter.api.Assertions.assertTrue
Expand Down Expand Up @@ -65,6 +66,7 @@ class ArticleIntegrationTests
mvc
.perform(
post("/api/v1/articles")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(request)),
).andExpect(status().isCreated)
Expand Down Expand Up @@ -124,6 +126,7 @@ class ArticleIntegrationTests
mvc
.perform(
patch("/api/v1/articles/${article.id}")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(request)),
).andExpect(status().isOk)
Expand Down Expand Up @@ -156,12 +159,16 @@ class ArticleIntegrationTests
val article = dataGenerator.generateArticle()

mvc
.perform(delete("/api/v1/articles/${article.id}"))
.andExpect(status().isNoContent)
.perform(
delete("/api/v1/articles/${article.id}")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin"))),
).andExpect(status().isNoContent)

mvc
.perform(delete("/api/v1/articles/${article.id}"))
.andExpect(status().isNotFound)
.perform(
delete("/api/v1/articles/${article.id}")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin"))),
).andExpect(status().isNotFound)
}

@Test
Expand Down Expand Up @@ -303,6 +310,7 @@ class ArticleIntegrationTests
mvc
.perform(
patch("/api/v1/articles/hots")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
.param("hotScore", "4")
.contentType(MediaType.APPLICATION_JSON),
).andExpect(status().isOk)
Expand Down
2 changes: 2 additions & 0 deletions src/test/kotlin/com/wafflestudio/team2server/ImageTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.wafflestudio.team2server.helper.DataGenerator
import com.wafflestudio.team2server.image.dto.CreateImageResponse
import jakarta.servlet.http.Cookie
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
Expand All @@ -19,6 +20,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPat
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.testcontainers.junit.jupiter.Testcontainers

@Disabled
@SpringBootTest
@ActiveProfiles("test")
@Testcontainers
Expand Down
1 change: 1 addition & 0 deletions src/test/kotlin/com/wafflestudio/team2server/InboxTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class InboxTests
mvc
.perform(
post("/api/v1/articles")
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(request)),
).andExpect(status().isCreated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class DataGenerator(
return user to jwtProvider.createToken(user.id!!)
}

fun generateToken(localId: String): String = jwtProvider.createToken(userRepository.findByLocalId(localId)!!.id!!)

fun generateArticle(
title: String? = null,
content: String? = null,
Expand Down