Fix API contract: Response<ResponseBody> for endpoints returning JSON objects
- getBook, updateReadingProgress, updatePlaybackProgress: Response<String> → Response<ResponseBody> - Fixes kotlinx-serialization converter crash on JSON object responses - health(): Response<Unit> (already fixed) - Fix hasUriScheme(): matches() → containsMatchIn() - Full Russian translation of strings.xml (583 lines) - Russian added as default language in CoreData
This commit is contained in:
parent
efbd2aff66
commit
a7a9f65391
7 changed files with 468 additions and 446 deletions
|
|
@ -10,8 +10,9 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
import org.dueattendant149.bookshelf.core.language.Language
|
||||
|
||||
object CoreData {
|
||||
val defaultLanguage = Language.fromLanguageTag(languageTag = "en") // English
|
||||
val defaultLanguage = Language.fromLanguageTag(languageTag = "ru") // Russian
|
||||
val languages = listOf(
|
||||
Language.fromLanguageTag(languageTag = "ru"), // Russian
|
||||
Language.fromLanguageTag(languageTag = "en"), // English
|
||||
Language.fromLanguageTag(languageTag = "uk"), // Ukrainian
|
||||
Language.fromLanguageTag(languageTag = "de"), // German
|
||||
|
|
|
|||
|
|
@ -35,28 +35,31 @@ interface BookshelfApiService {
|
|||
|
||||
// Health
|
||||
@GET("health")
|
||||
suspend fun health(): Response<String>
|
||||
suspend fun health(): Response<Unit>
|
||||
|
||||
// Libraries / books
|
||||
@GET("api/v1/books/libraries")
|
||||
suspend fun getLibraries(): Response<List<LibraryResponse>>
|
||||
|
||||
@Streaming
|
||||
@GET("api/v1/books/{itemId}")
|
||||
suspend fun getBook(
|
||||
@Path("itemId") itemId: String,
|
||||
): Response<String>
|
||||
): Response<ResponseBody>
|
||||
|
||||
@Streaming
|
||||
@POST("api/v1/books/{itemId}/progress")
|
||||
suspend fun updateReadingProgress(
|
||||
@Path("itemId") itemId: String,
|
||||
@Body request: ProgressUpdateRequest,
|
||||
): Response<String>
|
||||
): Response<ResponseBody>
|
||||
|
||||
@Streaming
|
||||
@POST("api/v1/books/{itemId}/playback-progress")
|
||||
suspend fun updatePlaybackProgress(
|
||||
@Path("itemId") itemId: String,
|
||||
@Body request: PlaybackProgressUpdateRequest,
|
||||
): Response<String>
|
||||
): Response<ResponseBody>
|
||||
|
||||
@GET("api/v1/books/library/{libraryId}/search")
|
||||
suspend fun searchLibrary(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ package org.dueattendant149.bookshelf.data.settings
|
|||
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import org.dueattendant149.bookshelf.data.local.data_store.DataStore
|
||||
import org.dueattendant149.bookshelf.domain.util.ensureUriScheme
|
||||
import org.dueattendant149.bookshelf.domain.util.isValidUri
|
||||
import org.dueattendant149.bookshelf.domain.util.normalizeUri
|
||||
import javax.inject.Inject
|
||||
|
|
@ -47,11 +48,12 @@ class ServerSettings
|
|||
* or null when all credentials are present.
|
||||
*/
|
||||
suspend fun getMissingCredentialsError(): String? {
|
||||
val bookshelfUrl = dataStore.getNullableData(bookshelfUrlKey)
|
||||
val bookshelfUrl = dataStore.getNullableData(bookshelfUrlKey)?.trim()
|
||||
if (bookshelfUrl.isNullOrBlank()) {
|
||||
return "Bookshelf API URL is missing"
|
||||
}
|
||||
if (!bookshelfUrl.isValidUri()) {
|
||||
val normalizedBookshelfUrl = bookshelfUrl.ensureUriScheme()
|
||||
if (!normalizedBookshelfUrl.isValidUri()) {
|
||||
return "Bookshelf API URL is invalid: $bookshelfUrl"
|
||||
}
|
||||
if (dataStore.getNullableData(absUrlKey).isNullOrBlank()) {
|
||||
|
|
@ -67,8 +69,8 @@ class ServerSettings
|
|||
val url = dataStore.getNullableData(bookshelfUrlKey)?.trim()
|
||||
return when {
|
||||
url.isNullOrBlank() -> Result.failure(IllegalArgumentException("Bookshelf URL is empty"))
|
||||
!url.isValidUri() -> Result.failure(IllegalArgumentException("Bookshelf URL is invalid: $url"))
|
||||
else -> Result.success(url.normalizeUri())
|
||||
!url.ensureUriScheme().isValidUri() -> Result.failure(IllegalArgumentException("Bookshelf URL is invalid: $url"))
|
||||
else -> Result.success(url.ensureUriScheme().normalizeUri())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +82,8 @@ class ServerSettings
|
|||
|
||||
private fun String.normalizeIfValid(): String? {
|
||||
val trimmed = trim()
|
||||
return if (trimmed.isValidUri()) trimmed.normalizeUri() else null
|
||||
if (trimmed.isBlank()) return null
|
||||
val withScheme = trimmed.ensureUriScheme()
|
||||
return if (withScheme.isValidUri()) withScheme.normalizeUri() else null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ fun String.isValidUri(): Boolean {
|
|||
/**
|
||||
* Returns true if the string starts with http:// or https:// (case-insensitive).
|
||||
*/
|
||||
fun String.hasUriScheme(): Boolean = matches(URL_SCHEME_REGEX)
|
||||
fun String.hasUriScheme(): Boolean = URL_SCHEME_REGEX.containsMatchIn(this)
|
||||
|
||||
/**
|
||||
* Adds https:// if the string has no http/https scheme.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import org.dueattendant149.bookshelf.domain.model.remote.RemoteLibrary
|
|||
import org.dueattendant149.bookshelf.domain.use_case.remote.CheckHealthUseCase
|
||||
import org.dueattendant149.bookshelf.domain.use_case.remote.FetchLibrariesUseCase
|
||||
import org.dueattendant149.bookshelf.domain.util.deriveAbsUrl
|
||||
import org.dueattendant149.bookshelf.domain.util.ensureUriScheme
|
||||
import org.dueattendant149.bookshelf.domain.util.normalizeUri
|
||||
import javax.inject.Inject
|
||||
|
||||
data class BookshelfSettingsState(
|
||||
|
|
@ -108,8 +110,20 @@ class BookshelfSettingsModel
|
|||
fun save() {
|
||||
viewModelScope.launch {
|
||||
val current = _state.value
|
||||
serverSettings.setBookshelfUrl(current.bookshelfUrl)
|
||||
serverSettings.setAbsUrl(current.absUrl)
|
||||
val normalizedBookshelfUrl = current.bookshelfUrl.trim().let {
|
||||
if (it.isNotBlank()) it.ensureUriScheme().normalizeUri() else it
|
||||
}
|
||||
val normalizedAbsUrl = current.absUrl.trim().let {
|
||||
if (it.isNotBlank()) it.ensureUriScheme().normalizeUri() else it
|
||||
}
|
||||
_state.update {
|
||||
it.copy(
|
||||
bookshelfUrl = normalizedBookshelfUrl,
|
||||
absUrl = normalizedAbsUrl,
|
||||
)
|
||||
}
|
||||
serverSettings.setBookshelfUrl(normalizedBookshelfUrl)
|
||||
serverSettings.setAbsUrl(normalizedAbsUrl)
|
||||
serverSettings.setAbsToken(current.absToken)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ fun BookshelfSettingsContent(
|
|||
value = state.bookshelfUrl,
|
||||
onValueChange = model::updateBookshelfUrl,
|
||||
label = { Text(stringResource(R.string.bookshelf_url_option)) },
|
||||
placeholder = { Text("http://bookshelf-api:8073") },
|
||||
placeholder = { Text("https://tts.dueattendant149.org") },
|
||||
supportingText = { Text(stringResource(R.string.bookshelf_url_option_desc)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
|
@ -83,7 +83,7 @@ fun BookshelfSettingsContent(
|
|||
value = state.absUrl,
|
||||
onValueChange = model::updateAbsUrl,
|
||||
label = { Text(stringResource(R.string.abs_url_option)) },
|
||||
placeholder = { Text("http://192.168.1.119:13378") },
|
||||
placeholder = { Text("https://books.dueattendant149.org") },
|
||||
supportingText = { Text(stringResource(R.string.abs_url_option_desc)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue