Private
Public Access
1
0

[android] backend: normalize base url

This commit is contained in:
2026-04-12 11:26:38 -07:00
parent fd3660858e
commit 7056a7f836
5 changed files with 32 additions and 7 deletions

View File

@@ -5,7 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

1
android/.idea/vcs.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -24,7 +24,7 @@ data class ServerConfig(
fun loadFromSettings(context: Context): ServerConfig { fun loadFromSettings(context: Context): ServerConfig {
val prefs = getSharedPreferences(context) val prefs = getSharedPreferences(context)
return ServerConfig( return ServerConfig(
serverName = prefs.getString("serverName", null), serverName = prefs.getString("serverName", null).normalizedBaseUrl(),
authentication = ServerAuthentication.loadFromEncryptedSettings(context) authentication = ServerAuthentication.loadFromEncryptedSettings(context)
) )
} }
@@ -37,7 +37,7 @@ data class ServerConfig(
fun saveToSettings(context: Context) { fun saveToSettings(context: Context) {
val prefs = getSharedPreferences(context) val prefs = getSharedPreferences(context)
prefs.edit { prefs.edit {
putString("serverName", serverName) putString("serverName", serverName.normalizedBaseUrl())
apply() apply()
} }
@@ -45,6 +45,11 @@ data class ServerConfig(
} }
} }
fun String?.normalizedBaseUrl(): String? {
val value = this?.trim()?.takeIf { it.isNotEmpty() } ?: return null
return if (value.endsWith("/")) value else "$value/"
}
data class ServerAuthentication( data class ServerAuthentication(
val username: String, val username: String,
val password: String, val password: String,
@@ -101,7 +106,9 @@ class ServerConfigRepository @Inject constructor(
fun applyConfig(applicator: ServerConfig.() -> Unit) { fun applyConfig(applicator: ServerConfig.() -> Unit) {
val config = _serverConfig.value.copy() val config = _serverConfig.value.copy()
_serverConfig.value = config.apply(applicator) _serverConfig.value = config.apply(applicator).also {
it.serverName = it.serverName.normalizedBaseUrl()
}
_serverConfig.value.saveToSettings(context) _serverConfig.value.saveToSettings(context)
} }
} }

View File

@@ -104,8 +104,10 @@ class APIClientFactory {
return InvalidConfigurationAPIClient(InvalidConfigurationAPIClient.Issue.NOT_CONFIGURED) return InvalidConfigurationAPIClient(InvalidConfigurationAPIClient.Issue.NOT_CONFIGURED)
} }
val normalizedServerString = serverString.ensureTrailingSlash()
// Try to parse server string // Try to parse server string
val serverURL = HttpUrl.parse(serverString) val serverURL = HttpUrl.parse(normalizedServerString)
?: return InvalidConfigurationAPIClient(InvalidConfigurationAPIClient.Issue.INVALID_HOST_URL) ?: return InvalidConfigurationAPIClient(InvalidConfigurationAPIClient.Issue.INVALID_HOST_URL)
return RetrofitAPIClient(serverURL.url(), authentication) return RetrofitAPIClient(serverURL.url(), authentication)
@@ -113,6 +115,10 @@ class APIClientFactory {
} }
} }
private fun String.ensureTrailingSlash(): String {
return if (endsWith("/")) this else "$this/"
}
// TODO: Is this a dumb idea? // TODO: Is this a dumb idea?
class InvalidConfigurationAPIClient(val issue: Issue): APIClient { class InvalidConfigurationAPIClient(val issue: Issue): APIClient {
enum class Issue { enum class Issue {

View File

@@ -11,6 +11,7 @@ import net.buzzert.kordophone.backend.db.CachedChatDatabase
import net.buzzert.kordophone.backend.model.Message import net.buzzert.kordophone.backend.model.Message
import net.buzzert.kordophone.backend.model.OutgoingMessage import net.buzzert.kordophone.backend.model.OutgoingMessage
import net.buzzert.kordophone.backend.server.APIClient import net.buzzert.kordophone.backend.server.APIClient
import net.buzzert.kordophone.backend.server.APIClientFactory
import net.buzzert.kordophone.backend.server.APIInterface import net.buzzert.kordophone.backend.server.APIInterface
import net.buzzert.kordophone.backend.server.Authentication import net.buzzert.kordophone.backend.server.Authentication
import net.buzzert.kordophone.backend.server.ChatRepository import net.buzzert.kordophone.backend.server.ChatRepository
@@ -38,6 +39,16 @@ class BackendTests {
return Pair(repository, mockServer) return Pair(repository, mockServer)
} }
@Test
fun testCreateClientAcceptsBaseUrlWithoutTrailingSlash() {
val client = APIClientFactory.createClient(
"https://example.com/api",
Authentication("test", "test")
)
assertTrue(client.isConfigured)
}
@Test @Test
fun testGetVersion() = runBlocking { fun testGetVersion() = runBlocking {
val (repository, mockServer) = mockRepository() val (repository, mockServer) = mockRepository()