Migrate Nextcloud News API to v1.3

This commit is contained in:
Shinokuni 2024-06-30 19:50:31 +02:00
parent a98ef5c449
commit 3f43d2219c
7 changed files with 38 additions and 72 deletions

View File

@ -1,9 +1,9 @@
package com.readrops.api.services package com.readrops.api.services
import com.readrops.api.services.freshrss.FreshRSSCredentials import com.readrops.api.services.freshrss.FreshRSSCredentials
import com.readrops.api.services.freshrss.FreshRSSService import com.readrops.api.services.freshrss.NewFreshRSSService
import com.readrops.api.services.nextcloudnews.NewNextcloudNewsService
import com.readrops.api.services.nextcloudnews.NextNewsCredentials import com.readrops.api.services.nextcloudnews.NextNewsCredentials
import com.readrops.api.services.nextcloudnews.NextNewsService
import com.readrops.db.entities.account.Account import com.readrops.db.entities.account.Account
import com.readrops.db.entities.account.AccountType import com.readrops.db.entities.account.AccountType
@ -23,8 +23,8 @@ abstract class Credentials(val authorization: String?, val url: String) {
private fun getEndPoint(accountType: AccountType): String { private fun getEndPoint(accountType: AccountType): String {
return when (accountType) { return when (accountType) {
AccountType.FRESHRSS -> FreshRSSService.END_POINT AccountType.FRESHRSS -> NewFreshRSSService.END_POINT
AccountType.NEXTCLOUD_NEWS -> NextNewsService.END_POINT AccountType.NEXTCLOUD_NEWS -> NewNextcloudNewsService.END_POINT
else -> throw IllegalArgumentException("Unknown account type") else -> throw IllegalArgumentException("Unknown account type")
} }
} }

View File

@ -8,7 +8,6 @@ import com.readrops.db.entities.Feed
import com.readrops.db.entities.Folder import com.readrops.db.entities.Folder
import com.readrops.db.entities.Item import com.readrops.db.entities.Item
import com.readrops.db.entities.account.Account import com.readrops.db.entities.account.Account
import com.readrops.db.pojo.StarItem
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -50,8 +49,7 @@ class NewNextcloudNewsDataSource(private val service: NewNextcloudNewsService) {
} else { } else {
listOf( listOf(
async { setItemsReadState(syncData) }, async { setItemsReadState(syncData) },
async { setItemsStarState(StateType.STAR, syncData.starredIds) }, async { setItemsStarState(syncData) },
async { setItemsStarState(StateType.UNSTAR, syncData.unstarredIds) }
).awaitAll() ).awaitAll()
SyncResult().apply { SyncResult().apply {
@ -108,32 +106,30 @@ class NewNextcloudNewsDataSource(private val service: NewNextcloudNewsService) {
if (unreadIds.isNotEmpty()) { if (unreadIds.isNotEmpty()) {
service.setReadState( service.setReadState(
StateType.UNREAD.name.lowercase(), StateType.UNREAD.name.lowercase(),
mapOf("items" to unreadIds) mapOf("itemIds" to unreadIds)
) )
} }
if (readIds.isNotEmpty()) { if (readIds.isNotEmpty()) {
service.setReadState( service.setReadState(
StateType.READ.name.lowercase(), StateType.READ.name.lowercase(),
mapOf("items" to readIds) mapOf("itemIds" to readIds)
) )
} }
} }
suspend fun setItemsStarState(stateType: StateType, itemIds: List<StarItem>) { suspend fun setItemsStarState(syncData: NextcloudNewsSyncData) = with(syncData) {
if (itemIds.isNotEmpty()) { if (starredIds.isNotEmpty()) {
val body = arrayListOf<Map<String, String>>()
for (item in itemIds) {
body += mapOf(
"feedId" to item.feedRemoteId,
"guidHash" to item.guidHash
)
}
service.setStarState( service.setStarState(
stateType.name.lowercase(), StateType.STAR.name.lowercase(),
mapOf("items" to body) mapOf("itemIds" to starredIds)
)
}
if (unstarredIds.isNotEmpty()) {
service.setStarState(
StateType.UNSTAR.name.lowercase(),
mapOf("itemIds" to unstarredIds)
) )
} }
} }

View File

@ -32,18 +32,18 @@ interface NewNextcloudNewsService {
@Query("type") type: Int @Query("type") type: Int
): List<Item> ): List<Item>
@PUT("items/{stateType}/multiple") @POST("items/{stateType}/multiple")
@JvmSuppressWildcards @JvmSuppressWildcards
suspend fun setReadState( suspend fun setReadState(
@Path("stateType") stateType: String, @Path("stateType") stateType: String,
@Body itemIdsMap: Map<String, List<Int>> @Body itemIdsMap: Map<String, List<Int>>
) )
@PUT("items/{starType}/multiple") @POST("items/{starType}/multiple")
@JvmSuppressWildcards @JvmSuppressWildcards
suspend fun setStarState( suspend fun setStarState(
@Path("starType") starType: String?, @Path("starType") starType: String?,
@Body body: Map<String?, List<Map<String, String>>> @Body body: Map<String, List<Int>>
) )
@POST("feeds") @POST("feeds")
@ -68,6 +68,6 @@ interface NewNextcloudNewsService {
suspend fun renameFolder(@Path("folderId") folderId: Int, @Body folderName: Map<String, String>) suspend fun renameFolder(@Path("folderId") folderId: Int, @Body folderName: Map<String, String>)
companion object { companion object {
const val END_POINT = "/index.php/apps/news/api/v1-2/" const val END_POINT = "/index.php/apps/news/api/v1-3/"
} }
} }

View File

@ -1,11 +1,9 @@
package com.readrops.api.services.nextcloudnews package com.readrops.api.services.nextcloudnews
import com.readrops.db.pojo.StarItem
data class NextcloudNewsSyncData( data class NextcloudNewsSyncData(
val lastModified: Long = 0, val lastModified: Long = 0,
val readIds: List<Int> = listOf(), val readIds: List<Int> = listOf(),
val unreadIds: List<Int> = listOf(), val unreadIds: List<Int> = listOf(),
val starredIds: List<StarItem> = listOf(), val starredIds: List<Int> = listOf(),
val unstarredIds: List<StarItem> = listOf(), val unstarredIds: List<Int> = listOf(),
) )

View File

@ -6,7 +6,6 @@ import com.readrops.api.enqueueOK
import com.readrops.api.enqueueStream import com.readrops.api.enqueueStream
import com.readrops.api.services.nextcloudnews.NextNewsDataSource.ItemQueryType import com.readrops.api.services.nextcloudnews.NextNewsDataSource.ItemQueryType
import com.readrops.db.entities.account.Account import com.readrops.db.entities.account.Account
import com.readrops.db.pojo.StarItem
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.Types import com.squareup.moshi.Types
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -251,8 +250,8 @@ class NextcloudNewsDataSourceTest : KoinTest {
val unreadBody = adapter.fromJson(unreadRequest.body)!! val unreadBody = adapter.fromJson(unreadRequest.body)!!
val readBody = adapter.fromJson(readRequest.body)!! val readBody = adapter.fromJson(readRequest.body)!!
assertEquals(data.readIds, readBody["items"]) assertEquals(data.readIds, readBody["itemIds"])
assertEquals(data.unreadIds, unreadBody["items"]) assertEquals(data.unreadIds, unreadBody["itemIds"])
} }
@Test @Test
@ -260,47 +259,27 @@ class NextcloudNewsDataSourceTest : KoinTest {
mockServer.enqueueOK() mockServer.enqueueOK()
mockServer.enqueueOK() mockServer.enqueueOK()
val starList = listOf( val data = NextcloudNewsSyncData(
StarItem("remote1", "guid1"), starredIds = listOf(15, 16, 17),
StarItem("remote2", "guid2") unstarredIds = listOf(18, 19, 20)
)
nextcloudNewsDataSource.setItemsStarState(
NewNextcloudNewsDataSource.StateType.STAR,
starList
) )
nextcloudNewsDataSource.setItemsStarState(data)
val starRequest = mockServer.takeRequest() val starRequest = mockServer.takeRequest()
val unstarList = listOf(
StarItem("remote3", "guid3"),
StarItem("remote4", "guid4")
)
nextcloudNewsDataSource.setItemsStarState(
NewNextcloudNewsDataSource.StateType.UNSTAR,
unstarList
)
val unstarRequest = mockServer.takeRequest() val unstarRequest = mockServer.takeRequest()
val type = val type =
Types.newParameterizedType( Types.newParameterizedType(
Map::class.java, Map::class.java,
String::class.java, String::class.java,
Types.newParameterizedType( Types.newParameterizedType(List::class.java, Int::class.javaObjectType)
List::class.java,
Types.newParameterizedType(
Map::class.java,
String::class.java,
String::class.java
)
)
) )
val adapter = moshi.adapter<Map<String, List<Map<String, String>>>>(type) val adapter = moshi.adapter<Map<String, List<Int>>>(type)
val starBody = adapter.fromJson(starRequest.body)!! val starBody = adapter.fromJson(starRequest.body)!!
val unstarBody = adapter.fromJson(unstarRequest.body)!! val unstarBody = adapter.fromJson(unstarRequest.body)!!
assertEquals(starList[0].feedRemoteId, starBody.values.first().first()["feedId"]) assertEquals(data.starredIds, starBody["itemIds"])
assertEquals(unstarList[0].feedRemoteId, unstarBody.values.first().first()["feedId"]) assertEquals(data.unstarredIds, unstarBody["itemIds"])
} }
} }

