Skip to content
Merged

Dev #87

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
17 changes: 17 additions & 0 deletions api/src/docs/asciidoc/Feed-API.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,20 @@ include::{snippets}/feed-controller-test/add-text-feed/http-response.adoc[]

=== 응답 필드 설명(Response Fields)
include::{snippets}/feed-controller-test/add-text-feed/response-fields.adoc[]

== 7. 피드 수정 04/12 수정

=== 요청(Request)
include::{snippets}/feed-controller-test/update-text-feed/http-request.adoc[]

=== 요청헤더(Request Headers)
include::{snippets}/feed-controller-test/update-text-feed/request-headers.adoc[]

=== 요청 필드 설명(Request Fields)
include::{snippets}/feed-controller-test/update-text-feed/request-fields.adoc[]

=== 응답(Response)
include::{snippets}/feed-controller-test/update-text-feed/http-response.adoc[]

=== 응답 필드 설명(Response Fields)
include::{snippets}/feed-controller-test/update-text-feed/response-fields.adoc[]
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.chewing.v1.controller.auth

import org.chewing.v1.dto.request.auth.LoginRequest
import org.chewing.v1.dto.request.auth.LogoutRequest
import org.chewing.v1.dto.request.auth.SignUpRequest
import org.chewing.v1.dto.request.auth.VerificationRequest
import org.chewing.v1.dto.request.auth.VerifyOnlyRequest
Expand Down Expand Up @@ -106,9 +107,13 @@ class AuthController(
@DeleteMapping("/logout")
fun logout(
@RequestHeader("Authorization") refreshToken: String,
@RequestBody request: LogoutRequest,
): ResponseEntity<HttpResponse<SuccessOnlyResponse>> {
jwtTokenUtil.validateRefreshToken(refreshToken)
authService.logout(refreshToken)
accountFacade.logout(
request.toDevice(),
refreshToken,
)
return ResponseHelper.successOnly()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,13 @@ class FeedController(
val feedId = feedService.makeText(userId, request.toFriendIds(), request.toContent(), request.toType())
return ResponseHelper.successCreate(FeedIdResponse.of(feedId))
}

@PutMapping("/text")
fun updateTextFeed(
@CurrentUser userId: UserId,
@RequestBody request: FeedRequest.UpdateText,
): SuccessResponseEntity<SuccessOnlyResponse> {
feedService.changeText(userId, request.toFeedId(), request.toContent())
return ResponseHelper.successOnly()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.chewing.v1.dto.request.auth

import org.chewing.v1.model.notification.PushInfo
import kotlin.text.uppercase

data class LogoutRequest(
val deviceId: String,
val provider: String,
) {
fun toDevice(): PushInfo.Device {
return PushInfo.Device.of(deviceId, PushInfo.Provider.valueOf(provider.uppercase()))
}
}
11 changes: 11 additions & 0 deletions api/src/main/kotlin/org/chewing/v1/dto/request/feed/FeedRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,15 @@ class FeedRequest {
return content
}
}
data class UpdateText(
val feedId: String,
val content: String,
) {
fun toFeedId(): FeedId {
return FeedId.of(feedId)
}
fun toContent(): String {
return content
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ class JwtTokenUtil(
@Value("\${jwt.access-expiration}") private val accessExpiration: Long,
@Value("\${jwt.refresh-expiration}") private val refreshExpiration: Long,
) {
private val secretKey: SecretKey
get() = Keys.hmacShaKeyFor(secretKeyString.toByteArray())
private val secretKey: SecretKey = Keys.hmacShaKeyFor(secretKeyString.toByteArray())

fun createJwtToken(userId: UserId): JwtToken {
val accessToken = createAccessToken(userId)
Expand Down Expand Up @@ -79,7 +78,7 @@ class JwtTokenUtil(

// 토큰에서 사용자 ID 추출
fun getUserIdFromToken(token: String): UserId {
val claims = getClaimsFromToken(token)
val claims = getClaimsFromToken(cleanedToken(token))
return UserId.of(claims.subject)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import io.mockk.verify
import org.chewing.v1.RestDocsTest
import org.chewing.v1.RestDocsUtils.requestAccessTokenFields
import org.chewing.v1.RestDocsUtils.requestPreprocessor
import org.chewing.v1.RestDocsUtils.requestRefreshTokenFields
import org.chewing.v1.RestDocsUtils.responseErrorFields
import org.chewing.v1.RestDocsUtils.responsePreprocessor
import org.chewing.v1.RestDocsUtils.responseSuccessFields
import org.chewing.v1.TestDataFactory.createJwtToken
import org.chewing.v1.TestDataFactory.createUserId
import org.chewing.v1.controller.auth.AuthController
import org.chewing.v1.dto.request.auth.LoginRequest
import org.chewing.v1.dto.request.auth.LogoutRequest
import org.chewing.v1.dto.request.auth.SignUpRequest
import org.chewing.v1.dto.request.auth.VerificationRequest
import org.chewing.v1.dto.request.auth.VerifyOnlyRequest
Expand Down Expand Up @@ -740,11 +740,17 @@ class AuthControllerTest : RestDocsTest() {
@Test
@DisplayName("로그아웃")
fun logout() {
every { authService.logout(any()) } just Runs
val requestBody = LogoutRequest(
deviceId = "testDeviceId",
provider = "ios",
)
every { accountFacade.logout(any(), any()) } just Runs
every { jwtTokenUtil.validateRefreshToken(any()) } just Runs

given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.header("Authorization", "Bearer refreshToken")
.body(requestBody)
.delete("/api/auth/logout")
.then()
.assertCommonSuccessResponse()
Expand All @@ -754,7 +760,13 @@ class AuthControllerTest : RestDocsTest() {
"{class-name}/{method-name}",
requestPreprocessor(),
responsePreprocessor(),
requestRefreshTokenFields(),
requestHeaders(
headerWithName("Authorization").description("리프레시 토큰"),
),
requestFields(
fieldWithPath("deviceId").description("디바이스 아이디(디바이스 식별을 위한 정보)"),
fieldWithPath("provider").description("플랫폼(ios, android)"),
),
responseSuccessFields(),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,34 @@ class FeedControllerTest : RestDocsTest() {
),
)
}

@Test
fun updateTextFeed() {
val requestBody = FeedRequest.UpdateText(
feedId = "testFeedId",
content = "testContent",
)
every { feedService.changeText(any(), any(), any()) } just Runs

given()
.setupAuthenticatedJsonRequest()
.body(requestBody)
.put("/api/feed/text")
.then()
.statusCode(HttpStatus.OK.value())
.body("status", equalTo(200))
.apply(
document(
"{class-name}/{method-name}",
requestPreprocessor(),
responsePreprocessor(),
requestAccessTokenFields(),
requestFields(
fieldWithPath("feedId").description("피드 아이디"),
fieldWithPath("content").description("피드 내용"),
),
responseSuccessFields(),
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,17 @@ class SpringSecurityTest2 : IntegrationTest() {
fun logout() {
val userId = TestDataFactory.createUserId()
val jwtToken = jwtTokenUtil.createRefreshToken(userId)
every { authService.logout(any()) } just Runs
val deviceId = "testDeviceId"
val provider = "IOS"
val requestBody = mapOf(
"deviceId" to deviceId,
"provider" to provider,
)
every { accountFacade.logout(any(), any()) } just Runs
mockMvc.perform(
MockMvcRequestBuilders.delete("/api/auth/logout")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestBody))
.header("Authorization", "Bearer ${jwtToken.token}"),
).andExpect(status().isOk)
}
Expand Down
33 changes: 33 additions & 0 deletions api/src/test/kotlin/org/chewing/v1/util/JwtTokenProviderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,37 @@ class JwtTokenProviderTest {
val cleaned = jwtTokenProvider.cleanedToken(token)
assert(cleaned == "someToken")
}

@Test
@DisplayName("Refresh 토큰으로 새로운 JwtToken을 재발급할 수 있어야 한다")
fun `test refresh`() {
val userId = TestDataFactory.createUserId()
val jwtToken = jwtTokenProvider.createJwtToken(userId)
val refreshToken = jwtToken.refreshToken.token

val (newToken, extractedUserId) = jwtTokenProvider.refresh("Bearer $refreshToken")

assert(newToken.accessToken.isNotBlank())
assert(newToken.refreshToken.token.isNotBlank())
assert(extractedUserId == userId)
}

@Test
@DisplayName("정상적인 refresh token에 대해 validateRefreshToken이 성공하는지 테스트")
fun `test validateRefreshToken with valid token`() {
val userId = TestDataFactory.createUserId()
val refreshToken = jwtTokenProvider.createRefreshToken(userId).token
jwtTokenProvider.validateRefreshToken("Bearer $refreshToken")
}

@Test
@DisplayName("Bearer prefix가 있는 토큰에서도 사용자 ID를 추출할 수 있어야 한다")
fun `test getUserIdFromToken with Bearer token`() {
val userId = TestDataFactory.createUserId()
val token = jwtTokenProvider.createAccessToken(userId)
val bearerToken = "Bearer $token"

val extractedUserId = jwtTokenProvider.getUserIdFromToken(bearerToken)
assert(extractedUserId == userId)
}
}
8 changes: 8 additions & 0 deletions domain/src/main/kotlin/org/chewing/v1/facade/AccountFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,12 @@ class AccountFacade(
userService.createDeviceInfo(user.info, device, appToken)
return user.info.userId
}

fun logout(
device: PushInfo.Device,
token: String,
) {
authService.logout(token)
userService.removePushInfo(device)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.chewing.v1.implementation.feed

import org.chewing.v1.model.feed.FeedId
import org.chewing.v1.repository.feed.FeedRepository
import org.springframework.stereotype.Component

@Component
class FeedUpdater(
private val feedRepository: FeedRepository,
) {
fun update(feedId: FeedId, content: String) {
feedRepository.update(feedId, content)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class FeedValidator(
}
}

fun isFeedOwner(feedId: FeedId, userId: UserId) {
if (!feedRepository.isOwner(feedId, userId)) {
throw ConflictException(ErrorCode.FEED_IS_NOT_OWNED)
}
}

fun isFeedVisible(feedId: FeedId, userId: UserId) {
if (!feedVisibilityRepository.isVisible(feedId, userId)) {
throw ConflictException(ErrorCode.FEED_IS_NOT_VISIBLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface FeedRepository {
fun removes(feedIds: List<FeedId>)
fun removesOwned(userId: UserId)
fun append(userId: UserId, content: String, type: FeedType): FeedId
fun update(feedId: FeedId, content: String)
fun isOwners(feedIds: List<FeedId>, userId: UserId): Boolean
fun isOwner(feedId: FeedId, userId: UserId): Boolean
fun readsOneDay(targetUserIds: List<UserId>): List<FeedInfo>
}
11 changes: 11 additions & 0 deletions domain/src/main/kotlin/org/chewing/v1/service/feed/FeedService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.chewing.v1.implementation.feed.FeedAppender
import org.chewing.v1.implementation.feed.FeedEnricher
import org.chewing.v1.implementation.feed.FeedReader
import org.chewing.v1.implementation.feed.FeedRemover
import org.chewing.v1.implementation.feed.FeedUpdater
import org.chewing.v1.implementation.feed.FeedValidator
import org.chewing.v1.implementation.media.FileHandler
import org.chewing.v1.model.feed.*
Expand All @@ -20,6 +21,7 @@ class FeedService(
private val fileHandler: FileHandler,
private val feedEnricher: FeedEnricher,
private val feedRemover: FeedRemover,
private val feedUpdater: FeedUpdater,
) {
fun getFeed(feedId: FeedId, userId: UserId): Feed {
feedValidator.isFeedVisible(feedId, userId)
Expand Down Expand Up @@ -73,4 +75,13 @@ class FeedService(
feedAppender.appendVisibility(feedId, targetUserIds)
return feedId
}

fun changeText(
userId: UserId,
feedId: FeedId,
content: String,
) {
feedValidator.isFeedOwner(feedId, userId)
feedUpdater.update(feedId, content)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ class UserService(
return userReader.readPushToken(userId, deviceId)
}

fun removePushInfo(
device: PushInfo.Device,
) {
userRemover.removePushToken(device)
}

fun updatePushNotification(
userId: UserId,
deviceId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.chewing.v1.implementation.feed.FeedAppender
import org.chewing.v1.implementation.feed.FeedEnricher
import org.chewing.v1.implementation.feed.FeedReader
import org.chewing.v1.implementation.feed.FeedRemover
import org.chewing.v1.implementation.feed.FeedUpdater
import org.chewing.v1.implementation.feed.FeedValidator
import org.chewing.v1.implementation.media.FileHandler
import org.chewing.v1.model.feed.FeedType
Expand All @@ -36,8 +37,9 @@ class FeedServiceTest {
private val feedValidator: FeedValidator = FeedValidator(feedRepository, feedVisibilityRepository)
private val feedEnricher: FeedEnricher = FeedEnricher()
private val feedRemover: FeedRemover = FeedRemover(feedRepository, feedDetailRepository, feedVisibilityRepository)
private val feedUpdater: FeedUpdater = FeedUpdater(feedRepository)
private val feedService: FeedService =
FeedService(feedReader, feedAppender, feedValidator, fileHandler, feedEnricher, feedRemover)
FeedService(feedReader, feedAppender, feedValidator, fileHandler, feedEnricher, feedRemover, feedUpdater)

@Test
fun `피드를 가져온다`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import java.util.*
internal class FeedJpaEntity(
@Id
private val feedId: String = UUID.randomUUID().toString(),
private val content: String,
private var content: String,
private val userId: String,
@Enumerated(EnumType.STRING)
private val type: FeedType,
Expand Down Expand Up @@ -59,6 +59,11 @@ internal class FeedJpaEntity(
type = type,
)
}

fun updateContent(content: String) {
this.content = content
}

fun delete() {
status = FeedStatus.DELETED
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal interface FeedJpaRepository : JpaRepository<FeedJpaEntity, String> {
fun findAllByUserId(userId: String): List<FeedJpaEntity>
fun findAllByUserIdAndStatus(userId: String, status: FeedStatus, sort: Sort): List<FeedJpaEntity>
fun existsByFeedIdInAndUserId(feedIds: List<String>, userId: String): Boolean
fun existsByFeedIdAndUserId(feedId: String, userId: String): Boolean
fun findByFeedIdIn(feedIds: List<String>): List<FeedJpaEntity>
fun findByFeedIdAndStatus(feedId: String, status: FeedStatus): FeedJpaEntity?
fun findAllByUserIdInAndCreatedAtAfter(userIds: List<String>, createdAt: LocalDateTime, sort: Sort): List<FeedJpaEntity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.chewing.v1.model.notification.PushInfo
import org.springframework.data.jpa.repository.JpaRepository

internal interface PushNotificationJpaRepository : JpaRepository<PushNotificationJpaEntity, String> {
fun deleteByDeviceIdAndProvider(deviceId: String, deviceProvider: PushInfo.Provider)
fun deleteAllByDeviceIdAndProvider(deviceId: String, deviceProvider: PushInfo.Provider)
fun findAllByUserId(userId: String): List<PushNotificationJpaEntity>
fun findAllByUserIdAndChatStatus(userId: String, chatStatus: NotificationStatus): List<PushNotificationJpaEntity>
fun findAllByUserIdAndScheduleStatus(userId: String, scheduleStatus: NotificationStatus): List<PushNotificationJpaEntity>
Expand Down
Loading