Skip to content

Commit 1c5e835

Browse files
authored
✨ admin role (#27)
1 parent 436f1dd commit 1c5e835

File tree

10 files changed

+62
-12
lines changed

10 files changed

+62
-12
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@
1919

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

22+
## 사용자 권한
23+
24+
- 사용자의 권한은 users 테이블의 role INT 필드에 저장되며, 기본값은 0입니다
25+
- 관리자 권한은 1000으로 설정됩니다
26+
27+
## Admin
28+
29+
- db 초기화 시 id: admin, pw: admin, role: 1000 인 사용자가 생성됩니다
30+
- 테스트는 이 상태를 기준으로 작성됩니다
31+
- 배포 환경에서는 배포 즉시 비밀번호를 변경해야 합니다
32+
2233
## Google OAuth2
2334

2435
- `/oauth2/authorization/google?redirect_uri=` 로 이동하면 구글 로그인 과정이 진행됩니다.

src/main/kotlin/com/wafflestudio/team2server/article/controller/ArticleController.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import com.wafflestudio.team2server.article.dto.response.ArticlePagingResponse
77
import com.wafflestudio.team2server.article.dto.response.CreateArticleResponse
88
import com.wafflestudio.team2server.article.service.ArticleService
99
import com.wafflestudio.team2server.hotstandard.dto.core.HotStandardDto
10+
import com.wafflestudio.team2server.user.AdminUser
11+
import com.wafflestudio.team2server.user.model.User
1012
import io.swagger.v3.oas.annotations.Operation
1113
import io.swagger.v3.oas.annotations.Parameter
1214
import io.swagger.v3.oas.annotations.responses.ApiResponse
@@ -71,14 +73,15 @@ class ArticleController(
7173
)
7274
@PatchMapping("/hots")
7375
fun hotsUpdate(
76+
@Parameter(hidden = true) @AdminUser user: User,
7477
@Parameter(
7578
description = "총 점수 기준",
7679
example = "10",
7780
)@RequestParam(value = "hotScore", required = false) hotScore: Long?,
7881
@Parameter(
7982
description = "조회수 가중치",
8083
example = "1.0",
81-
)@RequestParam(value = "viewsWeight", required = false) viewsWeight: kotlin.Double?,
84+
)@RequestParam(value = "viewsWeight", required = false) viewsWeight: Double?,
8285
): ResponseEntity<HotStandardDto> {
8386
val hotStandardDto =
8487
articleService.hotsUpdate(
@@ -88,7 +91,7 @@ class ArticleController(
8891
return ResponseEntity.ok(hotStandardDto)
8992
}
9093

91-
@Operation(summary = "게시글 생성", description = "게시판에 글이 작성되는지 확인.")
94+
@Operation(summary = "게시글 생성", description = "전용 게시판에 게시글 작성")
9295
@ApiResponses(
9396
value = [
9497
ApiResponse(responseCode = "201", description = "게시글 생성"),
@@ -98,6 +101,7 @@ class ArticleController(
98101
)
99102
@PostMapping
100103
fun create(
104+
@Parameter(hidden = true) @AdminUser user: User,
101105
@RequestBody createArticleRequest: CreateArticleRequest,
102106
): ResponseEntity<CreateArticleResponse> {
103107
val articleDto =
@@ -194,6 +198,7 @@ class ArticleController(
194198
)
195199
@PatchMapping("/{articleId}")
196200
fun update(
201+
@Parameter(hidden = true) @AdminUser user: User,
197202
@Parameter(
198203
description = "게시글 ID",
199204
example = "1",
@@ -222,6 +227,7 @@ class ArticleController(
222227
)
223228
@DeleteMapping("/{articleId}")
224229
fun delete(
230+
@Parameter(hidden = true) @AdminUser user: User,
225231
@Parameter(
226232
description = "게시글 ID",
227233
example = "1",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.wafflestudio.team2server.user
2+
3+
@Target(AnnotationTarget.VALUE_PARAMETER)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
annotation class AdminUser

src/main/kotlin/com/wafflestudio/team2server/user/UserArgumentResolver.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@ class UserArgumentResolver(
1414
private val userRepository: UserRepository,
1515
) : HandlerMethodArgumentResolver {
1616
override fun supportsParameter(parameter: MethodParameter): Boolean =
17-
parameter.hasParameterAnnotation(LoggedInUser::class.java) && parameter.parameterType == User::class.java
17+
(parameter.hasParameterAnnotation(LoggedInUser::class.java) && parameter.parameterType == User::class.java) ||
18+
(parameter.hasParameterAnnotation(AdminUser::class.java) && parameter.parameterType == User::class.java)
1819

1920
override fun resolveArgument(
2021
parameter: MethodParameter,
2122
mavContainer: ModelAndViewContainer?,
2223
webRequest: NativeWebRequest,
2324
binderFactory: WebDataBinderFactory?,
24-
): User =
25-
runCatching {
26-
val userId = webRequest.getAttribute("userId", 0) as Long
27-
userRepository.findById(userId)
28-
}.getOrNull()?.orElseThrow { AuthenticateException() } ?: throw AuthenticateException()
25+
): User {
26+
val user =
27+
runCatching {
28+
val userId = webRequest.getAttribute("userId", 0) as Long
29+
userRepository.findById(userId)
30+
}.getOrNull()?.orElseThrow { AuthenticateException() } ?: throw AuthenticateException()
31+
if (parameter.hasParameterAnnotation(AdminUser::class.java) && user.role < 1000) {
32+
throw AdminRequiredException()
33+
}
34+
return user
35+
}
2936
}

src/main/kotlin/com/wafflestudio/team2server/user/UserException.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ class AuthenticateException :
3939
msg = "Authenticate failed",
4040
)
4141

42+
class AdminRequiredException :
43+
UserException(
44+
errorCode = 0,
45+
httpStatusCode = HttpStatus.FORBIDDEN,
46+
msg = "Admin role required",
47+
)
48+
4249
class ChangePasswordIllegalStateException :
4350
UserException(
4451
errorCode = 0,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
INSERT INTO users (local_id, password, role) VALUES ('admin', 'admin', 1000);

src/test/kotlin/com/wafflestudio/team2server/ArticleIntegrationTests.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.wafflestudio.team2server.article.repository.ArticleRepository
1010
import com.wafflestudio.team2server.article.service.ArticleService
1111
import com.wafflestudio.team2server.helper.DataGenerator
1212
import com.wafflestudio.team2server.helper.QueryCounter
13+
import jakarta.servlet.http.Cookie
1314
import org.hamcrest.Matchers.containsString
1415
import org.hamcrest.Matchers.hasSize
1516
import org.junit.jupiter.api.Assertions.assertTrue
@@ -65,6 +66,7 @@ class ArticleIntegrationTests
6566
mvc
6667
.perform(
6768
post("/api/v1/articles")
69+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
6870
.contentType(MediaType.APPLICATION_JSON)
6971
.content(mapper.writeValueAsString(request)),
7072
).andExpect(status().isCreated)
@@ -124,6 +126,7 @@ class ArticleIntegrationTests
124126
mvc
125127
.perform(
126128
patch("/api/v1/articles/${article.id}")
129+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
127130
.contentType(MediaType.APPLICATION_JSON)
128131
.content(mapper.writeValueAsString(request)),
129132
).andExpect(status().isOk)
@@ -156,12 +159,16 @@ class ArticleIntegrationTests
156159
val article = dataGenerator.generateArticle()
157160

158161
mvc
159-
.perform(delete("/api/v1/articles/${article.id}"))
160-
.andExpect(status().isNoContent)
162+
.perform(
163+
delete("/api/v1/articles/${article.id}")
164+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin"))),
165+
).andExpect(status().isNoContent)
161166

162167
mvc
163-
.perform(delete("/api/v1/articles/${article.id}"))
164-
.andExpect(status().isNotFound)
168+
.perform(
169+
delete("/api/v1/articles/${article.id}")
170+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin"))),
171+
).andExpect(status().isNotFound)
165172
}
166173

167174
@Test
@@ -303,6 +310,7 @@ class ArticleIntegrationTests
303310
mvc
304311
.perform(
305312
patch("/api/v1/articles/hots")
313+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
306314
.param("hotScore", "4")
307315
.contentType(MediaType.APPLICATION_JSON),
308316
).andExpect(status().isOk)

src/test/kotlin/com/wafflestudio/team2server/ImageTests.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
44
import com.wafflestudio.team2server.helper.DataGenerator
55
import com.wafflestudio.team2server.image.dto.CreateImageResponse
66
import jakarta.servlet.http.Cookie
7+
import org.junit.jupiter.api.Disabled
78
import org.junit.jupiter.api.Test
89
import org.springframework.beans.factory.annotation.Autowired
910
import org.springframework.boot.test.context.SpringBootTest
@@ -19,6 +20,7 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPat
1920
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
2021
import org.testcontainers.junit.jupiter.Testcontainers
2122

23+
@Disabled
2224
@SpringBootTest
2325
@ActiveProfiles("test")
2426
@Testcontainers

src/test/kotlin/com/wafflestudio/team2server/InboxTests.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class InboxTests
161161
mvc
162162
.perform(
163163
post("/api/v1/articles")
164+
.cookie(Cookie("AUTH-TOKEN", dataGenerator.generateToken("admin")))
164165
.contentType(MediaType.APPLICATION_JSON)
165166
.content(mapper.writeValueAsString(request)),
166167
).andExpect(status().isCreated)

src/test/kotlin/com/wafflestudio/team2server/helper/DataGenerator.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class DataGenerator(
4141
return user to jwtProvider.createToken(user.id!!)
4242
}
4343

44+
fun generateToken(localId: String): String = jwtProvider.createToken(userRepository.findByLocalId(localId)!!.id!!)
45+
4446
fun generateArticle(
4547
title: String? = null,
4648
content: String? = null,

0 commit comments

Comments
 (0)