View File

@ -40,19 +40,16 @@ class NextcloudNewsRepository(
val itemStateChanges = database.newItemStateChangeDao() val itemStateChanges = database.newItemStateChangeDao()
.selectItemStateChanges(account.id) .selectItemStateChanges(account.id)
val starredIds = itemStateChanges.filter { it.starChange && it.starred }
.map { it.remoteId }
val unstarredIds = itemStateChanges.filter { it.starChange && !it.starred }
.map { it.remoteId }
val syncData = NextcloudNewsSyncData( val syncData = NextcloudNewsSyncData(
lastModified = account.lastModified, lastModified = account.lastModified,
readIds = itemStateChanges.filter { it.readChange && it.read } readIds = itemStateChanges.filter { it.readChange && it.read }
.map { it.remoteId.toInt() }, .map { it.remoteId.toInt() },
unreadIds = itemStateChanges.filter { it.readChange && !it.read } unreadIds = itemStateChanges.filter { it.readChange && !it.read }
.map { it.remoteId.toInt() }, .map { it.remoteId.toInt() },
starredIds = database.newItemDao().selectStarChanges(starredIds, account.id), starredIds = itemStateChanges.filter { it.starChange && it.starred }
unstarredIds = database.newItemDao().selectStarChanges(unstarredIds, account.id) .map { it.remoteId.toInt() },
unstarredIds = itemStateChanges.filter { it.starChange && !it.starred }
.map { it.remoteId.toInt() }
) )
val syncType = if (account.lastModified != 0L) { val syncType = if (account.lastModified != 0L) {

View File

@ -11,7 +11,6 @@ import com.readrops.db.entities.Folder
import com.readrops.db.entities.Item import com.readrops.db.entities.Item
import com.readrops.db.entities.ItemState import com.readrops.db.entities.ItemState
import com.readrops.db.pojo.ItemWithFeed import com.readrops.db.pojo.ItemWithFeed
import com.readrops.db.pojo.StarItem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -67,7 +66,4 @@ abstract class NewItemDao : NewBaseDao<Item> {
@Query("Select case When :guid In (Select guid From Item Inner Join Feed on Item.feed_id = Feed.id and account_id = :accountId) Then 1 else 0 end") @Query("Select case When :guid In (Select guid From Item Inner Join Feed on Item.feed_id = Feed.id and account_id = :accountId) Then 1 else 0 end")
abstract suspend fun itemExists(guid: String, accountId: Int): Boolean abstract suspend fun itemExists(guid: String, accountId: Int): Boolean
@Query("Select Item.guid, Feed.remoteId as feedRemoteId From Item Inner Join Feed On Item.feed_id = Feed.id Where Item.remoteId In (:remoteIds) And account_id = :accountId")
abstract suspend fun selectStarChanges(remoteIds: List<String>, accountId: Int): List<StarItem>
} }