Feature/tracking gql add option to delete remote binding on tracker (#919)

* Extract unbinding track into function

* Introduce new unbind mutation

* Add option to delete track binding on track service

---------

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
This commit is contained in:
schroda
2024-03-31 19:22:13 +02:00
committed by GitHub
parent 9db612bf03
commit 16474d4328
10 changed files with 80 additions and 24 deletions

View File

@@ -1,5 +1,7 @@
package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
@@ -133,6 +135,36 @@ class TrackMutation {
}
}
data class UnbindTrackInput(
val clientMutationId: String? = null,
val recordId: Int,
@GraphQLDescription("This will only work if the tracker of the track record supports deleting tracks")
val deleteRemoteTrack: Boolean? = null,
)
data class UnbindTrackPayload(
val clientMutationId: String?,
val trackRecord: TrackRecordType?,
)
fun unbindTrack(input: UnbindTrackInput): CompletableFuture<UnbindTrackPayload> {
val (clientMutationId, recordId, deleteRemoteTrack) = input
return future {
Track.unbind(recordId, deleteRemoteTrack)
val trackRecord =
transaction {
TrackRecordTable.select {
TrackRecordTable.id eq recordId
}.firstOrNull()
}
UnbindTrackPayload(
clientMutationId,
trackRecord?.let { TrackRecordType(it) },
)
}
}
data class TrackProgressInput(
val clientMutationId: String? = null,
val mangaId: Int,
@@ -168,6 +200,7 @@ class TrackMutation {
val scoreString: String? = null,
val startDate: Long? = null,
val finishDate: Long? = null,
@GraphQLDeprecated("Replaced with \"unbindTrack\" mutation", replaceWith = ReplaceWith("unbindTrack"))
val unbind: Boolean? = null,
)

View File

@@ -20,6 +20,7 @@ class TrackerType(
val icon: String,
val isLoggedIn: Boolean,
val authUrl: String?,
val supportsTrackDeletion: Boolean?,
) : Node {
constructor(tracker: Tracker) : this(
tracker.isLoggedIn,
@@ -36,6 +37,7 @@ class TrackerType(
} else {
tracker.authUrl()
},
tracker.supportsTrackDeletion,
)
fun statuses(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<TrackStatusType>> {

View File

@@ -15,6 +15,7 @@ import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.manga.impl.track.tracker.DeletableTrackService
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrack
@@ -186,11 +187,29 @@ object Track {
}
}
suspend fun unbind(
recordId: Int,
deleteRemoteTrack: Boolean? = false,
) {
val recordDb =
transaction {
TrackRecordTable.select { TrackRecordTable.id eq recordId }.first()
}
val tracker = TrackerManager.getTracker(recordDb[TrackRecordTable.trackerId])!!
if (deleteRemoteTrack == true && tracker is DeletableTrackService) {
tracker.delete(recordDb.toTrack())
}
transaction {
TrackRecordTable.deleteWhere { TrackRecordTable.id eq recordId }
}
}
suspend fun update(input: UpdateInput) {
if (input.unbind == true) {
transaction {
TrackRecordTable.deleteWhere { TrackRecordTable.id eq input.recordId }
}
unbind(input.recordId)
return
}
val recordDb =

View File

@@ -6,5 +6,5 @@ import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
* For track services api that support deleting a manga entry for a user's list
*/
interface DeletableTrackService {
suspend fun delete(track: Track): Track
suspend fun delete(track: Track)
}

View File

@@ -17,6 +17,8 @@ abstract class Tracker(val id: Int, val name: String) {
// Application and remote support for reading dates
open val supportsReadingDates: Boolean = false
abstract val supportsTrackDeletion: Boolean
override fun toString() = "$name ($id) (isLoggedIn= $isLoggedIn, isAuthExpired= ${getIfAuthExpired()})"
abstract fun getLogo(): String

View File

@@ -1,7 +1,6 @@
package suwayomi.tachidesk.manga.impl.track.tracker.anilist
import android.annotation.StringRes
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mu.KotlinLogging
@@ -29,6 +28,8 @@ class Anilist(id: Int) : Tracker(id, "AniList"), DeletableTrackService {
const val POINT_3 = "POINT_3"
}
override val supportsTrackDeletion: Boolean = true
private val json: Json by injectLazy()
private val interceptor by lazy { AnilistInterceptor(this) }
@@ -157,13 +158,13 @@ class Anilist(id: Int) : Tracker(id, "AniList"), DeletableTrackService {
return api.updateLibManga(track)
}
override suspend fun delete(track: Track): Track {
override suspend fun delete(track: Track) {
if (track.library_id == null || track.library_id!! == 0L) {
val libManga = api.findLibManga(track, getUsername().toInt()) ?: return track
val libManga = api.findLibManga(track, getUsername().toInt()) ?: return
track.library_id = libManga.library_id
}
return api.deleteLibManga(track)
api.deleteLibManga(track)
}
override suspend fun bind(

View File

@@ -115,7 +115,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
suspend fun deleteLibManga(track: Track): Track {
suspend fun deleteLibManga(track: Track) {
return withIOContext {
val query =
"""
@@ -135,7 +135,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess()
track
}
}

View File

@@ -1,5 +1,6 @@
package suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates
import suwayomi.tachidesk.manga.impl.track.tracker.DeletableTrackService
import suwayomi.tachidesk.manga.impl.track.tracker.Tracker
import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.ListItem
import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.Rating
@@ -8,8 +9,7 @@ import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.toTrackSearc
import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
import suwayomi.tachidesk.manga.impl.track.tracker.model.TrackSearch
class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
// , DeletableTracker
class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates"), DeletableTrackService {
companion object {
const val READING_LIST = 0
const val WISH_LIST = 1
@@ -31,6 +31,8 @@ class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
}
}
override val supportsTrackDeletion: Boolean = true
private val interceptor by lazy { MangaUpdatesInterceptor(this) }
private val api by lazy { MangaUpdatesApi(interceptor, client) }
@@ -74,9 +76,9 @@ class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
return track
}
// override suspend fun delete(track: Track) {
// api.deleteSeriesFromList(track)
// }
override suspend fun delete(track: Track) {
api.deleteSeriesFromList(track)
}
override suspend fun bind(
track: Track,

View File

@@ -1,7 +1,6 @@
package suwayomi.tachidesk.manga.impl.track.tracker.myanimelist
import android.annotation.StringRes
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mu.KotlinLogging
@@ -26,6 +25,8 @@ class MyAnimeList(id: Int) : Tracker(id, "MyAnimeList"), DeletableTrackService {
private const val SEARCH_LIST_PREFIX = "my:"
}
override val supportsTrackDeletion: Boolean = true
private val json: Json by injectLazy()
private val interceptor by lazy { MyAnimeListInterceptor(this) }
@@ -94,8 +95,8 @@ class MyAnimeList(id: Int) : Tracker(id, "MyAnimeList"), DeletableTrackService {
return api.updateItem(track)
}
override suspend fun delete(track: Track): Track {
return api.deleteItem(track)
override suspend fun delete(track: Track) {
api.deleteItem(track)
}
override suspend fun bind(

View File

@@ -164,18 +164,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
suspend fun deleteItem(track: Track): Track {
suspend fun deleteItem(track: Track) {
return withIOContext {
val request =
Request.Builder()
.url(mangaUrl(track.media_id).toString())
.delete()
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
track
}
authClient.newCall(request)
.awaitSuccess()
}
}