Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
30e57f7
[feat]: 책 검색(실시간, 검색 완료) 구현 완료 (#67)
rbqks529 Aug 11, 2025
38357a0
[feat]: 가장 많이 검색된 책 구현 완료 (#67)
rbqks529 Aug 11, 2025
e8abc49
[feat]: 최근 검색어 로직 구현 ui 오류 수정 (#67)
rbqks529 Aug 11, 2025
ec08e2c
[feat]: 책 자세히보기 dto, service, repository 구현 (#67)
rbqks529 Aug 12, 2025
1b9f563
[feat]: 책 자세히 보기 viewModel 및 함수 연결 (#67)
rbqks529 Aug 12, 2025
0224822
[feat]: 책 자세히 보기 네비게이션 연결 (#67)
rbqks529 Aug 12, 2025
66c1e30
[feat]: 책 자세히 보기 Detail 화면 수정 (#67)
rbqks529 Aug 12, 2025
03163bf
[feat]: 검색 화면 날짜 로직 수정 (#67)
rbqks529 Aug 12, 2025
1ed12fd
[feat]: 책 저장 상태 변경 dto, service, repository 구현 (#67)
rbqks529 Aug 12, 2025
3cee27f
[feat]: 책 저장 상태 변경 구현 (#67)
rbqks529 Aug 12, 2025
10c99be
[refactor]: 책 검색 viewModel, 상태 수정 (#67)
rbqks529 Aug 12, 2025
911f282
[feat]: 책으로 모집 중인 모임방 Response, Service, Repository 구현 (#67)
rbqks529 Aug 12, 2025
a3b0ffe
[feat]: 책으로 모집 중인 모임방 네비게이션 연결 (#67)
rbqks529 Aug 12, 2025
394cfeb
[feat]: 책으로 모집 중인 모임방 viewModel 구현 (#67)
rbqks529 Aug 12, 2025
b2667d6
[feat]: 책으로 모집 중인 모임방 화면 연결 (#67)
rbqks529 Aug 12, 2025
c6bdff8
[refactor]: ViewModel에 넣었던 임의 delay 삭제 (#67)
rbqks529 Aug 12, 2025
5a895c6
[refactor]: 책 신청하기 화면 이동 로직 추가 (#67)
rbqks529 Aug 12, 2025
82bd113
[feat]: 모집중인 모임방으로 이동 로직 구현 (#67)
rbqks529 Aug 12, 2025
2223337
[feat]: Screen을 content로 바꾸기 완료 (#67)
rbqks529 Aug 12, 2025
97a2e26
[feat]: 책검색 화면에서 모임 만들기 화면으로 이동 네비게이션 구현 완료 (#67)
rbqks529 Aug 13, 2025
bd8ef45
[feat]: 책검색 화면에서 모임 만들기 화면으로 이동 구현 (#67)
rbqks529 Aug 13, 2025
61f7a47
[refactor]: SerialName 추가 (#67)
rbqks529 Aug 13, 2025
7159215
[refactor]: DTO에 기본 값 추가 (#67)
rbqks529 Aug 13, 2025
4e4e19c
[refactor]: PR 리뷰 반영 (#67)
rbqks529 Aug 13, 2025
52483ae
[refactor]: 바뀐 책검색 API 반영 (#67)
rbqks529 Aug 13, 2025
1ec924c
[refactor]: PR리뷰 반영 및 중복 UI제거 (#67)
rbqks529 Aug 13, 2025
f19cb8c
[refactor]: 무한스크롤 로직 수정 (#67)
rbqks529 Aug 13, 2025
d88b5bc
[refactor]: 무한 스크롤 로직 추가 수정 (#67)
rbqks529 Aug 13, 2025
1cd08cf
[refactor]: 레포지토리 패턴 수정 (#67)
rbqks529 Aug 13, 2025
6ce2ba2
[refactor]: viewModel 및 상태 수정 (#67)
rbqks529 Aug 13, 2025
a8f0863
[refactor]: 검색 완료 시 모드 플래그 해제 구현 (#67)
rbqks529 Aug 13, 2025
198ea7d
[refactor]: 코드 래빗 최신 pr 리뷰 반영 (#67)
rbqks529 Aug 13, 2025
0e57563
[feat]: network security 설정 (#67)
rbqks529 Aug 13, 2025
0851901
[fix]: GroupMainCard DTO 수정 (#67)
rbqks529 Aug 13, 2025
7042833
[fix]: scrollState 추가 (#67)
rbqks529 Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".ThipApplication"
android:allowBackup="true"
android:networkSecurityConfig="@xml/network_security_config"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

networkSecurityConfig 연결 자체는 OK. 다만 현재 설정(IP 기반)은 적용되지 않을 수 있으며, 빌드타입 분리 필요.

  • @xml/network_security_config에서 IP를 사용하는 domain-config는 매칭되지 않아 cleartext 허용 효과가 없을 가능성이 큽니다.
  • 디버그/릴리스 분리 권장:
    • debug: 전역 cleartext 허용(또는 개발 도메인만 허용)
    • release: cleartext 전면 금지 및 HTTPS 사용

검증을 위해 코드베이스에서 실제 사용 중인 엔드포인트를 확인해주세요.

다음 스크립트로 http 스킴 및 해당 IP 사용 여부를 검색할 수 있습니다:

릴리스 안전성 강화를 위해(선택):

  • main Manifest에서는 해당 속성을 제거하고(src/debug/AndroidManifest.xml에만 추가) 릴리스에선 HTTPS만 허용하도록 유지하는 것을 권장합니다.

릴리스에서 속성을 제거하는 변경 예시:

-        android:networkSecurityConfig="@xml/network_security_config"

🏁 Script executed:

#!/bin/bash
# 루트에서 실행. 코드 내 http/해당 IP 레퍼런스 검색
rg -n -S -A 2 $'http://|https://|3\\.37\\.87\\.117'

Length of output: 54199


AndroidManifest 네트워크 보안 설정 빌드타입별 분리 권장

현 설정(IP 기반 domain-config)이 실제 엔드포인트와 매칭되지 않아 cleartext 허용 효과가 없으며, 코드베이스 전체에 http:// 스킴·해당 IP 참조도 발견되지 않았습니다. 빌드타입별로 설정을 분리해 명확히 관리하세요.

  • app/src/main/res/xml/network_security_config.xml
    • <domain-config>3.37.87.117만 명시 → Android의 <domain>은 도메인 네임만 처리하므로 IP 주소 매칭 불가
  • 코드베이스 전체에서 http:// 스킴·3.37.87.117 직접 호출 없음
    • 실제 API 엔드포인트가 외부 설정(BuildConfig, 플레버, CI/CD 변수 등)으로 주입된다면 해당 경로도 확인 필요

조치 제안

  1. app/src/main/AndroidManifest.xml에서
    • android:networkSecurityConfig="@xml/network_security_config" 제거
    • src/debug/AndroidManifest.xml에만 추가
  2. app/src/main/res/xml/network_security_config.xml 수정
    • IP 대신 개발용 도메인(예: dev.example.com)을 <domain>에 명시하거나
    • <base-config cleartextTrafficPermitted="true"/>로 전역 cleartext 허용
    • 릴리스용은 <base-config cleartextTrafficPermitted="false"/> 유지
  3. 릴리스 빌드에서는 HTTPS 전용 사용 강제
    • 필요 시 <application android:usesCleartextTraffic="false"> 추가

android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/texthip/thip/data/manager/Genre.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ enum class Genre(
val networkApiCategory: String = apiCategory
) {
LITERATURE("literature", "문학"),
SCIENCE_IT("science_it", "과학·IT", "과학/IT"),
SCIENCE_IT("science_it", "과학·IT", "과학·IT"),
SOCIAL_SCIENCE("social_science", "사회과학"),
HUMANITIES("humanities", "인문학"),
ART("art", "예술");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.texthip.thip.data.model.book.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BookSaveRequest(
@SerialName("type") val type: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BookDetailResponse(
@SerialName("title") val title: String = "",
@SerialName("imageUrl") val imageUrl: String? = null,
@SerialName("authorName") val authorName: String = "",
@SerialName("publisher") val publisher: String = "",
@SerialName("isbn") val isbn: String = "",
@SerialName("description") val description: String = "",
@SerialName("recruitingRoomCount") val recruitingRoomCount: Int = 0,
@SerialName("readCount") val readCount: Int = 0,
@SerialName("isSaved") val isSaved: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import kotlinx.serialization.Serializable

@Serializable
data class BookListResponse(
@SerialName("bookList") val bookList: List<BookSavedResponse>
@SerialName("bookList") val bookList: List<BookSavedResponse> = emptyList()
)

@Serializable
data class BookSavedResponse(
@SerialName("isbn") val isbn: String,
@SerialName("bookTitle") val bookTitle: String,
@SerialName("authorName") val authorName: String,
@SerialName("publisher") val publisher: String,
@SerialName("imageUrl") val imageUrl: String?
@SerialName("isbn") val isbn: String = "",
@SerialName("bookTitle") val bookTitle: String = "",
@SerialName("authorName") val authorName: String = "",
@SerialName("publisher") val publisher: String = "",
@SerialName("imageUrl") val imageUrl: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BookSaveResponse(
@SerialName("isbn") val isbn: String = "",
@SerialName("isSaved") val isSaved: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BookSearchResponse(
@SerialName("searchResult") val searchResult: List<BookSearchItem> = emptyList(),
@SerialName("page") val page: Int = 0,
@SerialName("size") val size: Int = 0,
@SerialName("totalElements") val totalElements: Int = 0,
@SerialName("totalPages") val totalPages: Int = 0,
@SerialName("last") val last: Boolean = true,
@SerialName("first") val first: Boolean = true
)

@Serializable
data class BookSearchItem(
@SerialName("title") val title: String = "",
@SerialName("imageUrl") val imageUrl: String? = null,
@SerialName("authorName") val authorName: String = "",
@SerialName("publisher") val publisher: String = "",
@SerialName("isbn") val isbn: String = ""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class MostSearchedBooksResponse(
@SerialName("bookList") val bookList: List<PopularBookItem> = emptyList()
)

@Serializable
data class PopularBookItem(
@SerialName("rank") val rank: Int = 0,
@SerialName("title") val title: String = "",
@SerialName("imageUrl") val imageUrl: String? = null,
@SerialName("isbn") val isbn: String = ""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RecentSearchResponse(
@SerialName("recentSearchList") val recentSearchList: List<RecentSearchItem> = emptyList()
)

@Serializable
data class RecentSearchItem(
@SerialName("recentSearchId") val recentSearchId: Int = 0,
@SerialName("searchTerm") val searchTerm: String = ""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.texthip.thip.data.model.book.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class RecruitingRoomsResponse(
@SerialName("recruitingRoomList") val recruitingRoomList: List<RecruitingRoomItem> = emptyList(),
@SerialName("totalRoomCount") val totalRoomCount: Int = 0,
@SerialName("nextCursor") val nextCursor: String? = null,
@SerialName("isLast") val isLast: Boolean = true
)

@Serializable
data class RecruitingRoomItem(
@SerialName("roomId") val roomId: Int = 0,
@SerialName("bookImageUrl") val bookImageUrl: String? = null,
@SerialName("roomName") val roomName: String = "",
@SerialName("memberCount") val memberCount: Int = 0,
@SerialName("recruitCount") val recruitCount: Int = 0,
@SerialName("deadlineEndDate") val deadlineEndDate: String = ""
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class JoinedRoomListResponse(
data class JoinedRoomResponse(
@SerialName("roomId") val roomId: Int,
@SerialName("bookImageUrl") val bookImageUrl: String?,
@SerialName("bookTitle") val bookTitle: String,
@SerialName("roomTitle") val roomTitle: String,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

서버 호환성(역직렬화) 위험: bookTitle → roomTitle 변경 시 구버전 응답과 불일치 가능

서버가 아직 "bookTitle"을 내려주는 경우 앱 크래시(누락 필드) 또는 역직렬화 실패가 발생할 수 있습니다. 구·신 규격을 모두 수용하도록 fallback을 추가하는 것을 권장합니다. 또한 안전성을 위해 기본값을 지정하면 런타임 오류를 줄일 수 있습니다.

권장 변경안:

 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonNames

 @Serializable
 data class JoinedRoomResponse(
     @SerialName("roomId") val roomId: Int,
     @SerialName("bookImageUrl") val bookImageUrl: String?,
-    @SerialName("roomTitle") val roomTitle: String,
+    @SerialName("roomTitle")
+    @JsonNames("bookTitle")
+    val roomTitle: String = "",
     @SerialName("memberCount") val memberCount: Int,
     @SerialName("userPercentage") val userPercentage: Int
 )

검증 스크립트(레포 내부에서 실행):


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) 코드베이스에서 남아있는 'bookTitle' 사용 위치 확인:"
rg -n --hidden --glob '!.git' --glob '!**/build/**' '\bbookTitle\b' -A 2

echo
echo "2) kotlinx-serialization 버전 확인(JsonNames 지원 여부 점검):"
rg -n --hidden --glob '!.git' --glob '!**/build/**' -e 'kotlinx-serialization' -e 'serialization' -e 'kotlinx.*serialization.*json' -A 2

Length of output: 24376


group 응답 모델 roomTitle 필드에 구버전 bookTitle 하위 호환 처리 필요

JoinedRoomListResponse.kt에서 서버가 여전히 bookTitle을 내려줄 경우 역직렬화 오류가 발생할 수 있으므로, 다음과 같이 fallback 및 기본값을 설정하세요.

• 파일:
app/src/main/java/com/texthip/thip/data/model/group/response/JoinedRoomListResponse.kt

• 수정 예시(diff):

 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonNames

 @Serializable
 data class JoinedRoomListResponse(
     @SerialName("roomId") val roomId: Int,
     @SerialName("bookImageUrl") val bookImageUrl: String?,
-    @SerialName("roomTitle") val roomTitle: String,
+    @SerialName("roomTitle")
+    @JsonNames("bookTitle")
+    val roomTitle: String = "",
     @SerialName("memberCount") val memberCount: Int,
     @SerialName("userPercentage") val userPercentage: Int
 )

• 추가 확인 사항
gradle/libs.versions.toml에서 kotlinx-serialization-json 버전이 1.3.0 이상인지 확인하여 @JsonNames 지원 여부를 보장하세요.
– 다른 그룹/룸 관련 응답 모델에도 동일한 필드명이 변경된 곳이 있는지 검토하고, 필요 시 동일한 패턴으로 하위 호환 처리를 적용하세요.

@SerialName("memberCount") val memberCount: Int,
@SerialName("userPercentage") val userPercentage: Int
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package com.texthip.thip.data.repository

import com.texthip.thip.data.model.base.handleBaseResponse
import com.texthip.thip.data.model.book.request.BookSaveRequest
import com.texthip.thip.data.model.book.response.BookDetailResponse
import com.texthip.thip.data.model.book.response.BookSaveResponse
import com.texthip.thip.data.model.book.response.BookSavedResponse
import com.texthip.thip.data.model.book.response.BookSearchResponse
import com.texthip.thip.data.model.book.response.MostSearchedBooksResponse
import com.texthip.thip.data.model.book.response.RecentSearchResponse
import com.texthip.thip.data.model.book.response.RecruitingRoomsResponse
import com.texthip.thip.data.service.BookService
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -11,10 +19,59 @@ class BookRepository @Inject constructor(
) {

/** 저장된 책 또는 모임 책 목록 조회 */
suspend fun getBooks(type: String) = runCatching {
suspend fun getBooks(type: String): Result<List<BookSavedResponse>> = runCatching {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에서 BookSavedResponse를 List로 감싸기보다는 애초에 List로 감싸진걸 BookSavedResponse로 만드는건 어떤가요?

bookService.getBooks(type)
.handleBaseResponse()
.getOrThrow()
?.bookList ?: emptyList()
}

/** 책 검색 */
suspend fun searchBooks(keyword: String, page: Int = 1, isFinalized: Boolean = false): Result<BookSearchResponse?> = runCatching {
bookService.searchBooks(keyword, page, isFinalized)
.handleBaseResponse()
.getOrThrow()
}

/** 인기 책 조회 */
suspend fun getMostSearchedBooks(): Result<MostSearchedBooksResponse?> = runCatching {
bookService.getMostSearchedBooks()
.handleBaseResponse()
.getOrThrow()
}

/** 최근 검색어 조회 */
suspend fun getRecentSearches(type: String = "BOOK"): Result<RecentSearchResponse?> = runCatching {
bookService.getRecentSearches(type)
.handleBaseResponse()
.getOrThrow()
}

/** 최근 검색어 삭제 */
suspend fun deleteRecentSearch(recentSearchId: Int): Result<Unit> = runCatching {
bookService.deleteRecentSearch(recentSearchId)
.handleBaseResponse()
.getOrThrow()
}

/** 책 상세 조회 */
suspend fun getBookDetail(isbn: String): Result<BookDetailResponse?> = runCatching {
bookService.getBookDetail(isbn)
.handleBaseResponse()
.getOrThrow()
}

/** 책 저장/저장취소 */
suspend fun saveBook(isbn: String, type: Boolean): Result<BookSaveResponse?> = runCatching {
bookService.saveBook(isbn, BookSaveRequest(type))
.handleBaseResponse()
.getOrThrow()
}

/** 모집중인 방 조회 */
suspend fun getRecruitingRooms(isbn: String, cursor: String? = null): Result<RecruitingRoomsResponse?> = runCatching {
bookService.getRecruitingRooms(isbn, cursor)
.handleBaseResponse()
.getOrThrow()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,26 @@ class GroupRepository @Inject constructor(
suspend fun getRoomRecruiting(roomId: Int): Result<RoomRecruitingResponse> = runCatching {
groupService.getRoomRecruiting(roomId)
.handleBaseResponse()
.getOrThrow()!!
.getOrThrow()
?: throw NoSuchElementException("모집중인 모임방 정보를 찾을 수 없습니다.")
}

/** 새 모임방 생성 */
suspend fun createRoom(request: CreateRoomRequest): Result<Int> = runCatching {
groupService.createRoom(request)
val response = groupService.createRoom(request)
.handleBaseResponse()
.getOrThrow()!!
.roomId
.getOrThrow()
?: throw NoSuchElementException("모임방 생성 응답을 받을 수 없습니다.")
response.roomId
}

/** 모임방 참여 또는 취소 */
suspend fun joinOrCancelRoom(roomId: Int, type: String): Result<String> = runCatching {
val request = RoomJoinRequest(type = type)
groupService.joinOrCancelRoom(roomId, request)
val response = groupService.joinOrCancelRoom(roomId, request)
.handleBaseResponse()
.getOrThrow()!!
.type
.getOrThrow()
?: throw NoSuchElementException("모임방 참여/취소 응답을 받을 수 없습니다.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 .handleBaseResponse().getOrThrow()이렇게 수정부탁드립니둥 ~

response.type
}
}
55 changes: 55 additions & 0 deletions app/src/main/java/com/texthip/thip/data/service/BookService.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package com.texthip.thip.data.service

import com.texthip.thip.data.model.base.BaseResponse
import com.texthip.thip.data.model.book.request.BookSaveRequest
import com.texthip.thip.data.model.book.response.BookDetailResponse
import com.texthip.thip.data.model.book.response.BookListResponse
import com.texthip.thip.data.model.book.response.BookSaveResponse
import com.texthip.thip.data.model.book.response.BookSearchResponse
import com.texthip.thip.data.model.book.response.MostSearchedBooksResponse
import com.texthip.thip.data.model.book.response.RecentSearchResponse
import com.texthip.thip.data.model.book.response.RecruitingRoomsResponse
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query

interface BookService {
Expand All @@ -12,4 +23,48 @@ interface BookService {
suspend fun getBooks(
@Query("type") type: String
): BaseResponse<BookListResponse>

/** 책 검색 */
@GET("books")
suspend fun searchBooks(
@Query("keyword") keyword: String,
@Query("page") page: Int = 1,
@Query("isFinalized") isFinalized: Boolean = false
): BaseResponse<BookSearchResponse>

/** 인기 책 조회 */
@GET("books/most-searched")
suspend fun getMostSearchedBooks(): BaseResponse<MostSearchedBooksResponse>

/** 최근 검색어 조회 */
@GET("recent-searches")
suspend fun getRecentSearches(
@Query("type") type: String
): BaseResponse<RecentSearchResponse>

/** 최근 검색어 삭제 */
@DELETE("recent-searches/{recentSearchId}")
suspend fun deleteRecentSearch(
@Path("recentSearchId") recentSearchId: Int
): BaseResponse<Unit>

/** 책 상세 조회 */
@GET("books/{isbn}")
suspend fun getBookDetail(
@Path("isbn") isbn: String
): BaseResponse<BookDetailResponse>

/** 책 저장/저장취소 */
@POST("books/{isbn}/saved")
suspend fun saveBook(
@Path("isbn") isbn: String,
@Body request: BookSaveRequest
): BaseResponse<BookSaveResponse>

/** 모집중인 방 조회 */
@GET("books/{isbn}/recruiting-rooms")
suspend fun getRecruitingRooms(
@Path("isbn") isbn: String,
@Query("cursor") cursor: String? = null
): BaseResponse<RecruitingRoomsResponse>
}
Loading