Compare commits

...

2 Commits

Author SHA1 Message Date
schroda
323d58717e Add mutation to bind existing track record (#2045)
Makes it possible to copy a bound track record to another manga.
This is necessary during a migration to prevent spamming the actual tracker and causing 429 errors

closes #2033
2026-06-29 14:59:53 -04:00
Mitchell Syer
4d7b7617a9 Fix backup corruption with new extension lib (#2146)
* Fix backup corruption with new extension lib

* Fix missing chapter memo backup
2026-06-29 14:52:37 -04:00
4 changed files with 79 additions and 4 deletions

View File

@@ -148,6 +148,36 @@ class TrackMutation {
} }
} }
data class BindTrackRecordInput(
val clientMutationId: String? = null,
val mangaId: Int,
val trackRecordId: Int,
)
data class BindTrackRecordPayload(
val clientMutationId: String?,
val trackRecord: TrackRecordType,
)
@RequireAuth
fun bindTrackRecord(input: BindTrackRecordInput): CompletableFuture<BindTrackRecordPayload?> {
val (clientMutationId, mangaId, trackRecordId) = input
return future {
val boundTrackRecordId = Track.bindTrackRecord(mangaId, trackRecordId)
val trackRecord =
transaction {
TrackRecordTable.selectAll().where { TrackRecordTable.id eq boundTrackRecordId }.first()
}
BindTrackRecordPayload(
clientMutationId,
TrackRecordType(trackRecord),
)
}
}
data class FetchTrackInput( data class FetchTrackInput(
val clientMutationId: String? = null, val clientMutationId: String? = null,
val recordId: Int, val recordId: Int,

View File

@@ -238,6 +238,7 @@ object Manga {
it[MangaTable.lastFetchedAt] = Instant.now().epochSecond it[MangaTable.lastFetchedAt] = Instant.now().epochSecond
it[MangaTable.updateStrategy] = sManga.update_strategy.name it[MangaTable.updateStrategy] = sManga.update_strategy.name
it[MangaTable.memo] = Json.encodeToString(sManga.memo)
} }
} }

View File

@@ -8,7 +8,6 @@ package suwayomi.tachidesk.manga.impl.backup.proto.handlers
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.v1.core.ResultRow import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.SortOrder import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
@@ -78,7 +77,7 @@ object BackupMangaHandler {
lastModifiedAt = mangaRow[MangaTable.lastModifiedAt], lastModifiedAt = mangaRow[MangaTable.lastModifiedAt],
version = mangaRow[MangaTable.version], version = mangaRow[MangaTable.version],
initialized = mangaRow[MangaTable.initialized], initialized = mangaRow[MangaTable.initialized],
memo = Json.encodeToString(mangaRow[MangaTable.memo]).encodeToByteArray(), memo = mangaRow[MangaTable.memo].encodeToByteArray(),
) )
val mangaId = mangaRow[MangaTable.id].value val mangaId = mangaRow[MangaTable.id].value
@@ -116,6 +115,7 @@ object BackupMangaHandler {
sourceOrder = chapters.size - it[ChapterTable.sourceOrder], sourceOrder = chapters.size - it[ChapterTable.sourceOrder],
lastModifiedAt = it[ChapterTable.lastModifiedAt], lastModifiedAt = it[ChapterTable.lastModifiedAt],
version = it[ChapterTable.version], version = it[ChapterTable.version],
memo = it[ChapterTable.memo].encodeToByteArray(),
).apply { ).apply {
if (flags.includeClientData) { if (flags.includeClientData) {
this.meta = chapterToMeta[it[ChapterTable.id].value] ?: emptyMap() this.meta = chapterToMeta[it[ChapterTable.id].value] ?: emptyMap()

View File

@@ -222,6 +222,48 @@ object Track {
} }
} }
fun bindTrackRecord(
mangaId: Int,
trackRecordId: Int,
): Int {
val (trackRecord, existingTrackRecord) =
transaction {
val trackRecord =
TrackRecordTable
.selectAll()
.where {
(TrackRecordTable.id eq trackRecordId)
}.first()
.toTrackRecordDataClass()
val existingTrackRecord =
TrackRecordTable
.selectAll()
.where {
(TrackRecordTable.mangaId eq mangaId) and (TrackRecordTable.trackerId eq trackRecord.trackerId)
}.firstOrNull()
?.toTrackRecordDataClass()
trackRecord to existingTrackRecord
}
val isAlreadyBoundToManga = trackRecord.mangaId == mangaId
if (isAlreadyBoundToManga) {
return trackRecordId
}
val hasRecordForTracker = existingTrackRecord != null
if (hasRecordForTracker) {
val updatedTrack = trackRecord.copy(id = existingTrackRecord.id, mangaId = mangaId).toTrack()
return updateTrackRecord(updatedTrack)
}
val newTrack = trackRecord.copy(mangaId = mangaId).toTrack()
return insertTrackRecord(newTrack)
}
suspend fun refresh(recordId: Int) { suspend fun refresh(recordId: Int) {
val recordDb = val recordDb =
transaction { transaction {
@@ -423,9 +465,9 @@ object Track {
} }
} }
fun updateTrackRecord(track: Track) = updateTrackRecords(listOf(track)) fun updateTrackRecord(track: Track): Int = updateTrackRecords(listOf(track)).first()
fun updateTrackRecords(tracks: List<Track>) = fun updateTrackRecords(tracks: List<Track>): List<Int> =
transaction { transaction {
if (tracks.isNotEmpty()) { if (tracks.isNotEmpty()) {
BatchUpdateStatement(TrackRecordTable) BatchUpdateStatement(TrackRecordTable)
@@ -447,6 +489,8 @@ object Track {
}.toExecutable() }.toExecutable()
.execute(this@transaction) .execute(this@transaction)
} }
tracks.map { it.id!! }
} }
fun insertTrackRecord(track: Track): Int = insertTrackRecords(listOf(track)).first() fun insertTrackRecord(track: Track): Int = insertTrackRecords(listOf(track)).first()