OauthRegisteredClientServiceImpl.kt

package io.github.lishangbu.avalon.auth.service.impl

import io.github.lishangbu.avalon.auth.entity.OauthRegisteredClient
import io.github.lishangbu.avalon.auth.entity.dto.OauthRegisteredClientSpecification
import io.github.lishangbu.avalon.auth.entity.dto.OauthRegisteredClientView
import io.github.lishangbu.avalon.auth.entity.dto.SaveOauthRegisteredClientInput
import io.github.lishangbu.avalon.auth.entity.dto.UpdateOauthRegisteredClientInput
import io.github.lishangbu.avalon.auth.repository.Oauth2RegisteredClientRepository
import io.github.lishangbu.avalon.auth.service.OauthRegisteredClientService
import io.github.lishangbu.avalon.jimmer.support.readOrNull
import org.babyfish.jimmer.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Instant
import java.util.*

/**
 * OAuth2 注册客户端服务实现
 *
 * 负责 OAuth2 注册客户端的查询与维护
 *
 * @author lishangbu
 * @since 2026/3/19
 */
@Service
class OauthRegisteredClientServiceImpl(
    /** OAuth2 注册客户端仓储 */
    private val oauth2RegisteredClientRepository: Oauth2RegisteredClientRepository,
) : OauthRegisteredClientService {
    /** 按条件分页查询 OAuth2 注册客户端 */
    override fun getPageByCondition(
        specification: OauthRegisteredClientSpecification,
        pageable: Pageable,
    ): Page<OauthRegisteredClientView> = oauth2RegisteredClientRepository.pageViews(specification, pageable)

    /** 按条件查询 OAuth2 注册客户端列表 */
    override fun listByCondition(specification: OauthRegisteredClientSpecification): List<OauthRegisteredClientView> = oauth2RegisteredClientRepository.listViews(specification)

    /** 按 ID 查询 OAuth2 注册客户端 */
    override fun getById(id: String): OauthRegisteredClientView? = oauth2RegisteredClientRepository.loadViewById(id)

    /** 保存 OAuth2 注册客户端 */
    @Transactional(rollbackFor = [Exception::class])
    override fun save(command: SaveOauthRegisteredClientInput): OauthRegisteredClientView {
        val registeredClient = command.toEntity()
        val now = Instant.now()
        val newId = registeredClient.readOrNull { id }?.takeIf { it.isNotBlank() } ?: UUID.randomUUID().toString()
        val saved =
            OauthRegisteredClient {
                id = newId
                clientId = registeredClient.readOrNull { clientId }
                clientIdIssuedAt = registeredClient.readOrNull { clientIdIssuedAt } ?: now
                clientSecret = registeredClient.readOrNull { clientSecret }
                clientSecretExpiresAt = registeredClient.readOrNull { clientSecretExpiresAt }
                clientName = registeredClient.readOrNull { clientName }
                clientAuthenticationMethods = registeredClient.readOrNull { clientAuthenticationMethods }
                authorizationGrantTypes = registeredClient.readOrNull { authorizationGrantTypes }
                redirectUris = registeredClient.readOrNull { redirectUris }
                postLogoutRedirectUris = registeredClient.readOrNull { postLogoutRedirectUris }
                scopes = registeredClient.readOrNull { scopes }
                requireProofKey = registeredClient.readOrNull { requireProofKey }
                requireAuthorizationConsent = registeredClient.readOrNull { requireAuthorizationConsent }
                jwkSetUrl = registeredClient.readOrNull { jwkSetUrl }
                tokenEndpointAuthenticationSigningAlgorithm =
                    registeredClient.readOrNull { tokenEndpointAuthenticationSigningAlgorithm }
                x509CertificateSubjectDn = registeredClient.readOrNull { x509CertificateSubjectDn }
                authorizationCodeTimeToLive = registeredClient.readOrNull { authorizationCodeTimeToLive }
                accessTokenTimeToLive = registeredClient.readOrNull { accessTokenTimeToLive }
                accessTokenFormat = registeredClient.readOrNull { accessTokenFormat }
                deviceCodeTimeToLive = registeredClient.readOrNull { deviceCodeTimeToLive }
                reuseRefreshTokens = registeredClient.readOrNull { reuseRefreshTokens }
                refreshTokenTimeToLive = registeredClient.readOrNull { refreshTokenTimeToLive }
                idTokenSignatureAlgorithm = registeredClient.readOrNull { idTokenSignatureAlgorithm }
                x509CertificateBoundAccessTokens =
                    registeredClient.readOrNull { x509CertificateBoundAccessTokens }
            }
        return oauth2RegisteredClientRepository.save(saved).let(::reloadView)
    }

    /** 更新 OAuth2 注册客户端 */
    @Transactional(rollbackFor = [Exception::class])
    override fun update(command: UpdateOauthRegisteredClientInput): OauthRegisteredClientView = oauth2RegisteredClientRepository.save(command.toEntity()).let(::reloadView)

    /** 按 ID 删除 OAuth2 注册客户端 */
    @Transactional(rollbackFor = [Exception::class])
    override fun removeById(id: String) {
        oauth2RegisteredClientRepository.deleteById(id)
    }

    private fun reloadView(registeredClient: OauthRegisteredClient): OauthRegisteredClientView =
        requireNotNull(oauth2RegisteredClientRepository.loadViewById(registeredClient.id)) {
            "未找到 ID=${registeredClient.id} 对应的 OAuth2 注册客户端"
        }
}