diff --git a/data/domain/src/main/java/com/ku_stacks/ku_ring/domain/Place.kt b/data/domain/src/main/java/com/ku_stacks/ku_ring/domain/Place.kt index 34b56a8e2..a925a60af 100644 --- a/data/domain/src/main/java/com/ku_stacks/ku_ring/domain/Place.kt +++ b/data/domain/src/main/java/com/ku_stacks/ku_ring/domain/Place.kt @@ -36,8 +36,18 @@ data class PlaceOperationHours( ) data class PlaceFacility( + val id: Long? = null, val name: String, val category: String, val location: String? = null, val operationHours: PlaceOperationHours? = null, + val imageUrl: String? = null, + val quantity: Int? = null, + val externalUrl: String? = null, +) + +data class PlaceCategory( + val name: String, + val korName: String, + val displayOrder: Int, ) diff --git a/data/place/build.gradle.kts b/data/place/build.gradle.kts index 48255a508..61c20d2cc 100644 --- a/data/place/build.gradle.kts +++ b/data/place/build.gradle.kts @@ -18,6 +18,7 @@ android { dependencies { implementation(projects.core.util) implementation(projects.data.domain) + implementation(projects.data.remote) implementation(projects.domain.place) testImplementation(libs.kotlinx.coroutines.test) diff --git a/data/place/src/main/java/com/ku_stacks/ku_ring/place/mapper/ResponseToDomain.kt b/data/place/src/main/java/com/ku_stacks/ku_ring/place/mapper/ResponseToDomain.kt new file mode 100644 index 000000000..2c35760e3 --- /dev/null +++ b/data/place/src/main/java/com/ku_stacks/ku_ring/place/mapper/ResponseToDomain.kt @@ -0,0 +1,61 @@ +package com.ku_stacks.ku_ring.place.mapper + +import com.ku_stacks.ku_ring.domain.Place +import com.ku_stacks.ku_ring.domain.PlaceCategory +import com.ku_stacks.ku_ring.domain.PlaceFacility +import com.ku_stacks.ku_ring.domain.PlaceOperationHours +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingDetailResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCampusPlaceResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCategoryResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceOperatingHoursResponse + +internal fun PlaceBuildingResponse.toDomain() = Place( + id = id.toString(), + name = name, + category = "", + address = address, + latitude = latitude, + longitude = longitude, + priority = Place.Priority.MIDDLE, +) + +internal fun PlaceBuildingDetailResponse.toDomain() = Place( + id = id.toString(), + name = name, + category = "", + address = address, + latitude = latitude, + longitude = longitude, + priority = Place.Priority.MIDDLE, + imageUrl = imageUrl, + operationHours = currentOperatingHours?.toDomain(), + facilities = campusPlaces.map { it.toDomain() }, +) + +internal fun PlaceCampusPlaceResponse.toDomain() = PlaceFacility( + name = name, + category = categoryKorName, + location = listOfNotNull(floor, locationDetail).joinToString(" ").ifBlank { null }, + operationHours = currentOperatingHours?.toDomain(), + id = id, + imageUrl = imageUrl, + quantity = quantity, + externalUrl = externalUrl, +) + +internal fun PlaceOperatingHoursResponse.toDomain() = PlaceOperationHours( + current = toDisplayText(), +) + +private fun PlaceOperatingHoursResponse.toDisplayText(): String = when (status) { + "OPEN_24_HOURS" -> "24시간 운영" + "CLOSED" -> "휴무" + else -> if (opensAt != null && closesAt != null) "$opensAt - $closesAt" else "-" +} + +internal fun PlaceCategoryResponse.toDomain() = PlaceCategory( + name = name, + korName = korName, + displayOrder = displayOrder, +) diff --git a/data/place/src/main/java/com/ku_stacks/ku_ring/place/repository/PlaceRepositoryImpl.kt b/data/place/src/main/java/com/ku_stacks/ku_ring/place/repository/PlaceRepositoryImpl.kt index c36713d19..6cafccd88 100644 --- a/data/place/src/main/java/com/ku_stacks/ku_ring/place/repository/PlaceRepositoryImpl.kt +++ b/data/place/src/main/java/com/ku_stacks/ku_ring/place/repository/PlaceRepositoryImpl.kt @@ -1,15 +1,63 @@ package com.ku_stacks.ku_ring.place.repository import com.ku_stacks.ku_ring.domain.Place +import com.ku_stacks.ku_ring.domain.PlaceCategory +import com.ku_stacks.ku_ring.domain.PlaceFacility import com.ku_stacks.ku_ring.domain.place.repository.PlaceRepository import com.ku_stacks.ku_ring.place.datasource.PlaceDataSource import com.ku_stacks.ku_ring.place.mapper.toDomain +import com.ku_stacks.ku_ring.remote.place.PlaceClient +import com.ku_stacks.ku_ring.util.suspendRunCatching import javax.inject.Inject class PlaceRepositoryImpl @Inject constructor( private val placeDataSource: PlaceDataSource, -): PlaceRepository { + private val placeClient: PlaceClient, +) : PlaceRepository { override suspend fun getPlaces(): List { return placeDataSource.getJsonPlaces().map { it.toDomain() } } + + override suspend fun getPlaceBuildingDetail(buildingId: Long): Result = + suspendRunCatching { + val response = placeClient.fetchBuildingDetail(buildingId) + when { + response.isSuccessAndDataExists -> response.data!!.toDomain() + else -> throw IllegalStateException(response.resultMsg) + } + } + + override suspend fun searchPlaceBuildings(keyword: String): Result> = + suspendRunCatching { + val response = placeClient.searchBuildings(keyword) + when { + response.isSuccessAndDataExists -> response.data!!.buildings.map { it.toDomain() } + else -> throw IllegalStateException(response.resultMsg) + } + } + + override suspend fun getPlaceBuildings(): Result> = suspendRunCatching { + val response = placeClient.fetchBuildings() + when { + response.isSuccessAndDataExists -> response.data!!.buildings.map { it.toDomain() } + else -> throw IllegalStateException(response.resultMsg) + } + } + + override suspend fun getPlaceCampusPlaces(category: String): Result> = + suspendRunCatching { + val response = placeClient.fetchCampusPlaces(category) + when { + response.isSuccessAndDataExists -> response.data!!.campusPlaces.map { it.toDomain() } + else -> throw IllegalStateException(response.resultMsg) + } + } + + override suspend fun getPlaceCategories(): Result> = suspendRunCatching { + val response = placeClient.fetchCategories() + when { + response.isSuccessAndDataExists -> response.data!!.categories.map { it.toDomain() } + else -> throw IllegalStateException(response.resultMsg) + } + } } diff --git a/data/place/src/test/java/com/ku_stacks/ku_ring/place/PlaceRepositoryTest.kt b/data/place/src/test/java/com/ku_stacks/ku_ring/place/PlaceRepositoryTest.kt index 3642a9570..68a58b3c9 100644 --- a/data/place/src/test/java/com/ku_stacks/ku_ring/place/PlaceRepositoryTest.kt +++ b/data/place/src/test/java/com/ku_stacks/ku_ring/place/PlaceRepositoryTest.kt @@ -6,6 +6,7 @@ import com.ku_stacks.ku_ring.domain.Place import com.ku_stacks.ku_ring.domain.place.repository.PlaceRepository import com.ku_stacks.ku_ring.place.datasource.PlaceDataSource import com.ku_stacks.ku_ring.place.repository.PlaceRepositoryImpl +import com.ku_stacks.ku_ring.remote.place.PlaceClient import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestDispatcher @@ -14,6 +15,7 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) @@ -21,6 +23,7 @@ class PlaceRepositoryTest { private lateinit var dataSource: PlaceDataSource private lateinit var repository: PlaceRepository private lateinit var testDispatcher: TestDispatcher + private val placeClient = Mockito.mock(PlaceClient::class.java) @Before fun setUp() { @@ -29,7 +32,7 @@ class PlaceRepositoryTest { testDispatcher = StandardTestDispatcher(testScheduler) dataSource = PlaceDataSource(context, testDispatcher) - repository = PlaceRepositoryImpl(dataSource) + repository = PlaceRepositoryImpl(dataSource, placeClient) } @Test diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceClient.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceClient.kt new file mode 100644 index 000000000..a39d3f30a --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceClient.kt @@ -0,0 +1,27 @@ +package com.ku_stacks.ku_ring.remote.place + +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingDetailResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingListResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCampusPlaceListResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCategoryListResponse +import com.ku_stacks.ku_ring.remote.util.DefaultResponse +import javax.inject.Inject + +class PlaceClient @Inject constructor( + private val placeService: PlaceService, +) { + suspend fun fetchBuildingDetail(buildingId: Long): DefaultResponse = + placeService.fetchBuildingDetail(buildingId) + + suspend fun searchBuildings(keyword: String): DefaultResponse = + placeService.searchBuildings(keyword) + + suspend fun fetchBuildings(): DefaultResponse = + placeService.fetchBuildings() + + suspend fun fetchCampusPlaces(category: String): DefaultResponse = + placeService.fetchCampusPlaces(category) + + suspend fun fetchCategories(): DefaultResponse = + placeService.fetchCategories() +} diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceService.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceService.kt new file mode 100644 index 000000000..ce1c2ea19 --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/PlaceService.kt @@ -0,0 +1,33 @@ +package com.ku_stacks.ku_ring.remote.place + +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingDetailResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceBuildingListResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCampusPlaceListResponse +import com.ku_stacks.ku_ring.remote.place.response.PlaceCategoryListResponse +import com.ku_stacks.ku_ring.remote.util.DefaultResponse +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface PlaceService { + @GET("v2/maps/buildings/{buildingId}") + suspend fun fetchBuildingDetail( + @Path("buildingId") buildingId: Long, + ): DefaultResponse + + @GET("v2/maps/buildings/search") + suspend fun searchBuildings( + @Query("keyword") keyword: String, + ): DefaultResponse + + @GET("v2/maps/buildings") + suspend fun fetchBuildings(): DefaultResponse + + @GET("v2/maps/campus-places") + suspend fun fetchCampusPlaces( + @Query("category") category: String, + ): DefaultResponse + + @GET("v2/maps/categories") + suspend fun fetchCategories(): DefaultResponse +} diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/di/PlaceModule.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/di/PlaceModule.kt new file mode 100644 index 000000000..06692174d --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/di/PlaceModule.kt @@ -0,0 +1,25 @@ +package com.ku_stacks.ku_ring.remote.place.di + +import com.ku_stacks.ku_ring.remote.place.PlaceClient +import com.ku_stacks.ku_ring.remote.place.PlaceService +import com.ku_stacks.ku_ring.remote.util.Default +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PlaceModule { + @Provides + @Singleton + fun providePlaceService(@Default retrofit: Retrofit): PlaceService = + retrofit.create(PlaceService::class.java) + + @Provides + @Singleton + fun providePlaceClient(placeService: PlaceService): PlaceClient = + PlaceClient(placeService) +} diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingDetailResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingDetailResponse.kt new file mode 100644 index 000000000..13b6c9348 --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingDetailResponse.kt @@ -0,0 +1,16 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceBuildingDetailResponse( + @SerialName("id") val id: Long, + @SerialName("name") val name: String, + @SerialName("address") val address: String, + @SerialName("latitude") val latitude: Double, + @SerialName("longitude") val longitude: Double, + @SerialName("imageUrl") val imageUrl: String? = null, + @SerialName("currentOperatingHours") val currentOperatingHours: PlaceOperatingHoursResponse? = null, + @SerialName("campusPlaces") val campusPlaces: List = emptyList(), +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingListResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingListResponse.kt new file mode 100644 index 000000000..79fe074c6 --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingListResponse.kt @@ -0,0 +1,9 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceBuildingListResponse( + @SerialName("buildings") val buildings: List, +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingResponse.kt new file mode 100644 index 000000000..5ae5b679f --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceBuildingResponse.kt @@ -0,0 +1,13 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceBuildingResponse( + @SerialName("id") val id: Long, + @SerialName("name") val name: String, + @SerialName("address") val address: String, + @SerialName("latitude") val latitude: Double, + @SerialName("longitude") val longitude: Double, +) \ No newline at end of file diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceListResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceListResponse.kt new file mode 100644 index 000000000..459999cea --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceListResponse.kt @@ -0,0 +1,9 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceCampusPlaceListResponse( + @SerialName("campusPlaces") val campusPlaces: List, +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceResponse.kt new file mode 100644 index 000000000..0ba02f3c8 --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCampusPlaceResponse.kt @@ -0,0 +1,19 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceCampusPlaceResponse( + @SerialName("id") val id: Long, + @SerialName("name") val name: String, + @SerialName("category") val category: String, + @SerialName("categoryKorName") val categoryKorName: String, + @SerialName("imageUrl") val imageUrl: String? = null, + @SerialName("locationType") val locationType: String, + @SerialName("floor") val floor: String? = null, + @SerialName("locationDetail") val locationDetail: String? = null, + @SerialName("quantity") val quantity: Int? = null, + @SerialName("currentOperatingHours") val currentOperatingHours: PlaceOperatingHoursResponse? = null, + @SerialName("externalUrl") val externalUrl: String? = null, +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryListResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryListResponse.kt new file mode 100644 index 000000000..3675291cf --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryListResponse.kt @@ -0,0 +1,9 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceCategoryListResponse( + @SerialName("categories") val categories: List, +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryResponse.kt new file mode 100644 index 000000000..370c8a55a --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceCategoryResponse.kt @@ -0,0 +1,11 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceCategoryResponse( + @SerialName("name") val name: String, + @SerialName("korName") val korName: String, + @SerialName("displayOrder") val displayOrder: Int, +) diff --git a/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceOperatingHoursResponse.kt b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceOperatingHoursResponse.kt new file mode 100644 index 000000000..8e69c5377 --- /dev/null +++ b/data/remote/src/main/java/com/ku_stacks/ku_ring/remote/place/response/PlaceOperatingHoursResponse.kt @@ -0,0 +1,13 @@ +package com.ku_stacks.ku_ring.remote.place.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PlaceOperatingHoursResponse( + @SerialName("period") val period: String, + @SerialName("dayGroup") val dayGroup: String, + @SerialName("status") val status: String, + @SerialName("opensAt") val opensAt: String? = null, + @SerialName("closesAt") val closesAt: String? = null, +) diff --git a/domain/place/src/main/java/com/ku_stacks/ku_ring/domain/place/repository/PlaceRepository.kt b/domain/place/src/main/java/com/ku_stacks/ku_ring/domain/place/repository/PlaceRepository.kt index 672d768c2..7febd56c3 100644 --- a/domain/place/src/main/java/com/ku_stacks/ku_ring/domain/place/repository/PlaceRepository.kt +++ b/domain/place/src/main/java/com/ku_stacks/ku_ring/domain/place/repository/PlaceRepository.kt @@ -1,7 +1,47 @@ package com.ku_stacks.ku_ring.domain.place.repository import com.ku_stacks.ku_ring.domain.Place +import com.ku_stacks.ku_ring.domain.PlaceCategory +import com.ku_stacks.ku_ring.domain.PlaceFacility interface PlaceRepository { suspend fun getPlaces(): List -} \ No newline at end of file + + /** + * 캠퍼스맵 건물의 상세 정보를 가져온다. + * + * @param buildingId 건물 ID + * @return 정상 처리 시 건물 상세 정보를 [Result]에 담아 반환. 실패 시 [Result.Failure] + */ + suspend fun getPlaceBuildingDetail(buildingId: Long): Result + + /** + * 키워드로 캠퍼스맵 건물을 검색한다. + * + * @param keyword 검색 키워드 + * @return 정상 처리 시 검색된 건물 목록을 [Result]에 담아 반환. 실패 시 [Result.Failure] + */ + suspend fun searchPlaceBuildings(keyword: String): Result> + + /** + * 캠퍼스맵 전체 건물 목록을 가져온다. + * + * @return 정상 처리 시 건물 목록을 [Result]에 담아 반환. 실패 시 [Result.Failure] + */ + suspend fun getPlaceBuildings(): Result> + + /** + * 카테고리에 해당하는 캠퍼스맵 시설 목록을 가져온다. + * + * @param category 시설 카테고리 + * @return 정상 처리 시 시설 목록을 [Result]에 담아 반환. 실패 시 [Result.Failure] + */ + suspend fun getPlaceCampusPlaces(category: String): Result> + + /** + * 캠퍼스맵 카테고리 목록을 가져온다. + * + * @return 정상 처리 시 카테고리 목록을 [Result]에 담아 반환. 실패 시 [Result.Failure] + */ + suspend fun getPlaceCategories(): Result> +}