Skip to content

Commit

Permalink
�[feat #37] 애플 회원 탈퇴 API (#39)
Browse files Browse the repository at this point in the history
* chore : OpenFeign 설정

* chore : User authPlatform 필드 추가

* chore : User authPlatform 필드 추가

* chore : 회원 탈퇴 API

* chore : 회원 탈퇴 Use case

* chore : 탈퇴 관련 port 구현

* chore : User 쿼리 수정 및 추가

* chore : 탈퇴 Dto

* chore : 탈퇴 관련 에러코드

* feat : UserInfo 수정에 따른 작업

* feat : 에러로그 추가

* feat : 회원탈퇴 시 컨텐츠 삭제 기능 추가
  • Loading branch information
dlswns2480 authored Jul 29, 2024
1 parent d4c6d7c commit 1f6ccf5
Show file tree
Hide file tree
Showing 29 changed files with 354 additions and 37 deletions.
21 changes: 17 additions & 4 deletions adapters/in-web/src/main/kotlin/com/pokit/auth/AuthController.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.pokit.auth

import com.pokit.auth.config.ErrorOperation
import com.pokit.auth.dto.request.ApiRevokeRequest
import com.pokit.auth.dto.request.toDto
import com.pokit.auth.model.PrincipalUser
import com.pokit.auth.model.toDomain
import com.pokit.auth.port.`in`.AuthUseCase
import com.pokit.common.wrapper.ResponseWrapper.wrapUnit
import com.pokit.token.dto.request.SignInRequest
import com.pokit.user.exception.UserErrorCode
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.ResponseEntity
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.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/v1/auth")
Expand All @@ -22,4 +25,14 @@ class AuthController(
fun signIn(
@RequestBody request: SignInRequest,
) = ResponseEntity.ok(authUseCase.signIn(request))

@PutMapping("/withdraw")
@Operation(summary = "회원 탈퇴 API")
fun withDraw(
@AuthenticationPrincipal user: PrincipalUser,
@RequestBody request: ApiRevokeRequest
): ResponseEntity<Unit> {
return authUseCase.withDraw(user.toDomain(), request.toDto())
.wrapUnit()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.pokit.auth.dto.request

import com.pokit.token.dto.request.RevokeRequest
import com.pokit.token.model.AuthPlatform
import io.swagger.v3.oas.annotations.media.Schema

data class ApiRevokeRequest(
@Schema(description = "플랫폼에서 받은 인가코드")
val authorizationCode: String,
val authPlatform: String
)

internal fun ApiRevokeRequest.toDto() = RevokeRequest(
authorizationCode = this.authorizationCode,
authPlatform = AuthPlatform.of(this.authPlatform)
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.auth.model

import com.pokit.token.model.AuthPlatform
import com.pokit.user.model.Role
import com.pokit.user.model.User
import org.springframework.security.core.GrantedAuthority
Expand All @@ -10,13 +11,15 @@ data class PrincipalUser(
val id: Long,
val email: String,
val role: Role,
val authPlatform: AuthPlatform
) : UserDetails {
companion object {
fun of(user: User) =
PrincipalUser(
id = user.id,
email = user.email,
role = user.role,
authPlatform = user.authPlatform
)
}

Expand All @@ -33,4 +36,4 @@ data class PrincipalUser(
}
}

fun PrincipalUser.toDomain() = User(id = this.id, email = this.email, role = this.role)
fun PrincipalUser.toDomain() = User(id = this.id, email = this.email, role = this.role, authPlatform = this.authPlatform)
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class ContentAdapter(
return SliceImpl(contents, pageable, hasNext)
}

override fun deleteByUserId(userId: Long) {
contentRepository.deleteByUserId(userId)
}

private fun ReadOrNot(
read: Boolean?,
query: JPAQuery<ContentEntity>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.pokit.out.persistence.content.persist

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

Expand All @@ -19,4 +20,12 @@ interface ContentRepository : JpaRepository<ContentEntity, Long> {
): ContentEntity?

fun countByCategoryId(id: Long): Int

@Modifying(clearAutomatically = true)
@Query("""
update ContentEntity c set c.deleted = true
where c.categoryId in
(select ct.id from CategoryEntity ct where ct.userId = :userId)
""")
fun deleteByUserId(@Param("userId") userId: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class UserAdapter(
override fun loadByEmail(email: String) = userRepository.findByEmail(email)
?.run { toDomain() }

override fun loadById(id: Long) = userRepository.findByIdOrNull(id)
override fun loadById(id: Long) = userRepository.findByIdAndDeleted(id, false)
?.run { toDomain() }

override fun register(user: User): User? {
Expand All @@ -32,4 +32,8 @@ class UserAdapter(
}

override fun checkByNickname(nickname: String) = userRepository.existsByNickname(nickname)
override fun delete(user: User) {
userRepository.findByIdOrNull(user.id)
?.delete()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.out.persistence.user.persist

import com.pokit.token.model.AuthPlatform
import com.pokit.user.model.Role
import com.pokit.user.model.User
import jakarta.persistence.*
Expand All @@ -19,14 +20,25 @@ class UserEntity(
val role: Role,

@Column(name = "nickname")
var nickname: String = email
var nickname: String = email,

@Column(name = "auth_platform")
val authPlatform: AuthPlatform,

@Column(name = "deleted")
var deleted: Boolean = false
) {
fun delete() {
this.deleted = true
}

companion object {
fun of(user: User) =
UserEntity(
email = user.email,
role = user.role,
nickname = user.nickName
nickname = user.nickName,
authPlatform = user.authPlatform
)
}
}
Expand All @@ -35,7 +47,8 @@ fun UserEntity.toDomain() = User(
id = this.id,
email = this.email,
role = this.role,
nickName = this.nickname
nickName = this.nickname,
authPlatform = this.authPlatform
)

fun UserEntity.registerInfo(user: User) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ interface UserRepository : JpaRepository<UserEntity, Long> {
fun findByEmail(email: String): UserEntity?

fun existsByNickname(nickname: String): Boolean

fun findByIdAndDeleted(id: Long, deleted: Boolean): UserEntity?
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pokit.user

import com.pokit.token.model.AuthPlatform
import com.pokit.user.dto.UserInfo
import com.pokit.user.dto.request.SignUpRequest
import com.pokit.user.model.InterestType
Expand All @@ -8,11 +9,11 @@ import com.pokit.user.model.User

class UserFixture {
companion object {
fun getUser() = User(1L, "[email protected]", Role.USER)
fun getUser() = User(1L, "[email protected]", Role.USER, authPlatform = AuthPlatform.GOOGLE)

fun getUserInfo() = UserInfo("[email protected]")
fun getUserInfo() = UserInfo("[email protected]", AuthPlatform.GOOGLE)

fun getInvalidUser() = User(2L, "[email protected]", Role.USER)
fun getInvalidUser() = User(2L, "[email protected]", Role.USER, authPlatform = AuthPlatform.GOOGLE)

fun getSignUpRequest() = SignUpRequest("인주니", listOf(InterestType.SPORTS))
}
Expand Down
12 changes: 12 additions & 0 deletions adapters/out-web/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ plugins {
kotlin("plugin.spring") version "1.9.24"
}

extra["springCloudVersion"] = "2023.0.3"

dependencies {
implementation(project(":application"))
implementation(project(":domain"))
Expand All @@ -16,9 +18,19 @@ dependencies {
implementation("io.jsonwebtoken:jjwt-api:0.12.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.5")
implementation("org.bouncycastle:bcpkix-jdk15on:1.69")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3")

}

tasks {
withType<Jar> { enabled = true }
withType<BootJar> { enabled = false }
}

dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.pokit.auth.common.config

import feign.Logger
import feign.RequestInterceptor
import feign.RequestTemplate
import feign.form.ContentProcessor.CONTENT_TYPE_HEADER
import org.springframework.cloud.openfeign.EnableFeignClients
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType

@Configuration
@EnableFeignClients(basePackages = ["com.pokit"])
class OpenFeignConfig {
@Bean
fun feignLoggerLevel() = Logger.Level.FULL

@Bean
fun requestInterceptor(): RequestInterceptor {
return RequestInterceptor { template: RequestTemplate ->
template.header(
CONTENT_TYPE_HEADER,
MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pokit.auth.common.dto

import com.fasterxml.jackson.annotation.JsonProperty

data class AppleRevokeRequest(
@JsonProperty("client_id")
val clientId: String,
@JsonProperty("client_secret")
val clientSecret: String,
val token: String,
@JsonProperty("token_type_hint")
val tokenTypeHint: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.pokit.auth.common.dto

import com.fasterxml.jackson.annotation.JsonProperty

data class AppleTokenResponse(
@JsonProperty("access_token")
val accessToken: String,
@JsonProperty("token_type")
val tokenType: String,
@JsonProperty("expires_in")
val expiresIn: Int,
@JsonProperty("refresh_token")
val refreshToken: String?,
@JsonProperty("id_token")
val idToken: String,
val error: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.pokit.auth.common.property

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "apple")
class AppleProperty(
val clientId: String,
val teamId: String,
val keyId: String,
val privateKey: String,
val tokenUrl: String,
val audience: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.pokit.auth.common.support

import com.pokit.auth.common.config.OpenFeignConfig
import com.pokit.auth.common.dto.ApplePublicKeys
import com.pokit.auth.common.dto.AppleRevokeRequest
import com.pokit.auth.common.dto.AppleTokenResponse
import feign.Response
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam


@FeignClient(
name = "appleClient",
url = "https://appleid.apple.com/auth",
configuration = [OpenFeignConfig::class]
)
interface AppleFeignClient {
@GetMapping("/keys")
fun getApplePublicKeys(): ApplePublicKeys

@PostMapping("/revoke", produces = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun revoke(
@RequestBody appleRevokeRequest: AppleRevokeRequest
): Response

@PostMapping("/token", produces = [MediaType.APPLICATION_FORM_URLENCODED_VALUE])
fun getToken(
@RequestParam("client_id") clientId: String,
@RequestParam("client_secret") clientSecret: String,
@RequestParam("code") code: String,
@RequestParam("grant_type") grantType: String,
): AppleTokenResponse?

}
Loading

0 comments on commit 1f6ccf5

Please sign in to comment.