Compare commits

..

2 Commits

Author SHA1 Message Date
4b9899c04e Remove login information from update request
Removed the username and password in the GET /update request.
The endpoint does not require authentication and it is unsafe to transmit login credentials over GET requests.
2025-04-12 23:00:24 +02:00
e9e2b6415f Support SSE for source updates
This uses the Server Side Events added in Selfoss API 6.1.0 when calling GET /update
This change only supports the new response format but provides no change for the users.
The older versions are still supported.
2025-04-12 22:59:48 +02:00
7 changed files with 17 additions and 65 deletions

View File

@ -65,7 +65,6 @@ class UpsertSourceActivity :
private fun initFields(items: Map<String, SelfossModel.Spout>) { private fun initFields(items: Map<String, SelfossModel.Spout>) {
binding.nameInput.setText(existingSource!!.title) binding.nameInput.setText(existingSource!!.title)
binding.tags.setText(existingSource!!.tags?.joinToString(", ")) binding.tags.setText(existingSource!!.tags?.joinToString(", "))
binding.filter.setText(existingSource!!.filter)
binding.sourceUri.setText(existingSource!!.params?.url) binding.sourceUri.setText(existingSource!!.params?.url)
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout)) binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE
@ -170,7 +169,6 @@ class UpsertSourceActivity :
url, url,
mSpoutsValue!!, mSpoutsValue!!,
binding.tags.text.toString(), binding.tags.text.toString(),
binding.filter.text.toString(),
) )
} else { } else {
repository.createSource( repository.createSource(
@ -178,7 +176,6 @@ class UpsertSourceActivity :
url, url,
mSpoutsValue!!, mSpoutsValue!!,
binding.tags.text.toString(), binding.tags.text.toString(),
binding.filter.text.toString(),
) )
} }
if (successfullyAddedSource) { if (successfullyAddedSource) {

View File

@ -64,28 +64,15 @@
android:id="@+id/tags" android:id="@+id/tags"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:autofillHints="false" android:autofillHints="false"
android:hint="@string/add_source_hint_tags" android:hint="@string/add_source_hint_tags"
android:inputType="text" android:inputType="text"
android:minHeight="48dp"
android:textColorHint="?android:textColorPrimary" android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceUri" /> app:layout_constraintTop_toBottomOf="@+id/sourceUri" />
<EditText
android:id="@+id/filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_filter"
android:inputType="text"
android:minHeight="48dp"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tags" />
<Spinner <Spinner
android:id="@+id/spoutsSpinner" android:id="@+id/spoutsSpinner"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -93,7 +80,7 @@
android:minHeight="48dp" android:minHeight="48dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filter" /> app:layout_constraintTop_toBottomOf="@+id/tags" />
<Button <Button
android:id="@+id/saveBtn" android:id="@+id/saveBtn"

View File

@ -17,7 +17,6 @@
<string name="add_source_hint_tags">"Tag1, Tag2, Tag3"</string> <string name="add_source_hint_tags">"Tag1, Tag2, Tag3"</string>
<string name="add_source_hint_url">"Link"</string> <string name="add_source_hint_url">"Link"</string>
<string name="add_source_hint_name">"Name"</string> <string name="add_source_hint_name">"Name"</string>
<string name="add_source_hint_filter">/Filter/</string>
<string name="add_source">"Add a source"</string> <string name="add_source">"Add a source"</string>
<string name="add_source_save">"Save"</string> <string name="add_source_save">"Save"</string>
<string name="wrong_infos">"Check your details again."</string> <string name="wrong_infos">"Check your details again."</string>

View File

@ -44,8 +44,6 @@ private const val FEED_URL = "https://test.com/feed"
private const val TAGS = "Test, New" private const val TAGS = "Test, New"
private const val FILTER = "/filter/"
private const val NUMBER_ARTICLES = 100 private const val NUMBER_ARTICLES = 100
private const val NUMBER_UNREAD = 50 private const val NUMBER_UNREAD = 50
private const val NUMBER_STARRED = 20 private const val NUMBER_STARRED = 20
@ -838,7 +836,7 @@ class RepositoryTest {
@Test @Test
fun create_source() { fun create_source() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository() initializeRepository()
@ -850,7 +848,6 @@ class RepositoryTest {
FEED_URL, FEED_URL,
SPOUT, SPOUT,
TAGS, TAGS,
FILTER,
) )
} }
@ -860,7 +857,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(),
) )
} }
assertSame(true, response) assertSame(true, response)
@ -868,7 +864,7 @@ class RepositoryTest {
@Test @Test
fun create_source_but_response_fails() { fun create_source_but_response_fails() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(false) SuccessResponse(false)
initializeRepository() initializeRepository()
@ -880,7 +876,6 @@ class RepositoryTest {
FEED_URL, FEED_URL,
SPOUT, SPOUT,
TAGS, TAGS,
FILTER,
) )
} }
@ -890,7 +885,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(),
) )
} }
assertSame(false, response) assertSame(false, response)
@ -898,7 +892,7 @@ class RepositoryTest {
@Test @Test
fun create_source_without_connection() { fun create_source_without_connection() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository(false) initializeRepository(false)
@ -910,7 +904,6 @@ class RepositoryTest {
FEED_URL, FEED_URL,
SPOUT, SPOUT,
TAGS, TAGS,
FILTER,
) )
} }
@ -920,7 +913,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(),
) )
} }
assertSame(false, response) assertSame(false, response)

