Skip to content

Commit

Permalink
feat: app 강제 업데이트 판별 기능 추가 (MZ-309) (#84)
Browse files Browse the repository at this point in the history
* feat: 버전 체크 기능 추가 및 applicationMetadata 엔티티 변경

* feat: 버전 체크 기능 테스트 추가
  • Loading branch information
wjdtkdgns authored Aug 26, 2024
1 parent 2165d25 commit d0449f3
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,89 @@
package com.oksusu.susu.api.metadata.application

import com.oksusu.susu.api.metadata.model.response.ApplicationVersionMetadataResponse
import com.oksusu.susu.api.metadata.model.ApplicationMetadataModel
import com.oksusu.susu.api.metadata.model.DeviceOS
import com.oksusu.susu.api.metadata.model.response.CheckApplicationVersionResposne
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.common.exception.ErrorCode
import com.oksusu.susu.common.exception.NotFoundException
import com.oksusu.susu.common.extension.resolveCancellation
import com.oksusu.susu.common.extension.withMDCContext
import com.oksusu.susu.domain.metadata.domain.ApplicationMetadata
import com.oksusu.susu.domain.metadata.infrastructure.ApplicationMetadataRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service

@Service
class ApplicationMetadataService(
private val applicationMetadataRepository: ApplicationMetadataRepository,
private val coroutineExceptionHandler: ErrorPublishingCoroutineExceptionHandler,
) {
suspend fun getApplicationVersionMetadata(): ApplicationVersionMetadataResponse {
val applicationMetadata = findTop1ByIsActiveOrderByCreatedAtDescOrThrow()
return ApplicationVersionMetadataResponse.from(applicationMetadata)
private val logger = KotlinLogging.logger { }
private var metadata: ApplicationMetadataModel = ApplicationMetadataModel(
id = 1L,
iosMinSupportVersion = "0.0.0",
aosMinSupportVersion = "0.0.0",
isActive = true
)

@Scheduled(
fixedRate = 1000 * 60 * 3,
initialDelayString = "\${oksusu.scheduled-tasks.refresh-application-metadata.initial-delay:0}"
)
fun refreshApplicationMetadata() {
CoroutineScope(Dispatchers.IO + Job() + coroutineExceptionHandler.handler).launch {
logger.info { "start refresh applicationMetadata" }

metadata = runCatching {
findTop1ByIsActiveOrderByCreatedAtDescOrThrow()
.run { ApplicationMetadataModel.from(this) }
}.onFailure { e ->
logger.resolveCancellation("refreshCategories", e)
}.getOrDefault(metadata)

logger.info { "finish refresh applicationMetadata" }
}
}

fun getMetadata(): ApplicationMetadataModel {
return metadata
}

fun checkApplicationVersion(deviceOS: DeviceOS, version: String): CheckApplicationVersionResposne {
val needForceUpdate = when (deviceOS) {
DeviceOS.AOS -> compareVersion(version, metadata.aosMinSupportVersion)
DeviceOS.IOS -> compareVersion(version, metadata.iosMinSupportVersion)
}.run { this < 0 }

return CheckApplicationVersionResposne(
needForceUpdate = needForceUpdate
)
}

/**
* version1 < version2 : -1
*
* version1 = version2 : 0
*
* version1 > version2 : 1
*/
private fun compareVersion(version1: String, version2: String): Int {
val parsedVersion1 = version1.split(".")
val parsedVersion2 = version2.split(".")

for (i in 0..2) {
if (parsedVersion1[i] > parsedVersion2[i]) {
return 1
} else if (parsedVersion1[i] < parsedVersion2[i]) {
return -1
}
}
return 0
}

suspend fun findTop1ByIsActiveOrderByCreatedAtDescOrThrow(): ApplicationMetadata {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.oksusu.susu.api.metadata.model

import com.oksusu.susu.domain.metadata.domain.ApplicationMetadata

class ApplicationMetadataModel(
val id: Long,

/** ios 최소 지원 어플리케이션 버전 */
val iosMinSupportVersion: String,

/** aos 최소 지원 어플리케이션 버전 */
val aosMinSupportVersion: String,

/** 활성화 여부 / 활성화 : 1, 비활성화 : 0 */
val isActive: Boolean,
) {
companion object {
fun from(metadata: ApplicationMetadata): ApplicationMetadataModel {
return ApplicationMetadataModel(
id = metadata.id,
iosMinSupportVersion = metadata.iosMinSupportVersion,
aosMinSupportVersion = metadata.aosMinSupportVersion,
isActive = metadata.isActive
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.oksusu.susu.api.metadata.model

enum class DeviceOS {
AOS,
IOS,
;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.oksusu.susu.api.metadata.model.response

class CheckApplicationVersionResposne(
val needForceUpdate: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.oksusu.susu.api.metadata.presentation
import com.oksusu.susu.api.config.web.SwaggerTag
import com.oksusu.susu.api.extension.wrapOk
import com.oksusu.susu.api.metadata.application.ApplicationMetadataService
import com.oksusu.susu.api.metadata.model.DeviceOS
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@Tag(name = SwaggerTag.APPLICATION_METADATA_SWAGGER_TAG, description = "어플리케이션 설정 정보 API")
Expand All @@ -16,7 +18,10 @@ import org.springframework.web.bind.annotation.RestController
class ApplicationMetadataResource(
private val applicationMetadataService: ApplicationMetadataService,
) {
@Operation(summary = "버전 설정 정보 조회")
@Operation(summary = "버전 설정 정보 체크")
@GetMapping("/version")
suspend fun getApplicationVersionMetadata() = applicationMetadataService.getApplicationVersionMetadata().wrapOk()
suspend fun checkApplicationVersion(
@RequestParam deviceOS: DeviceOS,
@RequestParam version: String,
) = applicationMetadataService.checkApplicationVersion(deviceOS, version).wrapOk()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.oksusu.susu.api.metadata.application

import com.oksusu.susu.api.metadata.model.DeviceOS
import com.oksusu.susu.client.common.coroutine.ErrorPublishingCoroutineExceptionHandler
import com.oksusu.susu.domain.metadata.domain.ApplicationMetadata
import com.oksusu.susu.domain.metadata.infrastructure.ApplicationMetadataRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.delay

class ApplicationMetadataServiceTest : DescribeSpec({
val logger = KotlinLogging.logger { }

val mockApplicationMetadataRepository = mockk<ApplicationMetadataRepository>()
val mockCoroutineExceptionHandler = mockk<ErrorPublishingCoroutineExceptionHandler>()

every { mockApplicationMetadataRepository.findTop1ByIsActiveOrderByCreatedAtDesc(any()) } returns ApplicationMetadata(
id = 1L,
iosMinSupportVersion = "1.1.1",
aosMinSupportVersion = "1.1.1",
isActive = true
)
every { mockCoroutineExceptionHandler.handler } returns CoroutineExceptionHandler { _, _ -> }

val service = ApplicationMetadataService(mockApplicationMetadataRepository, mockCoroutineExceptionHandler)

beforeSpec {
service.refreshApplicationMetadata()

delay(100)
}

describe("checkApplicationVersion") {
context("ios 버전을 체크했을 때,") {
it("버전이 최소 지원 버전 미만이면 강제 업데이트를 해야한다.") {
service.checkApplicationVersion(DeviceOS.IOS, "0.0.0").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.IOS, "1.0.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "0.1.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "0.0.1").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.IOS, "1.1.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "1.0.1").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "0.1.1").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.IOS, "1.0.2").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "0.2.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.IOS, "0.2.2").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.AOS, "0.0.0").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.AOS, "1.0.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "0.1.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "0.0.1").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.AOS, "1.1.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "1.0.1").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "0.1.1").needForceUpdate shouldBeEqual true

service.checkApplicationVersion(DeviceOS.AOS, "1.0.2").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "0.2.0").needForceUpdate shouldBeEqual true
service.checkApplicationVersion(DeviceOS.AOS, "0.2.2").needForceUpdate shouldBeEqual true
}

it("버전이 최소 지원 버전 이상이면 강제 업데이트를 하지않아도 된다.") {
service.checkApplicationVersion(DeviceOS.IOS, "1.1.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "1.1.2").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "1.2.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "1.2.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.0.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.0.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.1.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.1.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.1.2").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.IOS, "2.2.2").needForceUpdate shouldBeEqual false

service.checkApplicationVersion(DeviceOS.AOS, "1.1.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "1.1.2").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "1.2.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "1.2.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.0.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.0.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.1.0").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.1.1").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.1.2").needForceUpdate shouldBeEqual false
service.checkApplicationVersion(DeviceOS.AOS, "2.2.2").needForceUpdate shouldBeEqual false
}
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.oksusu.susu.domain.metadata.domain

import com.oksusu.susu.domain.common.BaseEntity
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "application_metadata")
Expand All @@ -11,17 +10,13 @@ class ApplicationMetadata(
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = -1,

/** 최신 어플리케이션 버전 */
@Column(name = "application_version")
val applicationVersion: String,
/** ios 최소 지원 어플리케이션 버전 */
@Column(name = "ios_min_support_version")
val iosMinSupportVersion: String,

/** 강제 업데이트 날짜 */
@Column(name = "forced_update_date")
val forcedUpdateDate: LocalDateTime,

/** 해당 버전의 주요 기능 설명 */
@Column(name = "description")
val description: String? = null,
/** aos 최소 지원 어플리케이션 버전 */
@Column(name = "aos_min_support_version")
val aosMinSupportVersion: String,

/** 활성화 여부 / 활성화 : 1, 비활성화 : 0 */
@Column(name = "is_active")
Expand Down
5 changes: 2 additions & 3 deletions sql/ddl/metadata/DDL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
CREATE TABLE `application_metadata`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '어플리케이션 설정 정보 id',
`application_version` varchar(255) NOT NULL COMMENT '최신 어플리케이션 버전',
`forced_update_date` datetime NOT NULL COMMENT '강제 업데이트 날짜',
`description` text COMMENT '해당 버전의 주요 기능 설명',
`ios_min_support_version` varchar(255) NOT NULL COMMENT 'ios 최소 지원 어플리케이션 버전',
`aos_min_support_version` varchar(255) NOT NULL COMMENT 'aos 최소 지원 어플리케이션 버전',
`is_active` tinyint NOT NULL COMMENT '활성화 : 1, 비활성화 : 0',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '생성일',
`modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일',
Expand Down

0 comments on commit d0449f3

Please sign in to comment.