diff --git a/.github/workflows/backend-cd-prod.yml b/.github/workflows/backend-cd-prod.yml
index 52d5b35c8..b749d345f 100644
--- a/.github/workflows/backend-cd-prod.yml
+++ b/.github/workflows/backend-cd-prod.yml
@@ -129,7 +129,7 @@ jobs:
- name: Tag successful deployment as latest
if: success()
run: |
- docker tag ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKERHUB_REPOSITORY:${{ github.sha }-prod ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKERHUB_REPOSITORY:latest
+ docker tag ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKERHUB_REPOSITORY:${{ github.sha }}-prod ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKERHUB_REPOSITORY:latest
docker push ${{ secrets.DOCKERHUB_USERNAME }}/$DOCKERHUB_REPOSITORY:latest
- name: Check Docker Process
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 53c658624..804732fef 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -76,7 +76,6 @@ kapt {
dependencies {
// androidx
- implementation(libs.androidx.webkit)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity)
implementation(libs.androidx.hilt.work)
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 278c9caa7..23f0dee44 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -96,6 +96,7 @@
? {
- val listType = Types.newParameterizedType(List::class.java, MateEta::class.java)
- val adapter: JsonAdapter> = moshi.adapter(listType)
- return adapter.fromJson(value)
+ return value.split(";").map { fromJson(it) }
}
@TypeConverter
fun fromMateEta(type: List): String {
- val listType = Types.newParameterizedType(List::class.java, MateEta::class.java)
- val adapter: JsonAdapter> = moshi.adapter(listType)
- return adapter.toJson(type)
+ return type.joinToString(";") { toJson(it) }
+ }
+
+ private fun toJson(mateEta: MateEta): String {
+ val etaStatusString =
+ when (mateEta.etaStatus) {
+ is EtaStatus.Arrived -> """{type:"Arrived"}"""
+ is EtaStatus.ArrivalSoon -> """{"type":"ArrivalSoon"/"durationMinutes":${mateEta.etaStatus.durationMinutes}}"""
+ is EtaStatus.LateWarning -> """{"type":"LateWarning"/"durationMinutes":${mateEta.etaStatus.durationMinutes}}"""
+ is EtaStatus.Late -> """{"type":"Late"/"durationMinutes":${mateEta.etaStatus.durationMinutes}}"""
+ is EtaStatus.Missing -> """{"type":"Missing"}"""
+ }
+
+ return """{"mateId":${mateEta.mateId},"nickname":"${mateEta.nickname}","etaStatus":$etaStatusString}"""
+ }
+
+ private fun fromJson(json: String): MateEta {
+ val cleanedJson = json.trim().removePrefix("{").removeSuffix("}")
+ val jsonObject =
+ cleanedJson.split(",").map { it.trim() }
+ .associate {
+ val (key, value) =
+ it.split(":", limit = 2)
+ .map { it.trim().removeSurrounding("\"") }
+ key to value
+ }
+
+ val mateId = jsonObject["mateId"]!!.toLong()
+ val nickname = jsonObject["nickname"]!!
+ val etaStatusJson = jsonObject["etaStatus"]!!.trim()
+ val etaStatusMap =
+ etaStatusJson.removePrefix("{").removeSuffix("}").split("/")
+ .map { it.trim() }
+ .associate {
+ val (key, value) = it.split(":", limit = 2).map { it.trim() }
+ key.removeSurrounding("\"") to value.trim().removeSurrounding("\"")
+ }
+
+ val etaStatusType =
+ etaStatusMap["type"] ?: throw IllegalArgumentException("Missing etaStatus type")
+ val etaStatus =
+ when (etaStatusType) {
+ "Arrived" -> EtaStatus.Arrived
+ "ArrivalSoon" -> {
+ val durationMinutes = etaStatusMap["durationMinutes"]?.toInt()
+ EtaStatus.ArrivalSoon(durationMinutes ?: throw IllegalArgumentException("Missing durationMinutes"))
+ }
+
+ "LateWarning" -> {
+ val durationMinutes = etaStatusMap["durationMinutes"]?.toInt()
+ EtaStatus.LateWarning(durationMinutes ?: throw IllegalArgumentException("Missing durationMinutes"))
+ }
+
+ "Late" -> {
+ val durationMinutes = etaStatusMap["durationMinutes"]?.toInt()
+ EtaStatus.Late(durationMinutes ?: throw IllegalArgumentException("Missing durationMinutes"))
+ }
+
+ "Missing" -> EtaStatus.Missing
+ else -> throw IllegalArgumentException("Unknown etaStatus type: $etaStatusType")
+ }
+
+ return MateEta(mateId, nickname, etaStatus)
}
}
diff --git a/android/app/src/main/java/com/mulberry/ody/data/remote/core/entity/meeting/mapper/MatesEtaResponseMapper.kt b/android/app/src/main/java/com/mulberry/ody/data/remote/core/entity/meeting/mapper/MatesEtaResponseMapper.kt
index e1e4f5718..855ed2bef 100644
--- a/android/app/src/main/java/com/mulberry/ody/data/remote/core/entity/meeting/mapper/MatesEtaResponseMapper.kt
+++ b/android/app/src/main/java/com/mulberry/ody/data/remote/core/entity/meeting/mapper/MatesEtaResponseMapper.kt
@@ -2,7 +2,7 @@ package com.mulberry.ody.data.remote.core.entity.meeting.mapper
import com.mulberry.ody.data.remote.core.entity.meeting.response.MateEtaResponse
import com.mulberry.ody.data.remote.core.entity.meeting.response.MatesEtaResponse
-import com.mulberry.ody.domain.model.EtaType
+import com.mulberry.ody.domain.model.EtaStatus
import com.mulberry.ody.domain.model.MateEta
import com.mulberry.ody.domain.model.MateEtaInfo
@@ -18,18 +18,17 @@ private fun MateEtaResponse.toMateEta(): MateEta {
return MateEta(
mateId = mateId,
nickname = nickname,
- etaType = status.toEtaType(),
- durationMinute = durationMinutes.toInt(),
+ etaStatus = status.toEtaStatus(durationMinutes),
)
}
-private fun String.toEtaType(): EtaType {
+private fun String.toEtaStatus(durationMinutes: Long): EtaStatus {
return when (this) {
- "LATE_WARNING" -> EtaType.LATE_WARNING
- "ARRIVAL_SOON" -> EtaType.ARRIVAL_SOON
- "ARRIVED" -> EtaType.ARRIVED
- "LATE" -> EtaType.LATE
- "MISSING" -> EtaType.MISSING
+ "ARRIVED" -> EtaStatus.Arrived
+ "ARRIVAL_SOON" -> EtaStatus.ArrivalSoon(durationMinutes = durationMinutes.toInt())
+ "LATE_WARNING" -> EtaStatus.LateWarning(durationMinutes = durationMinutes.toInt())
+ "LATE" -> EtaStatus.Late(durationMinutes = durationMinutes.toInt())
+ "MISSING" -> EtaStatus.Missing
else -> throw IllegalArgumentException("존재하지 않는 Eta Type 입니다.")
}
}
diff --git a/android/app/src/main/java/com/mulberry/ody/di/DBModule.kt b/android/app/src/main/java/com/mulberry/ody/di/DBModule.kt
index e5fe07dc6..d84a9c6c1 100644
--- a/android/app/src/main/java/com/mulberry/ody/di/DBModule.kt
+++ b/android/app/src/main/java/com/mulberry/ody/di/DBModule.kt
@@ -5,8 +5,6 @@ import com.mulberry.ody.data.local.db.EtaReservationDao
import com.mulberry.ody.data.local.db.MateEtaInfoDao
import com.mulberry.ody.data.local.db.OdyDatabase
import com.mulberry.ody.data.local.db.OdyDatastore
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -17,10 +15,6 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DBModule {
- @Provides
- @Singleton
- fun provideMoshi(): Moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
-
@Provides
@Singleton
fun provideDataStore(
@@ -33,9 +27,8 @@ object DBModule {
@Singleton
fun provideOdyDatabase(
@ApplicationContext context: Context,
- moshi: Moshi,
): OdyDatabase {
- return OdyDatabase.create(context, moshi)
+ return OdyDatabase.create(context)
}
@Provides
diff --git a/android/app/src/main/java/com/mulberry/ody/domain/model/EtaStatus.kt b/android/app/src/main/java/com/mulberry/ody/domain/model/EtaStatus.kt
new file mode 100644
index 000000000..3e1c228e3
--- /dev/null
+++ b/android/app/src/main/java/com/mulberry/ody/domain/model/EtaStatus.kt
@@ -0,0 +1,13 @@
+package com.mulberry.ody.domain.model
+
+sealed interface EtaStatus {
+ data object Arrived : EtaStatus
+
+ data class ArrivalSoon(val durationMinutes: Int) : EtaStatus
+
+ data class LateWarning(val durationMinutes: Int) : EtaStatus
+
+ data class Late(val durationMinutes: Int) : EtaStatus
+
+ data object Missing : EtaStatus
+}
diff --git a/android/app/src/main/java/com/mulberry/ody/domain/model/EtaType.kt b/android/app/src/main/java/com/mulberry/ody/domain/model/EtaType.kt
deleted file mode 100644
index 875b2a270..000000000
--- a/android/app/src/main/java/com/mulberry/ody/domain/model/EtaType.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.mulberry.ody.domain.model
-
-enum class EtaType {
- LATE_WARNING,
- ARRIVAL_SOON,
- ARRIVED,
- LATE,
- MISSING,
-}
diff --git a/android/app/src/main/java/com/mulberry/ody/domain/model/MateEta.kt b/android/app/src/main/java/com/mulberry/ody/domain/model/MateEta.kt
index 9e82fb29b..dbcc65ecc 100644
--- a/android/app/src/main/java/com/mulberry/ody/domain/model/MateEta.kt
+++ b/android/app/src/main/java/com/mulberry/ody/domain/model/MateEta.kt
@@ -3,6 +3,5 @@ package com.mulberry.ody.domain.model
data class MateEta(
val mateId: Long,
val nickname: String,
- val etaType: EtaType,
- val durationMinute: Int,
+ val etaStatus: EtaStatus,
)
diff --git a/android/app/src/main/java/com/mulberry/ody/domain/model/MateEtaInfo.kt b/android/app/src/main/java/com/mulberry/ody/domain/model/MateEtaInfo.kt
index bf4583338..a169ad549 100644
--- a/android/app/src/main/java/com/mulberry/ody/domain/model/MateEtaInfo.kt
+++ b/android/app/src/main/java/com/mulberry/ody/domain/model/MateEtaInfo.kt
@@ -1,8 +1,5 @@
package com.mulberry.ody.domain.model
-import com.squareup.moshi.JsonClass
-
-@JsonClass(generateAdapter = true)
data class MateEtaInfo(
val userId: Long,
val mateEtas: List,
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/notification/FCMNotification.kt b/android/app/src/main/java/com/mulberry/ody/presentation/notification/FCMNotification.kt
index f1d46e0ee..7ef539ab8 100644
--- a/android/app/src/main/java/com/mulberry/ody/presentation/notification/FCMNotification.kt
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/notification/FCMNotification.kt
@@ -6,8 +6,10 @@ import android.app.PendingIntent
import android.content.Context
import android.media.RingtoneManager
import androidx.core.app.NotificationCompat
+import androidx.core.app.TaskStackBuilder
import com.mulberry.ody.R
import com.mulberry.ody.domain.model.NotificationType
+import com.mulberry.ody.presentation.meetings.MeetingsActivity
import com.mulberry.ody.presentation.room.MeetingRoomActivity
import com.mulberry.ody.presentation.room.MeetingRoomActivity.Companion.NAVIGATE_TO_ETA_DASHBOARD
import com.mulberry.ody.presentation.room.MeetingRoomActivity.Companion.NAVIGATE_TO_NOTIFICATION_LOG
@@ -74,11 +76,15 @@ class FCMNotification
NotificationType.DEFAULT -> ""
}
- val intent = MeetingRoomActivity.getIntent(context, meetingId.toLong(), navigationTarget)
- return PendingIntent.getActivity(
- context,
+ val stackBuilder = TaskStackBuilder.create(context)
+ val parentIntent = MeetingsActivity.getIntent(context)
+ stackBuilder.addNextIntent(parentIntent)
+
+ val meetingRoomIntent = MeetingRoomActivity.getIntent(context, meetingId.toLong(), navigationTarget)
+ stackBuilder.addNextIntent(meetingRoomIntent)
+
+ return stackBuilder.getPendingIntent(
NOTIFICATION_REQUEST_CODE,
- intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)
}
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/EtaDashboardBindingAdapter.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/EtaDashboardBindingAdapter.kt
index 9d0a9a982..c83e8dc8f 100644
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/EtaDashboardBindingAdapter.kt
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/EtaDashboardBindingAdapter.kt
@@ -9,29 +9,18 @@ import androidx.databinding.BindingAdapter
import com.mulberry.ody.R
import com.mulberry.ody.presentation.room.etadashboard.listener.MissingToolTipListener
import com.mulberry.ody.presentation.room.etadashboard.listener.NudgeListener
-import com.mulberry.ody.presentation.room.etadashboard.model.EtaDurationMinuteTypeUiModel
-import com.mulberry.ody.presentation.room.etadashboard.model.EtaTypeUiModel
+import com.mulberry.ody.presentation.room.etadashboard.model.EtaStatusUiModel
import com.mulberry.ody.presentation.room.etadashboard.model.MateEtaUiModel
@BindingAdapter("etaType")
-fun TextView.setBadgeByEtaType(etaTypeUiModel: EtaTypeUiModel) {
- text = context.getString(etaTypeUiModel.messageId)
- backgroundTintList = ContextCompat.getColorStateList(context, etaTypeUiModel.colorId)
+fun TextView.setBadgeByEtaType(etaStatusUiModel: EtaStatusUiModel) {
+ text = context.getString(etaStatusUiModel.badgeMessageId)
+ backgroundTintList = ContextCompat.getColorStateList(context, etaStatusUiModel.badgeColorId)
}
@BindingAdapter("etaStatus")
-fun TextView.setEtaStatusText(mateEtaUiModel: MateEtaUiModel) {
- text =
- when (mateEtaUiModel.getEtaDurationMinuteTypeUiModel()) {
- EtaDurationMinuteTypeUiModel.ARRIVED -> context.getString(R.string.status_arrived)
- EtaDurationMinuteTypeUiModel.MISSING -> context.getString(R.string.status_missing)
- EtaDurationMinuteTypeUiModel.ARRIVAL_SOON -> context.getString(R.string.status_arrival_soon)
- EtaDurationMinuteTypeUiModel.ARRIVAL_REMAIN_TIME ->
- context.getString(
- R.string.status_arrival_remain_time,
- mateEtaUiModel.durationMinute,
- )
- }
+fun TextView.setEtaStatusText(etaStatusUiModel: EtaStatusUiModel) {
+ text = etaStatusUiModel.etaStatusMessage(context)
}
@BindingAdapter("isUserSelf", "missingToolTipListener")
@@ -46,19 +35,16 @@ fun TextView.setOnClickMissingTooltip(
}
@BindingAdapter("etaBadgeAnimation")
-fun TextView.setEtaBadgeAnimation(etaTypeUiModel: EtaTypeUiModel) {
- if (
- etaTypeUiModel == EtaTypeUiModel.LATE ||
- etaTypeUiModel == EtaTypeUiModel.LATE_WARNING
- ) {
+fun TextView.setEtaBadgeAnimation(etaStatusUiModel: EtaStatusUiModel) {
+ if (etaStatusUiModel.canNudge()) {
val animation = AnimationUtils.loadAnimation(context, R.anim.bounce_duration_500)
this.startAnimation(animation)
}
}
@BindingAdapter("etaBadgeAnimation")
-fun TextView.setEtaBadgeAnimation(isPossibleNudge: Boolean) {
- if (isPossibleNudge) {
+fun TextView.setEtaBadgeAnimation(canNudge: Boolean) {
+ if (canNudge) {
val animation = AnimationUtils.loadAnimation(context, R.anim.bounce_duration_500)
this.startAnimation(animation)
}
@@ -70,10 +56,7 @@ fun TextView.setOnClickNudge(
nudgeListener: NudgeListener,
) {
setOnClickListener {
- if (
- mateEtaUiModel.isUserSelf.not() &&
- (mateEtaUiModel.etaTypeUiModel == EtaTypeUiModel.LATE || mateEtaUiModel.etaTypeUiModel == EtaTypeUiModel.LATE_WARNING)
- ) {
+ if (mateEtaUiModel.isUserSelf.not() && mateEtaUiModel.etaStatusUiModel.canNudge()) {
nudgeListener.nudgeMate(
nudgeId = mateEtaUiModel.userId,
mateId = mateEtaUiModel.mateId,
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaDurationMinuteTypeUiModel.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaDurationMinuteTypeUiModel.kt
deleted file mode 100644
index 6487b47e2..000000000
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaDurationMinuteTypeUiModel.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.mulberry.ody.presentation.room.etadashboard.model
-
-enum class EtaDurationMinuteTypeUiModel {
- ARRIVED,
- MISSING,
- ARRIVAL_SOON,
- ARRIVAL_REMAIN_TIME,
-}
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaStatusUiModel.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaStatusUiModel.kt
new file mode 100644
index 000000000..0691acc89
--- /dev/null
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaStatusUiModel.kt
@@ -0,0 +1,87 @@
+package com.mulberry.ody.presentation.room.etadashboard.model
+
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.annotation.StringRes
+import com.mulberry.ody.R
+
+sealed class EtaStatusUiModel {
+ @get:ColorRes
+ abstract val badgeColorId: Int
+
+ @get:StringRes
+ abstract val badgeMessageId: Int
+
+ protected fun etaDurationMinutesMessage(
+ context: Context,
+ durationMinutes: Int,
+ ): String {
+ if (durationMinutes in ARRIVAL_SOON_VALUE_RANGE) {
+ return context.getString(R.string.status_arrival_soon)
+ }
+ return context.getString(R.string.status_arrival_remain_time, durationMinutes)
+ }
+
+ abstract fun canNudge(): Boolean
+
+ abstract fun etaStatusMessage(context: Context): String
+
+ data object Arrived : EtaStatusUiModel() {
+ override val badgeColorId: Int = R.color.blue
+ override val badgeMessageId: Int = R.string.badge_arrived
+
+ override fun etaStatusMessage(context: Context): String {
+ return context.getString(R.string.status_arrived)
+ }
+
+ override fun canNudge(): Boolean = false
+ }
+
+ data class ArrivalSoon(val durationMinutes: Int) : EtaStatusUiModel() {
+ override val badgeColorId: Int = R.color.green
+ override val badgeMessageId: Int = R.string.badge_arrival_soon
+
+ override fun etaStatusMessage(context: Context): String {
+ return super.etaDurationMinutesMessage(context, durationMinutes)
+ }
+
+ override fun canNudge(): Boolean = false
+ }
+
+ data class LateWarning(val durationMinutes: Int) : EtaStatusUiModel() {
+ override val badgeColorId: Int = R.color.yellow
+ override val badgeMessageId: Int = R.string.badge_late_warning
+
+ override fun etaStatusMessage(context: Context): String {
+ return super.etaDurationMinutesMessage(context, durationMinutes)
+ }
+
+ override fun canNudge(): Boolean = true
+ }
+
+ data class Late(val durationMinutes: Int) : EtaStatusUiModel() {
+ override val badgeColorId: Int = R.color.red
+ override val badgeMessageId: Int = R.string.badge_late
+
+ override fun etaStatusMessage(context: Context): String {
+ return super.etaDurationMinutesMessage(context, durationMinutes)
+ }
+
+ override fun canNudge(): Boolean = true
+ }
+
+ data object Missing : EtaStatusUiModel() {
+ override val badgeColorId: Int = R.color.gray_400
+ override val badgeMessageId: Int = R.string.badge_missing
+
+ override fun etaStatusMessage(context: Context): String {
+ return context.getString(R.string.status_missing)
+ }
+
+ override fun canNudge(): Boolean = false
+ }
+
+ companion object {
+ private val ARRIVAL_SOON_VALUE_RANGE = 1..10
+ }
+}
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModel.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModel.kt
deleted file mode 100644
index 49846821d..000000000
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModel.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.mulberry.ody.presentation.room.etadashboard.model
-
-import androidx.annotation.ColorRes
-import androidx.annotation.StringRes
-import com.mulberry.ody.R
-
-enum class EtaTypeUiModel(
- @ColorRes val colorId: Int,
- @StringRes val messageId: Int,
-) {
- LATE_WARNING(R.color.yellow, R.string.badge_late_warning),
- ARRIVAL_SOON(R.color.green, R.string.badge_arrival_soon),
- ARRIVED(R.color.blue, R.string.badge_arrived),
- LATE(R.color.red, R.string.badge_late),
- MISSING(R.color.gray_400, R.string.badge_missing),
-}
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModelMapper.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModelMapper.kt
index 8c8ac20e1..cce2ae447 100644
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModelMapper.kt
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/EtaTypeUiModelMapper.kt
@@ -1,13 +1,13 @@
package com.mulberry.ody.presentation.room.etadashboard.model
-import com.mulberry.ody.domain.model.EtaType
+import com.mulberry.ody.domain.model.EtaStatus
-fun EtaType.toEtaTypeUiModel(): EtaTypeUiModel {
+fun EtaStatus.toEtaStatusUiModel(): EtaStatusUiModel {
return when (this) {
- EtaType.LATE_WARNING -> EtaTypeUiModel.LATE_WARNING
- EtaType.ARRIVAL_SOON -> EtaTypeUiModel.ARRIVAL_SOON
- EtaType.ARRIVED -> EtaTypeUiModel.ARRIVED
- EtaType.LATE -> EtaTypeUiModel.LATE
- EtaType.MISSING -> EtaTypeUiModel.MISSING
+ EtaStatus.Arrived -> EtaStatusUiModel.Arrived
+ is EtaStatus.ArrivalSoon -> EtaStatusUiModel.ArrivalSoon(durationMinutes)
+ is EtaStatus.LateWarning -> EtaStatusUiModel.LateWarning(durationMinutes)
+ is EtaStatus.Late -> EtaStatusUiModel.Late(durationMinutes)
+ EtaStatus.Missing -> EtaStatusUiModel.Missing
}
}
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModel.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModel.kt
index 07d6a7313..139c9e538 100644
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModel.kt
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModel.kt
@@ -2,25 +2,9 @@ package com.mulberry.ody.presentation.room.etadashboard.model
data class MateEtaUiModel(
val nickname: String,
- val etaTypeUiModel: EtaTypeUiModel,
- val durationMinute: Int,
+ val etaStatusUiModel: EtaStatusUiModel,
val isMissing: Boolean,
val isUserSelf: Boolean,
val userId: Long,
val mateId: Long,
-) {
- fun getEtaDurationMinuteTypeUiModel(): EtaDurationMinuteTypeUiModel {
- return when (durationMinute) {
- ARRIVED_VALUE -> EtaDurationMinuteTypeUiModel.ARRIVED
- MISSING_VALUE -> EtaDurationMinuteTypeUiModel.MISSING
- in ARRIVAL_SOON_VALUE_RANGE -> EtaDurationMinuteTypeUiModel.ARRIVAL_SOON
- else -> EtaDurationMinuteTypeUiModel.ARRIVAL_REMAIN_TIME
- }
- }
-
- companion object {
- const val ARRIVED_VALUE = 0
- const val MISSING_VALUE = -1
- val ARRIVAL_SOON_VALUE_RANGE = 1..10
- }
-}
+)
diff --git a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModelMapper.kt b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModelMapper.kt
index 137cccc0b..cda2fb218 100644
--- a/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModelMapper.kt
+++ b/android/app/src/main/java/com/mulberry/ody/presentation/room/etadashboard/model/MateEtaUiModelMapper.kt
@@ -1,6 +1,6 @@
package com.mulberry.ody.presentation.room.etadashboard.model
-import com.mulberry.ody.domain.model.EtaType
+import com.mulberry.ody.domain.model.EtaStatus
import com.mulberry.ody.domain.model.MateEta
import com.mulberry.ody.domain.model.MateEtaInfo
@@ -11,9 +11,8 @@ fun MateEtaInfo.toMateEtaUiModels(): List {
private fun MateEta.toMateEtaUiModel(userId: Long): MateEtaUiModel {
return MateEtaUiModel(
nickname = nickname,
- etaTypeUiModel = etaType.toEtaTypeUiModel(),
- durationMinute = durationMinute,
- isMissing = etaType == EtaType.MISSING,
+ etaStatusUiModel = etaStatus.toEtaStatusUiModel(),
+ isMissing = etaStatus is EtaStatus.Missing,
isUserSelf = userId == mateId,
userId = userId,
mateId = mateId,
diff --git a/android/app/src/main/res/layout-sw1080dp/fragment_meeting_date.xml b/android/app/src/main/res/layout-sw1080dp/fragment_meeting_date.xml
index 4241b3e71..4c83549f3 100644
--- a/android/app/src/main/res/layout-sw1080dp/fragment_meeting_date.xml
+++ b/android/app/src/main/res/layout-sw1080dp/fragment_meeting_date.xml
@@ -15,7 +15,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="52dp"
android:text="@string/meeting_date_question_front"
- android:textColor="@color/purple_800"
+ android:textColor="@color/secondary"
app:layout_constraintEnd_toStartOf="@+id/tv_meet"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
@@ -40,6 +40,7 @@
android:layout_marginHorizontal="@dimen/page_horizontal_margin"
android:scaleX="2.0"
android:scaleY="2.0"
+ android:theme="@style/date_picker_style"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/android/app/src/main/res/layout-sw1080dp/fragment_meeting_time.xml b/android/app/src/main/res/layout-sw1080dp/fragment_meeting_time.xml
index fcd0cfa13..b7d84bf91 100644
--- a/android/app/src/main/res/layout-sw1080dp/fragment_meeting_time.xml
+++ b/android/app/src/main/res/layout-sw1080dp/fragment_meeting_time.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="52dp"
android:text="@string/meeting_time_question_front"
- android:textColor="@color/purple_800"
+ android:textColor="@color/secondary"
app:layout_constraintEnd_toStartOf="@id/tv_meeting_time_back"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
diff --git a/android/app/src/main/res/layout-sw720dp/fragment_meeting_date.xml b/android/app/src/main/res/layout-sw720dp/fragment_meeting_date.xml
index 0e8ccf833..065abad9c 100644
--- a/android/app/src/main/res/layout-sw720dp/fragment_meeting_date.xml
+++ b/android/app/src/main/res/layout-sw720dp/fragment_meeting_date.xml
@@ -15,7 +15,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="52dp"
android:text="@string/meeting_date_question_front"
- android:textColor="@color/purple_800"
+ android:textColor="@color/secondary"
app:layout_constraintEnd_toStartOf="@+id/tv_meet"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
@@ -40,6 +40,7 @@
android:scaleX="1.5"
android:scaleY="1.5"
android:layout_marginHorizontal="@dimen/page_horizontal_margin"
+ android:theme="@style/date_picker_style"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/android/app/src/main/res/layout-sw720dp/fragment_meeting_time.xml b/android/app/src/main/res/layout-sw720dp/fragment_meeting_time.xml
index 55726d71a..0503316d3 100644
--- a/android/app/src/main/res/layout-sw720dp/fragment_meeting_time.xml
+++ b/android/app/src/main/res/layout-sw720dp/fragment_meeting_time.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="52dp"
android:text="@string/meeting_time_question_front"
- android:textColor="@color/purple_800"
+ android:textColor="@color/secondary"
app:layout_constraintEnd_toStartOf="@id/tv_meeting_time_back"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
diff --git a/android/app/src/main/res/layout/item_eta_dashboard.xml b/android/app/src/main/res/layout/item_eta_dashboard.xml
index 9970282ae..f9377f011 100644
--- a/android/app/src/main/res/layout/item_eta_dashboard.xml
+++ b/android/app/src/main/res/layout/item_eta_dashboard.xml
@@ -42,19 +42,19 @@
@@ -76,7 +76,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
- app:etaStatus="@{eta}"
+ app:etaStatus="@{eta.etaStatusUiModel}"
tools:text="13분 후 도착" />
-
\ No newline at end of file
+
diff --git a/android/app/src/test/java/com/mulberry/ody/TestFixtures.kt b/android/app/src/test/java/com/mulberry/ody/TestFixtures.kt
index 1dd4a91b3..85dc663f2 100644
--- a/android/app/src/test/java/com/mulberry/ody/TestFixtures.kt
+++ b/android/app/src/test/java/com/mulberry/ody/TestFixtures.kt
@@ -1,7 +1,7 @@
package com.mulberry.ody
import com.mulberry.ody.domain.model.Address
-import com.mulberry.ody.domain.model.EtaType
+import com.mulberry.ody.domain.model.EtaStatus
import com.mulberry.ody.domain.model.LogType
import com.mulberry.ody.domain.model.Mate
import com.mulberry.ody.domain.model.MateEta
@@ -28,8 +28,6 @@ val meeting: Meeting =
inviteCode,
)
-val meetings: List = listOf(meeting)
-
val meetingCatalog =
MeetingCatalog(
meetingId,
@@ -105,15 +103,14 @@ val notificationLogs: List =
)
private val nicknames = listOf("콜리", "올리브", "카키", "해음")
-private val mateEtaTypes =
- listOf(EtaType.LATE_WARNING, EtaType.ARRIVAL_SOON, EtaType.ARRIVED, EtaType.MISSING)
-val mateEtaDurationMinutes = listOf(83, 10, 0, -1)
+private val mateEtaStatuses =
+ listOf(EtaStatus.LateWarning(15), EtaStatus.ArrivalSoon(5), EtaStatus.Arrived, EtaStatus.Missing)
val mateEtaInfo =
MateEtaInfo(
userId = 3L,
nicknames.mapIndexed { idx, nickname ->
- MateEta(idx.toLong(), nickname, mateEtaTypes[idx], mateEtaDurationMinutes[idx])
+ MateEta(idx.toLong(), nickname, mateEtaStatuses[idx])
},
)
diff --git a/android/app/src/test/java/com/mulberry/ody/presentation/room/MeetingRoomViewModelTest.kt b/android/app/src/test/java/com/mulberry/ody/presentation/room/MeetingRoomViewModelTest.kt
index 7ba51c717..3bde21977 100644
--- a/android/app/src/test/java/com/mulberry/ody/presentation/room/MeetingRoomViewModelTest.kt
+++ b/android/app/src/test/java/com/mulberry/ody/presentation/room/MeetingRoomViewModelTest.kt
@@ -7,7 +7,6 @@ import com.mulberry.ody.fake.FakeImageStorage
import com.mulberry.ody.fake.FakeMatesEtaRepository
import com.mulberry.ody.fake.FakeMeetingRepository
import com.mulberry.ody.fake.FakeNotificationLogRepository
-import com.mulberry.ody.mateEtaDurationMinutes
import com.mulberry.ody.mateEtaInfo
import com.mulberry.ody.meeting
import com.mulberry.ody.meetingId
@@ -61,15 +60,6 @@ class MeetingRoomViewModelTest {
}
}
- @Test
- fun `친구들과 나의 남은 시간을 볼 수 있다`() {
- runTest {
- // then
- val durationMinute = viewModel.mateEtaUiModels.first()?.map { it.durationMinute }
- assertThat(durationMinute).isEqualTo(mateEtaDurationMinutes)
- }
- }
-
@Test
fun `약속 id에 맞는 약속을 조회하고 해당하는 로그 목록을 가져온다`() {
runTest {
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index cb1c3ee97..962c41196 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -42,12 +42,10 @@ room = "2.6.1"
splashScreen = "1.0.1"
timber = "5.0.1"
viewPager = "1.1.0"
-webkit = "1.12.0"
workRuntimeKtx = "2.9.1"
[libraries]
# androidx
-androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltWork" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltWork" }
diff --git a/backend/src/main/java/com/ody/eta/dto/response/MateEtaResponse.java b/backend/src/main/java/com/ody/eta/dto/response/MateEtaResponse.java
index 35829a0c9..e6b573dfa 100644
--- a/backend/src/main/java/com/ody/eta/dto/response/MateEtaResponse.java
+++ b/backend/src/main/java/com/ody/eta/dto/response/MateEtaResponse.java
@@ -21,18 +21,7 @@ public static MateEtaResponse of(Eta eta, Meeting meeting) {
return new MateEtaResponse(
eta.getMate().getNickname().getValue(),
EtaStatus.of(eta, meeting),
- mapMinutes(eta, meeting)
+ eta.countDownMinutes()
);
}
-
- // 안드측과 협의해서 추후에 View 로직에서 처리 가능한 부분으로 사라질 메서드임 (DTO에 로직이 있는거 걱정 ㄴㄴ)
- private static long mapMinutes(Eta eta, Meeting meeting) {
- if (eta.isMissing()) {
- return -1L;
- }
- if (eta.isArrivalSoon(meeting)) {
- return 1L;
- }
- return eta.countDownMinutes();
- }
}
diff --git a/backend/src/main/java/com/ody/mate/controller/MateController.java b/backend/src/main/java/com/ody/mate/controller/MateController.java
index ad0b311a6..c2808d80d 100644
--- a/backend/src/main/java/com/ody/mate/controller/MateController.java
+++ b/backend/src/main/java/com/ody/mate/controller/MateController.java
@@ -11,6 +11,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@@ -39,4 +41,11 @@ public ResponseEntity nudgeMate(@RequestBody @Valid NudgeRequest nudgeRequ
mateService.nudge(nudgeRequest);
return ResponseEntity.ok().build();
}
+
+ @Override
+ @DeleteMapping("/meetings/{meetingId}/mate")
+ public ResponseEntity leave(@AuthMember Member member, @PathVariable Long meetingId) {
+ mateService.leaveByMeetingIdAndMemberId(meetingId, member.getId());
+ return ResponseEntity.noContent().build();
+ }
}
diff --git a/backend/src/main/java/com/ody/mate/controller/MateControllerSwagger.java b/backend/src/main/java/com/ody/mate/controller/MateControllerSwagger.java
index 0db366917..de9701378 100644
--- a/backend/src/main/java/com/ody/mate/controller/MateControllerSwagger.java
+++ b/backend/src/main/java/com/ody/mate/controller/MateControllerSwagger.java
@@ -51,4 +51,18 @@ ResponseEntity saveV2(
@ErrorCode401
@ErrorCode500
ResponseEntity nudgeMate(NudgeRequest nudgeRequest);
+
+ @Operation(
+ summary = "참여자 약속 방 나가기",
+ responses = {
+ @ApiResponse(
+ responseCode = "204",
+ description = "참여자 약속 방 나가기 성공"
+ )
+ }
+ )
+ @ErrorCode401
+ @ErrorCode404(description = "존재하지 않는 약속이거나 해당 약속 참여자가 아닌 경우")
+ @ErrorCode500
+ ResponseEntity leave(@Parameter(hidden = true) Member member, Long meetingId);
}
diff --git a/backend/src/main/java/com/ody/mate/repository/MateRepository.java b/backend/src/main/java/com/ody/mate/repository/MateRepository.java
index 10bd6f569..77687ffa4 100644
--- a/backend/src/main/java/com/ody/mate/repository/MateRepository.java
+++ b/backend/src/main/java/com/ody/mate/repository/MateRepository.java
@@ -30,6 +30,7 @@ public interface MateRepository extends JpaRepository {
select mate
from Mate mate
join fetch mate.meeting
+ join fetch mate.member
where mate.meeting.id = :meetingId
and mate.member.id = :memberId
""")
diff --git a/backend/src/main/java/com/ody/mate/service/MateService.java b/backend/src/main/java/com/ody/mate/service/MateService.java
index e7326c309..2fe881429 100644
--- a/backend/src/main/java/com/ody/mate/service/MateService.java
+++ b/backend/src/main/java/com/ody/mate/service/MateService.java
@@ -10,6 +10,7 @@
import com.ody.mate.dto.request.NudgeRequest;
import com.ody.mate.dto.response.MateSaveResponseV2;
import com.ody.mate.repository.MateRepository;
+import com.ody.meeting.domain.Coordinates;
import com.ody.meeting.domain.Meeting;
import com.ody.meeting.dto.response.MateEtaResponsesV2;
import com.ody.member.domain.Member;
@@ -54,10 +55,9 @@ public void validateAlreadyAttended(Member member, Meeting meeting) {
}
private Mate saveMateAndEta(MateSaveRequestV2 mateSaveRequest, Member member, Meeting meeting) {
- RouteTime routeTime = routeService.calculateRouteTime(
- mateSaveRequest.toOriginCoordinates(),
- meeting.getTargetCoordinates()
- );
+ Coordinates origin = mateSaveRequest.toOriginCoordinates();
+ Coordinates target = meeting.getTargetCoordinates();
+ RouteTime routeTime = routeService.calculateRouteTime(origin, target);
Mate mate = mateRepository.save(mateSaveRequest.toMate(meeting, member, routeTime.getMinutes()));
etaService.saveFirstEtaOfMate(mate, routeTime);
return mate;
@@ -108,13 +108,25 @@ private Mate findByMeetingIdAndMemberId(Long meetingId, Long memberId) {
@Transactional
public void deleteAllByMember(Member member) {
mateRepository.findFetchedAllByMemberId(member.getId())
- .forEach(this::delete);
+ .forEach(this::withdraw);
+ }
+
+ @Transactional
+ public void withdraw(Mate mate) {
+ notificationService.saveMemberDeletionNotification(mate);
+ delete(mate);
+ }
+
+ @Transactional
+ public void leaveByMeetingIdAndMemberId(Long meetingId, Long memberId) {
+ Mate mate = findByMeetingIdAndMemberId(meetingId, memberId);
+ notificationService.saveMateLeaveNotification(mate);
+ delete(mate);
}
@Transactional
public void delete(Mate mate) {
- notificationService.saveMemberDeletionNotification(mate); // TODO: noti 상위 서비스로 묶기
- notificationService.updateAllStatusPendingToDismissedByMateId(mate.getId());
+ notificationService.updateAllStatusToDismissByMateIdAndSendAtAfterNow(mate.getId());
notificationService.unSubscribeTopic(mate.getMeeting(), mate.getMember().getDeviceToken());
etaService.deleteByMateId(mate.getId());
mateRepository.deleteById(mate.getId());
diff --git a/backend/src/main/java/com/ody/meeting/controller/MeetingController.java b/backend/src/main/java/com/ody/meeting/controller/MeetingController.java
index 6df74d86b..ddb184024 100644
--- a/backend/src/main/java/com/ody/meeting/controller/MeetingController.java
+++ b/backend/src/main/java/com/ody/meeting/controller/MeetingController.java
@@ -10,11 +10,9 @@
import com.ody.meeting.dto.response.MeetingWithMatesResponse;
import com.ody.meeting.service.MeetingService;
import com.ody.member.domain.Member;
-import com.ody.notification.domain.Notification;
import com.ody.notification.dto.response.NotiLogFindResponses;
import com.ody.notification.service.NotificationService;
import jakarta.validation.Valid;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -68,7 +66,7 @@ public ResponseEntity findAllMeetingLogs(
@AuthMember Member member,
@PathVariable Long meetingId
) {
- NotiLogFindResponses response = notificationService.findAllMeetingLogs(meetingId);
+ NotiLogFindResponses response = notificationService.findAllNotiLogs(meetingId);
return ResponseEntity.ok(response);
}
diff --git a/backend/src/main/java/com/ody/notification/domain/Notification.java b/backend/src/main/java/com/ody/notification/domain/Notification.java
index b524b0f3c..2a2d24164 100644
--- a/backend/src/main/java/com/ody/notification/domain/Notification.java
+++ b/backend/src/main/java/com/ody/notification/domain/Notification.java
@@ -95,7 +95,17 @@ public static Notification createMemberDeletion(Mate mate) {
mate,
NotificationType.MEMBER_DELETION,
LocalDateTime.now(),
- NotificationStatus.DISMISSED,
+ NotificationStatus.DONE,
+ null
+ );
+ }
+
+ public static Notification createMateLeave(Mate mate) {
+ return new Notification(
+ mate,
+ NotificationType.LEAVE,
+ LocalDateTime.now(),
+ NotificationStatus.DONE,
null
);
}
@@ -111,8 +121,4 @@ public boolean isStatusDismissed() {
public void updateStatusToDone() {
this.status = NotificationStatus.DONE;
}
-
- public void updateStatusToDismissed() {
- this.status = NotificationStatus.DISMISSED;
- }
}
diff --git a/backend/src/main/java/com/ody/notification/domain/NotificationType.java b/backend/src/main/java/com/ody/notification/domain/NotificationType.java
index cd42f2fd2..2ac046657 100644
--- a/backend/src/main/java/com/ody/notification/domain/NotificationType.java
+++ b/backend/src/main/java/com/ody/notification/domain/NotificationType.java
@@ -5,6 +5,7 @@ public enum NotificationType {
ENTRY,
DEPARTURE_REMINDER,
NUDGE,
+ LEAVE,
MEMBER_DELETION,
ETA_NOTICE,
;
diff --git a/backend/src/main/java/com/ody/notification/repository/NotificationRepository.java b/backend/src/main/java/com/ody/notification/repository/NotificationRepository.java
index 8aee04ed6..6e356cc63 100644
--- a/backend/src/main/java/com/ody/notification/repository/NotificationRepository.java
+++ b/backend/src/main/java/com/ody/notification/repository/NotificationRepository.java
@@ -6,6 +6,7 @@
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
public interface NotificationRepository extends JpaRepository {
@@ -15,10 +16,13 @@ public interface NotificationRepository extends JpaRepository findAllMeetingLogsBeforeThanEqual(Long meetingId, LocalDateTime time);
+ List findAllByMeetingIdAndSentAtBeforeDateTimeAndStatusIsNotDismissed(
+ Long meetingId,
+ LocalDateTime dateTime
+ );
@Query("""
select noti
@@ -39,5 +43,7 @@ public interface NotificationRepository extends JpaRepository findAllMeetingIdAndType(Long meetingId, NotificationType type);
- List findAllByMateIdAndStatus(long mateId, NotificationStatus status);
+ @Modifying(clearAutomatically = true)
+ @Query("update Notification n set n.status = 'DISMISSED' where n.mate.id = :mateId and n.sendAt > :dateTime")
+ void updateAllStatusToDismissedByMateIdAndSendAtAfterDateTime(long mateId, LocalDateTime dateTime);
}
diff --git a/backend/src/main/java/com/ody/notification/service/NotificationService.java b/backend/src/main/java/com/ody/notification/service/NotificationService.java
index 25e2c427c..43d74eae2 100644
--- a/backend/src/main/java/com/ody/notification/service/NotificationService.java
+++ b/backend/src/main/java/com/ody/notification/service/NotificationService.java
@@ -90,8 +90,8 @@ public void schedulePendingNotification() {
}
@DisabledDeletedFilter
- public NotiLogFindResponses findAllMeetingLogs(Long meetingId) {
- List notifications = notificationRepository.findAllMeetingLogsBeforeThanEqual(
+ public NotiLogFindResponses findAllNotiLogs(Long meetingId) {
+ List notifications = notificationRepository.findAllByMeetingIdAndSentAtBeforeDateTimeAndStatusIsNotDismissed(
meetingId,
LocalDateTime.now()
);
@@ -108,12 +108,8 @@ public void sendNudgeMessage(Mate requestMate, Mate nudgedMate) {
}
@Transactional
- public void updateAllStatusPendingToDismissedByMateId(long mateId) {
- List notifications = notificationRepository.findAllByMateIdAndStatus(
- mateId,
- NotificationStatus.PENDING
- );
- notifications.forEach(Notification::updateStatusToDismissed);
+ public void updateAllStatusToDismissByMateIdAndSendAtAfterNow(long mateId) {
+ notificationRepository.updateAllStatusToDismissedByMateIdAndSendAtAfterDateTime(mateId, LocalDateTime.now());
}
@Transactional
@@ -122,6 +118,12 @@ public void saveMemberDeletionNotification(Mate mate) {
notificationRepository.save(notification);
}
+ @Transactional
+ public void saveMateLeaveNotification(Mate mate) {
+ Notification notification = Notification.createMateLeave(mate);
+ notificationRepository.save(notification);
+ }
+
public void unSubscribeTopic(Meeting meeting, DeviceToken deviceToken) {
FcmTopic fcmTopic = new FcmTopic(meeting);
fcmSubscriber.unSubscribeTopic(fcmTopic, deviceToken);
diff --git a/backend/src/main/java/com/ody/route/domain/RouteTime.java b/backend/src/main/java/com/ody/route/domain/RouteTime.java
index 45a5aa20f..0a3a89a6e 100644
--- a/backend/src/main/java/com/ody/route/domain/RouteTime.java
+++ b/backend/src/main/java/com/ody/route/domain/RouteTime.java
@@ -1,5 +1,6 @@
package com.ody.route.domain;
+import java.util.Objects;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -7,7 +8,25 @@
@Getter
public class RouteTime {
+ public static final RouteTime CLOSEST_EXCEPTION_TIME = new RouteTime(-1L);
public static final RouteTime ZERO = new RouteTime(0L);
private final long minutes;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RouteTime routeTime = (RouteTime) o;
+ return getMinutes() == routeTime.getMinutes();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getMinutes());
+ }
}
diff --git a/backend/src/main/java/com/ody/route/mapper/OdsayResponseMapper.java b/backend/src/main/java/com/ody/route/mapper/OdsayResponseMapper.java
index f5b6a9a11..20a751c04 100644
--- a/backend/src/main/java/com/ody/route/mapper/OdsayResponseMapper.java
+++ b/backend/src/main/java/com/ody/route/mapper/OdsayResponseMapper.java
@@ -2,6 +2,7 @@
import com.ody.common.exception.OdyBadRequestException;
import com.ody.common.exception.OdyServerErrorException;
+import com.ody.route.domain.RouteTime;
import com.ody.route.dto.OdsayResponse;
import java.util.Optional;
import lombok.AccessLevel;
@@ -15,7 +16,6 @@ public class OdsayResponseMapper {
private static final String CLOSE_LOCATION_CODE = "-98"; //출발지-도착지가 700m 이내일 때
private static final String ODSAY_SERVER_ERROR = "500";
private static final String EMPTY_MESSAGE = "";
- private static final long ZERO_TIME = 0L;
public static long mapMinutes(OdsayResponse response) {
if (response == null) {
@@ -23,7 +23,7 @@ public static long mapMinutes(OdsayResponse response) {
}
if (isCloseLocation(response)) {
- return ZERO_TIME;
+ return RouteTime.CLOSEST_EXCEPTION_TIME.getMinutes();
}
if (response.code().isPresent()) {
diff --git a/backend/src/main/java/com/ody/route/service/RouteService.java b/backend/src/main/java/com/ody/route/service/RouteService.java
index af394d60f..0f55c154f 100644
--- a/backend/src/main/java/com/ody/route/service/RouteService.java
+++ b/backend/src/main/java/com/ody/route/service/RouteService.java
@@ -13,6 +13,8 @@
@RequiredArgsConstructor
public class RouteService {
+ private static final long CLOSEST_LOCATION_DURATION = 10L;
+
private final List routeClients;
private final ApiCallService apiCallService;
@@ -24,7 +26,8 @@ public RouteTime calculateRouteTime(Coordinates origin, Coordinates target) {
}
try {
- RouteTime routeTime = client.calculateRouteTime(origin, target);
+
+ RouteTime routeTime = calculateTime(client, origin, target);
apiCallService.increaseCountByClientType(client.getClientType());
log.info("{}를 사용한 소요 시간 계산 성공", client.getClass().getSimpleName());
return routeTime;
@@ -36,6 +39,14 @@ public RouteTime calculateRouteTime(Coordinates origin, Coordinates target) {
throw new OdyServerErrorException("서버에 장애가 발생했습니다.");
}
+ private RouteTime calculateTime(RouteClient client, Coordinates origin, Coordinates target) {
+ RouteTime calculatedRouteTime = client.calculateRouteTime(origin, target);
+ if (calculatedRouteTime.equals(RouteTime.CLOSEST_EXCEPTION_TIME)) {
+ return new RouteTime(CLOSEST_LOCATION_DURATION);
+ }
+ return calculatedRouteTime;
+ }
+
private boolean isDisabled(RouteClient client) {
return Boolean.FALSE.equals(apiCallService.getEnabledByClientType(client.getClientType()));
}
diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml
index 3f0769d79..e5f927fae 100644
--- a/backend/src/main/resources/application-dev.yml
+++ b/backend/src/main/resources/application-dev.yml
@@ -4,9 +4,9 @@ spring:
ddl-auto: validate
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
- url: ENC(KUJNgDMlvi/imeVHjCO2QekgI4DNxZT9IrW0QlVVkJXJUUQ2DAUOqI91FVH7aFhXVCsfXtf0EUmF/JqN3GpXa/mVj3o4BNbqS3QsU50BgwEEodko+VwHY2ebDHUxlKA1/FXBSmu6p+Rd3rcNEzoBIA==)
- username: ENC(jNYhZN6xMCmPQ7GZM+Hu3A==)
- password: ENC(VRtr3RjM+XnPvRdLjrPMHyTSzDlEjR7o)
+ url: ENC(EbLprih9CBRFwayAtzOuQgmLIi1yhY0XKl7h7Ol8ioyJdMtwwHN6caPxplZ7UXUCSCTYfUZ9MgMZ+BLdP2rka97Bx/vmtXo5ysJzdYLLzywI0XTylX4o5N8RQlk1Tx2s7vRI2khoJZ5p6zCdIBaJWw==)
+ username: ENC(Wd8cwBhFGAJp/RMvZzrOzw==)
+ password: ENC(X2Gk3hAja3lLSUyJLewcdVWZh3hTJDiJ)
flyway:
baseline-version: 2
diff --git a/backend/src/main/resources/application-prod.yml b/backend/src/main/resources/application-prod.yml
index 9fcc558e7..0b78f18c1 100644
--- a/backend/src/main/resources/application-prod.yml
+++ b/backend/src/main/resources/application-prod.yml
@@ -5,17 +5,17 @@ spring:
datasource:
write:
driver-class-name: com.mysql.cj.jdbc.Driver
- jdbc-url: jdbc:mysql://database-project.cluster-cqsc6pyqhwww.ap-northeast-2.rds.amazonaws.com:3306/ody
- username: ody
- password: ENC(2n3gUzzFjknXpHDYwuF4d5BQw6XqLdsX)
+ jdbc-url: ENC(/pYkPoMxvv/cFWWYEEIJ1/7gtMSjD9WKhM4oSIUEiK3WGRrcXZGELR8s+ozO5hmimO3U99cs3yMKf5B/5uh7RLk4wC3W1IuZ3F0oX3hSEJeq16YkxR1okYZokeE3mZXTxcOHBb3krNg=)
+ username: ENC(Pjtz7HDMEVm9ScNAjaDEqA==)
+ password: ENC(9yYOk4n/7co8h0HDfOA20tduvw3htCFa)
read:
driver-class-name: com.mysql.cj.jdbc.Driver
- jdbc-url: jdbc:mysql://database-project.cluster-ro-cqsc6pyqhwww.ap-northeast-2.rds.amazonaws.com:3306/ody
- username: ody
- password: ENC(2n3gUzzFjknXpHDYwuF4d5BQw6XqLdsX)
+ jdbc-url: ENC(Ho2WMst/QlNJngqZ1nnRiQIaytH55rjohNVg/5W14vhcd88rAhk16y1yO3zzorKNGSsYV95t/lChWuFA5ivEz4Rh5NaZHjxddhvs1T7HHltl/eC5V1bhPh3+ck98Lk2iil0+RljDy+I=)
+ username: ENC(Pjtz7HDMEVm9ScNAjaDEqA==)
+ password: ENC(9yYOk4n/7co8h0HDfOA20tduvw3htCFa)
flyway:
baseline-version: 2
-
+
log:
file:
path: /ody-prod-logs
diff --git a/backend/src/main/resources/common.yml b/backend/src/main/resources/common.yml
index a6c6e86c7..ecffabcfb 100644
--- a/backend/src/main/resources/common.yml
+++ b/backend/src/main/resources/common.yml
@@ -22,21 +22,21 @@ springdoc:
odsay:
url: https://api.odsay.com/v1/api/searchPubTransPathT
- api-key: ENC(4+ag02XbL8zS6rWoWHh7WzhKqBj5kaBwBcg04MpLr8nLJefO2PRY8E5o0WpRd3oRtvFv1s9lnWI=)
+ api-key: ENC(7am2oBS6Y/CCfThV+Yv59ZpjjIu8vOxt8v0/cEvGFDfUemAmSpbz+vfR3bsoDrKoJ/EwmeV6VXo=)
google:
maps:
- api-key: ENC(ISqnJHlmWWslx8mw0KghjXcJsTcxZ97AoylekuQiZN3RGyolV7oejAM3ejRtUllp)
+ api-key: ENC(B/qgWb19BUQN+qdCHQKP7Ocx4xszftoRM4DfcjSIxCXlwgpfvZW4QpFBMGbNqVsf)
auth:
- access-key: ENC(jp1TJCM6l22zIzInTUvt9VXrb1WhNm1zY3dFgTSj6ad3585f5K7Q8A==)
- access-expiration: ENC(RA/POzGT/Baq/ba9MxmMAQ==)
- refresh-key: ENC(5uLKuwtNdyXnr61LRqQJ6Q2sfbhjW5vxgLADB4Na8YgoIkLvcmJk+A==)
- refresh-expiration: ENC(GU+tXC1UeXlXFBe9y6IOwnZFtwmRj/a2)
+ access-key: ENC(Ve3QKOE7PzbTOpQ6b58Oyi1Qrr9DojPgUA+jCwYcmDdSVdP34Z2eKw==)
+ refresh-key: ENC(Ve3QKOE7PzbTOpQ6b58Oyi1Qrr9DojPgUA+jCwYcmDdSVdP34Z2eKw==)
+ access-expiration: 7200000
+ refresh-expiration: 2419200000
fcm:
config:
- admin-sdk: ENC(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)
+ admin-sdk: ENC(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)
jasypt:
encryptor:
@@ -44,7 +44,7 @@ jasypt:
kakao:
url: https://kapi.kakao.com/v1/user/unlink
- admin-key: ENC(b8lXOQquegCbdIgayVYIDBrhkNeyuucBArJScDGtEA2IWFiWeGUViiDhyZg0XpFE)
+ admin-key: ENC(84d9WaXe+iSolXJ9wIEbJmFEBzeLkgbcP5zkLBRCUJk/Yyic9vwITdVeRArDlFoQ)
management:
health:
@@ -52,7 +52,7 @@ management:
enabled: false
api-call:
- page: ENC(Tmip6pkMmuStDTjkRyspLm2PSd5/ov+T9Gzu2XZJKTjmLsrx4dplPIt/8CarbG2B)
+ page: ENC(bDtVlLbmI0dmF9v3SFpquBZiPStB9d13/2invvZIxOcs+UdEvexTOpTl+jtHzxjY)
allowed-origins:
- api-call: ENC(GwAP/KfLoZ6IqqnMPFpqqmg11CXjh1Al6oMlwB+YTekyHGFuRPn1789uAwyJKx2mPX5BRS4I/CoBn6HgnTJwjPL2hm/eHn7VHACtur1NdVc=)
+ api-call: ENC(QicQ16sDQJlHTuf3E/wIS+VBG0UVfQ62HDBXQbtfM8pmnYsBAH8xmACD7PBVgpLjWAHdJXN6bOKv/Z8BZFFbFyfp5x5f7dNCgok83Cn6jSE=)
diff --git a/backend/src/main/resources/db/migration/V7__add_notification_type_leave.sql b/backend/src/main/resources/db/migration/V7__add_notification_type_leave.sql
new file mode 100644
index 000000000..4384e98cd
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V7__add_notification_type_leave.sql
@@ -0,0 +1,3 @@
+ALTER TABLE notification DROP CONSTRAINT notification_chk_1;
+
+ALTER TABLE notification ADD CONSTRAINT notification_chk_1 CHECK (`type` IN ('DEPARTURE_REMINDER', 'ENTRY', 'NUDGE', 'LEAVE', 'MEMBER_DELETION', 'ETA_NOTICE'));
diff --git a/backend/src/test/java/com/ody/common/FixtureGenerator.java b/backend/src/test/java/com/ody/common/FixtureGenerator.java
index 8dd9f7975..2dbce5bcd 100644
--- a/backend/src/test/java/com/ody/common/FixtureGenerator.java
+++ b/backend/src/test/java/com/ody/common/FixtureGenerator.java
@@ -165,6 +165,16 @@ public Notification generateNotification(Mate mate, NotificationStatus notificat
));
}
+ public Notification generateNotification(Mate mate, LocalDateTime sendAt, NotificationStatus notificationStatus) {
+ return notificationRepository.save(new Notification(
+ mate,
+ NotificationType.ENTRY,
+ sendAt,
+ notificationStatus,
+ new FcmTopic(mate.getMeeting())
+ ));
+ }
+
public Notification generateNotification(
Mate mate,
NotificationType type,
diff --git a/backend/src/test/java/com/ody/mate/service/MateServiceTest.java b/backend/src/test/java/com/ody/mate/service/MateServiceTest.java
index f5b2980a9..81499cbcc 100644
--- a/backend/src/test/java/com/ody/mate/service/MateServiceTest.java
+++ b/backend/src/test/java/com/ody/mate/service/MateServiceTest.java
@@ -189,7 +189,7 @@ void unSubscribeTopicWhenDeleteMate() {
FcmTopic fcmTopic = new FcmTopic(mate.getMeeting());
DeviceToken deviceToken = mate.getMember().getDeviceToken();
- mateService.delete(mate);
+ mateService.withdraw(mate);
Mockito.verify(fcmSubscriber, Mockito.times(1)).unSubscribeTopic(fcmTopic, deviceToken);
}
@@ -208,4 +208,17 @@ void unSubscribeAllTopicsWhenDeleteMember() {
Mockito.verify(fcmSubscriber, Mockito.times(2)).unSubscribeTopic(any(FcmTopic.class), any(DeviceToken.class));
}
+
+ @DisplayName("약속에 참여하고 있는 mate를 약속 방에서 삭제한다.")
+ @Test
+ void deleteMateByMeetingIdAndMemberId() {
+ Meeting meeting = fixtureGenerator.generateMeeting();
+ Member kaki = fixtureGenerator.generateMember("kaki");
+ fixtureGenerator.generateMate(meeting, kaki);
+
+ mateService.leaveByMeetingIdAndMemberId(meeting.getId(), kaki.getId());
+
+ assertThatThrownBy(() -> mateService.findAllByMeetingIdIfMate(kaki, meeting.getId()))
+ .isInstanceOf(OdyNotFoundException.class);
+ }
}
diff --git a/backend/src/test/java/com/ody/notification/repository/NotificationRepositoryTest.java b/backend/src/test/java/com/ody/notification/repository/NotificationRepositoryTest.java
index 55a07761a..0a42f4acd 100644
--- a/backend/src/test/java/com/ody/notification/repository/NotificationRepositoryTest.java
+++ b/backend/src/test/java/com/ody/notification/repository/NotificationRepositoryTest.java
@@ -40,7 +40,7 @@ void findAllMeetingLogsBeforeThanEqual() {
NotificationStatus.PENDING
);
- List notifications = notificationRepository.findAllMeetingLogsBeforeThanEqual(
+ List notifications = notificationRepository.findAllByMeetingIdAndSentAtBeforeDateTimeAndStatusIsNotDismissed(
odyMeeting.getId(),
LocalDateTime.now()
);
@@ -80,8 +80,10 @@ void findAllNotificationsById() {
notificationRepository.save(pastNotification);
notificationRepository.save(futureNotification);
- List notifications = notificationRepository.findAllMeetingLogsBeforeThanEqual(odyMeeting.getId(),
- LocalDateTime.now());
+ List notifications = notificationRepository.findAllByMeetingIdAndSentAtBeforeDateTimeAndStatusIsNotDismissed(
+ odyMeeting.getId(),
+ LocalDateTime.now()
+ );
assertThat(notifications.size()).isOne();
}
diff --git a/backend/src/test/java/com/ody/notification/service/NotificationServiceTest.java b/backend/src/test/java/com/ody/notification/service/NotificationServiceTest.java
index 5bc575192..a3afa1f00 100644
--- a/backend/src/test/java/com/ody/notification/service/NotificationServiceTest.java
+++ b/backend/src/test/java/com/ody/notification/service/NotificationServiceTest.java
@@ -111,24 +111,20 @@ void sendSendNudgeMessageMessage() {
Mockito.verify(fcmPushSender, Mockito.times(1)).sendNudgeMessage(any(), any());
}
- @DisplayName("특정 참여자의 PENDING 상태인 알람을 모두 DISMISSED 상태로 변경한다.")
+ @DisplayName("특정 참여자의 전송 전 알람을 모두 DISMISSED 상태로 변경한다.")
@Test
void updateAllStatusPendingToDismissedByMateId() {
Mate mate = fixtureGenerator.generateMate();
- fixtureGenerator.generateNotification(mate, NotificationStatus.PENDING);
- fixtureGenerator.generateNotification(mate, NotificationStatus.DONE);
- fixtureGenerator.generateNotification(mate, NotificationStatus.DISMISSED);
- fixtureGenerator.generateNotification(mate, NotificationStatus.PENDING);
+ LocalDateTime now = LocalDateTime.now();
+ fixtureGenerator.generateNotification(mate, now.minusSeconds(1), NotificationStatus.DONE);
+ fixtureGenerator.generateNotification(mate, now.minusSeconds(1), NotificationStatus.PENDING);
+ fixtureGenerator.generateNotification(mate, now.plusSeconds(1), NotificationStatus.PENDING);
- notificationService.updateAllStatusPendingToDismissedByMateId(mate.getId());
- List actual = notificationRepository.findAll().stream()
- .map(Notification::getStatus)
- .toList();
+ notificationService.updateAllStatusToDismissByMateIdAndSendAtAfterNow(mate.getId());
- assertThat(actual).containsExactly(
- NotificationStatus.DISMISSED,
+ assertThat(notificationRepository.findAll()).extracting(Notification::getStatus).containsExactly(
NotificationStatus.DONE,
- NotificationStatus.DISMISSED,
+ NotificationStatus.PENDING,
NotificationStatus.DISMISSED
);
}
@@ -142,7 +138,7 @@ void findAllMeetingLogsOrderOfEntryAndDepartureNotification() {
notificationService.saveAndSendNotifications(savedPastMeeting, mate, member.getDeviceToken());
- NotiLogFindResponses allMeetingLogs = notificationService.findAllMeetingLogs(savedPastMeeting.getId());
+ NotiLogFindResponses allMeetingLogs = notificationService.findAllNotiLogs(savedPastMeeting.getId());
assertThat(allMeetingLogs.notiLog()).extracting(NotiLogFindResponse::type)
.containsExactly(NotificationType.ENTRY.name(), NotificationType.DEPARTURE_REMINDER.name());
@@ -157,10 +153,25 @@ void findAllMeetingLogsIncludingDeletedMember() {
fixtureGenerator.generateNotification(deleteMate);
fixtureGenerator.generateNotification(mate);
- int logCountBeforeDelete = notificationService.findAllMeetingLogs(meeting.getId()).notiLog().size();
+ int logCountBeforeDelete = notificationService.findAllNotiLogs(meeting.getId()).notiLog().size();
memberService.delete(deleteMate.getMember());
- int logCountAfterDelete = notificationService.findAllMeetingLogs(meeting.getId()).notiLog().size();
+ int logCountAfterDelete = notificationService.findAllNotiLogs(meeting.getId()).notiLog().size();
assertThat(logCountAfterDelete).isEqualTo(logCountBeforeDelete + 1);
}
+
+
+ @DisplayName("DISMISSED 상태의 알림은 조회되지 않는다.")
+ @Test
+ void findAllMeetingLogsExcludingDisMissedStatus() {
+ Meeting meeting = fixtureGenerator.generateMeeting();
+ Mate mate = fixtureGenerator.generateMate(meeting);
+ fixtureGenerator.generateNotification(mate, NotificationStatus.PENDING);
+ fixtureGenerator.generateNotification(mate, NotificationStatus.DONE);
+ fixtureGenerator.generateNotification(mate, NotificationStatus.DISMISSED);
+
+ List notiLogFindResponses = notificationService.findAllNotiLogs(meeting.getId()).notiLog();
+
+ assertThat(notiLogFindResponses).hasSize(2);
+ }
}
diff --git a/backend/src/test/java/com/ody/route/mapper/OdsayResponseMapperTest.java b/backend/src/test/java/com/ody/route/mapper/OdsayResponseMapperTest.java
index 7381093ac..d4abb56b5 100644
--- a/backend/src/test/java/com/ody/route/mapper/OdsayResponseMapperTest.java
+++ b/backend/src/test/java/com/ody/route/mapper/OdsayResponseMapperTest.java
@@ -26,7 +26,7 @@ void mapMinutesSuccess() {
.isEqualTo(3L);
}
- @DisplayName("출, 도착지가 700m 이내일 때 0분을 반환한다")
+ @DisplayName("출, 도착지가 700m 이내일 때 -1분을 반환한다")
@Test
void mapZeroMinutesWhenCloseLocation() {
OdsayResponse closeLocationResponse = new OdsayResponse(
@@ -36,7 +36,7 @@ void mapZeroMinutesWhenCloseLocation() {
);
assertThat(OdsayResponseMapper.mapMinutes(closeLocationResponse))
- .isEqualTo(0L);
+ .isEqualTo(-1L);
}
@DisplayName("예외 응답 : 500 에러 코드는 OdyServerErrorException을 던진다")
diff --git a/backend/src/test/java/com/ody/route/service/OdsayRouteClientTest.java b/backend/src/test/java/com/ody/route/service/OdsayRouteClientTest.java
index da1662981..b0b5943d6 100644
--- a/backend/src/test/java/com/ody/route/service/OdsayRouteClientTest.java
+++ b/backend/src/test/java/com/ody/route/service/OdsayRouteClientTest.java
@@ -39,7 +39,7 @@ void calculateRouteTimeSuccess() throws IOException {
mockServer.verify();
}
- @DisplayName("도착지와 출발지가 700m 이내일 때, 소요시간 0분을 반환한다")
+ @DisplayName("도착지와 출발지가 700m 이내일 때, 소요시간 -1분을 반환한다")
@Test
void calculateRouteTimeWithDistanceWithIn700m() throws IOException {
Coordinates origin = new Coordinates("37.505419", "127.050817");
@@ -49,7 +49,7 @@ void calculateRouteTimeWithDistanceWithIn700m() throws IOException {
RouteTime routeTime = routeClient.calculateRouteTime(origin, target);
- assertThat(routeTime.getMinutes()).isZero();
+ assertThat(routeTime.getMinutes()).isEqualTo(-1L);
mockServer.verify();
}
diff --git a/backend/src/test/java/com/ody/route/service/RouteServiceTest.java b/backend/src/test/java/com/ody/route/service/RouteServiceTest.java
index 024ad1bf4..ff4204f19 100644
--- a/backend/src/test/java/com/ody/route/service/RouteServiceTest.java
+++ b/backend/src/test/java/com/ody/route/service/RouteServiceTest.java
@@ -64,7 +64,30 @@ void calculateRouteTimeByOdsayRouteClient() {
() -> verify(apiCallService, times(1)).increaseCountByClientType(odsayRouteClient.getClientType()),
() -> verify(apiCallService, Mockito.never()).increaseCountByClientType(
googleRouteClient.getClientType())
+ );
+ }
+
+ @DisplayName("OdsayRouteClient에서 700m 이내라 소요시간 -1을 반환하면 10분으로 소요시간이 전환된다")
+ @Test
+ void calculateClosestDurationRouteTimeByOdsayRouteClient() {
+ Coordinates origin = new Coordinates("37.505419", "127.050817");
+ Coordinates target = new Coordinates("37.515253", "127.102895");
+
+ Mockito.when(odsayRouteClient.calculateRouteTime(origin, target))
+ .thenReturn(new RouteTime(-1));
+ Mockito.when(googleRouteClient.calculateRouteTime(origin, target))
+ .thenReturn(new RouteTime(18));
+
+ RouteTime result = routeService.calculateRouteTime(origin, target);
+ RouteTime expectRouteTime = new RouteTime(10);
+
+ assertAll(
+ () -> assertThat(result).isEqualTo(expectRouteTime),
+ () -> Mockito.verify(odsayRouteClient, Mockito.times(1)).calculateRouteTime(origin, target),
+ () -> Mockito.verifyNoInteractions(googleRouteClient),
+ () -> Mockito.verify(apiCallService, Mockito.times(1)).increaseCountByClientType(odsayRouteClient.getClientType()),
+ () -> Mockito.verify(apiCallService, Mockito.never()).increaseCountByClientType(googleRouteClient.getClientType())
);
}
@@ -85,8 +108,7 @@ void calculateRouteTimeByGoogleRouteClient() {
() -> assertThat(result).isEqualTo(expectRouteTime.getMinutes()),
() -> verify(odsayRouteClient, times(1)).calculateRouteTime(origin, target),
() -> verify(googleRouteClient, times(1)).calculateRouteTime(origin, target),
- () -> verify(apiCallService, Mockito.never()).increaseCountByClientType(
- odsayRouteClient.getClientType()),
+ () -> verify(apiCallService, Mockito.never()).increaseCountByClientType(odsayRouteClient.getClientType()),
() -> verify(apiCallService, times(1)).increaseCountByClientType(googleRouteClient.getClientType())
);
}