View File

@ -102,7 +102,6 @@ class SelfossModel {
override var error: String? = null, override var error: String? = null,
override var icon: String? = null, override var icon: String? = null,
var params: SourceParams? = null, var params: SourceParams? = null,
var filter: String? = null,
) : Source ) : Source
@Serializable @Serializable

View File

@ -368,7 +368,6 @@ class Repository(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): Boolean { ): Boolean {
var response = false var response = false
if (connectivityService.isNetworkAvailable()) { if (connectivityService.isNetworkAvailable()) {
@ -378,7 +377,6 @@ class Repository(
url, url,
spout, spout,
tags, tags,
filter,
).isSuccess == true ).isSuccess == true
} }
@ -391,11 +389,10 @@ class Repository(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): Boolean { ): Boolean {
var response = false var response = false
if (connectivityService.isNetworkAvailable()) { if (connectivityService.isNetworkAvailable()) {
response = api.updateSourceForVersion(id, title, url, spout, tags, filter).isSuccess == true response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
} }
return response return response

View File

@ -19,6 +19,7 @@ import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.sse.SSE
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.headers import io.ktor.client.request.headers
import io.ktor.client.request.parameter import io.ktor.client.request.parameter
@ -89,6 +90,7 @@ class SelfossApi(
} }
} }
} }
install(SSE)
expectSuccess = false expectSuccess = false
} }
@ -363,26 +365,11 @@ class SelfossApi(
suspend fun update(): StatusAndData<String> = suspend fun update(): StatusAndData<String> =
bodyOrFailure( bodyOrFailure(
client.tryToGet(url("/update")) { client.tryToGet(url("/update")) {
if (!shouldHavePostLogin()) { headers {
parameter("username", appSettingsService.getUserName()) append(
parameter("password", appSettingsService.getPassword()) HttpHeaders.Accept,
} "text/event-stream",
if (appSettingsService )
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
} }
}, },
) )
@ -638,13 +625,12 @@ class SelfossApi(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (appSettingsService.getApiVersion() > 1) { if (appSettingsService.getApiVersion() > 1) {
createSource("tags[]", title, url, spout, tags, filter) createSource("tags[]", title, url, spout, tags)
} else { } else {
createSource("tags", title, url, spout, tags, filter) createSource("tags", title, url, spout, tags)
}, },
) )
@ -654,7 +640,6 @@ class SelfossApi(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): HttpResponse? = ): HttpResponse? =
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source"), url = url("/source"),
@ -667,7 +652,6 @@ class SelfossApi(
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append("filter", filter)
append(tagsParamName, tags) append(tagsParamName, tags)
}, },
block = { block = {
@ -697,13 +681,12 @@ class SelfossApi(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (appSettingsService.getApiVersion() > 1) { if (appSettingsService.getApiVersion() > 1) {
updateSource(id, "tags[]", title, url, spout, tags, filter) updateSource(id, "tags[]", title, url, spout, tags)
} else { } else {
updateSource(id, "tags", title, url, spout, tags, filter) updateSource(id, "tags", title, url, spout, tags)
}, },
) )
@ -714,7 +697,6 @@ class SelfossApi(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String,
): HttpResponse? = ): HttpResponse? =
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source/$id"), url = url("/source/$id"),
@ -728,7 +710,6 @@ class SelfossApi(
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append(tagsParamName, tags) append(tagsParamName, tags)
append("filter", filter)
}, },
block = { block = {
if (appSettingsService if (appSettingsService